From b931fd7f2e5b4837ffe4185b8942d579ce8b78a4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:43:27 +0100 Subject: [PATCH 0001/1178] refactor(api/hooks.js): improve ready detection --- api/hooks.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/api/hooks.js b/api/hooks.js index 7b99a49c50..d282a90622 100644 --- a/api/hooks.js +++ b/api/hooks.js @@ -135,7 +135,6 @@ function proxyGlobalEvents (global, target) { // state let isGlobalLoaded = false -let isRuntimeInitialized = false export const RUNTIME_INIT_EVENT_NAME = '__runtime_init__' @@ -277,7 +276,7 @@ export class Hooks extends EventTarget { * @type {boolean} */ get isRuntimeReady () { - return isRuntimeInitialized + return Boolean(globalThis.__RUNTIME_INIT_NOW__) } /** @@ -325,14 +324,12 @@ export class Hooks extends EventTarget { const { isWorkerContext, document, global } = this const readyState = document?.readyState - isRuntimeInitialized = Boolean(global.__RUNTIME_INIT_NOW__) - proxyGlobalEvents(global, this) // if runtime is initialized, then 'DOMContentLoaded' (document), // 'load' (window), and the 'init' (window) events have all been dispatched // prior to hook initialization - if (isRuntimeInitialized) { + if (this.isRuntimeReady) { dispatchLoadEvent(this) dispatchInitEvent(this) dispatchReadyEvent(this) @@ -340,7 +337,6 @@ export class Hooks extends EventTarget { } addEventListenerOnce(global, RUNTIME_INIT_EVENT_NAME, () => { - isRuntimeInitialized = true dispatchInitEvent(this) dispatchReadyEvent(this) }) From 63abdf0d4c6e732a566e056fdea31bb59c301692 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:43:48 +0100 Subject: [PATCH 0002/1178] refactor(api/internal/init.js): improve runtime worker init --- api/internal/init.js | 84 ++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index d6358d031a..e3ff8dabff 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -1,4 +1,4 @@ -/* global ArrayBuffer, Blob, DataTransfer, DragEvent, FileList */ +/* global ArrayBuffer, Blob, DataTransfer, DragEvent, FileList, MessageEvent, reportError */ /* eslint-disable import/first */ // mark when runtime did init console.assert( @@ -217,7 +217,7 @@ class RuntimeWorker extends GlobalWorker { Object.defineProperty(globalThis, 'RUNTIME_WORKER_LOCATION', { configurable: false, enumerable: false, - value: '${url}' + value: decodeURIComponent('${url}') }) Object.defineProperty(globalThis, 'RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG', { @@ -305,41 +305,43 @@ class RuntimeWorker extends GlobalWorker { } }) - this.addEventListener('message', (event) => { - const { data } = event - if (data?.__runtime_worker_ipc_request) { - const request = data.__runtime_worker_ipc_request - if ( - typeof request?.message === 'string' && - request.message.startsWith('ipc://') - ) { - queueMicrotask(async () => { - try { - // eslint-disable-next-line no-use-before-define - const message = ipc.Message.from(request.message, request.bytes) - const options = { bytes: message.bytes } - // eslint-disable-next-line no-use-before-define - const result = await ipc.request(message.name, message.rawParams, options) - const transfer = [] - - if (ArrayBuffer.isView(result.data) || result.data instanceof ArrayBuffer) { - transfer.push(result.data) - } - - this.postMessage({ - __runtime_worker_ipc_result: { - message: message.toJSON(), - result: result.toJSON() + queueMicrotask(() => { + addEventListener('message', (event) => { + const { data } = event + if (data?.__runtime_worker_ipc_request) { + const request = data.__runtime_worker_ipc_request + if ( + typeof request?.message === 'string' && + request.message.startsWith('ipc://') + ) { + queueMicrotask(async () => { + try { + // eslint-disable-next-line no-use-before-define + const message = ipc.Message.from(request.message, request.bytes) + const options = { bytes: message.bytes } + // eslint-disable-next-line no-use-before-define + const result = await ipc.request(message.name, message.rawParams, options) + const transfer = [] + + if (ArrayBuffer.isView(result.data) || result.data instanceof ArrayBuffer) { + transfer.push(result.data) } - }, transfer) - } catch (err) { - console.warn('RuntimeWorker:', err) - } - }) + + this.postMessage({ + __runtime_worker_ipc_result: { + message: message.toJSON(), + result: result.toJSON() + } + }, transfer) + } catch (err) { + console.warn('RuntimeWorker:', err) + } + }) + } + } else { + return eventTarget.dispatchEvent(new MessageEvent(event.type, event)) } - } else { - return eventTarget.dispatchEvent(event) - } + }) }) } @@ -429,8 +431,6 @@ import ipc from '../ipc.js' import '../console.js' -const isWorkerLike = !globalThis.window && globalThis.self && globalThis.postMessage - class ConcurrentQueue extends EventTarget { concurrency = Infinity pending = [] @@ -534,11 +534,9 @@ hooks.onLoad(() => { // async preload modules hooks.onReady(async () => { try { - if (!isWorkerLike) { - // precache fs.constants - await ipc.request('fs.constants', {}, { cache: true }) - } - + // precache fs.constants + await ipc.request('fs.constants', {}, { cache: true }) + await import('../worker.js') await import('../diagnostics.js') await import('../fs/fds.js') await import('../fs/constants.js') @@ -557,6 +555,8 @@ Object.defineProperty(globalThis, '__globals', { value: globals }) +ipc.send('platform.event', 'runtimeinit').catch(reportError) + export default { location } From 652fb0d762301f6fc4e581edb4fedde8328a1cff Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:44:07 +0100 Subject: [PATCH 0003/1178] fix(api/internal/worker.js): fix message backlog and dispatch --- api/internal/worker.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/api/internal/worker.js b/api/internal/worker.js index 224644c590..bfeebf748a 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -1,4 +1,4 @@ -/* global reportError, EventTarget, CustomEvent */ +/* global reportError, EventTarget, CustomEvent, MessageEvent */ import './init.js' import { rand64 } from '../crypto.js' @@ -95,8 +95,8 @@ if (source && typeof source === 'string') { // @ts-ignore await import(source) if (Array.isArray(globalThis.RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG)) { - for (const message of globalThis.RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG) { - globalThis.dispatchEvent(message) + for (const event of globalThis.RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG) { + globalThis.dispatchEvent(new MessageEvent(event.type, event)) } globalThis.RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG.splice( @@ -216,7 +216,7 @@ export async function onWorkerMessage (event) { return false } - return dispatchEvent(event) + return dispatchEvent(new MessageEvent(event.type, event)) } export function addEventListener (eventName, callback, ...args) { @@ -240,9 +240,10 @@ export function removeEventListener (eventName, callback, ...args) { } export function dispatchEvent (event) { - if (hooks.globalEvents.includes(event.type)) { + if (event.type !== 'message') { return worker.dispatchEvent(event) } + return workerGlobalScopeEventTarget.dispatchEvent(event) } From aba86fdec9ba3e76a4f1637209e0512345ac4c09 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:44:43 +0100 Subject: [PATCH 0004/1178] refactor(api/ipc.js): improve error factory and platform event emit --- api/ipc.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 4dd0058d27..615d8b1d4a 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -33,7 +33,7 @@ * ``` */ -/* global webkit, chrome, external */ +/* global webkit, chrome, external, reportError */ import { AbortError, InternalError, @@ -303,6 +303,7 @@ export function maybeMakeError (error, caller) { GeolocationPositionError: getErrorClass('GeolocationPositionError'), IndexSizeError: getErrorClass('IndexSizeError'), InternalError, + DOMException: getErrorClass('DOMContentLoaded'), InvalidAccessError: getErrorClass('InvalidAccessError'), NetworkError: getErrorClass('NetworkError'), NotAllowedError: getErrorClass('NotAllowedError'), @@ -331,7 +332,7 @@ export function maybeMakeError (error, caller) { delete error.type if (type in errors) { - err = new errors[type](error.message || '') + err = new errors[type](error.message || '', error.code) } else { for (const E of Object.values(errors)) { if ((E.code && type === E.code) || (code && code === E.code)) { @@ -1470,15 +1471,25 @@ Object.freeze(primordials) initializeXHRIntercept() if (typeof globalThis?.window !== 'undefined') { - document.addEventListener('DOMContentLoaded', () => { + if (globalThis.document.readyState === 'complete') { queueMicrotask(async () => { try { await send('platform.event', 'domcontentloaded') } catch (err) { - console.error('ERR:', err) + reportError(err) } }) - }) + } else { + globalThis.document.addEventListener('DOMContentLoaded', () => { + queueMicrotask(async () => { + try { + await send('platform.event', 'domcontentloaded') + } catch (err) { + reportError(err) + } + }) + }) + } } // eslint-disable-next-line From 5c11004dfd67761ac64ed66d475cda5236878670 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:45:01 +0100 Subject: [PATCH 0005/1178] refactor(src/window/window.hh): define service worker window (desktop only) --- src/window/window.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/window/window.hh b/src/window/window.hh index 134872e58d..cb45b32bd3 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -15,6 +15,8 @@ #define SSC_MAX_WINDOWS 32 #endif +#define SSC_SERVICE_WORKER_CONTAINER_WINDOW_INDEX SSC_MAX_WINDOWS + 1 + #ifndef SSC_MAX_WINDOWS_RESERVED #define SSC_MAX_WINDOWS_RESERVED 16 #endif From 0da902303d3c07a81228f7aca2ae407268a70c01 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:46:16 +0100 Subject: [PATCH 0006/1178] refactor(src/window/apple.mm): improve router/bridge 'isReady' state --- src/ipc/ipc.hh | 4 +++- src/window/apple.mm | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ipc/ipc.hh b/src/ipc/ipc.hh index 4f37c264b3..6e6eb73c93 100644 --- a/src/ipc/ipc.hh +++ b/src/ipc/ipc.hh @@ -263,12 +263,12 @@ namespace SSC::IPC { EvaluateJavaScriptCallback evaluateJavaScriptFunction = nullptr; std::function dispatchFunction = nullptr; BufferMap buffers; - bool isReady = false; Mutex mutex; Table table; Listeners listeners; Core *core = nullptr; Bridge *bridge = nullptr; + AtomicBool isReady = false; #if defined(__APPLE__) SSCIPCNetworkStatusObserver* networkStatusObserver = nullptr; SSCLocationObserver* locationObserver = nullptr; @@ -320,6 +320,8 @@ namespace SSC::IPC { Router router; Bluetooth bluetooth; Core *core = nullptr; + uint64_t id = 0; + // AtomicBool isReady = false; #if !defined(__ANDROID__) && (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR)) FileSystemWatcher* fileSystemWatcher = nullptr; #endif diff --git a/src/window/apple.mm b/src/window/apple.mm index c90b435a28..236ef20852 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -59,11 +59,13 @@ - (void) webView: (WKWebView*) webview } if (request.starts_with("socket:")) { + self.bridge->router.isReady = false; decisionHandler(WKNavigationActionPolicyAllow); return; } if (request.starts_with(devHost)) { + self.bridge->router.isReady = false; decisionHandler(WKNavigationActionPolicyAllow); return; } @@ -72,6 +74,7 @@ - (void) webView: (WKWebView*) webview return; } + self.bridge->router.isReady = false; decisionHandler(WKNavigationActionPolicyAllow); } @@ -893,7 +896,7 @@ - (void) webView: (WKWebView*) webView debug("Failed to set preference: 'offlineApplicationCacheIsEnabled': %@", error); } - WKUserContentController* controller = [config userContentController]; + WKUserContentController* controller = config.userContentController; // Add preload script, normalizing the interface to be cross-platform. SSC::String preload = createPreload(opts); @@ -901,14 +904,14 @@ - (void) webView: (WKWebView*) webView WKUserScript* userScript = [WKUserScript alloc]; [userScript - initWithSource: [NSString stringWithUTF8String:preload.c_str()] - injectionTime: WKUserScriptInjectionTimeAtDocumentStart + initWithSource: @(preload.c_str()) + injectionTime: WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly: NO ]; [controller addUserScript: userScript]; - webview = [[SSCBridgedWebView alloc] + webview = [SSCBridgedWebView.alloc initWithFrame: NSZeroRect configuration: config ]; From 3613ec6c90ef0a0642e447e5e59eb750fdfde547 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:47:57 +0100 Subject: [PATCH 0007/1178] refactor(src/desktop/main.cc): create service worker window on desktop --- src/desktop/main.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 00ae007012..f0eea518bb 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1433,6 +1433,21 @@ MAIN { defaultWindow->show(EMPTY_SEQ); + if (app.appData["permissions_allow_service_worker"] != "false") { + auto serviceWorkerWindow = windowManager.createWindow({ + .canExit = false, + .index = SSC_SERVICE_WORKER_CONTAINER_WINDOW_INDEX, + // .headless = true, + }); + + app.core->serviceWorker.init(serviceWorkerWindow->bridge); + serviceWorkerWindow->show(EMPTY_SEQ); + serviceWorkerWindow->navigate( + EMPTY_SEQ, + "socket://" + app.appData["meta_bundle_identifier"] + "/socket/service-worker/index.html" + ); + } + if (_port > 0) { defaultWindow->navigate(EMPTY_SEQ, _host + ":" + std::to_string(_port)); defaultWindow->setSystemMenu(EMPTY_SEQ, String( From acc67648e0af7252d2605bf5861781113a16d5b9 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:48:08 +0100 Subject: [PATCH 0008/1178] chore(src/core/file_system_watcher.hh): clean up --- src/core/file_system_watcher.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/file_system_watcher.hh b/src/core/file_system_watcher.hh index e19673c5ea..b320ac090a 100644 --- a/src/core/file_system_watcher.hh +++ b/src/core/file_system_watcher.hh @@ -1,5 +1,5 @@ -#ifndef SSC_FILE_SYSTEM_WATCHER -#define SSC_FILE_SYSTEM_WATCHER +#ifndef SSC_FILE_SYSTEM_WATCHER_H +#define SSC_FILE_SYSTEM_WATCHER_H #include "platform.hh" #include "types.hh" From 736fdcbffdec612c9f76d7a2145e8837264dbf84 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:48:31 +0100 Subject: [PATCH 0009/1178] refactor(src/cli/cli.cc): include 'src/core/service_worker_container.hh' --- src/cli/cli.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 2e79efd3e1..e7fce7e085 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -3204,6 +3204,7 @@ int main (const int argc, const char* argv[]) { fs::copy(trim(prefixFile("src/core/json.hh")), jni / "core", fs::copy_options::overwrite_existing); fs::copy(trim(prefixFile("src/core/platform.hh")), jni / "core", fs::copy_options::overwrite_existing); fs::copy(trim(prefixFile("src/core/preload.hh")), jni / "core", fs::copy_options::overwrite_existing); + fs::copy(trim(prefixFile("src/core/service_worker_container.hh")), jni / "core", fs::copy_options::overwrite_existing); fs::copy(trim(prefixFile("src/core/string.hh")), jni / "core", fs::copy_options::overwrite_existing); fs::copy(trim(prefixFile("src/core/types.hh")), jni / "core", fs::copy_options::overwrite_existing); fs::copy(trim(prefixFile("src/core/version.hh")), jni / "core", fs::copy_options::overwrite_existing); From 53a9df60fc36f95a70e199589778d1783af4ddd0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:49:15 +0100 Subject: [PATCH 0010/1178] feat(core/service_worker_container): initial core service worker container --- src/core/core.hh | 7 +- src/core/service_worker_container.cc | 246 +++++++++++++++++++++++++++ src/core/service_worker_container.hh | 88 ++++++++++ 3 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 src/core/service_worker_container.cc create mode 100644 src/core/service_worker_container.hh diff --git a/src/core/core.hh b/src/core/core.hh index 7b5d65f006..b1c45d8696 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -10,6 +10,7 @@ #include "json.hh" #include "platform.hh" #include "preload.hh" +#include "service_worker_container.hh" #include "string.hh" #include "types.hh" #include "version.hh" @@ -47,7 +48,7 @@ namespace SSC { String FormatError (DWORD error, String source); #endif - + // forward class Core; @@ -719,6 +720,7 @@ namespace SSC { ); }; + ServiceWorkerContainer serviceWorker; Diagnostics diagnostics; DNS dns; FS fs; @@ -765,7 +767,8 @@ namespace SSC { fs(this), os(this), platform(this), - udp(this) + udp(this), + serviceWorker(this) { this->posts = std::shared_ptr(new Posts()); initEventLoop(); diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc new file mode 100644 index 0000000000..ea3ebbfdad --- /dev/null +++ b/src/core/service_worker_container.cc @@ -0,0 +1,246 @@ +#include "service_worker_container.hh" + +#include "../ipc/ipc.hh" + +namespace SSC { + static IPC::Bridge* serviceWorkerBridge = nullptr; + + const JSON::Object ServiceWorkerContainer::Registration::json () const { + String stateString = "registered"; + + if (this->state == Registration::State::Installing) { + stateString = "installing"; + } else if (this->state == Registration::State::Installed) { + stateString = "installed"; + } else if (this->state == Registration::State::Activating) { + stateString = "activating"; + } else if (this->state == Registration::State::Activated) { + stateString = "activated"; + } + + return JSON::Object::Entries { + {"id", std::to_string(this->id)}, + {"scriptURL", this->scriptURL}, + {"scope", this->options.scope}, + {"state", stateString} + }; + } + + ServiceWorkerContainer::ServiceWorkerContainer (Core* core) { + this->core = core; + } + + ServiceWorkerContainer::~ServiceWorkerContainer () { + if (this->bridge != nullptr) { + this->bridge->router.emit("serviceWorker.destroy", "{}"); + } + + this->core = nullptr; + this->bridge = nullptr; + this->registrations.clear(); + } + + void ServiceWorkerContainer::init (IPC::Bridge* bridge) { + this->bridge = bridge; + this->bridge->router.map("serviceWorker.fetch.response", [this](auto message, auto router, auto reply) mutable { + uint64_t clientId = 0; + uint64_t id = 0; + int statusCode = 200; + + try { + id = std::stoull(message.get("id")); + } catch (...) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"message", "Invalid 'id' given in parameters"} + }}); + } + + try { + clientId = std::stoull(message.get("clientId")); + } catch (...) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"message", "Invalid 'clientId' given in parameters"} + }}); + } + + if (!this->fetches.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchCallback'"} + }}); + } + + const auto callback = this->fetches.at(id); + + try { + statusCode = std::stoi(message.get("statusCode")); + } catch (...) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"message", "Invalid 'statusCode' given in parameters"} + }}); + } + + const auto headers = split(message.get("headers"), '\n'); + const auto response = FetchResponse { + id, + statusCode, + headers, + { message.buffer.size, message.buffer.bytes }, + { clientId } + }; + + this->fetches.erase(id); + callback(response); + }); + } + + const ServiceWorkerContainer::Registration ServiceWorkerContainer::registerServiceWorker ( + const RegistrationOptions& options + ) { + if (this->registrations.contains(options.scope)) { + auto& registration = this->registrations.at(options.scope); + + if (this->bridge != nullptr) { + this->bridge->router.emit("serviceWorker.register", registration.json().str()); + } + + return registration; + } + + const auto id = rand64(); + const auto registration = Registration { + id, + options.scriptURL, + Registration::State::Registered, + options + }; + + this->registrations.insert_or_assign(options.scope, registration); + + if (this->bridge != nullptr) { + this->bridge->router.emit("serviceWorker.register", registration.json().str()); + } + + return registration; + } + + bool ServiceWorkerContainer::unregisterServiceWorker (String scopeOrScriptURL) { + const auto& scope = scopeOrScriptURL; + const auto& scriptURL = scopeOrScriptURL; + + if (this->registrations.contains(scope)) { + const auto registration = this->registrations.at(scope); + this->registrations.erase(scope); + if (this->bridge != nullptr) { + return this->bridge->router.emit("serviceWorker.unregister", registration.json().str()); + } + } + + for (const auto& entry : this->registrations) { + if (entry.second.scriptURL == scriptURL) { + const auto registration = this->registrations.at(entry.first); + this->registrations.erase(entry.first); + if (this->bridge != nullptr) { + return this->bridge->router.emit("serviceWorker.unregister", registration.json().str()); + } + } + } + + return false; + } + + bool ServiceWorkerContainer::unregisterServiceWorker (uint64_t id) { + for (const auto& entry : this->registrations) { + if (entry.second.id == id) { + return this->unregisterServiceWorker(entry.first); + } + } + + return false; + } + + void ServiceWorkerContainer::skipWaiting (uint64_t id) { + for (auto& entry : this->registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + if ( + registration.state == Registration::State::Installing || + registration.state == Registration::State::Installed + ) { + registration.state = Registration::State::Activating; + + if (this->bridge != nullptr) { + this->bridge->router.emit("serviceWorker.skipWaiting", registration.json().str()); + } + } + break; + } + } + } + + void ServiceWorkerContainer::updateState (uint64_t id, const String& stateString) { + for (auto& entry : this->registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + if (stateString == "error") { + registration.state = Registration::State::Error; + } else if (stateString == "registered") { + registration.state = Registration::State::Registered; + } else if (stateString == "installing") { + registration.state = Registration::State::Installing; + } else if (stateString == "installed") { + registration.state = Registration::State::Installed; + } else if (stateString == "activating") { + registration.state = Registration::State::Activating; + } else if (stateString == "activated") { + registration.state = Registration::State::Activated; + } else { + break; + } + + if (this->bridge != nullptr) { + this->bridge->router.emit("serviceWorker.updateState", registration.json().str()); + } + + break; + } + } + } + + bool ServiceWorkerContainer::fetch (FetchRequest request, FetchCallback callback) { + if (this->bridge == nullptr) { + return false; + } + + for (const auto& entry : this->registrations) { + const auto& registration = entry.second; + if (request.pathname.starts_with(registration.options.scope)) { + auto headers = JSON::Array {}; + + for (const auto& header : request.headers) { + headers.push(header); + } + + const auto id = rand64(); + const auto client = JSON::Object::Entries { + {"id", std::to_string(request.client.id)} + }; + + const auto fetch = JSON::Object::Entries { + {"id", std::to_string(id)}, + {"pathname", request.pathname}, + {"query", request.query}, + {"headers", headers}, + {"client", client} + }; + + auto json = registration.json(); + json.set("fetch", fetch); + this->fetches.insert_or_assign(id, callback); + return this->bridge->router.emit("serviceWorker.fetch", json.str()); + } + } + + return false; + } +} diff --git a/src/core/service_worker_container.hh b/src/core/service_worker_container.hh new file mode 100644 index 0000000000..1d4effb552 --- /dev/null +++ b/src/core/service_worker_container.hh @@ -0,0 +1,88 @@ +#ifndef SSC_SERVICE_WORKER_CONTAINER_H +#define SSC_SERVICE_WORKER_CONTAINER_H + +#include "platform.hh" +#include "types.hh" +#include "json.hh" + +namespace SSC { + // forward + namespace IPC { class Bridge; } + class Core; + + class ServiceWorkerContainer { + public: + struct RegistrationOptions { + enum class Type { Classic, Module }; + + Type type = Type::Module; + String scope; + String scriptURL; + }; + + struct Client { + uint64_t id = 0; + }; + + struct Registration { + enum class State { + Error, + Registered, + Installing, + Installed, + Activating, + Activated + }; + + uint64_t id = 0; + String scriptURL; + State state = State::Registered; + RegistrationOptions options; + Vector clients; + const SSC::JSON::Object json () const; + }; + + struct FetchBuffer { + size_t size = 0; + char* bytes = nullptr; + }; + + struct FetchRequest { + String pathname; + String query; + Vector headers; + Client client; + }; + + struct FetchResponse { + uint64_t id = 0; + int statusCode = 200; + Vector headers; + FetchBuffer buffer; + Client client; + }; + + using Registrations = std::map; + using FetchCallback = std::function; + using FetchCallbacks = std::map; + + Core* core = nullptr; + IPC::Bridge* bridge = nullptr; + Registrations registrations; + FetchCallbacks fetches; + + ServiceWorkerContainer (Core* core); + ~ServiceWorkerContainer (); + + void init (IPC::Bridge* bridge); + const Registration registerServiceWorker (const RegistrationOptions& options); + bool unregisterServiceWorker (uint64_t id); + bool unregisterServiceWorker (String scopeOrScriptURL); + void skipWaiting (uint64_t id); + void updateState (uint64_t id, const String& stateString); + + bool fetch (FetchRequest request, FetchCallback callback); + }; +} + +#endif From e280c7ffa0a03b63e1518ac102414bd7340fc3bb Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:49:32 +0100 Subject: [PATCH 0011/1178] refactor(src/ios/main.mm): configure serviec worker --- src/ios/main.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ios/main.mm b/src/ios/main.mm index 0d1d2e69fe..7560967150 100644 --- a/src/ios/main.mm +++ b/src/ios/main.mm @@ -270,6 +270,7 @@ - (BOOL) application: (UIApplication*) application }); }; + core->serviceWorker.init(bridge); auto appFrame = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame: appFrame]; From a4aa24e583fcaf04222aa8d1a2cd97cc45167734 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:52:39 +0100 Subject: [PATCH 0012/1178] refactor(src/android): configure service worker on android window --- src/android/window.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/android/window.cc b/src/android/window.cc index 8fa7f0b84c..9d4b42e0d8 100644 --- a/src/android/window.cc +++ b/src/android/window.cc @@ -16,6 +16,7 @@ namespace SSC::android { this->bridge = bridge; this->config = SSC::getUserConfig(); this->pointer = reinterpret_cast(this); + this->bridge->runtime->serviceWorker.init(reinterpret_cast(this->bridge)); StringStream stream; From eddedf0c967fb747ed54376c49bf6f355f149670 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:56:30 +0100 Subject: [PATCH 0013/1178] feat(api/service-worker): initial runtime service worker API --- api/README.md | 4 +- api/index.d.ts | 135 ++++++++++++++++- api/internal/monkeypatch.js | 74 ++++++---- api/internal/service-worker.js | 4 + api/service-worker/clients.js | 45 ++++++ api/service-worker/container.js | 218 ++++++++++++++++++++++++++++ api/service-worker/events.js | 129 ++++++++++++++++ api/service-worker/global.js | 126 ++++++++++++++++ api/service-worker/index.html | 21 +++ api/service-worker/init.js | 89 ++++++++++++ api/service-worker/instance.js | 109 ++++++++++++++ api/service-worker/registration.js | 113 ++++++++++++++ api/service-worker/shared-worker.js | 62 ++++++++ api/service-worker/state.js | 114 +++++++++++++++ api/service-worker/worker.js | 203 ++++++++++++++++++++++++++ api/vm/worker.js | 8 +- 16 files changed, 1416 insertions(+), 38 deletions(-) create mode 100644 api/internal/service-worker.js create mode 100644 api/service-worker/clients.js create mode 100644 api/service-worker/container.js create mode 100644 api/service-worker/events.js create mode 100644 api/service-worker/global.js create mode 100644 api/service-worker/index.html create mode 100644 api/service-worker/init.js create mode 100644 api/service-worker/instance.js create mode 100644 api/service-worker/registration.js create mode 100644 api/service-worker/shared-worker.js create mode 100644 api/service-worker/state.js create mode 100644 api/service-worker/worker.js diff --git a/api/README.md b/api/README.md index 6017d619ca..eebadeca32 100644 --- a/api/README.md +++ b/api/README.md @@ -1438,7 +1438,7 @@ Watch for changes at `path` calling `callback` This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1100) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1101) Emit event to be dispatched on `window` object. @@ -1449,7 +1449,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1159) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1160) Sends an async IPC command request with parameters. diff --git a/api/index.d.ts b/api/index.d.ts index 1794c5dc5b..b25b37a6e1 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -8925,6 +8925,54 @@ declare module "socket:internal/geolocation" { } export default _default; } +declare module "socket:service-worker/state" { + export const channel: BroadcastChannel; + export const state: any; + export default state; +} +declare module "socket:service-worker/instance" { + export function createServiceWorker(currentState?: any): any; + export const SHARED_WORKER_URL: URL; + const _default: any; + export default _default; +} +declare module "socket:service-worker/registration" { + export class ServiceWorkerRegistration { + constructor(info: any, serviceWorker: any); + get scope(): any; + get updateViaCache(): string; + get installing(): any; + get waiting(): any; + get active(): any; + set onupdatefound(onupdatefound: any); + get onupdatefound(): any; + get navigationPreload(): any; + getNotifications(): Promise; + showNotification(): Promise; + unregister(): Promise; + update(): Promise; + #private; + } + export default ServiceWorkerRegistration; +} +declare module "socket:service-worker/container" { + export class ServiceWorkerContainer extends EventTarget { + init(): void; + register(scriptURL: any, options?: any): Promise; + getRegistration(clientURL: any): Promise; + getRegistrations(): Promise; + get ready(): any; + get controller(): any; + startMessages(): void; + } + export default ServiceWorkerContainer; + import { ServiceWorkerRegistration } from "socket:service-worker/registration"; +} +declare module "socket:internal/service-worker" { + export const serviceWorker: ServiceWorkerContainer; + export default serviceWorker; + import { ServiceWorkerContainer } from "socket:service-worker/container"; +} declare module "socket:internal/webassembly" { /** * The `instantiateStreaming()` function compiles and instantiates a WebAssembly @@ -9111,6 +9159,88 @@ declare module "socket:internal/worker" { } export default _default; } +declare module "socket:service-worker/clients" { + export class Client { + postMessage(message: any, optionsOrTransferables?: any): any; + #private; + } + export class WindowClient extends Client { + focus(): void; + navigate(): void; + } + export class Clients { + get(id: any): Promise; + matchAll(): Promise; + openWindow(): Promise; + claim(): Promise; + } + export default Clients; +} +declare module "socket:service-worker/events" { + export class ExtendableEvent extends Event { + waitUntil(promise: any): void; + waitsFor(): Promise; + get pendingPromises(): number; + get isActive(): boolean; + #private; + } + export class FetchEvent extends ExtendableEvent { + constructor(type?: string, options?: any); + get handled(): any; + get request(): any; + get clientId(): any; + get isReload(): boolean; + get preloadResponse(): Promise; + respondWith(response: any): void; + #private; + } + namespace _default { + export { ExtendableEvent }; + export { FetchEvent }; + } + export default _default; +} +declare module "socket:service-worker/global" { + export class ServiceWorkerGlobalScope { + get ExtendableEvent(): typeof ExtendableEvent; + get FetchEvent(): typeof FetchEvent; + get serviceWorker(): any; + set registration(value: any); + get registration(): any; + get clients(): Clients; + set onactivate(listener: any); + get onactivate(): any; + set onmessage(listener: any); + get onmessage(): any; + set oninstall(listener: any); + get oninstall(): any; + set onfetch(listener: any); + get onfetch(): any; + skipWaiting(): Promise; + } + const _default: ServiceWorkerGlobalScope; + export default _default; + import { ExtendableEvent } from "socket:service-worker/events"; + import { FetchEvent } from "socket:service-worker/events"; + import { Clients } from "socket:service-worker/clients"; +} +declare module "socket:service-worker/init" { + const _default: any; + export default _default; +} +declare function isTypedArray(object: any): boolean; +declare function isTypedArray(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; +declare function findMessageTransfers(transfers: any, object: any, options?: any): any; +declare function findMessageTransfers(transfers: any, object: any, options?: any): any; +declare const Uint8ArrayPrototype: Uint8Array; +declare const TypedArrayPrototype: any; +declare const TypedArray: any; +declare const ports: any[]; +declare module "socket:service-worker/worker" { + export {}; +} declare module "socket:test/harness" { /** * @typedef {import('./index').Test} Test @@ -9230,10 +9360,11 @@ declare module "socket:test/harness" { declare module "socket:vm/init" { export {}; } -declare function reportError(e: any): void; -declare function reportError(err: any): void; +declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; +declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; declare const TypedArrayPrototype: any; diff --git a/api/internal/monkeypatch.js b/api/internal/monkeypatch.js index c8eb17b9f4..ca43e4eee9 100644 --- a/api/internal/monkeypatch.js +++ b/api/internal/monkeypatch.js @@ -1,6 +1,7 @@ /* global MutationObserver */ import { fetch, Headers, Request, Response } from '../fetch.js' import { URL, URLPattern, URLSearchParams } from '../url.js' +import serviceWorker from './service-worker.js' import SharedWorker from './shared-worker.js' import Notification from '../notification.js' import geolocation from './geolocation.js' @@ -34,7 +35,7 @@ const natives = {} const patches = {} export function init () { - if (applied || !globalThis.window) { + if (applied || globalThis.self !== globalThis) { return { natives, patches } } @@ -76,7 +77,11 @@ export function init () { if (nativeImplementation !== implementation) { // let this fail, the environment implementation may not be writable try { - target[actualName] = implementation + Object.defineProperty( + target, + actualName, + Object.getOwnPropertyDescriptor(implementations, actualName) + ) } catch {} patches[actualName] = implementation @@ -188,42 +193,57 @@ export function init () { isSocketRuntime: true }) - // navigator - install({ geolocation, permissions }, globalThis.navigator, 'navigator') + if (globalThis.navigator) { + // environment navigator + install({ + geolocation, + permissions, + serviceWorker + }, globalThis.navigator, 'navigator') + + // manually install 'navigator.serviceWorker' accessors from prototype + Object.defineProperties( + globalThis.navigator.serviceWorker, + Object.getOwnPropertyDescriptors(Object.getPrototypeOf(serviceWorker)) + ) + + serviceWorker.init.call(globalThis.navigator.serviceWorker) + } // WebAssembly install(WebAssembly, globalThis.WebAssembly, 'WebAssembly') applied = true - // create tag in document if it doesn't exist - globalThis.document.title ||= '' - // initial value - globalThis.document.addEventListener('DOMContentLoaded', () => { - const title = globalThis.document.title - if (title.length !== 0) { - const index = globalThis.__args.index - const o = new URLSearchParams({ value: title, index }).toString() - ipc.postMessage(`ipc://window.setTitle?${o}`) - } - }) - // - // globalThis.document is uncofigurable property so we need to use MutationObserver here - // - const observer = new MutationObserver((mutationList) => { - for (const mutation of mutationList) { - if (mutation.type === 'childList') { + if (globalThis.document) { + // create <title> tag in document if it doesn't exist + globalThis.document.title ||= '' + // initial value + globalThis.document.addEventListener('DOMContentLoaded', () => { + const title = globalThis.document.title + if (title.length !== 0) { const index = globalThis.__args.index - const title = mutation.addedNodes[0].textContent const o = new URLSearchParams({ value: title, index }).toString() ipc.postMessage(`ipc://window.setTitle?${o}`) } - } - }) + }) + + // globalThis.document is unconfigurable property so we need to use MutationObserver here + const observer = new MutationObserver((mutationList) => { + for (const mutation of mutationList) { + if (mutation.type === 'childList') { + const index = globalThis.__args.index + const title = mutation.addedNodes[0].textContent + const o = new URLSearchParams({ value: title, index }).toString() + ipc.postMessage(`ipc://window.setTitle?${o}`) + } + } + }) - const titleElement = document.querySelector('head > title') - if (titleElement) { - observer.observe(titleElement, { childList: true }) + const titleElement = document.querySelector('head > title') + if (titleElement) { + observer.observe(titleElement, { childList: true }) + } } return { natives, patches } diff --git a/api/internal/service-worker.js b/api/internal/service-worker.js new file mode 100644 index 0000000000..ebe3806f5d --- /dev/null +++ b/api/internal/service-worker.js @@ -0,0 +1,4 @@ +import { ServiceWorkerContainer } from '../service-worker/container.js' + +export const serviceWorker = new ServiceWorkerContainer() +export default serviceWorker diff --git a/api/service-worker/clients.js b/api/service-worker/clients.js new file mode 100644 index 0000000000..67a9d8b913 --- /dev/null +++ b/api/service-worker/clients.js @@ -0,0 +1,45 @@ +import { SHARED_WORKER_URL } from './instance.js' +import { SharedWorker } from '../internal/shared-worker.js' +import state from './state.js' +import ipc from '../ipc.js' + +export class Client { + #id = null + #url = null + #type = null + #frameType = null + #sharedWorker = null + + constructor () { + this.#sharedWorker = new SharedWorker(SHARED_WORKER_URL) + this.#sharedWorker.port.start() + } + + postMessage (message, optionsOrTransferables = null) { + return this.#sharedWorker.port.postMessage(message, optionsOrTransferables) + } +} + +export class WindowClient extends Client { + focus () { + } + + navigate () { + } +} + +export class Clients { + async get (id) { + } + + async matchAll () { + } + + async openWindow () { + } + + async claim () { + } +} + +export default Clients diff --git a/api/service-worker/container.js b/api/service-worker/container.js new file mode 100644 index 0000000000..f32da5069d --- /dev/null +++ b/api/service-worker/container.js @@ -0,0 +1,218 @@ +/* global EventTarget */ +import serviceWorker, { createServiceWorker, SHARED_WORKER_URL } from './instance.js' +import { ServiceWorkerRegistration } from './registration.js' +import { InvertedPromise } from '../util.js' +import { SharedWorker } from '../internal/shared-worker.js' +import ipc from '../ipc.js' + +class State { + sharedWorker = null + controller = null + channel = new BroadcastChannel('ServiceWorkerContainer') + ready = new InvertedPromise() + + // level 1 events + #oncontrollerchange = null + #onmessageerror = null + #onmessage = null + #onerror = null +} + +const state = new Map() + +export class ServiceWorkerContainer extends EventTarget { + init () { + state.set(this, new State()) + + this.register = this.register.bind(this) + this.getRegistration = this.getRegistration.bind(this) + this.getRegistrations = this.getRegistrations.bind(this) + + Object.defineProperty(this, 'controller', { + configurable: false, + enumerable: true, + get: () => state.get(this).controller + }) + + Object.defineProperty(this, 'oncontrollerchange', { + configurable: false, + enumerable: true, + get: () => state.get(this).oncontrollerchange, + set: (oncontrollerchange) => { + if (state.get(this).oncontrollerchange) { + this.removeEventListener('controllerchange', state.get(this).oncontrollerchange) + } + + state.get(this).oncontrollerchange = null + + if (typeof oncontrollerchange === 'function') { + this.addEventListener('controllerchange', oncontrollerchange) + state.get(this).oncontrollerchange = oncontrollerchange + } + } + }) + + Object.defineProperty(this, 'onmessageerror', { + configurable: false, + enumerable: true, + get: () => state.get(this).onmessageerror, + set: (onmessageerror) => { + if (state.get(this).onmessageerror) { + this.removeEventListener('messageerror', state.get(this).onmessageerror) + } + + state.get(this).onmessageerror = null + + if (typeof onmessageerror === 'function') { + this.addEventListener('messageerror', onmessageerror) + state.get(this).onmessageerror = onmessageerror + } + } + }) + + Object.defineProperty(this, 'onmessage', { + configurable: false, + enumerable: true, + get: () => state.get(this).onmessage, + set: (onmessage) => { + if (state.get(this).onmessage) { + this.removeEventListener('message', state.get(this).onmessage) + } + + state.get(this).onmessage = null + + if (typeof onmessage === 'function') { + this.addEventListener('message', onmessage) + state.get(this).onmessage = onmessage + } + } + }) + + Object.defineProperty(this, 'onerror', { + configurable: false, + enumerable: true, + get: () => state.get(this).onerror, + set: (onerror) => { + if (state.get(this).onerror) { + this.removeEventListener('error', state.get(this).onerror) + } + + state.get(this).onerror = null + + if (typeof onerror === 'function') { + this.addEventListener('error', onerror) + state.get(this).onerror = onerror + } + } + }) + + this.getRegistration().then((registration) => { + if (registration) { + if (registration.active) { + queueMicrotask(() => this.dispatchEvent(new Event('controllerchange'))) + queueMicrotask(() => state.get(this).ready.resolve(registration)) + } else { + serviceWorker.addEventListener('statechange', () => { + if (serviceWorker.state === 'activating' || serviceWorker.state === 'activated') { + queueMicrotask(() => this.dispatchEvent(new Event('controllerchange'))) + queueMicrotask(() => state.get(this).ready.resolve(registration)) + } + }) + } + } + }) + + state.get(this).ready.then((registration) => { + if (registration) { + state.get(this).controller = registration.active + state.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) + } + }) + } + + get ready () { + return state.get(this).ready + } + + get controller () { + return state.get(this).controller + } + + async getRegistration (clientURL) { + const scope = clientURL || new URL(globalThis.location.href).pathname + const result = await ipc.request('serviceWorker.getRegistration', { scope }) + + if (result.err) { + throw result.err + } + + if (result.data?.registration?.id) { + return new ServiceWorkerRegistration(result.data, serviceWorker) + } + } + + async getRegistrations () { + const result = await ipc.request('serviceWorker.getRegistrations') + + if (result.err) { + throw result.err + } + + const registrations = [] + + if (Array.isArray(result.data)) { + for (const registration of result.data) { + const info = { registration } + const serviceWorker = createServiceWorker(registration.state) + registrations.push(new ServiceWorkerRegistration(info, serviceWorker)) + } + } + + return registrations + } + + async register (scriptURL, options = null) { + scriptURL = new URL(scriptURL, globalThis.location.href).toString() + + if (!options || typeof options !== 'object') { + options = {} + } + + if (!options.scope || typeof options.scope !== 'string') { + options.scope = new URL('./', scriptURL).pathname + } + + const result = await ipc.request('serviceWorker.register', { + ...options, + scriptURL + }) + + if (result.err) { + throw result.err + } + + const url = new URL(scriptURL) + + if (url.pathname.startsWith(options.scope)) { + const registration = new ServiceWorkerRegistration(result.data, serviceWorker) + serviceWorker.addEventListener('statechange', () => { + if (serviceWorker.state === 'activating' || serviceWorker.state === 'activated') { + queueMicrotask(() => this.dispatchEvent(new Event('controllerchange'))) + queueMicrotask(() => state.get(this).ready.resolve(registration)) + } + }) + return registration + } + } + + startMessages () { + state.get(this).ready.then(() => { + state.get(this).sharedWorker.port.start() + state.get(this).sharedWorker.port.addEventListener('message', (event) => { + this.dispatchEvent(new MessageEvent(event.type, event)) + }) + }) + } +} + +export default ServiceWorkerContainer diff --git a/api/service-worker/events.js b/api/service-worker/events.js new file mode 100644 index 0000000000..fd14003e84 --- /dev/null +++ b/api/service-worker/events.js @@ -0,0 +1,129 @@ +import { InvertedPromise } from '../util.js' +import state from './state.js' +import ipc from '../ipc.js' + +export class ExtendableEvent extends Event { + #promise = new InvertedPromise() + #promises = [] + #pendingPromiseCount = 0 + + waitUntil (promise) { + // we ignore the isTrusted check here and just verify the event phase + if (this.eventPhase !== Event.AT_TARGET) { + throw new DOMException('Event is not active', 'InvalidStateError') + } + + if (promise && promise instanceof Promise) { + this.#pendingPromiseCount++ + this.#promises.push(promise) + promise.then( + () => queueMicrotask(() => { + if (--this.#pendingPromiseCount === 0) { + this.#promise.resolve() + } + }), + () => queueMicrotask(() => { + if (--this.#pendingPromiseCount === 0) { + this.#promise.resolve() + } + }) + ) + + // handle 0 pending promises + } + } + + async waitsFor () { + if (this.#pendingPromiseCount === 0) { + this.#promise.resolve() + } + + return await this.#promise + } + + get pendingPromises () { + return this.#pendingPromiseCount + } + + get isActive () { + return ( + this.#pendingPromiseCount > 0 || + this.eventPhase === Event.AT_TARGET + ) + } +} + +export class FetchEvent extends ExtendableEvent { + #handled = new InvertedPromise() + #request = null + #clientId = null + #isReload = false + #fetchId = null + + constructor (type = 'fetch', options = null) { + super(type, options) + + this.#fetchId = options?.fetchId ?? null + this.#request = options?.request ?? null + this.#clientId = options?.clientId ?? '' + this.#isReload = options?.isReload === true + } + + get handled () { + return this.#handled.then(Promise.resolve()) + } + + get request () { + return this.#request + } + + get clientId () { + return this.#clientId + } + + get isReload () { + return this.#isReload + } + + get preloadResponse () { + return Promise.resolve(null) + } + + respondWith (response) { + const clientId = this.#clientId + const handled = this.#handled + const id = this.#fetchId + + queueMicrotask(async () => { + try { + response = await response + + const arrayBuffer = await response.arrayBuffer() + const statusCode = response.status ?? 200 + const headers = Array.from(response.headers.entries()) + .map((entry) => entry.join(':')) + .join('\n') + + const options = { statusCode, clientId, headers, id } + const result = await ipc.write( + 'serviceWorker.fetch.response', + options, + new Uint8Array(arrayBuffer) + ) + + if (result.err) { + state.reportError(result.err) + } + + handled.resolve() + } catch (err) { + state.reportError(err) + } + }) + } +} + +export default { + ExtendableEvent, + FetchEvent +} diff --git a/api/service-worker/global.js b/api/service-worker/global.js new file mode 100644 index 0000000000..70f41bb3bf --- /dev/null +++ b/api/service-worker/global.js @@ -0,0 +1,126 @@ +import { ExtendableEvent, FetchEvent } from './events.js' +import { ServiceWorkerRegistration } from './registration.js' +import serviceWorker from './instance.js' +import { Clients } from './clients.js' +import state from './state.js' +import ipc from '../ipc.js' + +const clients = new Clients() + +// events +let onactivate = null +let onmessage = null +let oninstall = null +let onfetch = null + +// this is set one time +let registration = null + +export class ServiceWorkerGlobalScope { + get ExtendableEvent () { + return ExtendableEvent + } + + get FetchEvent () { + return FetchEvent + } + + get serviceWorker () { + return serviceWorker + } + + get registration () { + return registration + } + + set registration (value) { + if (!registration) { + const info = { registration: value } + registration = new ServiceWorkerRegistration(info, serviceWorker) + } + } + + get clients () { + return clients + } + + get onactivate () { + return onactivate + } + + set onactivate (listener) { + if (onactivate) { + globalThis.removeEventListener('activate', onactivate) + } + + onactivate = null + + if (typeof listener === 'function') { + globalThis.addEventListener('activate', listener) + onactivate = listener + } + } + + get onmessage () { + return onmessage + } + + set onmessage (listener) { + if (onmessage) { + globalThis.removeEventListener('message', onmessage) + } + + onmessage = null + + if (typeof listener === 'function') { + globalThis.addEventListener('message', listener) + onmessage = listener + } + } + + get oninstall () { + return oninstall + } + + set oninstall (listener) { + if (oninstall) { + globalThis.removeEventListener('install', oninstall) + } + + oninstall = null + + if (typeof listener === 'function') { + globalThis.addEventListener('install', listener) + oninstall = listener + } + } + + get onfetch () { + return onfetch + } + + set onfetch (listener) { + if (fetch) { + globalThis.removeEventListener('fetch', fetch) + } + + onfetch = null + + if (typeof listener === 'function') { + globalThis.addEventListener('fetch', listener) + onfetch = listener + } + } + + async skipWaiting () { + const result = await ipc.request('serviceWorker.skipWaiting', { + id: state.id + }) + + if (result.err) { + throw result.err + } + } +} + +export default ServiceWorkerGlobalScope.prototype diff --git a/api/service-worker/index.html b/api/service-worker/index.html new file mode 100644 index 0000000000..3668b4e5b6 --- /dev/null +++ b/api/service-worker/index.html @@ -0,0 +1,21 @@ +<!doctype html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta + http-equiv="Content-Security-Policy" + content=" + connect-src http: https: ipc: socket:; + script-src https: blob: socket: 'unsafe-eval' 'unsafe-inline'; + worker-src https: blob: socket: 'unsafe-eval' 'unsafe-inline'; + img-src 'none'; + child-src https: socket: blob:; + object-src 'none'; + " + > + <title> + + + + + diff --git a/api/service-worker/init.js b/api/service-worker/init.js new file mode 100644 index 0000000000..d8c60f10bf --- /dev/null +++ b/api/service-worker/init.js @@ -0,0 +1,89 @@ +/* global Worker */ +import crypto from '../crypto.js' + +const workers = new Map() + +class ServiceWorkerInfo { + id = null + url = null + hash = null + scope = null + scriptURL = null + + constructor (data) { + for (const key in data) { + const value = data[key] + if (key in this) { + this[key] = value + } + } + + this.url = new URL(this.scriptURL) + this.hash = crypto.murmur3(this.url.pathname + (this.scope || '')) + } +} + +globalThis.addEventListener('serviceWorker.register', onRegister) +globalThis.addEventListener('serviceWorker.skipWaiting', onSkipWaiting) +globalThis.addEventListener('serviceWorker.fetch', onFetch) + +async function onRegister (event) { + const info = new ServiceWorkerInfo(event.detail) + + if (!info.id || workers.has(info.hash)) { + return + } + + const worker = new Worker('./worker.js') + + workers.set(info.hash, worker) + worker.addEventListener('message', onHandShakeMessage) + + async function onHandShakeMessage (event) { + if (event.data.__service_worker_ready === true) { + worker.postMessage({ register: info }) + } else if (event.data.__service_worker_registered?.id === info.id) { + worker.postMessage({ install: info }) + worker.removeEventListener('message', onHandShakeMessage) + } + } +} + +async function onSkipWaiting (event) { + const info = new ServiceWorkerInfo(event.detail) + + if (!workers.has(info.hash)) { + return + } + + const worker = workers.get(info.hash) + + worker.postMessage({ activate: info }) +} + +async function onFetch (event) { + const info = new ServiceWorkerInfo(event.detail) + + if (!workers.has(info.hash)) { + return + } + + const client = event.detail.fetch.client ?? {} + const request = { + id: event.detail.fetch.id, + url: new URL( + event.detail.fetch.pathname + '?' + event.detail.fetch.query, + globalThis.origin + ).toString(), + + headers: event.detail.fetch.headers + .map((entry) => entry.split(':')) + .reduce((object, entry) => Object.assign(object, { [entry[0]]: entry[1].trim() }), {}) + } + + const worker = workers.get(info.hash) + + worker.postMessage({ fetch: { ...info, client, request } }) +} + +export default null diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js new file mode 100644 index 0000000000..788de9eaa0 --- /dev/null +++ b/api/service-worker/instance.js @@ -0,0 +1,109 @@ +/* global ServiceWorker */ +import { SharedWorker } from '../internal/shared-worker.js' +import state from './state.js' + +export const SHARED_WORKER_URL = new URL('./shared-worker.js', import.meta.url) + +export function createServiceWorker (currentState = state.serviceWorker.state) { + // events + const sharedWorker = new SharedWorker(SHARED_WORKER_URL) + const eventTarget = new EventTarget() + let onstatechange = null + let onerror = null + + sharedWorker.port.start() + sharedWorker.port.addEventListener('message', (event) => { + eventTarget.dispatchEvent(new MessageEvent('message', event.data)) + }) + + const serviceWorker = Object.create(ServiceWorker.prototype, { + postMessage: { + enumerable: false, + configurable: false, + value (message, ...args) { + sharedWorker.postMessage(message, ...args) + } + }, + + state: { + configurable: true, + enumerable: false, + get: () => currentState + }, + + onerror: { + enumerable: false, + get: () => onerror, + set: (listener) => { + if (onerror) { + eventTarget.removeEventListener('error', onerror) + } + + onerror = null + + if (typeof listener === 'function') { + eventTarget.addEventListener('error', listener) + onerror = listener + } + } + }, + + onstatechange: { + enumerable: false, + get: () => onstatechange, + set: (listener) => { + if (onstatechange) { + eventTarget.removeEventListener('statechange', onstatechange) + } + + onstatechange = null + + if (typeof listener === 'function') { + eventTarget.addEventListener('statechange', listener) + onstatechange = listener + } + } + }, + + dispatchEvent: { + configurable: false, + enumerable: false, + value: eventTarget.dispatchEvent.bind(eventTarget) + }, + + addEventListener: { + configurable: false, + enumerable: false, + value: eventTarget.addEventListener.bind(eventTarget) + }, + + removeEventListener: { + configurable: false, + enumerable: false, + value: eventTarget.removeEventListener.bind(eventTarget) + } + }) + + state.channel.addEventListener('message', (event) => { + const { data } = event + if (data.serviceWorker) { + if (data.serviceWorker.state && data.serviceWorker.state !== currentState) { + const scope = new URL(globalThis.location.href).pathname + if (scope.startsWith(data.serviceWorker.scope)) { + currentState = data.serviceWorker.state + const event = new Event('statechange') + + Object.defineProperties(event, { + target: { value: serviceWorker } + }) + + eventTarget.dispatchEvent(event) + } + } + } + }) + + return serviceWorker +} + +export default createServiceWorker(state.serviceWorker.state) diff --git a/api/service-worker/registration.js b/api/service-worker/registration.js new file mode 100644 index 0000000000..6f09eb1be7 --- /dev/null +++ b/api/service-worker/registration.js @@ -0,0 +1,113 @@ +import ipc from '../ipc.js' + +export class ServiceWorkerRegistration { + #info = null + #active = null + #waiting = null + #installing = null + #onupdatefound = null + + constructor (info, serviceWorker) { + this.#info = info + + if (serviceWorker.state === 'installing' || serviceWorker.state === 'parsed') { + this.#installing = serviceWorker + } else if (serviceWorker.state === 'installed') { + this.#waiting = serviceWorker + } else if (serviceWorker.state === 'activating' || serviceWorker.state === 'activated') { + this.#active = serviceWorker + } + + serviceWorker.addEventListener('statechange', (event) => { + const { state } = event.target + + if (state === 'installing') { + this.#active = null + this.#waiting = null + this.#installing = serviceWorker + } + + if (state === 'installed') { + this.#active = null + this.#waiting = serviceWorker + this.#installing = null + } + + if (state === 'activating' || state === 'activated') { + this.#active = serviceWorker + this.#waiting = null + this.#installing = null + } + }) + } + + get scope () { + return this.#info.registration.scope + } + + get updateViaCache () { + return 'none' + } + + get installing () { + return this.#installing + } + + get waiting () { + return this.#waiting + } + + get active () { + return this.#active + } + + get onupdatefound () { + return this.#onupdatefound + } + + set onupdatefound (onupdatefound) { + if (this.#onupdatefound) { + this.removeEventListener('updatefound', this.#onupdatefound) + } + + this.#onupdatefound = null + + if (typeof onupdatefound === 'function') { + this.addEventListener('updatefound', this.#onupdatefound) + this.#onupdatefound = onupdatefound + } + } + + get navigationPreload () { + return null + } + + async getNotifications () { + return [] + } + + async showNotification () { + } + + async unregister () { + const result = await ipc.request('serviceWorker.unregister', { + scope: this.scope + }) + + if (result.err) { + throw result.err + } + } + + async update () { + } +} + +if (typeof globalThis.ServiceWorkerRegistration === 'function') { + Object.setPrototypeOf( + ServiceWorkerRegistration.prototype, + globalThis.ServiceWorkerRegistration.prototype + ) +} + +export default ServiceWorkerRegistration diff --git a/api/service-worker/shared-worker.js b/api/service-worker/shared-worker.js new file mode 100644 index 0000000000..0028d81dbc --- /dev/null +++ b/api/service-worker/shared-worker.js @@ -0,0 +1,62 @@ +/* global reportError */ +const Uint8ArrayPrototype = Uint8Array.prototype +const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) +const TypedArray = TypedArrayPrototype.constructor + +function isTypedArray (object) { + return object instanceof TypedArray +} + +function isArrayBuffer (object) { + return object instanceof ArrayBuffer +} + +function findMessageTransfers (transfers, object, options = null) { + if (isTypedArray(object) || ArrayBuffer.isView(object)) { + add(object.buffer) + } else if (isArrayBuffer(object)) { + add(object) + } else if (object instanceof MessagePort) { + add(object) + } else if (Array.isArray(object)) { + for (const value of object) { + findMessageTransfers(transfers, value, options) + } + } else if (object && typeof object === 'object') { + for (const key in object) { + if ( + key.startsWith('__vmScriptReferenceArgs_') && + options?.ignoreScriptReferenceArgs === true + ) { + continue + } + + findMessageTransfers(transfers, object[key], options) + } + } + + return transfers + + function add (value) { + if (!transfers.includes(value)) { + transfers.push(value) + } + } +} + +const ports = [] +globalThis.addEventListener('connect', (event) => { + for (const port of event.ports) { + port.start() + ports.push(port) + port.addEventListener('message', (event) => { + for (const p of ports) { + if (p !== port) { + const transfer = [] + findMessageTransfers(transfer, event.data) + p.postMessage(event.data, { transfer }) + } + } + }) + } +}) diff --git a/api/service-worker/state.js b/api/service-worker/state.js new file mode 100644 index 0000000000..b428f605cc --- /dev/null +++ b/api/service-worker/state.js @@ -0,0 +1,114 @@ +/* global reportError */ +import ipc from '../ipc.js' + +export const channel = new BroadcastChannel('service-worker-state') + +const descriptors = { + channel: { + configurable: false, + enumerable: false, + value: channel + }, + + notify: { + configurable: false, + enumerable: false, + writable: false, + async value (type) { + channel.postMessage({ [type]: this[type] }) + + if (type === 'serviceWorker') { + await ipc.request('serviceWorker.updateState', { + id: this.id, + state: this.serviceWorker.state + }) + } + } + }, + + serviceWorker: { + configurable: false, + enumerable: true, + value: Object.create(null, { + scope: { + configurable: false, + enumerable: true, + writable: true, + value: '/' + }, + + state: { + configurable: false, + enumerable: true, + writable: true, + value: 'parsed' + } + }) + } +} + +if (!globalThis.window) { + Object.assign(descriptors, { + id: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + scriptURL: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + scope: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + fetch: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + install: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + activate: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + reportError: { + configurable: false, + enumerable: false, + writable: true, + value: reportError + } + }) +} + +export const state = Object.create(null, descriptors) + +channel.addEventListener('message', (event) => { + if (event.data.serviceWorker) { + const scope = new URL(globalThis.location.href).pathname + if (scope.startsWith(event.data.serviceWorker.scope)) { + Object.assign(state.serviceWorker, event.data.serviceWorker) + } + } +}) + +export default state diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js new file mode 100644 index 0000000000..4852321c6d --- /dev/null +++ b/api/service-worker/worker.js @@ -0,0 +1,203 @@ +import { ExtendableEvent, FetchEvent } from './events.js' +import { ServiceWorkerGlobalScope } from './global.js' +import { InvertedPromise } from '../util.js' +import hooks from '../hooks.js' +import state from './state.js' + +const SERVICE_WORKER_READY_TOKEN = { __service_worker_ready: true } + +Object.defineProperties( + globalThis, + Object.getOwnPropertyDescriptors(ServiceWorkerGlobalScope.prototype) +) + +const events = new Set() +const env = {} + +hooks.onReady(onReady) +globalThis.addEventListener('message', onMessage) + +function onReady () { + globalThis.postMessage(SERVICE_WORKER_READY_TOKEN) +} + +async function onMessage (event) { + const { data } = event + + if (data?.register) { + const { id, scope, scriptURL } = data.register + let module = null + + state.id = id + state.scope = scope + state.scriptURL = scriptURL + state.serviceWorker.scope = scope + + try { + module = await import(scriptURL) + } catch (err) { + state.serviceWorker.state = 'error' + await state.notify('serviceWorker') + return state.reportError(err) + } + + if (module.default && typeof module.default === 'object') { + if (typeof module.default.fetch === 'function') { + state.fetch = module.default.fetch + } + } else if (typeof module.default === 'function') { + state.fetch = module.default + } else if (typeof module.fetch === 'function') { + state.fetch = module.fetch + } + + if (module.default && typeof module.default === 'object') { + if (typeof module.default.install === 'function') { + state.install = module.default.install + } + } else if (typeof module.install === 'function') { + state.install = module.install + } + + if (module.default && typeof module.default === 'object') { + if (typeof module.default.activate === 'function') { + state.activate = module.default.activate + } + } else if (typeof module.activate === 'function') { + state.activate = module.activate + } + + if (module.default && typeof module.default === 'object') { + if (typeof module.default.reportError === 'function') { + state.reportError = module.default.reportError + } + } else if (typeof module.reportError === 'function') { + state.reportError = module.reportError + } + + if (typeof state.activate === 'function') { + globalThis.addEventListener('activate', async () => { + const context = { + passThroughOnException () {}, + async waitUntil (...args) { + return await event.waitUntil(...args) + } + } + + try { + await state.activate(env, context) + } catch (err) { + state.reportError(err) + } + }) + } + + if (typeof state.install === 'function') { + globalThis.addEventListener('install', async () => { + const context = { + passThroughOnException () {}, + async waitUntil (...args) { + return await event.waitUntil(...args) + } + } + + try { + await state.install(env, context) + } catch (err) { + state.reportError(err) + } + }) + } + + if (typeof state.fetch === 'function') { + if (!state.install) { + globalThis.addEventListener('install', () => { + globalThis.skipWaiting() + }) + } + + globalThis.addEventListener('fetch', async (event) => { + const { request } = event + const context = { + passThroughOnException () {}, + async waitUntil (...args) { + return await event.waitUntil(...args) + }, + + async handled () { + return await event.handled + } + } + + const promise = new InvertedPromise() + let response = null + + event.respondWith(promise) + + try { + response = await state.fetch(request, env, context) + } catch (err) { + state.reportError(err) + response = new Response(err.message, { + status: 500 + }) + } + + if (response) { + promise.resolve(response) + } else { + promise.resolve(new Response('', { status: 400 })) + } + }) + } + + globalThis.registration = data.register + + globalThis.postMessage({ __service_worker_registered: { id } }) + return + } + + if (data?.install?.id === state.id) { + const event = new ExtendableEvent('install') + state.serviceWorker.state = 'installing' + await state.notify('serviceWorker') + globalThis.dispatchEvent(event) + await event.waitsFor() + state.serviceWorker.state = 'installed' + await state.notify('serviceWorker') + events.delete(event) + } + + if (data?.activate?.id === state.id) { + const event = new ExtendableEvent('activate') + state.serviceWorker.state = 'activating' + await state.notify('serviceWorker') + events.add(event) + globalThis.dispatchEvent(event) + await event.waitsFor() + state.serviceWorker.state = 'activated' + await state.notify('serviceWorker') + events.delete(event) + // TODO(@jwerle): handle 'activate' + } + + if (data?.fetch) { + const event = new FetchEvent('fetch', { + clientId: data.fetch.client.id, + fetchId: data.fetch.request.id, + request: new Request(data.fetch.request.url, { + method: data.fetch.request.method ?? 'GET' + }) + }) + + globalThis.dispatchEvent(event) + } + + if (event.data?.notificationclick) { + // TODO(@jwerle) + } + + if (event.data?.notificationshow) { + // TODO(@jwerle) + } +} diff --git a/api/vm/worker.js b/api/vm/worker.js index 09559180c6..eb7af5248c 100644 --- a/api/vm/worker.js +++ b/api/vm/worker.js @@ -1,14 +1,8 @@ +/* global reportError */ const Uint8ArrayPrototype = Uint8Array.prototype const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) const TypedArray = TypedArrayPrototype.constructor -// eslint-disable-next-line -function reportError (err) { - if (typeof globalThis.reportError === 'function') { - globalThis.reportError(err) - } -} - function isTypedArray (object) { return object instanceof TypedArray } From 28c753bb5337b0e876ff8d3c780ec3bfb2f468a0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 11 Feb 2024 00:57:09 +0100 Subject: [PATCH 0014/1178] refactor(src/ipc/bridge.cc): initial serviceWorker bridge routes --- src/ipc/bridge.cc | 287 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 267 insertions(+), 20 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index f8329596a4..54e3a9ee43 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -1895,7 +1895,7 @@ static void initRouterTable (Router *router) { } auto error = [NSError - errorWithDomain: @(userConfig["bundle_identifier"].c_str()) + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) code: -1 userInfo: @{ NSLocalizedDescriptionKey: reason @@ -2008,7 +2008,9 @@ static void initRouterTable (Router *router) { return reply(Result { message.seq, message, err }); } - if (!router->isReady) router->isReady = true; + if (message.value == "runtimeinit") { + router->isReady = true; + } router->core->platform.event( message.seq, @@ -2169,6 +2171,160 @@ static void initRouterTable (Router *router) { reply(Result::Data { message, JSON::Object {}}); }); + /** + * TODO + */ + router->map("serviceWorker.clients.claim", [](auto message, auto router, auto reply) { + }); + + /** + * TODO + */ + router->map("serviceWorker.clients.openWindow", [](auto message, auto router, auto reply) { + }); + + /** + * TODO + */ + router->map("serviceWorker.register", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scriptURL", "scope"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto options = ServiceWorkerContainer::RegistrationOptions { + .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, + .scope = message.get("scope"), + .scriptURL = message.get("scriptURL") + }; + + const auto registration = router->core->serviceWorker.registerServiceWorker(options); + auto json = JSON::Object { + JSON::Object::Entries { + {"registration", registration.json()}, + {"client", JSON::Object::Entries { + {"id", std::to_string(router->bridge->id)} + }} + } + }; + + auto& clients = router->core->serviceWorker.registrations.at(options.scope).clients; + bool clientExists = false; + for (const auto& client : clients) { + if (client.id == router->bridge->id) { + clientExists = true; + break; + } + } + + if (!clientExists) { + clients.push_back(ServiceWorkerContainer::Client { router->bridge->id }); + } + + reply(Result::Data { message, json }); + }); + + /** + * TODO + */ + router->map("serviceWorker.unregister", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scope"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto scope = message.get("scope"); + router->core->serviceWorker.unregisterServiceWorker(scope); + + return reply(Result::Data { message, JSON::Object {} }); + }); + + /** + * TODO + */ + router->map("serviceWorker.getRegistration", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scope"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto scope = message.get("scope"); + + for (const auto& entry : router->core->serviceWorker.registrations) { + const auto& registration = entry.second; + if (registration.options.scope.starts_with(scope)) { + auto json = JSON::Object { + JSON::Object::Entries { + {"registration", registration.json()}, + {"client", JSON::Object::Entries { + {"id", std::to_string(router->bridge->id)} + }} + } + }; + + return reply(Result::Data { message, json }); + } + } + + return reply(Result::Data { message, JSON::Object {} }); + }); + + /** + * TODO + */ + router->map("serviceWorker.getRegistrations", [](auto message, auto router, auto reply) { + auto json = JSON::Array::Entries {}; + for (const auto& entry : router->core->serviceWorker.registrations) { + const auto& registration = entry.second; + json.push_back(registration.json()); + } + return reply(Result::Data { message, json }); + }); + + /** + * TODO + */ + router->map("serviceWorker.skipWaiting", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->serviceWorker.skipWaiting(id); + + reply(Result::Data { message, JSON::Object {}}); + }); + + /** + * TODO + */ + router->map("serviceWorker.startMessages", [](auto message, auto router, auto reply) { + }); + + /** + * TODO + */ + router->map("serviceWorker.updateState", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "state"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->serviceWorker.updateState(id, message.get("state")); + reply(Result::Data { message, JSON::Object {}}); + }); + /** * Binds an UDP socket to a specified port, and optionally a host * address (default: 0.0.0.0). @@ -2853,6 +3009,7 @@ static void registerSchemeHandler (Router *router) { NSData* data = nullptr; bool isModule = false; + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR const auto basePath = String(NSBundle.mainBundle.resourcePath.UTF8String) + "/ui"; #else @@ -2961,6 +3118,74 @@ static void registerSchemeHandler (Router *router) { if (userConfig.contains("webview_default_index")) { path = userConfig["webview_default_index"]; } else { + if (self.router->core->serviceWorker.registrations.size() > 0) { + auto fetchRequest = ServiceWorkerContainer::FetchRequest { + String(request.URL.path.UTF8String) + }; + + fetchRequest.client.id = self.router->bridge->id; + if (request.URL.query != nullptr) { + fetchRequest.query = String(request.URL.query.UTF8String); + } + + if (request.allHTTPHeaderFields != nullptr) { + for (NSString* key in request.allHTTPHeaderFields) { + const auto value = [request.allHTTPHeaderFields objectForKey: key]; + if (value != nullptr) { + const auto entry = String(key.UTF8String) + ": " + String(value.UTF8String); + fetchRequest.headers.push_back(entry); + } + } + } + + const auto fetched = self.router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) { + auto headers = [NSMutableDictionary dictionary]; + auto webviewHeaders = split(userConfig["webview_headers"], '\n'); + + for (const auto& line : webviewHeaders) { + auto pair = split(trim(line), ':'); + auto key = [NSString stringWithUTF8String: trim(pair[0]).c_str()]; + auto value = [NSString stringWithUTF8String: trim(pair[1]).c_str()]; + headers[key] = value; + } + + headers[@"access-control-allow-origin"] = @"*"; + headers[@"access-control-allow-methods"] = @"*"; + headers[@"access-control-allow-headers"] = @"*"; + + for (const auto& entry : res.headers) { + auto pair = split(trim(entry), ':'); + auto key = @(trim(pair[0]).c_str()); + auto value = @(trim(pair[1]).c_str()); + headers[key] = value; + } + + auto data = [NSData dataWithBytes: res.buffer.bytes length: res.buffer.size]; + auto response = [[NSHTTPURLResponse alloc] + initWithURL: request.URL + statusCode: res.statusCode + HTTPVersion: @"HTTP/1.1" + headerFields: headers + ]; + + [task didReceiveResponse: response]; + + if (res.buffer.size && data.length > 0) { + [task didReceiveData: data]; + } + + [task didFinish]; + + #if !__has_feature(objc_arc) + [response release]; + #endif + }); + + if (fetched) { + return; + } + } + auto response = [[NSHTTPURLResponse alloc] initWithURL: request.URL statusCode: 404 @@ -3091,25 +3316,46 @@ static void registerSchemeHandler (Router *router) { components.scheme = @("socket"); headers[@"content-location"] = components.URL.absoluteString; + const auto absoluteURL = String(components.URL.absoluteString.UTF8String); + const auto socketModulePrefix = "socket://" + userConfig["meta_bundle_identifier"] + "/socket/"; - auto statusCode = exists ? 200 : 404; - auto response = [[NSHTTPURLResponse alloc] - initWithURL: components.URL - statusCode: statusCode - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; + if (!isModule && absoluteURL.starts_with(socketModulePrefix)) { + isModule = true; + } - [task didReceiveResponse: response]; + dispatch_async(queue, ^{ + if ( + !isModule && ( + absoluteURL.ends_with(".js") || + absoluteURL.ends_with(".cjs") || + absoluteURL.ends_with(".mjs") + ) + ) { + while (!self.router->isReady) { + msleep(1); + } + } - if (data && data.length > 0) { - [task didReceiveData: data]; - } - [task didFinish]; + auto statusCode = exists ? 200 : 404; + auto response = [NSHTTPURLResponse.alloc + initWithURL: components.URL + statusCode: statusCode + HTTPVersion: @"HTTP/1.1" + headerFields: headers + ]; + + [task didReceiveResponse: response]; + + if (data && data.length > 0) { + [task didReceiveData: data]; + } + + [task didFinish]; #if !__has_feature(objc_arc) - [response release]; + [response release]; #endif + }); return; } @@ -3409,7 +3655,7 @@ static void registerSchemeHandler (Router *router) { } auto error = [NSError - errorWithDomain: @(userConfig["bundle_identifier"].c_str()) + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) code: -1 userInfo: @{ NSLocalizedDescriptionKey: reason @@ -3455,7 +3701,7 @@ static void registerSchemeHandler (Router *router) { static auto userConfig = SSC::getUserConfig(); if (!isAuthorized) { auto error = [NSError - errorWithDomain: @(userConfig["bundle_identifier"].c_str()) + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) code: -1 userInfo: @{ @"Error reason": @("Location observer could not be activated") @@ -3705,6 +3951,7 @@ namespace SSC::IPC { Bridge::Bridge (Core *core) : router() { static auto userConfig = SSC::getUserConfig(); + this->id = rand64(); this->core = core; this->router.core = core; this->router.bridge = this; @@ -3838,7 +4085,7 @@ namespace SSC::IPC { } // Resolve the full path - fs::path fullPath = (fs::path(basePath) / fs::path(inputPath)).make_preferred(); + const auto fullPath = (fs::path(basePath) / fs::path(inputPath)).make_preferred(); // 1. Try the given path if it's a file if (fs::is_regular_file(fullPath)) { @@ -3846,7 +4093,7 @@ namespace SSC::IPC { } // 2. Try appending a `/` to the path and checking for an index.html - fs::path indexPath = fullPath / fs::path("index.html"); + const auto indexPath = fullPath / fs::path("index.html"); if (fs::is_regular_file(indexPath)) { if (fullPath.string().ends_with("\\") || fullPath.string().ends_with("/")) { return Router::WebViewURLPathResolution{ @@ -3862,7 +4109,7 @@ namespace SSC::IPC { } // 3. Check if appending a .html file extension gives a valid file - fs::path htmlPath = fullPath; + auto htmlPath = fullPath; htmlPath.replace_extension(".html"); if (fs::is_regular_file(htmlPath)) { return Router::WebViewURLPathResolution{"/" + replace(fs::relative(htmlPath, basePath).string(), "\\\\", "/")}; From 047a613c649eae7f7be973b3cf27d3e387b625f5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:35:46 +0100 Subject: [PATCH 0015/1178] fix(api/application.js): improve 'getWindows()' --- api/application.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/api/application.js b/api/application.js index 312a4afcfd..45addec757 100644 --- a/api/application.js +++ b/api/application.js @@ -152,14 +152,33 @@ export async function getWindows (indices) { } // TODO: create a local registry and return from it when possible const resultIndices = indices ?? [] + if (!Array.isArray(resultIndices)) { throw new Error('Indices list must be an array of integer numbers') } + for (const index of resultIndices) { throwOnInvalidIndex(index) } - const { data: windows } = await ipc.send('application.getWindows', resultIndices) - return Object.fromEntries(windows.map(window => [Number(window.index), new ApplicationWindow(window)])) + + const result = await ipc.send('application.getWindows', resultIndices) + + if (result.err) { + throw result.err + } + + // 0 indexed based key to `ApplicationWindow` object map + const windows = {} + + if (!Array.isArray(result.data)) { + return windows + } + + for (const data of result.data) { + windows[data.index] = new ApplicationWindow(data) + } + + return windows } /** From c0c47f075d3571734216ca1c103954574d3700be Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:35:56 +0100 Subject: [PATCH 0016/1178] chore(api/internal/events.js): fix lint --- api/internal/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/events.js b/api/internal/events.js index 1666106bf9..9aa394d74f 100644 --- a/api/internal/events.js +++ b/api/internal/events.js @@ -1,4 +1,4 @@ -/* global MessageEvent */ +/* global Event, MessageEvent */ /** * An event dispatched when an application URL is opening the application. From 1c0b3d45547fa80d99e789c4a71ff7dd6947c7a4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:36:31 +0100 Subject: [PATCH 0017/1178] refactor(api/internal/init.js): improve 'platformdrop', set worker client info --- api/internal/init.js | 90 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index e3ff8dabff..f6dbd1691e 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -19,6 +19,8 @@ import { CustomEvent, ErrorEvent } from '../events.js' import { rand64 } from '../crypto.js' import location from '../location.js' import { URL } from '../url.js' +import mime from '../mime.js' +import path from '../path.js' import fs from '../fs/promises.js' import { createFileSystemDirectoryHandle, @@ -91,11 +93,33 @@ if ((globalThis.window) === globalThis) { if (Array.isArray(event.detail?.files)) { for (const file of event.detail.files) { if (typeof file === 'string') { - const stats = await fs.stat(file) - if (stats.isDirectory()) { - handles.push(await createFileSystemDirectoryHandle(file, { writable: false })) - } else { - handles.push(await createFileSystemFileHandle(file, { writable: false })) + try { + const stats = await fs.stat(file) + if (stats.isDirectory()) { + handles.push(await createFileSystemDirectoryHandle(file, { writable: false })) + } else { + handles.push(await createFileSystemFileHandle(file, { writable: false })) + } + } catch (err) { + try { + // try to read from navigator + const response = await fetch(file) + if (response.ok) { + const lastModified = Date.now() + const buffer = new Uint8Array(await response.arrayBuffer()) + const types = await mime.lookup(path.extname(file).slice(1)) + const type = types[0]?.mime ?? '' + + handles.push(await createFileSystemFileHandle( + new File(buffer, { lastModified, type }), + { writable: false } + )) + } else { + console.warn('platformdrop: ', err) + } + } catch (err) { + console.warn('platformdrop: ', err) + } } } } @@ -208,6 +232,10 @@ class RuntimeWorker extends GlobalWorker { value: ${JSON.stringify(globalThis.__args)} }) + globalThis.__args.client.id = '${id}' + globalThis.__args.client.type = 'worker' + globalThis.__args.client.frameType = 'none' + Object.defineProperty(globalThis, 'RUNTIME_WORKER_ID', { configurable: false, enumerable: false, @@ -384,14 +412,20 @@ if (typeof globalThis.XMLHttpRequest === 'function') { const value = open.call(this, method, url, isAsyncRequest !== false, ...args) - if (typeof globalThis.RUNTIME_WORKER_ID === 'string') { - this.setRequestHeader('Runtime-Worker-ID', globalThis.RUNTIME_WORKER_ID) - } + this.setRequestHeader('Runtime-Client-ID', globalThis.__args.client.id) if (typeof globalThis.RUNTIME_WORKER_LOCATION === 'string') { this.setRequestHeader('Runtime-Worker-Location', globalThis.RUNTIME_WORKER_LOCATION) } + if (globalThis.top && globalThis.top !== globalThis) { + this.setRequestHeader('Runtime-Frame-Type', 'nested') + } else if (!globalThis.window && globalThis.self === globalThis) { + this.setRequestHeader('Runtime-Frame-Type', 'worker') + } else { + this.setRequestHeader('Runtime-Frame-Type', 'top-level') + } + return value } @@ -524,7 +558,45 @@ class RuntimeXHRPostQueue extends ConcurrentQueue { } } -hooks.onLoad(() => { +hooks.onLoad(async () => { + const serviceWorkerScripts = config['webview_service-workers'] + const pending = [] + if (serviceWorkerScripts) { + for (const serviceWorkerScript of serviceWorkerScripts.split(' ')) { + let scriptURL = serviceWorkerScript.trim() + + if (!scriptURL.startsWith('/') && scriptURL.startsWith('.')) { + if (!URL.canParse(scriptURL, globalThis.location.href)) { + scriptURL = `./${scriptURL}` + } + } + + const promise = globalThis.navigator.serviceWorker.register(scriptURL) + + pending.push(promise) + + promise + .then((registration) => { + if (registration) { + console.log('ServiceWorker registered in preload: %s', scriptURL) + } else { + console.warn( + 'ServiceWorker failed to register in preload: %s', + scriptURL + ) + } + }) + .catch((err) => { + console.error( + 'ServiceWorker registration error occurred in preload: %s:', + scriptURL, + err + ) + }) + } + } + + await Promise.all(pending) if (typeof globalThis.dispatchEvent === 'function') { globalThis.__RUNTIME_INIT_NOW__ = performance.now() globalThis.dispatchEvent(new RuntimeInitEvent()) From 08211022d5f9a87ec3b74c9c86200ef614854641 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:36:46 +0100 Subject: [PATCH 0018/1178] chore(api/internal/monkeypatch.js): document code --- api/internal/monkeypatch.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/internal/monkeypatch.js b/api/internal/monkeypatch.js index ca43e4eee9..f6110e857d 100644 --- a/api/internal/monkeypatch.js +++ b/api/internal/monkeypatch.js @@ -207,7 +207,12 @@ export function init () { Object.getOwnPropertyDescriptors(Object.getPrototypeOf(serviceWorker)) ) + // manually initialize `ServiceWorkerContainer` instance with the + // runtime implementations serviceWorker.init.call(globalThis.navigator.serviceWorker) + + // TODO(@jwerle): handle 'popstate' for service workers + // globalThis.addEventListener('popstate', (event) => { }) } // WebAssembly From 87245b3b122ce44e38e4a6eb907db2142b5a8873 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:37:19 +0100 Subject: [PATCH 0019/1178] refactor(api/service-worker): more service worker progress --- api/service-worker/clients.js | 212 +++++++++++++++++++++- api/service-worker/container.js | 262 +++++++++++++++++++--------- api/service-worker/global.js | 4 +- api/service-worker/init.js | 5 + api/service-worker/instance.js | 54 ++++-- api/service-worker/shared-worker.js | 1 - api/service-worker/state.js | 124 ++++++++++--- api/service-worker/worker.js | 37 +++- 8 files changed, 562 insertions(+), 137 deletions(-) diff --git a/api/service-worker/clients.js b/api/service-worker/clients.js index 67a9d8b913..6e0bdd5322 100644 --- a/api/service-worker/clients.js +++ b/api/service-worker/clients.js @@ -1,7 +1,32 @@ import { SHARED_WORKER_URL } from './instance.js' import { SharedWorker } from '../internal/shared-worker.js' +import application from '../application.js' import state from './state.js' -import ipc from '../ipc.js' +// import ipc from '../ipc.js' + +const MAX_WINDOWS = 32 +const CLIENT_GET_TIMEOUT = 100 +const CLIENT_MATCH_ALL_TIMEOUT = 50 + +function normalizeURL (url) { + if (typeof url !== 'string') { + url = url.toString() + } + + if (!URL.canParse(url) && !url.startsWith('/') && !url.startsWith('.')) { + url = `./${url}` + } + + return new URL(url, globalThis.location.origin) +} + +function getOrigin () { + if (globalThis.location.origin.startsWith('blob:')) { + return new URL(globalThis.location.origin).pathname + } + + return globalThis.location.origin +} export class Client { #id = null @@ -10,36 +35,207 @@ export class Client { #frameType = null #sharedWorker = null - constructor () { + constructor (options) { + this.#id = options?.id ?? null + this.#url = options?.url ?? null + this.#type = options?.type ?? null + this.#frameType = options?.frameType ?? null this.#sharedWorker = new SharedWorker(SHARED_WORKER_URL) this.#sharedWorker.port.start() } + get id () { + return this.#id + } + + get url () { + return this.#url + } + + get type () { + return this.#type ?? 'none' + } + + get frameType () { + return this.#frameType ?? 'none' + } + postMessage (message, optionsOrTransferables = null) { - return this.#sharedWorker.port.postMessage(message, optionsOrTransferables) + // TODO(@jwerle) + // return this.#sharedWorker.port.postMessage(message, optionsOrTransferables) } } export class WindowClient extends Client { - focus () { + #url = null + #window = null + #focused = false + #ancestorOrigins = [] + #visibilityState = 'prerender' + + constructor (options) { + super({ + ...options, + id: options?.window?.id + }) + + this.#url = options?.url ?? null + this.#window = options?.window ?? null + + state.channel.addEventListener('message', (event) => { + if (event.data?.client?.id === this.id) { + if ('focused' in event.data.client) { + this.#focused = Boolean(event.data.client.focused) + } + + if ('visibilityState' in event.data.client) { + this.#visibilityState = event.data.client.visibilityState + } + } + }) + } + + get url () { + return this.#url } - navigate () { + get focused () { + return this.#focused + } + + get ancestorOrigins () { + return this.#ancestorOrigins + } + + get visibilityState () { + return this.#visibilityState + } + + async focus () { + state.channel.postMessage({ + client: { + id: this.id, + focus: true + } + }) + + return this + } + + async navigate (url) { + const origin = getOrigin() + + url = normalizeURL(url) + + if (!url.toString().startsWith(origin)) { + throw new TypeError('WindowClient cannot navigate outside of origin') + } + + console.log('navigate', url) + await this.#window.navigate(url.pathname + url.search) + this.#url = url.pathname + url.search + return this } } export class Clients { async get (id) { + state.channel.postMessage({ + clients: { + get: { id } + } + }) + + const result = await new Promise((resolve) => { + const timeout = setTimeout(onTimeout, CLIENT_GET_TIMEOUT) + + state.channel.addEventListener('message', onMessage) + + function onMessage (event) { + if (event.data?.clients?.get?.result?.client?.id === id) { + clearTimeout(timeout) + state.channel.removeEventListener('message', onMessage) + resolve(event.data.client) + } + } + + function onTimeout () { + state.channel.removeEventListener('message', onMessage) + resolve(null) + } + }) + + if (result) { + return new Client(result) + } } - async matchAll () { + async matchAll (options = null) { + state.channel.postMessage({ + clients: { + matchAll: options ?? {} + } + }) + + const results = await new Promise((resolve) => { + setTimeout(onTimeout, CLIENT_MATCH_ALL_TIMEOUT) + const clients = [] + + state.channel.addEventListener('message', onMessage) + + function onMessage (event) { + if (event.data?.clients?.matchAll?.result?.client) { + const { client } = event.data.client.matchAll.result + if (!options?.type || options.type === 'all') { + clients.push(client) + } else if (options.type === client.type) { + clients.push(client) + } + } + } + + function onTimeout () { + state.channel.removeEventListener('message', onMessage) + resolve(clients) + } + }) + + return results.map((result) => new Client(result)) } - async openWindow () { + async openWindow (url, options = null) { + const windows = await application.getWindows() + const indices = Object.keys(windows) + .map((key) => parseInt(key)) + .filter((index) => !Number.isNaN(index) && index < MAX_WINDOWS) + .sort() + + const index = indices.pop() + 1 + + if (index < MAX_WINDOWS) { + throw new DOMException('Max windows are opened', 'InvalidAccessError') + } + + const window = await application.createWindow({ ...options, index, path: url }) + + return new WindowClient({ + frameType: 'top-level', + type: 'window', + window, + url + }) } async claim () { + state.channel.postMessage({ + clients: { + claim: { + scope: state.serviceWorker.scope, + scriptURL: state.serviceWorker.scriptURL + } + } + }) } } -export default Clients +export default new Clients() diff --git a/api/service-worker/container.js b/api/service-worker/container.js index f32da5069d..db42a27f51 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -1,154 +1,250 @@ /* global EventTarget */ -import serviceWorker, { createServiceWorker, SHARED_WORKER_URL } from './instance.js' +import { createServiceWorker, SHARED_WORKER_URL } from './instance.js' import { ServiceWorkerRegistration } from './registration.js' import { InvertedPromise } from '../util.js' import { SharedWorker } from '../internal/shared-worker.js' +import application from '../application.js' +import state from './state.js' import ipc from '../ipc.js' -class State { +class ServiceWorkerContainerInternalStateMap extends Map { + define (container, property, descriptor) { + Object.defineProperty(container, property, { + configurable: false, + enumerable: true, + ...descriptor + }) + } +} + +class ServiceWorkerContainerInternalState { + currentWindow = null sharedWorker = null controller = null channel = new BroadcastChannel('ServiceWorkerContainer') ready = new InvertedPromise() // level 1 events - #oncontrollerchange = null - #onmessageerror = null - #onmessage = null - #onerror = null + oncontrollerchange = null + onmessageerror = null + onmessage = null + onerror = null } -const state = new Map() +const internal = new ServiceWorkerContainerInternalStateMap() + +async function preloadExistingRegistration (container) { + const registration = await container.getRegistration() + if (registration) { + if (registration.active) { + queueMicrotask(() => { + container.dispatchEvent(new Event('controllerchange')) + }) + + queueMicrotask(() => { + internal.get(container).ready.resolve(registration) + }) + } else { + const serviceWorker = registration.installing || registration.waiting + serviceWorker.addEventListener('statechange', function onStateChange (event) { + if ( + serviceWorker.state === 'activating' || + serviceWorker.state === 'activated' + ) { + serviceWorker.removeEventListener('statechange', onStateChange) + + queueMicrotask(() => { + container.dispatchEvent(new Event('controllerchange')) + }) + + queueMicrotask(() => { + internal.get(container).ready.resolve(registration) + }) + } + }) + } + } +} + +async function activateRegistrationFromClaim (container, claim) { + await container.register(claim.scriptURL) + await preloadExistingRegistration(container) +} export class ServiceWorkerContainer extends EventTarget { - init () { - state.set(this, new State()) + /** + * A special initialization function for augmenting the global + * `globalThis.navigator.serviceWorker` platform `ServiceWorkerContainer` + * instance. + * + * All functions MUST be sure to what a lexically bound `this` becomes as the + * target could change with respect to the `internal` `Map` instance which + * contains private implementation properties relevant to the runtime + * `ServiceWorkerContainer` internal state implementations. + * @ignore + * @private + */ + async init () { + internal.set(this, new ServiceWorkerContainerInternalState()) this.register = this.register.bind(this) this.getRegistration = this.getRegistration.bind(this) this.getRegistrations = this.getRegistrations.bind(this) - Object.defineProperty(this, 'controller', { - configurable: false, - enumerable: true, - get: () => state.get(this).controller + internal.define(this, 'controller', { + get () { + return internal.get(this).controller + } }) - Object.defineProperty(this, 'oncontrollerchange', { - configurable: false, - enumerable: true, - get: () => state.get(this).oncontrollerchange, - set: (oncontrollerchange) => { - if (state.get(this).oncontrollerchange) { - this.removeEventListener('controllerchange', state.get(this).oncontrollerchange) + internal.define(this, 'oncontrollerchange', { + get () { + return internal.get(this).oncontrollerchange + }, + + set (oncontrollerchange) { + if (internal.get(this).oncontrollerchange) { + this.removeEventListener('controllerchange', internal.get(this).oncontrollerchange) } - state.get(this).oncontrollerchange = null + internal.get(this).oncontrollerchange = null if (typeof oncontrollerchange === 'function') { this.addEventListener('controllerchange', oncontrollerchange) - state.get(this).oncontrollerchange = oncontrollerchange + internal.get(this).oncontrollerchange = oncontrollerchange } } }) - Object.defineProperty(this, 'onmessageerror', { - configurable: false, - enumerable: true, - get: () => state.get(this).onmessageerror, - set: (onmessageerror) => { - if (state.get(this).onmessageerror) { - this.removeEventListener('messageerror', state.get(this).onmessageerror) + internal.define(this, 'onmessageerror', { + get () { + return internal.get(this).onmessageerror + }, + + set (onmessageerror) { + if (internal.get(this).onmessageerror) { + this.removeEventListener('messageerror', internal.get(this).onmessageerror) } - state.get(this).onmessageerror = null + internal.get(this).onmessageerror = null if (typeof onmessageerror === 'function') { this.addEventListener('messageerror', onmessageerror) - state.get(this).onmessageerror = onmessageerror + internal.get(this).onmessageerror = onmessageerror } } }) - Object.defineProperty(this, 'onmessage', { - configurable: false, - enumerable: true, - get: () => state.get(this).onmessage, - set: (onmessage) => { - if (state.get(this).onmessage) { - this.removeEventListener('message', state.get(this).onmessage) + internal.define(this, 'onmessage', { + get () { + return internal.get(this).onmessage + }, + + set (onmessage) { + if (internal.get(this).onmessage) { + this.removeEventListener('message', internal.get(this).onmessage) } - state.get(this).onmessage = null + internal.get(this).onmessage = null if (typeof onmessage === 'function') { this.addEventListener('message', onmessage) - state.get(this).onmessage = onmessage + internal.get(this).onmessage = onmessage } } }) - Object.defineProperty(this, 'onerror', { - configurable: false, - enumerable: true, - get: () => state.get(this).onerror, - set: (onerror) => { - if (state.get(this).onerror) { - this.removeEventListener('error', state.get(this).onerror) + internal.define(this, 'onerror', { + get () { + return internal.get(this).onerror + }, + + set (onerror) { + if (internal.get(this).onerror) { + this.removeEventListener('error', internal.get(this).onerror) } - state.get(this).onerror = null + internal.get(this).onerror = null if (typeof onerror === 'function') { this.addEventListener('error', onerror) - state.get(this).onerror = onerror + internal.get(this).onerror = onerror } } }) - this.getRegistration().then((registration) => { + preloadExistingRegistration(this) + + internal.get(this).ready.then(async (registration) => { if (registration) { - if (registration.active) { - queueMicrotask(() => this.dispatchEvent(new Event('controllerchange'))) - queueMicrotask(() => state.get(this).ready.resolve(registration)) - } else { - serviceWorker.addEventListener('statechange', () => { - if (serviceWorker.state === 'activating' || serviceWorker.state === 'activated') { - queueMicrotask(() => this.dispatchEvent(new Event('controllerchange'))) - queueMicrotask(() => state.get(this).ready.resolve(registration)) - } - }) - } + internal.get(this).controller = registration.active + internal.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) + internal.get(this).currentWindow = await application.getCurrentWindow() } }) - state.get(this).ready.then((registration) => { - if (registration) { - state.get(this).controller = registration.active - state.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) + state.channel.addEventListener('message', (event) => { + if (event.data?.clients?.claim?.scope) { + const { scope } = event.data.clients.claim + if (globalThis.location.pathname.startsWith(scope)) { + activateRegistrationFromClaim(this, event.data.clients.claim) + } } }) } get ready () { - return state.get(this).ready + return internal.get(this).ready } get controller () { - return state.get(this).controller + return internal.get(this).controller } async getRegistration (clientURL) { - const scope = clientURL || new URL(globalThis.location.href).pathname + let scope = clientURL + let currentScope = null + + if (scope) { + try { + scope = new URL(scope, globalThis.location.href).pathname + } catch {} + } + + if (globalThis.location.protocol === 'blob:') { + currentScope = new URL('.', globalThis.location.pathname).pathname + } else { + currentScope = new URL('.', globalThis.location.href).pathname + } + + if (!scope) { + scope = currentScope + } + const result = await ipc.request('serviceWorker.getRegistration', { scope }) if (result.err) { throw result.err } - if (result.data?.registration?.id) { - return new ServiceWorkerRegistration(result.data, serviceWorker) + const info = result.data + + if (!info?.registration?.state || !info?.registration?.id) { + return + } + + if (scope === currentScope) { + state.serviceWorker.state = info.registration.state.replace('registered', 'installing') + state.serviceWorker.scope = scope + state.serviceWorker.scriptURL = info.registration.scriptURL } + + const serviceWorker = createServiceWorker(state.serviceWorker.state, { + subscribe: scope === currentScope, + scriptURL: info.registration.scriptURL + }) + + return new ServiceWorkerRegistration(info, serviceWorker) } async getRegistrations () { @@ -163,7 +259,10 @@ export class ServiceWorkerContainer extends EventTarget { if (Array.isArray(result.data)) { for (const registration of result.data) { const info = { registration } - const serviceWorker = createServiceWorker(registration.state) + info.registration.state = info.registration.state.replace('registered', 'installing') + const serviceWorker = createServiceWorker(registration.state, { + scriptURL: info.registration.scriptURL + }) registrations.push(new ServiceWorkerRegistration(info, serviceWorker)) } } @@ -191,24 +290,27 @@ export class ServiceWorkerContainer extends EventTarget { throw result.err } + const info = result.data const url = new URL(scriptURL) if (url.pathname.startsWith(options.scope)) { - const registration = new ServiceWorkerRegistration(result.data, serviceWorker) - serviceWorker.addEventListener('statechange', () => { - if (serviceWorker.state === 'activating' || serviceWorker.state === 'activated') { - queueMicrotask(() => this.dispatchEvent(new Event('controllerchange'))) - queueMicrotask(() => state.get(this).ready.resolve(registration)) - } + state.serviceWorker.state = info.registration.state.replace('registered', 'installing') + state.serviceWorker.scriptURL = info.registration.scriptURL + + const serviceWorker = createServiceWorker(state.serviceWorker.state, { + scriptURL: info.registration.scriptURL }) + + const registration = new ServiceWorkerRegistration(info, serviceWorker) + return registration } } startMessages () { - state.get(this).ready.then(() => { - state.get(this).sharedWorker.port.start() - state.get(this).sharedWorker.port.addEventListener('message', (event) => { + internal.get(this).ready.then(() => { + internal.get(this).sharedWorker.port.start() + internal.get(this).sharedWorker.port.addEventListener('message', (event) => { this.dispatchEvent(new MessageEvent(event.type, event)) }) }) diff --git a/api/service-worker/global.js b/api/service-worker/global.js index 70f41bb3bf..34766677dc 100644 --- a/api/service-worker/global.js +++ b/api/service-worker/global.js @@ -1,12 +1,10 @@ import { ExtendableEvent, FetchEvent } from './events.js' import { ServiceWorkerRegistration } from './registration.js' import serviceWorker from './instance.js' -import { Clients } from './clients.js' +import clients from './clients.js' import state from './state.js' import ipc from '../ipc.js' -const clients = new Clients() - // events let onactivate = null let onmessage = null diff --git a/api/service-worker/init.js b/api/service-worker/init.js index d8c60f10bf..92244b92b5 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -25,6 +25,7 @@ class ServiceWorkerInfo { globalThis.addEventListener('serviceWorker.register', onRegister) globalThis.addEventListener('serviceWorker.skipWaiting', onSkipWaiting) +globalThis.addEventListener('serviceWorker.activate', onActivate) globalThis.addEventListener('serviceWorker.fetch', onFetch) async function onRegister (event) { @@ -50,6 +51,10 @@ async function onRegister (event) { } async function onSkipWaiting (event) { + onActivate(event) +} + +async function onActivate (event) { const info = new ServiceWorkerInfo(event.detail) if (!workers.has(info.hash)) { diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 788de9eaa0..5aa56f0b08 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -4,13 +4,21 @@ import state from './state.js' export const SHARED_WORKER_URL = new URL('./shared-worker.js', import.meta.url) -export function createServiceWorker (currentState = state.serviceWorker.state) { - // events +export function createServiceWorker ( + currentState = state.serviceWorker.state, + options = null +) { + // client message bus worker const sharedWorker = new SharedWorker(SHARED_WORKER_URL) + + // events const eventTarget = new EventTarget() let onstatechange = null let onerror = null + // state + let scriptURL = options?.scriptURL ?? null + sharedWorker.port.start() sharedWorker.port.addEventListener('message', (event) => { eventTarget.dispatchEvent(new MessageEvent('message', event.data)) @@ -31,6 +39,12 @@ export function createServiceWorker (currentState = state.serviceWorker.state) { get: () => currentState }, + scriptURL: { + configurable: true, + enumerable: false, + get: () => scriptURL + }, + onerror: { enumerable: false, get: () => onerror, @@ -84,24 +98,28 @@ export function createServiceWorker (currentState = state.serviceWorker.state) { } }) - state.channel.addEventListener('message', (event) => { - const { data } = event - if (data.serviceWorker) { - if (data.serviceWorker.state && data.serviceWorker.state !== currentState) { - const scope = new URL(globalThis.location.href).pathname - if (scope.startsWith(data.serviceWorker.scope)) { - currentState = data.serviceWorker.state - const event = new Event('statechange') - - Object.defineProperties(event, { - target: { value: serviceWorker } - }) - - eventTarget.dispatchEvent(event) + if (options?.subscribe !== false) { + state.channel.addEventListener('message', (event) => { + const { data } = event + if (data.serviceWorker) { + if (data.serviceWorker.state && data.serviceWorker.state !== currentState) { + const scope = new URL(globalThis.location.href).pathname + console.log('instance channel message', scope, data.serviceWorker.scope) + if (scope.startsWith(data.serviceWorker.scope)) { + currentState = data.serviceWorker.state + scriptURL = data.serviceWorker.scriptURL + const event = new Event('statechange') + + Object.defineProperties(event, { + target: { value: serviceWorker } + }) + + eventTarget.dispatchEvent(event) + } } } - } - }) + }) + } return serviceWorker } diff --git a/api/service-worker/shared-worker.js b/api/service-worker/shared-worker.js index 0028d81dbc..9821a8351a 100644 --- a/api/service-worker/shared-worker.js +++ b/api/service-worker/shared-worker.js @@ -1,4 +1,3 @@ -/* global reportError */ const Uint8ArrayPrototype = Uint8Array.prototype const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) const TypedArray = TypedArrayPrototype.constructor diff --git a/api/service-worker/state.js b/api/service-worker/state.js index b428f605cc..1407e79417 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -1,7 +1,8 @@ /* global reportError */ +import application from '../application.js' import ipc from '../ipc.js' -export const channel = new BroadcastChannel('service-worker-state') +export const channel = new BroadcastChannel('serviceWorker.state') const descriptors = { channel: { @@ -10,6 +11,12 @@ const descriptors = { value: channel }, + clients: { + configurable: false, + enumerable: true, + value: Object.create(null) + }, + notify: { configurable: false, enumerable: false, @@ -17,10 +24,12 @@ const descriptors = { async value (type) { channel.postMessage({ [type]: this[type] }) - if (type === 'serviceWorker') { + if (this.id && type === 'serviceWorker') { await ipc.request('serviceWorker.updateState', { id: this.id, - state: this.serviceWorker.state + scope: this.serviceWorker.scope, + state: this.serviceWorker.state, + scriptURL: this.serviceWorker.scriptURL }) } } @@ -34,7 +43,16 @@ const descriptors = { configurable: false, enumerable: true, writable: true, - value: '/' + value: globalThis.window + ? new URL('.', globalThis.location.href).pathname + : '/' + }, + + scriptURL: { + configurable: false, + enumerable: false, + writable: true, + value: null }, state: { @@ -56,20 +74,6 @@ if (!globalThis.window) { value: null }, - scriptURL: { - configurable: false, - enumerable: false, - writable: true, - value: null - }, - - scope: { - configurable: false, - enumerable: false, - writable: true, - value: null - }, - fetch: { configurable: false, enumerable: false, @@ -95,7 +99,7 @@ if (!globalThis.window) { configurable: false, enumerable: false, writable: true, - value: reportError + value: reportError.bind(globalThis) } }) } @@ -103,12 +107,90 @@ if (!globalThis.window) { export const state = Object.create(null, descriptors) channel.addEventListener('message', (event) => { - if (event.data.serviceWorker) { - const scope = new URL(globalThis.location.href).pathname + if (event.data?.serviceWorker) { + const scope = new URL('.', globalThis.location.href).pathname if (scope.startsWith(event.data.serviceWorker.scope)) { Object.assign(state.serviceWorker, event.data.serviceWorker) } + } else if (event.data?.clients?.get?.id) { + if (event.data.clients.get.id === globalThis.__args.client.id) { + channel.postMessage({ + clients: { + get: { + result: { + client: { + id: globalThis.__args.client.id, + url: globalThis.location.pathname + globalThis.location.search, + type: globalThis.__args.client.type, + index: globalThis.__args.index, + frameType: globalThis.__args.client.frameType + } + } + } + } + }) + } + } else if (event.data?.clients?.matchAll) { + const type = event.data.clients.matchAll?.type ?? 'window' + if (type === 'all' || type === globalThis.__args.type) { + channel.postMessage({ + clients: { + matchAll: { + result: { + client: { + id: globalThis.__args.client.id, + url: globalThis.location.pathname + globalThis.location.search, + type: globalThis.__args.client.type, + index: globalThis.__args.index, + frameType: globalThis.__args.client.frameType + } + } + } + } + }) + } } }) +if (globalThis.document) { + channel.addEventListener('message', async (event) => { + if (event.data?.client?.id === globalThis.__args.client.id) { + if (event.data.client.focus === true) { + const currentWindow = await application.getCurrentWindow() + try { + await currentWindow.restore() + } catch {} + globalThis.focus() + } + } + }) + + globalThis.document.addEventListener('visibilitychange', (event) => { + channel.postMessage({ + client: { + id: globalThis.__args.client.id, + visibilityState: globalThis.document.visibilityState + } + }) + }) + + globalThis.addEventListener('focus', (event) => { + channel.postMessage({ + client: { + id: globalThis.__args.client.id, + focused: true + } + }) + }) + + globalThis.addEventListener('blur', (event) => { + channel.postMessage({ + client: { + id: globalThis.__args.client.id, + focused: false + } + }) + }) +} + export default state diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 4852321c6d..152fa7c22c 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -1,8 +1,10 @@ import { ExtendableEvent, FetchEvent } from './events.js' import { ServiceWorkerGlobalScope } from './global.js' import { InvertedPromise } from '../util.js' +import clients from './clients.js' import hooks from '../hooks.js' import state from './state.js' +import ipc from '../ipc.js' const SERVICE_WORKER_READY_TOKEN = { __service_worker_ready: true } @@ -29,9 +31,8 @@ async function onMessage (event) { let module = null state.id = id - state.scope = scope - state.scriptURL = scriptURL state.serviceWorker.scope = scope + state.serviceWorker.scriptURL = scriptURL try { module = await import(scriptURL) @@ -116,6 +117,12 @@ async function onMessage (event) { }) } + if (!state.activate) { + globalThis.addEventListener('activate', () => { + clients.claim() + }) + } + globalThis.addEventListener('fetch', async (event) => { const { request } = event const context = { @@ -126,6 +133,14 @@ async function onMessage (event) { async handled () { return await event.handled + }, + + async client () { + if (event.clientId) { + return await clients.get(event.clientId) + } + + return null } } @@ -146,7 +161,7 @@ async function onMessage (event) { if (response) { promise.resolve(response) } else { - promise.resolve(new Response('', { status: 400 })) + promise.resolve(new Response('', { status: 404 })) } }) } @@ -178,15 +193,25 @@ async function onMessage (event) { state.serviceWorker.state = 'activated' await state.notify('serviceWorker') events.delete(event) - // TODO(@jwerle): handle 'activate' } - if (data?.fetch) { + if (data?.fetch?.request) { + if (/post|put/i.test(data.fetch.request.method)) { + const result = await ipc.request('serviceWorker.fetch.request.body', { + id: data.fetch.request.id + }) + + if (result.data) { + data.fetch.request.body = result.data + } + } + const event = new FetchEvent('fetch', { clientId: data.fetch.client.id, fetchId: data.fetch.request.id, request: new Request(data.fetch.request.url, { - method: data.fetch.request.method ?? 'GET' + method: data.fetch.request.method ?? 'GET', + body: data.fetch.request.body }) }) From 2cf0d0835f7fad2fef9d89d69f4e0f1ff0c7996f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:37:35 +0100 Subject: [PATCH 0020/1178] chore(api/vm/worker.js): clean lint --- api/vm/worker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/vm/worker.js b/api/vm/worker.js index eb7af5248c..639e875e97 100644 --- a/api/vm/worker.js +++ b/api/vm/worker.js @@ -1,4 +1,3 @@ -/* global reportError */ const Uint8ArrayPrototype = Uint8Array.prototype const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) const TypedArray = TypedArrayPrototype.constructor From 125e7b94de6310fc1f8c673b302b1da5f52c1009 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:37:59 +0100 Subject: [PATCH 0021/1178] refactor(api/window.js): setup broadcast channel for windows --- api/window.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/api/window.js b/api/window.js index 106cc62801..252e3ca32b 100644 --- a/api/window.js +++ b/api/window.js @@ -32,8 +32,10 @@ export function formatURL (url) { * Represents a window in the application */ export class ApplicationWindow { + #id = null #index #options + #channel = null #senderWindowIndex = globalThis.__args.index #listeners = {} // TODO(@chicoxyzzy): add parent and children? (needs native process support) @@ -42,8 +44,10 @@ export class ApplicationWindow { static hotkey = hotkey constructor ({ index, ...options }) { + this.#id = options?.id this.#index = index this.#options = options + this.#channel = new BroadcastChannel(`window.${this.#id}`) } #updateOptions (response) { @@ -51,11 +55,20 @@ export class ApplicationWindow { if (err) { throw new Error(err) } - const { index, ...options } = data + const { id, index, ...options } = data + this.#id = id ?? null this.#options = options return data } + /** + * The unique ID of this window. + * @type {string} + */ + get id () { + return this.#id + } + /** * Get the index of the window * @return {number} - the index of the window @@ -71,6 +84,14 @@ export class ApplicationWindow { return hotkey } + /** + * The broadcast channel for this window. + * @type {BroadcastChannel} + */ + get channel () { + return this.#channel + } + /** * Get the size of the window * @return {{ width: number, height: number }} - the size of the window From ad8cf4fd02c80b38b8d3c748261569f0d087cea5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:38:44 +0100 Subject: [PATCH 0022/1178] refactor(src/android): use module based preload, set client ID --- src/android/webview.kt | 2 +- src/android/window.cc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/android/webview.kt b/src/android/webview.kt index eed7103164..8dae4a9fc5 100644 --- a/src/android/webview.kt +++ b/src/android/webview.kt @@ -511,7 +511,7 @@ export default module } var html = String(assetStream.readAllBytes()) - val script = """""" + val script = """""" val stream = java.io.PipedOutputStream() val response = android.webkit.WebResourceResponse( "text/html", diff --git a/src/android/window.cc b/src/android/window.cc index 9d4b42e0d8..6c0f2694d1 100644 --- a/src/android/window.cc +++ b/src/android/window.cc @@ -51,9 +51,10 @@ namespace SSC::android { options.appData = this->config; options.argv = argv; options.isTest = argv.find("--test") != -1; + options.clientId = this->bridge->id; preloadSource = createPreload(options, PreloadOptions { - .module = false + .module = true }); } From c8edca706e5ae38a4c4834bad3355a7a6c754e6b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:40:48 +0100 Subject: [PATCH 0023/1178] refactor(src/window): configure client id --- src/window/apple.mm | 101 +++++++++++++++++++++--------------------- src/window/linux.cc | 1 + src/window/options.hh | 1 + src/window/win.cc | 29 +++++++----- src/window/window.hh | 14 ++++++ 5 files changed, 84 insertions(+), 62 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 236ef20852..5429a15814 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -2,9 +2,23 @@ #include "../ipc/ipc.hh" @implementation SSCNavigationDelegate +- (void) webView: (WKWebView*) webView + didFailNavigation: (WKNavigation*) navigation + withError: (NSError*) error +{ + // TODO(@jwerle) +} + +- (void) webView: (WKWebView*) webView + didFailProvisionalNavigation: (WKNavigation*) navigation + withError: (NSError*) error { + // TODO(@jwerle) +} + - (void) webView: (WKWebView*) webview decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction - decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler { + decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler +{ if ( webview.URL.absoluteString.UTF8String != nullptr && navigationAction.request.URL.absoluteString.UTF8String != nullptr @@ -58,23 +72,12 @@ - (void) webView: (WKWebView*) webview } } - if (request.starts_with("socket:")) { - self.bridge->router.isReady = false; - decisionHandler(WKNavigationActionPolicyAllow); + if (!request.starts_with("socket:") && !request.starts_with(devHost)) { + decisionHandler(WKNavigationActionPolicyCancel); return; } - - if (request.starts_with(devHost)) { - self.bridge->router.isReady = false; - decisionHandler(WKNavigationActionPolicyAllow); - return; - } - - decisionHandler(WKNavigationActionPolicyCancel); - return; } - self.bridge->router.isReady = false; decisionHandler(WKNavigationActionPolicyAllow); } @@ -196,7 +199,6 @@ - (void) draggingEnded: (id) info { }; const auto json = SSC::JSON::Object { data }; - debug("files: %s", json.str().c_str()); const auto payload = SSC::getEmitToRenderProcessJavaScript("dropin", json.str()); [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; @@ -880,70 +882,69 @@ - (void) webView: (WKWebView*) webView config.defaultWebpagePreferences.allowsContentJavaScript = YES; config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; - config.websiteDataStore = [WKWebsiteDataStore defaultDataStore]; + config.websiteDataStore = WKWebsiteDataStore.defaultDataStore; config.processPool = [WKProcessPool new]; - /** [config.websiteDataStore.httpCookieStore - setCookiePolicy: WKCookiePolicyAllow + setCookiePolicy: WKCookiePolicyAllow completionHandler: ^(){} ]; - */ - - @try { - [prefs setValue: @YES forKey: @"offlineApplicationCacheIsEnabled"]; - } @catch (NSException *error) { - debug("Failed to set preference: 'offlineApplicationCacheIsEnabled': %@", error); - } - - WKUserContentController* controller = config.userContentController; - - // Add preload script, normalizing the interface to be cross-platform. - SSC::String preload = createPreload(opts); - - WKUserScript* userScript = [WKUserScript alloc]; - - [userScript - initWithSource: @(preload.c_str()) - injectionTime: WKUserScriptInjectionTimeAtDocumentStart - forMainFrameOnly: NO - ]; - [controller addUserScript: userScript]; - - webview = [SSCBridgedWebView.alloc - initWithFrame: NSZeroRect - configuration: config - ]; - - [webview.configuration + [config setValue: @YES forKey: @"allowUniversalAccessFromFileURLs" ]; - [webview.configuration.preferences + [config.preferences setValue: @YES forKey: @"allowFileAccessFromFileURLs" ]; - [webview.configuration.processPool + [config.processPool performSelector: @selector(_registerURLSchemeAsSecure:) withObject: @"socket" ]; - [webview.configuration.processPool + [config.processPool performSelector: @selector(_registerURLSchemeAsSecure:) withObject: @"ipc" ]; static const auto devHost = SSC::getDevHost(); if (devHost.starts_with("http:")) { - [webview.configuration.processPool + [config.processPool performSelector: @selector(_registerURLSchemeAsSecure:) withObject: @"http" ]; } + @try { + [prefs setValue: @YES forKey: @"offlineApplicationCacheIsEnabled"]; + } @catch (NSException *error) { + debug("Failed to set preference: 'offlineApplicationCacheIsEnabled': %@", error); + } + + WKUserContentController* controller = config.userContentController; + + opts.clientId = this->bridge->id; + + // Add preload script, normalizing the interface to be cross-platform. + this->bridge->preload = createPreload(opts, { .module = true }); + + webview = [SSCBridgedWebView.alloc + initWithFrame: NSZeroRect + configuration: config + ]; + + const auto processInfo = NSProcessInfo.processInfo; + webview.customUserAgent = [NSString + stringWithFormat: @("Mozilla/5.0 (Macintosh; Intel Mac OS X %d_%d_%d) AppleWebKit/605.1.15 (KHTML, like Gecko) SocketRuntime/%s"), + processInfo.operatingSystemVersion.majorVersion, + processInfo.operatingSystemVersion.minorVersion, + processInfo.operatingSystemVersion.patchVersion, + SSC::VERSION_STRING.c_str() + ]; + /* [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground" diff --git a/src/window/linux.cc b/src/window/linux.cc index 6b4435426e..21b8fd43e5 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -800,6 +800,7 @@ namespace SSC { this ); + opts.clientId = this->bridge->id; String preload = createPreload(opts); WebKitUserContentManager *manager = diff --git a/src/window/options.hh b/src/window/options.hh index 9cc96fcdcc..ed9709b21b 100644 --- a/src/window/options.hh +++ b/src/window/options.hh @@ -29,6 +29,7 @@ namespace SSC { Map appData; MessageCallback onMessage = [](const String) {}; ExitCallback onExit = nullptr; + uint64_t clientId = 0; }; } #endif diff --git a/src/window/win.cc b/src/window/win.cc index a8e8351589..b941d98e64 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -810,11 +810,11 @@ namespace SSC { return TRUE; }, (LPARAM)window); - reinterpret_cast(webview)->SetVirtualHostNameToFolderMapping( - convertStringToWString(userConfig["meta_bundle_identifier"]).c_str(), - this->modulePath.parent_path().c_str(), - COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW - ); + reinterpret_cast(webview)->SetVirtualHostNameToFolderMapping( + convertStringToWString(userConfig["meta_bundle_identifier"]).c_str(), + this->modulePath.parent_path().c_str(), + COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW + ); EventRegistrationToken tokenNavigation; @@ -855,15 +855,15 @@ namespace SSC { ICoreWebView2_22* webview22 = nullptr; webview->QueryInterface(IID_PPV_ARGS(&webview22)); - if (webview22 != nullptr) { - webview22->AddWebResourceRequestedFilterWithRequestSourceKinds( + if (webview22 != nullptr) { + webview22->AddWebResourceRequestedFilterWithRequestSourceKinds( L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL, COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL ); - debug("Configured CoreWebView2 (ICoreWebView2_22) request filter with all request source kinds"); - } + debug("Configured CoreWebView2 (ICoreWebView2_22) request filter with all request source kinds"); + } webview->add_WebResourceRequested( Microsoft::WRL::Callback( @@ -915,6 +915,7 @@ namespace SSC { 204, L"OK", L"Connection: keep-alive\n" + L"Cache-Control: no-cache\n" L"Access-Control-Allow-Headers: *\n" L"Access-Control-Allow-Origin: *\n" L"Access-Control-Allow-Methods: GET, POST, PUT, HEAD\n", @@ -980,6 +981,7 @@ namespace SSC { } headers += "Connection: keep-alive\n"; + headers += "Cache-Control: no-cache\n"; headers += "Access-Control-Allow-Headers: *\n"; headers += "Access-Control-Allow-Origin: *\n"; headers += "Content-Length: "; @@ -1023,9 +1025,9 @@ namespace SSC { : "socket/" + uri ); - const auto parts = split(path, '?'); - const auto query = parts.size() > 1 ? String("?") + parts[1] : ""; - path = parts[0]; + const auto parts = split(path, '?'); + const auto query = parts.size() > 1 ? String("?") + parts[1] : ""; + path = parts[0]; auto ext = fs::path(path).extension().string(); @@ -1059,6 +1061,7 @@ namespace SSC { auto length = moduleSource.size(); headers = "Content-Type: text/javascript\n"; + headers += "Cache-Control: no-cache\n"; headers += "Connection: keep-alive\n"; headers += "Access-Control-Allow-Headers: *\n"; headers += "Access-Control-Allow-Origin: *\n"; @@ -1225,6 +1228,7 @@ namespace SSC { headers = "Content-Type: "; headers += convertWStringToString(mimeType) + "\n"; headers += "Connection: keep-alive\n"; + headers += "Cache-Control: no-cache\n"; headers += "Access-Control-Allow-Headers: *\n"; headers += "Access-Control-Allow-Origin: *\n"; headers += "Content-Length: "; @@ -1295,6 +1299,7 @@ namespace SSC { WindowOptions options = opts; webview->QueryInterface(IID_PPV_ARGS(&webview22)); options.appData["env_COREWEBVIEW2_22_AVAILABLE"] = webview22 != nullptr ? "true" : ""; + options.clientId = this->bridge->id; auto preload = createPreload(options); webview->AddScriptToExecuteOnDocumentCreated( // Note that this may not do anything as preload goes out of scope before event fires diff --git a/src/window/window.hh b/src/window/window.hh index cb45b32bd3..fabf97e707 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -114,6 +114,14 @@ sourceOperationMaskForDraggingContext: (NSDraggingContext) context; @interface SSCNavigationDelegate : NSObject @property (nonatomic) SSC::IPC::Bridge* bridge; +- (void) webView: (WKWebView*) webView + didFailNavigation: (WKNavigation*) navigation + withError: (NSError*) error; + +- (void) webView: (WKWebView*) webView + didFailProvisionalNavigation: (WKNavigation*) navigation + withError: (NSError*) error; + - (void) webView: (WKWebView*) webview decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler; @@ -385,8 +393,14 @@ namespace SSC { JSON::Object json () { auto index = this->opts.index; auto size = this->getSize(); + uint64_t id = 0; + + if (this->bridge != nullptr) { + id = this->bridge->id; + } return JSON::Object::Entries { + { "id", std::to_string(id) }, { "index", index }, { "title", this->getTitle() }, { "width", size.width }, From 0724888095096f3dc0a6907115fa7868ae5aa164 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:46:04 +0100 Subject: [PATCH 0024/1178] refactor(src/ipc): inject preload into HTML, clean up --- src/desktop/main.cc | 2 +- src/ipc/bridge.cc | 140 ++++++++++++++++++++++++-------------------- src/ipc/ipc.hh | 14 ++++- 3 files changed, 90 insertions(+), 66 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index f0eea518bb..b61a57b1bf 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1488,7 +1488,7 @@ MAIN { std::cin >> value; } - if (value.size() > 0 && defaultWindow->bridge->router.isReady) { + if (value.size() > 0) { defaultWindow->eval(getEmitToRenderProcessJavaScript("process.stdin", value)); value.clear(); } diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 54e3a9ee43..2290e6c44e 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2002,16 +2002,12 @@ static void initRouterTable (Router *router) { * @param data Optional data associated with the platform event. */ router->map("platform.event", [](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"value"}); + const auto err = validateMessageParameters(message, {"value"}); if (err.type != JSON::Type::Null) { return reply(Result { message.seq, message, err }); } - if (message.value == "runtimeinit") { - router->isReady = true; - } - router->core->platform.event( message.seq, message.value, @@ -2171,12 +2167,6 @@ static void initRouterTable (Router *router) { reply(Result::Data { message, JSON::Object {}}); }); - /** - * TODO - */ - router->map("serviceWorker.clients.claim", [](auto message, auto router, auto reply) { - }); - /** * TODO */ @@ -2202,26 +2192,10 @@ static void initRouterTable (Router *router) { const auto registration = router->core->serviceWorker.registerServiceWorker(options); auto json = JSON::Object { JSON::Object::Entries { - {"registration", registration.json()}, - {"client", JSON::Object::Entries { - {"id", std::to_string(router->bridge->id)} - }} + {"registration", registration.json()} } }; - auto& clients = router->core->serviceWorker.registrations.at(options.scope).clients; - bool clientExists = false; - for (const auto& client : clients) { - if (client.id == router->bridge->id) { - clientExists = true; - break; - } - } - - if (!clientExists) { - clients.push_back(ServiceWorkerContainer::Client { router->bridge->id }); - } - reply(Result::Data { message, json }); }); @@ -2651,6 +2625,7 @@ static void registerSchemeHandler (Router *router) { auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); auto response = webkit_uri_scheme_response_new(stream, size); + soup_message_headers_append(headers, "cache-control", "no-cache"); for (const auto& header : result.headers.entries) { soup_message_headers_append(headers, header.key.c_str(), header.value.c_str()); } @@ -2859,9 +2834,11 @@ static void registerSchemeHandler (Router *router) { auto response = webkit_uri_scheme_response_new(stream, (gint64) size); auto webviewHeaders = split(userConfig["webview_headers"], '\n'); + soup_message_headers_append(headers, "cache-control", "no-cache"); soup_message_headers_append(headers, "access-control-allow-origin", "*"); soup_message_headers_append(headers, "access-control-allow-methods", "*"); soup_message_headers_append(headers, "access-control-allow-headers", "*"); + soup_message_headers_append(headers, "access-control-allow-credentials", "true"); for (const auto& line : webviewHeaders) { auto pair = split(trim(line), ':'); @@ -2960,13 +2937,24 @@ static void registerSchemeHandler (Router *router) { static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; static auto fileManager = [[NSFileManager alloc] init]; - auto request = task.request; - auto url = String(request.URL.absoluteString.UTF8String); + const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); + const auto request = task.request; + const auto url = String(request.URL.absoluteString.UTF8String); + + uint64_t clientId = self.router->bridge->id; + + if (request.allHTTPHeaderFields != nullptr) { + if (request.allHTTPHeaderFields[@"runtime-client-id"] != nullptr) { + try { + clientId = std::stoull(request.allHTTPHeaderFields[@"runtime-client-id"].UTF8String); + } catch (...) {} + } + } + auto message = Message(url, true); message.isHTTP = true; message.cancel = std::make_shared(); - auto webviewHeaders = split(userConfig["webview_headers"], '\n'); auto headers = [NSMutableDictionary dictionary]; for (const auto& line : webviewHeaders) { @@ -2976,9 +2964,11 @@ static void registerSchemeHandler (Router *router) { headers[key] = value; } + headers[@"cache-control"] = @"no-cache"; headers[@"access-control-allow-origin"] = @"*"; headers[@"access-control-allow-methods"] = @"*"; headers[@"access-control-allow-headers"] = @"*"; + headers[@"access-control-allow-credentials"] = @"true"; if (String(request.HTTPMethod.UTF8String) == "OPTIONS") { auto response = [[NSHTTPURLResponse alloc] @@ -3120,10 +3110,12 @@ static void registerSchemeHandler (Router *router) { } else { if (self.router->core->serviceWorker.registrations.size() > 0) { auto fetchRequest = ServiceWorkerContainer::FetchRequest { - String(request.URL.path.UTF8String) + String(request.URL.path.UTF8String), + String(request.HTTPMethod.UTF8String) }; - fetchRequest.client.id = self.router->bridge->id; + fetchRequest.client.id = clientId; + if (request.URL.query != nullptr) { fetchRequest.query = String(request.URL.query.UTF8String); } @@ -3138,7 +3130,13 @@ static void registerSchemeHandler (Router *router) { } } + [self enqueueTask: task withMessage: message]; + const auto fetched = self.router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) { + if (![self waitingForTask: task]) { + return; + } + auto headers = [NSMutableDictionary dictionary]; auto webviewHeaders = split(userConfig["webview_headers"], '\n'); @@ -3149,9 +3147,11 @@ static void registerSchemeHandler (Router *router) { headers[key] = value; } + headers[@"access-control-allow-credentials"] = @"true"; headers[@"access-control-allow-origin"] = @"*"; headers[@"access-control-allow-methods"] = @"*"; headers[@"access-control-allow-headers"] = @"*"; + headers[@"cache-control"] = @"no-cache"; for (const auto& entry : res.headers) { auto pair = split(trim(entry), ':'); @@ -3316,28 +3316,40 @@ static void registerSchemeHandler (Router *router) { components.scheme = @("socket"); headers[@"content-location"] = components.URL.absoluteString; - const auto absoluteURL = String(components.URL.absoluteString.UTF8String); const auto socketModulePrefix = "socket://" + userConfig["meta_bundle_identifier"] + "/socket/"; + const auto absoluteURL = String(components.URL.absoluteString.UTF8String); + const auto absoluteURLPathExtension = components.URL.pathExtension != nullptr + ? String(components.URL.pathExtension.UTF8String) + : String(""); + + if (!isModule && absoluteURL.starts_with(socketModulePrefix)) { isModule = true; } - dispatch_async(queue, ^{ - if ( - !isModule && ( - absoluteURL.ends_with(".js") || - absoluteURL.ends_with(".cjs") || - absoluteURL.ends_with(".mjs") - ) - ) { - while (!self.router->isReady) { - msleep(1); - } + if (absoluteURLPathExtension.ends_with("html")) { + const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; + auto html = String(string.UTF8String); + const auto script = String( + "\n\n" + ); + + if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else { + html = script + html; } - auto statusCode = exists ? 200 : 404; - auto response = [NSHTTPURLResponse.alloc + data = [@(html.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; + } + + const auto statusCode = exists ? 200 : 404; + const auto response = [NSHTTPURLResponse.alloc initWithURL: components.URL statusCode: statusCode HTTPVersion: @"HTTP/1.1" @@ -3355,7 +3367,6 @@ static void registerSchemeHandler (Router *router) { #if !__has_feature(objc_arc) [response release]; #endif - }); return; } @@ -3427,9 +3438,11 @@ static void registerSchemeHandler (Router *router) { auto id = result.id; auto headers = [NSMutableDictionary dictionary]; + headers[@"cache-control"] = @"no-cache"; headers[@"access-control-allow-origin"] = @"*"; headers[@"access-control-allow-methods"] = @"*"; headers[@"access-control-allow-headers"] = @"*"; + headers[@"access-control-allow-credentials"] = @"true"; for (const auto& header : result.headers.entries) { auto key = [NSString stringWithUTF8String: trim(header.key).c_str()]; @@ -3438,8 +3451,8 @@ static void registerSchemeHandler (Router *router) { } NSData* data = nullptr; - if (result.post.event_stream != nullptr) { - *result.post.event_stream = [=]( + if (result.post.eventStream != nullptr) { + *result.post.eventStream = [=]( const char* name, const char* data, bool finished @@ -3448,17 +3461,17 @@ static void registerSchemeHandler (Router *router) { return false; } - auto event_name = [NSString stringWithUTF8String: name]; - auto event_data = [NSString stringWithUTF8String: data]; + auto eventName = [NSString stringWithUTF8String: name]; + auto eventData = [NSString stringWithUTF8String: data]; - if (event_name.length > 0 || event_data.length > 0) { - auto event = - event_name.length > 0 && event_data.length > 0 - ? [NSString stringWithFormat:@"event: %@\ndata: %@\n\n", - event_name, event_data] - : event_data.length > 0 - ? [NSString stringWithFormat:@"data: %@\n\n", event_data] - : [NSString stringWithFormat:@"event: %@\n\n", event_name]; + if (eventName.length > 0 || eventData.length > 0) { + auto event = eventName.length > 0 && eventData.length > 0 + ? [NSString stringWithFormat: + @"event: %@\ndata: %@\n\n", eventName, eventData + ] + : eventData.length > 0 + ? [NSString stringWithFormat: @"data: %@\n\n", eventData] + : [NSString stringWithFormat: @"event: %@\n\n", eventName]; [task didReceiveData: [event dataUsingEncoding:NSUTF8StringEncoding]]; } @@ -3472,8 +3485,8 @@ static void registerSchemeHandler (Router *router) { }; headers[@"content-type"] = @"text/event-stream"; headers[@"cache-control"] = @"no-store"; - } else if (result.post.chunk_stream != nullptr) { - *result.post.chunk_stream = [=]( + } else if (result.post.chunkStream != nullptr) { + *result.post.chunkStream = [=]( const char* chunk, size_t chunk_size, bool finished @@ -3541,6 +3554,7 @@ static void registerSchemeHandler (Router *router) { auto str = [NSString stringWithUTF8String: msg.c_str()]; auto data = [str dataUsingEncoding: NSUTF8StringEncoding]; + headers[@"access-control-allow-credentials"] = @"true"; headers[@"access-control-allow-origin"] = @"*"; headers[@"access-control-allow-headers"] = @"*"; headers[@"content-length"] = [@(msg.size()) stringValue]; @@ -3782,8 +3796,8 @@ static void registerSchemeHandler (Router *router) { - (void) locationManager: (CLLocationManager*) locationManager didFinishDeferredUpdatesWithError: (NSError*) error { - debug("locationManager:didFinishDeferredUpdatesWithError: %@", error); // TODO(@jwerle): handle deferred error + debug("locationManager:didFinishDeferredUpdatesWithError: %@", error); } - (void) locationManagerDidPauseLocationUpdates: (CLLocationManager*) locationManager { diff --git a/src/ipc/ipc.hh b/src/ipc/ipc.hh index 6e6eb73c93..2fa344c11f 100644 --- a/src/ipc/ipc.hh +++ b/src/ipc/ipc.hh @@ -117,6 +117,15 @@ namespace SSC::IPC { #endif namespace SSC::IPC { + struct Client { + struct CurrentRequest { + String url; + }; + + String id; + CurrentRequest currentRequest; + }; + struct MessageBuffer { size_t size = 0; char* bytes = nullptr; @@ -145,6 +154,8 @@ namespace SSC::IPC { public: using Seq = String; MessageBuffer buffer; + Client client; + String value = ""; String name = ""; String uri = ""; @@ -268,7 +279,6 @@ namespace SSC::IPC { Listeners listeners; Core *core = nullptr; Bridge *bridge = nullptr; - AtomicBool isReady = false; #if defined(__APPLE__) SSCIPCNetworkStatusObserver* networkStatusObserver = nullptr; SSCLocationObserver* locationObserver = nullptr; @@ -320,8 +330,8 @@ namespace SSC::IPC { Router router; Bluetooth bluetooth; Core *core = nullptr; + String preload = ""; uint64_t id = 0; - // AtomicBool isReady = false; #if !defined(__ANDROID__) && (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR)) FileSystemWatcher* fileSystemWatcher = nullptr; #endif From 38cdc40e84ccb88adbc51b9352eda5afe1891720 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:46:38 +0100 Subject: [PATCH 0025/1178] refactor(src/ios): clean up, set user agent --- src/ios/main.mm | 86 +++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/src/ios/main.mm b/src/ios/main.mm index 7560967150..e20dcad795 100644 --- a/src/ios/main.mm +++ b/src/ios/main.mm @@ -271,15 +271,20 @@ - (BOOL) application: (UIApplication*) application }; core->serviceWorker.init(bridge); - auto appFrame = [[UIScreen mainScreen] bounds]; - self.window = [[UIWindow alloc] initWithFrame: appFrame]; + auto userConfig = SSC::getUserConfig(); + const auto resourcePath = NSBundle.mainBundle.resourcePath; + const auto cwd = [resourcePath stringByAppendingPathComponent: @"ui"]; + const auto argv = userConfig["ssc_argv"]; + const auto appFrame = UIScreen.mainScreen.bounds; + const auto viewController = [UIViewController new]; + const auto config = [WKWebViewConfiguration new]; + const auto processInfo = NSProcessInfo.processInfo; - UIViewController *viewController = [[UIViewController alloc] init]; viewController.view.frame = appFrame; - self.window.rootViewController = viewController; - auto userConfig = SSC::getUserConfig(); + self.window = [UIWindow.alloc initWithFrame: appFrame]; + self.window.rootViewController = viewController; StringStream env; @@ -300,12 +305,6 @@ - (BOOL) application: (UIApplication*) application env << String("width=" + std::to_string(appFrame.size.width) + "&"); env << String("height=" + std::to_string(appFrame.size.height) + "&"); - NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; - NSString* cwd = [resourcePath stringByAppendingPathComponent: @"ui"]; - const auto argv = userConfig["ssc_argv"]; - - uv_chdir(cwd.UTF8String); - WindowOptions opts { .debug = isDebugEnabled(), .isTest = argv.find("--test") != -1, @@ -314,48 +313,57 @@ - (BOOL) application: (UIApplication*) application .appData = userConfig }; + opts.clientId = bridge->id; // Note: you won't see any logs in the preload script before the // Web Inspector is opened - String preload = createPreload(opts); - - WKUserScript* initScript = [[WKUserScript alloc] - initWithSource: [NSString stringWithUTF8String: preload.c_str()] - injectionTime: WKUserScriptInjectionTimeAtDocumentStart - forMainFrameOnly: NO]; - - WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"ipc"]; + bridge->preload = createPreload(opts, { + .module = true + }); - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"socket"]; + uv_chdir(cwd.UTF8String); - self.content = [config userContentController]; + [config setValue: @YES forKey: @"allowUniversalAccessFromFileURLs"]; - [self.content addScriptMessageHandler:self name: @"external"]; - [self.content addUserScript: initScript]; + [config + setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"ipc" + ]; - self.webview = [[SSCBridgedWebView alloc] initWithFrame: appFrame configuration: config]; - self.webview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [config + setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"socket" + ]; - WKPreferences* prefs = self.webview.configuration.preferences; + self.content = config.userContentController; - [self.webview.configuration - setValue: @YES - forKey: @"allowUniversalAccessFromFileURLs" + [self.content + addScriptMessageHandler: self + name: @"external" ]; - [self.webview.configuration.preferences - setValue: @YES - forKey: @"allowFileAccessFromFileURLs" + self.webview = [SSCBridgedWebView.alloc + initWithFrame: appFrame + configuration: config ]; - [self.webview.configuration.preferences - setValue: @YES - forKey: @"javaScriptEnabled" + self.webview.autoresizingMask = ( + UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight + ); + + self.webview.customUserAgent = [NSString + stringWithFormat: @("Mozilla/5.0 (iPhone; CPU iPhone OS %d_%d_%d like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1 SocketRuntime/%s"), + processInfo.operatingSystemVersion.majorVersion, + processInfo.operatingSystemVersion.minorVersion, + processInfo.operatingSystemVersion.patchVersion, + SSC::VERSION_STRING.c_str() ]; + const auto prefs = self.webview.configuration.preferences; + + [prefs setValue: @YES forKey: @"allowFileAccessFromFileURLs" ]; + [prefs setValue: @YES forKey: @"javaScriptEnabled" ]; + if (userConfig["permissions_allow_fullscreen"] == "false") { [prefs setValue: @NO forKey: @"fullScreenEnabled"]; } else { From c47a5b334bdbf05891bde2c37a48b94c0ec810ab Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:47:13 +0100 Subject: [PATCH 0026/1178] Refactor(src/core): handle 'module' type preload, inject client info, improve SW --- src/core/core.hh | 16 +++- src/core/preload.cc | 86 ++++++++++++++++++---- src/core/service_worker_container.cc | 106 +++++++++++++++++++++------ src/core/service_worker_container.hh | 15 ++-- 4 files changed, 178 insertions(+), 45 deletions(-) diff --git a/src/core/core.hh b/src/core/core.hh index b1c45d8696..358b8c4828 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -105,14 +105,26 @@ namespace SSC { }; struct Post { + using EventStreamCallback = std::function; + + using ChunkStreamCallback = std::function; + uint64_t id = 0; uint64_t ttl = 0; char* body = nullptr; size_t length = 0; String headers = ""; String workerId = ""; - std::shared_ptr> event_stream; - std::shared_ptr> chunk_stream; + std::shared_ptr eventStream; + std::shared_ptr chunkStream; }; using Posts = std::map; diff --git a/src/core/preload.cc b/src/core/preload.cc index a0f737d81a..0f7d64b198 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -1,12 +1,15 @@ #include "codec.hh" +#include "core.hh" #include "preload.hh" #include "string.hh" +#include "config.hh" namespace SSC { String createPreload ( const WindowOptions opts, const PreloadOptions preloadOptions ) { + static auto userConfig = SSC::getUserConfig(); auto argv = opts.argv; #ifdef _WIN32 // Escape backslashes in paths. @@ -27,11 +30,32 @@ namespace SSC { " value: [" +argv + "], \n" " enumerable: true \n" " }, \n" + " client: { \n" + " configurable: false, \n" + " enumerable: true, \n" + " writable: false, \n" + " value: { \n" + " id: globalThis.window && globalThis.top !== globalThis.window \n" + " ? '" + std::to_string(rand64()) + "' \n" + " : globalThis.window && globalThis.top \n" + " ? '" + std::to_string(opts.clientId) + "' \n" + " : null, \n" + " type: globalThis.window \n" + " ? 'window' \n" + " : 'worker', \n" + " frameType: \n" + " globalThis.window && globalThis.top !== globalThis.window \n" + " ? 'nested' \n" + " : globalThis.window && globalThis.top \n" + " ? 'top-level' \n" + " : 'none' \n" + " } \n" + " }, \n" " config: { \n" - " value: {}, \n" + " configurable: false, \n" " enumerable: true, \n" - " writable: true, \n" - " configurable: true \n" + " writable: false, \n" + " value: {} \n" " }, \n" " debug: { \n" " value: Boolean(" + std::to_string(opts.debug) + "), \n" @@ -89,12 +113,19 @@ namespace SSC { " } \n" " }); \n" " \n" - " globalThis.addEventListener(' __runtime_init__', () => { \n" + " globalThis.addEventListener('__runtime_init__', () => { \n" " if (Array.isArray(APPLICATION_URL_EVENT_BACKLOG)) { \n" " for (const event of APPLICATION_URL_EVENT_BACKLOG) { \n" - " globalThis.dispatchEvent(event); \n" + " globalThis.dispatchEvent( \n" + " new ApplicationURLEvent(event.type, event) \n" + " ); \n" " } \n" " } \n" + " \n" + " APPLICATION_URL_EVENT_BACKLOG.splice( \n" + " 0, \n" + " APPLICATION_URL_EVENT_BACKLOG.length - 1 \n" + " ); \n" " }, { once: true }); \n" ); @@ -104,9 +135,9 @@ namespace SSC { opts.appData.at("webview_watch_reload") != "false" ) { preload += ( - " globalThis.addEventListener('filedidchange', () => { \n" - " location.reload() \n" - " }); \n" + " globalThis.addEventListener('filedidchange', () => { \n" + " location.reload() \n" + " }); \n" ); } } @@ -158,6 +189,7 @@ namespace SSC { } preload += ( + " Object.freeze(globalThis.__args.client); \n" " Object.freeze(globalThis.__args.config); \n" " Object.freeze(globalThis.__args.argv); \n" " Object.freeze(globalThis.__args.env); \n" @@ -185,17 +217,39 @@ namespace SSC { ); preload += ( - "if (document.readyState === 'complete') { \n" - " import('socket:internal/init').catch(console.error); \n" - "} else { \n" - " document.addEventListener('readystatechange', () => { \n" - " if (/interactive|complete/.test(document.readyState)) { \n" - " import('socket:internal/init').catch(console.error); \n" - " } \n" - " }, { once: true }); \n" + "function __postMessage__ (...args) { \n" + " if (globalThis?.webkit?.messageHandlers?.external?.postMessage) { \n" + " return webkit.messageHandlers.external.postMessage(...args) \n" + " } else if (globalThis?.chrome?.webview?.postMessage) { \n" + " return chrome.webview.postMessage(...args) \n" + " } else if (globalThis?.external?.postMessage) { \n" + " return external.postMessage(...args) \n" + " } \n" + " \n" + " throw new TypeError( \n" + " 'Could not determine postMessage during init preload.' \n" + " ) \n" "} \n" ); + if (preloadOptions.module) { + preload += ( + "\nimport 'socket:internal/init' \n" + ); + } else { + preload += ( + "if (document.readyState === 'complete') { \n" + " import('socket:internal/init').catch(console.error); \n" + "} else { \n" + " document.addEventListener('readystatechange', () => { \n" + " if (/interactive|complete/.test(document.readyState)) { \n" + " import('socket:internal/init').catch(console.error); \n" + " } \n" + " }, { once: true }); \n" + "} \n" + ); + } + return preload; } } diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index ea3ebbfdad..237aae4c0e 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -2,6 +2,11 @@ #include "../ipc/ipc.hh" +// TODO(@jwerle): create a better platform macro to drop this garbage below +#if defined(_WIN32) || (defined(__linux__) && !defined(__ANDROID__)) || (defined(__APPLE__) && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) +#include "../app/app.hh" +#endif + namespace SSC { static IPC::Bridge* serviceWorkerBridge = nullptr; @@ -41,7 +46,40 @@ namespace SSC { } void ServiceWorkerContainer::init (IPC::Bridge* bridge) { + Lock lock(this->mutex); + this->bridge = bridge; + this->bridge->router.map("serviceWorker.fetch.request.body", [this](auto message, auto router, auto reply) mutable { + uint64_t id = 0; + + try { + id = std::stoull(message.get("id")); + } catch (...) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"message", "Invalid 'id' given in parameters"} + }}); + } + + do { + Lock lock(this->mutex); + if (!this->fetchRequests.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} + }}); + } + } while (0); + + const auto& request = this->fetchRequests.at(id); + const auto post = Post { 0, 0, request.buffer.bytes, request.buffer.size }; + reply(IPC::Result { message.seq, message, JSON::Object {}, post }); + + do { + Lock lock(this->mutex); + this->fetchRequests.erase(id); + } while (0); + }); + this->bridge->router.map("serviceWorker.fetch.response", [this](auto message, auto router, auto reply) mutable { uint64_t clientId = 0; uint64_t id = 0; @@ -63,14 +101,17 @@ namespace SSC { }}); } - if (!this->fetches.contains(id)) { - return reply(IPC::Result::Err { message, JSON::Object::Entries { - {"type", "NotFoundError"}, - {"message", "Callback 'id' given in parameters does not have a 'FetchCallback'"} - }}); - } + do { + Lock lock(this->mutex); + if (!this->fetchCallbacks.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchCallback'"} + }}); + } + } while (0); - const auto callback = this->fetches.at(id); + const auto callback = this->fetchCallbacks.at(id); try { statusCode = std::stoi(message.get("statusCode")); @@ -89,33 +130,39 @@ namespace SSC { { clientId } }; - this->fetches.erase(id); + // no `app` pointer or on mobile, just call callback callback(response); + reply(IPC::Result { message.seq, message }); + + do { + Lock lock(this->mutex); + this->fetchCallbacks.erase(id); + if (this->fetchRequests.contains(id)) { + this->fetchRequests.erase(id); + } + } while(0); }); } - const ServiceWorkerContainer::Registration ServiceWorkerContainer::registerServiceWorker ( + const ServiceWorkerContainer::Registration& ServiceWorkerContainer::registerServiceWorker ( const RegistrationOptions& options ) { + Lock lock(this->mutex); + if (this->registrations.contains(options.scope)) { auto& registration = this->registrations.at(options.scope); - if (this->bridge != nullptr) { - this->bridge->router.emit("serviceWorker.register", registration.json().str()); - } - return registration; } - const auto id = rand64(); - const auto registration = Registration { - id, + this->registrations.insert_or_assign(options.scope, Registration { + rand64(), options.scriptURL, Registration::State::Registered, options - }; + }); - this->registrations.insert_or_assign(options.scope, registration); + const auto& registration = this->registrations.at(options.scope); if (this->bridge != nullptr) { this->bridge->router.emit("serviceWorker.register", registration.json().str()); @@ -125,11 +172,13 @@ namespace SSC { } bool ServiceWorkerContainer::unregisterServiceWorker (String scopeOrScriptURL) { + Lock lock(this->mutex); + const auto& scope = scopeOrScriptURL; const auto& scriptURL = scopeOrScriptURL; if (this->registrations.contains(scope)) { - const auto registration = this->registrations.at(scope); + const auto& registration = this->registrations.at(scope); this->registrations.erase(scope); if (this->bridge != nullptr) { return this->bridge->router.emit("serviceWorker.unregister", registration.json().str()); @@ -138,7 +187,7 @@ namespace SSC { for (const auto& entry : this->registrations) { if (entry.second.scriptURL == scriptURL) { - const auto registration = this->registrations.at(entry.first); + const auto& registration = this->registrations.at(entry.first); this->registrations.erase(entry.first); if (this->bridge != nullptr) { return this->bridge->router.emit("serviceWorker.unregister", registration.json().str()); @@ -150,6 +199,8 @@ namespace SSC { } bool ServiceWorkerContainer::unregisterServiceWorker (uint64_t id) { + Lock lock(this->mutex); + for (const auto& entry : this->registrations) { if (entry.second.id == id) { return this->unregisterServiceWorker(entry.first); @@ -160,6 +211,8 @@ namespace SSC { } void ServiceWorkerContainer::skipWaiting (uint64_t id) { + Lock lock(this->mutex); + for (auto& entry : this->registrations) { if (entry.second.id == id) { auto& registration = entry.second; @@ -179,6 +232,8 @@ namespace SSC { } void ServiceWorkerContainer::updateState (uint64_t id, const String& stateString) { + Lock lock(this->mutex); + for (auto& entry : this->registrations) { if (entry.second.id == id) { auto& registration = entry.second; @@ -207,7 +262,9 @@ namespace SSC { } } - bool ServiceWorkerContainer::fetch (FetchRequest request, FetchCallback callback) { + bool ServiceWorkerContainer::fetch (const FetchRequest& request, FetchCallback callback) { + Lock lock(this->mutex); + if (this->bridge == nullptr) { return false; } @@ -228,6 +285,7 @@ namespace SSC { const auto fetch = JSON::Object::Entries { {"id", std::to_string(id)}, + {"method", request.method}, {"pathname", request.pathname}, {"query", request.query}, {"headers", headers}, @@ -236,11 +294,15 @@ namespace SSC { auto json = registration.json(); json.set("fetch", fetch); - this->fetches.insert_or_assign(id, callback); + + this->fetchCallbacks.insert_or_assign(id, callback); + this->fetchRequests.insert_or_assign(id, request); + return this->bridge->router.emit("serviceWorker.fetch", json.str()); } } + debug("no registration found for fetch"); return false; } } diff --git a/src/core/service_worker_container.hh b/src/core/service_worker_container.hh index 1d4effb552..9d19ea0801 100644 --- a/src/core/service_worker_container.hh +++ b/src/core/service_worker_container.hh @@ -38,7 +38,6 @@ namespace SSC { String scriptURL; State state = State::Registered; RegistrationOptions options; - Vector clients; const SSC::JSON::Object json () const; }; @@ -49,8 +48,10 @@ namespace SSC { struct FetchRequest { String pathname; + String method; String query; Vector headers; + FetchBuffer buffer; Client client; }; @@ -65,23 +66,27 @@ namespace SSC { using Registrations = std::map; using FetchCallback = std::function; using FetchCallbacks = std::map; + using FetchRequests = std::map; + Mutex mutex; Core* core = nullptr; IPC::Bridge* bridge = nullptr; Registrations registrations; - FetchCallbacks fetches; + + FetchRequests fetchRequests; + FetchCallbacks fetchCallbacks; ServiceWorkerContainer (Core* core); ~ServiceWorkerContainer (); void init (IPC::Bridge* bridge); - const Registration registerServiceWorker (const RegistrationOptions& options); + const Registration& registerServiceWorker (const RegistrationOptions& options); bool unregisterServiceWorker (uint64_t id); bool unregisterServiceWorker (String scopeOrScriptURL); void skipWaiting (uint64_t id); void updateState (uint64_t id, const String& stateString); - - bool fetch (FetchRequest request, FetchCallback callback); + bool fetch (const FetchRequest& request, FetchCallback callback); + bool claimClients (const String& scope); }; } From 60fa281cb23cfa738d5f63c711d80074faacc51c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 16:47:29 +0100 Subject: [PATCH 0027/1178] refactor(src/extension/ipc.cc): clean up --- src/extension/ipc.cc | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/extension/ipc.cc b/src/extension/ipc.cc index 915c9d265f..f38321c087 100644 --- a/src/extension/ipc.cc +++ b/src/extension/ipc.cc @@ -172,7 +172,7 @@ bool sapi_ipc_send_chunk ( "IPC method '" + result->message.name + "' must be invoked with HTTP"; return sapi_ipc_reply_with_error(result, error.c_str()); } - auto send_chunk_ptr = result->post.chunk_stream; + auto send_chunk_ptr = result->post.chunkStream; if (send_chunk_ptr == nullptr) { debug( "Cannot use 'sapi_ipc_send_chunk' before setting the \"Transfer-Encoding\"" @@ -203,7 +203,7 @@ bool sapi_ipc_send_event ( "IPC method '" + result->message.name + "' must be invoked with HTTP"; return sapi_ipc_reply_with_error(result, error.c_str()); } - auto send_event_ptr = result->post.event_stream; + auto send_event_ptr = result->post.eventStream; if (send_event_ptr == nullptr) { debug( "Cannot use 'sapi_ipc_send_event' before setting the \"Content-Type\"" @@ -681,24 +681,28 @@ void sapi_ipc_result_set_header ( result->headers.set(name, value); #if !defined(_WIN32) - if (strcasecmp(name, "content-type") == 0 && - strcasecmp(value, "text/event-stream") == 0) { + if ( + strcasecmp(name, "content-type") == 0 && + strcasecmp(value, "text/event-stream") == 0 + ) { result->context->retain(); result->post = SSC::Post(); - result->post.event_stream = - std::make_shared>( - [result](const char* name, const char* data, bool finished) { - return false; - }); - } else if (strcasecmp(name, "transfer-encoding") == 0 && - strcasecmp(value, "chunked") == 0) { + result->post.eventStream = std::make_shared( + [result](const char* name, const char* data, bool finished) { + return false; + } + ); + } else if ( + strcasecmp(name, "transfer-encoding") == 0 && + strcasecmp(value, "chunked") == 0 + ) { result->context->retain(); result->post = SSC::Post(); - result->post.chunk_stream = - std::make_shared>( - [result](const char* chunk, size_t chunk_size, bool finished) { - return false; - }); + result->post.chunkStream = std::make_shared( + [result](const char* chunk, size_t chunk_size, bool finished) { + return false; + } + ); } #endif } From 392e845605d10528b961e95061359a0840ea9342 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 17:29:21 +0100 Subject: [PATCH 0028/1178] refactor(api/internal/worker.js): fix 'dispatchEvent' --- api/internal/worker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/internal/worker.js b/api/internal/worker.js index bfeebf748a..79bde1024a 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -240,11 +240,11 @@ export function removeEventListener (eventName, callback, ...args) { } export function dispatchEvent (event) { - if (event.type !== 'message') { - return worker.dispatchEvent(event) + if (event.type === 'message' || event.type === 'connect') { + return workerGlobalScopeEventTarget.dispatchEvent(event) } - return workerGlobalScopeEventTarget.dispatchEvent(event) + return worker.dispatchEvent(event) } export function postMessage (message, ...args) { From 1ad016288382aebb47dd1691d0e66be9438e7ec0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 15 Feb 2024 19:14:58 +0100 Subject: [PATCH 0029/1178] refactor(api/internal): fix worker message queue --- api/internal/init.js | 38 ++++++++++++++++++++++++++++++++++---- api/internal/worker.js | 1 + 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index f6dbd1691e..87883f5195 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -21,6 +21,7 @@ import location from '../location.js' import { URL } from '../url.js' import mime from '../mime.js' import path from '../path.js' +import ipc from '../ipc.js' import fs from '../fs/promises.js' import { createFileSystemDirectoryHandle, @@ -299,9 +300,21 @@ class RuntimeWorker extends GlobalWorker { globalThis.addEventListener('data', this.#onglobaldata) + const postMessage = this.postMessage.bind(this) const addEventListener = this.addEventListener.bind(this) const removeEventListener = this.removeEventListener.bind(this) + const postMessageQueue = [] + let isReady = false + + this.postMessage = (...args) => { + if (!isReady) { + postMessageQueue.push(args) + } else { + return postMessage(...args) + } + } + this.addEventListener = (eventName, ...args) => { if (eventName === 'message') { return eventTarget.addEventListener(eventName, ...args) @@ -336,7 +349,15 @@ class RuntimeWorker extends GlobalWorker { queueMicrotask(() => { addEventListener('message', (event) => { const { data } = event - if (data?.__runtime_worker_ipc_request) { + if (data?.__runtime_worker_init === true) { + isReady = true + + for (const args of postMessageQueue) { + postMessage(...args) + } + + postMessageQueue.splice(0, postMessageQueue.length) + } else if (data?.__runtime_worker_ipc_request) { const request = data.__runtime_worker_ipc_request if ( typeof request?.message === 'string' && @@ -348,7 +369,17 @@ class RuntimeWorker extends GlobalWorker { const message = ipc.Message.from(request.message, request.bytes) const options = { bytes: message.bytes } // eslint-disable-next-line no-use-before-define - const result = await ipc.request(message.name, message.rawParams, options) + let result = message.name.startsWith('application.') + ? await ipc.send(message.name, message.rawParams, options) + : await ipc.request(message.name, message.rawParams, options) + + if (result.err?.type === 'NotFoundError') { + const otherResult = await ipc.send(message.name, message.rawParams, options) + if (!otherResult.err) { + result = otherResult + } + } + const transfer = [] if (ArrayBuffer.isView(result.data) || result.data instanceof ArrayBuffer) { @@ -360,7 +391,7 @@ class RuntimeWorker extends GlobalWorker { message: message.toJSON(), result: result.toJSON() } - }, transfer) + }, { transfer }) } catch (err) { console.warn('RuntimeWorker:', err) } @@ -461,7 +492,6 @@ if (typeof globalThis.XMLHttpRequest === 'function') { import hooks, { RuntimeInitEvent } from '../hooks.js' import { config } from '../application.js' import globals from './globals.js' -import ipc from '../ipc.js' import '../console.js' diff --git a/api/internal/worker.js b/api/internal/worker.js index 79bde1024a..18728cd614 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -104,6 +104,7 @@ if (source && typeof source === 'string') { globalThis.RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG.length ) } + globalThis.postMessage({ __runtime_worker_init: true }) } catch (err) { reportError(err) } From 6e4efe82684719f9a562a0d7c3b3a8872d380963 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 17 Feb 2024 20:11:25 +0100 Subject: [PATCH 0030/1178] refactor(): wip --- api/README.md | 151 ++++++---- api/assert.js | 127 ++++++++ api/constants.js | 4 + api/dns/index.js | 1 + api/fs/handle.js | 21 ++ api/fs/index.js | 41 ++- api/fs/promises.js | 15 + api/gc.js | 25 +- api/http.js | 369 +++++++++++++++++++++++ api/https.js | 44 +++ api/internal/init.js | 5 + api/internal/monkeypatch.js | 15 + api/internal/scheduler.js | 3 + api/internal/symbols.js | 5 + api/internal/timers.js | 12 + api/mime/index.js | 75 +++++ api/module.js | 194 +++++++++--- api/process.js | 105 ++++++- api/querystring.js | 429 +++++++++++++++++++++++++++ api/service-worker/clients.js | 1 - api/service-worker/events.js | 6 + api/service-worker/index.html | 2 +- api/service-worker/instance.js | 1 - api/service-worker/worker.js | 9 +- api/stream.js | 2 + api/stream/web.js | 21 ++ api/string_decoder.js | 298 +++++++++++++++++++ api/timers.js | 7 + api/timers/index.js | 65 ++++ api/timers/promises.js | 72 +++++ api/timers/scheduler.js | 22 ++ api/timers/timer.js | 102 +++++++ api/util.js | 124 +++++++- api/vm.js | 7 +- api/window/constants.js | 4 +- src/android/webview.kt | 2 +- src/android/window.cc | 3 +- src/core/preload.cc | 39 +-- src/core/preload.hh | 1 + src/core/service_worker_container.cc | 11 + src/ios/main.mm | 3 +- src/ipc/bridge.cc | 8 +- src/window/apple.mm | 14 +- 43 files changed, 2323 insertions(+), 142 deletions(-) create mode 100644 api/assert.js create mode 100644 api/constants.js create mode 100644 api/http.js create mode 100644 api/https.js create mode 100644 api/internal/scheduler.js create mode 100644 api/internal/symbols.js create mode 100644 api/internal/timers.js create mode 100644 api/querystring.js create mode 100644 api/stream/web.js create mode 100644 api/string_decoder.js create mode 100644 api/timers.js create mode 100644 api/timers/index.js create mode 100644 api/timers/promises.js create mode 100644 api/timers/scheduler.js create mode 100644 api/timers/timer.js diff --git a/api/README.md b/api/README.md index eebadeca32..4e4c78a87a 100644 --- a/api/README.md +++ b/api/README.md @@ -65,7 +65,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L171) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L190) Returns the ApplicationWindow instance for the given index @@ -77,7 +77,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L181) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L200) Returns the ApplicationWindow instance for the current window. @@ -85,7 +85,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L190) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L209) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -97,7 +97,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L287) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L306) Set the native menu for the app. @@ -192,11 +192,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L294) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L313) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L303) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L322) Set the enabled state of the system menu. @@ -208,23 +208,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L311) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L330) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L317) +## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L336) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L323) +## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L342) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L328) +## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L347) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L334) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L353) @@ -237,7 +237,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L342) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L361) @@ -1028,7 +1028,20 @@ Removes directory at `path`. ## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L765) +Get the stats of a file +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL \| number | | false | filename or file descriptor | +| options | object? | | false | | +| options.encoding ? utf8 | string? | | true | | +| options.flag ? r | string? | | true | | +| options.signal | AbortSignal? | | true | | +| callback | function(Error?, Stats?) | | false | | + +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L803) + +Get the stats of a symbolic link | Argument | Type | Default | Optional | Description | | :--- | :--- | :---: | :---: | :--- | @@ -1039,7 +1052,7 @@ Removes directory at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L799) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L837) Creates a symlink of `src` at `dest`. @@ -1048,7 +1061,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L840) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L878) Unlinks (removes) file at `path`. @@ -1057,7 +1070,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L865) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L903) @@ -1072,7 +1085,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L910) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L948) Watch for changes at `path` calling `callback` @@ -1321,10 +1334,25 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L405) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L406) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options +Get the stats of a file + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| options | object? | | true | | +| options.bigint | boolean? | false | true | | + +| Return Value | Type | Description | +| :--- | :--- | :--- | +| Not specified | Promise | | + +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L420) +External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options +Get the stats of a symbolic link. | Argument | Type | Default | Optional | Description | | :--- | :--- | :---: | :---: | :--- | @@ -1336,7 +1364,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options | :--- | :--- | :--- | | Not specified | Promise | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L417) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L432) Creates a symlink of `src` at `dest`. @@ -1349,7 +1377,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L452) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L467) Unlinks (removes) file at `path`. @@ -1361,7 +1389,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L475) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L490) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1380,7 +1408,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L495) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L510) Watch for changes at `path` calling `callback` @@ -1834,7 +1862,12 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L42) +## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L15) + +This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. + + +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L123) Adds callback to the 'nextTick' queue. @@ -1842,7 +1875,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L71) +## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L152) @@ -1850,7 +1883,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :--- | | Not specified | string | The home directory of the current user. | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L80) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L161) Computed high resolution time as a `BigInt`. @@ -1862,7 +1895,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L104) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L187) @@ -1870,7 +1903,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L116) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L199) Returns an object describing the memory usage of the Node.js process measured in bytes. @@ -2450,7 +2483,11 @@ Retrieves the computed styles for a given element. Represents a window in the application -### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L63) +### [`id()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L68) + +The unique ID of this window. + +### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L76) Get the index of the window @@ -2458,11 +2495,15 @@ Get the index of the window | :--- | :--- | :--- | | Not specified | number | the index of the window | -### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L70) +### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L83) + + +### [`channel()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L91) +The broadcast channel for this window. -### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L78) +### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L99) Get the size of the window @@ -2470,7 +2511,7 @@ Get the size of the window | :--- | :--- | :--- | | Not specified | { width: number, height: number | } - the size of the window | -### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L89) +### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L110) Get the title of the window @@ -2478,7 +2519,7 @@ Get the title of the window | :--- | :--- | :--- | | Not specified | string | the title of the window | -### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L97) +### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L118) Get the status of the window @@ -2486,7 +2527,7 @@ Get the status of the window | :--- | :--- | :--- | | Not specified | string | the status of the window | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L105) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L126) Close the window @@ -2494,7 +2535,7 @@ Close the window | :--- | :--- | :--- | | Not specified | Promise | the options of the window | -### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L120) +### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L141) Shows the window @@ -2502,7 +2543,7 @@ Shows the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L129) +### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L150) Hides the window @@ -2510,7 +2551,7 @@ Hides the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L138) +### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L159) Maximize the window @@ -2518,7 +2559,7 @@ Maximize the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L147) +### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L168) Minimize the window @@ -2526,7 +2567,7 @@ Minimize the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L156) +### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L177) Restore the window @@ -2534,7 +2575,7 @@ Restore the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L166) +### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L187) Sets the title of the window @@ -2546,7 +2587,7 @@ Sets the title of the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L179) +### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L200) Sets the size of the window @@ -2560,7 +2601,7 @@ Sets the size of the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L219) +### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L240) Navigate the window to a given path @@ -2572,7 +2613,7 @@ Navigate the window to a given path | :--- | :--- | :--- | | Not specified | Promise | | -### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L228) +### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L249) Opens the Web Inspector for the window @@ -2580,7 +2621,7 @@ Opens the Web Inspector for the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L245) +### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L266) Sets the background color of the window @@ -2596,7 +2637,7 @@ Sets the background color of the window | :--- | :--- | :--- | | Not specified | Promise | | -### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L255) +### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L276) Opens a native context menu. @@ -2608,7 +2649,7 @@ Opens a native context menu. | :--- | :--- | :--- | | Not specified | Promise | | -### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L264) +### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L285) Shows a native open file dialog. @@ -2620,7 +2661,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L282) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L303) Shows a native save file dialog. @@ -2632,7 +2673,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L300) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L321) Shows a native directory dialog. @@ -2644,7 +2685,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L325) +### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L346) This is a high-level API that you should use instead of `ipc.send` when you want to send a message to another window or to the backend. @@ -2658,7 +2699,7 @@ This is a high-level API that you should use instead of `ipc.send` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L366) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L387) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -2671,7 +2712,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L384) +### [`openExternal(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L405) Opens an URL in the default browser. @@ -2683,7 +2724,7 @@ Opens an URL in the default browser. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L395) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L416) Adds a listener to the window. @@ -2692,7 +2733,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L413) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L434) Adds a listener to the window. An alias for `addListener`. @@ -2701,7 +2742,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L430) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L451) Adds a listener to the window. The listener is removed after the first call. @@ -2710,7 +2751,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L446) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L467) Removes a listener from the window. @@ -2719,7 +2760,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L459) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L480) Removes all listeners from the window. @@ -2727,7 +2768,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L475) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L496) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/assert.js b/api/assert.js new file mode 100644 index 0000000000..207ea63234 --- /dev/null +++ b/api/assert.js @@ -0,0 +1,127 @@ +import fastDeepEqual from './test/fast-deep-equal.js' +import util from './util.js' + +export class AssertionError extends Error { + actual = null + expected = null + operator = null + + constructor (options) { + super(options.message) + } +} + +export function assert (value, message = null) { + if (value === undefined) { + throw new AssertionError({ + operator: '==', + message: 'No value argument passed to `assert()`', + actual: value, + expected: true + }) + } else if (!value) { + throw new AssertionError({ + operator: '==', + message: message || 'The expression evaluated to a falsy value:', + actual: value, + expected: true + }) + } +} + +export function ok (value, message = null) { + if (value === undefined) { + throw new AssertionError({ + operator: '==', + message: 'No value argument passed to `assert.ok()`', + actual: value, + expected: true + }) + } else if (!value) { + throw new AssertionError({ + operator: '==', + message: message || 'The expression evaluated to a falsy value:', + actual: value, + expected: true + }) + } +} + +export function equal (actual, expected, message = null) { + // eslint-disable-next-line + if (actual != expected) { + throw new AssertionError({ + operator: '==', + message: message || `${util.inspect(actual)} == ${util.inspect(expected)}`, + actual, + expected + }) + } +} + +export function notEqual (actual, expected, message = null) { + // eslint-disable-next-line + if (actual == expected) { + throw new AssertionError({ + operator: '!=', + message: message || `${util.inspect(actual)} != ${util.inspect(expected)}`, + actual, + expected + }) + } +} + +export function strictEqual (actual, expected, message = null) { + if (actual !== expected) { + throw new AssertionError({ + operator: '===', + message: message || `${util.inspect(actual)} === ${util.inspect(expected)}`, + actual, + expected + }) + } +} + +export function notStrictEqual (actual, expected, message = null) { + if (actual === expected) { + throw new AssertionError({ + operator: '!==', + message: message || `${util.inspect(actual)} !== ${util.inspect(expected)}`, + actual, + expected + }) + } +} + +export function deepEqual (actual, expected, message = null) { + if (fastDeepEqual(actual, expected)) { + throw new AssertionError({ + operator: '==', + message: message || `${util.inspect(actual)} == ${util.inspect(expected)}`, + actual, + expected + }) + } +} + +export function notDeepEqual (actual, expected, message = null) { + if (!fastDeepEqual(actual, expected)) { + throw new AssertionError({ + operator: '!=', + message: message || `${util.inspect(actual)} != ${util.inspect(expected)}`, + actual, + expected + }) + } +} + +export default Object.assign(assert, { + AssertionError, + ok, + equal, + notEqual, + strictEqual, + notStrictEqual, + deepEqual, + notDeepEqual +}) diff --git a/api/constants.js b/api/constants.js new file mode 100644 index 0000000000..a7505a8afc --- /dev/null +++ b/api/constants.js @@ -0,0 +1,4 @@ +import * as exports from './constants.js' +export * from './fs/constants.js' +export * from './window/constants.js' +export default exports diff --git a/api/dns/index.js b/api/dns/index.js index 927d334909..7601cb635e 100644 --- a/api/dns/index.js +++ b/api/dns/index.js @@ -111,5 +111,6 @@ for (const key in exports) { const value = exports[key] if (key in promises && isFunction(value) && isFunction(promises[key])) { value[Symbol.for('nodejs.util.promisify.custom')] = promises[key] + value[Symbol.for('socket.util.promisify.custom')] = promises[key] } } diff --git a/api/fs/handle.js b/api/fs/handle.js index ac86dae1ae..057c93f41a 100644 --- a/api/fs/handle.js +++ b/api/fs/handle.js @@ -604,6 +604,27 @@ export class FileHandle extends EventEmitter { return stats } + /** + * Returns the stats of the underlying symbolic link. + * @param {object=} [options] + * @return {Promise} + */ + async lstat (options) { + if (this.closing || this.closed) { + throw new Error('FileHandle is not opened') + } + + const result = await ipc.request('fs.lstat', { ...options, path: this.path }) + + if (result.err) { + throw result.err + } + + const stats = Stats.from(result.data, Boolean(options?.bigint)) + stats.handle = this + return stats + } + /** * Synchronize a file's in-core state with storage device * @return {Promise} diff --git a/api/fs/index.js b/api/fs/index.js index 667a7474b0..b51ac09e3b 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -754,7 +754,7 @@ export function rmdir (path, callback) { } /** - * + * Get the stats of a file * @param {string | Buffer | URL | number } path - filename or file descriptor * @param {object?} options * @param {string?} [options.encoding ? 'utf8'] @@ -791,6 +791,44 @@ export function stat (path, options, callback) { }) } +/** + * Get the stats of a symbolic link + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback + */ +export function lstat (path, options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + + if (typeof callback !== 'function') { + throw new TypeError('callback must be a function.') + } + + visit(path, {}, async (err, handle) => { + let stats = null + + if (err) { + callback(err) + return + } + + try { + stats = await handle.lstat(options) + } catch (err) { + callback(err) + return + } + + callback(null, stats) + }) +} + /** * Creates a symlink of `src` at `dest`. * @param {string} src @@ -938,5 +976,6 @@ for (const key in exports) { const value = exports[key] if (key in promises && isFunction(value) && isFunction(promises[key])) { value[Symbol.for('nodejs.util.promisify.custom')] = promises[key] + value[Symbol.for('socket.util.promisify.custom')] = promises[key] } } diff --git a/api/fs/promises.js b/api/fs/promises.js index 3d40221406..9bcf4573b1 100644 --- a/api/fs/promises.js +++ b/api/fs/promises.js @@ -396,6 +396,7 @@ export async function rmdir (path) { } /** + * Get the stats of a file * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options} * @param {string | Buffer | URL} path * @param {object?} [options] @@ -408,6 +409,20 @@ export async function stat (path, options) { }) } +/** + * Get the stats of a symbolic link. + * @see {@link https://nodejs.org/api/fs.html#fspromiseslstatpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {boolean?} [options.bigint = false] + * @return {Promise} + */ +export async function lstat (path, options) { + return await visit(path, {}, async (handle) => { + return await handle.lstat(options) + }) +} + /** * Creates a symlink of `src` at `dest`. * @param {string} src diff --git a/api/gc.js b/api/gc.js index a4cb325b36..a91ccdde52 100644 --- a/api/gc.js +++ b/api/gc.js @@ -1,6 +1,7 @@ import { FinalizationRegistryCallbackError } from './errors.js' import diagnostics from './diagnostics.js' import { noop } from './util.js' +import symbols from './internal/symbols.js' import console from './console.js' if (typeof FinalizationRegistry === 'undefined') { @@ -137,8 +138,16 @@ export class Finalizer { * @return {boolean} */ export async function ref (object, ...args) { - if (object && typeof object[kFinalizer] === 'function') { - const finalizer = Finalizer.from(await object[kFinalizer](...args)) + if ( + object && ( + typeof object[kFinalizer] === 'function' || + typeof object[symbols.dispose] === 'function' + ) + ) { + const finalizer = Finalizer.from( + await (object[kFinalizer] || object[symbols.dispose]).call(object, ...args) + ) + const weakRef = new WeakRef(finalizer) finalizers.set(object, weakRef) @@ -163,7 +172,12 @@ export function unref (object) { return false } - if (typeof object[kFinalizer] === 'function' && finalizers.has(object)) { + if ( + finalizers.has(object) && ( + typeof object[kFinalizer] === 'function' || + typeof object[symbols.dispose] === 'function' + ) + ) { const weakRef = finalizers.get(object) if (weakRef) { @@ -202,7 +216,10 @@ export async function finalize (object, ...args) { if (finalizer instanceof Finalizer && await unref(object)) { await finalizationRegistryCallback(finalizer) } else { - const finalizer = Finalizer.from(await object[kFinalizer](...args)) + const finalizer = Finalizer.from( + await (object[kFinalizer] || object[symbols.dispose]).call(object, ...args) + ) + await finalizationRegistryCallback(finalizer) } return true diff --git a/api/http.js b/api/http.js new file mode 100644 index 0000000000..6504be1fee --- /dev/null +++ b/api/http.js @@ -0,0 +1,369 @@ +import { Writable, Duplex } from './stream.js' +import { EventEmitter } from './events.js' + +export const METHODS = [ + 'ACL', + 'BIND', + 'CHECKOUT', + 'CONNECT', + 'COPY', + 'DELETE', + 'GET', + 'HEAD', + 'LINK', + 'LOCK', + 'M-SEARCH', + 'MERGE', + 'MKACTIVITY', + 'MKCALENDAR', + 'MKCOL', + 'MOVE', + 'NOTIFY', + 'OPTIONS', + 'PATCH', + 'POST', + 'PROPFIND', + 'PROPPATCH', + 'PURGE', + 'PUT', + 'REBIND', + 'REPORT', + 'SEARCH', + 'SOURCE', + 'SUBSCRIBE', + 'TRACE', + 'UNBIND', + 'UNLINK', + 'UNLOCK', + 'UNSUBSCRIBE' +] + +export const STATUS_CODES = { + 100: 'Continue', + 101: 'Switching Protocols', + 102: 'Processing', + 103: 'Early Hints', + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: "I'm a Teapot", + 421: 'Misdirected Request', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 509: 'Bandwidth Limit Exceeded', + 510: 'Not Extended', + 511: 'Network Authentication Required' +} + +export class OutgoingMessage extends Writable { + headers = new Headers() + socket = null + + get headersSent () { + return true + } + + appendHeader (name, value) { + this.headers.set(name.toLowerCase(), value) + return this + } + + setHeader (name, value) { + return this.appendHeader(name, value) + } + + flushHeaders () { + this.emit('flushheaders') + } + + getHeader (name) { + return this.headers.get(name.toLowerCase()) + } + + getHeaderNames () { + return Array.from(this.headers.keys()) + } + + getHeaders () { + return Object.fromEntries(this.headers.entries()) + } + + hasHeader (name) { + return this.headers.has(name.toLowerCase()) + } + + removeHeader (name) { + return this.headers.delete(name.toLowerCase()) + } +} + +export class ClientRequest extends OutgoingMessage {} + +export class ServerResponse extends OutgoingMessage {} + +export class AgentOptions { + keepAlive = false + timeout = -1 + + constructor (options) { + this.keepAlive = options?.keepAlive === true + this.timeout = Number.isFinite(options?.timeout) && options.timeout > 0 + ? options.timeout + : -1 + } +} + +export class Agent extends EventEmitter { + defaultProtocol = 'http:' + options = null + + constructor (options) { + super() + this.options = new AgentOptions(options) + } + + createConnection (options, callback = null) { + let controller = null + let timeout = null + let url = null + + const abortController = new AbortController() + const readable = new ReadableStream({ start (c) { controller = c } }) + const pending = { callbacks: [], data: [] } + + const stream = new Duplex({ + signal: abortController.signal, + write (data, cb) { + console.log('write') + controller.enqueue(data) + cb(null) + }, + + read (cb) { + if (pending.data.length) { + const data = pending.data.shift() + this.push(data) + cb(null) + } else { + pending.callbacks.push(cb) + } + } + }) + + stream.on('finish', () => readable.close()) + + url = `${options.protocol ?? this.defaultProtocol}//` + url += (options.host || options.hostname) + if (options.port) { + url += `:${options.port}` + } + + url += (options.path || options.pathname) + + if (options.signal) { + options.signal.addEventListener('abort', () => { + abortController.abort(options.signal.reason) + }) + } + + if (options.timeout) { + timeout = setTimeout(() => { + abortController.abort('Connection timed out') + stream.emit('timeout') + }, options.timeout) + } + + abortController.signal.addEventListener('abort', () => { + stream.emit('aborted') + stream.emit('error', Object.assign(new Error('aborted'), { code: 'ECONNRESET' })) + }) + + const deferredRequestPromise = options.makeRequest + ? options.makeRequest() + : Promise.resolve() + + deferredRequestPromise.then(makeRequest) + + function makeRequest (req) { + const request = fetch(url, { + headers: Object.fromEntries( + Array.from(Object.entries( + options.headers?.entries?.() ?? options.headers ?? {} + )).concat(req.headers.entries()) + ), + signal: abortController.signal, + method: options.method ?? 'GET', + body: /put|post/i.test(options.method ?? '') + ? readable + : undefined + }) + + if (options.handleResponse) { + request.then(options.handleResponse) + } + + request.finally(() => clearTimeout(timeout)) + request + .then((response) => { + if (response.body) { + return response.body.getReader() + } + + return response + .blob() + .then((blob) => blob.stream().getReader()) + }) + .then((reader) => { + read() + function read () { + reader.read() + .then(({ done, value }) => { + if (pending.callbacks.length) { + const cb = pending.callbacks.shift() + stream.push(value) + cb(null) + } else { + pending.data.push(value ?? null) + } + + if (!done) { + read() + } + }) + } + }) + + if (typeof callback === 'function') { + callback(stream) + } + } + + return stream + } +} + +export const globalAgent = new Agent() + +async function request (optionsOrURL, options, callback) { + if (optionsOrURL && typeof optionsOrURL === 'object') { + options = optionsOrURL + callback = options + } else if (typeof optionsOrURL === 'string') { + const url = globalThis.location.origin.startsWith('blob') + ? new URL(optionsOrURL, new URL(globalThis.location.origin).pathname) + : new URL(optionsOrURL, globalThis.location.origin) + + options = { + host: url.host, + port: url.port, + pathname: url.pathname, + protocol: url.protocol, + ...options + } + } + + const request = new ClientRequest() + let stream = null + let agent = null + + options = { + ...options, + makeRequest () { + return new Promise((resolve) => { + if (!/post|put/i.test(options.method ?? '')) { + resolve(request) + } else { + stream.on('finish', () => resolve(request)) + } + + request.emit('connect') + }) + }, + + handleResponse (response) { + stream.response = response + request.emit('response', stream) + } + } + + if (options.agent) { + agent = options.agent + } else if (options.agent === false) { + agent = new (options.Agent ?? Agent)() + } else { + agent = globalAgent + } + + stream = agent.createConnection(options, callback) + + stream.on('finish', () => request.emit('finish')) + stream.on('timeout', () => request.emit('timeout')) + + return request +} + +export function get (optionsOrURL, options, callback) { + return request(optionsOrURL, options, callback) +} + +export default { + METHODS, + STATUS_CODES, + AgentOptions, + Agent, + globalAgent, + request, + OutgoingMessage, + ClientRequest, + ServerResponse, + get +} diff --git a/api/https.js b/api/https.js new file mode 100644 index 0000000000..255de6aa78 --- /dev/null +++ b/api/https.js @@ -0,0 +1,44 @@ +import http from './http.js' + +export const METHODS = http.METHODS +export const STATUS_CODES = http.STATUS_CODES + +export class AgentOptions extends http.AgentOptions {} + +export class Agent extends http.Agent { + defaultProtocol = 'https:' +} + +export class OutgoingMessage extends http.OutgoingMessage {} +export class ClientRequest extends http.ClientRequest {} +export class ServerResponse extends http.ServerResponse {} + +export const globalAgent = new Agent() + +export function request (optionsOrURL, options, callback) { + if (typeof optionsOrURL === 'string') { + options = { Agent, ...options } + return http.request(optionsOrURL, options, callback) + } + + options = { Agent, ...optionsOrURL } + callback = options + return http.request(optionsOrURL, options, callback) +} + +export function get (optionsOrURL, options, callback) { + return request(optionsOrURL, options, callback) +} + +export default { + METHODS, + STATUS_CODES, + AgentOptions, + Agent, + globalAgent, + request, + OutgoingMessage, + ClientRequest, + ServerResponse, + get +} diff --git a/api/internal/init.js b/api/internal/init.js index 87883f5195..1b1d6f565a 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -453,6 +453,11 @@ if (typeof globalThis.XMLHttpRequest === 'function') { this.setRequestHeader('Runtime-Frame-Type', 'nested') } else if (!globalThis.window && globalThis.self === globalThis) { this.setRequestHeader('Runtime-Frame-Type', 'worker') + if (globalThis.clients && globalThis.FetchEvent) { + this.setRequestHeader('Runtime-Worker-Type', 'serviceworker') + } else { + this.setRequestHeader('Runtime-Worker-Type', 'worker') + } } else { this.setRequestHeader('Runtime-Frame-Type', 'top-level') } diff --git a/api/internal/monkeypatch.js b/api/internal/monkeypatch.js index f6110e857d..9c11d3dff1 100644 --- a/api/internal/monkeypatch.js +++ b/api/internal/monkeypatch.js @@ -1,6 +1,7 @@ /* global MutationObserver */ import { fetch, Headers, Request, Response } from '../fetch.js' import { URL, URLPattern, URLSearchParams } from '../url.js' +import { setImmediate, clearImmediate } from './timers.js' import serviceWorker from './service-worker.js' import SharedWorker from './shared-worker.js' import Notification from '../notification.js' @@ -8,6 +9,8 @@ import geolocation from './geolocation.js' import permissions from './permissions.js' import WebAssembly from './webassembly.js' import { Buffer } from '../buffer.js' +import scheduler from './scheduler.js' +import symbols from './symbols.js' import { ApplicationURLEvent, @@ -144,6 +147,10 @@ export function init () { } } + try { + Symbol.dispose = symbols.dispose + } catch {} + if ( typeof globalThis.webkitSpeechRecognition === 'function' && typeof globalThis.SpeechRecognition !== 'function' @@ -189,10 +196,18 @@ export function init () { // workers SharedWorker, + // timers + setImmediate, + clearImmediate, + // platform detection isSocketRuntime: true }) + if (globalThis.scheduler) { + install(scheduler, globalThis.scheduler) + } + if (globalThis.navigator) { // environment navigator install({ diff --git a/api/internal/scheduler.js b/api/internal/scheduler.js new file mode 100644 index 0000000000..9bc20ea425 --- /dev/null +++ b/api/internal/scheduler.js @@ -0,0 +1,3 @@ +import scheduler from '../timers/scheduler.js' +export * from '../timers/scheduler.js' +export default scheduler diff --git a/api/internal/symbols.js b/api/internal/symbols.js new file mode 100644 index 0000000000..74f679d614 --- /dev/null +++ b/api/internal/symbols.js @@ -0,0 +1,5 @@ +export const dispose = Symbol.dispose ?? Symbol.for('dispose') + +export default { + dispose +} diff --git a/api/internal/timers.js b/api/internal/timers.js new file mode 100644 index 0000000000..5948a6c77a --- /dev/null +++ b/api/internal/timers.js @@ -0,0 +1,12 @@ +export function setImmediate (callback, ...args) { + return setTimeout(callback, 0, ...args) +} + +export function clearImmediate (immediate) { + return clearTimeout(immediate) +} + +export default { + setImmediate, + clearTimeout +} diff --git a/api/mime/index.js b/api/mime/index.js index 3e002044f2..af20bd69ad 100644 --- a/api/mime/index.js +++ b/api/mime/index.js @@ -226,11 +226,86 @@ export async function lookup (query) { return results } +export class MIMEParams extends Map { + toString () { + return Array + .from(this.entries()) + .map((entry) => entry.join('=')) + .join(';') + } +} + +export class MIMEType { + #type = null + #params = new MIMEParams() + #subtype = null + + constructor (input) { + input = String(input) + const args = input.split(';') + const mime = args.shift() + const types = mime.split('/') + + if (types.length !== 2 || !types[0] || !types[1]) { + throw new TypeError(`Invalid MIMEType input given: ${mime}`) + } + + const [type, subtype] = types + + this.#type = type + this.#subtype = subtype + } + + get type () { + return this.#type + } + + set type (value) { + if (!value || typeof value !== 'string') { + throw new TypeError('MIMEType type must be a string') + } + + this.#type = value + } + + get subtype () { + return this.#subtype + } + + set subtype (value) { + if (!value || typeof value !== 'string') { + throw new TypeError('MIMEType subtype must be a string') + } + + this.#subtype = value + } + + get essence () { + return `${this.type}/${this.subtype}` + } + + toString () { + const params = this.params.toString() + + if (params) { + return `${this.essence};${params}` + } + + return this.essence + } + + toJSON () { + return this.toString() + } +} + export default { // API Database, databases, lookup, + MIMEParams, + MIMEType, // databases application, diff --git a/api/module.js b/api/module.js index 2b0c31087f..ddf3e5a313 100644 --- a/api/module.js +++ b/api/module.js @@ -8,30 +8,39 @@ import { ModuleNotFoundError } from './errors.js' import { ErrorEvent, Event } from './events.js' import { Headers } from './ipc.js' -import location from './location.js' import { URL } from './url.js' -import * as exports from './module.js' -export default exports - // builtins +import application from './application.js' +import assert from './assert.js' import buffer from './buffer.js' import console from './console.js' +import constants from './constants.js' +import crypto from './crypto.js' import dgram from './dgram.js' import dns from './dns.js' import events from './events.js' import extension from './extension.js' import fs from './fs.js' import gc from './gc.js' +import http from './http.js' +import https from './https.js' import ipc from './ipc.js' +import language from './language.js' +import mime from './mime.js' import os from './os.js' import { posix as path } from './path.js' import process from './process.js' +import querystring from './querystring.js' import stream from './stream.js' +// eslint-disable-next-line +import string_decoder from './string_decoder.js' import test from './test.js' +import timers from './timers.js' import url from './url.js' import util from './util.js' import vm from './vm.js' +import window from './window.js' /** * @typedef {function(string, Module, function): undefined} ModuleResolver @@ -49,7 +58,11 @@ class ModuleRequest { } constructor (pathname, parent) { - this.url = new URL(pathname, parent || location.origin || '/') + const origin = globalThis.location.origin.startsWith('blob:') + ? new URL(new URL(globalThis.location.href).pathname).origin + : globalThis.location.origin + + this.url = new URL(pathname, parent || origin || '/') this.id = this.url.toString() } @@ -89,7 +102,7 @@ class ModuleRequest { const response = new ModuleResponse(this, headers, responseText) - if (request.status < 400 && responseText) { + if (request.status < 400) { cache.set(id, response) } @@ -130,6 +143,10 @@ function CommonJSModuleScope ( // eslint-disable-next-line no-unused-vars const console = require('socket:console') // eslint-disable-next-line no-unused-vars + const crypto = require('socket:crypto') + // eslint-disable-next-line no-unused-vars + const { Buffer } = require('socket:buffer') + // eslint-disable-next-line no-unused-vars const global = new Proxy(globalThis, { get (target, key, receiver) { if (key === 'process') { @@ -140,6 +157,30 @@ function CommonJSModuleScope ( return console } + if (key === 'crypto') { + return crypto + } + + if (key === 'Buffer') { + return Buffer + } + + if (key === 'global') { + return global + } + + if (key === 'module') { + return Module.main + } + + if (key === 'exports') { + return Module.main.exports + } + + if (key === 'require') { + return Module.main.require + } + return Reflect.get(target, key, receiver) } }) @@ -147,7 +188,7 @@ function CommonJSModuleScope ( // eslint-disable-next-line no-unused-expressions void exports, require, module, __filename, __dirname // eslint-disable-next-line no-unused-expressions - void process, console, global + void process, console, global, crypto, Buffer return (async function () { 'module code' @@ -158,8 +199,12 @@ function CommonJSModuleScope ( * A limited set of builtins exposed to CommonJS modules. */ export const builtins = { + application, + assert, buffer, console, + constants, + crypto, dgram, dns, 'dns/promises': dns.promises, @@ -168,24 +213,63 @@ export const builtins = { fs, 'fs/promises': fs.promises, gc, + http, + https, ipc, - module: exports, + language, + mime, + net: {}, os, path, + // eslint-disable-next-line + perf_hooks: { + performance: globalThis.performance + }, process, + querystring, stream, + 'stream/web': stream.web, + // eslint-disable-next-line + string_decoder, test, + timers, + 'timers/promises': timers.promises, util, url, - vm + vm, + window, + // eslint-disable-next-line + worker_threads: { + BroadcastChannel: globalThis.BroadcastChannel, + MessageChannel: globalThis.MessageChannel, + MessagePort: globalThis.MessagePort + } } +const socketRuntimeModules = [ + 'application', + 'extension', + 'gc', + 'ipc', + 'language', + 'mime', + 'window' +] + // alias export const builtinModules = builtins export function isBuiltin (name) { + const originalName = name name = name.replace(/^(socket|node):/, '') + if ( + socketRuntimeModules.includes(name) && + !originalName.startsWith('socket:') + ) { + return false + } + if (name in builtins) { return true } @@ -205,7 +289,9 @@ export const COMMONJS_WRAPPER = CommonJSModuleScope * The main entry source origin. * @type {string} */ -export const MAIN_SOURCE_ORIGIN = location.href +export const MAIN_SOURCE_ORIGIN = globalThis.location.href.startsWith('blob:') + ? new URL(new URL(globalThis.location.href).pathname).href + : globalThis.location.href /** * Creates a `require` function from a source URL. @@ -258,6 +344,12 @@ export class Module extends EventTarget { */ static wrapper = COMMONJS_WRAPPER + /** + * A limited set of builtins exposed to CommonJS modules. + * @type {object} + */ + static builtins = builtins + /** * Creates a `require` function from a source URL. * @param {URL|string} sourcePath @@ -265,10 +357,10 @@ export class Module extends EventTarget { */ static createRequire (sourcePath) { if (!sourcePath) { - return this.main.createRequire() + return Module.main.createRequire() } - return this.from(sourcePath).createRequire() + return Module.from(sourcePath).createRequire() } /** @@ -283,8 +375,6 @@ export class Module extends EventTarget { const main = this.cache[MAIN_SOURCE_ORIGIN] = new Module(MAIN_SOURCE_ORIGIN) main.filename = main.id main.loaded = true - Object.freeze(main) - Object.seal(main) return main } @@ -420,7 +510,7 @@ export class Module extends EventTarget { * @type {boolean} */ get isNamed () { - return !this.isMain && !this.sourcePath?.startsWith('.') + return !this.isMain && !this.sourcePath?.startsWith('.') && !this.sourcePath?.startsWith('/') } /** @@ -454,6 +544,10 @@ export class Module extends EventTarget { const prefixes = (process.env.SOCKET_MODULE_PATH_PREFIX || '').split(':') const urls = [] + if (this.loaded) { + return true + } + Module.previous = Module.current Module.current = this @@ -463,12 +557,16 @@ export class Module extends EventTarget { if (isNamed) { const name = sourcePath for (const prefix of prefixes) { - let current = new URL('.', this.id).toString() + let current = new URL('./', this.id).toString() + do { const prefixURL = new URL(prefix, current) urls.push(new URL(name, prefixURL + '/').toString()) current = new URL('..', current).toString() } while (new URL(current).pathname !== '/') + + const prefixURL = new URL(prefix, current) + urls.push(new URL(name, prefixURL + '/').toString()) } } else { urls.push(this.id) @@ -492,7 +590,7 @@ export class Module extends EventTarget { const urls = [] const extname = path.extname(url) - if (extname && !hasTrailingSlash) { + if (/\.(js|json|mjs|cjs)/.test(extname) && !hasTrailingSlash) { urls.push(url) } else { if (hasTrailingSlash) { @@ -514,7 +612,7 @@ export class Module extends EventTarget { const filename = urls.shift() const response = request(filename) - if (response.data) { + if (response.data !== null) { try { evaluate(module, filename, response.data) } catch (error) { @@ -529,20 +627,23 @@ export class Module extends EventTarget { } } - const response = request(path.join(url, 'package.json')) + const response = request( + url + (hasTrailingSlash ? 'package.json' : '/package.json') + ) + if (response.data) { try { // @ts-ignore const packageJSON = JSON.parse(response.data) const filename = !packageJSON.exports - ? path.resolve('/', url, packageJSON.browser || packageJSON.main) - : ( - packageJSON.exports?.['.'] || - packageJSON.exports?.['./index.js'] || - packageJSON.exports?.['index.js'] - ) - - evaluate(module, filename, request(filename).data) + ? path.resolve('/', url, packageJSON.main || packageJSON.browser) + : path.resolve(url, ( + packageJSON.exports?.['.'] || + packageJSON.exports?.['./index.js'] || + packageJSON.exports?.['index.js'] + )) + + loadPackage(module, filename) } catch (error) { error.module = module module.dispatchEvent(new ErrorEvent('error', { error })) @@ -572,8 +673,6 @@ export class Module extends EventTarget { return false } finally { module.loaded = true - Object.freeze(module) - Object.seal(module) } if (module.parent) { @@ -589,9 +688,17 @@ export class Module extends EventTarget { // eslint-disable-next-line no-new-func const define = new Function(`return ${source}`)() + const oldId = module.id module.id = new URL(filename, module.parent.id).toString() module.filename = filename + if (oldId !== module.id && Module.cache[module.id]) { + module.exports = Module.cache[module.id].exports + return true + } + + Module.cache[module.id] = module + // eslint-disable-next-line no-useless-call const promise = define.call(null, module.exports, @@ -620,8 +727,6 @@ export class Module extends EventTarget { return false } finally { module.loaded = true - Object.freeze(module) - Object.seal(module) } } } @@ -634,9 +739,12 @@ export class Module extends EventTarget { createRequire () { const module = this - Object.assign(require, { cache: Module.cache }) - Object.freeze(require) - Object.seal(require) + Object.assign(require, { + cache: Module.cache, + resolve (filename) { + return resolve(filename, Array.from(Module.resolvers)) + } + }) return require @@ -647,7 +755,17 @@ export class Module extends EventTarget { if (typeof result === 'string') { const name = result.replace(/^(socket|node):/, '') - if (name in builtins) { + if ( + socketRuntimeModules.includes(name) && + !result.startsWith('socket:') + ) { + throw new ModuleNotFoundError( + `Cannot require module ${filename} without 'socket:' prefix`, + this.children + ) + } + + if (isBuiltin(result)) { return builtins[name] } @@ -702,8 +820,10 @@ export class Module extends EventTarget { } } +export default Module + +builtins.module = Module + // builtins should never be overloaded through this object, instead // a custom resolver should be used Object.freeze(Object.seal(builtins)) -// prevent misuse of the `Module` class -Object.freeze(Object.seal(Module)) diff --git a/api/process.js b/api/process.js index 77de01822e..1388252a84 100644 --- a/api/process.js +++ b/api/process.js @@ -12,16 +12,97 @@ import os from './os.js' let didEmitExitEvent = false +export const env = Object.create({}, { + proxy: { + configurable: false, + enumerable: false, + writable: false, + value: new Proxy({}, { + get (_, property, receiver) { + if (Reflect.has(env, property)) { + return Reflect.get(env, property) + } + + return Reflect.get(globalThis.__args.env, property) + }, + + set (_, property, value) { + return Reflect.set(env, property, value) + }, + + deleteProperty (_, property) { + return Reflect.deleteProperty(env, property) + }, + + has (_, property) { + return ( + Reflect.has(env, property) || + Reflect.has(globalThis.__args.env, property) + ) + }, + + ownKeys (_) { + const keys = [] + keys.push(...Reflect.ownKeys(env)) + keys.push(...Reflect.ownKeys(globalThis.__args.env)) + return Array.from(new Set(keys)) + } + }) + } +}) + class Process extends EventEmitter { - arch = primordials.arch - argv = globalThis.__args?.argv ?? [] - argv0 = globalThis.__args?.argv?.[0] ?? '' - cwd = () => primordials.cwd - env = { ...(globalThis.__args?.env ?? {}) } - exit = exit - homedir = homedir - platform = primordials.platform - version = primordials.version + get version () { + return primordials.version + } + + get platform () { + return primordials.platform + } + + get env () { + return env.proxy + } + + get arch () { + return primordials.arch + } + + get argv () { + return globalThis.__args?.argv ?? [] + } + + get argv0 () { + return this.argv[0] ?? '' + } + + get execArgv () { + return [] + } + + cwd () { + return primordials.cwd + } + + exit (code) { + return exit(code) + } + + homedir () { + return homedir() + } + + nextTick (callback) { + return nextTick(callback) + } + + hrtime (time = [0, 0]) { + return hrtime(time) + } + + memoryUsage () { + return memoryUsage + } } const isNode = Boolean(globalThis.process?.versions?.node) @@ -40,7 +121,7 @@ export default process * @param {Function} callback */ export function nextTick (callback) { - if (typeof process.nextTick === 'function' && process.nextTick !== nextTick) { + if (isNode && typeof process.nextTick === 'function' && process.nextTick !== nextTick) { process.nextTick(callback) } else if (typeof globalThis.setImmediate === 'function') { globalThis.setImmediate(callback) @@ -98,6 +179,8 @@ if (typeof process.hrtime !== 'function') { process.hrtime = hrtime } +process.hrtime.bigint = hrtime.bigint + /** * @param {number=} [code=0] - The exit code. Default: 0. */ @@ -128,3 +211,5 @@ memoryUsage.rss = function rss () { const rusage = os.rusage() return rusage.ru_maxrss } + +process.memoryUsage.rss = memoryUsage.rss diff --git a/api/querystring.js b/api/querystring.js new file mode 100644 index 0000000000..954224f605 --- /dev/null +++ b/api/querystring.js @@ -0,0 +1,429 @@ +/* eslint-disable */ +// The MIT License (MIT) +// +// Copyright (c) 2015 Mathias Rasmussen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Query String Utilities - https://github.com/mathiasvr/querystring + +import { Buffer } from './buffer.js' + +const HEX_TABLE = new Array(256) + +for (var i = 0; i < 256; ++i) { + HEX_TABLE[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase() +} + + +// a safe fast alternative to decodeURIComponent +export function unescapeBuffer (s, decodeSpaces) { + var out = new Buffer(s.length); + var state = 0; + var n, m, hexchar; + + for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) { + var c = inIndex < s.length ? s.charCodeAt(inIndex) : NaN; + switch (state) { + case 0: // Any character + switch (c) { + case 37: // '%' + n = 0; + m = 0; + state = 1; + break; + case 43: // '+' + if (decodeSpaces) + c = 32; // ' ' + // falls through + default: + out[outIndex++] = c; + break; + } + break; + + case 1: // First hex digit + hexchar = c; + if (c >= 48/*0*/ && c <= 57/*9*/) { + n = c - 48/*0*/; + } else if (c >= 65/*A*/ && c <= 70/*F*/) { + n = c - 65/*A*/ + 10; + } else if (c >= 97/*a*/ && c <= 102/*f*/) { + n = c - 97/*a*/ + 10; + } else { + out[outIndex++] = 37/*%*/; + out[outIndex++] = c; + state = 0; + break; + } + state = 2; + break; + + case 2: // Second hex digit + state = 0; + if (c >= 48/*0*/ && c <= 57/*9*/) { + m = c - 48/*0*/; + } else if (c >= 65/*A*/ && c <= 70/*F*/) { + m = c - 65/*A*/ + 10; + } else if (c >= 97/*a*/ && c <= 102/*f*/) { + m = c - 97/*a*/ + 10; + } else { + out[outIndex++] = 37/*%*/; + out[outIndex++] = hexchar; + out[outIndex++] = c; + break; + } + out[outIndex++] = 16 * n + m; + break; + } + } + + return out.slice(0, outIndex - 1); +} + +export function unescape (s, decodeSpaces) { + try { + return decodeURIComponent(s); + } catch (e) { + return unescapeBuffer(s, decodeSpaces).toString(); + } +} + +export function escape (str) { + // replaces encodeURIComponent + // http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 + if (typeof str !== 'string') + str += ''; + var out = ''; + var lastPos = 0; + + for (var i = 0; i < str.length; ++i) { + var c = str.charCodeAt(i); + + // These characters do not need escaping (in order): + // ! - . _ ~ + // ' ( ) * + // digits + // alpha (uppercase) + // alpha (lowercase) + if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E || + (c >= 0x27 && c <= 0x2A) || + (c >= 0x30 && c <= 0x39) || + (c >= 0x41 && c <= 0x5A) || + (c >= 0x61 && c <= 0x7A)) { + continue; + } + + if (i - lastPos > 0) + out += str.slice(lastPos, i); + + // Other ASCII characters + if (c < 0x80) { + lastPos = i + 1; + out += HEX_TABLE[c]; + continue; + } + + // Multi-byte characters ... + if (c < 0x800) { + lastPos = i + 1; + out += HEX_TABLE[0xC0 | (c >> 6)] + HEX_TABLE[0x80 | (c & 0x3F)]; + continue; + } + if (c < 0xD800 || c >= 0xE000) { + lastPos = i + 1; + out += HEX_TABLE[0xE0 | (c >> 12)] + + HEX_TABLE[0x80 | ((c >> 6) & 0x3F)] + + HEX_TABLE[0x80 | (c & 0x3F)]; + continue; + } + // Surrogate pair + ++i; + var c2; + if (i < str.length) + c2 = str.charCodeAt(i) & 0x3FF; + else + throw new URIError('URI malformed'); + lastPos = i + 1; + c = 0x10000 + (((c & 0x3FF) << 10) | c2); + out += HEX_TABLE[0xF0 | (c >> 18)] + + HEX_TABLE[0x80 | ((c >> 12) & 0x3F)] + + HEX_TABLE[0x80 | ((c >> 6) & 0x3F)] + + HEX_TABLE[0x80 | (c & 0x3F)]; + } + if (lastPos === 0) + return str; + if (lastPos < str.length) + return out + str.slice(lastPos); + + return out; +} + +function stringifyPrimitive (v) { + if (typeof v === 'string') + return v; + if (typeof v === 'number' && isFinite(v)) + return '' + v; + if (typeof v === 'boolean') + return v ? 'true' : 'false'; + return ''; +} + +export function stringify (obj, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + + var encode = escape; + if (options && typeof options.encodeURIComponent === 'function') { + encode = options.encodeURIComponent; + } + + if (obj !== null && typeof obj === 'object') { + var keys = Object.keys(obj); + var len = keys.length; + var flast = len - 1; + var fields = ''; + for (var i = 0; i < len; ++i) { + var k = keys[i]; + var v = obj[k]; + var ks = encode(stringifyPrimitive(k)) + eq; + + if (Array.isArray(v)) { + var vlen = v.length; + var vlast = vlen - 1; + for (var j = 0; j < vlen; ++j) { + fields += ks + encode(stringifyPrimitive(v[j])); + if (j < vlast) + fields += sep; + } + if (vlen && i < flast) + fields += sep; + } else { + fields += ks + encode(stringifyPrimitive(v)); + if (i < flast) + fields += sep; + } + } + return fields; + } + return ''; +} + +// Parse a key/val string. +export function parse (qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + + var obj = {} + + if (typeof qs !== 'string' || qs.length === 0) { + return obj; + } + + if (typeof sep !== 'string') + sep += ''; + + var eqLen = eq.length; + var sepLen = sep.length; + + var maxKeys = 1000; + if (options && typeof options.maxKeys === 'number') { + maxKeys = options.maxKeys; + } + + var pairs = Infinity; + if (maxKeys > 0) + pairs = maxKeys; + + var decode = unescape; + if (options && typeof options.decodeURIComponent === 'function') { + decode = options.decodeURIComponent; + } + var customDecode = (decode !== unescape); + + var keys = []; + var lastPos = 0; + var sepIdx = 0; + var eqIdx = 0; + var key = ''; + var value = ''; + var keyEncoded = customDecode; + var valEncoded = customDecode; + var encodeCheck = 0; + for (var i = 0; i < qs.length; ++i) { + var code = qs.charCodeAt(i); + + // Try matching key/value pair separator (e.g. '&') + if (code === sep.charCodeAt(sepIdx)) { + if (++sepIdx === sepLen) { + // Key/value pair separator match! + var end = i - sepIdx + 1; + if (eqIdx < eqLen) { + // If we didn't find the key/value separator, treat the substring as + // part of the key instead of the value + if (lastPos < end) + key += qs.slice(lastPos, end); + } else if (lastPos < end) + value += qs.slice(lastPos, end); + if (keyEncoded) + key = decodeStr(key, decode); + if (valEncoded) + value = decodeStr(value, decode); + // Use a key array lookup instead of using hasOwnProperty(), which is + // slower + if (keys.indexOf(key) === -1) { + obj[key] = value; + keys[keys.length] = key; + } else { + var curValue = obj[key]; + // `instanceof Array` is used instead of Array.isArray() because it + // is ~15-20% faster with v8 4.7 and is safe to use because we are + // using it with values being created within this function + if (curValue instanceof Array) + curValue[curValue.length] = value; + else + obj[key] = [curValue, value]; + } + if (--pairs === 0) + break; + keyEncoded = valEncoded = customDecode; + encodeCheck = 0; + key = value = ''; + lastPos = i + 1; + sepIdx = eqIdx = 0; + } + continue; + } else { + sepIdx = 0; + if (!valEncoded) { + // Try to match an (valid) encoded byte (once) to minimize unnecessary + // calls to string decoding functions + if (code === 37/*%*/) { + encodeCheck = 1; + } else if (encodeCheck > 0 && + ((code >= 48/*0*/ && code <= 57/*9*/) || + (code >= 65/*A*/ && code <= 70/*Z*/) || + (code >= 97/*a*/ && code <= 102/*z*/))) { + if (++encodeCheck === 3) + valEncoded = true; + } else { + encodeCheck = 0; + } + } + } + + // Try matching key/value separator (e.g. '=') if we haven't already + if (eqIdx < eqLen) { + if (code === eq.charCodeAt(eqIdx)) { + if (++eqIdx === eqLen) { + // Key/value separator match! + var end = i - eqIdx + 1; + if (lastPos < end) + key += qs.slice(lastPos, end); + encodeCheck = 0; + lastPos = i + 1; + } + continue; + } else { + eqIdx = 0; + if (!keyEncoded) { + // Try to match an (valid) encoded byte once to minimize unnecessary + // calls to string decoding functions + if (code === 37/*%*/) { + encodeCheck = 1; + } else if (encodeCheck > 0 && + ((code >= 48/*0*/ && code <= 57/*9*/) || + (code >= 65/*A*/ && code <= 70/*Z*/) || + (code >= 97/*a*/ && code <= 102/*z*/))) { + if (++encodeCheck === 3) + keyEncoded = true; + } else { + encodeCheck = 0; + } + } + } + } + + if (code === 43/*+*/) { + if (eqIdx < eqLen) { + if (i - lastPos > 0) + key += qs.slice(lastPos, i); + key += '%20'; + keyEncoded = true; + } else { + if (i - lastPos > 0) + value += qs.slice(lastPos, i); + value += '%20'; + valEncoded = true; + } + lastPos = i + 1; + } + } + + // Check if we have leftover key or value data + if (pairs > 0 && (lastPos < qs.length || eqIdx > 0)) { + if (lastPos < qs.length) { + if (eqIdx < eqLen) + key += qs.slice(lastPos); + else if (sepIdx < sepLen) + value += qs.slice(lastPos); + } + if (keyEncoded) + key = decodeStr(key, decode); + if (valEncoded) + value = decodeStr(value, decode); + // Use a key array lookup instead of using hasOwnProperty(), which is + // slower + if (keys.indexOf(key) === -1) { + obj[key] = value; + keys[keys.length] = key; + } else { + var curValue = obj[key]; + // `instanceof Array` is used instead of Array.isArray() because it + // is ~15-20% faster with v8 4.7 and is safe to use because we are + // using it with values being created within this function + if (curValue instanceof Array) + curValue[curValue.length] = value; + else + obj[key] = [curValue, value]; + } + } + + return obj; +} + +export const decode = parse +export const encode = stringify + +export default { + decode, + encode, + parse, + stringify, + escape, + unescape +} + +function decodeStr(s, decoder) { + try { + return decoder(s); + } catch (e) { + return unescape(s, true); + } +} diff --git a/api/service-worker/clients.js b/api/service-worker/clients.js index 6e0bdd5322..84e151809e 100644 --- a/api/service-worker/clients.js +++ b/api/service-worker/clients.js @@ -131,7 +131,6 @@ export class WindowClient extends Client { throw new TypeError('WindowClient cannot navigate outside of origin') } - console.log('navigate', url) await this.#window.navigate(url.pathname + url.search) this.#url = url.pathname + url.search return this diff --git a/api/service-worker/events.js b/api/service-worker/events.js index fd14003e84..0e5b479cb1 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -41,6 +41,10 @@ export class ExtendableEvent extends Event { return await this.#promise } + get awaiting () { + return this.waitsFor() + } + get pendingPromises () { return this.#pendingPromiseCount } @@ -118,6 +122,8 @@ export class FetchEvent extends ExtendableEvent { handled.resolve() } catch (err) { state.reportError(err) + } finally { + handled.resolve() } }) } diff --git a/api/service-worker/index.html b/api/service-worker/index.html index 3668b4e5b6..abebbe1524 100644 --- a/api/service-worker/index.html +++ b/api/service-worker/index.html @@ -6,7 +6,7 @@ http-equiv="Content-Security-Policy" content=" connect-src http: https: ipc: socket:; - script-src https: blob: socket: 'unsafe-eval' 'unsafe-inline'; + script-src https: blob: socket: node: 'unsafe-eval' 'unsafe-inline'; worker-src https: blob: socket: 'unsafe-eval' 'unsafe-inline'; img-src 'none'; child-src https: socket: blob:; diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 5aa56f0b08..07fa27fc28 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -104,7 +104,6 @@ export function createServiceWorker ( if (data.serviceWorker) { if (data.serviceWorker.state && data.serviceWorker.state !== currentState) { const scope = new URL(globalThis.location.href).pathname - console.log('instance channel message', scope, data.serviceWorker.scope) if (scope.startsWith(data.serviceWorker.scope)) { currentState = data.serviceWorker.state scriptURL = data.serviceWorker.scriptURL diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 152fa7c22c..4ad2dea8b5 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -1,6 +1,7 @@ import { ExtendableEvent, FetchEvent } from './events.js' import { ServiceWorkerGlobalScope } from './global.js' import { InvertedPromise } from '../util.js' +import { createRequire } from '../module.js' import clients from './clients.js' import hooks from '../hooks.js' import state from './state.js' @@ -34,6 +35,8 @@ async function onMessage (event) { state.serviceWorker.scope = scope state.serviceWorker.scriptURL = scriptURL + globalThis.require = createRequire(scriptURL) + try { module = await import(scriptURL) } catch (err) { @@ -154,6 +157,7 @@ async function onMessage (event) { } catch (err) { state.reportError(err) response = new Response(err.message, { + statusText: 'Internal Server Error', status: 500 }) } @@ -161,7 +165,10 @@ async function onMessage (event) { if (response) { promise.resolve(response) } else { - promise.resolve(new Response('', { status: 404 })) + promise.resolve(new Response('', { + statusText: 'Not found', + status: 404 + })) } }) } diff --git a/api/stream.js b/api/stream.js index afae1294cd..cc0cf9bcea 100644 --- a/api/stream.js +++ b/api/stream.js @@ -27,7 +27,9 @@ */ import { EventEmitter } from './events.js' import * as exports from './stream.js' +import web from './stream/web.js' +export { web } export default exports const STREAM_DESTROYED = new Error('Stream was destroyed') diff --git a/api/stream/web.js b/api/stream/web.js new file mode 100644 index 0000000000..c932de1cc7 --- /dev/null +++ b/api/stream/web.js @@ -0,0 +1,21 @@ +import * as exports from './web.js' + +export const ReadableStream = globalThis.ReadableStream +export const ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader +export const ReadableStreamBYOBReader = globalThis.ReadableStreamBYOBReader +export const ReadableStreamBYOBRequest = globalThis.ReadableStreamBYOBRequest +export const ReadableByteStreamController = globalThis.ReadableByteStreamController +export const ReadableStreamDefaultController = globalThis.ReadableStreamDefaultController +export const TransformStream = globalThis.TransformStream +export const TransformStreamDefaultController = globalThis.TransformStreamDefaultController +export const WritableStream = globalThis.WritableStream +export const WritableStreamDefaultWriter = globalThis.WritableStreamDefaultWriter +export const WritableStreamDefaultController = globalThis.WritableStreamDefaultController +export const ByteLengthQueuingStrategy = globalThis.ByteLengthQueuingStrategy +export const CountQueuingStrategy = globalThis.CountQueuingStrategy +export const TextEncoderStream = globalThis.TextEncoderStream +export const TextDecoderStream = globalThis.TextDecoderStream +export const CompressionStream = globalThis.CompressionStream +export const DecompressionStream = globalThis.DecompressionStream + +export default exports diff --git a/api/string_decoder.js b/api/string_decoder.js new file mode 100644 index 0000000000..de3ce55852 --- /dev/null +++ b/api/string_decoder.js @@ -0,0 +1,298 @@ +/* eslint-disable */ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +import { Buffer } from './buffer.js' +/**/ + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +export function StringDecoder (encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} + +export default StringDecoder diff --git a/api/timers.js b/api/timers.js new file mode 100644 index 0000000000..d53d48da6f --- /dev/null +++ b/api/timers.js @@ -0,0 +1,7 @@ +/** + * @notice This is a re-exports of `timers/index.js` so consumers will + * need to only `import * as timers from 'socket:timers'` + */ +import * as exports from './timers/index.js' +export * from './timers/index.js' +export default exports diff --git a/api/timers/index.js b/api/timers/index.js new file mode 100644 index 0000000000..e384fbb0c1 --- /dev/null +++ b/api/timers/index.js @@ -0,0 +1,65 @@ +import { Timeout, Immediate, Interval } from './timer.js' +import scheduler from './scheduler.js' +import promises from './promises.js' + +const platform = { + setTimeout: globalThis.setTimeout.bind(globalThis), + setInterval: globalThis.setInterval.bind(globalThis), + setImmediate: globalThis.setTimeout.bind(globalThis) +} + +export function setTimeout (callback, delay, ...args) { + return Timeout.from(callback, delay, ...args) +} + +export function clearTimeout (timeout) { + if (timeout instanceof Timeout) { + timeout.close() + } else { + platform.clearTimeout(timeout) + } +} + +export function setInterval (callback, delay, ...args) { + return Interval.from(callback, delay, ...args) +} + +export function clearInterval (interval) { + if (interval instanceof Interval) { + interval.close() + } else { + platform.clearInterval(interval) + } +} + +export function setImmediate (callback, ...args) { + return Immediate.from(callback, ...args) +} + +export function clearImmediate (immediate) { + if (immediate instanceof Immediate) { + immediate.close() + } else { + platform.clearImmediate(immediate) + } +} + +setTimeout[Symbol.for('nodejs.util.promisify.custom')] = promises.setTimeout +setTimeout[Symbol.for('socket.util.promisify.custom')] = promises.setTimeout + +setInterval[Symbol.for('nodejs.util.promisify.custom')] = promises.setInterval +setInterval[Symbol.for('socket.util.promisify.custom')] = promises.setInterval + +setImmediate[Symbol.for('nodejs.util.promisify.custom')] = promises.setImmediate +setImmediate[Symbol.for('socket.util.promisify.custom')] = promises.setImmediate + +export default { + promises, + scheduler, + setTimeout, + clearTimeout, + setInterval, + clearInterval, + setImmediate, + clearImmediate +} diff --git a/api/timers/promises.js b/api/timers/promises.js new file mode 100644 index 0000000000..c705009d3e --- /dev/null +++ b/api/timers/promises.js @@ -0,0 +1,72 @@ +import { Timeout, Immediate } from './timer.js' + +export async function setTimeout (delay = 1, value = undefined, options = null) { + return await new Promise((resolve, reject) => { + const signal = options?.signal + + if (signal?.aborted) { + return reject(new DOMException('This operation was aborted', 'AbortError')) + } + + const timeout = Timeout.from(callback, delay) + + if (signal) { + signal.addEventListener('abort', () => { + timeout.close() + reject(new DOMException('This operation was aborted', 'AbortError')) + }) + } + + function callback () { + resolve(value) + } + }) +} + +export async function * setInterval (delay = 1, value = undefined, options = null) { + const signal = options?.signal + + while (true) { + yield await new Promise((resolve, reject) => { + const timeout = Timeout.from(callback, delay) + + if (signal?.aborted) { + timeout.close() + return reject(new DOMException('This operation was aborted', 'AbortError')) + } + + function callback () { + resolve(value) + } + }) + } +} + +export async function setImmediate (value = undefined, options = null) { + return await new Promise((resolve, reject) => { + const signal = options?.signal + + if (signal?.aborted) { + return reject(new DOMException('This operation was aborted', 'AbortError')) + } + + const immediate = Immediate.from(callback, 0) + + if (signal) { + signal.addEventListener('abort', () => { + immediate.close() + reject(new DOMException('This operation was aborted', 'AbortError')) + }) + } + + function callback () { + resolve(value) + } + }) +} + +export default { + setImmediate, + setInterval, + setTimeout +} diff --git a/api/timers/scheduler.js b/api/timers/scheduler.js new file mode 100644 index 0000000000..13d0933d1d --- /dev/null +++ b/api/timers/scheduler.js @@ -0,0 +1,22 @@ +import { setImmediate, setTimeout } from './promises.js' + +const platform = { + postTask: ( + globalThis.scheduler?.postTask?.bind?.(globalThis?.scheduler) ?? + async function notSupported () {} + ) +} + +export async function wait (delay, options = null) { + return await setTimeout(delay, undefined, options) +} + +export async function postTask (callback, options = null) { + return await platform.postTask(callback, options) +} + +export default { + postTask, + yield: setImmediate, + wait +} diff --git a/api/timers/timer.js b/api/timers/timer.js new file mode 100644 index 0000000000..e5589865b9 --- /dev/null +++ b/api/timers/timer.js @@ -0,0 +1,102 @@ +import gc from '../gc.js' + +const platform = { + setTimeout: globalThis.setTimeout.bind(globalThis), + clearTimeout: globalThis.clearTimeout.bind(globalThis), + setInterval: globalThis.setInterval.bind(globalThis), + clearInterval: globalThis.clearInterval.bind(globalThis), + setImmediate: globalThis.setTimeout.bind(globalThis), + clearImmediate: globalThis.clearTimeout.bind(globalThis) +} + +export class Timer { + #id = 0 + #closed = false + #create = null + #destroy = null + + static from (...args) { + const timer = new this() + return timer.init(...args) + } + + constructor (create, destroy) { + if (typeof create !== 'function') { + throw new TypeError('Timer creator must be a function') + } + + if (typeof destroy !== 'function') { + throw new TypeError('Timer destroyer must be a function') + } + + this.#create = create + this.#destroy = destroy + } + + get id () { + return this.#id + } + + init (...args) { + this.#id = this.create(...args) + gc.ref(this) + } + + close () { + if (this.#id) { + this.#destroy(this.#id) + this.#closed = true + this.#id = 0 + return true + } + + return false + } + + [Symbol.toPrimitive] () { + return this.#id + } + + [gc.finalizer] () { + return { + args: [this.id, this.#destroy], + handle (id, destroy) { + destroy(id) + } + } + } +} + +export class Timeout extends Timer { + constructor () { + super( + (callback, delay, ...args) => { + return platform.setTimeout( + (...args) => { + this.close() + // eslint-disable-next-line + callback(...args) + }, + delay, + ...args + ) + }, + platform.clearTimeout + ) + } +} + +export class Interval extends Timer { + constructor () { + super(platform.setInterval, platform.clearInterval) + } +} + +export class Immediate extends Timeout {} + +export default { + Timer, + Immediate, + Timeout, + Interval +} diff --git a/api/util.js b/api/util.js index 08332f0e44..99869b1b06 100644 --- a/api/util.js +++ b/api/util.js @@ -1,6 +1,7 @@ import { IllegalConstructorError } from './errors.js' import { Buffer } from './buffer.js' import { URL } from './url.js' +import mime from './mime.js' import * as exports from './util.js' @@ -23,10 +24,52 @@ function maybeURL (...args) { } } +export const TextDecoder = globalThis.TextDecoder +export const TextEncoder = globalThis.TextEncoder +export const isArray = Array.isArray.bind(Array) + +export function debug (section) { + let enabled = false + const env = globalThis.__args?.env ?? {} + const sections = [].concat( + (env.SOCKET_DEBUG ?? '').split(','), + (env.NODE_DEBUG ?? '').split(',') + ).map((section) => section.trim()) + + if (section && sections.includes(section)) { + enabled = true + } + + function logger (...args) { + if (enabled) { + return console.debug(...args) + } + } + + Object.defineProperty(logger, 'enabled', { + configurable: false, + enumerable: false, + get: () => enabled, + set: (value) => { + if (value === true) { + enabled = true + } else if (value === false) { + enabled = false + } + } + }) + + return logger +} + export function hasOwnProperty (object, property) { return ObjectPrototype.hasOwnProperty.call(object, String(property)) } +export function isDate (object) { + return object instanceof Date +} + export function isTypedArray (object) { return object instanceof TypedArray } @@ -39,12 +82,35 @@ export function isArrayLike (object) { ) } +export function isError (object) { + return object && object instanceof Error +} + +export function isSymbol (value) { + return typeof value === 'symbol' +} + +export function isNumber (value) { + return !isUndefined(value) && !isNull(value) && ( + typeof value === 'number' || + value instanceof Number + ) +} + +export function isBoolean (value) { + return !isUndefined(value) && !isNull(value) && ( + value === true || + value === false || + value instanceof Boolean + ) +} + export function isArrayBufferView (buf) { return !Buffer.isBuffer(buf) && ArrayBuffer.isView(buf) } export function isAsyncFunction (object) { - return object instanceof AsyncFunction + return object && object instanceof AsyncFunction } export function isArgumentsObject (object) { @@ -70,6 +136,32 @@ export function isObject (object) { ) } +export function isUndefined (value) { + return value === undefined +} + +export function isNull (value) { + return value === null +} + +export function isNullOrUndefined (value) { + return isNull(value) || isUndefined(value) +} + +export function isPrimitive (value) { + return ( + isNullOrUndefined(value) || + typeof value === 'number' || + typeof value === 'string' || + typeof value === 'symbol' || + typeof value === 'boolean' + ) +} + +export function isRegExp (value) { + return value && value instanceof RegExp +} + export function isPlainObject (object) { return ( object !== null && @@ -106,6 +198,10 @@ export function isClass (value) { ) } +export function isBuffer (value) { + return Buffer.isBuffer(value) +} + export function isPromiseLike (object) { return isFunction(object?.then) } @@ -204,7 +300,12 @@ export function promisify (original) { } for (const key in original) { - object[key] = promisify(original[key]) + const value = original[key] + if (typeof value === 'function' || (value && typeof value === 'object')) { + object[key] = promisify(original[key].bind(original)) + } else { + object[key] = original[key] + } } Object.defineProperty(object, promisify.custom, { @@ -219,6 +320,7 @@ export function promisify (original) { } if (typeof original !== 'function') { + console.log({ original }) throw new TypeError('Expecting original to be a function or object.') } @@ -824,4 +926,22 @@ export function compareBuffers (a, b) { return toBuffer(a).compare(toBuffer(b)) } +export function inherits (Constructor, Super) { + Object.defineProperty(Constructor, 'super_', { + configurable: true, + writable: true, + value: Super, + __proto__: null + }) + + Object.setPrototypeO(Constructor.prototype, Super.prototype) +} + +export function deprecate (...args) { + // noop +} + +export const MIMEType = mime.MIMEType +export const MIMEParams = mime.MIMEParams + export default exports diff --git a/api/vm.js b/api/vm.js index 55ed73e4dd..4d22ca955d 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1455,6 +1455,10 @@ export function getTrasferables (object) { return transferables } +export function createContext (object) { + return object +} + export default { compileFunction, createReference, @@ -1468,5 +1472,6 @@ export default { runInContext, runInNewContext, runInThisContext, - Script + Script, + createContext } diff --git a/api/window/constants.js b/api/window/constants.js index c694d32459..013e7a0b5b 100644 --- a/api/window/constants.js +++ b/api/window/constants.js @@ -1,3 +1,5 @@ +import * as exports from './constants.js' + export const WINDOW_ERROR = -1 export const WINDOW_NONE = 0 export const WINDOW_CREATING = 10 @@ -13,4 +15,4 @@ export const WINDOW_EXITED = 51 export const WINDOW_KILLING = 60 export const WINDOW_KILLED = 61 -export * as default from './constants.js' +export default exports diff --git a/src/android/webview.kt b/src/android/webview.kt index 8dae4a9fc5..d35afbe8cc 100644 --- a/src/android/webview.kt +++ b/src/android/webview.kt @@ -511,7 +511,7 @@ export default module } var html = String(assetStream.readAllBytes()) - val script = """""" + val script = """$preload""" val stream = java.io.PipedOutputStream() val response = android.webkit.WebResourceResponse( "text/html", diff --git a/src/android/window.cc b/src/android/window.cc index 6c0f2694d1..8dd38caf97 100644 --- a/src/android/window.cc +++ b/src/android/window.cc @@ -54,7 +54,8 @@ namespace SSC::android { options.clientId = this->bridge->id; preloadSource = createPreload(options, PreloadOptions { - .module = true + .module = true, + .wrap = true }); } diff --git a/src/core/preload.cc b/src/core/preload.cc index 0f7d64b198..a5a4c2e8af 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -20,7 +20,13 @@ namespace SSC { } #endif - auto preload = String( + String preload = ""; + + if (preloadOptions.wrap) { + preload += "\n"; + preload += "\n"; + } } else { preload += ( "if (document.readyState === 'complete') { \n" @@ -248,6 +247,10 @@ namespace SSC { " }, { once: true }); \n" "} \n" ); + + if (preloadOptions.wrap) { + preload += "\n"; + } } return preload; diff --git a/src/core/preload.hh b/src/core/preload.hh index 871408ef63..6c40a507ff 100644 --- a/src/core/preload.hh +++ b/src/core/preload.hh @@ -6,6 +6,7 @@ namespace SSC { struct PreloadOptions { bool module = false; + bool wrap = false; }; String createPreload ( diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 237aae4c0e..3134cb771f 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -269,6 +269,17 @@ namespace SSC { return false; } + for (const auto& entry : request.headers) { + const auto parts = split(trim(entry), ':'); + if (parts.size() == 2) { + const auto key = trim(parts[0]); + const auto value = trim(parts[1]); + if (key == "runtime-worker-type" && value == "serviceworker") { + return false; + } + } + } + for (const auto& entry : this->registrations) { const auto& registration = entry.second; if (request.pathname.starts_with(registration.options.scope)) { diff --git a/src/ios/main.mm b/src/ios/main.mm index e20dcad795..54edb96419 100644 --- a/src/ios/main.mm +++ b/src/ios/main.mm @@ -317,7 +317,8 @@ - (BOOL) application: (UIApplication*) application // Note: you won't see any logs in the preload script before the // Web Inspector is opened bridge->preload = createPreload(opts, { - .module = true + .module = true, + .wrap = true }); uv_chdir(cwd.UTF8String); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 2290e6c44e..7041065a5c 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2939,6 +2939,7 @@ static void registerSchemeHandler (Router *router) { const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); const auto request = task.request; + const auto scheme = String(request.URL.scheme.UTF8String); const auto url = String(request.URL.absoluteString.UTF8String); uint64_t clientId = self.router->bridge->id; @@ -2987,7 +2988,7 @@ static void registerSchemeHandler (Router *router) { return; } - if (String(request.URL.scheme.UTF8String) == "socket") { + if (scheme == "socket" || scheme == "node") { auto host = request.URL.host; auto components = [NSURLComponents componentsWithURL: request.URL @@ -3030,6 +3031,7 @@ static void registerSchemeHandler (Router *router) { } if ( + scheme != "node" && host.UTF8String != nullptr && String(host.UTF8String) == bundleIdentifier ) { @@ -3331,9 +3333,7 @@ static void registerSchemeHandler (Router *router) { if (absoluteURLPathExtension.ends_with("html")) { const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; auto html = String(string.UTF8String); - const auto script = String( - "\n\n" - ); + const auto script = self.router->bridge->preload; if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); diff --git a/src/window/apple.mm b/src/window/apple.mm index 5429a15814..27eab149ae 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -46,6 +46,7 @@ - (void) webView: (WKWebView*) webview if (hasAppLink) { if (self.bridge != nullptr) { + debug("CANCEL #1: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); SSC::JSON::Object json = SSC::JSON::Object::Entries {{ "url", request @@ -58,9 +59,11 @@ - (void) webView: (WKWebView*) webview if ( userConfig["meta_application_protocol"].size() > 0 && - request.starts_with(userConfig["meta_application_protocol"]) + request.starts_with(userConfig["meta_application_protocol"]) && + !request.starts_with("socket://" + userConfig["meta_bundle_identifier"]) ) { if (self.bridge != nullptr) { + debug("CANCEL #2: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); SSC::JSON::Object json = SSC::JSON::Object::Entries {{ @@ -73,6 +76,7 @@ - (void) webView: (WKWebView*) webview } if (!request.starts_with("socket:") && !request.starts_with(devHost)) { + debug("CANCEL #3: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); return; } @@ -766,6 +770,9 @@ - (void) webView: (WKWebView*) webView [config setURLSchemeHandler: bridge->router.schemeHandler forURLScheme: @"socket"]; + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"node"]; + [config setValue: @NO forKey: @"crossOriginAccessControlCheckEnabled"]; WKPreferences* prefs = [config preferences]; @@ -929,7 +936,10 @@ - (void) webView: (WKWebView*) webView opts.clientId = this->bridge->id; // Add preload script, normalizing the interface to be cross-platform. - this->bridge->preload = createPreload(opts, { .module = true }); + this->bridge->preload = createPreload(opts, { + .module = true, + .wrap = true + }); webview = [SSCBridgedWebView.alloc initWithFrame: NSZeroRect From 29e0df7aa4d0b615d0549901e33b8e4eb2de055e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 19 Feb 2024 18:20:29 +0100 Subject: [PATCH 0031/1178] refactor(): more platform APIs progress --- api/async_context.js | 28 +++ api/async_hooks.js | 3 + api/extension.js | 2 +- api/fs/index.js | 229 ++++++++++++++++++++++++- api/http.js | 81 +++++++-- api/internal/monkeypatch.js | 4 + api/module.js | 60 +++++-- api/node/index.js | 5 +- api/path/well-known.js | 16 +- api/process.js | 16 +- api/stream/web.js | 36 ++-- api/url/index.js | 81 ++++++++- api/util.js | 8 +- api/vm.js | 57 ++++-- api/worker_threads.js | 333 ++++++++++++++++++++++++++++++++++++ api/worker_threads/init.js | 315 ++++++++++++++++++++++++++++++++++ src/android/webview.kt | 2 +- src/ipc/bridge.cc | 8 +- src/window/win.cc | 6 +- 19 files changed, 1205 insertions(+), 85 deletions(-) create mode 100644 api/async_context.js create mode 100644 api/async_hooks.js create mode 100644 api/worker_threads.js create mode 100644 api/worker_threads/init.js diff --git a/api/async_context.js b/api/async_context.js new file mode 100644 index 0000000000..235be388b0 --- /dev/null +++ b/api/async_context.js @@ -0,0 +1,28 @@ +export class AsyncLocalStorage { + static bind (fn) { + } + + static snapshot () { + } + + disable () { + } + + getStore () { + } + + enterWith (store) { + } + + run (store, callback, ...args) { + } + + exit (callback, ...args) { + } +} + +export class AsyncResource {} + +export default { + AsyncLocalStorage +} diff --git a/api/async_hooks.js b/api/async_hooks.js new file mode 100644 index 0000000000..a4d7dd0149 --- /dev/null +++ b/api/async_hooks.js @@ -0,0 +1,3 @@ +import context from './async_context.js' +export * from './async_context.js' +export default context diff --git a/api/extension.js b/api/extension.js index 893b7ce837..eab6ef87c5 100644 --- a/api/extension.js +++ b/api/extension.js @@ -18,7 +18,7 @@ import ipc from './ipc.js' import fs from './fs/promises.js' /** - * @typedef {number} {Pointer} + * @typedef {number} Pointer */ const $loaded = Symbol('loaded') diff --git a/api/fs/index.js b/api/fs/index.js index b51ac09e3b..08ac429dd1 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -24,6 +24,8 @@ */ import { isBufferLike, isFunction, noop } from '../util.js' +import { rand64 } from '../crypto.js' +import { Buffer } from '../buffer.js' import console from '../console.js' import ipc from '../ipc.js' import gc from '../gc.js' @@ -31,6 +33,7 @@ import gc from '../gc.js' import { Dir, Dirent, sortDirectoryEntries } from './dir.js' import { DirectoryHandle, FileHandle } from './handle.js' import { ReadStream, WriteStream } from './stream.js' +import { normalizeFlags } from './flags.js' import * as constants from './constants.js' import * as promises from './promises.js' import { Watcher } from './watcher.js' @@ -99,13 +102,54 @@ export function access (path, mode, callback) { } /** - * @ignore + * Synchronously check access a file for a given mode calling `callback` + * upon success or error. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?} [mode = F_OK(0)] */ -export function appendFile (path, data, options, callback) { +export function accessSync (path, mode) { + const result = ipc.sendSync('fs.access', { path, mode }) + + if (result.err) { + throw result.err + } + + // F_OK means access in any way + return mode === constants.F_OK ? true : (result.data?.mode && mode) > 0 +} + +/** + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] + */ +export function exists (path, callback) { + if (typeof callback !== 'function') { + throw new TypeError('callback must be a function.') + } + + access(path, (err) => { + // eslint-disable-next-line + callback(err !== null) + }) +} + +/** + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] + */ +export function existsSync (path) { + try { + accessSync(path) + return true + } catch { + return false + } } /** - * * Asynchronously changes the permissions of a file. * No arguments other than a possible exception are given to the completion callback * @@ -133,6 +177,29 @@ export function chmod (path, mode, callback) { }) } +/** + * Synchronously changes the permissions of a file. + * + * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * @param {string | Buffer | URL} path + * @param {number} mode + */ +export function chmodSync (path, mode) { + if (typeof mode !== 'number') { + throw new TypeError(`The argument 'mode' must be a 32-bit unsigned integer or an octal string. Received ${mode}`) + } + + if (mode < 0 || !Number.isInteger(mode)) { + throw new RangeError(`The value of "mode" is out of range. It must be an integer. Received ${mode}`) + } + + const result = ipc.sendSync('fs.chmod', { mode, path }) + + if (result.err) { + throw result.err + } +} + /** * Changes ownership of file or directory at `path` with `uid` and `gid`. * @param {string} path @@ -162,6 +229,32 @@ export function chown (path, uid, gid, callback) { }).catch(callback) } +/** + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid + */ +export function chownSync (path, uid, gid) { + if (typeof path !== 'string') { + throw new TypeError('The argument \'path\' must be a string') + } + + if (!Number.isInteger(uid)) { + throw new TypeError('The argument \'uid\' must be an integer') + } + + if (!Number.isInteger(gid)) { + throw new TypeError('The argument \'gid\' must be an integer') + } + + const result = ipc.sendSync('fs.chown', { path, uid, gid }) + + if (result.err) { + throw result.err + } +} + /** * Asynchronously close a file descriptor calling `callback` upon success or error. * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsclosefd-callback} @@ -214,6 +307,33 @@ export function copyFile (src, dest, flags, callback) { }).catch(callback) } +/** + * Synchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} + */ +export function copyFileSync (src, dest, flags) { + if (typeof src !== 'string') { + throw new TypeError('The argument \'src\' must be a string') + } + + if (typeof dest !== 'string') { + throw new TypeError('The argument \'dest\' must be a string') + } + + if (!Number.isInteger(flags)) { + throw new TypeError('The argument \'flags\' must be an integer') + } + + const result = ipc.sendSync('fs.copyFile', { src, dest, flags }) + + if (result.err) { + throw result.err + } +} + /** * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path @@ -461,6 +581,32 @@ export function mkdir (path, options, callback) { .catch(err => callback(err)) } +/** + * @ignore + */ +export function mkdirSync (path, options) { + if ((typeof options === 'undefined') || (typeof options === 'function')) { + throw new TypeError('options must be an object.') + } + + const mode = options.mode || 0o777 + const recursive = Boolean(options.recursive) // default to false + + if (typeof mode !== 'number') { + throw new TypeError('mode must be a number.') + } + + if (mode < 0 || !Number.isInteger(mode)) { + throw new RangeError('mode must be a positive finite number.') + } + + const result = ipc.sendSync('fs.mkdir', { mode, path, recursive }) + + if (result.err) { + throw result.err + } +} + /** * Asynchronously open a file calling `callback` upon success or error. * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} @@ -672,6 +818,66 @@ export function readFile (path, options = {}, callback) { }) } +/** + * @param {string | Buffer | URL | number } path + * @param {object?|function(Error?, Buffer?)} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + */ +export function readFileSync (path, options = {}) { + if (typeof options === 'string') { + options = { encoding: options } + } + + options = { + flags: 'r', + ...options + } + + let result = null + + const id = String(options?.id || rand64()) + const stats = statSync(path) + + result = ipc.sendSync('fs.open', { + id, + mode: FileHandle.DEFAULT_OPEN_MODE, + path, + flags: normalizeFlags(options.flags) + }, options) + + if (result.err) { + throw result.err + } + + result = ipc.sendSync('fs.read', { + id, + size: stats.size, + offset: 0 + }, { responseType: 'arraybuffer' }) + + if (result.err) { + throw result.err + } + + const data = result.data + + result = ipc.sendSync('fs.close', { id }, options) + + if (result.err) { + throw result.err + } + + const buffer = Buffer.from(data) + + if (typeof options?.encoding === 'string') { + return buffer.toString(options.encoding) + } + + return buffer +} + /** * Reads link at `path` * @param {string} path @@ -753,6 +959,23 @@ export function rmdir (path, callback) { }) } +/** + * Synchronously get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + */ +export function statSync (path, options) { + const result = ipc.sendSync('fs.stat', { path }) + + if (result.err) { + throw result.err + } + + return Stats.from(result.data, Boolean(options?.bigint)) +} + /** * Get the stats of a file * @param {string | Buffer | URL | number } path - filename or file descriptor diff --git a/api/http.js b/api/http.js index 6504be1fee..fdecc5d75a 100644 --- a/api/http.js +++ b/api/http.js @@ -106,19 +106,27 @@ export const STATUS_CODES = { export class OutgoingMessage extends Writable { headers = new Headers() - socket = null get headersSent () { return true } + get socket () { + return this + } + + get writableEnded () { + return this._writableState?.ended === true + } + appendHeader (name, value) { - this.headers.set(name.toLowerCase(), value) + this.headers.append(name.toLowerCase(), value) return this } setHeader (name, value) { - return this.appendHeader(name, value) + this.headers.set(name.toLowerCase(), value) + return this } flushHeaders () { @@ -146,9 +154,38 @@ export class OutgoingMessage extends Writable { } } -export class ClientRequest extends OutgoingMessage {} +export class ClientRequest extends OutgoingMessage { + url = null + path = null + host = null + agent = null + method = null + + constructor (options) { + super() + this.url = options?.url ?? null + this.agent = options?.agent ?? null + this.method = options?.method ?? null + + if (this.url) { + const url = new URL(this.url, globalThis.location.href) + this.protocol = url.protocol + this.path = url.pathname + this.host = url.hostname + this.url = url.pathname + url.search + } + } +} + +export class ServerResponse extends OutgoingMessage { + statusCode = 200 + statusMessage = '' -export class ServerResponse extends OutgoingMessage {} + constructor (req) { + super() + this.req = req + } +} export class AgentOptions { keepAlive = false @@ -311,10 +348,30 @@ async function request (optionsOrURL, options, callback) { } } - const request = new ClientRequest() - let stream = null let agent = null + if (options.agent) { + agent = options.agent + } else if (options.agent === false) { + agent = new (options.Agent ?? Agent)() + } else { + agent = globalAgent + } + + let url = `${options.protocol ?? agent?.defaultProtocol ?? 'http:'}//${options.host || options.hostname}` + + if (options.port) { + url += `:${options.port}` + } + + url += options.pathname ?? '/' + + const request = new ClientRequest({ + method: options?.method ?? 'GET', + agent, + url + }) + options = { ...options, makeRequest () { @@ -335,15 +392,7 @@ async function request (optionsOrURL, options, callback) { } } - if (options.agent) { - agent = options.agent - } else if (options.agent === false) { - agent = new (options.Agent ?? Agent)() - } else { - agent = globalAgent - } - - stream = agent.createConnection(options, callback) + const stream = agent.createConnection(options, callback) stream.on('finish', () => request.emit('finish')) stream.on('timeout', () => request.emit('timeout')) diff --git a/api/internal/monkeypatch.js b/api/internal/monkeypatch.js index 9c11d3dff1..4495dade7e 100644 --- a/api/internal/monkeypatch.js +++ b/api/internal/monkeypatch.js @@ -235,6 +235,10 @@ export function init () { applied = true + if (!Error.captureStackTrace) { + Error.captureStackTrace = function () {} + } + if (globalThis.document) { // create tag in document if it doesn't exist globalThis.document.title ||= '' diff --git a/api/module.js b/api/module.js index ddf3e5a313..32b7dff5a8 100644 --- a/api/module.js +++ b/api/module.js @@ -11,6 +11,10 @@ import { Headers } from './ipc.js' import { URL } from './url.js' // builtins +// eslint-disable-next-line +import async_context from './async_context.js' +// eslint-disable-next-line +import async_hooks from './async_hooks.js' import application from './application.js' import assert from './assert.js' import buffer from './buffer.js' @@ -41,6 +45,8 @@ import url from './url.js' import util from './util.js' import vm from './vm.js' import window from './window.js' +// eslint-disable-next-line +import worker_threads from './worker_threads.js' /** * @typedef {function(string, Module, function): undefined} ModuleResolver @@ -57,6 +63,20 @@ class ModuleRequest { return request.load() } + static verifyRequestContentLocation (request) { + const headers = Headers.from(request) + const contentLocation = headers.get('content-location') + // if a content location was given, check extension name + if (URL.canParse(contentLocation, globalThis.location.href)) { + const url = new URL(contentLocation, globalThis.location.href) + if (!/js|json|mjs|cjs|jsx|ts|tsx/.test(path.extname(url.pathname))) { + return false + } + } + + return true + } + constructor (pathname, parent) { const origin = globalThis.location.origin.startsWith('blob:') ? new URL(new URL(globalThis.location.href).pathname).origin @@ -71,6 +91,11 @@ class ModuleRequest { const request = new XMLHttpRequest() request.open('HEAD', id, false) request.send(null) + + if (!ModuleRequest.verifyRequestContentLocation(request)) { + return 404 + } + return request.status } @@ -90,6 +115,10 @@ class ModuleRequest { request.open('GET', id, false) request.send(null) + if (!ModuleRequest.verifyRequestContentLocation(request)) { + return null + } + const headers = Headers.from(request) let responseText = null @@ -199,11 +228,16 @@ function CommonJSModuleScope ( * A limited set of builtins exposed to CommonJS modules. */ export const builtins = { + // eslint-disable-next-line + async_context, + // eslint-disable-next-line + async_hooks, application, assert, buffer, console, constants, + child_process: {}, crypto, dgram, dns, @@ -212,8 +246,8 @@ export const builtins = { extension, fs, 'fs/promises': fs.promises, - gc, http, + gc, https, ipc, language, @@ -231,19 +265,21 @@ export const builtins = { 'stream/web': stream.web, // eslint-disable-next-line string_decoder, + sys: util, test, timers, 'timers/promises': timers.promises, + tty: { + isatty: () => false, + WriteStream: util.IllegalConstructor, + ReadStream: util.IllegalConstructor + }, util, url, vm, window, // eslint-disable-next-line - worker_threads: { - BroadcastChannel: globalThis.BroadcastChannel, - MessageChannel: globalThis.MessageChannel, - MessagePort: globalThis.MessagePort - } + worker_threads } const socketRuntimeModules = [ @@ -749,10 +785,14 @@ export class Module extends EventTarget { return require function require (filename) { + if (filename.startsWith('/') || filename.startsWith('\\')) { + filename = filename.replace(process.cwd(), '') + } + const resolvers = Array.from(Module.resolvers) const result = resolve(filename, resolvers) - if (typeof result === 'string') { + if (typeof result === 'string' && result.length < 256 && !/^[\s|\n]+/.test(result)) { const name = result.replace(/^(socket|node):/, '') if ( @@ -778,7 +818,7 @@ export class Module extends EventTarget { throw new ModuleNotFoundError( `Cannot find module ${filename}`, - this.children + module.children ) } @@ -823,7 +863,3 @@ export class Module extends EventTarget { export default Module builtins.module = Module - -// builtins should never be overloaded through this object, instead -// a custom resolver should be used -Object.freeze(Object.seal(builtins)) diff --git a/api/node/index.js b/api/node/index.js index 85f75e4181..0f6803e3f2 100644 --- a/api/node/index.js +++ b/api/node/index.js @@ -1,7 +1,8 @@ import sugar from '../stream-relay/sugar.js' import { Cache, Packet, sha256, Encryption, NAT } from '../stream-relay/index.js' -import events from 'events' -import dgram from 'dgram' +import worker from 'node:worker_threads' +import events from 'node:events' +import dgram from 'node:dgram' const network = sugar(dgram, events) diff --git a/api/path/well-known.js b/api/path/well-known.js index 0998863801..a1061a0050 100644 --- a/api/path/well-known.js +++ b/api/path/well-known.js @@ -6,50 +6,50 @@ const paths = ipc.sendSync('os.paths') * Well known path to the user's "Downloads" folder. * @type {?string} */ -export const DOWNLOADS = paths.data.downloads || null +export const DOWNLOADS = paths?.data?.downloads || null /** * Well known path to the user's "Documents" folder. * @type {?string} */ -export const DOCUMENTS = paths.data.documents || null +export const DOCUMENTS = paths?.data?.documents || null /** * Well known path to the user's "Pictures" folder. * @type {?string} */ -export const PICTURES = paths.data.pictures || null +export const PICTURES = paths?.data?.pictures || null /** * Well known path to the user's "Desktop" folder. * @type {?string} */ -export const DESKTOP = paths.data.desktop || null +export const DESKTOP = paths?.data?.desktop || null /** * Well known path to the user's "Videos" folder. * @type {?string} */ -export const VIDEOS = paths.data.videos || null +export const VIDEOS = paths?.data?.videos || null /** * Well known path to the user's "Music" folder. * @type {?string} */ -export const MUSIC = paths.data.music || null +export const MUSIC = paths?.data?.music || null /** * Well known path to the application's "resources" folder. * @type {?string} */ -export const RESOURCES = paths.data.resources || null +export const RESOURCES = paths?.data?.resources || null /** * Well known path to the application's "home" folder. * This may be the user's HOME directory or the application container sandbox. * @type {?string} */ -export const HOME = paths.data.home || null +export const HOME = paths?.data?.home || null export default { DOWNLOADS, diff --git a/api/process.js b/api/process.js index 1388252a84..b0fb28fb83 100644 --- a/api/process.js +++ b/api/process.js @@ -12,7 +12,15 @@ import os from './os.js' let didEmitExitEvent = false -export const env = Object.create({}, { +export class ProcessEnvironmentEvent extends Event { + constructor (type, key, value) { + super(type) + this.key = key + this.value = value ?? undefined + } +} + +export const env = Object.defineProperties(new EventTarget(), { proxy: { configurable: false, enumerable: false, @@ -27,10 +35,16 @@ export const env = Object.create({}, { }, set (_, property, value) { + if (Reflect.get(env, property) !== value) { + env.dispatchEvent(new ProcessEnvironmentEvent('set', property, value)) + } return Reflect.set(env, property, value) }, deleteProperty (_, property) { + if (Reflect.has(env, property)) { + env.dispatchEvent(new ProcessEnvironmentEvent('delete', property)) + } return Reflect.deleteProperty(env, property) }, diff --git a/api/stream/web.js b/api/stream/web.js index c932de1cc7..92a186deae 100644 --- a/api/stream/web.js +++ b/api/stream/web.js @@ -1,21 +1,23 @@ import * as exports from './web.js' -export const ReadableStream = globalThis.ReadableStream -export const ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader -export const ReadableStreamBYOBReader = globalThis.ReadableStreamBYOBReader -export const ReadableStreamBYOBRequest = globalThis.ReadableStreamBYOBRequest -export const ReadableByteStreamController = globalThis.ReadableByteStreamController -export const ReadableStreamDefaultController = globalThis.ReadableStreamDefaultController -export const TransformStream = globalThis.TransformStream -export const TransformStreamDefaultController = globalThis.TransformStreamDefaultController -export const WritableStream = globalThis.WritableStream -export const WritableStreamDefaultWriter = globalThis.WritableStreamDefaultWriter -export const WritableStreamDefaultController = globalThis.WritableStreamDefaultController -export const ByteLengthQueuingStrategy = globalThis.ByteLengthQueuingStrategy -export const CountQueuingStrategy = globalThis.CountQueuingStrategy -export const TextEncoderStream = globalThis.TextEncoderStream -export const TextDecoderStream = globalThis.TextDecoderStream -export const CompressionStream = globalThis.CompressionStream -export const DecompressionStream = globalThis.DecompressionStream +class UnsupportedStreamInterface {} + +export const ReadableStream = globalThis.ReadableStream ?? UnsupportedStreamInterface +export const ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader ?? UnsupportedStreamInterface +export const ReadableStreamBYOBReader = globalThis.ReadableStreamBYOBReader ?? UnsupportedStreamInterface +export const ReadableStreamBYOBRequest = globalThis.ReadableStreamBYOBRequest ?? UnsupportedStreamInterface +export const ReadableByteStreamController = globalThis.ReadableByteStreamController ?? UnsupportedStreamInterface +export const ReadableStreamDefaultController = globalThis.ReadableStreamDefaultController ?? UnsupportedStreamInterface +export const TransformStream = globalThis.TransformStream ?? UnsupportedStreamInterface +export const TransformStreamDefaultController = globalThis.TransformStreamDefaultController ?? UnsupportedStreamInterface +export const WritableStream = globalThis.WritableStream ?? UnsupportedStreamInterface +export const WritableStreamDefaultWriter = globalThis.WritableStreamDefaultWriter ?? UnsupportedStreamInterface +export const WritableStreamDefaultController = globalThis.WritableStreamDefaultController ?? UnsupportedStreamInterface +export const ByteLengthQueuingStrategy = globalThis.ByteLengthQueuingStrategy ?? UnsupportedStreamInterface +export const CountQueuingStrategy = globalThis.CountQueuingStrategy ?? UnsupportedStreamInterface +export const TextEncoderStream = globalThis.TextEncoderStream ?? UnsupportedStreamInterface +export const TextDecoderStream = globalThis.TextDecoderStream ?? UnsupportedStreamInterface +export const CompressionStream = globalThis.CompressionStream ?? UnsupportedStreamInterface +export const DecompressionStream = globalThis.DecompressionStream ?? UnsupportedStreamInterface export default exports diff --git a/api/url/index.js b/api/url/index.js index 8c78888769..241f4c574b 100644 --- a/api/url/index.js +++ b/api/url/index.js @@ -1,5 +1,6 @@ import { URLPattern } from './urlpattern/urlpattern.js' import url from './url/url.js' +import qs from '../querystring.js' const { URL, @@ -16,8 +17,20 @@ for (const key in globalThis.URL) { } URL.resolve = resolve +URL.parse = parse +URL.format = format -export const parse = parseURL +export function parse (input) { + if (URL.canParse(input)) { + return new URL(input) + } + + if (URL.canParse(input, 'socket://')) { + return new URL(input, 'socket://') + } + + return null +} // lifted from node export function resolve (from, to) { @@ -31,6 +44,72 @@ export function resolve (from, to) { return resolved.toString() } +export function format (input) { + if (!input || (typeof input !== 'string' && typeof input !== 'object')) { + throw new TypeError( + `The 'input' argument must be one of type object or string. Received: ${input}` + ) + } + + if (typeof input === 'string') { + if (!URL.canParse(input)) { + return '' + } + + return new URL(input).toString() + } + + let formatted = '' + + if (!input.hostname) { + return '' + } + + if (input.protocol) { + formatted += `${input.protocol}//` + } + + if (input.username) { + formatted += encodeURIComponent(input.username) + + if (input.password) { + formatted += `:${encodeURIComponent(input.password)}` + } + + formatted += '@' + } + + formatted += input.hostname + + if (input.port) { + formatted += `:${input.port}` + } + + if (input.pathname) { + formatted += input.pathname + } + + if (input.query && typeof input.query === 'object') { + formatted += `?${qs.stringify(input.query)}` + } else if (input.query && typeof input.query === 'string') { + if (!input.query.startsWith('?')) { + formatted += '?' + } + + formatted += encodeURIComponent(decodeURIComponent(input.query)) + } + + if (input.hash && typeof input.hash === 'string') { + if (!input.hash.startsWith('#')) { + formatted += '#' + } + + formatted += input.hash + } + + return formatted +} + url.serializeURLOrigin = function (input) { const { scheme, protocol, host } = input diff --git a/api/util.js b/api/util.js index 99869b1b06..49cc7b859e 100644 --- a/api/util.js +++ b/api/util.js @@ -320,7 +320,6 @@ export function promisify (original) { } if (typeof original !== 'function') { - console.log({ original }) throw new TypeError('Expecting original to be a function or object.') } @@ -902,10 +901,11 @@ export function parseHeaders (headers) { } return headers - .split('\n') + .split(/\r?\n/) .map((l) => l.trim().split(':')) - .filter((e) => e.length === 2) - .map((e) => [e[0].trim().toLowerCase(), e[1].trim().toLowerCase()]) + .filter((e) => e.length >= 2) + .map((e) => [e[0].trim().toLowerCase(), e.slice(1).join(':').trim().toLowerCase()]) + .filter((e) => e[0].length && e[1].length) } export function noop () {} diff --git a/api/vm.js b/api/vm.js index 4d22ca955d..f82c3be5c3 100644 --- a/api/vm.js +++ b/api/vm.js @@ -19,7 +19,7 @@ * // that exists on the user context * value = json.value * ` - * const result = await vm.runIntContext(source, context) + * const result = await vm.runInContext(source, context) * console.log(context.value) // set from `json.value` in VM context * ``` */ @@ -1094,7 +1094,7 @@ export async function terminateContextWorker () { * Creates a prototype object of known global reserved intrinsics. * @ignore */ -export function createIntrinsics () { +export function createIntrinsics (options) { const descriptors = Object.create(null) const propertyNames = Object.getOwnPropertyNames(globalThis) const propertySymbols = Object.getOwnPropertySymbols(globalThis) @@ -1102,7 +1102,7 @@ export function createIntrinsics () { for (const property of propertyNames) { const intrinsic = Object.getOwnPropertyDescriptor(globalThis, property) const descriptor = Object.assign(Object.create(null), { - configurable: false, + configurable: options?.configurable === true, enumerable: true, value: intrinsic.value ?? globalThis[property] ?? undefined }) @@ -1112,7 +1112,7 @@ export function createIntrinsics () { for (const symbol of propertySymbols) { descriptors[symbol] = { - configurable: false, + configurable: options?.configurable === true, enumberale: false, value: globalThis[symbol] } @@ -1127,9 +1127,9 @@ export function createIntrinsics () { * @param {object} context * @return {Proxy} */ -export function createGlobalObject (context) { +export function createGlobalObject (context, options) { const prototype = Object.getPrototypeOf(globalThis) - const intrinsics = createIntrinsics() + const intrinsics = createIntrinsics(options) const descriptors = Object.getOwnPropertyDescriptors(intrinsics) const globalObject = Object.create(prototype, descriptors) @@ -1143,7 +1143,8 @@ export function createGlobalObject (context) { } catch {} } - return new Proxy(target, { + const handler = {} + const traps = { get (_, property, receiver) { if (property === 'console') { return console @@ -1194,16 +1195,21 @@ export function createGlobalObject (context) { defineProperty (_, property, descriptor) { if (RESERVED_GLOBAL_INTRINSICS.includes(property)) { - return false + return true } + console.log('defineProperty', { property }) if (context) { - Reflect.defineProperty(context, property, descriptor) - return true + return ( + Reflect.defineProperty(context, property, descriptor) && + Reflect.getOwnPropertyDescriptor(context, property) !== undefined + ) } - Reflect.defineProperty(globalObject, property, descriptor) - return true + return ( + Reflect.defineProperty(globalObject, property, descriptor) && + Reflect.getOwnPropertyDescriptor(globalObject, property) !== undefined + ) }, deleteProperty (_, property) { @@ -1219,6 +1225,7 @@ export function createGlobalObject (context) { }, getOwnPropertyDescriptor (_, property) { + console.log('getOwnPropertyDescriptor', { property }) if (context) { const descriptor = Reflect.getOwnPropertyDescriptor(context, property) if (descriptor) { @@ -1271,6 +1278,7 @@ export function createGlobalObject (context) { }, preventExtensions (_) { + console.log('preventExtensions') if (context) { Reflect.preventExtensions(context) return true @@ -1278,7 +1286,27 @@ export function createGlobalObject (context) { return false } - }) + } + + if (Array.isArray(options?.traps)) { + for (const trap of options.traps) { + if (typeof traps[trap] === 'function') { + handler[trap] = traps[trap] + } + } + } else if (options?.traps && typeof options?.traps === 'object') { + for (const key in traps) { + if (options.traps[key] !== false) { + handler[key] = traps[key] + } + } + } else { + for (const key in traps) { + handler[key] = traps[key] + } + } + + return new Proxy(target, handler) } /** @@ -1456,10 +1484,11 @@ export function getTrasferables (object) { } export function createContext (object) { - return object + return Object.assign(new EventTarget(), object) } export default { + createGlobalObject, compileFunction, createReference, getContextWindow, diff --git a/api/worker_threads.js b/api/worker_threads.js new file mode 100644 index 0000000000..a9debf507b --- /dev/null +++ b/api/worker_threads.js @@ -0,0 +1,333 @@ +import { Writable, Readable } from './stream.js' +import init, { SHARE_ENV } from './worker_threads/init.js' +import { getTrasferables } from './vm.js' +import { maybeMakeError } from './ipc.js' +import { EventEmitter } from './events.js' +import { env } from './process.js' + +/** + * A pool of known worker threads. + * @type {<Map<string, Worker>} + */ +export const workers = new Map() + +/** + * `true` if this is the "main" thread, otherwise `false` + * The "main" thread is the top level webview window. + * @type {boolean} + */ +export const isMainThread = init.state.isMainThread + +/** + * The main thread `MessagePort` which is `null` when the + * current context is not the "main thread". + * @type {MessagePort?} + */ +export const mainPort = init.state.mainPort + +/** + * A worker thread `BroadcastChannel` class. + */ +export class BroadcastChannel extends globalThis.BroadcastChannel {} + +/** + * A worker thread `MessageChannel` class. + */ +export class MessageChannel extends globalThis.MessageChannel {} + +/** + * A worker thread `MessagePort` class. + */ +export class MessagePort extends globalThis.MessagePort {} + +// inherit `EventEmitter` +Object.assign(BroadcastChannel.prototype, EventEmitter.prototype) +Object.assign(MessageChannel.prototype, EventEmitter.prototype) +Object.assign(MessagePort.prototype, EventEmitter.prototype) + +/** + * The current unique thread ID. + * @type {number} + */ +export const threadId = isMainThread + ? 0 + : globalThis.RUNTIME_WORKER_ID + ? (parseInt(globalThis.RUNTIME_WORKER_ID ?? '0') || 0) + : (parseInt(globalThis.__args.client?.id) || 0) + +/** + * The parent `MessagePort` instance + * @type {MessagePort?} + */ +export const parentPort = init.state.parentPort + +/** + * Transferred "worker data" when creating a new `Worker` instance. + * @type {any?} + */ +export const workerData = init.state.workerData + +/** + * Set shared worker environment data. + * @param {string} key + * @param {any} value + */ +export function setEnvironmentData (key, value) { + // update this thread state + init.state.env[key] = value + + for (const worker of workers.values()) { + const transfer = getTrasferables(value) + worker.postMessage({ + worker_threads: { + env: { key, value } + } + }, { transfer }) + } +} + +/** + * Get shared worker environment data. + * @param {string} key + * @return {any} + */ +export function getEnvironmentData (key) { + return init.state.env[key] ?? null +} + +/** + * @typedef {{ + * env?: object, + * stdin?: boolean = false, + * stdout?: boolean = false, + * stderr?: boolean = false, + * workerData?: any, + * transferList?: any[], + * eval?: boolean = false + * }} WorkerOptions + +/** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ +export class Worker extends EventEmitter { + #worker = null + #stdin = null + #stdout = null + #stderr = null + + /** + * `Worker` class constructor. + * @param {string} filename + * @param {WorkerOptions=} [options] + */ + constructor (filename, options = null) { + super() + + options = { ...options } + + const url = new URL('./worker_threads/init.js', import.meta.url) + this.onWorkerMessage = this.onWorkerMessage.bind(this) + this.onProcessEnvironmentEvent = this.onProcessEnvironmentEvent.bind(this) + + if (options.env === SHARE_ENV) { + options.env = SHARE_ENV.toString() + env.addEventListener('set', this.onProcessEnvironmentEvent) + env.addEventListener('delete', this.onProcessEnvironmentEvent) + } + + if (options.stdin === true) { + this.#stdin = new Writable({ + write (data, cb) { + const transfer = getTrasferables(data) + this.#worker.postMessage( + { worker_threads: { stdin: { data } } }, + { transfer } + ) + + cb(null) + } + }) + } + + if (options.stdout === true) { + this.#stdout = new Readable() + } + + if (options.stderr === true) { + this.#stderr = new Readable() + } + + this.#worker = new globalThis.Worker(url.toString()) + this.#worker.addEventListener('message', this.onWorkerMessage) + + if (options.workerData) { + const transfer = options.transferList ?? getTrasferables(options.workerData) + const message = { + worker_threads: { workerData: options.workerData } + } + + this.#worker.postMessage(message, { transfer }) + } + + this.#worker.postMessage({ + worker_threads: { + init: { + id: this.id, + url: new URL(filename, globalThis.location.href).toString(), + eval: options?.eval === true, + process: { + env: options.env ?? {}, + stdin: options.stdin === true, + stdout: options.stdout === true, + stderr: options.stdout === true + } + } + } + }) + } + + /** + * The unique ID for this `Worker` thread instace. + * @type {number} + */ + get id () { + return this.#worker.id + } + + /** + * A `Writable` standard input stream if `{ stdin: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Writable?} + */ + get stdin () { + return this.#stdin + } + + /** + * A `Readable` standard output stream if `{ stdout: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stdout () { + return this.#stdout + } + + /** + * A `Readable` standard error stream if `{ stderr: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stderr () { + return this.#stderr + } + + /** + * Terminates the `Worker` instance + */ + terminate () { + this.#worker.terminate() + workers.delete(this.id) + this.#worker.removeEventListener('message', this.onMainThreadMessage) + + env.removeEventListener('set', this.onProcessEnvironmentEvent) + env.removeEventListener('delete', this.onProcessEnvironmentEvent) + + if (this.#stdin) { + this.#stdin.destroy() + this.#stdin = null + } + + if (this.#stdout) { + this.#stdout.destroy() + this.#stdout = null + } + + if (this.#stderr) { + this.#stderr.destroy() + this.#stderr = null + } + } + + /** + * Handles incoming worker messages. + * @ignore + * @param {MessageEvent} event + */ + onWorkerMessage (event) { + const request = event.data?.worker_threads ?? {} + + if (request.online?.id) { + workers.set(this.id, this) + this.emit('online') + } + + if (request.error) { + this.emit('error', maybeMakeError(request.error)) + } + + if (request.process?.stdout?.data && this.#stdout) { + this.#stdout.push(request.process.stdout.data) + } + + if (request.process?.stderr?.data && this.#stderr) { + this.#stderr.push(request.process.stderr.data) + } + + if (/set|delete/.test(request.process?.env?.type ?? '')) { + if (request.process.env.type === 'set') { + Reflect.set(env, request.process.env.key, request.process.env.value) + } else if (request.process.env.type === 'delete') { + Reflect.deleteProperty(env, request.process.env.key) + } + } + + if (request.process?.exit) { + this.#worker.terminate() + this.emit('exit', request.process.exit.code ?? 0) + } + + if (event.data?.worker_threads) { + event.stopImmediatePropagation() + return false + } + + this.emit('message', event.data) + + if (mainPort) { + mainPort.dispatchEvent(new MessageEvent('message', event)) + } + } + + /** + * Handles process environment change events + * @ignore + * @param {import('./process.js').ProcessEnvironmentEvent} event + */ + onProcessEnvironmentEvent (event) { + this.#worker.postMessage({ + worker_threads: { + process: { + env: { + type: event.type, + key: event.key, + value: event.value + } + } + } + }) + } +} + +export { SHARE_ENV, init } + +export default { + Worker, + isMainThread, + parentPort, + setEnvironmentData, + getEnvironmentData, + workerData, + threadId, + SHARE_ENV +} diff --git a/api/worker_threads/init.js b/api/worker_threads/init.js new file mode 100644 index 0000000000..4afe598aa4 --- /dev/null +++ b/api/worker_threads/init.js @@ -0,0 +1,315 @@ +import vm, { getTrasferables } from '../vm.js' +import { Writable, Readable } from '../stream.js' +import process, { env } from '../process.js' + +export const SHARE_ENV = Symbol.for('socket.worker_threads.SHARE_ENV') +export const isMainThread = Boolean( + globalThis.window && + globalThis.top === globalThis.window && + globalThis.window === globalThis +) + +export const state = { + isMainThread, + + parentPort: isMainThread + ? null + : Object.create(MessagePort.prototype), + + mainPort: isMainThread + ? Object.create(MessagePort.prototype) + : null, + + workerData: null, + url: null, + env: {}, + id: 0 +} + +process.exit = (code) => { + globalThis.postMessage({ + worker_threads: { process: { exit: { code } } } + }) +} + +globalThis.addEventListener('message', onMainThreadMessage) +globalThis.addEventListener('error', (event) => { + propagateWorkerError( + event.error ?? + new Error( + event.reason?.message ?? + event.reason ?? + 'An unknown error occurred' + ) + ) +}) + +globalThis.addEventListener('unhandledrejection', (event) => { + propagateWorkerError( + event.error ?? + new Error( + event.reason?.message ?? + event.reason ?? + 'An unknown error occurred' + ) + ) +}) + +function propagateWorkerError (err) { + globalThis.postMessage({ + worker_threads: { + error: { + name: err.name, + type: err.constructor.name, + code: err.code ?? undefined, + stack: err.stack, + message: err.message + } + } + }) +} + +function onMainThreadMessage (event) { + const request = event.data?.worker_threads ?? {} + + if (request.workerData) { + state.workerData = request.workerData + } + + if (request.init && request.init.id && request.init.url) { + state.id = request.init.id + state.url = request.init.url + + if ( + request.init.process?.env && + typeof request.init.process.env === 'object' + ) { + for (const key in request.init.process.env) { + process.env[key] = request.init.process.env[key] + } + } else if (request.init.process?.env === SHARE_ENV.toString()) { + env.addEventListener('set', (event) => { + globalThis.postMessage({ + worker_threads: { + process: { + env: { + type: event.type, + key: event.key, + value: event.value + } + } + } + }) + }) + + env.addEventListener('delete', (event) => { + globalThis.postMessage({ + worker_threads: { + process: { + env: { + type: event.type, + key: event.key + } + } + } + }) + }) + } + + if (request.init.process?.stdin === true) { + process.stdin = new Readable() + } + + if (request.init.process?.stdout === true) { + process.stdout = new Writable({ + write (data, cb) { + const transfer = getTrasferables(data) + globalThis.postMessage({ + worker_threads: { + process: { + stdout: { data } + } + } + }, { transfer }) + + cb(null) + } + }) + } + + if (request.init.process?.stderr === true) { + process.stderr = new Writable({ + write (data, cb) { + const transfer = getTrasferables(data) + globalThis.postMessage({ + worker_threads: { + process: { + stderr: { data } + } + } + }, { transfer }) + + cb(null) + } + }) + } + + globalThis.postMessage({ + worker_threads: { online: { id: state.id } } + }) + + if (request.init.eval === true) { + state.url = '' + vm.runInThisContext(request.init.url).catch(propagateWorkerError) + } else { + import(state.url).catch(propagateWorkerError) + } + } + + if (request.env && typeof request.env === 'object') { + for (const key in request.env) { + state.env[key] = request.env + } + } + + if (/set|delete/.test(request.process?.env?.type ?? '')) { + if (request.process.env.type === 'set') { + Reflect.set(env, request.process.env.key, request.process.env.value) + } else if (request.process.env.type === 'delete') { + Reflect.deleteProperty(env, request.process.env.key) + } + } + + if (request.process?.stdin?.data && process.stdin) { + process.stdin.push(request.process.stdin.data) + } + + if (request) { + event.stopImmediatePropagation() + return false + } +} + +if (state.parentPort) { + let onmessageerror = null + let onmessage = null + Object.defineProperties(state.parentPort, { + postMessage: { + configurable: false, + enumerable: false, + value: globalThis.top + ? globalThis.top.postMessage.bind(globalThis.top) + : globalThis.postMessage.bind(globalThis) + }, + + close: { + configurable: false, + enumerable: false, + value () {} + }, + + start: { + configurable: false, + enumerable: false, + value () {} + }, + + onmessage: { + enumerable: true, + get: () => onmessage, + set: (value) => { + if (typeof onmessage === 'function') { + globalThis.removeEventListener('message', onmessage) + } + + onmessage = null + + if (typeof value === 'function') { + onmessage = value + globalThis.addEventListener('message', onmessage) + } + } + }, + + onmessageerror: { + enumerable: true, + get: () => onmessageerror, + set: (value) => { + if (typeof onmessageerror === 'function') { + globalThis.removeEventListener('messageerror', onmessageerror) + } + + onmessageerror = null + + if (typeof value === 'function') { + onmessageerror = value + globalThis.addEventListener('messageerror', onmessageerror) + } + } + } + }) +} else if (state.mainPort) { + let onmessageerror = null + let onmessage = null + Object.defineProperties(state.mainPort, { + addEventListener: { + configurable: false, + enumerable: false, + value (...args) { + return globalThis.addEventListener(...args) + } + }, + + removeEventListener: { + configurable: false, + enumerable: false, + value (...args) { + return globalThis.removeEventListener(...args) + } + }, + + dispatchEvent: { + configurable: false, + enumerable: false, + value (...args) { + return globalThis.dispatchEvent(...args) + } + }, + + onmessage: { + enumerable: true, + get: () => onmessage, + set: (value) => { + if (typeof onmessage === 'function') { + globalThis.removeEventListener('message', onmessage) + } + + onmessage = null + + if (typeof value === 'function') { + onmessage = value + globalThis.addEventListener('message', onmessage) + } + } + }, + + onmessageerror: { + enumerable: true, + get: () => onmessageerror, + set: (value) => { + if (typeof onmessageerror === 'function') { + globalThis.removeEventListener('messageerror', onmessageerror) + } + + onmessageerror = null + + if (typeof value === 'function') { + onmessageerror = value + globalThis.addEventListener('messageerror', onmessageerror) + } + } + } + }) +} + +export default { state } diff --git a/src/android/webview.kt b/src/android/webview.kt index d35afbe8cc..41f662c853 100644 --- a/src/android/webview.kt +++ b/src/android/webview.kt @@ -377,7 +377,7 @@ open class WebViewClient (activity: WebViewActivity) : android.webkit.WebViewCli response.responseHeaders = mapOf( "Location" to redirectURL, - "Content-Location" to redirectURL + "Content-Location" to resolved.path ) response.setStatusCodeAndReasonPhrase(200, "OK") diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 7041065a5c..270a77712f 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2757,9 +2757,10 @@ static void registerSchemeHandler (Router *router) { auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); auto response = webkit_uri_scheme_response_new(stream, (gint64) size); + auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); soup_message_headers_append(headers, "location", redirectURL.c_str()); - soup_message_headers_append(headers, "content-location", redirectURL.c_str()); + soup_message_headers_append(headers, "content-location", contentLocation.c_str()); webkit_uri_scheme_response_set_http_headers(response, headers); webkit_uri_scheme_response_set_content_type(response, "text/html"); @@ -2789,9 +2790,10 @@ static void registerSchemeHandler (Router *router) { auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); auto response = webkit_uri_scheme_response_new(stream, (gint64) size); + auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); soup_message_headers_append(headers, "location", redirectURL.c_str()); - soup_message_headers_append(headers, "content-location", redirectURL.c_str()); + soup_message_headers_append(headers, "content-location", contentLocation.c_str()); webkit_uri_scheme_response_set_http_headers(response, headers); webkit_uri_scheme_response_set_content_type(response, "text/html"); @@ -3317,7 +3319,7 @@ static void registerSchemeHandler (Router *router) { } components.scheme = @("socket"); - headers[@"content-location"] = components.URL.absoluteString; + headers[@"content-location"] = components.URL.path; const auto socketModulePrefix = "socket://" + userConfig["meta_bundle_identifier"] + "/socket/"; const auto absoluteURL = String(components.URL.absoluteString.UTF8String); diff --git a/src/window/win.cc b/src/window/win.cc index b941d98e64..735af03a45 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1125,13 +1125,14 @@ namespace SSC { } ICoreWebView2WebResourceResponse* res = nullptr; + auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); env->CreateWebResourceResponse( nullptr, 301, L"Moved Permanently", WString( convertStringToWString("Location: ") + convertStringToWString(redirectURL) + L"\n" + - convertStringToWString("Content-Location: ") + convertStringToWString(redirectURL) + L"\n" + convertStringToWString("Content-Location: ") + convertStringToWString(contentLocation) + L"\n" ).c_str(), &res ); @@ -1152,6 +1153,7 @@ namespace SSC { redirectURL += "#" + parsedPath.fragment; } + auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); ICoreWebView2WebResourceResponse* res = nullptr; env->CreateWebResourceResponse( nullptr, @@ -1159,7 +1161,7 @@ namespace SSC { L"Moved Permanently", WString( convertStringToWString("Location: ") + convertStringToWString(redirectURL) + L"\n" + - convertStringToWString("Content-Location: ") + convertStringToWString(redirectURL) + L"\n" + convertStringToWString("Content-Location: ") + convertStringToWString(contentLocation) + L"\n" ).c_str(), &res ); From d62af26bb3bfd7eae41d44e9114be729a1670a42 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Feb 2024 21:33:57 +0100 Subject: [PATCH 0032/1178] refactor(): clean up --- api/README.md | 157 +- api/application.js | 2 +- api/constants.js | 8 +- api/index.d.ts | 1893 +++++++++++++++-- api/internal/init.js | 2 +- .../{monkeypatch.js => primitives.js} | 0 src/window/apple.mm | 12 +- 7 files changed, 1828 insertions(+), 246 deletions(-) rename api/internal/{monkeypatch.js => primitives.js} (100%) diff --git a/api/README.md b/api/README.md index 4e4c78a87a..1ee604383f 100644 --- a/api/README.md +++ b/api/README.md @@ -63,7 +63,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | Return Value | Type | Description | | :--- | :--- | :--- | -| Not specified | Promise<Object.<number, ApplicationWindow>> | | +| Not specified | Promise<Object.<number?, ApplicationWindow>> | | ## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L190) @@ -781,7 +781,7 @@ External docs: https://nodejs.org/api/events.html import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L85) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L88) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -793,7 +793,36 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L118) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L111) + +External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback +Synchronously check access a file for a given mode calling `callback` + upon success or error. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| mode | string? | F_OK(0) | true | | + +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L127) + +Checks if a path exists + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| callback | function(Boolean)? | | true | | + +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L143) + +Checks if a path exists + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| callback | function(Boolean)? | | true | | + +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L162) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -807,7 +836,18 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L143) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L187) + +External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback +Synchronously changes the permissions of a file. + + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| mode | number | | false | | + +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L210) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -818,7 +858,17 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L171) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L238) + +Changes ownership of file or directory at `path` with `uid` and `gid`. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string | | false | | +| uid | number | | false | | +| gid | number | | false | | + +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L264) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -828,7 +878,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L195) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L288) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -840,7 +890,18 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L223) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L317) + +External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback +Synchronously copies `src` to `dest` calling `callback` upon success or error. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| src | string | | false | The source file path. | +| dest | string | | false | The destination file path. | +| flags | number | | false | Modifiers for copy operation. | + +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L343) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options @@ -854,7 +915,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewri | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L266) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L386) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options @@ -868,7 +929,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewri | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L312) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L432) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -882,7 +943,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L339) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L459) Request that all data for the open file descriptor is flushed to the storage device. @@ -892,7 +953,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L361) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L481) Truncates the file up to `offset` bytes. @@ -902,7 +963,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L389) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L509) Chages ownership of link at `path` with `uid` and `gid. @@ -913,7 +974,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L417) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L537) Creates a link to `dest` from `src`. @@ -923,7 +984,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L473) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L619) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -936,7 +997,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L526) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L672) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -949,7 +1010,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L552) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L698) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -963,7 +1024,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L586) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L732) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -976,7 +1037,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L637) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L783) @@ -989,7 +1050,19 @@ Asynchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L680) +## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L828) + + + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL \| number | | false | | +| options | object? \| function(Error?, Buffer?) | | true | | +| options.encoding ? utf8 | string? | | true | | +| options.flag ? r | string? | | true | | +| options.signal | AbortSignal? | | true | | + +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L886) Reads link at `path` @@ -998,7 +1071,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L699) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L905) Computes real path for `path` @@ -1007,7 +1080,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L719) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L925) Renames file or directory at `src` to `dest`. @@ -1017,7 +1090,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L742) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L948) Removes directory at `path`. @@ -1026,7 +1099,18 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L765) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L969) + +Synchronously get the stats of a file + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL \| number | | false | filename or file descriptor | +| options | object? | | false | | +| options.encoding ? utf8 | string? | | true | | +| options.flag ? r | string? | | true | | + +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L988) Get the stats of a file @@ -1039,7 +1123,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L803) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1026) Get the stats of a symbolic link @@ -1052,7 +1136,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L837) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1060) Creates a symlink of `src` at `dest`. @@ -1061,7 +1145,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L878) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1101) Unlinks (removes) file at `path`. @@ -1070,7 +1154,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L903) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1126) @@ -1085,7 +1169,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L948) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1171) Watch for changes at `path` calling `callback` @@ -1862,12 +1946,17 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L15) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L15) + +This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. + + +## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L23) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L123) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L137) Adds callback to the 'nextTick' queue. @@ -1875,7 +1964,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L152) +## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L166) @@ -1883,7 +1972,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :--- | | Not specified | string | The home directory of the current user. | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L161) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L175) Computed high resolution time as a `BigInt`. @@ -1895,7 +1984,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L187) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L201) @@ -1903,7 +1992,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L199) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L213) Returns an object describing the memory usage of the Node.js process measured in bytes. diff --git a/api/application.js b/api/application.js index 45addec757..dc70f6cd9f 100644 --- a/api/application.js +++ b/api/application.js @@ -135,8 +135,8 @@ function throwOnInvalidIndex (index) { /** * Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. * @param {number[]} [indices] - the indices of the windows - * @return {Promise<Object.<number, ApplicationWindow>>} * @throws {Error} - if indices is not an array of integer numbers + * @return {Promise<Object.<number?, ApplicationWindow>>} */ export async function getWindows (indices) { if (os.platform() === 'ios' || os.platform() === 'android') { diff --git a/api/constants.js b/api/constants.js index a7505a8afc..2f67095823 100644 --- a/api/constants.js +++ b/api/constants.js @@ -1,4 +1,8 @@ -import * as exports from './constants.js' +import fs from './fs/constants.js' +import window from './window/constants.js' export * from './fs/constants.js' export * from './window/constants.js' -export default exports +export default { + ...fs, + ...window +} diff --git a/api/index.d.ts b/api/index.d.ts index b25b37a6e1..dd930a2775 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -488,9 +488,28 @@ declare module "socket:url/url/url" { const _default: any; export default _default; } +declare module "socket:querystring" { + export function unescapeBuffer(s: any, decodeSpaces: any): any; + export function unescape(s: any, decodeSpaces: any): any; + export function escape(str: any): any; + export function stringify(obj: any, sep: any, eq: any, options: any): string; + export function parse(qs: any, sep: any, eq: any, options: any): {}; + export function decode(qs: any, sep: any, eq: any, options: any): {}; + export function encode(obj: any, sep: any, eq: any, options: any): string; + namespace _default { + export { decode }; + export { encode }; + export { parse }; + export { stringify }; + export { escape }; + export { unescape }; + } + export default _default; +} declare module "socket:url/index" { + export function parse(input: any): any; export function resolve(from: any, to: any): any; - export const parse: any; + export function format(input: any): any; export default URL; export const URL: any; import { URLPattern } from "socket:url/urlpattern/urlpattern"; @@ -503,21 +522,205 @@ declare module "socket:url" { export default URL; import URL from "socket:url/index"; } +declare module "socket:mime/index" { + /** + * Look up a MIME type in various MIME databases. + * @param {string} query + * @return {Promise<DatabaseQueryResult[]>} + */ + export function lookup(query: string): Promise<DatabaseQueryResult[]>; + /** + * A container for a database lookup query. + */ + export class DatabaseQueryResult { + /** + * `DatabaseQueryResult` class constructor. + * @ignore + * @param {Database} database + * @param {string} name + * @param {string} mime + */ + constructor(database: Database, name: string, mime: string); + /** + * @type {string} + */ + name: string; + /** + * @type {string} + */ + mime: string; + database: Database; + } + /** + * A container for MIME types by class (audio, video, text, etc) + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} + */ + export class Database { + /** + * `Database` class constructor. + * @param {string} name + */ + constructor(name: string); + /** + * The name of the MIME database. + * @type {string} + */ + name: string; + /** + * The URL of the MIME database. + * @type {URL} + */ + url: URL; + /** + * The mapping of MIME name to the MIME "content type" + * @type {Map} + */ + map: Map<any, any>; + /** + * An index of MIME "content type" to the MIME name. + * @type {Map} + */ + index: Map<any, any>; + /** + * An enumeration of all database entries. + * @return {Array<Array<string>>} + */ + entries(): Array<Array<string>>; + /** + * Loads database MIME entries into internal map. + * @return {Promise} + */ + load(): Promise<any>; + /** + * Lookup MIME type by name or content type + * @param {string} query + * @return {Promise<DatabaseQueryResult>} + */ + lookup(query: string): Promise<DatabaseQueryResult>; + } + /** + * A database of MIME types for 'application/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} + */ + export const application: Database; + /** + * A database of MIME types for 'audio/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} + */ + export const audio: Database; + /** + * A database of MIME types for 'font/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} + */ + export const font: Database; + /** + * A database of MIME types for 'image/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} + */ + export const image: Database; + /** + * A database of MIME types for 'model/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} + */ + export const model: Database; + /** + * A database of MIME types for 'multipart/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} + */ + export const multipart: Database; + /** + * A database of MIME types for 'text/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} + */ + export const text: Database; + /** + * A database of MIME types for 'video/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} + */ + export const video: Database; + /** + * An array of known MIME databases. Custom databases can be added to this + * array in userspace for lookup with `mime.lookup()` + * @type {Database[]} + */ + export const databases: Database[]; + export class MIMEParams extends Map<any, any> { + constructor(); + constructor(entries?: readonly (readonly [any, any])[]); + constructor(); + constructor(iterable?: Iterable<readonly [any, any]>); + } + export class MIMEType { + constructor(input: any); + set type(value: any); + get type(): any; + set subtype(value: any); + get subtype(): any; + get essence(): string; + toString(): string; + toJSON(): string; + #private; + } + namespace _default { + export { Database }; + export { databases }; + export { lookup }; + export { MIMEParams }; + export { MIMEType }; + export { application }; + export { audio }; + export { font }; + export { image }; + export { model }; + export { multipart }; + export { text }; + export { video }; + } + export default _default; +} +declare module "socket:mime" { + export * from "socket:mime/index"; + export default exports; + import * as exports from "socket:mime/index"; +} declare module "socket:util" { + export function debug(section: any): { + (...args: any[]): void; + enabled: boolean; + }; export function hasOwnProperty(object: any, property: any): any; + export function isDate(object: any): boolean; export function isTypedArray(object: any): boolean; export function isArrayLike(object: any): boolean; + export function isError(object: any): boolean; + export function isSymbol(value: any): boolean; + export function isNumber(value: any): boolean; + export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; export function isAsyncFunction(object: any): boolean; export function isArgumentsObject(object: any): boolean; export function isEmptyObject(object: any): boolean; export function isObject(object: any): boolean; + export function isUndefined(value: any): boolean; + export function isNull(value: any): boolean; + export function isNullOrUndefined(value: any): boolean; + export function isPrimitive(value: any): boolean; + export function isRegExp(value: any): boolean; export function isPlainObject(object: any): boolean; export function isArrayBuffer(object: any): boolean; export function isBufferLike(object: any): boolean; export function isFunction(value: any): boolean; export function isErrorLike(error: any): boolean; export function isClass(value: any): boolean; + export function isBuffer(value: any): boolean; export function isPromiseLike(object: any): boolean; export function toString(object: any): string; export function toBuffer(object: any, encoding?: any): any; @@ -537,9 +740,23 @@ declare module "socket:util" { export function noop(): void; export function isValidPercentageValue(input: any): boolean; export function compareBuffers(a: any, b: any): any; + export function inherits(Constructor: any, Super: any): void; + export function deprecate(...args: any[]): void; + export const TextDecoder: { + new (label?: string, options?: TextDecoderOptions): TextDecoder; + prototype: TextDecoder; + }; + export const TextEncoder: { + new (): TextEncoder; + prototype: TextEncoder; + }; + export const isArray: any; export class IllegalConstructor { } + export const MIMEType: typeof mime.MIMEType; + export const MIMEParams: typeof mime.MIMEParams; export default exports; + import mime from "socket:mime"; import * as exports from "socket:util"; } @@ -558,7 +775,8 @@ declare module "socket:window/constants" { export const WINDOW_EXITED: 51; export const WINDOW_KILLING: 60; export const WINDOW_KILLED: 61; - export * as _default from "socket:window/constants"; + export default exports; + import * as exports from "socket:window/constants"; } declare module "socket:location" { @@ -654,7 +872,7 @@ declare module "socket:events" { prototype: CustomEvent<any>; } | { new (type: any, options: any): { - "__#3@#detail": any; + "__#4@#detail": any; readonly detail: any; }; }; @@ -663,8 +881,8 @@ declare module "socket:events" { prototype: MessageEvent<any>; } | { new (type: any, options: any): { - "__#4@#detail": any; - "__#4@#data": any; + "__#5@#detail": any; + "__#5@#data": any; readonly detail: any; readonly data: any; }; @@ -674,8 +892,8 @@ declare module "socket:events" { prototype: ErrorEvent; } | { new (type: any, options: any): { - "__#5@#detail": any; - "__#5@#error": any; + "__#6@#detail": any; + "__#6@#error": any; readonly detail: any; readonly error: any; }; @@ -849,6 +1067,12 @@ declare module "socket:process" { export namespace memoryUsage { function rss(): any; } + export class ProcessEnvironmentEvent extends Event { + constructor(type: any, key: any, value: any); + key: any; + value: any; + } + export const env: any; export default process; const process: any; } @@ -1648,6 +1872,13 @@ declare module "socket:diagnostics" { export default exports; import * as exports from "socket:diagnostics/index"; } +declare module "socket:internal/symbols" { + export const dispose: any; + namespace _default { + export { dispose }; + } + export default _default; +} declare module "socket:gc" { /** * Track `object` ref to call `Symbol.for('gc.finalize')` method when @@ -1720,12 +1951,74 @@ declare module "socket:gc" { handle: any; } } +declare module "socket:stream/web" { + export const ReadableStream: { + new (underlyingSource: UnderlyingByteSource, strategy?: { + highWaterMark?: number; + }): ReadableStream<Uint8Array>; + new <R = any>(underlyingSource: UnderlyingDefaultSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>; + new <R_1 = any>(underlyingSource?: UnderlyingSource<R_1>, strategy?: QueuingStrategy<R_1>): ReadableStream<R_1>; + prototype: ReadableStream<any>; + } | typeof UnsupportedStreamInterface; + export const ReadableStreamDefaultReader: { + new <R = any>(stream: ReadableStream<R>): ReadableStreamDefaultReader<R>; + prototype: ReadableStreamDefaultReader<any>; + } | typeof UnsupportedStreamInterface; + export const ReadableStreamBYOBReader: { + new (stream: ReadableStream<any>): ReadableStreamBYOBReader; + prototype: ReadableStreamBYOBReader; + } | typeof UnsupportedStreamInterface; + export const ReadableStreamBYOBRequest: typeof UnsupportedStreamInterface; + export const ReadableByteStreamController: typeof UnsupportedStreamInterface; + export const ReadableStreamDefaultController: typeof UnsupportedStreamInterface; + export const TransformStream: { + new <I = any, O = any>(transformer?: Transformer<I, O>, writableStrategy?: QueuingStrategy<I>, readableStrategy?: QueuingStrategy<O>): TransformStream<I, O>; + prototype: TransformStream<any, any>; + } | typeof UnsupportedStreamInterface; + export const TransformStreamDefaultController: typeof UnsupportedStreamInterface; + export const WritableStream: { + new <W = any>(underlyingSink?: UnderlyingSink<W>, strategy?: QueuingStrategy<W>): WritableStream<W>; + prototype: WritableStream<any>; + } | typeof UnsupportedStreamInterface; + export const WritableStreamDefaultWriter: { + new <W = any>(stream: WritableStream<W>): WritableStreamDefaultWriter<W>; + prototype: WritableStreamDefaultWriter<any>; + } | typeof UnsupportedStreamInterface; + export const WritableStreamDefaultController: typeof UnsupportedStreamInterface; + export const ByteLengthQueuingStrategy: { + new (init: QueuingStrategyInit): ByteLengthQueuingStrategy; + prototype: ByteLengthQueuingStrategy; + } | typeof UnsupportedStreamInterface; + export const CountQueuingStrategy: { + new (init: QueuingStrategyInit): CountQueuingStrategy; + prototype: CountQueuingStrategy; + } | typeof UnsupportedStreamInterface; + export const TextEncoderStream: typeof UnsupportedStreamInterface; + export const TextDecoderStream: { + new (label?: string, options?: TextDecoderOptions): TextDecoderStream; + prototype: TextDecoderStream; + } | typeof UnsupportedStreamInterface; + export const CompressionStream: { + new (format: CompressionFormat): CompressionStream; + prototype: CompressionStream; + } | typeof UnsupportedStreamInterface; + export const DecompressionStream: { + new (format: CompressionFormat): DecompressionStream; + prototype: DecompressionStream; + } | typeof UnsupportedStreamInterface; + export default exports; + class UnsupportedStreamInterface { + } + import * as exports from "socket:stream/web"; + +} declare module "socket:stream" { export function pipelinePromise(...streams: any[]): Promise<any>; export function pipeline(stream: any, ...streams: any[]): any; export function isStream(stream: any): boolean; export function isStreamx(stream: any): boolean; export function isReadStreamx(stream: any): any; + export { web }; export default exports; export class FixedFIFO { constructor(hwm: any); @@ -1873,6 +2166,7 @@ declare module "socket:stream" { } export class PassThrough extends exports.Transform { } + import web from "socket:stream/web"; import * as exports from "socket:stream"; import { EventEmitter } from "socket:events"; @@ -2276,6 +2570,12 @@ declare module "socket:fs/handle" { * @return {Promise<Stats>} */ stat(options?: object | undefined): Promise<Stats>; + /** + * Returns the stats of the underlying symbolic link. + * @param {object=} [options] + * @return {Promise<Stats>} + */ + lstat(options?: object | undefined): Promise<Stats>; /** * Synchronize a file's in-core state with storage device * @return {Promise} @@ -3029,6 +3329,7 @@ declare module "socket:fs/promises" { */ export function rmdir(path: string): Promise<any>; /** + * Get the stats of a file * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options} * @param {string | Buffer | URL} path * @param {object?} [options] @@ -3036,6 +3337,15 @@ declare module "socket:fs/promises" { * @return {Promise<Stats>} */ export function stat(path: string | Buffer | URL, options?: object | null): Promise<Stats>; + /** + * Get the stats of a symbolic link. + * @see {@link https://nodejs.org/api/fs.html#fspromiseslstatpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {boolean?} [options.bigint = false] + * @return {Promise<Stats>} + */ + export function lstat(path: string | Buffer | URL, options?: object | null): Promise<Stats>; /** * Creates a symlink of `src` at `dest`. * @param {string} src @@ -3099,11 +3409,26 @@ declare module "socket:fs/index" { */ export function access(path: string | Buffer | URL, mode: any, callback?: ((arg0: Error | null) => any) | null): void; /** - * @ignore + * Synchronously check access a file for a given mode calling `callback` + * upon success or error. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?} [mode = F_OK(0)] */ - export function appendFile(path: any, data: any, options: any, callback: any): void; + export function accessSync(path: string | Buffer | URL, mode?: string | null): boolean; + /** + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] + */ + export function exists(path: string | Buffer | URL, callback?: ((arg0: boolean) => any) | null): void; + /** + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] + */ + export function existsSync(path: string | Buffer | URL): boolean; /** - * * Asynchronously changes the permissions of a file. * No arguments other than a possible exception are given to the completion callback * @@ -3114,6 +3439,14 @@ declare module "socket:fs/index" { * @param {function(Error?)} callback */ export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): void; + /** + * Synchronously changes the permissions of a file. + * + * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * @param {string | Buffer | URL} path + * @param {number} mode + */ + export function chmodSync(path: string | Buffer | URL, mode: number): void; /** * Changes ownership of file or directory at `path` with `uid` and `gid`. * @param {string} path @@ -3122,6 +3455,13 @@ declare module "socket:fs/index" { * @param {function} callback */ export function chown(path: string, uid: number, gid: number, callback: Function): void; + /** + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid + */ + export function chownSync(path: string, uid: number, gid: number): void; /** * Asynchronously close a file descriptor calling `callback` upon success or error. * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsclosefd-callback} @@ -3138,6 +3478,14 @@ declare module "socket:fs/index" { * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFile(src: string, dest: string, flags: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; + /** + * Synchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} + */ + export function copyFileSync(src: string, dest: string, flags: number): void; /** * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path @@ -3196,6 +3544,10 @@ declare module "socket:fs/index" { * @ignore */ export function mkdir(path: any, options: any, callback: any): void; + /** + * @ignore + */ + export function mkdirSync(path: any, options: any): void; /** * Asynchronously open a file calling `callback` upon success or error. * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} @@ -3246,6 +3598,14 @@ declare module "socket:fs/index" { * @param {function(Error?, Buffer?)} callback */ export function readFile(path: string | Buffer | URL | number, options: {}, callback: (arg0: Error | null, arg1: Buffer | null) => any): void; + /** + * @param {string | Buffer | URL | number } path + * @param {object?|function(Error?, Buffer?)} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + */ + export function readFileSync(path: string | Buffer | URL | number, options?: {}): any; /** * Reads link at `path` * @param {string} path @@ -3272,7 +3632,15 @@ declare module "socket:fs/index" { */ export function rmdir(path: string, callback: Function): void; /** - * + * Synchronously get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + */ + export function statSync(path: string | Buffer | URL | number, options: object | null): promises.Stats; + /** + * Get the stats of a file * @param {string | Buffer | URL | number } path - filename or file descriptor * @param {object?} options * @param {string?} [options.encoding ? 'utf8'] @@ -3281,6 +3649,16 @@ declare module "socket:fs/index" { * @param {function(Error?, Stats?)} callback */ export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + /** + * Get the stats of a symbolic link + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback + */ + export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; /** * Creates a symlink of `src` at `dest`. * @param {string} src @@ -3317,9 +3695,11 @@ declare module "socket:fs/index" { export default exports; export type Buffer = import("socket:buffer").Buffer; export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; import { ReadStream } from "socket:fs/stream"; import { WriteStream } from "socket:fs/stream"; import { Dir } from "socket:fs/dir"; + import * as promises from "socket:fs/promises"; import { Stats } from "socket:fs/stats"; import { Watcher } from "socket:fs/watcher"; import * as constants from "socket:fs/constants"; @@ -3327,7 +3707,6 @@ declare module "socket:fs/index" { import { Dirent } from "socket:fs/dir"; import fds from "socket:fs/fds"; import { FileHandle } from "socket:fs/handle"; - import * as promises from "socket:fs/promises"; import * as exports from "socket:fs/index"; export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; @@ -4350,6 +4729,11 @@ declare module "socket:window" { [x: string]: any; index: any; }); + /** + * The unique ID of this window. + * @type {string} + */ + get id(): string; /** * Get the index of the window * @return {number} - the index of the window @@ -4359,6 +4743,11 @@ declare module "socket:window" { * @type {import('./window/hotkey.js').default} */ get hotkey(): import("socket:window/hotkey").Bindings; + /** + * The broadcast channel for this window. + * @type {BroadcastChannel} + */ + get channel(): BroadcastChannel; /** * Get the size of the window * @return {{ width: number, height: number }} - the size of the window @@ -4612,12 +5001,10 @@ declare module "socket:application" { /** * Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. * @param {number[]} [indices] - the indices of the windows - * @return {Promise<Object.<number, ApplicationWindow>>} * @throws {Error} - if indices is not an array of integer numbers + * @return {Promise<Object.<number?, ApplicationWindow>>} */ - export function getWindows(indices?: number[]): Promise<{ - [x: number]: ApplicationWindow; - }>; + export function getWindows(indices?: number[]): Promise<any>; /** * Returns the ApplicationWindow instance for the given index * @param {number} index - the index of the window @@ -4773,8 +5160,60 @@ declare module "socket:application" { import * as exports from "socket:application"; } -declare module "socket:bluetooth" { - export default exports; +declare module "socket:test/fast-deep-equal" { + export default function equal(a: any, b: any): boolean; +} +declare module "socket:assert" { + export function assert(value: any, message?: any): void; + export function ok(value: any, message?: any): void; + export function equal(actual: any, expected: any, message?: any): void; + export function notEqual(actual: any, expected: any, message?: any): void; + export function strictEqual(actual: any, expected: any, message?: any): void; + export function notStrictEqual(actual: any, expected: any, message?: any): void; + export function deepEqual(actual: any, expected: any, message?: any): void; + export function notDeepEqual(actual: any, expected: any, message?: any): void; + export class AssertionError extends Error { + constructor(options: any); + actual: any; + expected: any; + operator: any; + } + const _default: typeof assert & { + AssertionError: typeof AssertionError; + ok: typeof ok; + equal: typeof equal; + notEqual: typeof notEqual; + strictEqual: typeof strictEqual; + notStrictEqual: typeof notStrictEqual; + deepEqual: typeof deepEqual; + notDeepEqual: typeof notDeepEqual; + }; + export default _default; +} +declare module "socket:async_context" { + export class AsyncLocalStorage { + static bind(fn: any): void; + static snapshot(): void; + disable(): void; + getStore(): void; + enterWith(store: any): void; + run(store: any, callback: any, ...args: any[]): void; + exit(callback: any, ...args: any[]): void; + } + export class AsyncResource { + } + namespace _default { + export { AsyncLocalStorage }; + } + export default _default; +} +declare module "socket:async_hooks" { + export * from "socket:async_context"; + export default context; + import context from "socket:async_context"; +} +declare module "socket:bluetooth" { + export default exports; /** * Create an instance of a Bluetooth service. */ @@ -4861,6 +5300,87 @@ declare module "socket:bootstrap" { } import { EventEmitter } from "socket:events"; } +declare module "socket:constants" { + export * from "socket:fs/constants"; + export * from "socket:window/constants"; + const _default: { + WINDOW_ERROR: -1; + WINDOW_NONE: 0; + WINDOW_CREATING: 10; + WINDOW_CREATED: 11; + WINDOW_HIDING: 20; + WINDOW_HIDDEN: 21; + WINDOW_SHOWING: 30; + WINDOW_SHOWN: 31; + WINDOW_CLOSING: 40; + WINDOW_CLOSED: 41; + WINDOW_EXITING: 50; + WINDOW_EXITED: 51; + WINDOW_KILLING: 60; + WINDOW_KILLED: 61; + default: typeof window; + COPYFILE_EXCL: 1; + COPYFILE_FICLONE: 2; + COPYFILE_FICLONE_FORCE: 4; + UV_DIRENT_UNKNOWN: any; + UV_DIRENT_FILE: any; + UV_DIRENT_DIR: any; + UV_DIRENT_LINK: any; + UV_DIRENT_FIFO: any; + UV_DIRENT_SOCKET: any; + UV_DIRENT_CHAR: any; + UV_DIRENT_BLOCK: any; + UV_FS_SYMLINK_DIR: any; + UV_FS_SYMLINK_JUNCTION: any; + O_RDONLY: any; + O_WRONLY: any; + O_RDWR: any; + O_APPEND: any; + O_ASYNC: any; + O_CLOEXEC: any; + O_CREAT: any; + O_DIRECT: any; + O_DIRECTORY: any; + O_DSYNC: any; + O_EXCL: any; + O_LARGEFILE: any; + O_NOATIME: any; + O_NOCTTY: any; + O_NOFOLLOW: any; + O_NONBLOCK: any; + O_NDELAY: any; + O_PATH: any; + O_SYNC: any; + O_TMPFILE: any; + O_TRUNC: any; + S_IFMT: any; + S_IFREG: any; + S_IFDIR: any; + S_IFCHR: any; + S_IFBLK: any; + S_IFIFO: any; + S_IFLNK: any; + S_IFSOCK: any; + S_IRWXU: any; + S_IRUSR: any; + S_IWUSR: any; + S_IXUSR: any; + S_IRWXG: any; + S_IRGRP: any; + S_IWGRP: any; + S_IXGRP: any; + S_IRWXO: any; + S_IROTH: any; + S_IWOTH: any; + S_IXOTH: any; + F_OK: any; + R_OK: any; + W_OK: any; + X_OK: any; + }; + export default _default; + import window from "socket:window/constants"; +} declare module "socket:ip" { /** * Normalizes input as an IPv4 address string @@ -5239,156 +5759,6 @@ declare module "socket:enumeration" { } export default Enumeration; } -declare module "socket:mime/index" { - /** - * Look up a MIME type in various MIME databases. - * @param {string} query - * @return {Promise<DatabaseQueryResult[]>} - */ - export function lookup(query: string): Promise<DatabaseQueryResult[]>; - /** - * A container for a database lookup query. - */ - export class DatabaseQueryResult { - /** - * `DatabaseQueryResult` class constructor. - * @ignore - * @param {Database} database - * @param {string} name - * @param {string} mime - */ - constructor(database: Database, name: string, mime: string); - /** - * @type {string} - */ - name: string; - /** - * @type {string} - */ - mime: string; - database: Database; - } - /** - * A container for MIME types by class (audio, video, text, etc) - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} - */ - export class Database { - /** - * `Database` class constructor. - * @param {string} name - */ - constructor(name: string); - /** - * The name of the MIME database. - * @type {string} - */ - name: string; - /** - * The URL of the MIME database. - * @type {URL} - */ - url: URL; - /** - * The mapping of MIME name to the MIME "content type" - * @type {Map} - */ - map: Map<any, any>; - /** - * An index of MIME "content type" to the MIME name. - * @type {Map} - */ - index: Map<any, any>; - /** - * An enumeration of all database entries. - * @return {Array<Array<string>>} - */ - entries(): Array<Array<string>>; - /** - * Loads database MIME entries into internal map. - * @return {Promise} - */ - load(): Promise<any>; - /** - * Lookup MIME type by name or content type - * @param {string} query - * @return {Promise<DatabaseQueryResult>} - */ - lookup(query: string): Promise<DatabaseQueryResult>; - } - /** - * A database of MIME types for 'application/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} - */ - export const application: Database; - /** - * A database of MIME types for 'audio/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} - */ - export const audio: Database; - /** - * A database of MIME types for 'font/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} - */ - export const font: Database; - /** - * A database of MIME types for 'image/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} - */ - export const image: Database; - /** - * A database of MIME types for 'model/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} - */ - export const model: Database; - /** - * A database of MIME types for 'multipart/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} - */ - export const multipart: Database; - /** - * A database of MIME types for 'text/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} - */ - export const text: Database; - /** - * A database of MIME types for 'video/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} - */ - export const video: Database; - /** - * An array of known MIME databases. Custom databases can be added to this - * array in userspace for lookup with `mime.lookup()` - * @type {Database[]} - */ - export const databases: Database[]; - namespace _default { - export { Database }; - export { databases }; - export { lookup }; - export { application }; - export { audio }; - export { font }; - export { image }; - export { model }; - export { multipart }; - export { text }; - export { video }; - } - export default _default; -} -declare module "socket:mime" { - export * from "socket:mime/index"; - export default exports; - import * as exports from "socket:mime/index"; -} declare module "socket:fs/web" { /** * Creates a new `File` instance from `filename`. @@ -5612,6 +5982,7 @@ declare module "socket:extension" { export { stats }; } export default _default; + export type Pointer = number; export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; @@ -5690,11 +6061,7 @@ declare module "socket:extension" { } const $type: unique symbol; /** - * {Pointer} - */ - type $loaded = number; - /** - * @typedef {number} {Pointer} + * @typedef {number} Pointer */ const $loaded: unique symbol; import path from "socket:path"; @@ -5774,6 +6141,232 @@ declare module "socket:fetch" { export default fetch; import fetch from "socket:fetch/index"; } +declare module "socket:http" { + export function get(optionsOrURL: any, options: any, callback: any): Promise<ClientRequest>; + export const METHODS: string[]; + export const STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + export class OutgoingMessage extends Writable { + headers: Headers; + get headersSent(): boolean; + get socket(): this; + get writableEnded(): boolean; + appendHeader(name: any, value: any): this; + setHeader(name: any, value: any): this; + flushHeaders(): void; + getHeader(name: any): string; + getHeaderNames(): string[]; + getHeaders(): { + [k: string]: string; + }; + hasHeader(name: any): boolean; + removeHeader(name: any): void; + } + export class ClientRequest extends OutgoingMessage { + url: any; + path: any; + host: any; + agent: any; + method: any; + protocol: string; + } + export class ServerResponse extends OutgoingMessage { + statusCode: number; + statusMessage: string; + req: any; + } + export class AgentOptions { + constructor(options: any); + keepAlive: boolean; + timeout: number; + } + export class Agent extends EventEmitter { + constructor(options: any); + defaultProtocol: string; + options: any; + createConnection(options: any, callback?: any): Duplex; + } + export const globalAgent: Agent; + namespace _default { + export { METHODS }; + export { STATUS_CODES }; + export { AgentOptions }; + export { Agent }; + export { globalAgent }; + export { request }; + export { OutgoingMessage }; + export { ClientRequest }; + export { ServerResponse }; + export { get }; + } + export default _default; + import { Writable } from "socket:stream"; + import { EventEmitter } from "socket:events"; + import { Duplex } from "socket:stream"; + function request(optionsOrURL: any, options: any, callback: any): Promise<ClientRequest>; +} +declare module "socket:https" { + export function request(optionsOrURL: any, options: any, callback: any): Promise<import("socket:http").ClientRequest>; + export function get(optionsOrURL: any, options: any, callback: any): Promise<import("socket:http").ClientRequest>; + export const METHODS: string[]; + export const STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + const AgentOptions_base: typeof import("socket:http").AgentOptions; + export class AgentOptions extends AgentOptions_base { + } + const Agent_base: typeof import("socket:http").Agent; + export class Agent extends Agent_base { + } + const OutgoingMessage_base: typeof import("socket:http").OutgoingMessage; + export class OutgoingMessage extends OutgoingMessage_base { + } + const ClientRequest_base: typeof import("socket:http").ClientRequest; + export class ClientRequest extends ClientRequest_base { + } + const ServerResponse_base: typeof import("socket:http").ServerResponse; + export class ServerResponse extends ServerResponse_base { + } + export const globalAgent: Agent; + namespace _default { + export { METHODS }; + export { STATUS_CODES }; + export { AgentOptions }; + export { Agent }; + export { globalAgent }; + export { request }; + export { OutgoingMessage }; + export { ClientRequest }; + export { ServerResponse }; + export { get }; + } + export default _default; +} declare module "socket:language" { /** * Look up a language name or code by query. @@ -7118,8 +7711,37 @@ declare module "socket:index" { import { NAT } from "socket:node/index"; export { network, Cache, sha256, Encryption, Packet, NAT }; } -declare module "socket:test/fast-deep-equal" { - export default function equal(a: any, b: any): boolean; +declare module "socket:string_decoder" { + export function StringDecoder(encoding: any): void; + export class StringDecoder { + constructor(encoding: any); + encoding: any; + text: typeof utf16Text | typeof base64Text; + end: typeof utf16End | typeof base64End | typeof simpleEnd; + fillLast: typeof utf8FillLast; + write: typeof simpleWrite; + lastNeed: number; + lastTotal: number; + lastChar: Uint8Array; + } + export default StringDecoder; + function utf16Text(buf: any, i: any): any; + class utf16Text { + constructor(buf: any, i: any); + lastNeed: number; + lastTotal: number; + } + function base64Text(buf: any, i: any): any; + class base64Text { + constructor(buf: any, i: any); + lastNeed: number; + lastTotal: number; + } + function utf16End(buf: any): any; + function base64End(buf: any): any; + function simpleEnd(buf: any): any; + function utf8FillLast(buf: any): any; + function simpleWrite(buf: any): any; } declare module "socket:test/context" { export default function _default(GLOBAL_TEST_RUNNER: any): void; @@ -7772,6 +8394,80 @@ declare module "socket:test" { export default test; import test from "socket:test/index"; } +declare module "socket:timers/timer" { + export class Timer { + static from(...args: any[]): void; + constructor(create: any, destroy: any); + get id(): number; + init(...args: any[]): void; + close(): boolean; + [Symbol.toPrimitive](): number; + #private; + } + export class Timeout extends Timer { + constructor(); + } + export class Interval extends Timer { + constructor(); + } + export class Immediate extends Timeout { + } + namespace _default { + export { Timer }; + export { Immediate }; + export { Timeout }; + export { Interval }; + } + export default _default; +} +declare module "socket:timers/promises" { + export function setTimeout(delay?: number, value?: any, options?: any): Promise<any>; + export function setInterval(delay?: number, value?: any, options?: any): AsyncGenerator<any, void, unknown>; + export function setImmediate(value?: any, options?: any): Promise<any>; + namespace _default { + export { setImmediate }; + export { setInterval }; + export { setTimeout }; + } + export default _default; +} +declare module "socket:timers/scheduler" { + export function wait(delay: any, options?: any): Promise<any>; + export function postTask(callback: any, options?: any): Promise<any>; + namespace _default { + export { postTask }; + export { setImmediate as yield }; + export { wait }; + } + export default _default; + import { setImmediate } from "socket:timers/promises"; +} +declare module "socket:timers/index" { + export function setTimeout(callback: any, delay: any, ...args: any[]): void; + export function clearTimeout(timeout: any): void; + export function setInterval(callback: any, delay: any, ...args: any[]): void; + export function clearInterval(interval: any): void; + export function setImmediate(callback: any, ...args: any[]): void; + export function clearImmediate(immediate: any): void; + namespace _default { + export { promises }; + export { scheduler }; + export { setTimeout }; + export { clearTimeout }; + export { setInterval }; + export { clearInterval }; + export { setImmediate }; + export { clearImmediate }; + } + export default _default; + import promises from "socket:timers/promises"; + import scheduler from "socket:timers/scheduler"; +} +declare module "socket:timers" { + export * from "socket:timers/index"; + export default exports; + import * as exports from "socket:timers/index"; +} declare module "socket:internal/globals" { /** * Gets a runtime global value by name. @@ -7894,14 +8590,14 @@ declare module "socket:vm" { * Creates a prototype object of known global reserved intrinsics. * @ignore */ - export function createIntrinsics(): any; + export function createIntrinsics(options: any): any; /** * Creates a global proxy object for context execution. * @ignore * @param {object} context * @return {Proxy} */ - export function createGlobalObject(context: object): ProxyConstructor; + export function createGlobalObject(context: object, options: any): ProxyConstructor; /** * @ignore * @param {string} source @@ -7972,6 +8668,7 @@ declare module "socket:vm" { * @return {object[]} */ export function getTrasferables(object: object): object[]; + export function createContext(object: any): any; /** * A container for a context worker message channel that looks like a "worker". * @ignore @@ -8131,6 +8828,7 @@ declare module "socket:vm" { #private; } namespace _default { + export { createGlobalObject }; export { compileFunction }; export { createReference }; export { getContextWindow }; @@ -8144,6 +8842,7 @@ declare module "socket:vm" { export { runInNewContext }; export { runInThisContext }; export { Script }; + export { createContext }; } export default _default; export type ScriptOptions = { @@ -8152,6 +8851,179 @@ declare module "socket:vm" { }; import { SharedWorker } from "socket:worker"; } +declare module "socket:worker_threads/init" { + export const SHARE_ENV: unique symbol; + export const isMainThread: boolean; + export namespace state { + export { isMainThread }; + export let parentPort: any; + export let mainPort: any; + export let workerData: any; + export let url: any; + export let env: {}; + export let id: number; + } + namespace _default { + export { state }; + } + export default _default; +} +declare module "socket:worker_threads" { + /** + * Set shared worker environment data. + * @param {string} key + * @param {any} value + */ + export function setEnvironmentData(key: string, value: any): void; + /** + * Get shared worker environment data. + * @param {string} key + * @return {any} + */ + export function getEnvironmentData(key: string): any; + /** + * A pool of known worker threads. + * @type {<Map<string, Worker>} + */ + export const workers: <Map_1>() => <string, Worker_1>() => any; + /** + * `true` if this is the "main" thread, otherwise `false` + * The "main" thread is the top level webview window. + * @type {boolean} + */ + export const isMainThread: boolean; + /** + * The main thread `MessagePort` which is `null` when the + * current context is not the "main thread". + * @type {MessagePort?} + */ + export const mainPort: MessagePort | null; + /** + * A worker thread `BroadcastChannel` class. + */ + export class BroadcastChannel extends globalThis.BroadcastChannel { + } + /** + * A worker thread `MessageChannel` class. + */ + export class MessageChannel extends globalThis.MessageChannel { + } + /** + * A worker thread `MessagePort` class. + */ + export class MessagePort extends globalThis.MessagePort { + } + /** + * The current unique thread ID. + * @type {number} + */ + export const threadId: number; + /** + * The parent `MessagePort` instance + * @type {MessagePort?} + */ + export const parentPort: MessagePort | null; + /** + * Transferred "worker data" when creating a new `Worker` instance. + * @type {any?} + */ + export const workerData: any | null; + /** + * @typedef {{ + * env?: object, + * stdin?: boolean = false, + * stdout?: boolean = false, + * stderr?: boolean = false, + * workerData?: any, + * transferList?: any[], + * eval?: boolean = false + * }} WorkerOptions + + /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export class Worker extends EventEmitter { + /** + * `Worker` class constructor. + * @param {string} filename + * @param {WorkerOptions=} [options] + */ + constructor(filename: string, options?: WorkerOptions | undefined); + /** + * Handles incoming worker messages. + * @ignore + * @param {MessageEvent} event + */ + onWorkerMessage(event: MessageEvent): boolean; + /** + * Handles process environment change events + * @ignore + * @param {import('./process.js').ProcessEnvironmentEvent} event + */ + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + /** + * The unique ID for this `Worker` thread instace. + * @type {number} + */ + get id(): number; + /** + * A `Writable` standard input stream if `{ stdin: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Writable?} + */ + get stdin(): Writable; + /** + * A `Readable` standard output stream if `{ stdout: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stdout(): Readable; + /** + * A `Readable` standard error stream if `{ stderr: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stderr(): Readable; + /** + * Terminates the `Worker` instance + */ + terminate(): void; + #private; + } + namespace _default { + export { Worker }; + export { isMainThread }; + export { parentPort }; + export { setEnvironmentData }; + export { getEnvironmentData }; + export { workerData }; + export { threadId }; + export { SHARE_ENV }; + } + export default _default; + /** + * /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export type WorkerOptions = { + env?: object; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + workerData?: any; + transferList?: any[]; + eval?: boolean; + }; + import { EventEmitter } from "socket:events"; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; + import { SHARE_ENV } from "socket:worker_threads/init"; + import init from "socket:worker_threads/init"; + import { env } from "socket:process"; + export { SHARE_ENV, init }; +} declare module "socket:module" { export function isBuiltin(name: any): boolean; /** @@ -8160,13 +9032,106 @@ declare module "socket:module" { * @return {function} */ export function createRequire(sourcePath: URL | string): Function; - export default exports; /** * A limited set of builtins exposed to CommonJS modules. */ export const builtins: { + async_context: { + AsyncLocalStorage: typeof import("socket:async_context").AsyncLocalStorage; + }; + async_hooks: { + AsyncLocalStorage: typeof import("socket:async_context").AsyncLocalStorage; + }; + application: typeof application; + assert: typeof import("socket:assert").assert & { + AssertionError: typeof import("socket:assert").AssertionError; + ok: typeof import("socket:assert").ok; + equal: typeof import("socket:assert").equal; + notEqual: typeof import("socket:assert").notEqual; + strictEqual: typeof import("socket:assert").strictEqual; + notStrictEqual: typeof import("socket:assert").notStrictEqual; + deepEqual: typeof import("socket:assert").deepEqual; + notDeepEqual: typeof import("socket:assert").notDeepEqual; + }; buffer: typeof buffer; console: import("socket:console").Console; + constants: { + WINDOW_ERROR: -1; + WINDOW_NONE: 0; + WINDOW_CREATING: 10; + WINDOW_CREATED: 11; + WINDOW_HIDING: 20; + WINDOW_HIDDEN: 21; + WINDOW_SHOWING: 30; + WINDOW_SHOWN: 31; + WINDOW_CLOSING: 40; + WINDOW_CLOSED: 41; + WINDOW_EXITING: 50; + WINDOW_EXITED: 51; + WINDOW_KILLING: 60; + WINDOW_KILLED: 61; + default: typeof import("socket:window/constants"); + COPYFILE_EXCL: 1; + COPYFILE_FICLONE: 2; + COPYFILE_FICLONE_FORCE: 4; + UV_DIRENT_UNKNOWN: any; + UV_DIRENT_FILE: any; + UV_DIRENT_DIR: any; + UV_DIRENT_LINK: any; + UV_DIRENT_FIFO: any; + UV_DIRENT_SOCKET: any; + UV_DIRENT_CHAR: any; + UV_DIRENT_BLOCK: any; + UV_FS_SYMLINK_DIR: any; + UV_FS_SYMLINK_JUNCTION: any; + O_RDONLY: any; + O_WRONLY: any; + O_RDWR: any; + O_APPEND: any; + O_ASYNC: any; + O_CLOEXEC: any; + O_CREAT: any; + O_DIRECT: any; + O_DIRECTORY: any; + O_DSYNC: any; + O_EXCL: any; + O_LARGEFILE: any; + O_NOATIME: any; + O_NOCTTY: any; + O_NOFOLLOW: any; + O_NONBLOCK: any; + O_NDELAY: any; + O_PATH: any; + O_SYNC: any; + O_TMPFILE: any; + O_TRUNC: any; + S_IFMT: any; + S_IFREG: any; + S_IFDIR: any; + S_IFCHR: any; + S_IFBLK: any; + S_IFIFO: any; + S_IFLNK: any; + S_IFSOCK: any; + S_IRWXU: any; + S_IRUSR: any; + S_IWUSR: any; + S_IXUSR: any; + S_IRWXG: any; + S_IRGRP: any; + S_IWGRP: any; + S_IXGRP: any; + S_IRWXO: any; + S_IROTH: any; + S_IWOTH: any; + S_IXOTH: any; + F_OK: any; + R_OK: any; + W_OK: any; + X_OK: any; + }; + child_process: {}; + crypto: typeof crypto; dgram: typeof dgram; dns: typeof dns; 'dns/promises': typeof dns.promises; @@ -8177,17 +9142,199 @@ declare module "socket:module" { }; fs: typeof fs; 'fs/promises': typeof fs.promises; + http: { + METHODS: string[]; + STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + AgentOptions: typeof import("socket:http").AgentOptions; + Agent: typeof import("socket:http").Agent; + globalAgent: import("socket:http").Agent; + request: (optionsOrURL: any, options: any, callback: any) => Promise<import("socket:http").ClientRequest>; + OutgoingMessage: typeof import("socket:http").OutgoingMessage; + ClientRequest: typeof import("socket:http").ClientRequest; + ServerResponse: typeof import("socket:http").ServerResponse; + get: typeof import("socket:http").get; + }; gc: any; + https: { + METHODS: string[]; + STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + AgentOptions: typeof import("socket:https").AgentOptions; + Agent: typeof import("socket:https").Agent; + globalAgent: import("socket:https").Agent; + request: typeof import("socket:https").request; + OutgoingMessage: typeof import("socket:https").OutgoingMessage; + ClientRequest: typeof import("socket:https").ClientRequest; + ServerResponse: typeof import("socket:https").ServerResponse; + get: typeof import("socket:https").get; + }; ipc: typeof ipc; - module: typeof exports; + language: { + codes: string[]; + describe: typeof import("socket:language").describe; + lookup: typeof import("socket:language").lookup; + names: string[]; + tags: import("socket:enumeration").Enumeration; + }; + mime: typeof mime; + net: {}; os: typeof os; path: typeof path; + perf_hooks: { + performance: Performance; + }; process: any; + querystring: { + decode: typeof import("socket:querystring").parse; + encode: typeof import("socket:querystring").stringify; + parse: typeof import("socket:querystring").parse; + stringify: typeof import("socket:querystring").stringify; + escape: typeof import("socket:querystring").escape; + unescape: typeof import("socket:querystring").unescape; + }; stream: typeof stream; + 'stream/web': typeof stream.web; + string_decoder: typeof string_decoder; + sys: typeof util; test: typeof test; + timers: typeof timers; + 'timers/promises': any; + tty: { + isatty: () => boolean; + WriteStream: typeof util.IllegalConstructor; + ReadStream: typeof util.IllegalConstructor; + }; util: typeof util; url: any; vm: { + createGlobalObject: typeof import("socket:vm").createGlobalObject; compileFunction: typeof import("socket:vm").compileFunction; createReference: typeof import("socket:vm").createReference; getContextWindow: typeof import("socket:vm").getContextWindow; @@ -8201,11 +9348,117 @@ declare module "socket:module" { runInNewContext: typeof import("socket:vm").runInNewContext; runInThisContext: typeof import("socket:vm").runInThisContext; Script: typeof import("socket:vm").Script; + createContext: typeof import("socket:vm").createContext; + }; + window: typeof window; + worker_threads: { + Worker: typeof import("socket:worker_threads").Worker; + isMainThread: boolean; + parentPort: import("socket:worker_threads").MessagePort; + setEnvironmentData: typeof import("socket:worker_threads").setEnvironmentData; + getEnvironmentData: typeof import("socket:worker_threads").getEnvironmentData; + workerData: any; + threadId: number; + SHARE_ENV: symbol; }; }; export const builtinModules: { + async_context: { + AsyncLocalStorage: typeof import("socket:async_context").AsyncLocalStorage; + }; + async_hooks: { + AsyncLocalStorage: typeof import("socket:async_context").AsyncLocalStorage; + }; + application: typeof application; + assert: typeof import("socket:assert").assert & { + AssertionError: typeof import("socket:assert").AssertionError; + ok: typeof import("socket:assert").ok; + equal: typeof import("socket:assert").equal; + notEqual: typeof import("socket:assert").notEqual; + strictEqual: typeof import("socket:assert").strictEqual; + notStrictEqual: typeof import("socket:assert").notStrictEqual; + deepEqual: typeof import("socket:assert").deepEqual; + notDeepEqual: typeof import("socket:assert").notDeepEqual; + }; buffer: typeof buffer; console: import("socket:console").Console; + constants: { + WINDOW_ERROR: -1; + WINDOW_NONE: 0; + WINDOW_CREATING: 10; + WINDOW_CREATED: 11; + WINDOW_HIDING: 20; + WINDOW_HIDDEN: 21; + WINDOW_SHOWING: 30; + WINDOW_SHOWN: 31; + WINDOW_CLOSING: 40; + WINDOW_CLOSED: 41; + WINDOW_EXITING: 50; + WINDOW_EXITED: 51; + WINDOW_KILLING: 60; + WINDOW_KILLED: 61; + default: typeof import("socket:window/constants"); + COPYFILE_EXCL: 1; + COPYFILE_FICLONE: 2; + COPYFILE_FICLONE_FORCE: 4; + UV_DIRENT_UNKNOWN: any; + UV_DIRENT_FILE: any; + UV_DIRENT_DIR: any; + UV_DIRENT_LINK: any; + UV_DIRENT_FIFO: any; + UV_DIRENT_SOCKET: any; + UV_DIRENT_CHAR: any; + UV_DIRENT_BLOCK: any; + UV_FS_SYMLINK_DIR: any; + UV_FS_SYMLINK_JUNCTION: any; + O_RDONLY: any; + O_WRONLY: any; + O_RDWR: any; + O_APPEND: any; + O_ASYNC: any; + O_CLOEXEC: any; + O_CREAT: any; + O_DIRECT: any; + O_DIRECTORY: any; + O_DSYNC: any; + O_EXCL: any; + O_LARGEFILE: any; + O_NOATIME: any; + O_NOCTTY: any; + O_NOFOLLOW: any; + O_NONBLOCK: any; + O_NDELAY: any; + O_PATH: any; + O_SYNC: any; + O_TMPFILE: any; + O_TRUNC: any; + S_IFMT: any; + S_IFREG: any; + S_IFDIR: any; + S_IFCHR: any; + S_IFBLK: any; + S_IFIFO: any; + S_IFLNK: any; + S_IFSOCK: any; + S_IRWXU: any; + S_IRUSR: any; + S_IWUSR: any; + S_IXUSR: any; + S_IRWXG: any; + S_IRGRP: any; + S_IWGRP: any; + S_IXGRP: any; + S_IRWXO: any; + S_IROTH: any; + S_IWOTH: any; + S_IXOTH: any; + F_OK: any; + R_OK: any; + W_OK: any; + X_OK: any; + }; + child_process: {}; + crypto: typeof crypto; dgram: typeof dgram; dns: typeof dns; 'dns/promises': typeof dns.promises; @@ -8216,17 +9469,199 @@ declare module "socket:module" { }; fs: typeof fs; 'fs/promises': typeof fs.promises; + http: { + METHODS: string[]; + STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + AgentOptions: typeof import("socket:http").AgentOptions; + Agent: typeof import("socket:http").Agent; + globalAgent: import("socket:http").Agent; + request: (optionsOrURL: any, options: any, callback: any) => Promise<import("socket:http").ClientRequest>; + OutgoingMessage: typeof import("socket:http").OutgoingMessage; + ClientRequest: typeof import("socket:http").ClientRequest; + ServerResponse: typeof import("socket:http").ServerResponse; + get: typeof import("socket:http").get; + }; gc: any; + https: { + METHODS: string[]; + STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + AgentOptions: typeof import("socket:https").AgentOptions; + Agent: typeof import("socket:https").Agent; + globalAgent: import("socket:https").Agent; + request: typeof import("socket:https").request; + OutgoingMessage: typeof import("socket:https").OutgoingMessage; + ClientRequest: typeof import("socket:https").ClientRequest; + ServerResponse: typeof import("socket:https").ServerResponse; + get: typeof import("socket:https").get; + }; ipc: typeof ipc; - module: typeof exports; + language: { + codes: string[]; + describe: typeof import("socket:language").describe; + lookup: typeof import("socket:language").lookup; + names: string[]; + tags: import("socket:enumeration").Enumeration; + }; + mime: typeof mime; + net: {}; os: typeof os; path: typeof path; + perf_hooks: { + performance: Performance; + }; process: any; + querystring: { + decode: typeof import("socket:querystring").parse; + encode: typeof import("socket:querystring").stringify; + parse: typeof import("socket:querystring").parse; + stringify: typeof import("socket:querystring").stringify; + escape: typeof import("socket:querystring").escape; + unescape: typeof import("socket:querystring").unescape; + }; stream: typeof stream; + 'stream/web': typeof stream.web; + string_decoder: typeof string_decoder; + sys: typeof util; test: typeof test; + timers: typeof timers; + 'timers/promises': any; + tty: { + isatty: () => boolean; + WriteStream: typeof util.IllegalConstructor; + ReadStream: typeof util.IllegalConstructor; + }; util: typeof util; url: any; vm: { + createGlobalObject: typeof import("socket:vm").createGlobalObject; compileFunction: typeof import("socket:vm").compileFunction; createReference: typeof import("socket:vm").createReference; getContextWindow: typeof import("socket:vm").getContextWindow; @@ -8240,6 +9675,18 @@ declare module "socket:module" { runInNewContext: typeof import("socket:vm").runInNewContext; runInThisContext: typeof import("socket:vm").runInThisContext; Script: typeof import("socket:vm").Script; + createContext: typeof import("socket:vm").createContext; + }; + window: typeof window; + worker_threads: { + Worker: typeof import("socket:worker_threads").Worker; + isMainThread: boolean; + parentPort: import("socket:worker_threads").MessagePort; + setEnvironmentData: typeof import("socket:worker_threads").setEnvironmentData; + getEnvironmentData: typeof import("socket:worker_threads").getEnvironmentData; + workerData: any; + threadId: number; + SHARE_ENV: symbol; }; }; /** @@ -8261,18 +9708,18 @@ declare module "socket:module" { * to the "main" module and global object (if possible). */ export class Module extends EventTarget { - static set current(module: exports.Module); + static set current(module: Module); /** * A reference to the currently scoped module. * @type {Module?} */ - static get current(): exports.Module; - static set previous(module: exports.Module); + static get current(): Module; + static set previous(module: Module); /** * A reference to the previously scoped module. * @type {Module?} */ - static get previous(): exports.Module; + static get previous(): Module; /** * Module cache. * @ignore @@ -8288,6 +9735,11 @@ declare module "socket:module" { * @ignore */ static wrapper: string; + /** + * A limited set of builtins exposed to CommonJS modules. + * @type {object} + */ + static builtins: object; /** * Creates a `require` function from a source URL. * @param {URL|string} sourcePath @@ -8298,7 +9750,7 @@ declare module "socket:module" { * The main entry module, lazily created. * @type {Module} */ - static get main(): exports.Module; + static get main(): Module; /** * Wraps source in a CommonJS module scope. */ @@ -8396,21 +9848,26 @@ declare module "socket:module" { */ [Symbol.toStringTag](): string; } + export default Module; export type ModuleResolver = (arg0: string, arg1: Module, arg2: Function) => undefined; import { URL } from "socket:url/index"; - import * as exports from "socket:module"; + import application from "socket:application"; import buffer from "socket:buffer"; + import crypto from "socket:crypto"; import dgram from "socket:dgram"; import dns from "socket:dns"; import events from "socket:events"; import fs from "socket:fs"; import ipc from "socket:ipc"; + import mime from "socket:mime"; import os from "socket:os"; import { posix as path } from "socket:path"; import stream from "socket:stream"; - import test from "socket:test"; + import string_decoder from "socket:string_decoder"; import util from "socket:util"; - + import test from "socket:test"; + import timers from "socket:timers"; + import window from "socket:window"; } declare module "socket:network" { export default network; @@ -8925,13 +10382,22 @@ declare module "socket:internal/geolocation" { } export default _default; } +declare module "socket:internal/timers" { + export function setImmediate(callback: any, ...args: any[]): number; + export function clearImmediate(immediate: any): void; + namespace _default { + export { setImmediate }; + export let clearTimeout: typeof globalThis.clearTimeout; + } + export default _default; +} declare module "socket:service-worker/state" { export const channel: BroadcastChannel; export const state: any; export default state; } declare module "socket:service-worker/instance" { - export function createServiceWorker(currentState?: any): any; + export function createServiceWorker(currentState?: any, options?: any): any; export const SHARED_WORKER_URL: URL; const _default: any; export default _default; @@ -8957,7 +10423,19 @@ declare module "socket:service-worker/registration" { } declare module "socket:service-worker/container" { export class ServiceWorkerContainer extends EventTarget { - init(): void; + /** + * A special initialization function for augmenting the global + * `globalThis.navigator.serviceWorker` platform `ServiceWorkerContainer` + * instance. + * + * All functions MUST be sure to what a lexically bound `this` becomes as the + * target could change with respect to the `internal` `Map` instance which + * contains private implementation properties relevant to the runtime + * `ServiceWorkerContainer` internal state implementations. + * @ignore + * @private + */ + private init; register(scriptURL: any, options?: any): Promise<ServiceWorkerRegistration>; getRegistration(clientURL: any): Promise<ServiceWorkerRegistration>; getRegistrations(): Promise<ServiceWorkerRegistration[]>; @@ -8996,6 +10474,11 @@ declare module "socket:internal/webassembly" { } export default _default; } +declare module "socket:internal/scheduler" { + export * from "socket:timers/scheduler"; + export default scheduler; + import scheduler from "socket:timers/scheduler"; +} declare module "socket:internal/pickers" { /** * @typedef {{ @@ -9090,7 +10573,7 @@ declare module "socket:internal/pickers" { }; import { FileSystemHandle } from "socket:fs/web"; } -declare module "socket:internal/monkeypatch" { +declare module "socket:internal/primitives" { export function init(): { natives: {}; patches: {}; @@ -9161,25 +10644,36 @@ declare module "socket:internal/worker" { } declare module "socket:service-worker/clients" { export class Client { - postMessage(message: any, optionsOrTransferables?: any): any; + constructor(options: any); + get id(): any; + get url(): any; + get type(): any; + get frameType(): any; + postMessage(message: any, optionsOrTransferables?: any): void; #private; } export class WindowClient extends Client { - focus(): void; - navigate(): void; + get focused(): boolean; + get ancestorOrigins(): any[]; + get visibilityState(): string; + focus(): Promise<this>; + navigate(url: any): Promise<this>; + #private; } export class Clients { - get(id: any): Promise<void>; - matchAll(): Promise<void>; - openWindow(): Promise<void>; + get(id: any): Promise<Client>; + matchAll(options?: any): Promise<any>; + openWindow(url: any, options?: any): Promise<WindowClient>; claim(): Promise<void>; } - export default Clients; + const _default: Clients; + export default _default; } declare module "socket:service-worker/events" { export class ExtendableEvent extends Event { waitUntil(promise: any): void; waitsFor(): Promise<any>; + get awaiting(): Promise<any>; get pendingPromises(): number; get isActive(): boolean; #private; @@ -9207,7 +10701,7 @@ declare module "socket:service-worker/global" { get serviceWorker(): any; set registration(value: any); get registration(): any; - get clients(): Clients; + get clients(): import("socket:service-worker/clients").Clients; set onactivate(listener: any); get onactivate(): any; set onmessage(listener: any); @@ -9222,7 +10716,6 @@ declare module "socket:service-worker/global" { export default _default; import { ExtendableEvent } from "socket:service-worker/events"; import { FetchEvent } from "socket:service-worker/events"; - import { Clients } from "socket:service-worker/clients"; } declare module "socket:service-worker/init" { const _default: any; diff --git a/api/internal/init.js b/api/internal/init.js index 1b1d6f565a..94d9a5c829 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -12,7 +12,7 @@ console.assert( 'This could lead to undefined behavior.' ) -import './monkeypatch.js' +import './primitives.js' import { IllegalConstructor, InvertedPromise } from '../util.js' import { CustomEvent, ErrorEvent } from '../events.js' diff --git a/api/internal/monkeypatch.js b/api/internal/primitives.js similarity index 100% rename from api/internal/monkeypatch.js rename to api/internal/primitives.js diff --git a/src/window/apple.mm b/src/window/apple.mm index 27eab149ae..56e605056b 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -46,7 +46,6 @@ - (void) webView: (WKWebView*) webview if (hasAppLink) { if (self.bridge != nullptr) { - debug("CANCEL #1: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); SSC::JSON::Object json = SSC::JSON::Object::Entries {{ "url", request @@ -63,7 +62,6 @@ - (void) webView: (WKWebView*) webview !request.starts_with("socket://" + userConfig["meta_bundle_identifier"]) ) { if (self.bridge != nullptr) { - debug("CANCEL #2: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); SSC::JSON::Object json = SSC::JSON::Object::Entries {{ @@ -76,7 +74,6 @@ - (void) webView: (WKWebView*) webview } if (!request.starts_with("socket:") && !request.starts_with(devHost)) { - debug("CANCEL #3: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); return; } @@ -884,7 +881,7 @@ - (void) webView: (WKWebView*) webView config.allowsAirPlayForMediaPlayback = YES; } } @catch (NSException *error) { - debug("%@", error); + debug("Failed to set preference 'allowsAirPlayForMediaPlayback': %@", error); } config.defaultWebpagePreferences.allowsContentJavaScript = YES; @@ -983,17 +980,16 @@ - (void) webView: (WKWebView*) webView imp_implementationWithBlock( [&](id self, SEL cmd, id notification) { auto window = (Window*) objc_getAssociatedObject(self, "window"); - if (!window) { + if (!window || window->exiting) { return true; } - if (exiting) return true; - if (window->opts.canExit) { - exiting = true; + window->exiting = true; window->exit(0); return true; } + window->eval(getEmitToRenderProcessJavaScript("windowHide", "{}")); window->hide(); return false; From e02ec988203b96b9427dfb9d9dc91c1660d4b65d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Feb 2024 21:12:37 +0100 Subject: [PATCH 0033/1178] refactor(vm): fix frame posting message, improve VM, add VM tests --- api/hooks.js | 37 +-- api/index.d.ts | 462 +++++++++++++++++++-------------- api/internal/init.js | 3 +- api/internal/worker.js | 8 +- api/service-worker/instance.js | 3 +- api/vm.js | 434 ++++++++++++++++++++++++++++--- api/vm/init.js | 7 +- api/vm/world.js | 14 +- api/worker.js | 26 +- api/worker_threads/init.js | 54 ++-- test/src/index.js | 1 + test/src/vm.js | 96 +++++++ 12 files changed, 825 insertions(+), 320 deletions(-) create mode 100644 test/src/vm.js diff --git a/api/hooks.js b/api/hooks.js index d282a90622..027e3fc9a3 100644 --- a/api/hooks.js +++ b/api/hooks.js @@ -72,19 +72,12 @@ import location from './location.js' * @typedef {{ signal?: AbortSignal }} WaitOptions */ -// primordial setup -const EventTargetPrototype = { - addEventListener: Function.prototype.call.bind(EventTarget.prototype.addEventListener), - removeEventListener: Function.prototype.call.bind(EventTarget.prototype.removeEventListener), - dispatchEvent: Function.prototype.call.bind(EventTarget.prototype.dispatchEvent) -} - -function addEventListener (target, type, callback) { - EventTargetPrototype.addEventListener(target, type, callback) +function addEventListener (target, type, callback, ...args) { + target.addEventListener(type, callback, ...args) } function addEventListenerOnce (target, type, callback) { - EventTargetPrototype.addEventListener(target, type, callback, { once: true }) + target.addEventListener(type, callback, { once: true }) } async function waitForEvent (target, type) { @@ -94,7 +87,7 @@ async function waitForEvent (target, type) { } function dispatchEvent (target, event) { - queueMicrotask(() => EventTargetPrototype.dispatchEvent(target, event)) + queueMicrotask(() => target.dispatchEvent(event)) } function dispatchInitEvent (target) { @@ -114,20 +107,30 @@ function proxyGlobalEvents (global, target) { addEventListener(global, type, (event) => { const { type, data, detail = null, error } = event const { origin } = location + if (type === 'applicationurl') { dispatchEvent(target, new ApplicationURLEvent(type, { + ...event, + origin, data: event.data, url: event.url.toString() })) - } else if (error) { + } else if (type === 'error' || error) { const { message, filename = import.meta.url || globalThis.location.href } = error - dispatchEvent(target, new ErrorEvent(type, { message, filename, error, detail })) - } else if (type && data) { - dispatchEvent(target, new MessageEvent(type, { origin, data, detail })) + dispatchEvent(target, new ErrorEvent(type, { + ...event, + message, + filename, + error, + detail, + origin + })) + } else if ((type && data) || type === 'message') { + dispatchEvent(target, new MessageEvent(type, { ...event, origin })) } else if (detail) { - dispatchEvent(target, new CustomEvent(type, { detail })) + dispatchEvent(target, new CustomEvent(type, { ...event, origin })) } else { - dispatchEvent(target, new Event(type)) + dispatchEvent(target, new Event(type, { ...event, origin })) } }) } diff --git a/api/index.d.ts b/api/index.d.ts index dd930a2775..d69bc76d07 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -8512,14 +8512,200 @@ declare module "socket:internal/shared-worker" { } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; export default SharedWorker; } -declare module "socket:worker" { - export { SharedWorker }; +declare module "socket:service-worker/state" { + export const channel: BroadcastChannel; + export const state: any; + export default state; +} +declare module "socket:service-worker/instance" { + export function createServiceWorker(currentState?: any, options?: any): any; + export const SHARED_WORKER_URL: URL; + export const ServiceWorker: { + new (): ServiceWorker; + prototype: ServiceWorker; + }; + const _default: any; + export default _default; +} +declare module "socket:worker_threads/init" { + export const SHARE_ENV: unique symbol; + export const isMainThread: boolean; + export namespace state { + export { isMainThread }; + export let parentPort: any; + export let mainPort: any; + export let workerData: any; + export let url: any; + export let env: {}; + export let id: number; + } + namespace _default { + export { state }; + } + export default _default; +} +declare module "socket:worker_threads" { + /** + * Set shared worker environment data. + * @param {string} key + * @param {any} value + */ + export function setEnvironmentData(key: string, value: any): void; + /** + * Get shared worker environment data. + * @param {string} key + * @return {any} + */ + export function getEnvironmentData(key: string): any; + /** + * A pool of known worker threads. + * @type {<Map<string, Worker>} + */ + export const workers: <Map_1>() => <string, Worker_1>() => any; + /** + * `true` if this is the "main" thread, otherwise `false` + * The "main" thread is the top level webview window. + * @type {boolean} + */ + export const isMainThread: boolean; + /** + * The main thread `MessagePort` which is `null` when the + * current context is not the "main thread". + * @type {MessagePort?} + */ + export const mainPort: MessagePort | null; + /** + * A worker thread `BroadcastChannel` class. + */ + export class BroadcastChannel extends globalThis.BroadcastChannel { + } + /** + * A worker thread `MessageChannel` class. + */ + export class MessageChannel extends globalThis.MessageChannel { + } + /** + * A worker thread `MessagePort` class. + */ + export class MessagePort extends globalThis.MessagePort { + } + /** + * The current unique thread ID. + * @type {number} + */ + export const threadId: number; + /** + * The parent `MessagePort` instance + * @type {MessagePort?} + */ + export const parentPort: MessagePort | null; + /** + * Transferred "worker data" when creating a new `Worker` instance. + * @type {any?} + */ + export const workerData: any | null; + /** + * @typedef {{ + * env?: object, + * stdin?: boolean = false, + * stdout?: boolean = false, + * stderr?: boolean = false, + * workerData?: any, + * transferList?: any[], + * eval?: boolean = false + * }} WorkerOptions + + /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export class Worker extends EventEmitter { + /** + * `Worker` class constructor. + * @param {string} filename + * @param {WorkerOptions=} [options] + */ + constructor(filename: string, options?: WorkerOptions | undefined); + /** + * Handles incoming worker messages. + * @ignore + * @param {MessageEvent} event + */ + onWorkerMessage(event: MessageEvent): boolean; + /** + * Handles process environment change events + * @ignore + * @param {import('./process.js').ProcessEnvironmentEvent} event + */ + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + /** + * The unique ID for this `Worker` thread instace. + * @type {number} + */ + get id(): number; + /** + * A `Writable` standard input stream if `{ stdin: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Writable?} + */ + get stdin(): Writable; + /** + * A `Readable` standard output stream if `{ stdout: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stdout(): Readable; + /** + * A `Readable` standard error stream if `{ stderr: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stderr(): Readable; + /** + * Terminates the `Worker` instance + */ + terminate(): void; + #private; + } + namespace _default { + export { Worker }; + export { isMainThread }; + export { parentPort }; + export { setEnvironmentData }; + export { getEnvironmentData }; + export { workerData }; + export { threadId }; + export { SHARE_ENV }; + } + export default _default; /** - * @type {import('dom').Worker} + * /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. */ - export const Worker: any; + export type WorkerOptions = { + env?: object; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + workerData?: any; + transferList?: any[]; + eval?: boolean; + }; + import { EventEmitter } from "socket:events"; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; + import { SHARE_ENV } from "socket:worker_threads/init"; + import init from "socket:worker_threads/init"; + import { env } from "socket:process"; + export { SHARE_ENV, init }; +} +declare module "socket:worker" { export default Worker; - import SharedWorker from "socket:internal/shared-worker"; + import { SharedWorker } from "socket:internal/shared-worker"; + import { ServiceWorker } from "socket:service-worker/instance"; + import { Worker } from "socket:worker_threads"; + export { SharedWorker, ServiceWorker, Worker }; } declare module "socket:vm" { /** @@ -8591,6 +8777,24 @@ declare module "socket:vm" { * @ignore */ export function createIntrinsics(options: any): any; + /** + * Returns `true` if value is an intrinsic, otherwise `false`. + * @param {any} value + * @return {boolean} + */ + export function isIntrinsic(value: any): boolean; + /** + * Get the intrinsic type of a given `value`. + * @param {any} + * @return {function|object|null|undefined} + */ + export function getIntrinsicType(value: any): Function | object | null | undefined; + /** + * Get the intrinsic type string of a given `value`. + * @param {any} + * @return {string|null} + */ + export function getIntrinsicTypeString(value: any): string | null; /** * Creates a global proxy object for context execution. * @ignore @@ -8617,12 +8821,12 @@ declare module "socket:vm" { * context is preserved until the `context` object that points to it is * garbage collected or there are no longer any references to it and its * associated `Script` instance. - * @param {string} source + * @param {string|object|function} source * @param {ScriptOptions=} [options] * @param {object=} [context] * @return {Promise<any>} */ - export function runInContext(source: string, options?: ScriptOptions | undefined, context?: object | undefined): Promise<any>; + export function runInContext(source: string | object | Function, options?: ScriptOptions | undefined, context?: object | undefined): Promise<any>; /** * Run `source` JavaScript in new context. The script context is destroyed after * execution. This is typically a "one off" isolated run. @@ -8648,9 +8852,10 @@ declare module "socket:vm" { * Create a `Reference` for a `value` in a script `context`. * @param {any} value * @param {object} context + * @param {object=} [options] * @return {Reference} */ - export function createReference(value: any, context: object): Reference; + export function createReference(value: any, context: object, options?: object | undefined): Reference; /** * Get a script context by ID or values * @param {string|object|function} id @@ -8668,7 +8873,18 @@ declare module "socket:vm" { * @return {object[]} */ export function getTrasferables(object: object): object[]; - export function createContext(object: any): any; + /** + * @ignore + * @param {object} object + * @return {object} + */ + export function createContext(object: object): object; + /** + * Returns `true` if `object` is a "context" object. + * @param {object} + * @return {boolean} + */ + export function isContext(object: any): boolean; /** * A container for a context worker message channel that looks like a "worker". * @ignore @@ -8699,13 +8915,21 @@ declare module "socket:vm" { * `Script` instance. */ export class Reference { + /** + * Predicate function to determine if a `value` is an internal or external + * script reference value. + * @param {amy} value + * @return {boolean} + */ + static isReference(value: amy): boolean; /** * `Reference` class constructor. * @param {string} id * @param {any} value * @param {object=} [context] + * @param {object=} [options] */ - constructor(id: string, value: any, context?: object | undefined); + constructor(id: string, value: any, context?: object | undefined, options?: object | undefined); /** * The unique id of the reference * @type {string} @@ -8722,6 +8946,11 @@ declare module "socket:vm" { * @type {any?} */ get value(): any; + /** + * The name of the type. + * @type {string?} + */ + get name(): string; /** * The `Script` this value belongs to, if available. * @type {Script?} @@ -8732,6 +8961,23 @@ declare module "socket:vm" { * @type {object?} */ get context(): any; + /** + * A boolean value to indicate if the underlying reference value is an + * intrinsic value. + * @type {boolean} + */ + get isIntrinsic(): boolean; + /** + * A boolean value to indicate if the underlying reference value is an + * external reference value. + * @type {boolean} + */ + get isExternal(): boolean; + /** + * The intrinsic type this reference may be an instance of or directly refer to. + * @type {function|object} + */ + get intrinsicType(): any; /** * Releases strongly held value and weak references * to the "context object". @@ -8745,12 +8991,9 @@ declare module "socket:vm" { __vmScriptReference__: boolean; id: string; type: "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; - value: any; - } | { - __vmScriptReference__: boolean; - id: string; - type: "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; - value?: undefined; + name: string; + isIntrinsic: boolean; + intrinsicType: string; }; #private; } @@ -8843,6 +9086,7 @@ declare module "socket:vm" { export { runInThisContext }; export { Script }; export { createContext }; + export { isContext }; } export default _default; export type ScriptOptions = { @@ -8851,179 +9095,6 @@ declare module "socket:vm" { }; import { SharedWorker } from "socket:worker"; } -declare module "socket:worker_threads/init" { - export const SHARE_ENV: unique symbol; - export const isMainThread: boolean; - export namespace state { - export { isMainThread }; - export let parentPort: any; - export let mainPort: any; - export let workerData: any; - export let url: any; - export let env: {}; - export let id: number; - } - namespace _default { - export { state }; - } - export default _default; -} -declare module "socket:worker_threads" { - /** - * Set shared worker environment data. - * @param {string} key - * @param {any} value - */ - export function setEnvironmentData(key: string, value: any): void; - /** - * Get shared worker environment data. - * @param {string} key - * @return {any} - */ - export function getEnvironmentData(key: string): any; - /** - * A pool of known worker threads. - * @type {<Map<string, Worker>} - */ - export const workers: <Map_1>() => <string, Worker_1>() => any; - /** - * `true` if this is the "main" thread, otherwise `false` - * The "main" thread is the top level webview window. - * @type {boolean} - */ - export const isMainThread: boolean; - /** - * The main thread `MessagePort` which is `null` when the - * current context is not the "main thread". - * @type {MessagePort?} - */ - export const mainPort: MessagePort | null; - /** - * A worker thread `BroadcastChannel` class. - */ - export class BroadcastChannel extends globalThis.BroadcastChannel { - } - /** - * A worker thread `MessageChannel` class. - */ - export class MessageChannel extends globalThis.MessageChannel { - } - /** - * A worker thread `MessagePort` class. - */ - export class MessagePort extends globalThis.MessagePort { - } - /** - * The current unique thread ID. - * @type {number} - */ - export const threadId: number; - /** - * The parent `MessagePort` instance - * @type {MessagePort?} - */ - export const parentPort: MessagePort | null; - /** - * Transferred "worker data" when creating a new `Worker` instance. - * @type {any?} - */ - export const workerData: any | null; - /** - * @typedef {{ - * env?: object, - * stdin?: boolean = false, - * stdout?: boolean = false, - * stderr?: boolean = false, - * workerData?: any, - * transferList?: any[], - * eval?: boolean = false - * }} WorkerOptions - - /** - * A worker thread that can communicate directly with a parent thread, - * share environment data, and process streamed data. - */ - export class Worker extends EventEmitter { - /** - * `Worker` class constructor. - * @param {string} filename - * @param {WorkerOptions=} [options] - */ - constructor(filename: string, options?: WorkerOptions | undefined); - /** - * Handles incoming worker messages. - * @ignore - * @param {MessageEvent} event - */ - onWorkerMessage(event: MessageEvent): boolean; - /** - * Handles process environment change events - * @ignore - * @param {import('./process.js').ProcessEnvironmentEvent} event - */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; - /** - * The unique ID for this `Worker` thread instace. - * @type {number} - */ - get id(): number; - /** - * A `Writable` standard input stream if `{ stdin: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Writable?} - */ - get stdin(): Writable; - /** - * A `Readable` standard output stream if `{ stdout: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Readable?} - */ - get stdout(): Readable; - /** - * A `Readable` standard error stream if `{ stderr: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Readable?} - */ - get stderr(): Readable; - /** - * Terminates the `Worker` instance - */ - terminate(): void; - #private; - } - namespace _default { - export { Worker }; - export { isMainThread }; - export { parentPort }; - export { setEnvironmentData }; - export { getEnvironmentData }; - export { workerData }; - export { threadId }; - export { SHARE_ENV }; - } - export default _default; - /** - * /** - * A worker thread that can communicate directly with a parent thread, - * share environment data, and process streamed data. - */ - export type WorkerOptions = { - env?: object; - stdin?: boolean; - stdout?: boolean; - stderr?: boolean; - workerData?: any; - transferList?: any[]; - eval?: boolean; - }; - import { EventEmitter } from "socket:events"; - import { Writable } from "socket:stream"; - import { Readable } from "socket:stream"; - import { SHARE_ENV } from "socket:worker_threads/init"; - import init from "socket:worker_threads/init"; - import { env } from "socket:process"; - export { SHARE_ENV, init }; -} declare module "socket:module" { export function isBuiltin(name: any): boolean; /** @@ -9349,6 +9420,7 @@ declare module "socket:module" { runInThisContext: typeof import("socket:vm").runInThisContext; Script: typeof import("socket:vm").Script; createContext: typeof import("socket:vm").createContext; + isContext: typeof import("socket:vm").isContext; }; window: typeof window; worker_threads: { @@ -9676,6 +9748,7 @@ declare module "socket:module" { runInThisContext: typeof import("socket:vm").runInThisContext; Script: typeof import("socket:vm").Script; createContext: typeof import("socket:vm").createContext; + isContext: typeof import("socket:vm").isContext; }; window: typeof window; worker_threads: { @@ -10391,17 +10464,6 @@ declare module "socket:internal/timers" { } export default _default; } -declare module "socket:service-worker/state" { - export const channel: BroadcastChannel; - export const state: any; - export default state; -} -declare module "socket:service-worker/instance" { - export function createServiceWorker(currentState?: any, options?: any): any; - export const SHARED_WORKER_URL: URL; - const _default: any; - export default _default; -} declare module "socket:service-worker/registration" { export class ServiceWorkerRegistration { constructor(info: any, serviceWorker: any); diff --git a/api/internal/init.js b/api/internal/init.js index 94d9a5c829..660b5f0697 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -613,7 +613,7 @@ hooks.onLoad(async () => { promise .then((registration) => { if (registration) { - console.log('ServiceWorker registered in preload: %s', scriptURL) + console.info('ServiceWorker registered in preload: %s', scriptURL) } else { console.warn( 'ServiceWorker failed to register in preload: %s', @@ -643,7 +643,6 @@ hooks.onReady(async () => { try { // precache fs.constants await ipc.request('fs.constants', {}, { cache: true }) - await import('../worker.js') await import('../diagnostics.js') await import('../fs/fds.js') await import('../fs/constants.js') diff --git a/api/internal/worker.js b/api/internal/worker.js index 18728cd614..b11b0c5f82 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -221,9 +221,7 @@ export async function onWorkerMessage (event) { } export function addEventListener (eventName, callback, ...args) { - if (eventName === 'message') { - return workerGlobalScopeEventTarget.addEventListener(eventName, callback, ...args) - } else if (eventName === 'connect') { + if (eventName === 'message' || eventName === 'connect') { return workerGlobalScopeEventTarget.addEventListener(eventName, callback, ...args) } else { return worker.addEventListener(eventName, callback, ...args) @@ -231,9 +229,7 @@ export function addEventListener (eventName, callback, ...args) { } export function removeEventListener (eventName, callback, ...args) { - if (eventName === 'message') { - return workerGlobalScopeEventTarget.removeEventListener(eventName, callback, ...args) - } else if (eventName === 'connect') { + if (eventName === 'message' || eventName === 'connect') { return workerGlobalScopeEventTarget.removeEventListener(eventName, callback, ...args) } else { return worker.removeEventListener(eventName, callback, ...args) diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 07fa27fc28..5e3e3be46c 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -1,9 +1,10 @@ -/* global ServiceWorker */ import { SharedWorker } from '../internal/shared-worker.js' import state from './state.js' export const SHARED_WORKER_URL = new URL('./shared-worker.js', import.meta.url) +export const ServiceWorker = globalThis.ServiceWorker + export function createServiceWorker ( currentState = state.serviceWorker.state, options = null diff --git a/api/vm.js b/api/vm.js index f82c3be5c3..dcf51073b7 100644 --- a/api/vm.js +++ b/api/vm.js @@ -40,6 +40,8 @@ const Uint8ArrayPrototype = Uint8Array.prototype const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) const TypedArray = TypedArrayPrototype.constructor +const kContextTag = Symbol('socket.vm.Context') + const VM_WINDOW_INDEX = 47 const VM_WINDOW_TITLE = 'socket:vm' const VM_WINDOW_PATH = `${globalThis.origin}/socket/vm/index.html` @@ -55,6 +57,12 @@ let contextWindow = null // resources created in the script "world" in the VM realm const scripts = new WeakMap() +// A weak mapping of created contexts +const contexts = new WeakMap() + +// a shared context when one is not given +const sharedContext = createContext({}) + // A weak mapping of values to reference objects const references = Object.assign(new WeakMap(), { // A mapping of reference IDs to weakly held `Reference` instances @@ -69,6 +77,27 @@ function isArrayBuffer (object) { return object instanceof ArrayBuffer } +function convertSourceToString (source) { + if (source && typeof source !== 'string') { + if (typeof source.valueOf === 'function') { + source = source.valueOf() + } + + if (typeof source.toString === 'function') { + source = source.toString() + } + } + + if (typeof source !== 'string') { + throw new TypeError( + 'Expecting Script source to be a string ' + + `or a value that can be converted to one. Received: ${source}` + ) + } + + return source +} + /** * @ignore * @param {object[]} transfer @@ -161,7 +190,30 @@ export function applyOutputContextReferences (context) { visitObject(context) function visitObject (object) { - for (const key in object) { + if (object.__vmScriptReference__ && 'value' in object) { + object = object.value + } + + if (!object || typeof object !== 'object') { + return + } + + const keys = new Set(Object.keys(object)) + if ( + object && + typeof object === 'object' && + !(object instanceof Reference) && + Object.getPrototypeOf(object) !== Object.prototype && + Object.getPrototypeOf(object) !== Array.prototype + ) { + for (const key of Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(object)))) { + if (key !== 'constructor') { + keys.add(key) + } + } + } + + for (const key of keys) { if (key.startsWith('__vmScriptReferenceArgs_')) { Reflect.deleteProperty(object, key) continue @@ -243,36 +295,76 @@ export function applyContextDifferences ( Reflect.set(currentContext, key, ref.value) } else if (script) { const container = { - async [key] (...args) { + [key]: function (...args) { + const isConstructorCall = this instanceof container[key] const scriptReferenceArgsKey = `__vmScriptReferenceArgs_${reference.id}__` Reflect.set(contextReference, scriptReferenceArgsKey, args) Reflect.set(contextReference, reference.id, reference) - try { - return await script.runInContext(contextReference, { + const promise = new Promise((resolve, reject) => { + const promise = script.runInContext(contextReference, { mode: 'classic', - source: `globalObject['${reference.id}'](...globalObject['${scriptReferenceArgsKey}'])` + source: `${isConstructorCall ? 'new ' : ''}globalObject['${reference.id}'](...globalObject['${scriptReferenceArgsKey}'])` }) - // eslint-disable-next-line - } catch (err) { - throw err - } finally { + + promise.then(resolve).catch(reject) + }) + + promise.finally(() => { Reflect.deleteProperty(contextReference, reference.id) Reflect.deleteProperty(contextReference, scriptReferenceArgsKey) + }) + + if (!isConstructorCall) { + return promise } + + return new Proxy(function () {}, { + get (target, property, receiver) { + return new Proxy(function () {}, { + apply (target, __, argumentList) { + return promise.then((result) => { + applyContextDifferences(result, result, contextReference) + return result.value[property](...argumentList) + }) + } + }) + }, + apply (target, thisArg, argumentList) { + return promise + .then((result) => typeof result === 'function' + ? result(...args) + : result + ) + .then((result) => typeof result === 'function' + ? isConstructorCall + ? result.call(thisArg, ...argumentList) + : result.bind(thisArg, ...argumentList) + : result + ) + .then((result) => { + applyContextDifferences(result, result, contextReference) + return result + }) + } + }) } } - // bind `null` this for proxy function - const proxyFunction = container[key].bind(null) // wrap into container for named function, called in tail with an // intentional omission of `await` for an async call stack collapse // this preserves naming in `console.log`: // [AsyncFunction: functionName] // while also removing an unneeded tail call in a stack trace const containerForNamedFunction = { - async [key] (...args) { return proxyFunction(...args) } + [key]: function (...args) { + if (this instanceof containerForNamedFunction[key]) { + return new container[key](...args) + } + + return container[key].call(this, ...args) + } } // the reference ID was created on the other side, just use it here @@ -282,8 +374,9 @@ export function applyContextDifferences ( putReference(new Reference( reference.id, containerForNamedFunction[key], - contextReference) - ) + contextReference, + { external: true } + )) // emplace an actual function on `currentContext` at the property // `key` which will do the actual proxy call to the VM script @@ -342,7 +435,15 @@ export function applyContextDifferences ( * @param {object=} [options] */ export function wrapFunctionSource (source, options = null) { - if (source.includes('return') || source.includes(';') || source.includes('throw')) { + source = source.trim() + if ( + source.startsWith('{') || + source.startsWith('async') || + source.startsWith('function') || + source.startsWith('class') + ) { + source = `(${source})` + } else if (source.includes('return') || source.includes(';') || source.includes('throw')) { source = `{ ${source} }` } else if (source.includes('\n')) { const parts = source.trim().split('\n') @@ -511,6 +612,24 @@ export const RESERVED_GLOBAL_INTRINSICS = [ * `Script` instance. */ export class Reference { + /** + * Predicate function to determine if a `value` is an internal or external + * script reference value. + * @param {amy} value + * @return {boolean} + */ + static isReference (value) { + if (references.has(value)) { + return true + } + + if (value?.__vmScriptReference__ === true && typeof value?.id === 'string') { + return true + } + + return false + } + /** * The underlying reference ID. * @ignore @@ -539,13 +658,34 @@ export class Reference { */ #context = null + /** + * A boolean value to indicate if the underlying reference value is an + * intrinsic value. + * @type {boolean} + */ + #isIntrinsic = false + + /** + * The intrinsic type this reference may be an instance of or directly refer to. + * @type {function|object} + */ + #intrinsicType = null + + /** + * A boolean value to indicate if the underlying reference value is an + * external reference value. + * @type {boolean} + */ + #isExternal = false + /** * `Reference` class constructor. * @param {string} id * @param {any} value * @param {object=} [context] + * @param {object=} [options] */ - constructor (id, value, context = null) { + constructor (id, value, context = null, options) { this.#id = id this.#type = value !== null ? typeof value : 'undefined' this.#value = value !== null && value !== undefined @@ -555,6 +695,10 @@ export class Reference { this.#context = context !== null && context !== undefined ? new WeakRef(context) : null + + this.#intrinsicType = getIntrinsicType(this.#value) + this.#isIntrinsic = isIntrinsic(this.#value) + this.#isExternal = options?.external === true } /** @@ -582,6 +726,26 @@ export class Reference { return this.#value } + /** + * The name of the type. + * @type {string?} + */ + get name () { + if (this.type === 'function') { + return this.value.name + } + + if (this.value && this.type === 'object') { + const prototype = Reflect.getPrototypeOf(this.value) + + if (prototype.constructor.name) { + return prototype.constructor.name + } + } + + return null + } + /** * The `Script` this value belongs to, if available. * @type {Script?} @@ -598,6 +762,32 @@ export class Reference { return this.#context?.deref?.() ?? null } + /** + * A boolean value to indicate if the underlying reference value is an + * intrinsic value. + * @type {boolean} + */ + get isIntrinsic () { + return this.#isIntrinsic + } + + /** + * A boolean value to indicate if the underlying reference value is an + * external reference value. + * @type {boolean} + */ + get isExternal () { + return this.#isExternal + } + + /** + * The intrinsic type this reference may be an instance of or directly refer to. + * @type {function|object} + */ + get intrinsicType () { + return this.#intrinsicType + } + /** * Releases strongly held value and weak references * to the "context object". @@ -612,13 +802,22 @@ export class Reference { * @param {boolean=} [includeValue = false] */ toJSON (includeValue = false) { - const { value, type, id } = this + const { isIntrinsic, value, name, type, id } = this + const intrinsicType = getIntrinsicTypeString(this.intrinsicType) + const json = { + __vmScriptReference__: true, + id, + type, + name, + isIntrinsic, + intrinsicType + } if (includeValue) { - return { __vmScriptReference__: true, id, type, value } + json.value = value } - return { __vmScriptReference__: true, id, type } + return json } } @@ -664,7 +863,7 @@ export class Script extends EventTarget { this.#id = crypto.randomBytes(8).toString('base64') this.#source = source - this.#context = options?.context ?? null + this.#context = options?.context ?? {} if (typeof options?.filename === 'string' && options.filename) { this.#filename = options.filename @@ -747,7 +946,7 @@ export class Script extends EventTarget { async runInContext (context, options = null) { await this.ready - const contextReference = context ?? this.context + const contextReference = createContext(context ?? this.context) context = { ...(context ?? this.#context) } const filename = options?.filename || this.filename @@ -792,9 +991,26 @@ export class Script extends EventTarget { if (event.data.err) { reject(maybeMakeError(event.data.err)) } else { - const result = { data: event.data.data } - applyContextDifferences(result, event.data, contextReference) - resolve(result.data) + const { data } = event + const result = { data: data.data } + // check if result data is an external reference + const isReference = Reference.isReference(result.data) + const name = isReference ? result.data.name : null + + if (name) { + result[name] = result.data + data[name] = data.data + delete data.data + delete result.data + } + + applyContextDifferences(result, data, contextReference) + + if (name) { + resolve(result[name]) + } else { + resolve(result.data) + } } } } @@ -856,9 +1072,26 @@ export class Script extends EventTarget { if (event.data.err) { reject(maybeMakeError(event.data.err)) } else { - const result = { data: event.data.data } - applyContextDifferences(result, event.data, contextReference) - resolve(result.data) + const { data } = event + const result = { data: data.data } + // check if result data is an external reference + const isReference = Reference.isReference(result.data) + const name = isReference ? result.data.name : null + + if (name) { + result[name] = result.data + data[name] = data.data + delete data.data + delete result.data + } + + applyContextDifferences(result, data, contextReference) + + if (name) { + resolve(result[name]) + } else { + resolve(result.data) + } } } } @@ -1121,6 +1354,93 @@ export function createIntrinsics (options) { return Object.create(null, descriptors) } +/** + * Returns `true` if value is an intrinsic, otherwise `false`. + * @param {any} value + * @return {boolean} + */ +export function isIntrinsic (value) { + if (value === undefined) { + return true + } + + if (value === null) { + return null + } + + for (const key of RESERVED_GLOBAL_INTRINSICS) { + const intrinsic = globalThis[key] + if (intrinsic === value) { + return true + } else if (typeof intrinsic === 'function' && typeof value === 'object') { + const prototype = Object.getPrototypeOf(value) + if (prototype === intrinsic.prototype) { + return true + } + } + } + + return false +} + +/** + * Get the intrinsic type of a given `value`. + * @param {any} + * @return {function|object|null|undefined} + */ +export function getIntrinsicType (value) { + if (value === undefined) { + return undefined + } + + if (value === null) { + return null + } + + for (const key of RESERVED_GLOBAL_INTRINSICS) { + const intrinsic = globalThis[key] + if (intrinsic === value) { + return intrinsic + } else if (typeof intrinsic === 'function' && typeof value === 'object') { + const prototype = Object.getPrototypeOf(value) + if (prototype === intrinsic.prototype) { + return intrinsic + } + } + } + + return undefined +} + +/** + * Get the intrinsic type string of a given `value`. + * @param {any} + * @return {string|null} + */ +export function getIntrinsicTypeString (value) { + if (value === null) { + return null + } + + if (value === undefined) { + return 'undefined' + } + + for (const key of RESERVED_GLOBAL_INTRINSICS) { + const intrinsic = globalThis[key] + if (intrinsic === value) { + return key + } else if (typeof intrinsic === 'function' && typeof value === 'object') { + const prototype = Object.getPrototypeOf(value) + if (prototype === intrinsic.prototype) { + return key + } + } + } + + return null +} + /** * Creates a global proxy object for context execution. * @ignore @@ -1197,7 +1517,6 @@ export function createGlobalObject (context, options) { if (RESERVED_GLOBAL_INTRINSICS.includes(property)) { return true } - console.log('defineProperty', { property }) if (context) { return ( @@ -1225,7 +1544,6 @@ export function createGlobalObject (context, options) { }, getOwnPropertyDescriptor (_, property) { - console.log('getOwnPropertyDescriptor', { property }) if (context) { const descriptor = Reflect.getOwnPropertyDescriptor(context, property) if (descriptor) { @@ -1278,7 +1596,6 @@ export function createGlobalObject (context, options) { }, preventExtensions (_) { - console.log('preventExtensions') if (context) { Reflect.preventExtensions(context) return true @@ -1326,6 +1643,8 @@ export function detectFunctionSourceType (source) { * @return {function} */ export function compileFunction (source, options = null) { + source = convertSourceToString(source) + options = { ...options } // detect source type naively if (!options?.type) { @@ -1377,19 +1696,25 @@ export function compileFunction (source, options = null) { * context is preserved until the `context` object that points to it is * garbage collected or there are no longer any references to it and its * associated `Script` instance. - * @param {string} source + * @param {string|object|function} source * @param {ScriptOptions=} [options] * @param {object=} [context] * @return {Promise<any>} */ export async function runInContext (source, options, context) { - const script = scripts.get(options?.context ?? context) ?? new Script(source, options) + source = convertSourceToString(source) + context = options?.context ?? context ?? sharedContext - if (options?.context ?? context) { - scripts.set(options?.context ?? context, script) - } + const script = scripts.get(context) ?? new Script(source, options) - return await script.runInContext(options?.context ?? context, { ...options, source }) + scripts.set(context, script) + + const result = await script.runInContext(context, { + ...options, + source + }) + + return result } /** @@ -1401,8 +1726,11 @@ export async function runInContext (source, options, context) { * @return {Promise<any>} */ export async function runInNewContext (source, options, context) { + source = convertSourceToString(source) + context = options?.context ?? context ?? {} const script = new Script(source, options) - const result = await script.runInNewContext(options.context ?? context, options) + scripts.set(script.context, script) + const result = await script.runInNewContext(context, options) await script.destroy() return result } @@ -1414,6 +1742,8 @@ export async function runInNewContext (source, options, context) { * @return {Promise<any>} */ export async function runInThisContext (source, options) { + source = convertSourceToString(source) + const script = new Script(source, options) const result = await script.runInThisContext(options) await script.destroy() @@ -1442,11 +1772,12 @@ export function putReference (reference) { * Create a `Reference` for a `value` in a script `context`. * @param {any} value * @param {object} context + * @param {object=} [options] * @return {Reference} */ -export function createReference (value, context) { +export function createReference (value, context, options = null) { const id = crypto.randomBytes(8).toString('base64') - const reference = new Reference(id, value, context) + const reference = new Reference(id, value, context, options) putReference(reference) return reference } @@ -1483,8 +1814,28 @@ export function getTrasferables (object) { return transferables } +/** + * @ignore + * @param {object} object + * @return {object} + */ export function createContext (object) { - return Object.assign(new EventTarget(), object) + if (isContext(object)) { + return object + } else if (object && typeof object === 'object') { + contexts.set(object, kContextTag) + } + + return object +} + +/** + * Returns `true` if `object` is a "context" object. + * @param {object} + * @return {boolean} + */ +export function isContext (object) { + return contexts.has(object) } export default { @@ -1502,5 +1853,6 @@ export default { runInNewContext, runInThisContext, Script, - createContext + createContext, + isContext } diff --git a/api/vm/init.js b/api/vm/init.js index 648cd912c3..145746e6a6 100644 --- a/api/vm/init.js +++ b/api/vm/init.js @@ -14,7 +14,7 @@ class World extends EventTarget { this.id = id this.frame = createWorld({ id }) this.ready = new Promise((resolve) => { - this.frame.addEventListener('load', resolve, { once: true }) + this.frame.contentWindow.addEventListener('load', resolve, { once: true }) }) } @@ -137,7 +137,10 @@ class State { } onWorkerMessageError (event) { - console.error('onWorkerMessageError', event) + globalThis.reportError( + event.error ?? + new Error('An unknown VM worker error occurred', { cause: event }) + ) } } diff --git a/api/vm/world.js b/api/vm/world.js index bfa3a04db3..274ae65d69 100644 --- a/api/vm/world.js +++ b/api/vm/world.js @@ -101,9 +101,17 @@ globalThis.addEventListener('message', async (event) => { } if (typeof result === 'function') { - result = vm.createReference(result, context) - } else if (typeof result === 'object') { - vm.applyOutputContextReferences(result) + result = vm.createReference(result, context).toJSON() + } else if (result && typeof result === 'object') { + if ( + Object.getPrototypeOf(result) === Object.prototype || + result instanceof Array + ) { + vm.applyOutputContextReferences(result) + } else { + vm.applyOutputContextReferences(result) + result = vm.createReference(result, context).toJSON(true) + } } vm.applyOutputContextReferences(context) diff --git a/api/worker.js b/api/worker.js index 0e8db46c43..196ba9aa37 100644 --- a/api/worker.js +++ b/api/worker.js @@ -1,24 +1,6 @@ -import SharedWorker from './internal/shared-worker.js' -import console from './console.js' +import { ServiceWorker } from './service-worker/instance.js' +import { SharedWorker } from './internal/shared-worker.js' +import { Worker } from './worker_threads.js' -export { SharedWorker } - -// eslint-disable-next-line no-new-func -const GlobalWorker = new Function('return this.Worker')() - -class UnsupportedWorker extends EventTarget { - constructor () { - super() - console.warn('Worker is not supported in this environment') - } -} - -/** - * @type {import('dom').Worker} - */ -export const Worker = GlobalWorker || UnsupportedWorker - -/** - * @type {import('dom').SharedWorker} - */ +export { SharedWorker, ServiceWorker, Worker } export default Worker diff --git a/api/worker_threads/init.js b/api/worker_threads/init.js index 4afe598aa4..164384160b 100644 --- a/api/worker_threads/init.js +++ b/api/worker_threads/init.js @@ -26,34 +26,36 @@ export const state = { id: 0 } -process.exit = (code) => { - globalThis.postMessage({ - worker_threads: { process: { exit: { code } } } - }) -} +if (!isMainThread) { + process.exit = (code) => { + globalThis.postMessage({ + worker_threads: { process: { exit: { code } } } + }) + } -globalThis.addEventListener('message', onMainThreadMessage) -globalThis.addEventListener('error', (event) => { - propagateWorkerError( - event.error ?? - new Error( - event.reason?.message ?? - event.reason ?? - 'An unknown error occurred' + globalThis.addEventListener('message', onMainThreadMessage) + globalThis.addEventListener('error', (event) => { + propagateWorkerError( + event.error ?? + new Error( + event.reason?.message ?? + event.reason ?? + 'An unknown error occurred' + ) ) - ) -}) - -globalThis.addEventListener('unhandledrejection', (event) => { - propagateWorkerError( - event.error ?? - new Error( - event.reason?.message ?? - event.reason ?? - 'An unknown error occurred' + }) + + globalThis.addEventListener('unhandledrejection', (event) => { + propagateWorkerError( + event.error ?? + new Error( + event.reason?.message ?? + event.reason ?? + 'An unknown error occurred' + ) ) - ) -}) + }) +} function propagateWorkerError (err) { globalThis.postMessage({ @@ -184,7 +186,7 @@ function onMainThreadMessage (event) { process.stdin.push(request.process.stdin.data) } - if (request) { + if (event.data?.worker_threads) { event.stopImmediatePropagation() return false } diff --git a/test/src/index.js b/test/src/index.js index 275a8a628c..001a8d148f 100644 --- a/test/src/index.js +++ b/test/src/index.js @@ -25,3 +25,4 @@ import './router-resolution.js' import './mime.js' import './application-url-event.js' import './webassembly.js' +import './vm.js' diff --git a/test/src/vm.js b/test/src/vm.js new file mode 100644 index 0000000000..3cdf6baa9d --- /dev/null +++ b/test/src/vm.js @@ -0,0 +1,96 @@ +import test from 'socket:test' +import vm from 'socket:vm' + +test('vm.runInContext(source) - simple', async (t) => { + t.equal(await vm.runInContext('1 + 2 + 3'), 6, 'vm.runInContext("1 + 2 + 3")') + t.deepEqual( + await vm.runInContext('{ number: 123 }'), + { number: 123 }, + 'vm.runInContext("{ number: 123 }")' + ) +}) + +test('vm.runInContext(source) - script refererences', async (t) => { + async function identity (...args) { + return args + } + + const result = await vm.runInContext(identity) + t.deepEqual(await result(1, 2, 3), [1, 2, 3], 'function reference returns value') + + const FunctionReference = await vm.runInContext('Function') + const fn = new FunctionReference('return 123') + + t.equal(123, await fn(), 'Function constructor with new call') + + const Class = await vm.runInContext('class Class { method (...args) { return args } }') + t.equal('function', typeof Class, 'Class constructor') + + const instance = new Class() + + t.deepEqual([1, 2, 3], await instance.method(1, 2, 3), 'class instance method') +}) + +test('vm.runInContext(source, context) - context', async (t) => { + const context = { + key: 'value', + functions: {} + } + + const value = await vm.runInContext('key', { context }) + t.equal(context.key, value, 'context.key === value') + + await vm.runInContext('key = "other value"', { context }) + t.equal(context.key, 'other value', 'context.key === "other value"') + + await vm.runInContext('functions.hello = () => "hello world"', { context }) + t.equal('function', typeof context.functions.hello, 'typeof context.function.hello === "function"') + t.equal('hello world', await context.functions.hello(), 'await context.function.hello() === "hello world"') +}) + +test('vm.runInContext(source, context) - transferables', async (t) => { + const channel = new MessageChannel() + const context = { + buffer: new TextEncoder().encode('hello world'), + scope: {} + } + + await vm.runInContext(` + let port = null + + scope.setMessagePort = (value) => { + port = value + port.onmessage = (event) => port.postMessage(event.data) + } + + scope.decoded = new TextDecoder().decode(buffer) + `, { context }) + + await context.scope.setMessagePort(channel.port2) + t.equal('hello world', context.scope.decoded, 'context.scope.decoded === "hello world"') + channel.port1.postMessage(context.scope.decoded) + await new Promise((resolve) => { + channel.port1.onmessage = (event) => { + t.equal('hello world', event.data, 'port1 message is "hello world"') + resolve() + } + }) +}) + +test('vm.runInContext(source, context) - ESM', async (t) => { + const module = await vm.runInContext(` + const storage = {} + export function set (key, value) { + storage[key] = value + } + + export function get (key) { + return storage[key] + } + `) + + t.equal('function', typeof module.set, 'typeof module.set === "function"') + t.equal('function', typeof module.get, 'typeof module.set === "function"') + await module.set('key', 'value') + t.equal('value', await module.get('key'), 'module.get("key") === "value"') +}) From 8763f9684974017c156e67511809bd09766a5af9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Feb 2024 21:51:04 +0100 Subject: [PATCH 0034/1178] fix(vm): fix typo: --- api/index.d.ts | 8 ++++---- api/vm.js | 4 ++-- api/worker_threads.js | 8 ++++---- api/worker_threads/init.js | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index d69bc76d07..3556772f14 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -8872,7 +8872,7 @@ declare module "socket:vm" { * @param {object} object * @return {object[]} */ - export function getTrasferables(object: object): object[]; + export function getTransferables(object: object): object[]; /** * @ignore * @param {object} object @@ -9077,7 +9077,7 @@ declare module "socket:vm" { export { getContextWindow }; export { getContextWorker }; export { getReference }; - export { getTrasferables }; + export { getTransferables }; export { putReference }; export { Reference }; export { removeReference }; @@ -9411,7 +9411,7 @@ declare module "socket:module" { getContextWindow: typeof import("socket:vm").getContextWindow; getContextWorker: typeof import("socket:vm").getContextWorker; getReference: typeof import("socket:vm").getReference; - getTrasferables: typeof import("socket:vm").getTrasferables; + getTransferables: typeof import("socket:vm").getTransferables; putReference: typeof import("socket:vm").putReference; Reference: typeof import("socket:vm").Reference; removeReference: typeof import("socket:vm").removeReference; @@ -9739,7 +9739,7 @@ declare module "socket:module" { getContextWindow: typeof import("socket:vm").getContextWindow; getContextWorker: typeof import("socket:vm").getContextWorker; getReference: typeof import("socket:vm").getReference; - getTrasferables: typeof import("socket:vm").getTrasferables; + getTransferables: typeof import("socket:vm").getTransferables; putReference: typeof import("socket:vm").putReference; Reference: typeof import("socket:vm").Reference; removeReference: typeof import("socket:vm").removeReference; diff --git a/api/vm.js b/api/vm.js index dcf51073b7..dd27cf4056 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1808,7 +1808,7 @@ export function removeReference (id) { * @param {object} object * @return {object[]} */ -export function getTrasferables (object) { +export function getTransferables (object) { const transferables = [] findMessageTransfers(transferables, object) return transferables @@ -1845,7 +1845,7 @@ export default { getContextWindow, getContextWorker, getReference, - getTrasferables, + getTransferables, putReference, Reference, removeReference, diff --git a/api/worker_threads.js b/api/worker_threads.js index a9debf507b..03ccae7ad5 100644 --- a/api/worker_threads.js +++ b/api/worker_threads.js @@ -1,6 +1,6 @@ import { Writable, Readable } from './stream.js' import init, { SHARE_ENV } from './worker_threads/init.js' -import { getTrasferables } from './vm.js' +import { getTransferables } from './vm.js' import { maybeMakeError } from './ipc.js' import { EventEmitter } from './events.js' import { env } from './process.js' @@ -77,7 +77,7 @@ export function setEnvironmentData (key, value) { init.state.env[key] = value for (const worker of workers.values()) { - const transfer = getTrasferables(value) + const transfer = getTransferables(value) worker.postMessage({ worker_threads: { env: { key, value } @@ -139,7 +139,7 @@ export class Worker extends EventEmitter { if (options.stdin === true) { this.#stdin = new Writable({ write (data, cb) { - const transfer = getTrasferables(data) + const transfer = getTransferables(data) this.#worker.postMessage( { worker_threads: { stdin: { data } } }, { transfer } @@ -162,7 +162,7 @@ export class Worker extends EventEmitter { this.#worker.addEventListener('message', this.onWorkerMessage) if (options.workerData) { - const transfer = options.transferList ?? getTrasferables(options.workerData) + const transfer = options.transferList ?? getTransferables(options.workerData) const message = { worker_threads: { workerData: options.workerData } } diff --git a/api/worker_threads/init.js b/api/worker_threads/init.js index 164384160b..c9a0d539ce 100644 --- a/api/worker_threads/init.js +++ b/api/worker_threads/init.js @@ -1,4 +1,4 @@ -import vm, { getTrasferables } from '../vm.js' +import vm, { getTransferables } from '../vm.js' import { Writable, Readable } from '../stream.js' import process, { env } from '../process.js' @@ -125,7 +125,7 @@ function onMainThreadMessage (event) { if (request.init.process?.stdout === true) { process.stdout = new Writable({ write (data, cb) { - const transfer = getTrasferables(data) + const transfer = getTransferables(data) globalThis.postMessage({ worker_threads: { process: { @@ -142,7 +142,7 @@ function onMainThreadMessage (event) { if (request.init.process?.stderr === true) { process.stderr = new Writable({ write (data, cb) { - const transfer = getTrasferables(data) + const transfer = getTransferables(data) globalThis.postMessage({ worker_threads: { process: { From f2f9eb3739b5bfead5b2d3dd13a0a51b675e36be Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Feb 2024 22:02:18 +0100 Subject: [PATCH 0035/1178] chore(process,os): move 'process.homedir' to 'os.homedir' --- api/README.md | 46 ++++++++--------- api/index.d.ts | 117 ++++++++++++++++++++++---------------------- api/os.js | 11 ++++- api/process.js | 11 ----- test/src/process.js | 4 -- 5 files changed, 92 insertions(+), 97 deletions(-) diff --git a/api/README.md b/api/README.md index 1ee604383f..2da466b967 100644 --- a/api/README.md +++ b/api/README.md @@ -1603,7 +1603,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide import { arch, platform } from 'socket:os' ``` -## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L56) +## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L57) Returns the operating system CPU architecture for which Socket was compiled. @@ -1611,7 +1611,7 @@ Returns the operating system CPU architecture for which Socket was compiled. | :--- | :--- | :--- | | Not specified | string | 'arm64', 'ia32', 'x64', or 'unknown' | -## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L74) +## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L75) External docs: https://nodejs.org/api/os.html#os_os_cpus Returns an array of objects containing information about each CPU/core. @@ -1629,7 +1629,7 @@ Returns an array of objects containing information about each CPU/core. | :--- | :--- | :--- | | cpus | Array<object> | An array of objects containing information about each CPU/core. | -## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L98) +## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L99) External docs: https://nodejs.org/api/os.html#os_os_networkinterfaces Returns an object containing network interfaces that have been assigned a network address. @@ -1647,7 +1647,7 @@ Returns an object containing network interfaces that have been assigned a networ | :--- | :--- | :--- | | Not specified | object | An object containing network interfaces that have been assigned a network address. | -## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L186) +## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L187) External docs: https://nodejs.org/api/os.html#os_os_platform Returns the operating system platform. @@ -1657,7 +1657,7 @@ Returns the operating system platform. | :--- | :--- | :--- | | Not specified | string | 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' | -## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L195) +## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L196) External docs: https://nodejs.org/api/os.html#os_os_type Returns the operating system name. @@ -1666,7 +1666,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' | -## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L234) +## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L235) @@ -1674,7 +1674,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | boolean | `true` if the operating system is Windows. | -## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L246) +## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L247) @@ -1682,15 +1682,15 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system's default directory for temporary files. | -## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L294) +## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L295) The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. -## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L306) +## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L307) Get resource usage. -## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L316) +## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L317) Returns the system uptime in seconds. @@ -1698,7 +1698,7 @@ Returns the system uptime in seconds. | :--- | :--- | :--- | | Not specified | number | The system uptime in seconds. | -## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L327) +## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L328) Returns the operating system name. @@ -1706,6 +1706,14 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system name. | +## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L386) + +Returns the home directory of the current user. + +| Return Value | Type | Description | +| :--- | :--- | :--- | +| Not specified | string | | + <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> @@ -1956,7 +1964,7 @@ This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L137) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L133) Adds callback to the 'nextTick' queue. @@ -1964,15 +1972,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L166) - - - -| Return Value | Type | Description | -| :--- | :--- | :--- | -| Not specified | string | The home directory of the current user. | - -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L175) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L164) Computed high resolution time as a `BigInt`. @@ -1984,7 +1984,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L201) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L190) @@ -1992,7 +1992,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L213) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L202) Returns an object describing the memory usage of the Node.js process measured in bytes. diff --git a/api/index.d.ts b/api/index.d.ts index 3556772f14..4db5ceeb40 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -931,6 +931,60 @@ declare module "socket:events" { import * as exports from "socket:events"; } +declare module "socket:path/well-known" { + /** + * Well known path to the user's "Downloads" folder. + * @type {?string} + */ + export const DOWNLOADS: string | null; + /** + * Well known path to the user's "Documents" folder. + * @type {?string} + */ + export const DOCUMENTS: string | null; + /** + * Well known path to the user's "Pictures" folder. + * @type {?string} + */ + export const PICTURES: string | null; + /** + * Well known path to the user's "Desktop" folder. + * @type {?string} + */ + export const DESKTOP: string | null; + /** + * Well known path to the user's "Videos" folder. + * @type {?string} + */ + export const VIDEOS: string | null; + /** + * Well known path to the user's "Music" folder. + * @type {?string} + */ + export const MUSIC: string | null; + /** + * Well known path to the application's "resources" folder. + * @type {?string} + */ + export const RESOURCES: string | null; + /** + * Well known path to the application's "home" folder. + * This may be the user's HOME directory or the application container sandbox. + * @type {?string} + */ + export const HOME: string | null; + namespace _default { + export { DOWNLOADS }; + export { DOCUMENTS }; + export { RESOURCES }; + export { PICTURES }; + export { DESKTOP }; + export { VIDEOS }; + export { MUSIC }; + export { HOME }; + } + export default _default; +} declare module "socket:os" { /** * Returns the operating system CPU architecture for which Socket was compiled. @@ -1027,6 +1081,11 @@ declare module "socket:os" { * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + /** + * Returns the home directory of the current user. + * @return {string} + */ + export function homedir(): string; /** * @type {string} * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. @@ -1042,10 +1101,6 @@ declare module "socket:process" { * @param {Function} callback */ export function nextTick(callback: Function): void; - /** - * @returns {string} The home directory of the current user. - */ - export function homedir(): string; /** * Computed high resolution time as a `BigInt`. * @param {Array<number>?} [time] @@ -1277,60 +1332,6 @@ declare module "socket:path/path" { }); import { URL } from "socket:url/index"; } -declare module "socket:path/well-known" { - /** - * Well known path to the user's "Downloads" folder. - * @type {?string} - */ - export const DOWNLOADS: string | null; - /** - * Well known path to the user's "Documents" folder. - * @type {?string} - */ - export const DOCUMENTS: string | null; - /** - * Well known path to the user's "Pictures" folder. - * @type {?string} - */ - export const PICTURES: string | null; - /** - * Well known path to the user's "Desktop" folder. - * @type {?string} - */ - export const DESKTOP: string | null; - /** - * Well known path to the user's "Videos" folder. - * @type {?string} - */ - export const VIDEOS: string | null; - /** - * Well known path to the user's "Music" folder. - * @type {?string} - */ - export const MUSIC: string | null; - /** - * Well known path to the application's "resources" folder. - * @type {?string} - */ - export const RESOURCES: string | null; - /** - * Well known path to the application's "home" folder. - * This may be the user's HOME directory or the application container sandbox. - * @type {?string} - */ - export const HOME: string | null; - namespace _default { - export { DOWNLOADS }; - export { DOCUMENTS }; - export { RESOURCES }; - export { PICTURES }; - export { DESKTOP }; - export { VIDEOS }; - export { MUSIC }; - export { HOME }; - } - export default _default; -} declare module "socket:path/win32" { /** * Computes current working directory for a path diff --git a/api/os.js b/api/os.js index cff6db835c..43ea323bfb 100644 --- a/api/os.js +++ b/api/os.js @@ -10,8 +10,9 @@ * ``` */ -import { toProperCase } from './util.js' import ipc, { primordials } from './ipc.js' +import { toProperCase } from './util.js' +import { HOME } from './path/well-known.js' const UNKNOWN = 'unknown' @@ -378,6 +379,14 @@ export function host () { return primordials['host-operating-system'] || 'unknown' } +/** + * Returns the home directory of the current user. + * @return {string} + */ +export function homedir () { + return globalThis.__args.env.HOME ?? HOME ?? '' +} + // eslint-disable-next-line import * as exports from './os.js' export default exports diff --git a/api/process.js b/api/process.js index b0fb28fb83..fac85a9abf 100644 --- a/api/process.js +++ b/api/process.js @@ -102,10 +102,6 @@ class Process extends EventEmitter { return exit(code) } - homedir () { - return homedir() - } - nextTick (callback) { return nextTick(callback) } @@ -160,13 +156,6 @@ if (typeof process.nextTick !== 'function') { process.nextTick = nextTick } -/** - * @returns {string} The home directory of the current user. - */ -export function homedir () { - return globalThis.__args.env.HOME ?? '' -} - /** * Computed high resolution time as a `BigInt`. * @param {Array<number>?} [time] diff --git a/test/src/process.js b/test/src/process.js index a47b9293cd..ce8e301242 100644 --- a/test/src/process.js +++ b/test/src/process.js @@ -7,10 +7,6 @@ test('process', (t) => { t.ok(typeof process.addListener === 'function', 'process is an EventEmitter') }) -test('process.homedir()', (t) => { - t.ok(typeof process.homedir() === 'string', 'process.homedir() returns a string') -}) - test('process.exit()', (t) => { t.ok(typeof process.exit === 'function', 'process.exit() is a function') }) From 01a84d9ab3414a1ce6252577cb251e366f5bb221 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Feb 2024 22:08:11 +0100 Subject: [PATCH 0036/1178] test(os): fix typo --- test/src/os.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/src/os.js b/test/src/os.js index ae7f3e43dc..f4e0be48db 100644 --- a/test/src/os.js +++ b/test/src/os.js @@ -95,3 +95,7 @@ test('os.EOL', (t) => { t.equal(os.EOL, '\n') } }) + +test('os.homedir()', (t) => { + t.ok(typeof os.homedir() === 'string', 'os.homedir() returns a string') +}) From 39d8ba50ec5999f6e8d30cd285539d2aa7c58866 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 19 Feb 2024 23:50:28 +0100 Subject: [PATCH 0037/1178] refactor(api): initial commit, basic structure for child_process --- api/child_process.js | 80 ++++++++++++++++++++ api/child_process/worker.js | 54 ++++++++++++++ api/worker_threads.js | 8 ++ src/core/child_process.cc | 141 ++++++++++++++++++++++++++++++++++++ src/core/core.hh | 12 +++ src/ipc/bridge.cc | 44 +++++++++++ src/process/process.hh | 2 +- 7 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 api/child_process.js create mode 100644 api/child_process/worker.js create mode 100644 src/core/child_process.cc diff --git a/api/child_process.js b/api/child_process.js new file mode 100644 index 0000000000..ab113fb899 --- /dev/null +++ b/api/child_process.js @@ -0,0 +1,80 @@ +import events from './events.js' +import ipc from './ipc.js' +import { Worker } from './worker_threads.js' +import { rand64 } from '../crypto.js' + +class ChildProcess extends events.EventEmitter { + constructor (options) { + super() + + // 'close' + // 'disconnect' + // 'error' + // 'exit' + // 'message' + // 'spawn' * + + this.#id = rand64() + + this.#state = { + killed: false, + signalCode: null, + exitCode: null, + spawnfile: null, + pid: 0 + } + + this.#worker = new Worker('./child_process/worker.js', { + env: options.env, + stdin: true, + stdout: true, + stderr: true, + workerData: { id: this.#id } + }) + + this.#worker.on('message', data => { + if (data.method === 'kill' && args[0] === true) { + this.#killed = true + } + + if (data.method === 'state') { + if (this.#state.pid !== args[0].pid) this.emit('spawn') + Object.assign(this.#state, args[0]) + } + }) + + this.#worker.on('error', err => { + this.emit('error', err) + }) + } + + get stdin () { + this.#worker.stdin + } + + get stdout () { + this.#worker.stdout + } + + get stderr () { + this.#worker.stderr + } + + kill (...args) { + this.#worker.postMessage({ id: this.#id, method: 'kill', args }) + } + + spawn (...args) { + this.#worker.postMessage({ id: this.#id, method: 'spawn', args }) + } +} + +function spawn (command, args, options) { + const child = new ChildProcess(options) + child.spawn(command, args) + // TODO signal + // TODO timeout + return child +} + +export { spawn } diff --git a/api/child_process/worker.js b/api/child_process/worker.js new file mode 100644 index 0000000000..de0b05da30 --- /dev/null +++ b/api/child_process/worker.js @@ -0,0 +1,54 @@ +import ipc from '../ipc.js' +import { isMainThread, parentPort } from '../worker_threads.js' +import process from 'socket:process' + +const state = {} + +const propagateWorkerError = err => parentPort.postMessage({ + worker_threads: { + error: { + name: err.name, + message: err.message, + stack: err.stack, + type: err.name + } + } +}) + +parentPort.onmessage = ({ data: { id, method, args } }) => { + if (method === 'spawn') { + const command = args[0] + const argv = args[1] + const opts = args[2] + + const params = { + args: Array.from(argv ?? []).join('\u0001'), + command, + cwd: opts?.cwd + } + + const { data, err } = await ipc.send('child_process.spawn', params) + + if (err) return propagateWorkerError(err) + + state.id = data.id + state.pid = data.pid + + parentPort.postMessage({ method: 'state', args: [state] }) + + globalThis.addEventListener('data', ({ detail }) => { + const { err, data, source } = detail.params + const buffer = detail.data + + if (err && err.id === state.id) { + return propagateWokerError(err) + } + + if (!data || BigInt(data.id) !== state.id) return + + if (source === 'child_process.spawn') { + process.stdout.write(data) + } + }) + } +} diff --git a/api/worker_threads.js b/api/worker_threads.js index 03ccae7ad5..ce02b55a61 100644 --- a/api/worker_threads.js +++ b/api/worker_threads.js @@ -195,6 +195,10 @@ export class Worker extends EventEmitter { return this.#worker.id } + get threadId () { + return this.id + } + /** * A `Writable` standard input stream if `{ stdin: true }` was set when * creating this `Worker` instance. @@ -317,6 +321,10 @@ export class Worker extends EventEmitter { } }) } + + postMessage (...args) { + this.#worker.postMessage(...args) + } } export { SHARE_ENV, init } diff --git a/src/core/child_process.cc b/src/core/child_process.cc new file mode 100644 index 0000000000..38165db81b --- /dev/null +++ b/src/core/child_process.cc @@ -0,0 +1,141 @@ +#include "core.hh" + +namespace SSC { + // + // TODO(@heapwolf): clean up all threads on process exit + // + void Core::ChildProcess::kill (const String seq, uint64_t id, const int signal, Module::Callback cb) { + this->core->dispatchEventLoop([=, this] { + if (!this->processes.contains(id)) { + auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "NotFoundError"}, + {"message", "A process with that id does not exist"} + }} + }; + + return cb(seq, json, Post{}); + } + + auto p = this->processes.at(id); + + #ifdef _WIN32 + p->kill(); + #else + ::kill(p->id, signal); + #endif + + cb(seq, JSON::Object{}, Post{}); + }); + } + + void Core::ChildProcess::spawn (const String seq, uint64_t id, const String cwd, Vector<String> args, Module::Callback cb) { + this->core->dispatchEventLoop([=, this] { + auto command = args.at(0); + + if (this->processes.contains(id)) { + auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"message", "A process with that id already exists"} + }} + }; + + return cb(seq, json, Post{}); + } + + const auto argv = join(args.size() > 1 ? Vector<String>{ args.begin() + 1, args.end() } : Vector<String>{}, " "); + + Process* p = new Process( + command, + argv, + cwd, + [=](SSC::String const &out) { + Post post; + + auto bytes = new char[out.size()]{0}; + memcpy(bytes, out.c_str(), out.size()); + + auto headers = Headers {{ + {"content-type" ,"application/octet-stream"}, + {"content-length", (int) out.size()} + }}; + + post.id = rand64(); + post.body = bytes; + post.length = (int) out.size(); + post.headers = headers.str(); + + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"source", "stdout" } + }} + }; + + cb("-1", json, post); + }, + [=](SSC::String const &out) { + Post post; + + auto bytes = new char[out.size()]{0}; + memcpy(bytes, out.c_str(), out.size()); + + auto headers = Headers {{ + {"content-type" ,"application/octet-stream"}, + {"content-length", (int) out.size()} + }}; + + post.id = rand64(); + post.body = bytes; + post.length = (int) out.size(); + post.headers = headers.str(); + + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"source", "stderr" } + }} + }; + + cb("-1", json, post); + }, + [=, this](SSC::String const &code){ + this->processes.erase(id); + + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"status", "exit"}, + {"pid", p->id} + }} + }; + + cb("-1", json, Post{}); + + this->core->dispatchEventLoop([=, this] { + delete p; + }); + } + ); + + this->processes.insert_or_assign(id, p); + + auto pid = p->open(); + + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"pid", pid} + }} + }; + + cb(seq, json, Post{}); + }); + } +} diff --git a/src/core/core.hh b/src/core/core.hh index 358b8c4828..693f37a3c0 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -9,6 +9,7 @@ #include "io.hh" #include "json.hh" #include "platform.hh" +#include "../process/process.hh" #include "preload.hh" #include "service_worker_container.hh" #include "string.hh" @@ -682,6 +683,15 @@ namespace SSC { ); }; + class ChildProcess : public Module { + public: + ChildProcess (auto core) : Module(core) {} + std::map<uint64_t, Process*> processes; + + void kill (const String seq, uint64_t id, const int signal, Module::Callback cb); + void spawn (const String seq, uint64_t id, const String cwd, Vector<String> args, Module::Callback cb); + }; + class UDP : public Module { public: UDP (auto core) : Module(core) {} @@ -739,6 +749,7 @@ namespace SSC { OS os; Platform platform; UDP udp; + ChildProcess childProcess; std::shared_ptr<Posts> posts; std::map<uint64_t, Peer*> peers; @@ -780,6 +791,7 @@ namespace SSC { os(this), platform(this), udp(this), + childProcess(this), serviceWorker(this) { this->posts = std::shared_ptr<Posts>(new Posts()); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 270a77712f..da1807b562 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -252,6 +252,50 @@ static void initRouterTable (Router *router) { reply(Result { message.seq, message }); }); + router->map("child_process.kill", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"signal"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto signal = message.get("signal"); + + router->core->childProcess.kill(message.seq, id, signal, [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + }); + }); + + router->map("child_process.spwan", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"args", "cwd", "id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto args = split(message.get("args"), 0x001); + + if (args.size() == 0 || args.at(0).size() == 0) { + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"err", JSON::Object::Entries { + {"message", "Spawn requires at least one argument with a length greater than zero"}, + }} + }; + + return reply(Result { message.seq, message, json }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + auto cwd = message.get("cwd", getcwd()); + + router->core->childProcess.spawn(message.seq, id, cwd, args, [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + }); + }); + /** * Look up an IP address by `hostname`. * @param hostname Host name to lookup diff --git a/src/process/process.hh b/src/process/process.hh index 21527a9607..ad3c4351e2 100644 --- a/src/process/process.hh +++ b/src/process/process.hh @@ -3,7 +3,7 @@ #include <iostream> -#include "../core/core.hh" +#include "../core/types.hh" #ifndef WIFEXITED #define WIFEXITED(w) ((w) & 0x7f) From b4381c99615f7fd8983c043321e6b7755fac487d Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 20 Feb 2024 21:50:51 +0100 Subject: [PATCH 0038/1178] fix include --- api/vm.js | 1 - src/process/process.hh | 8 +------- src/process/unix.cc | 9 +++++++++ src/process/win.cc | 9 +++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/api/vm.js b/api/vm.js index dd27cf4056..fc4e837819 100644 --- a/api/vm.js +++ b/api/vm.js @@ -546,7 +546,6 @@ export const RESERVED_GLOBAL_INTRINSICS = [ 'chrome', 'external', 'postMessage', - 'console', 'globalThis', 'Infinity', 'NaN', diff --git a/src/process/process.hh b/src/process/process.hh index ad3c4351e2..34a6136015 100644 --- a/src/process/process.hh +++ b/src/process/process.hh @@ -189,13 +189,7 @@ namespace SSC { this->kill(this->getPID()); } - int wait () { - do { - msleep(Process::PROCESS_WAIT_TIMEOUT); - } while (this->closed == false); - - return this->status; - } + int wait (); private: Data data; diff --git a/src/process/unix.cc b/src/process/unix.cc index a82bf5636d..c87f7985cc 100644 --- a/src/process/unix.cc +++ b/src/process/unix.cc @@ -13,6 +13,7 @@ #include <unistd.h> #include "process.hh" +#include "../core/core.hh" namespace SSC { @@ -225,6 +226,14 @@ Process::id_type Process::open(const SSC::String &command, const SSC::String &pa }); } +int Process::wait () { + do { + msleep(Process::PROCESS_WAIT_TIMEOUT); + } while (this->closed == false); + + return this->status; +} + void Process::read() noexcept { if (data.id <= 0 || (!stdout_fd && !stderr_fd)) { return; diff --git a/src/process/win.cc b/src/process/win.cc index fa5ab71378..e2cc5f0e0e 100644 --- a/src/process/win.cc +++ b/src/process/win.cc @@ -1,4 +1,5 @@ #include "process.hh" +#include "../core/core.hh" #include <cstring> #include <iostream> @@ -53,6 +54,14 @@ Process::Process( this->path = path; } +int Process::wait () { + do { + msleep(Process::PROCESS_WAIT_TIMEOUT); + } while (this->closed == false); + + return this->status; +} + // Simple HANDLE wrapper to close it automatically from the destructor. class Handle { public: From f459b4f2683c84d0054420ed7cbd95a9d491d457 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 20 Feb 2024 23:39:03 +0100 Subject: [PATCH 0039/1178] refactor(api): implements child process --- api/child_process.js | 62 +++++++++++++++++++-------------- api/child_process/worker.js | 40 ++++++++++++++------- api/vm.js | 2 +- api/worker_threads/init.js | 14 ++++---- src/core/child_process.cc | 18 ++++++++-- src/ipc/bridge.cc | 10 ++++-- src/process/process.hh | 4 ++- test/socket.ini | 2 +- test/src/child_process/index.js | 25 +++++++++++++ test/src/index.html | 1 + 10 files changed, 123 insertions(+), 55 deletions(-) create mode 100644 test/src/child_process/index.js diff --git a/api/child_process.js b/api/child_process.js index ab113fb899..85f3dc4012 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -1,30 +1,27 @@ import events from './events.js' -import ipc from './ipc.js' import { Worker } from './worker_threads.js' -import { rand64 } from '../crypto.js' +import { rand64 } from './crypto.js' class ChildProcess extends events.EventEmitter { + #id = rand64() + #worker = null + #state = { + killed: false, + signalCode: null, + exitCode: null, + spawnfile: null, + pid: 0 + } + constructor (options) { super() - // 'close' - // 'disconnect' - // 'error' - // 'exit' - // 'message' - // 'spawn' * - - this.#id = rand64() - - this.#state = { - killed: false, - signalCode: null, - exitCode: null, - spawnfile: null, - pid: 0 - } + // + // This does not implement disconnect or message because this is not node! + // + const workerLocation = new URL('./child_process/worker.js', import.meta.url) - this.#worker = new Worker('./child_process/worker.js', { + this.#worker = new Worker(workerLocation.toString(), { env: options.env, stdin: true, stdout: true, @@ -33,13 +30,19 @@ class ChildProcess extends events.EventEmitter { }) this.#worker.on('message', data => { - if (data.method === 'kill' && args[0] === true) { - this.#killed = true + if (data.method === 'kill' && data.args[0] === true) { + this.#state.killed = true } if (data.method === 'state') { - if (this.#state.pid !== args[0].pid) this.emit('spawn') - Object.assign(this.#state, args[0]) + if (this.#state.pid !== data.args[0].pid) this.emit('spawn') + Object.assign(this.#state, data.args[0]) + if (this.#state.lifecycle == 'exit') this.emit('exit', this.#state.exitCode) + if (this.#state.lifecycle == 'close') this.emit('close', this.#state.exitCode) + } + + if (data.method === 'exit') { + this.emit('exit', data.args[0]) } }) @@ -49,15 +52,19 @@ class ChildProcess extends events.EventEmitter { } get stdin () { - this.#worker.stdin + return this.#worker.stdin } get stdout () { - this.#worker.stdout + return this.#worker.stdout } get stderr () { - this.#worker.stderr + return this.#worker.stderr + } + + get worker () { + return this.#worker } kill (...args) { @@ -71,10 +78,11 @@ class ChildProcess extends events.EventEmitter { function spawn (command, args, options) { const child = new ChildProcess(options) - child.spawn(command, args) + child.worker.on('online', () => child.spawn(command, args)) // TODO signal // TODO timeout return child } export { spawn } +export default { spawn } diff --git a/api/child_process/worker.js b/api/child_process/worker.js index de0b05da30..a3ec58eecf 100644 --- a/api/child_process/worker.js +++ b/api/child_process/worker.js @@ -1,5 +1,5 @@ import ipc from '../ipc.js' -import { isMainThread, parentPort } from '../worker_threads.js' +import { parentPort } from '../worker_threads.js' import process from 'socket:process' const state = {} @@ -15,39 +15,55 @@ const propagateWorkerError = err => parentPort.postMessage({ } }) -parentPort.onmessage = ({ data: { id, method, args } }) => { +parentPort.onmessage = async ({ data: { id, method, args } }) => { if (method === 'spawn') { const command = args[0] const argv = args[1] const opts = args[2] const params = { - args: Array.from(argv ?? []).join('\u0001'), - command, - cwd: opts?.cwd + args: [command, ...Array.from(argv ?? [])].join('\u0001'), + id, + cwd: opts?.cwd ?? '' } const { data, err } = await ipc.send('child_process.spawn', params) - + if (err) return propagateWorkerError(err) - - state.id = data.id + + state.id = BigInt(data.id) state.pid = data.pid parentPort.postMessage({ method: 'state', args: [state] }) - + globalThis.addEventListener('data', ({ detail }) => { const { err, data, source } = detail.params const buffer = detail.data if (err && err.id === state.id) { - return propagateWokerError(err) + return propagateWorkerError(err) } if (!data || BigInt(data.id) !== state.id) return - if (source === 'child_process.spawn') { - process.stdout.write(data) + if (source === 'child_process.spawn' && data.source === 'stdout') { + process.stdout.write(buffer) + } + + if (source === 'child_process.spawn' && data.source === 'stderr') { + process.stderr.write(buffer) + } + + if (source === 'child_process.spawn' && data.status === 'close') { + state.exitCode = data.code + state.lifecycle = 'close' + parentPort.postMessage({ method: 'state', args: [state] }) + } + + if (source === 'child_process.spawn' && data.status === 'exit') { + state.exitCode = data.code + state.lifecycle = 'exit' + parentPort.postMessage({ method: 'state', args: [state] }) } }) } diff --git a/api/vm.js b/api/vm.js index fc4e837819..bf41fe5da7 100644 --- a/api/vm.js +++ b/api/vm.js @@ -26,7 +26,7 @@ /* global ErrorEvent, EventTarget, MessagePort */ import { maybeMakeError } from './ipc.js' -import { SharedWorker } from './worker.js' +import { SharedWorker } from './internal/shared-worker.js' import application from './application.js' import globals from './internal/globals.js' import process from './process.js' diff --git a/api/worker_threads/init.js b/api/worker_threads/init.js index c9a0d539ce..f699fdb8b2 100644 --- a/api/worker_threads/init.js +++ b/api/worker_threads/init.js @@ -71,7 +71,7 @@ function propagateWorkerError (err) { }) } -function onMainThreadMessage (event) { +async function onMainThreadMessage (event) { const request = event.data?.worker_threads ?? {} if (request.workerData) { @@ -156,16 +156,16 @@ function onMainThreadMessage (event) { }) } - globalThis.postMessage({ - worker_threads: { online: { id: state.id } } - }) - if (request.init.eval === true) { state.url = '' - vm.runInThisContext(request.init.url).catch(propagateWorkerError) + await vm.runInThisContext(request.init.url).catch(propagateWorkerError) } else { - import(state.url).catch(propagateWorkerError) + await import(state.url).catch(propagateWorkerError) } + + globalThis.postMessage({ + worker_threads: { online: { id: state.id } } + }) } if (request.env && typeof request.env === 'object') { diff --git a/src/core/child_process.cc b/src/core/child_process.cc index 38165db81b..d58b448843 100644 --- a/src/core/child_process.cc +++ b/src/core/child_process.cc @@ -4,7 +4,7 @@ namespace SSC { // // TODO(@heapwolf): clean up all threads on process exit // - void Core::ChildProcess::kill (const String seq, uint64_t id, const int signal, Module::Callback cb) { + void Core::ChildProcess::kill (const String seq, uint64_t id, int signal, Module::Callback cb) { this->core->dispatchEventLoop([=, this] { if (!this->processes.contains(id)) { auto json = JSON::Object::Entries { @@ -103,7 +103,7 @@ namespace SSC { cb("-1", json, post); }, - [=, this](SSC::String const &code){ + [=, this](SSC::String const &code) { this->processes.erase(id); auto json = JSON::Object::Entries { @@ -111,14 +111,26 @@ namespace SSC { {"data", JSON::Object::Entries { {"id", std::to_string(id)}, {"status", "exit"}, - {"pid", p->id} + {"code", std::stoi(code)} }} }; cb("-1", json, Post{}); this->core->dispatchEventLoop([=, this] { + auto code = p->wait(); delete p; + + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"status", "close"}, + {"code", code} + }} + }; + + cb("-1", json, Post{}); }); } ); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index da1807b562..03f2f24338 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -259,15 +259,19 @@ static void initRouterTable (Router *router) { return reply(Result::Err { message, err }); } - auto signal = message.get("signal"); + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + int signal; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoi); router->core->childProcess.kill(message.seq, id, signal, [message, reply](auto seq, auto json, auto post) { reply(Result { seq, message, json, post }); }); }); - router->map("child_process.spwan", [](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"args", "cwd", "id"}); + router->map("child_process.spawn", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"args", "id"}); if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); diff --git a/src/process/process.hh b/src/process/process.hh index 34a6136015..8796196fd4 100644 --- a/src/process/process.hh +++ b/src/process/process.hh @@ -4,6 +4,7 @@ #include <iostream> #include "../core/types.hh" +#include "../core/string.hh" #ifndef WIFEXITED #define WIFEXITED(w) ((w) & 0x7f) @@ -177,7 +178,8 @@ namespace SSC { void close_stdin() noexcept; id_type open() noexcept { if (this->command.size() == 0) return 0; - auto pid = open(this->command + this->argv, this->path); + auto str = SSC::trim(this->command + " " + this->argv); + auto pid = open(str, this->path); read(); return pid; } diff --git a/test/socket.ini b/test/socket.ini index 8f81fae163..aae3ef7331 100644 --- a/test/socket.ini +++ b/test/socket.ini @@ -6,7 +6,7 @@ output = build ; Compiler Settings flags = "-O3 -g" -headless = true +; headless = true env[] = PWD env[] = TMP env[] = TEMP diff --git a/test/src/child_process/index.js b/test/src/child_process/index.js new file mode 100644 index 0000000000..d1b3207a2e --- /dev/null +++ b/test/src/child_process/index.js @@ -0,0 +1,25 @@ +import test from 'socket:test' +import { spawn } from 'socket:child_process' + +test('basic spawn', async t => { + const command = 'ls' + const args = ['-la'] + const options = {} + + let hasDir = false + + await new Promise((resolve, reject) => { + const c = spawn(command, args, options) + + c.stdout.on('data', data => { + if (Buffer.from(data).toString().includes('child_process')) { + hasDir = true + } + }) + + c.on('exit', resolve) + c.on('error', reject) + }) + + t.ok(hasDir, 'the ls command ran and discovered the child_process directory') +}) diff --git a/test/src/index.html b/test/src/index.html index 7e0da1ac06..ac7c795c00 100644 --- a/test/src/index.html +++ b/test/src/index.html @@ -7,6 +7,7 @@ content=" default-src http://* https://* ipc://* file://* socket://* data://*; script-src socket: https: 'unsafe-eval'; + worker-src socket: blob: 'unsafe-inline' 'unsafe-eval'; " > <title>Socket Runtime JavaScript Tests From ec6774104ceaffd7ccd8fc9520ee81f1f4f0c5fe Mon Sep 17 00:00:00 2001 From: heapwolf Date: Tue, 20 Feb 2024 23:43:03 +0100 Subject: [PATCH 0040/1178] lint --- api/child_process.js | 4 ++-- test/src/child_process/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/child_process.js b/api/child_process.js index 85f3dc4012..5200c16a9e 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -37,8 +37,8 @@ class ChildProcess extends events.EventEmitter { if (data.method === 'state') { if (this.#state.pid !== data.args[0].pid) this.emit('spawn') Object.assign(this.#state, data.args[0]) - if (this.#state.lifecycle == 'exit') this.emit('exit', this.#state.exitCode) - if (this.#state.lifecycle == 'close') this.emit('close', this.#state.exitCode) + if (this.#state.lifecycle === 'exit') this.emit('exit', this.#state.exitCode) + if (this.#state.lifecycle === 'close') this.emit('close', this.#state.exitCode) } if (data.method === 'exit') { diff --git a/test/src/child_process/index.js b/test/src/child_process/index.js index d1b3207a2e..f95f85daca 100644 --- a/test/src/child_process/index.js +++ b/test/src/child_process/index.js @@ -10,7 +10,7 @@ test('basic spawn', async t => { await new Promise((resolve, reject) => { const c = spawn(command, args, options) - + c.stdout.on('data', data => { if (Buffer.from(data).toString().includes('child_process')) { hasDir = true From 34240d14bf3ab826a004331e519dbfeb8185df8a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 21 Feb 2024 21:47:04 +0100 Subject: [PATCH 0041/1178] refactor(): improve child process, introduce constants --- api/README.md | 62 +- api/application/menu.js | 2 +- api/child_process.js | 385 +- api/child_process/worker.js | 56 +- api/constants.js | 118 +- api/errno.js | 237 + api/errors.js | 35 + api/fs/index.js | 3 +- api/index.d.ts | 11526 ++++++++++++++++-------------- api/internal/events.js | 46 + api/internal/init.js | 11 +- api/internal/primitives.js | 4 + api/ipc.js | 5 +- api/os.js | 3 + api/os/constants.js | 842 +++ api/path/well-known.js | 18 +- api/process.js | 19 + api/service-worker/container.js | 2 +- api/service-worker/state.js | 2 +- api/signal.js | 169 + api/window.js | 2 +- api/window/hotkey.js | 2 +- src/core/child_process.cc | 307 +- src/core/core.cc | 801 --- src/core/core.hh | 38 +- src/core/dns.cc | 96 + src/core/headers.cc | 143 + src/core/os.cc | 683 ++ src/core/platform.cc | 252 + src/core/platform.hh | 105 + src/desktop/main.cc | 135 +- src/ipc/bridge.cc | 106 +- src/process/process.hh | 19 +- src/process/unix.cc | 23 +- src/process/win.cc | 14 +- test/src/child_process/index.js | 33 +- test/src/fs/index.js | 4 +- 37 files changed, 10010 insertions(+), 6298 deletions(-) create mode 100644 api/errno.js create mode 100644 api/os/constants.js create mode 100644 api/signal.js create mode 100644 src/core/dns.cc create mode 100644 src/core/headers.cc create mode 100644 src/core/os.cc diff --git a/api/README.md b/api/README.md index 2da466b967..8fd84912a3 100644 --- a/api/README.md +++ b/api/README.md @@ -1071,7 +1071,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L905) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L906) Computes real path for `path` @@ -1080,7 +1080,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L925) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L926) Renames file or directory at `src` to `dest`. @@ -1090,7 +1090,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L948) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L949) Removes directory at `path`. @@ -1099,7 +1099,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L969) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L970) Synchronously get the stats of a file @@ -1110,7 +1110,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L988) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L989) Get the stats of a file @@ -1123,7 +1123,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1026) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1027) Get the stats of a symbolic link @@ -1136,7 +1136,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1060) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1061) Creates a symlink of `src` at `dest`. @@ -1145,7 +1145,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1101) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1102) Unlinks (removes) file at `path`. @@ -1154,7 +1154,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1126) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1127) @@ -1169,7 +1169,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1171) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1172) Watch for changes at `path` calling `callback` @@ -1545,12 +1545,12 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L298) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L299) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1101) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1104) Emit event to be dispatched on `window` object. @@ -1561,7 +1561,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1160) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1163) Sends an async IPC command request with parameters. @@ -1603,7 +1603,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide import { arch, platform } from 'socket:os' ``` -## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L57) +## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L60) Returns the operating system CPU architecture for which Socket was compiled. @@ -1611,7 +1611,7 @@ Returns the operating system CPU architecture for which Socket was compiled. | :--- | :--- | :--- | | Not specified | string | 'arm64', 'ia32', 'x64', or 'unknown' | -## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L75) +## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L78) External docs: https://nodejs.org/api/os.html#os_os_cpus Returns an array of objects containing information about each CPU/core. @@ -1629,7 +1629,7 @@ Returns an array of objects containing information about each CPU/core. | :--- | :--- | :--- | | cpus | Array | An array of objects containing information about each CPU/core. | -## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L99) +## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L102) External docs: https://nodejs.org/api/os.html#os_os_networkinterfaces Returns an object containing network interfaces that have been assigned a network address. @@ -1647,7 +1647,7 @@ Returns an object containing network interfaces that have been assigned a networ | :--- | :--- | :--- | | Not specified | object | An object containing network interfaces that have been assigned a network address. | -## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L187) +## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L190) External docs: https://nodejs.org/api/os.html#os_os_platform Returns the operating system platform. @@ -1657,7 +1657,7 @@ Returns the operating system platform. | :--- | :--- | :--- | | Not specified | string | 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' | -## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L196) +## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L199) External docs: https://nodejs.org/api/os.html#os_os_type Returns the operating system name. @@ -1666,7 +1666,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' | -## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L235) +## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L238) @@ -1674,7 +1674,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | boolean | `true` if the operating system is Windows. | -## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L247) +## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L250) @@ -1682,15 +1682,15 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system's default directory for temporary files. | -## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L295) +## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L298) The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. -## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L307) +## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L310) Get resource usage. -## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L317) +## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L320) Returns the system uptime in seconds. @@ -1698,7 +1698,7 @@ Returns the system uptime in seconds. | :--- | :--- | :--- | | Not specified | number | The system uptime in seconds. | -## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L328) +## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L331) Returns the operating system name. @@ -1706,7 +1706,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system name. | -## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L386) +## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L389) Returns the home directory of the current user. @@ -1954,17 +1954,17 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L15) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L16) This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. -## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L23) +## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L24) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L133) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L152) Adds callback to the 'nextTick' queue. @@ -1972,7 +1972,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L164) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L183) Computed high resolution time as a `BigInt`. @@ -1984,7 +1984,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L190) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L209) @@ -1992,7 +1992,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L202) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L221) Returns an object describing the memory usage of the Node.js process measured in bytes. diff --git a/api/application/menu.js b/api/application/menu.js index 3a3ad264a6..fdb53ea4f8 100644 --- a/api/application/menu.js +++ b/api/application/menu.js @@ -50,7 +50,7 @@ export class Menu extends EventTarget { constructor (type) { super() this.#type = type - this.#channel = new BroadcastChannel(`application.menu.${type}`) + this.#channel = new BroadcastChannel(`socket.runtime.application.menu.${type}`) this.#channel.addEventListener('message', (event) => { this.dispatchEvent(new MenuItemEvent('menuitem', event.data, this)) diff --git a/api/child_process.js b/api/child_process.js index 5200c16a9e..561497b1e4 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -1,15 +1,33 @@ -import events from './events.js' +/* global ErrorEvent */ +import { EventEmitter } from './events.js' +import diagnostics from './diagnostics.js' import { Worker } from './worker_threads.js' +import { Buffer } from './buffer.js' import { rand64 } from './crypto.js' +import process from './process.js' +import ipc from './ipc.js' +import gc from './gc.js' -class ChildProcess extends events.EventEmitter { +const dc = diagnostics.channels.group('child_process', [ + 'spawn', + 'close', + 'exit', + 'kill' +]) + +class ChildProcess extends EventEmitter { #id = rand64() #worker = null + #signal = null + #timeout = null + #env = { ...process.env } #state = { killed: false, signalCode: null, exitCode: null, spawnfile: null, + spawnargs: [], + lifecycle: 'init', pid: 0 } @@ -21,14 +39,38 @@ class ChildProcess extends events.EventEmitter { // const workerLocation = new URL('./child_process/worker.js', import.meta.url) + // TODO(@jwerle): support environment variable inject + if (options?.env && typeof options?.env === 'object') { + this.#env = options.env + } + this.#worker = new Worker(workerLocation.toString(), { env: options.env, - stdin: true, - stdout: true, - stderr: true, + stdin: options?.stdin !== false, + stdout: options?.stdout !== false, + stderr: options?.stderr !== false, workerData: { id: this.#id } }) + if (options?.signal) { + this.#signal = options.signal + this.#signal.addEventListener('abort', () => { + this.emit('error', new Error(this.#signal.reason)) + this.kill(options?.killSignal ?? 'SIGKILL') + }) + } + + if (options?.timeout) { + this.#timeout = setTimeout(() => { + this.emit('error', new Error('Child process timed out')) + this.kill(options?.killSignal ?? 'SIGKILL') + }, options.timeout) + + this.once('exit', () => { + clearTimeout(this.#timeout) + }) + } + this.#worker.on('message', data => { if (data.method === 'kill' && data.args[0] === true) { this.#state.killed = true @@ -37,8 +79,32 @@ class ChildProcess extends events.EventEmitter { if (data.method === 'state') { if (this.#state.pid !== data.args[0].pid) this.emit('spawn') Object.assign(this.#state, data.args[0]) - if (this.#state.lifecycle === 'exit') this.emit('exit', this.#state.exitCode) - if (this.#state.lifecycle === 'close') this.emit('close', this.#state.exitCode) + + switch (this.#state.lifecycle) { + case 'spawn': { + gc.ref(this) + dc.channel('spawn').publish({ child_process: this }) + break + } + + case 'exit': { + this.emit('exit', this.#state.exitCode) + dc.channel('exit').publish({ child_process: this }) + break + } + + case 'close': { + this.emit('close', this.#state.exitCode) + dc.channel('close').publish({ child_process: this }) + break + } + + case 'kill': { + this.#state.killed = true + dc.channel('kill').publish({ child_process: this }) + break + } + } } if (data.method === 'exit') { @@ -51,32 +117,248 @@ class ChildProcess extends events.EventEmitter { }) } + /** + * `true` if the child process was killed with kill()`, + * otherwise `false`. + * @type {boolean} + */ + get killed () { + return this.#state.killed + } + + /** + * The process identifier for the child process. This value is + * `> 0` if the process was spawned successfully, otherwise `0`. + * @type {number} + */ + get pid () { + return this.#state.pid + } + + /** + * The executable file name of the child process that is launched. This + * value is `null` until the child process has successfully been spawned. + * @type {string?} + */ + get spawnfile () { + return this.#state.spawnfile ?? null + } + + /** + * The full list of command-line arguments the child process was spawned with. + * This value is an empty array until the child process has successfully been + * spawned. + * @type {string[]} + */ + get spawnargs () { + return this.#state.spawnargs + } + + /** + * Always `false` as the IPC messaging is not supported. + * @type {boolean} + */ + get connected () { + return false + } + + /** + * The child process exit code. This value is `null` if the child process + * is still running, otherwise it is a positive integer. + * @type {number?} + */ + get exitCode () { + return this.#state.exitCode ?? null + } + + /** + * If available, the underlying `stdin` writable stream for + * the child process. + * @type {import('./stream').Writable?} + */ get stdin () { - return this.#worker.stdin + return this.#worker.stdin ?? null } + /** + * If available, the underlying `stdout` readable stream for + * the child process. + * @type {import('./stream').Readable?} + */ get stdout () { - return this.#worker.stdout + return this.#worker.stdout ?? null } + /** + * If available, the underlying `stderr` readable stream for + * the child process. + * @type {import('./stream').Readable?} + */ get stderr () { - return this.#worker.stderr + return this.#worker.stderr ?? null } + /** + * The underlying worker thread. + * @ignore + * @type {import('./worker_threads').Worker} + */ get worker () { return this.#worker } + /** + * This function does nothing, but is present for nodejs compat. + */ + disconnect () { + return false + } + + /** + * This function does nothing, but is present for nodejs compat. + * @return {boolean} + */ + send () { + return false + } + + /** + * This function does nothing, but is present for nodejs compat. + */ + ref () { + return false + } + + /** + * This function does nothing, but is present for nodejs compat. + */ + unref () { + return false + } + + /** + * Kills the child process. This function throws an error if the child + * process has not been spawned or is already killed. + * @param {number|string} signal + */ kill (...args) { + if (/spawn/.test(this.#state.lifecycle)) { + throw new Error('Cannot kill a child process that has not been spawned') + } + + if (this.killed) { + throw new Error('Cannot kill an already killed child process') + } + this.#worker.postMessage({ id: this.#id, method: 'kill', args }) + return this } + /** + * Spawns the child process. This function will thrown an error if the process + * is already spawned. + * @param {string} command + * @param {string[]=} [args] + * @return {ChildProcess} + */ spawn (...args) { - this.#worker.postMessage({ id: this.#id, method: 'spawn', args }) + if (/spawning|spawn/.test(this.#state.lifecycle)) { + throw new Error('Cannot spawn an already spawned ChildProcess') + } + + if (!args[0] || typeof args[0] !== 'string') { + throw new TypeError('Expecting command to be a string.') + } + + this.#state.lifecycle = 'spawning' + this.#worker.postMessage({ + id: this.#id, + env: this.#env, + method: 'spawn', + args + }) + + return this + } + + /** + * `EventTarget` based `addEventListener` method. + * @param {string} event + * @param {function(Event)} callback + * @param {{ once?: false }} [options] + */ + addEventListener (event, callback, options = null) { + callback.listener = (...args) => { + if (event === 'error') { + callback(new ErrorEvent('error', { + target: this, + error: args[0] + })) + } else { + callback(new Event(event, args[0])) + } + } + + if (options?.once === true) { + this.once(event, callback.listener) + } else { + this.on(event, callback.listener) + } + } + + /** + * `EventTarget` based `removeEventListener` method. + * @param {string} event + * @param {function(Event)} callback + * @param {{ once?: false }} [options] + */ + removeEventListener (event, callback) { + this.off(event, callback.listener ?? callback) + } + + /** + * Implements `gc.finalizer` for gc'd resource cleanup. + * @return {gc.Finalizer} + * @ignore + */ + [gc.finalizer] () { + return { + args: [this.id], + async handle (id) { + const result = await ipc.send('child_process.kill', { + id, + signal: 'SIGTERM' + }) + + if (result.err) { + console.warn(result.err) + } + } + } } } -function spawn (command, args, options) { +/** + * Spawns a child process exeucting `command` with `args` + * @param {string} command + * @param {string[]|object=} [args] + * @param {object=} [options + * @return {ChildProcess} + */ +export function spawn (command, args = [], options = null) { + if (args && typeof args === 'object' && !Array.isArray(args)) { + options = args + args = [] + } + + if (!command || typeof command !== 'string') { + throw new TypeError('Expecting command to be a string.') + } + + if (args && typeof args === 'string') { + args = args.split(' ') + } + const child = new ChildProcess(options) child.worker.on('online', () => child.spawn(command, args)) // TODO signal @@ -84,5 +366,80 @@ function spawn (command, args, options) { return child } -export { spawn } -export default { spawn } +export function exec (command, options, callback) { + const child = spawn(command, options) + const stdout = [] + const stderr = [] + let hasError = false + + if (child.stdout) { + child.stdout.on('data', (data) => { + if (hasError) { + return + } + + stdout.push(data) + }) + } + + if (child.stderr) { + child.stderr.on('data', (data) => { + if (hasError) { + return + } + + stderr.push(data) + }) + } + + child.once('error', (err) => { + hasError = true + callback(err, null, null) + }) + + child.on('close', () => { + if (hasError) { + return + } + + if (options?.encoding === 'buffer') { + callback( + null, + Buffer.concat(stdout), + Buffer.concat(stderr) + ) + } else { + const encoding = options?.encoding ?? 'utf8' + callback( + null, + Buffer.concat(stdout).toString(encoding), + Buffer.concat(stderr).toString(encoding) + ) + } + }) + + return child +} + +export const execFile = exec + +exec[Symbol.for('nodejs.util.promisify.custom')] = +exec[Symbol.for('socket.util.promisify.custom')] = + async function execPromisify (command, options) { + return await new Promise((resolve, reject) => { + exec(command, options, (err, stdout, stderr) => { + if (err) { + reject(err) + } else { + resolve({ stdout, stderr }) + } + }) + }) + } + +export default { + ChildProcess, + spawn, + execFile, + exec +} diff --git a/api/child_process/worker.js b/api/child_process/worker.js index a3ec58eecf..932172ebe4 100644 --- a/api/child_process/worker.js +++ b/api/child_process/worker.js @@ -1,6 +1,7 @@ -import ipc from '../ipc.js' import { parentPort } from '../worker_threads.js' -import process from 'socket:process' +import process from '../process.js' +import signal from '../signal.js' +import ipc from '../ipc.js' const state = {} @@ -15,6 +16,17 @@ const propagateWorkerError = err => parentPort.postMessage({ } }) +if (process.stdin) { + process.stdin.on('data', async (data) => { + const { id } = state + const result = await ipc.write('child_process.spawn', { id }, data) + + if (result.err) { + propagateWorkerError(result.err) + } + }) +} + parentPort.onmessage = async ({ data: { id, method, args } }) => { if (method === 'spawn') { const command = args[0] @@ -24,15 +36,23 @@ parentPort.onmessage = async ({ data: { id, method, args } }) => { const params = { args: [command, ...Array.from(argv ?? [])].join('\u0001'), id, - cwd: opts?.cwd ?? '' + cwd: opts?.cwd ?? '', + stdin: opts?.stdin !== false, + stdout: opts?.stdout !== false, + stderr: opts?.stderr !== false } - const { data, err } = await ipc.send('child_process.spawn', params) + const result = await ipc.send('child_process.spawn', params) - if (err) return propagateWorkerError(err) + if (result.err) { + return propagateWorkerError(result.err) + } - state.id = BigInt(data.id) - state.pid = data.pid + state.id = BigInt(result.data.id) + state.pid = result.data.pid + state.spawnfile = command + state.spawnargs = argv + state.lifecycle = 'spawn' parentPort.postMessage({ method: 'state', args: [state] }) @@ -47,11 +67,15 @@ parentPort.onmessage = async ({ data: { id, method, args } }) => { if (!data || BigInt(data.id) !== state.id) return if (source === 'child_process.spawn' && data.source === 'stdout') { - process.stdout.write(buffer) + if (process.stdout) { + process.stdout.write(buffer) + } } if (source === 'child_process.spawn' && data.source === 'stderr') { - process.stderr.write(buffer) + if (process.stderr) { + process.stderr.write(buffer) + } } if (source === 'child_process.spawn' && data.status === 'close') { @@ -67,4 +91,18 @@ parentPort.onmessage = async ({ data: { id, method, args } }) => { } }) } + + if (method === 'kill') { + const result = await ipc.send('child_process.kill', { + id: state.id, + signal: signal.getCode(args[0]) + }) + + if (result.err) { + return propagateWorkerError(result.err) + } + + state.lifecycle = 'kill' + parentPort.postMessage({ method: 'state', args: [state] }) + } } diff --git a/api/constants.js b/api/constants.js index 2f67095823..73580977e9 100644 --- a/api/constants.js +++ b/api/constants.js @@ -1,8 +1,124 @@ import fs from './fs/constants.js' import window from './window/constants.js' +import os from './os/constants.js' export * from './fs/constants.js' export * from './window/constants.js' + +export const E2BIG = os.errno.E2BIG +export const EACCES = os.errno.EACCES +export const EADDRINUSE = os.errno.EADDRINUSE +export const EADDRNOTAVAIL = os.errno.EADDRNOTAVAIL +export const EAFNOSUPPORT = os.errno.EAFNOSUPPORT +export const EAGAIN = os.errno.EAGAIN +export const EALREADY = os.errno.EALREADY +export const EBADF = os.errno.EBADF +export const EBADMSG = os.errno.EBADMSG +export const EBUSY = os.errno.EBUSY +export const ECANCELED = os.errno.ECANCELED +export const ECHILD = os.errno.ECHILD +export const ECONNABORTED = os.errno.ECONNABORTED +export const ECONNREFUSED = os.errno.ECONNREFUSED +export const ECONNRESET = os.errno.ECONNRESET +export const EDEADLK = os.errno.EDEADLK +export const EDESTADDRREQ = os.errno.EDESTADDRREQ +export const EDOM = os.errno.EDOM +export const EDQUOT = os.errno.EDQUOT +export const EEXIST = os.errno.EEXIST +export const EFAULT = os.errno.EFAULT +export const EFBIG = os.errno.EFBIG +export const EHOSTUNREACH = os.errno.EHOSTUNREACH +export const EIDRM = os.errno.EIDRM +export const EILSEQ = os.errno.EILSEQ +export const EINPROGRESS = os.errno.EINPROGRESS +export const EINTR = os.errno.EINTR +export const EINVAL = os.errno.EINVAL +export const EIO = os.errno.EIO +export const EISCONN = os.errno.EISCONN +export const EISDIR = os.errno.EISDIR +export const ELOOP = os.errno.ELOOP +export const EMFILE = os.errno.EMFILE +export const EMLINK = os.errno.EMLINK +export const EMSGSIZE = os.errno.EMSGSIZE +export const EMULTIHOP = os.errno.EMULTIHOP +export const ENAMETOOLONG = os.errno.ENAMETOOLONG +export const ENETDOWN = os.errno.ENETDOWN +export const ENETRESET = os.errno.ENETRESET +export const ENETUNREACH = os.errno.ENETUNREACH +export const ENFILE = os.errno.ENFILE +export const ENOBUFS = os.errno.ENOBUFS +export const ENODATA = os.errno.ENODATA +export const ENODEV = os.errno.ENODEV +export const ENOENT = os.errno.ENOENT +export const ENOEXEC = os.errno.ENOEXEC +export const ENOLCK = os.errno.ENOLCK +export const ENOLINK = os.errno.ENOLINK +export const ENOMEM = os.errno.ENOMEM +export const ENOMSG = os.errno.ENOMSG +export const ENOPROTOOPT = os.errno.ENOPROTOOPT +export const ENOSPC = os.errno.ENOSPC +export const ENOSR = os.errno.ENOSR +export const ENOSTR = os.errno.ENOSTR +export const ENOSYS = os.errno.ENOSYS +export const ENOTCONN = os.errno.ENOTCONN +export const ENOTDIR = os.errno.ENOTDIR +export const ENOTEMPTY = os.errno.ENOTEMPTY +export const ENOTSOCK = os.errno.ENOTSOCK +export const ENOTSUP = os.errno.ENOTSUP +export const ENOTTY = os.errno.ENOTTY +export const ENXIO = os.errno.ENXIO +export const EOPNOTSUPP = os.errno.EOPNOTSUPP +export const EOVERFLOW = os.errno.EOVERFLOW +export const EPERM = os.errno.EPERM +export const EPIPE = os.errno.EPIPE +export const EPROTO = os.errno.EPROTO +export const EPROTONOSUPPORT = os.errno.EPROTONOSUPPORT +export const EPROTOTYPE = os.errno.EPROTOTYPE +export const ERANGE = os.errno.ERANGE +export const EROFS = os.errno.EROFS +export const ESPIPE = os.errno.ESPIPE +export const ESRCH = os.errno.ESRCH +export const ESTALE = os.errno.ESTALE +export const ETIME = os.errno.ETIME +export const ETIMEDOUT = os.errno.ETIMEDOUT +export const ETXTBSY = os.errno.ETXTBSY +export const EWOULDBLOCK = os.errno.EWOULDBLOCK +export const EXDEV = os.errno.EXDEV + +export const SIGHUP = os.signal.SIGHUP +export const SIGINT = os.signal.SIGINT +export const SIGQUIT = os.signal.SIGQUIT +export const SIGILL = os.signal.SIGILL +export const SIGTRAP = os.signal.SIGTRAP +export const SIGABRT = os.signal.SIGABRT +export const SIGIOT = os.signal.SIGIOT +export const SIGBUS = os.signal.SIGBUS +export const SIGFPE = os.signal.SIGFPE +export const SIGKILL = os.signal.SIGKILL +export const SIGUSR1 = os.signal.SIGUSR1 +export const SIGSEGV = os.signal.SIGSEGV +export const SIGUSR2 = os.signal.SIGUSR2 +export const SIGPIPE = os.signal.SIGPIPE +export const SIGALRM = os.signal.SIGALRM +export const SIGTERM = os.signal.SIGTERM +export const SIGCHLD = os.signal.SIGCHLD +export const SIGCONT = os.signal.SIGCONT +export const SIGSTOP = os.signal.SIGSTOP +export const SIGTSTP = os.signal.SIGTSTP +export const SIGTTIN = os.signal.SIGTTIN +export const SIGTTOU = os.signal.SIGTTOU +export const SIGURG = os.signal.SIGURG +export const SIGXCPU = os.signal.SIGXCPU +export const SIGXFSZ = os.signal.SIGXFSZ +export const SIGVTALRM = os.signal.SIGVTALRM +export const SIGPROF = os.signal.SIGPROF +export const SIGWINCH = os.signal.SIGWINCH +export const SIGIO = os.signal.SIGIO +export const SIGINFO = os.signal.SIGINFO +export const SIGSYS = os.signal.SIGSYS + export default { ...fs, - ...window + ...window, + ...os.errno, + ...os.signal } diff --git a/api/errno.js b/api/errno.js new file mode 100644 index 0000000000..870210bbce --- /dev/null +++ b/api/errno.js @@ -0,0 +1,237 @@ +import { errno as constants } from './os/constants.js' + +/** + * @typedef {import('./os/constants.js').errno} errno + */ + +export const E2BIG = constants.E2BIG +export const EACCES = constants.EACCES +export const EADDRINUSE = constants.EADDRINUSE +export const EADDRNOTAVAIL = constants.EADDRNOTAVAIL +export const EAFNOSUPPORT = constants.EAFNOSUPPORT +export const EAGAIN = constants.EAGAIN +export const EALREADY = constants.EALREADY +export const EBADF = constants.EBADF +export const EBADMSG = constants.EBADMSG +export const EBUSY = constants.EBUSY +export const ECANCELED = constants.ECANCELED +export const ECHILD = constants.ECHILD +export const ECONNABORTED = constants.ECONNABORTED +export const ECONNREFUSED = constants.ECONNREFUSED +export const ECONNRESET = constants.ECONNRESET +export const EDEADLK = constants.EDEADLK +export const EDESTADDRREQ = constants.EDESTADDRREQ +export const EDOM = constants.EDOM +export const EDQUOT = constants.EDQUOT +export const EEXIST = constants.EEXIST +export const EFAULT = constants.EFAULT +export const EFBIG = constants.EFBIG +export const EHOSTUNREACH = constants.EHOSTUNREACH +export const EIDRM = constants.EIDRM +export const EILSEQ = constants.EILSEQ +export const EINPROGRESS = constants.EINPROGRESS +export const EINTR = constants.EINTR +export const EINVAL = constants.EINVAL +export const EIO = constants.EIO +export const EISCONN = constants.EISCONN +export const EISDIR = constants.EISDIR +export const ELOOP = constants.ELOOP +export const EMFILE = constants.EMFILE +export const EMLINK = constants.EMLINK +export const EMSGSIZE = constants.EMSGSIZE +export const EMULTIHOP = constants.EMULTIHOP +export const ENAMETOOLONG = constants.ENAMETOOLONG +export const ENETDOWN = constants.ENETDOWN +export const ENETRESET = constants.ENETRESET +export const ENETUNREACH = constants.ENETUNREACH +export const ENFILE = constants.ENFILE +export const ENOBUFS = constants.ENOBUFS +export const ENODATA = constants.ENODATA +export const ENODEV = constants.ENODEV +export const ENOENT = constants.ENOENT +export const ENOEXEC = constants.ENOEXEC +export const ENOLCK = constants.ENOLCK +export const ENOLINK = constants.ENOLINK +export const ENOMEM = constants.ENOMEM +export const ENOMSG = constants.ENOMSG +export const ENOPROTOOPT = constants.ENOPROTOOPT +export const ENOSPC = constants.ENOSPC +export const ENOSR = constants.ENOSR +export const ENOSTR = constants.ENOSTR +export const ENOSYS = constants.ENOSYS +export const ENOTCONN = constants.ENOTCONN +export const ENOTDIR = constants.ENOTDIR +export const ENOTEMPTY = constants.ENOTEMPTY +export const ENOTSOCK = constants.ENOTSOCK +export const ENOTSUP = constants.ENOTSUP +export const ENOTTY = constants.ENOTTY +export const ENXIO = constants.ENXIO +export const EOPNOTSUPP = constants.EOPNOTSUPP +export const EOVERFLOW = constants.EOVERFLOW +export const EPERM = constants.EPERM +export const EPIPE = constants.EPIPE +export const EPROTO = constants.EPROTO +export const EPROTONOSUPPORT = constants.EPROTONOSUPPORT +export const EPROTOTYPE = constants.EPROTOTYPE +export const ERANGE = constants.ERANGE +export const EROFS = constants.EROFS +export const ESPIPE = constants.ESPIPE +export const ESRCH = constants.ESRCH +export const ESTALE = constants.ESTALE +export const ETIME = constants.ETIME +export const ETIMEDOUT = constants.ETIMEDOUT +export const ETXTBSY = constants.ETXTBSY +export const EWOULDBLOCK = constants.EWOULDBLOCK +export const EXDEV = constants.EXDEV + +export const strings = { + [E2BIG]: 'Arg list too long', + [EACCES]: 'Permission denied', + [EADDRINUSE]: 'Address already in use', + [EADDRNOTAVAIL]: 'Cannot assign requested address', + [EAFNOSUPPORT]: 'Address family not supported by protocol family', + [EAGAIN]: 'Resource temporarily unavailabl', + [EALREADY]: 'Operation already in progress', + [EBADF]: 'Bad file descriptor', + [EBADMSG]: 'Bad message', + [EBUSY]: 'Resource busy', + [ECANCELED]: 'Operation canceled', + [ECHILD]: 'No child processes', + [ECONNABORTED]: 'Software caused connection abort', + [ECONNREFUSED]: 'Connection refused', + [ECONNRESET]: 'Connection reset by peer', + [EDEADLK]: 'Resource deadlock avoided', + [EDESTADDRREQ]: 'Destination address required', + [EDOM]: 'Numerical argument out of domain', + [EDQUOT]: 'Disc quota exceeded', + [EEXIST]: 'File exists', + [EFAULT]: 'Bad address', + [EFBIG]: 'File too large', + [EHOSTUNREACH]: 'No route to host', + [EIDRM]: 'Identifier removed', + [EILSEQ]: 'Illegal byte sequence', + [EINPROGRESS]: 'Operation now in progress', + [EINTR]: 'Interrupted function call', + [EINVAL]: 'Invalid argument', + [EIO]: 'Input/output error', + [EISCONN]: 'Socket is already connected', + [EISDIR]: 'Is a directory', + [ELOOP]: 'Too many levels of symbolic links', + [EMFILE]: 'Too many open files', + [EMLINK]: 'Too many links', + [EMSGSIZE]: 'Message too long', + [EMULTIHOP]: '', + [ENAMETOOLONG]: 'File name too long', + [ENETDOWN]: 'Network is down', + [ENETRESET]: 'Network dropped connection on reset', + [ENETUNREACH]: 'Network is unreachable', + [ENFILE]: 'Too many open files in system', + [ENOBUFS]: 'No buffer space available', + [ENODATA]: 'No message available', + [ENODEV]: 'Operation not supported by device', + [ENOENT]: 'No such file or directory', + [ENOEXEC]: 'Exec format error', + [ENOLCK]: 'No locks available', + [ENOLINK]: '', + [ENOMEM]: 'Cannot allocate memory', + [ENOMSG]: 'No message of desired type', + [ENOPROTOOPT]: 'Protocol not available', + [ENOSPC]: 'Device out of space', + [ENOSR]: 'No STREAM resources', + [ENOSTR]: 'Not a STREAM', + [ENOSYS]: 'Function not implemented', + [ENOTCONN]: 'Socket is not connected', + [ENOTDIR]: 'Not a directory', + [ENOTEMPTY]: 'Directory not empty', + [ENOTSOCK]: 'Socket operation on non-socket', + [ENOTSUP]: 'Not supported', + [ENOTTY]: 'Inappropriate ioctl for device', + [ENXIO]: 'No such device or address', + [EOPNOTSUPP]: 'Operation not supported on socket', + [EOVERFLOW]: 'Value too large to be stored in data type', + [EPERM]: 'Operation not permitted', + [EPIPE]: 'Broken pipe', + [EPROTO]: 'Protocol error', + [EPROTONOSUPPORT]: 'Protocol not supported', + [EPROTOTYPE]: 'Protocol wrong type for socket', + [ERANGE]: 'Numerical result out of range', + [EROFS]: 'Read-only file system', + [ESPIPE]: 'Illegal seek', + [ESRCH]: 'No such process', + [ESTALE]: 'Stale NFS file handle', + [ETIME]: 'STREAM ioctl() timeout', + [ETIMEDOUT]: 'Operation timed out', + [ETXTBSY]: 'Text file busy', + [EWOULDBLOCK]: 'Operation would block', + [EXDEV]: 'Improper link' +} + +/** + * Converts an `errno` code to its corresponding string message. + * @param {import('./os/constants.js').errno} {code} + * @return {string} + */ +export function toString (code) { + code = Math.abs(code) + return strings[code] ?? '' +} + +/** + * Gets the code for a given 'errno' name. + * @param {string|number} name + * @return {errno} + */ +export function getCode (name) { + if (typeof name !== 'string') { + name = name.toString() + } + + name = name.toUpperCase() + for (const key in constants) { + if (name === key) { + return constants[key] + } + } + + return 0 +} + +/** + * Gets the name for a given 'errno' code + * @return {string} + * @param {string|number} code + */ +export function getName (code) { + code = getCode(code) + for (const key in constants) { + const value = constants[key] + if (value === code) { + return key + } + } + + return '' +} + +/** + * Gets the message for a 'errno' code. + * @param {number|string} code + * @return {string} + */ +export function getMessage (code) { + if (typeof code === 'string') { + code = getCode(code) + } + + code = Math.abs(code) + return toString(code) +} + +export { constants } +export default { + constants, + strings, + toString, + getCode, + getMessage +} diff --git a/api/errors.js b/api/errors.js index c5fad948e9..fc91b40646 100644 --- a/api/errors.js +++ b/api/errors.js @@ -120,6 +120,41 @@ export class EncodingError extends Error { } } +/** + * An error type derived from an `errno` code. + */ +export class ErrnoError extends Error { + static get code () { return '' } + // lazily set during init phase + static errno = null + + #name = '' + #code = 0 + + /** + * `ErrnoError` class constructor. + * @param {import('./errno').errno|string} code + */ + constructor (code, message = null, ...args) { + super(message || ErrnoError.errno.getMessage(code) || '', ...args) + + this.#code = ErrnoError.errno.getCode(code) + this.#name = ErrnoError.errno.getName(code) || 'SystemError' + + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, EncodingError) + } + } + + get name () { + return this.#name + } + + get code () { + return this.#code + } +} + /** * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception * has occurred, such as in the native IPC layer. diff --git a/api/fs/index.js b/api/fs/index.js index 08ac429dd1..afd81073be 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -893,7 +893,8 @@ export function readlink (path, callback) { } ipc.request('fs.readlink', { path }).then((result) => { - result?.err ? callback(result.err) : callback(result.data.path) + console.log({ path, readLink: result }) + result?.err ? callback(result.err) : callback(null, result.data.path) }).catch(callback) } diff --git a/api/index.d.ts b/api/index.d.ts index 4db5ceeb40..0dabf376cc 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -1,1491 +1,1284 @@ -declare module "socket:errors" { - export default exports; - export const ABORT_ERR: any; - export const ENCODING_ERR: any; - export const INVALID_ACCESS_ERR: any; - export const INDEX_SIZE_ERR: any; - export const NETWORK_ERR: any; - export const NOT_ALLOWED_ERR: any; - export const NOT_FOUND_ERR: any; - export const NOT_SUPPORTED_ERR: any; - export const OPERATION_ERR: any; - export const SECURITY_ERR: any; - export const TIMEOUT_ERR: any; +declare module "socket:buffer" { + export default Buffer; /** - * An `AbortError` is an error type thrown in an `onabort()` level 0 - * event handler on an `AbortSignal` instance. + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. */ - export class AbortError extends Error { - /** - * The code given to an `ABORT_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `AbortError` class constructor. - * @param {AbortSignal|string} reasonOrSignal - * @param {AbortSignal=} [signal] - */ - constructor(reason: any, signal?: AbortSignal | undefined, ...args: any[]); - signal: AbortSignal; - get name(): string; - get code(): string; - } /** - * An `BadRequestError` is an error type thrown in an `onabort()` level 0 - * event handler on an `BadRequestSignal` instance. + * @name Buffer + * @extends {Uint8Array} */ - export class BadRequestError extends Error { + export function Buffer(arg: any, encodingOrOffset: any, length: any): any; + export class Buffer { /** - * The default code given to a `BadRequestError` + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. */ - static get code(): number; /** - * `BadRequestError` class constructor. - * @param {string} message - * @param {number} [code] + * @name Buffer + * @extends {Uint8Array} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + constructor(arg: any, encodingOrOffset: any, length: any); + get parent(): any; + get offset(): any; + _isBuffer: boolean; + swap16(): this; + swap32(): this; + swap64(): this; + toString(...args: any[]): any; + toLocaleString: any; + equals(b: any): boolean; + inspect(): string; + compare(target: any, start: any, end: any, thisStart: any, thisEnd: any): 0 | 1 | -1; + includes(val: any, byteOffset: any, encoding: any): boolean; + indexOf(val: any, byteOffset: any, encoding: any): any; + lastIndexOf(val: any, byteOffset: any, encoding: any): any; + write(string: any, offset: any, length: any, encoding: any): number; + toJSON(): { + type: string; + data: any; + }; + slice(start: any, end: any): any; + readUintLE: (offset: any, byteLength: any, noAssert: any) => any; + readUIntLE(offset: any, byteLength: any, noAssert: any): any; + readUintBE: (offset: any, byteLength: any, noAssert: any) => any; + readUIntBE(offset: any, byteLength: any, noAssert: any): any; + readUint8: (offset: any, noAssert: any) => any; + readUInt8(offset: any, noAssert: any): any; + readUint16LE: (offset: any, noAssert: any) => number; + readUInt16LE(offset: any, noAssert: any): number; + readUint16BE: (offset: any, noAssert: any) => number; + readUInt16BE(offset: any, noAssert: any): number; + readUint32LE: (offset: any, noAssert: any) => number; + readUInt32LE(offset: any, noAssert: any): number; + readUint32BE: (offset: any, noAssert: any) => number; + readUInt32BE(offset: any, noAssert: any): number; + readBigUInt64LE: any; + readBigUInt64BE: any; + readIntLE(offset: any, byteLength: any, noAssert: any): any; + readIntBE(offset: any, byteLength: any, noAssert: any): any; + readInt8(offset: any, noAssert: any): any; + readInt16LE(offset: any, noAssert: any): number; + readInt16BE(offset: any, noAssert: any): number; + readInt32LE(offset: any, noAssert: any): number; + readInt32BE(offset: any, noAssert: any): number; + readBigInt64LE: any; + readBigInt64BE: any; + readFloatLE(offset: any, noAssert: any): number; + readFloatBE(offset: any, noAssert: any): number; + readDoubleLE(offset: any, noAssert: any): number; + readDoubleBE(offset: any, noAssert: any): number; + writeUintLE: (value: any, offset: any, byteLength: any, noAssert: any) => any; + writeUIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeUintBE: (value: any, offset: any, byteLength: any, noAssert: any) => any; + writeUIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeUint8: (value: any, offset: any, noAssert: any) => any; + writeUInt8(value: any, offset: any, noAssert: any): any; + writeUint16LE: (value: any, offset: any, noAssert: any) => any; + writeUInt16LE(value: any, offset: any, noAssert: any): any; + writeUint16BE: (value: any, offset: any, noAssert: any) => any; + writeUInt16BE(value: any, offset: any, noAssert: any): any; + writeUint32LE: (value: any, offset: any, noAssert: any) => any; + writeUInt32LE(value: any, offset: any, noAssert: any): any; + writeUint32BE: (value: any, offset: any, noAssert: any) => any; + writeUInt32BE(value: any, offset: any, noAssert: any): any; + writeBigUInt64LE: any; + writeBigUInt64BE: any; + writeIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeInt8(value: any, offset: any, noAssert: any): any; + writeInt16LE(value: any, offset: any, noAssert: any): any; + writeInt16BE(value: any, offset: any, noAssert: any): any; + writeInt32LE(value: any, offset: any, noAssert: any): any; + writeInt32BE(value: any, offset: any, noAssert: any): any; + writeBigInt64LE: any; + writeBigInt64BE: any; + writeFloatLE(value: any, offset: any, noAssert: any): any; + writeFloatBE(value: any, offset: any, noAssert: any): any; + writeDoubleLE(value: any, offset: any, noAssert: any): any; + writeDoubleBE(value: any, offset: any, noAssert: any): any; + copy(target: any, targetStart: any, start: any, end: any): number; + fill(val: any, start: any, end: any, encoding: any): this; } - /** - * An `EncodingError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class EncodingError extends Error { + export namespace Buffer { + export let TYPED_ARRAY_SUPPORT: boolean; + export let poolSize: number; /** - * The code given to an `ENCODING_ERR` `DOMException`. - */ - static get code(): any; + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ + export function from(value: any, encodingOrOffset?: any, length?: any): any; /** - * `EncodingError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class FinalizationRegistryCallbackError extends Error { + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ + export function alloc(size: any, fill: any, encoding: any): Uint8Array; /** - * The default code given to an `FinalizationRegistryCallbackError` - */ - static get code(): number; + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ + export function allocUnsafe(size: any): Uint8Array; /** - * `FinalizationRegistryCallbackError` class constructor. - * @param {string} message - * @param {number} [code] + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + export function allocUnsafeSlow(size: any): Uint8Array; + export function isBuffer(b: any): boolean; + export function compare(a: any, b: any): 0 | 1 | -1; + export function isEncoding(encoding: any): boolean; + export function concat(list: any, length: any): Uint8Array; + export { byteLength }; } - /** - * An `IllegalConstructorError` is an error type thrown when a constructor is - * called for a class constructor when it shouldn't be. - */ - export class IllegalConstructorError extends TypeError { + export function SlowBuffer(length: any): Uint8Array; + export const INSPECT_MAX_BYTES: 50; + export const kMaxLength: 2147483647; + function byteLength(string: any, encoding: any, ...args: any[]): any; +} +declare module "socket:console" { + export function patchGlobalConsole(globalConsole: any, options?: {}): any; + export const globalConsole: globalThis.Console; + export class Console { /** - * The default code given to an `IllegalConstructorError` + * @ignore */ - static get code(): number; + constructor(options: any); /** - * `IllegalConstructorError` class constructor. - * @param {string} message - * @param {number} [code] + * @type {import('dom').Console} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `IndexSizeError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class IndexSizeError extends Error { + console: any; /** - * The code given to an `INDEX_SIZE_ERR` `DOMException` + * @type {Map} */ - static get code(): any; + timers: Map; /** - * `IndexSizeError` class constructor. - * @param {string} message - * @param {number} [code] + * @type {Map} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - export const kInternalErrorCode: unique symbol; - /** - * An `InternalError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class InternalError extends Error { + counters: Map; /** - * The default code given to an `InternalError` + * @type {function?} */ - static get code(): number; - /** - * `InternalError` class constructor. - * @param {string} message - * @param {number} [code] + postMessage: Function | null; + write(destination: any, ...args: any[]): Promise; + assert(assertion: any, ...args: any[]): void; + clear(): void; + count(label?: string): void; + countReset(label?: string): void; + debug(...args: any[]): void; + dir(...args: any[]): void; + dirxml(...args: any[]): void; + error(...args: any[]): void; + info(...args: any[]): void; + log(...args: any[]): void; + table(...args: any[]): any; + time(label?: string): void; + timeEnd(label?: string): void; + timeLog(label?: string): void; + trace(...objects: any[]): void; + warn(...args: any[]): void; + } + const _default: Console; + export default _default; +} +declare module "socket:events" { + export const Event: { + new (type: string, eventInitDict?: EventInit): Event; + prototype: Event; + readonly NONE: 0; + readonly CAPTURING_PHASE: 1; + readonly AT_TARGET: 2; + readonly BUBBLING_PHASE: 3; + } | { + new (): {}; + }; + export const EventTarget: { + new (): {}; + }; + export const CustomEvent: { + new (type: string, eventInitDict?: CustomEventInit): CustomEvent; + prototype: CustomEvent; + } | { + new (type: any, options: any): { + "__#1@#detail": any; + readonly detail: any; + }; + }; + export const MessageEvent: { + new (type: string, eventInitDict?: MessageEventInit): MessageEvent; + prototype: MessageEvent; + } | { + new (type: any, options: any): { + "__#2@#detail": any; + "__#2@#data": any; + readonly detail: any; + readonly data: any; + }; + }; + export const ErrorEvent: { + new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; + prototype: ErrorEvent; + } | { + new (type: any, options: any): { + "__#3@#detail": any; + "__#3@#error": any; + readonly detail: any; + readonly error: any; + }; + }; + export default exports; + export function EventEmitter(): void; + export class EventEmitter { + _events: any; + _eventsCount: number; + _maxListeners: number; + setMaxListeners(n: any): this; + getMaxListeners(): any; + emit(type: any, ...args: any[]): boolean; + addListener(type: any, listener: any): any; + on(arg0: any, arg1: any): any; + prependListener(type: any, listener: any): any; + once(type: any, listener: any): this; + prependOnceListener(type: any, listener: any): this; + removeListener(type: any, listener: any): this; + off(type: any, listener: any): this; + removeAllListeners(type: any, ...args: any[]): this; + listeners(type: any): any[]; + rawListeners(type: any): any[]; + listenerCount(type: any): any; + eventNames(): any; + } + export namespace EventEmitter { + export { EventEmitter }; + export let defaultMaxListeners: number; + export function init(): void; + export function listenerCount(emitter: any, type: any): any; + export { once }; + } + export function once(emitter: any, name: any): Promise; + import * as exports from "socket:events"; + +} +declare module "socket:application/menu" { + /** + * Internal IPC for setting an application menu + * @ignore + */ + export function setMenu(options: any, type: any): Promise; + /** + * Internal IPC for setting an application context menu + * @ignore + */ + export function setContextMenu(options: any): Promise; + /** + * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + */ + export class Menu extends EventTarget { + /** + * `Menu` class constructor. + * @ignore + * @param {string} type */ - constructor(message: string, code?: number, ...args: any[]); - get name(): string; + constructor(type: string); /** - * @param {number|string} + * The `Menu` instance type. + * @type {('context'|'system'|'tray')?} */ - set code(code: string | number); + get type(): "tray" | "system" | "context"; /** - * @type {number|string} + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} */ - get code(): string | number; - [exports.kInternalErrorCode]: number; - } - /** - * An `InvalidAccessError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class InvalidAccessError extends Error { + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * The code given to an `INVALID_ACCESS_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - static get code(): any; + get onerror(): (arg0: ErrorEvent) => any; /** - * `InvalidAccessError` class constructor. - * @param {string} message - * @param {number} [code] + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `NetworkError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class NetworkError extends Error { + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); /** - * The code given to an `NETWORK_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} */ - static get code(): any; + get onmenuitem(): (arg0: menuitemEvent) => any; /** - * `NetworkError` class constructor. - * @param {string} message - * @param {number} [code] + * Set the menu layout for this `Menu` instance. + * @param {string|object} layoutOrOptions + * @param {object=} [options] */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + set(layoutOrOptions: string | object, options?: object | undefined): Promise; + #private; } /** - * An `NotAllowedError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * A container for various `Menu` instances. */ - export class NotAllowedError extends Error { + export class MenuContainer extends EventTarget { /** - * The code given to an `NOT_ALLOWED_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * `MenuContainer` class constructor. + * @param {EventTarget} [sourceEventTarget] + * @param {object=} [options] */ - static get code(): any; + constructor(sourceEventTarget?: EventTarget, options?: object | undefined); /** - * `NotAllowedError` class constructor. - * @param {string} message - * @param {number} [code] + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `NotFoundError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class NotFoundError extends Error { + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * The code given to an `NOT_FOUND_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - static get code(): any; + get onerror(): (arg0: ErrorEvent) => any; /** - * `NotFoundError` class constructor. - * @param {string} message - * @param {number} [code] + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `NotSupportedError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class NotSupportedError extends Error { + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); /** - * The code given to an `NOT_SUPPORTED_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} */ - static get code(): any; + get onmenuitem(): (arg0: menuitemEvent) => any; /** - * `NotSupportedError` class constructor. - * @param {string} message - * @param {number} [code] + * The `TrayMenu` instance for the application. + * @type {TrayMenu} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + get tray(): TrayMenu; + /** + * The `SystemMenu` instance for the application. + * @type {SystemMenu} + */ + get system(): SystemMenu; + /** + * The `ContextMenu` instance for the application. + * @type {ContextMenu} + */ + get context(): ContextMenu; + #private; } /** - * An `ModuleNotFoundError` is an error type thrown when an imported or - * required module is not found. + * A `Menu` instance that represents a context menu. */ - export class ModuleNotFoundError extends exports.NotFoundError { + export class ContextMenu extends Menu { + constructor(); + } + /** + * A `Menu` instance that represents the system menu. + */ + export class SystemMenu extends Menu { + constructor(); + } + /** + * A `Menu` instance that represents the tray menu. + */ + export class TrayMenu extends Menu { + constructor(); + } + /** + * The application tray menu. + * @type {TrayMenu} + */ + export const tray: TrayMenu; + /** + * The application system menu. + * @type {SystemMenu} + */ + export const system: SystemMenu; + /** + * The application context menu. + * @type {ContextMenu} + */ + export const context: ContextMenu; + /** + * The application menus container. + * @type {MenuContainer} + */ + export const container: MenuContainer; + export default container; + import ipc from "socket:ipc"; +} +declare module "socket:internal/events" { + /** + * An event dispatched when an application URL is opening the application. + */ + export class ApplicationURLEvent extends Event { /** - * `ModuleNotFoundError` class constructor. - * @param {string} message + * `ApplicationURLEvent` class constructor. + * @param {string=} [type] + * @param {object=} [options] */ - constructor(message: string, requireStack: any); - requireStack: any; + constructor(type?: string | undefined, options?: object | undefined); + /** + * `true` if the application URL is valid (parses correctly). + * @type {boolean} + */ + get isValid(): boolean; + /** + * Data associated with the `ApplicationURLEvent`. + * @type {?any} + */ + get data(): any; + /** + * The original source URI + * @type {?string} + */ + get source(): string; + /** + * The `URL` for the `ApplicationURLEvent`. + * @type {?URL} + */ + get url(): URL; + /** + * String tag name for an `ApplicationURLEvent` instance. + * @type {string} + */ + get [Symbol.toStringTag](): string; + #private; } /** - * An `OperationError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * An event dispacted for a registered global hotkey expression. */ - export class OperationError extends Error { + export class HotKeyEvent extends MessageEvent { /** - * The code given to an `OPERATION_ERR` `DOMException` + * `HotKeyEvent` class constructor. + * @ignore + * @param {string=} [type] + * @param {object=} [data] */ - static get code(): any; + constructor(type?: string | undefined, data?: object | undefined); /** - * `OperationError` class constructor. - * @param {string} message - * @param {number} [code] + * The global unique ID for this hotkey binding. + * @type {number?} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + get id(): number; + /** + * The computed hash for this hotkey binding. + * @type {number?} + */ + get hash(): number; + /** + * The normalized hotkey expression as a sequence of tokens. + * @type {string[]} + */ + get sequence(): string[]; + /** + * The original expression of the hotkey binding. + * @type {string?} + */ + get expression(): string; } /** - * An `SecurityError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * An event dispacted when a menu item is selected. */ - export class SecurityError extends Error { + export class MenuItemEvent extends MessageEvent { /** - * The code given to an `SECURITY_ERR` `DOMException` + * `MenuItemEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [data] + * @param {import('../application/menu.js').Menu} menu */ - static get code(): any; + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** - * `SecurityError` class constructor. - * @param {string} message - * @param {number} [code] + * The `Menu` this event has been dispatched for. + * @type {import('../application/menu.js').Menu?} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + get menu(): import("socket:application/menu").Menu; + /** + * The title of the menu item. + * @type {string?} + */ + get title(): string; + /** + * An optional tag value for the menu item that may also be the + * parent menu item title. + * @type {string?} + */ + get tag(): string; + /** + * The parent title of the menu item. + * @type {string?} + */ + get parent(): string; + #private; } /** - * An `TimeoutError` is an error type thrown when an operation timesout. + * An event dispacted when the application receives an OS signal */ - export class TimeoutError extends Error { + export class SignalEvent extends MessageEvent { /** - * The code given to an `TIMEOUT_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * `SignalEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [options] */ - static get code(): any; + constructor(type?: string | undefined, options?: object | undefined); /** - * `TimeoutError` class constructor. - * @param {string} message + * The code of the signal. + * @type {import('../signal.js').signal} + */ + get code(): number; + /** + * The name of the signal. + * @type {string} */ - constructor(message: string, ...args: any[]); get name(): string; - get code(): string; + /** + * An optional message describing the signal + * @type {string} + */ + get message(): string; + #private; } - import * as exports from "socket:errors"; - + namespace _default { + export { ApplicationURLEvent }; + export { MenuItemEvent }; + export { SignalEvent }; + export { HotKeyEvent }; + } + export default _default; } -declare module "socket:buffer" { - export default Buffer; +declare module "socket:path/well-known" { /** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. + * Well known path to the user's "Downloads" folder. + * @type {?string} */ + export const DOWNLOADS: string | null; /** - * @name Buffer - * @extends {Uint8Array} + * Well known path to the user's "Documents" folder. + * @type {?string} */ - export function Buffer(arg: any, encodingOrOffset: any, length: any): any; - export class Buffer { - /** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. - */ - /** - * @name Buffer - * @extends {Uint8Array} - */ - constructor(arg: any, encodingOrOffset: any, length: any); - get parent(): any; - get offset(): any; - _isBuffer: boolean; - swap16(): this; - swap32(): this; - swap64(): this; - toString(...args: any[]): any; - toLocaleString: any; - equals(b: any): boolean; - inspect(): string; - compare(target: any, start: any, end: any, thisStart: any, thisEnd: any): 0 | 1 | -1; - includes(val: any, byteOffset: any, encoding: any): boolean; - indexOf(val: any, byteOffset: any, encoding: any): any; - lastIndexOf(val: any, byteOffset: any, encoding: any): any; - write(string: any, offset: any, length: any, encoding: any): number; - toJSON(): { - type: string; - data: any; - }; - slice(start: any, end: any): any; - readUintLE: (offset: any, byteLength: any, noAssert: any) => any; - readUIntLE(offset: any, byteLength: any, noAssert: any): any; - readUintBE: (offset: any, byteLength: any, noAssert: any) => any; - readUIntBE(offset: any, byteLength: any, noAssert: any): any; - readUint8: (offset: any, noAssert: any) => any; - readUInt8(offset: any, noAssert: any): any; - readUint16LE: (offset: any, noAssert: any) => number; - readUInt16LE(offset: any, noAssert: any): number; - readUint16BE: (offset: any, noAssert: any) => number; - readUInt16BE(offset: any, noAssert: any): number; - readUint32LE: (offset: any, noAssert: any) => number; - readUInt32LE(offset: any, noAssert: any): number; - readUint32BE: (offset: any, noAssert: any) => number; - readUInt32BE(offset: any, noAssert: any): number; - readBigUInt64LE: any; - readBigUInt64BE: any; - readIntLE(offset: any, byteLength: any, noAssert: any): any; - readIntBE(offset: any, byteLength: any, noAssert: any): any; - readInt8(offset: any, noAssert: any): any; - readInt16LE(offset: any, noAssert: any): number; - readInt16BE(offset: any, noAssert: any): number; - readInt32LE(offset: any, noAssert: any): number; - readInt32BE(offset: any, noAssert: any): number; - readBigInt64LE: any; - readBigInt64BE: any; - readFloatLE(offset: any, noAssert: any): number; - readFloatBE(offset: any, noAssert: any): number; - readDoubleLE(offset: any, noAssert: any): number; - readDoubleBE(offset: any, noAssert: any): number; - writeUintLE: (value: any, offset: any, byteLength: any, noAssert: any) => any; - writeUIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeUintBE: (value: any, offset: any, byteLength: any, noAssert: any) => any; - writeUIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeUint8: (value: any, offset: any, noAssert: any) => any; - writeUInt8(value: any, offset: any, noAssert: any): any; - writeUint16LE: (value: any, offset: any, noAssert: any) => any; - writeUInt16LE(value: any, offset: any, noAssert: any): any; - writeUint16BE: (value: any, offset: any, noAssert: any) => any; - writeUInt16BE(value: any, offset: any, noAssert: any): any; - writeUint32LE: (value: any, offset: any, noAssert: any) => any; - writeUInt32LE(value: any, offset: any, noAssert: any): any; - writeUint32BE: (value: any, offset: any, noAssert: any) => any; - writeUInt32BE(value: any, offset: any, noAssert: any): any; - writeBigUInt64LE: any; - writeBigUInt64BE: any; - writeIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeInt8(value: any, offset: any, noAssert: any): any; - writeInt16LE(value: any, offset: any, noAssert: any): any; - writeInt16BE(value: any, offset: any, noAssert: any): any; - writeInt32LE(value: any, offset: any, noAssert: any): any; - writeInt32BE(value: any, offset: any, noAssert: any): any; - writeBigInt64LE: any; - writeBigInt64BE: any; - writeFloatLE(value: any, offset: any, noAssert: any): any; - writeFloatBE(value: any, offset: any, noAssert: any): any; - writeDoubleLE(value: any, offset: any, noAssert: any): any; - writeDoubleBE(value: any, offset: any, noAssert: any): any; - copy(target: any, targetStart: any, start: any, end: any): number; - fill(val: any, start: any, end: any, encoding: any): this; - } - export namespace Buffer { - export let TYPED_ARRAY_SUPPORT: boolean; - export let poolSize: number; - /** - * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError - * if value is a number. - * Buffer.from(str[, encoding]) - * Buffer.from(array) - * Buffer.from(buffer) - * Buffer.from(arrayBuffer[, byteOffset[, length]]) - **/ - export function from(value: any, encodingOrOffset?: any, length?: any): any; - /** - * Creates a new filled Buffer instance. - * alloc(size[, fill[, encoding]]) - **/ - export function alloc(size: any, fill: any, encoding: any): Uint8Array; - /** - * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. - * */ - export function allocUnsafe(size: any): Uint8Array; - /** - * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. - */ - export function allocUnsafeSlow(size: any): Uint8Array; - export function isBuffer(b: any): boolean; - export function compare(a: any, b: any): 0 | 1 | -1; - export function isEncoding(encoding: any): boolean; - export function concat(list: any, length: any): Uint8Array; - export { byteLength }; - } - export function SlowBuffer(length: any): Uint8Array; - export const INSPECT_MAX_BYTES: 50; - export const kMaxLength: 2147483647; - function byteLength(string: any, encoding: any, ...args: any[]): any; -} -declare module "socket:url/urlpattern/urlpattern" { - export { me as URLPattern }; - var me: { - new (t: {}, r: any, n: any): { - "__#2@#i": any; - "__#2@#n": {}; - "__#2@#t": {}; - "__#2@#e": {}; - "__#2@#s": {}; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - }; - compareComponent(t: any, r: any, n: any): number; - }; -} -declare module "socket:url/url/url" { - const _default: any; - export default _default; -} -declare module "socket:querystring" { - export function unescapeBuffer(s: any, decodeSpaces: any): any; - export function unescape(s: any, decodeSpaces: any): any; - export function escape(str: any): any; - export function stringify(obj: any, sep: any, eq: any, options: any): string; - export function parse(qs: any, sep: any, eq: any, options: any): {}; - export function decode(qs: any, sep: any, eq: any, options: any): {}; - export function encode(obj: any, sep: any, eq: any, options: any): string; - namespace _default { - export { decode }; - export { encode }; - export { parse }; - export { stringify }; - export { escape }; - export { unescape }; - } - export default _default; -} -declare module "socket:url/index" { - export function parse(input: any): any; - export function resolve(from: any, to: any): any; - export function format(input: any): any; - export default URL; - export const URL: any; - import { URLPattern } from "socket:url/urlpattern/urlpattern"; - export const URLSearchParams: any; - export const parseURL: any; - export { URLPattern }; -} -declare module "socket:url" { - export * from "socket:url/index"; - export default URL; - import URL from "socket:url/index"; -} -declare module "socket:mime/index" { + export const DOCUMENTS: string | null; /** - * Look up a MIME type in various MIME databases. - * @param {string} query - * @return {Promise} + * Well known path to the user's "Pictures" folder. + * @type {?string} */ - export function lookup(query: string): Promise; + export const PICTURES: string | null; /** - * A container for a database lookup query. + * Well known path to the user's "Desktop" folder. + * @type {?string} */ - export class DatabaseQueryResult { - /** - * `DatabaseQueryResult` class constructor. - * @ignore - * @param {Database} database - * @param {string} name - * @param {string} mime - */ - constructor(database: Database, name: string, mime: string); - /** - * @type {string} - */ - name: string; - /** - * @type {string} - */ - mime: string; - database: Database; - } + export const DESKTOP: string | null; /** - * A container for MIME types by class (audio, video, text, etc) - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} + * Well known path to the user's "Videos" folder. + * @type {?string} */ - export class Database { - /** - * `Database` class constructor. - * @param {string} name - */ - constructor(name: string); - /** - * The name of the MIME database. - * @type {string} - */ - name: string; - /** - * The URL of the MIME database. - * @type {URL} - */ - url: URL; - /** - * The mapping of MIME name to the MIME "content type" - * @type {Map} - */ - map: Map; - /** - * An index of MIME "content type" to the MIME name. - * @type {Map} - */ - index: Map; - /** - * An enumeration of all database entries. - * @return {Array>} - */ - entries(): Array>; - /** - * Loads database MIME entries into internal map. - * @return {Promise} - */ - load(): Promise; - /** - * Lookup MIME type by name or content type - * @param {string} query - * @return {Promise} - */ - lookup(query: string): Promise; - } + export const VIDEOS: string | null; /** - * A database of MIME types for 'application/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} + * Well known path to the user's "Music" folder. + * @type {?string} */ - export const application: Database; + export const MUSIC: string | null; /** - * A database of MIME types for 'audio/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} + * Well known path to the application's "resources" folder. + * @type {?string} */ - export const audio: Database; + export const RESOURCES: string | null; /** - * A database of MIME types for 'font/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} + * Well known path to the application's "home" folder. + * This may be the user's HOME directory or the application container sandbox. + * @type {?string} */ - export const font: Database; + export const HOME: string | null; + namespace _default { + export { DOWNLOADS }; + export { DOCUMENTS }; + export { RESOURCES }; + export { PICTURES }; + export { DESKTOP }; + export { VIDEOS }; + export { MUSIC }; + export { HOME }; + } + export default _default; +} +declare module "socket:os" { /** - * A database of MIME types for 'image/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} + * Returns the operating system CPU architecture for which Socket was compiled. + * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' */ - export const image: Database; + export function arch(): string; /** - * A database of MIME types for 'model/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} + * Returns an array of objects containing information about each CPU/core. + * @returns {Array} cpus - An array of objects containing information about each CPU/core. + * The properties of the objects are: + * - model `` - CPU model name. + * - speed `` - CPU clock speed (in MHz). + * - times `` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. + * - user `` - Time spent by this CPU or core in user mode. + * - nice `` - Time spent by this CPU or core in user mode with low priority (nice). + * - sys `` - Time spent by this CPU or core in system mode. + * - idle `` - Time spent by this CPU or core in idle mode. + * - irq `` - Time spent by this CPU or core in IRQ mode. + * @see {@link https://nodejs.org/api/os.html#os_os_cpus} */ - export const model: Database; + export function cpus(): Array; /** - * A database of MIME types for 'multipart/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} + * Returns an object containing network interfaces that have been assigned a network address. + * @returns {object} - An object containing network interfaces that have been assigned a network address. + * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. + * The properties available on the assigned network address object include: + * - address `` - The assigned IPv4 or IPv6 address. + * - netmask `` - The IPv4 or IPv6 network mask. + * - family `` - The address family ('IPv4' or 'IPv6'). + * - mac `` - The MAC address of the network interface. + * - internal `` - Indicates whether the network interface is a loopback interface. + * - scopeid `` - The numeric scope ID (only specified when family is 'IPv6'). + * - cidr `` - The CIDR notation of the interface. + * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} */ - export const multipart: Database; + export function networkInterfaces(): object; /** - * A database of MIME types for 'text/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} + * Returns the operating system platform. + * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_platform} + * The returned value is equivalent to `process.platform`. */ - export const text: Database; + export function platform(): string; /** - * A database of MIME types for 'video/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} + * Returns the operating system name. + * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_type} */ - export const video: Database; + export function type(): string; /** - * An array of known MIME databases. Custom databases can be added to this - * array in userspace for lookup with `mime.lookup()` - * @type {Database[]} + * @returns {boolean} - `true` if the operating system is Windows. */ - export const databases: Database[]; - export class MIMEParams extends Map { - constructor(); - constructor(entries?: readonly (readonly [any, any])[]); - constructor(); - constructor(iterable?: Iterable); + export function isWindows(): boolean; + /** + * @returns {string} - The operating system's default directory for temporary files. + */ + export function tmpdir(): string; + /** + * Get resource usage. + */ + export function rusage(): any; + /** + * Returns the system uptime in seconds. + * @returns {number} - The system uptime in seconds. + */ + export function uptime(): number; + /** + * Returns the operating system name. + * @returns {string} - The operating system name. + */ + export function uname(): string; + /** + * It's implemented in process.hrtime.bigint() + * @ignore + */ + export function hrtime(): any; + /** + * Node.js doesn't have this method. + * @ignore + */ + export function availableMemory(): any; + /** + * The host operating system. This value can be one of: + * - android + * - android-emulator + * - iphoneos + * - iphone-simulator + * - linux + * - macosx + * - unix + * - unknown + * - win32 + * @ignore + * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} + */ + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + /** + * Returns the home directory of the current user. + * @return {string} + */ + export function homedir(): string; + export { constants }; + /** + * @type {string} + * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. + */ + export const EOL: string; + export default exports; + import constants from "socket:os/constants"; + import * as exports from "socket:os"; + +} +declare module "socket:signal" { + /** + * Converts an `signal` code to its corresponding string message. + * @param {import('./os/constants.js').signal} {code} + * @return {string} + */ + export function toString(code: any): string; + /** + * Gets the code for a given 'signal' name. + * @param {string|number} name + * @return {signal} + */ + export function getCode(name: string | number): signal; + /** + * Gets the name for a given 'signal' code + * @return {string} + * @param {string|number} code + */ + export function getName(code: string | number): string; + /** + * Gets the message for a 'signal' code. + * @param {number|string} code + * @return {string} + */ + export function getMessage(code: number | string): string; + export { constants }; + export const channel: BroadcastChannel; + export const SIGHUP: number; + export const SIGINT: number; + export const SIGQUIT: number; + export const SIGILL: number; + export const SIGTRAP: number; + export const SIGABRT: number; + export const SIGIOT: number; + export const SIGBUS: number; + export const SIGFPE: number; + export const SIGKILL: number; + export const SIGUSR1: number; + export const SIGSEGV: number; + export const SIGUSR2: number; + export const SIGPIPE: number; + export const SIGALRM: number; + export const SIGTERM: number; + export const SIGCHLD: number; + export const SIGCONT: number; + export const SIGSTOP: number; + export const SIGTSTP: number; + export const SIGTTIN: number; + export const SIGTTOU: number; + export const SIGURG: number; + export const SIGXCPU: number; + export const SIGXFSZ: number; + export const SIGVTALRM: number; + export const SIGPROF: number; + export const SIGWINCH: number; + export const SIGIO: number; + export const SIGINFO: number; + export const SIGSYS: number; + export const strings: { + [x: number]: string; + }; + namespace _default { + export { constants }; + export { channel }; + export { strings }; + export { toString }; + export { getName }; + export { getCode }; + export { getMessage }; } - export class MIMEType { - constructor(input: any); - set type(value: any); - get type(): any; - set subtype(value: any); - get subtype(): any; - get essence(): string; - toString(): string; - toJSON(): string; - #private; + export default _default; + export type signal = import("socket:os/constants").signal; + import { signal as constants } from "socket:os/constants"; +} +declare module "socket:process" { + /** + * Adds callback to the 'nextTick' queue. + * @param {Function} callback + */ + export function nextTick(callback: Function): void; + /** + * Computed high resolution time as a `BigInt`. + * @param {Array?} [time] + * @return {bigint} + */ + export function hrtime(time?: Array | null): bigint; + export namespace hrtime { + function bigint(): any; } + /** + * @param {number=} [code=0] - The exit code. Default: 0. + */ + export function exit(code?: number | undefined): Promise; + /** + * Returns an object describing the memory usage of the Node.js process measured in bytes. + * @returns {Object} + */ + export function memoryUsage(): any; + export namespace memoryUsage { + function rss(): any; + } + export class ProcessEnvironmentEvent extends Event { + constructor(type: any, key: any, value: any); + key: any; + value: any; + } + export const env: any; + export default process; + const process: any; +} +declare module "socket:location" { + export function toString(): string; + export const globalLocation: Location | { + origin: string; + host: string; + hostname: string; + pathname: string; + href: string; + }; + export const href: string; + export const protocol: "socket:"; + export const hostname: string; + export const host: string; + export const search: string; + export const hash: string; + export const pathname: string; + export const origin: string; namespace _default { - export { Database }; - export { databases }; - export { lookup }; - export { MIMEParams }; - export { MIMEType }; - export { application }; - export { audio }; - export { font }; - export { image }; - export { model }; - export { multipart }; - export { text }; - export { video }; + export { origin }; + export { href }; + export { protocol }; + export { hostname }; + export { host }; + export { search }; + export { pathname }; + export { toString }; } export default _default; } -declare module "socket:mime" { - export * from "socket:mime/index"; - export default exports; - import * as exports from "socket:mime/index"; -} -declare module "socket:util" { - export function debug(section: any): { - (...args: any[]): void; - enabled: boolean; +declare module "socket:url/urlpattern/urlpattern" { + export { me as URLPattern }; + var me: { + new (t: {}, r: any, n: any): { + "__#10@#i": any; + "__#10@#n": {}; + "__#10@#t": {}; + "__#10@#e": {}; + "__#10@#s": {}; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + }; + compareComponent(t: any, r: any, n: any): number; }; - export function hasOwnProperty(object: any, property: any): any; - export function isDate(object: any): boolean; - export function isTypedArray(object: any): boolean; - export function isArrayLike(object: any): boolean; - export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; - export function isNumber(value: any): boolean; - export function isBoolean(value: any): boolean; - export function isArrayBufferView(buf: any): boolean; - export function isAsyncFunction(object: any): boolean; - export function isArgumentsObject(object: any): boolean; - export function isEmptyObject(object: any): boolean; - export function isObject(object: any): boolean; - export function isUndefined(value: any): boolean; - export function isNull(value: any): boolean; - export function isNullOrUndefined(value: any): boolean; - export function isPrimitive(value: any): boolean; - export function isRegExp(value: any): boolean; - export function isPlainObject(object: any): boolean; - export function isArrayBuffer(object: any): boolean; - export function isBufferLike(object: any): boolean; - export function isFunction(value: any): boolean; - export function isErrorLike(error: any): boolean; - export function isClass(value: any): boolean; - export function isBuffer(value: any): boolean; - export function isPromiseLike(object: any): boolean; - export function toString(object: any): string; - export function toBuffer(object: any, encoding?: any): any; - export function toProperCase(string: any): any; - export function splitBuffer(buffer: any, highWaterMark: any): any[]; - export function InvertedPromise(): Promise; - export function clamp(value: any, min: any, max: any): number; - export function promisify(original: any): any; - export function inspect(value: any, options: any): any; - export namespace inspect { - let custom: symbol; - let ignore: symbol; - } - export function format(format: any, ...args: any[]): string; - export function parseJSON(string: any): any; - export function parseHeaders(headers: any): string[][]; - export function noop(): void; - export function isValidPercentageValue(input: any): boolean; - export function compareBuffers(a: any, b: any): any; - export function inherits(Constructor: any, Super: any): void; - export function deprecate(...args: any[]): void; - export const TextDecoder: { - new (label?: string, options?: TextDecoderOptions): TextDecoder; - prototype: TextDecoder; - }; - export const TextEncoder: { - new (): TextEncoder; - prototype: TextEncoder; - }; - export const isArray: any; - export class IllegalConstructor { - } - export const MIMEType: typeof mime.MIMEType; - export const MIMEParams: typeof mime.MIMEParams; - export default exports; - import mime from "socket:mime"; - import * as exports from "socket:util"; - } -declare module "socket:window/constants" { - export const WINDOW_ERROR: -1; - export const WINDOW_NONE: 0; - export const WINDOW_CREATING: 10; - export const WINDOW_CREATED: 11; - export const WINDOW_HIDING: 20; - export const WINDOW_HIDDEN: 21; - export const WINDOW_SHOWING: 30; - export const WINDOW_SHOWN: 31; - export const WINDOW_CLOSING: 40; - export const WINDOW_CLOSED: 41; - export const WINDOW_EXITING: 50; - export const WINDOW_EXITED: 51; - export const WINDOW_KILLING: 60; - export const WINDOW_KILLED: 61; - export default exports; - import * as exports from "socket:window/constants"; - +declare module "socket:url/url/url" { + const _default: any; + export default _default; } -declare module "socket:location" { - export function toString(): string; - export const globalLocation: Location | { - origin: string; - host: string; - hostname: string; - pathname: string; - href: string; - }; - export const href: string; - export const protocol: "socket:"; - export const hostname: string; - export const host: string; - export const search: string; - export const hash: string; - export const pathname: string; - export const origin: string; +declare module "socket:querystring" { + export function unescapeBuffer(s: any, decodeSpaces: any): any; + export function unescape(s: any, decodeSpaces: any): any; + export function escape(str: any): any; + export function stringify(obj: any, sep: any, eq: any, options: any): string; + export function parse(qs: any, sep: any, eq: any, options: any): {}; + export function decode(qs: any, sep: any, eq: any, options: any): {}; + export function encode(obj: any, sep: any, eq: any, options: any): string; namespace _default { - export { origin }; - export { href }; - export { protocol }; - export { hostname }; - export { host }; - export { search }; - export { pathname }; - export { toString }; + export { decode }; + export { encode }; + export { parse }; + export { stringify }; + export { escape }; + export { unescape }; } export default _default; } -declare module "socket:console" { - export function patchGlobalConsole(globalConsole: any, options?: {}): any; - export const globalConsole: globalThis.Console; - export class Console { +declare module "socket:url/index" { + export function parse(input: any): any; + export function resolve(from: any, to: any): any; + export function format(input: any): any; + export default URL; + export const URL: any; + import { URLPattern } from "socket:url/urlpattern/urlpattern"; + export const URLSearchParams: any; + export const parseURL: any; + export { URLPattern }; +} +declare module "socket:url" { + export * from "socket:url/index"; + export default URL; + import URL from "socket:url/index"; +} +declare module "socket:path/path" { + /** + * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. + * @param {strig} ...paths + * @returns {string} + * @see {@link https://nodejs.org/api/path.html#path_path_resolve_paths} + */ + export function resolve(options: any, ...components: any[]): string; + /** + * Computes current working directory for a path + * @param {object=} [opts] + * @param {boolean=} [opts.posix] Set to `true` to force POSIX style path + * @return {string} + */ + export function cwd(opts?: object | undefined): string; + /** + * Computed location origin. Defaults to `socket:///` if not available. + * @return {string} + */ + export function origin(): string; + /** + * Computes the relative path from `from` to `to`. + * @param {object} options + * @param {PathComponent} from + * @param {PathComponent} to + * @return {string} + */ + export function relative(options: object, from: PathComponent, to: PathComponent): string; + /** + * Joins path components. This function may not return an absolute path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} + */ + export function join(options: object, ...components: PathComponent[]): string; + /** + * Computes directory name of path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} + */ + export function dirname(options: object, path: any): string; + /** + * Computes base name of path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} + */ + export function basename(options: object, path: any): string; + /** + * Computes extension name of path. + * @param {object} options + * @param {PathComponent} path + * @return {string} + */ + export function extname(options: object, path: PathComponent): string; + /** + * Computes normalized path + * @param {object} options + * @param {PathComponent} path + * @return {string} + */ + export function normalize(options: object, path: PathComponent): string; + /** + * Formats `Path` object into a string. + * @param {object} options + * @param {object|Path} path + * @return {string} + */ + export function format(options: object, path: object | Path): string; + /** + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {object} + */ + export function parse(path: PathComponent): object; + /** + * @typedef {(string|Path|URL|{ pathname: string }|{ url: string)} PathComponent + */ + /** + * A container for a parsed Path. + */ + export class Path { /** - * @ignore + * Creates a `Path` instance from `input` and optional `cwd`. + * @param {PathComponent} input + * @param {string} [cwd] */ - constructor(options: any); + static from(input: PathComponent, cwd?: string): any; /** - * @type {import('dom').Console} + * `Path` class constructor. + * @protected + * @param {string} pathname + * @param {string} [cwd = Path.cwd()] */ - console: any; + protected constructor(); + pattern: { + "__#10@#i": any; + "__#10@#n": {}; + "__#10@#t": {}; + "__#10@#e": {}; + "__#10@#s": {}; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + }; + url: any; + get pathname(): any; + get protocol(): any; + get href(): any; /** - * @type {Map} + * `true` if the path is relative, otherwise `false. + * @type {boolean} */ - timers: Map; + get isRelative(): boolean; /** - * @type {Map} + * The working value of this path. */ - counters: Map; + get value(): any; /** - * @type {function?} + * The original source, unresolved. + * @type {string} */ - postMessage: Function | null; - write(destination: any, ...args: any[]): Promise; - assert(assertion: any, ...args: any[]): void; - clear(): void; - count(label?: string): void; - countReset(label?: string): void; - debug(...args: any[]): void; - dir(...args: any[]): void; - dirxml(...args: any[]): void; - error(...args: any[]): void; - info(...args: any[]): void; - log(...args: any[]): void; - table(...args: any[]): any; - time(label?: string): void; - timeEnd(label?: string): void; - timeLog(label?: string): void; - trace(...objects: any[]): void; - warn(...args: any[]): void; - } - const _default: Console; - export default _default; -} -declare module "socket:events" { - export const Event: { - new (type: string, eventInitDict?: EventInit): Event; - prototype: Event; - readonly NONE: 0; - readonly CAPTURING_PHASE: 1; - readonly AT_TARGET: 2; - readonly BUBBLING_PHASE: 3; - } | { - new (): {}; - }; - export const EventTarget: { - new (): {}; - }; - export const CustomEvent: { - new (type: string, eventInitDict?: CustomEventInit): CustomEvent; - prototype: CustomEvent; - } | { - new (type: any, options: any): { - "__#4@#detail": any; - readonly detail: any; - }; - }; - export const MessageEvent: { - new (type: string, eventInitDict?: MessageEventInit): MessageEvent; - prototype: MessageEvent; - } | { - new (type: any, options: any): { - "__#5@#detail": any; - "__#5@#data": any; - readonly detail: any; - readonly data: any; - }; - }; - export const ErrorEvent: { - new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; - prototype: ErrorEvent; - } | { - new (type: any, options: any): { - "__#6@#detail": any; - "__#6@#error": any; - readonly detail: any; - readonly error: any; + get source(): string; + /** + * Computed parent path. + * @type {string} + */ + get parent(): string; + /** + * Computed root in path. + * @type {string} + */ + get root(): string; + /** + * Computed directory name in path. + * @type {string} + */ + get dir(): string; + /** + * Computed base name in path. + * @type {string} + */ + get base(): string; + /** + * Computed base name in path without path extension. + * @type {string} + */ + get name(): string; + /** + * Computed extension name in path. + * @type {string} + */ + get ext(): string; + /** + * The computed drive, if given in the path. + * @type {string?} + */ + get drive(): string; + /** + * @return {URL} + */ + toURL(): URL; + /** + * Converts this `Path` instance to a string. + * @return {string} + */ + toString(): string; + /** + * @ignore + */ + inspect(): { + root: string; + dir: string; + base: string; + ext: string; + name: string; }; - }; - export default exports; - export function EventEmitter(): void; - export class EventEmitter { - _events: any; - _eventsCount: number; - _maxListeners: number; - setMaxListeners(n: any): this; - getMaxListeners(): any; - emit(type: any, ...args: any[]): boolean; - addListener(type: any, listener: any): any; - on(arg0: any, arg1: any): any; - prependListener(type: any, listener: any): any; - once(type: any, listener: any): this; - prependOnceListener(type: any, listener: any): this; - removeListener(type: any, listener: any): this; - off(type: any, listener: any): this; - removeAllListeners(type: any, ...args: any[]): this; - listeners(type: any): any[]; - rawListeners(type: any): any[]; - listenerCount(type: any): any; - eventNames(): any; - } - export namespace EventEmitter { - export { EventEmitter }; - export let defaultMaxListeners: number; - export function init(): void; - export function listenerCount(emitter: any, type: any): any; - export { once }; + /** + * @ignore + */ + [Symbol.toStringTag](): string; + #private; } - export function once(emitter: any, name: any): Promise; - import * as exports from "socket:events"; - + export default Path; + export type PathComponent = (string | Path | URL | { + pathname: string; + } | { + url: string; + }); + import { URL } from "socket:url/index"; } -declare module "socket:path/well-known" { +declare module "socket:path/win32" { /** - * Well known path to the user's "Downloads" folder. - * @type {?string} + * Computes current working directory for a path + * @param {string} */ - export const DOWNLOADS: string | null; + export function cwd(): any; /** - * Well known path to the user's "Documents" folder. - * @type {?string} + * Resolves path components to an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export const DOCUMENTS: string | null; + export function resolve(...components: PathComponent[]): string; /** - * Well known path to the user's "Pictures" folder. - * @type {?string} + * Joins path components. This function may not return an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export const PICTURES: string | null; + export function join(...components: PathComponent[]): string; /** - * Well known path to the user's "Desktop" folder. - * @type {?string} + * Computes directory name of path. + * @param {PathComponent} path + * @return {string} */ - export const DESKTOP: string | null; + export function dirname(path: PathComponent): string; /** - * Well known path to the user's "Videos" folder. - * @type {?string} + * Computes base name of path. + * @param {PathComponent} path + * @param {string} suffix + * @return {string} */ - export const VIDEOS: string | null; + export function basename(path: PathComponent, suffix: string): string; /** - * Well known path to the user's "Music" folder. - * @type {?string} + * Computes extension name of path. + * @param {PathComponent} path + * @return {string} */ - export const MUSIC: string | null; + export function extname(path: PathComponent): string; /** - * Well known path to the application's "resources" folder. - * @type {?string} + * Predicate helper to determine if path is absolute. + * @param {PathComponent} path + * @return {boolean} */ - export const RESOURCES: string | null; + export function isAbsolute(path: PathComponent): boolean; /** - * Well known path to the application's "home" folder. - * This may be the user's HOME directory or the application container sandbox. - * @type {?string} + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} */ - export const HOME: string | null; - namespace _default { - export { DOWNLOADS }; - export { DOCUMENTS }; - export { RESOURCES }; - export { PICTURES }; - export { DESKTOP }; - export { VIDEOS }; - export { MUSIC }; - export { HOME }; - } - export default _default; -} -declare module "socket:os" { + export function parse(path: PathComponent): Path; /** - * Returns the operating system CPU architecture for which Socket was compiled. - * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' + * Formats `Path` object into a string. + * @param {object|Path} path + * @return {string} */ - export function arch(): string; + export function format(path: object | Path): string; /** - * Returns an array of objects containing information about each CPU/core. - * @returns {Array} cpus - An array of objects containing information about each CPU/core. - * The properties of the objects are: - * - model `` - CPU model name. - * - speed `` - CPU clock speed (in MHz). - * - times `` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. - * - user `` - Time spent by this CPU or core in user mode. - * - nice `` - Time spent by this CPU or core in user mode with low priority (nice). - * - sys `` - Time spent by this CPU or core in system mode. - * - idle `` - Time spent by this CPU or core in idle mode. - * - irq `` - Time spent by this CPU or core in IRQ mode. - * @see {@link https://nodejs.org/api/os.html#os_os_cpus} + * Normalizes `path` resolving `..` and `.\` preserving trailing + * slashes. + * @param {string} path */ - export function cpus(): Array; - /** - * Returns an object containing network interfaces that have been assigned a network address. - * @returns {object} - An object containing network interfaces that have been assigned a network address. - * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. - * The properties available on the assigned network address object include: - * - address `` - The assigned IPv4 or IPv6 address. - * - netmask `` - The IPv4 or IPv6 network mask. - * - family `` - The address family ('IPv4' or 'IPv6'). - * - mac `` - The MAC address of the network interface. - * - internal `` - Indicates whether the network interface is a loopback interface. - * - scopeid `` - The numeric scope ID (only specified when family is 'IPv6'). - * - cidr `` - The CIDR notation of the interface. - * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} - */ - export function networkInterfaces(): object; - /** - * Returns the operating system platform. - * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_platform} - * The returned value is equivalent to `process.platform`. - */ - export function platform(): string; - /** - * Returns the operating system name. - * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_type} - */ - export function type(): string; - /** - * @returns {boolean} - `true` if the operating system is Windows. - */ - export function isWindows(): boolean; - /** - * @returns {string} - The operating system's default directory for temporary files. - */ - export function tmpdir(): string; - /** - * Get resource usage. - */ - export function rusage(): any; - /** - * Returns the system uptime in seconds. - * @returns {number} - The system uptime in seconds. - */ - export function uptime(): number; - /** - * Returns the operating system name. - * @returns {string} - The operating system name. - */ - export function uname(): string; - /** - * It's implemented in process.hrtime.bigint() - * @ignore - */ - export function hrtime(): any; - /** - * Node.js doesn't have this method. - * @ignore - */ - export function availableMemory(): any; - /** - * The host operating system. This value can be one of: - * - android - * - android-emulator - * - iphoneos - * - iphone-simulator - * - linux - * - macosx - * - unix - * - unknown - * - win32 - * @ignore - * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} - */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function normalize(path: string): any; /** - * Returns the home directory of the current user. + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to * @return {string} */ - export function homedir(): string; - /** - * @type {string} - * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. - */ - export const EOL: string; + export function relative(from: string, to: string): string; export default exports; - import * as exports from "socket:os"; - -} -declare module "socket:process" { - /** - * Adds callback to the 'nextTick' queue. - * @param {Function} callback - */ - export function nextTick(callback: Function): void; - /** - * Computed high resolution time as a `BigInt`. - * @param {Array?} [time] - * @return {bigint} - */ - export function hrtime(time?: Array | null): bigint; - export namespace hrtime { - function bigint(): any; - } - /** - * @param {number=} [code=0] - The exit code. Default: 0. - */ - export function exit(code?: number | undefined): Promise; - /** - * Returns an object describing the memory usage of the Node.js process measured in bytes. - * @returns {Object} - */ - export function memoryUsage(): any; - export namespace memoryUsage { - function rss(): any; - } - export class ProcessEnvironmentEvent extends Event { - constructor(type: any, key: any, value: any); - key: any; - value: any; + export namespace win32 { + let sep: "\\"; + let delimiter: ";"; } - export const env: any; - export default process; - const process: any; + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; + import * as posix from "socket:path/posix"; + import { RESOURCES } from "socket:path/well-known"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import * as exports from "socket:path/win32"; + + export { posix, Path, RESOURCES, DOWNLOADS, DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC }; } -declare module "socket:path/path" { - /** - * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. - * @param {strig} ...paths - * @returns {string} - * @see {@link https://nodejs.org/api/path.html#path_path_resolve_paths} - */ - export function resolve(options: any, ...components: any[]): string; +declare module "socket:path/posix" { /** * Computes current working directory for a path - * @param {object=} [opts] - * @param {boolean=} [opts.posix] Set to `true` to force POSIX style path - * @return {string} - */ - export function cwd(opts?: object | undefined): string; - /** - * Computed location origin. Defaults to `socket:///` if not available. + * @param {string} * @return {string} */ - export function origin(): string; + export function cwd(): string; /** - * Computes the relative path from `from` to `to`. - * @param {object} options - * @param {PathComponent} from - * @param {PathComponent} to + * Resolves path components to an absolute path. + * @param {...PathComponent} components * @return {string} */ - export function relative(options: object, from: PathComponent, to: PathComponent): string; + export function resolve(...components: PathComponent[]): string; /** * Joins path components. This function may not return an absolute path. - * @param {object} options * @param {...PathComponent} components * @return {string} */ - export function join(options: object, ...components: PathComponent[]): string; + export function join(...components: PathComponent[]): string; /** * Computes directory name of path. - * @param {object} options - * @param {...PathComponent} components + * @param {PathComponent} path * @return {string} */ - export function dirname(options: object, path: any): string; + export function dirname(path: PathComponent): string; /** * Computes base name of path. - * @param {object} options - * @param {...PathComponent} components + * @param {PathComponent} path + * @param {string} suffix * @return {string} */ - export function basename(options: object, path: any): string; + export function basename(path: PathComponent, suffix: string): string; /** * Computes extension name of path. - * @param {object} options * @param {PathComponent} path * @return {string} */ - export function extname(options: object, path: PathComponent): string; + export function extname(path: PathComponent): string; /** - * Computes normalized path - * @param {object} options + * Predicate helper to determine if path is absolute. * @param {PathComponent} path - * @return {string} + * @return {boolean} */ - export function normalize(options: object, path: PathComponent): string; + export function isAbsolute(path: PathComponent): boolean; + /** + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} + */ + export function parse(path: PathComponent): Path; /** * Formats `Path` object into a string. - * @param {object} options * @param {object|Path} path * @return {string} */ - export function format(options: object, path: object | Path): string; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {object} - */ - export function parse(path: PathComponent): object; + export function format(path: object | Path): string; /** - * @typedef {(string|Path|URL|{ pathname: string }|{ url: string)} PathComponent + * Normalizes `path` resolving `..` and `./` preserving trailing + * slashes. + * @param {string} path */ + export function normalize(path: string): any; /** - * A container for a parsed Path. - */ - export class Path { - /** - * Creates a `Path` instance from `input` and optional `cwd`. - * @param {PathComponent} input - * @param {string} [cwd] - */ - static from(input: PathComponent, cwd?: string): any; - /** - * `Path` class constructor. - * @protected - * @param {string} pathname - * @param {string} [cwd = Path.cwd()] - */ - protected constructor(); - pattern: { - "__#2@#i": any; - "__#2@#n": {}; - "__#2@#t": {}; - "__#2@#e": {}; - "__#2@#s": {}; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - }; - url: any; - get pathname(): any; - get protocol(): any; - get href(): any; - /** - * `true` if the path is relative, otherwise `false. - * @type {boolean} - */ - get isRelative(): boolean; - /** - * The working value of this path. - */ - get value(): any; - /** - * The original source, unresolved. - * @type {string} - */ - get source(): string; - /** - * Computed parent path. - * @type {string} - */ - get parent(): string; - /** - * Computed root in path. - * @type {string} - */ - get root(): string; - /** - * Computed directory name in path. - * @type {string} - */ - get dir(): string; - /** - * Computed base name in path. - * @type {string} - */ - get base(): string; - /** - * Computed base name in path without path extension. - * @type {string} - */ - get name(): string; - /** - * Computed extension name in path. - * @type {string} - */ - get ext(): string; - /** - * The computed drive, if given in the path. - * @type {string?} - */ - get drive(): string; - /** - * @return {URL} - */ - toURL(): URL; - /** - * Converts this `Path` instance to a string. - * @return {string} - */ - toString(): string; - /** - * @ignore - */ - inspect(): { - root: string; - dir: string; - base: string; - ext: string; - name: string; - }; - /** - * @ignore - */ - [Symbol.toStringTag](): string; - #private; - } - export default Path; - export type PathComponent = (string | Path | URL | { - pathname: string; - } | { - url: string; - }); - import { URL } from "socket:url/index"; -} -declare module "socket:path/win32" { - /** - * Computes current working directory for a path - * @param {string} - */ - export function cwd(): any; - /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function resolve(...components: PathComponent[]): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function join(...components: PathComponent[]): string; - /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} - */ - export function dirname(path: PathComponent): string; - /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string} suffix - * @return {string} - */ - export function basename(path: PathComponent, suffix: string): string; - /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} - */ - export function extname(path: PathComponent): string; - /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} - */ - export function isAbsolute(path: PathComponent): boolean; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} - */ - export function parse(path: PathComponent): Path; - /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} - */ - export function format(path: object | Path): string; - /** - * Normalizes `path` resolving `..` and `.\` preserving trailing - * slashes. - * @param {string} path - */ - export function normalize(path: string): any; - /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} - */ - export function relative(from: string, to: string): string; - export default exports; - export namespace win32 { - let sep: "\\"; - let delimiter: ";"; - } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as posix from "socket:path/posix"; - import { RESOURCES } from "socket:path/well-known"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import * as exports from "socket:path/win32"; - - export { posix, Path, RESOURCES, DOWNLOADS, DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC }; -} -declare module "socket:path/posix" { - /** - * Computes current working directory for a path - * @param {string} - * @return {string} - */ - export function cwd(): string; - /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function resolve(...components: PathComponent[]): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function join(...components: PathComponent[]): string; - /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} - */ - export function dirname(path: PathComponent): string; - /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string} suffix - * @return {string} - */ - export function basename(path: PathComponent, suffix: string): string; - /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} - */ - export function extname(path: PathComponent): string; - /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} - */ - export function isAbsolute(path: PathComponent): boolean; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} - */ - export function parse(path: PathComponent): Path; - /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} - */ - export function format(path: object | Path): string; - /** - * Normalizes `path` resolving `..` and `./` preserving trailing - * slashes. - * @param {string} path - */ - export function normalize(path: string): any; - /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} */ export function relative(from: string, to: string): string; export default exports; @@ -4170,777 +3963,1349 @@ declare module "socket:ipc" { import * as exports from "socket:ipc"; } -declare module "socket:application/menu" { +declare module "socket:os/constants" { + export type errno = number; + export namespace errno { + let E2BIG: errno; + let EACCES: errno; + let EADDRINUSE: errno; + let EADDRNOTAVAIL: errno; + let EAFNOSUPPORT: errno; + let EAGAIN: errno; + let EALREADY: errno; + let EBADF: errno; + let EBADMSG: errno; + let EBUSY: errno; + let ECANCELED: errno; + let ECHILD: errno; + let ECONNABORTED: errno; + let ECONNREFUSED: errno; + let ECONNRESET: errno; + let EDEADLK: errno; + let EDESTADDRREQ: errno; + let EDOM: errno; + let EDQUOT: errno; + let EEXIST: errno; + let EFAULT: errno; + let EFBIG: errno; + let EHOSTUNREACH: errno; + let EIDRM: errno; + let EILSEQ: errno; + let EINPROGRESS: errno; + let EINTR: errno; + let EINVAL: errno; + let EIO: errno; + let EISCONN: errno; + let EISDIR: errno; + let ELOOP: errno; + let EMFILE: errno; + let EMLINK: errno; + let EMSGSIZE: errno; + let EMULTIHOP: errno; + let ENAMETOOLONG: errno; + let ENETDOWN: errno; + let ENETRESET: errno; + let ENETUNREACH: errno; + let ENFILE: errno; + let ENOBUFS: errno; + let ENODATA: errno; + let ENODEV: errno; + let ENOENT: errno; + let ENOEXEC: errno; + let ENOLCK: errno; + let ENOLINK: errno; + let ENOMEM: errno; + let ENOMSG: errno; + let ENOPROTOOPT: errno; + let ENOSPC: errno; + let ENOSR: errno; + let ENOSTR: errno; + let ENOSYS: errno; + let ENOTCONN: errno; + let ENOTDIR: errno; + let ENOTEMPTY: errno; + let ENOTSOCK: errno; + let ENOTSUP: errno; + let ENOTTY: errno; + let ENXIO: errno; + let EOPNOTSUPP: errno; + let EOVERFLOW: errno; + let EPERM: errno; + let EPIPE: errno; + let EPROTO: errno; + let EPROTONOSUPPORT: errno; + let EPROTOTYPE: errno; + let ERANGE: errno; + let EROFS: errno; + let ESPIPE: errno; + let ESRCH: errno; + let ESTALE: errno; + let ETIME: errno; + let ETIMEDOUT: errno; + let ETXTBSY: errno; + let EWOULDBLOCK: errno; + let EXDEV: errno; + } + export type signal = number; + export namespace signal { + let SIGHUP: signal; + let SIGINT: signal; + let SIGQUIT: signal; + let SIGILL: signal; + let SIGTRAP: signal; + let SIGABRT: signal; + let SIGIOT: signal; + let SIGBUS: signal; + let SIGFPE: signal; + let SIGKILL: signal; + let SIGUSR1: signal; + let SIGSEGV: signal; + let SIGUSR2: signal; + let SIGPIPE: signal; + let SIGALRM: signal; + let SIGTERM: signal; + let SIGCHLD: signal; + let SIGCONT: signal; + let SIGSTOP: signal; + let SIGTSTP: signal; + let SIGTTIN: signal; + let SIGTTOU: signal; + let SIGURG: signal; + let SIGXCPU: signal; + let SIGXFSZ: signal; + let SIGVTALRM: signal; + let SIGPROF: signal; + let SIGWINCH: signal; + let SIGIO: signal; + let SIGINFO: signal; + let SIGSYS: signal; + } + namespace _default { + export { errno }; + export { signal }; + } + export default _default; +} +declare module "socket:errno" { /** - * Internal IPC for setting an application menu - * @ignore + * Converts an `errno` code to its corresponding string message. + * @param {import('./os/constants.js').errno} {code} + * @return {string} */ - export function setMenu(options: any, type: any): Promise; + export function toString(code: any): string; /** - * Internal IPC for setting an application context menu - * @ignore + * Gets the code for a given 'errno' name. + * @param {string|number} name + * @return {errno} */ - export function setContextMenu(options: any): Promise; + export function getCode(name: string | number): errno; /** - * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + * Gets the name for a given 'errno' code + * @return {string} + * @param {string|number} code */ - export class Menu extends EventTarget { - /** - * `Menu` class constructor. - * @ignore - * @param {string} type - */ - constructor(type: string); - /** - * The `Menu` instance type. - * @type {('context'|'system'|'tray')?} - */ - get type(): "tray" | "system" | "context"; - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); + export function getName(code: string | number): string; + /** + * Gets the message for a 'errno' code. + * @param {number|string} code + * @return {string} + */ + export function getMessage(code: number | string): string; + /** + * @typedef {import('./os/constants.js').errno} errno + */ + export const E2BIG: number; + export const EACCES: number; + export const EADDRINUSE: number; + export const EADDRNOTAVAIL: number; + export const EAFNOSUPPORT: number; + export const EAGAIN: number; + export const EALREADY: number; + export const EBADF: number; + export const EBADMSG: number; + export const EBUSY: number; + export const ECANCELED: number; + export const ECHILD: number; + export const ECONNABORTED: number; + export const ECONNREFUSED: number; + export const ECONNRESET: number; + export const EDEADLK: number; + export const EDESTADDRREQ: number; + export const EDOM: number; + export const EDQUOT: number; + export const EEXIST: number; + export const EFAULT: number; + export const EFBIG: number; + export const EHOSTUNREACH: number; + export const EIDRM: number; + export const EILSEQ: number; + export const EINPROGRESS: number; + export const EINTR: number; + export const EINVAL: number; + export const EIO: number; + export const EISCONN: number; + export const EISDIR: number; + export const ELOOP: number; + export const EMFILE: number; + export const EMLINK: number; + export const EMSGSIZE: number; + export const EMULTIHOP: number; + export const ENAMETOOLONG: number; + export const ENETDOWN: number; + export const ENETRESET: number; + export const ENETUNREACH: number; + export const ENFILE: number; + export const ENOBUFS: number; + export const ENODATA: number; + export const ENODEV: number; + export const ENOENT: number; + export const ENOEXEC: number; + export const ENOLCK: number; + export const ENOLINK: number; + export const ENOMEM: number; + export const ENOMSG: number; + export const ENOPROTOOPT: number; + export const ENOSPC: number; + export const ENOSR: number; + export const ENOSTR: number; + export const ENOSYS: number; + export const ENOTCONN: number; + export const ENOTDIR: number; + export const ENOTEMPTY: number; + export const ENOTSOCK: number; + export const ENOTSUP: number; + export const ENOTTY: number; + export const ENXIO: number; + export const EOPNOTSUPP: number; + export const EOVERFLOW: number; + export const EPERM: number; + export const EPIPE: number; + export const EPROTO: number; + export const EPROTONOSUPPORT: number; + export const EPROTOTYPE: number; + export const ERANGE: number; + export const EROFS: number; + export const ESPIPE: number; + export const ESRCH: number; + export const ESTALE: number; + export const ETIME: number; + export const ETIMEDOUT: number; + export const ETXTBSY: number; + export const EWOULDBLOCK: number; + export const EXDEV: number; + export const strings: { + [x: number]: string; + }; + export { constants }; + namespace _default { + export { constants }; + export { strings }; + export { toString }; + export { getCode }; + export { getMessage }; + } + export default _default; + export type errno = import("socket:os/constants").errno; + import { errno as constants } from "socket:os/constants"; +} +declare module "socket:errors" { + export default exports; + export const ABORT_ERR: any; + export const ENCODING_ERR: any; + export const INVALID_ACCESS_ERR: any; + export const INDEX_SIZE_ERR: any; + export const NETWORK_ERR: any; + export const NOT_ALLOWED_ERR: any; + export const NOT_FOUND_ERR: any; + export const NOT_SUPPORTED_ERR: any; + export const OPERATION_ERR: any; + export const SECURITY_ERR: any; + export const TIMEOUT_ERR: any; + /** + * An `AbortError` is an error type thrown in an `onabort()` level 0 + * event handler on an `AbortSignal` instance. + */ + export class AbortError extends Error { /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} + * The code given to an `ABORT_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - get onerror(): (arg0: ErrorEvent) => any; + static get code(): any; /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} + * `AbortError` class constructor. + * @param {AbortSignal|string} reasonOrSignal + * @param {AbortSignal=} [signal] */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + constructor(reason: any, signal?: AbortSignal | undefined, ...args: any[]); + signal: AbortSignal; + get name(): string; + get code(): string; + } + /** + * An `BadRequestError` is an error type thrown in an `onabort()` level 0 + * event handler on an `BadRequestSignal` instance. + */ + export class BadRequestError extends Error { /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} + * The default code given to a `BadRequestError` */ - get onmenuitem(): (arg0: menuitemEvent) => any; + static get code(): number; /** - * Set the menu layout for this `Menu` instance. - * @param {string|object} layoutOrOptions - * @param {object=} [options] + * `BadRequestError` class constructor. + * @param {string} message + * @param {number} [code] */ - set(layoutOrOptions: string | object, options?: object | undefined): Promise; - #private; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } /** - * A container for various `Menu` instances. + * An `EncodingError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class MenuContainer extends EventTarget { - /** - * `MenuContainer` class constructor. - * @param {EventTarget} [sourceEventTarget] - * @param {object=} [options] - */ - constructor(sourceEventTarget?: EventTarget, options?: object | undefined); + export class EncodingError extends Error { /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} + * The code given to an `ENCODING_ERR` `DOMException`. */ - set onerror(onerror: (arg0: ErrorEvent) => any); + static get code(): any; /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} + * `EncodingError` class constructor. + * @param {string} message + * @param {number} [code] */ - get onerror(): (arg0: ErrorEvent) => any; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An error type derived from an `errno` code. + */ + export class ErrnoError extends Error { + static get code(): string; + static errno: any; /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} + * `ErrnoError` class constructor. + * @param {import('./errno').errno|string} code */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + get name(): string; + get code(): number; + #private; + } + /** + * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class FinalizationRegistryCallbackError extends Error { /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} + * The default code given to an `FinalizationRegistryCallbackError` */ - get onmenuitem(): (arg0: menuitemEvent) => any; + static get code(): number; /** - * The `TrayMenu` instance for the application. - * @type {TrayMenu} + * `FinalizationRegistryCallbackError` class constructor. + * @param {string} message + * @param {number} [code] */ - get tray(): TrayMenu; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `IllegalConstructorError` is an error type thrown when a constructor is + * called for a class constructor when it shouldn't be. + */ + export class IllegalConstructorError extends TypeError { /** - * The `SystemMenu` instance for the application. - * @type {SystemMenu} + * The default code given to an `IllegalConstructorError` */ - get system(): SystemMenu; + static get code(): number; /** - * The `ContextMenu` instance for the application. - * @type {ContextMenu} + * `IllegalConstructorError` class constructor. + * @param {string} message + * @param {number} [code] */ - get context(): ContextMenu; - #private; - } - /** - * A `Menu` instance that represents a context menu. - */ - export class ContextMenu extends Menu { - constructor(); - } - /** - * A `Menu` instance that represents the system menu. - */ - export class SystemMenu extends Menu { - constructor(); - } - /** - * A `Menu` instance that represents the tray menu. - */ - export class TrayMenu extends Menu { - constructor(); + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } /** - * The application tray menu. - * @type {TrayMenu} - */ - export const tray: TrayMenu; - /** - * The application system menu. - * @type {SystemMenu} - */ - export const system: SystemMenu; - /** - * The application context menu. - * @type {ContextMenu} - */ - export const context: ContextMenu; - /** - * The application menus container. - * @type {MenuContainer} - */ - export const container: MenuContainer; - export default container; - import ipc from "socket:ipc"; -} -declare module "socket:internal/events" { - /** - * An event dispatched when an application URL is opening the application. + * An `IndexSizeError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class ApplicationURLEvent extends Event { + export class IndexSizeError extends Error { /** - * `ApplicationURLEvent` class constructor. - * @param {string=} [type] - * @param {object=} [options] + * The code given to an `INDEX_SIZE_ERR` `DOMException` */ - constructor(type?: string | undefined, options?: object | undefined); + static get code(): any; /** - * `true` if the application URL is valid (parses correctly). - * @type {boolean} + * `IndexSizeError` class constructor. + * @param {string} message + * @param {number} [code] */ - get isValid(): boolean; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + export const kInternalErrorCode: unique symbol; + /** + * An `InternalError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class InternalError extends Error { /** - * Data associated with the `ApplicationURLEvent`. - * @type {?any} + * The default code given to an `InternalError` */ - get data(): any; + static get code(): number; /** - * The original source URI - * @type {?string} + * `InternalError` class constructor. + * @param {string} message + * @param {number} [code] */ - get source(): string; + constructor(message: string, code?: number, ...args: any[]); + get name(): string; /** - * The `URL` for the `ApplicationURLEvent`. - * @type {?URL} + * @param {number|string} */ - get url(): URL; + set code(code: string | number); /** - * String tag name for an `ApplicationURLEvent` instance. - * @type {string} + * @type {number|string} */ - get [Symbol.toStringTag](): string; - #private; + get code(): string | number; + [exports.kInternalErrorCode]: number; } /** - * An event dispacted for a registered global hotkey expression. + * An `InvalidAccessError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class HotKeyEvent extends MessageEvent { + export class InvalidAccessError extends Error { /** - * `HotKeyEvent` class constructor. - * @ignore - * @param {string=} [type] - * @param {object=} [data] + * The code given to an `INVALID_ACCESS_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - constructor(type?: string | undefined, data?: object | undefined); + static get code(): any; /** - * The global unique ID for this hotkey binding. - * @type {number?} - */ - get id(): number; - /** - * The computed hash for this hotkey binding. - * @type {number?} + * `InvalidAccessError` class constructor. + * @param {string} message + * @param {number} [code] */ - get hash(): number; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `NetworkError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class NetworkError extends Error { /** - * The normalized hotkey expression as a sequence of tokens. - * @type {string[]} + * The code given to an `NETWORK_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - get sequence(): string[]; + static get code(): any; /** - * The original expression of the hotkey binding. - * @type {string?} + * `NetworkError` class constructor. + * @param {string} message + * @param {number} [code] */ - get expression(): string; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } /** - * An event dispacted when a menu item is selected. + * An `NotAllowedError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class MenuItemEvent extends MessageEvent { - /** - * `MenuItemEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [data] - * @param {import('../application/menu.js').Menu} menu - */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + export class NotAllowedError extends Error { /** - * The `Menu` this event has been dispatched for. - * @type {import('../application/menu.js').Menu?} + * The code given to an `NOT_ALLOWED_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - get menu(): import("socket:application/menu").Menu; + static get code(): any; /** - * The title of the menu item. - * @type {string?} + * `NotAllowedError` class constructor. + * @param {string} message + * @param {number} [code] */ - get title(): string; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `NotFoundError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class NotFoundError extends Error { /** - * An optional tag value for the menu item that may also be the - * parent menu item title. - * @type {string?} + * The code given to an `NOT_FOUND_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - get tag(): string; + static get code(): any; /** - * The parent title of the menu item. - * @type {string?} + * `NotFoundError` class constructor. + * @param {string} message + * @param {number} [code] */ - get parent(): string; - #private; - } - namespace _default { - export { ApplicationURLEvent }; - export { MenuItemEvent }; - export { HotKeyEvent }; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } - export default _default; -} -declare module "socket:window/hotkey" { - /** - * Normalizes an expression string. - * @param {string} expression - * @return {string} - */ - export function normalizeExpression(expression: string): string; /** - * Bind a global hotkey expression. - * @param {string} expression - * @param {{ passive?: boolean }} [options] - * @return {Promise} + * An `NotSupportedError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export function bind(expression: string, options?: { - passive?: boolean; - }): Promise; + export class NotSupportedError extends Error { + /** + * The code given to an `NOT_SUPPORTED_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `NotSupportedError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * Bind a global hotkey expression. - * @param {string} expression - * @param {object=} [options] - * @return {Promise} + * An `ModuleNotFoundError` is an error type thrown when an imported or + * required module is not found. */ - export function unbind(id: any, options?: object | undefined): Promise; + export class ModuleNotFoundError extends exports.NotFoundError { + /** + * `ModuleNotFoundError` class constructor. + * @param {string} message + */ + constructor(message: string, requireStack: any); + requireStack: any; + } /** - * Get all known globally register hotkey bindings. - * @param {object=} [options] - * @return {Promise} + * An `OperationError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export function getBindings(options?: object | undefined): Promise; + export class OperationError extends Error { + /** + * The code given to an `OPERATION_ERR` `DOMException` + */ + static get code(): any; + /** + * `OperationError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * Get all known possible keyboard modifier and key mappings for - * expression bindings. - * @param {object=} [options] - * @return {Promise<{ keys: object, modifiers: object }>} + * An `SecurityError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export function getMappings(options?: object | undefined): Promise<{ - keys: object; - modifiers: object; - }>; + export class SecurityError extends Error { + /** + * The code given to an `SECURITY_ERR` `DOMException` + */ + static get code(): any; + /** + * `SecurityError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * Adds an event listener to the global active bindings. This function is just - * proxy to `bindings.addEventListener`. - * @param {string} type - * @param {function(Event)} listener - * @param {(boolean|object)=} [optionsOrUseCapture] + * An `TimeoutError` is an error type thrown when an operation timesout. */ - export function addEventListener(type: string, listener: (arg0: Event) => any, optionsOrUseCapture?: (boolean | object) | undefined): void; + export class TimeoutError extends Error { + /** + * The code given to an `TIMEOUT_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `TimeoutError` class constructor. + * @param {string} message + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + import * as exports from "socket:errors"; + +} +declare module "socket:mime/index" { /** - * Removes an event listener to the global active bindings. This function is - * just a proxy to `bindings.removeEventListener` - * @param {string} type - * @param {function(Event)} listener - * @param {(boolean|object)=} [optionsOrUseCapture] + * Look up a MIME type in various MIME databases. + * @param {string} query + * @return {Promise} */ - export function removeEventListener(type: string, listener: (arg0: Event) => any, optionsOrUseCapture?: (boolean | object) | undefined): void; + export function lookup(query: string): Promise; /** - * A high level bindings container map that dispatches events. + * A container for a database lookup query. */ - export class Bindings extends EventTarget { + export class DatabaseQueryResult { /** - * `Bindings` class constructor. + * `DatabaseQueryResult` class constructor. * @ignore - * @param {EventTarget} [sourceEventTarget] + * @param {Database} database + * @param {string} name + * @param {string} mime */ - constructor(sourceEventTarget?: EventTarget); + constructor(database: Database, name: string, mime: string); /** - * Global `HotKeyEvent` event listener for `Binding` instance event dispatch. - * @ignore - * @param {import('../internal/events.js').HotKeyEvent} event + * @type {string} */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + name: string; /** - * The number of `Binding` instances in the mapping. - * @type {number} + * @type {string} */ - get size(): number; + mime: string; + database: Database; + } + /** + * A container for MIME types by class (audio, video, text, etc) + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} + */ + export class Database { /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} + * `Database` class constructor. + * @param {string} name */ - set onerror(onerror: (arg0: ErrorEvent) => any); + constructor(name: string); /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'hotkey'` event listener. - * @ignore - * @type {function(HotKeyEvent)?} - */ - set onhotkey(onhotkey: (arg0: hotkeyEvent) => any); - /** - * Level 1 'hotkey'` event listener. - * @type {function(hotkeyEvent)?} - */ - get onhotkey(): (arg0: hotkeyEvent) => any; - /** - * Initializes bindings from global context. - * @ignore - * @return {Promise} - */ - init(): Promise; - /** - * Get a binding by `id` - * @param {number} id - * @return {Binding} - */ - get(id: number): Binding; - /** - * Set a `binding` a by `id`. - * @param {number} id - * @param {Binding} binding - */ - set(id: number, binding: Binding): void; - /** - * Delete a binding by `id` - * @param {number} id - * @return {boolean} - */ - delete(id: number): boolean; - /** - * Returns `true` if a binding exists in the mapping, otherwise `false`. - * @return {boolean} - */ - has(id: any): boolean; - /** - * Known `Binding` values in the mapping. - * @return {{ next: function(): { value: Binding|undefined, done: boolean } }} - */ - values(): { - next: () => { - value: Binding | undefined; - done: boolean; - }; - }; - /** - * Known `Binding` keys in the mapping. - * @return {{ next: function(): { value: number|undefined, done: boolean } }} - */ - keys(): { - next: () => { - value: number | undefined; - done: boolean; - }; - }; - /** - * Known `Binding` ids in the mapping. - * @return {{ next: function(): { value: number|undefined, done: boolean } }} + * The name of the MIME database. + * @type {string} */ - ids(): { - next: () => { - value: number | undefined; - done: boolean; - }; - }; + name: string; /** - * Known `Binding` ids and values in the mapping. - * @return {{ next: function(): { value: [number, Binding]|undefined, done: boolean } }} + * The URL of the MIME database. + * @type {URL} */ - entries(): { - next: () => { - value: [number, Binding] | undefined; - done: boolean; - }; - }; + url: URL; /** - * Bind a global hotkey expression. - * @param {string} expression - * @return {Promise} + * The mapping of MIME name to the MIME "content type" + * @type {Map} */ - bind(expression: string): Promise; + map: Map; /** - * Bind a global hotkey expression. - * @param {string} expression - * @return {Promise} + * An index of MIME "content type" to the MIME name. + * @type {Map} */ - unbind(expression: string): Promise; + index: Map; /** - * Returns an array of all active bindings for the application. - * @return {Promise} + * An enumeration of all database entries. + * @return {Array>} */ - active(): Promise; + entries(): Array>; /** - * Resets all active bindings in the application. - * @param {boolean=} [currentContextOnly] + * Loads database MIME entries into internal map. * @return {Promise} */ - reset(currentContextOnly?: boolean | undefined): Promise; - /** - * Implements the `Iterator` protocol for each currently registered - * active binding in this window context. The `AsyncIterator` protocol - * will probe for all gloally active bindings. - * @return {Iterator} - */ - [Symbol.iterator](): Iterator; + load(): Promise; /** - * Implements the `AsyncIterator` protocol for each globally active - * binding registered to the application. This differs from the `Iterator` - * protocol as this will probe for _all_ active bindings in the entire - * application context. - * @return {AsyncGenerator} + * Lookup MIME type by name or content type + * @param {string} query + * @return {Promise} */ - [Symbol.asyncIterator](): AsyncGenerator; - #private; + lookup(query: string): Promise; } /** - * An `EventTarget` container for a hotkey binding. + * A database of MIME types for 'application/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} */ - export class Binding extends EventTarget { - /** - * `Binding` class constructor. - * @ignore - * @param {object} data - */ - constructor(data: object); - /** - * `true` if the binding is valid, otherwise `false`. - * @type {boolean} - */ - get isValid(): boolean; - /** - * `true` if the binding is considered active, otherwise `false`. - * @type {boolean} - */ - get isActive(): boolean; - /** - * The global unique ID for this binding. - * @type {number?} - */ - get id(): number; - /** - * The computed hash for this binding expression. - * @type {number?} - */ - get hash(): number; - /** - * The normalized expression as a sequence of tokens. - * @type {string[]} - */ - get sequence(): string[]; - /** - * The original expression of the binding. - * @type {string?} - */ - get expression(): string; - /** - * Setter for the level 1 'hotkey'` event listener. - * @ignore - * @type {function(HotKeyEvent)?} - */ - set onhotkey(onhotkey: (arg0: hotkeyEvent) => any); - /** - * Level 1 'hotkey'` event listener. - * @type {function(hotkeyEvent)?} - */ - get onhotkey(): (arg0: hotkeyEvent) => any; - /** - * Binds this hotkey expression. - * @return {Promise} - */ - bind(): Promise; - /** - * Unbinds this hotkey expression. - * @return {Promise} - */ - unbind(): Promise; - /** - * Implements the `AsyncIterator` protocol for async 'hotkey' events - * on this binding instance. - * @return {AsyncGenerator} - */ - [Symbol.asyncIterator](): AsyncGenerator; - #private; - } + export const application: Database; /** - * A container for all the bindings currently bound - * by this window context. - * @type {Bindings} + * A database of MIME types for 'audio/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} */ - export const bindings: Bindings; - export default bindings; -} -declare module "socket:window" { + export const audio: Database; /** - * @param {string} url - * @return {string} - * @ignore + * A database of MIME types for 'font/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} */ - export function formatURL(url: string): string; + export const font: Database; /** - * @class ApplicationWindow - * Represents a window in the application + * A database of MIME types for 'image/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} */ - export class ApplicationWindow { - static constants: typeof statuses; - static hotkey: import("socket:window/hotkey").Bindings; - constructor({ index, ...options }: { - [x: string]: any; - index: any; - }); - /** - * The unique ID of this window. - * @type {string} - */ - get id(): string; - /** - * Get the index of the window - * @return {number} - the index of the window - */ - get index(): number; - /** - * @type {import('./window/hotkey.js').default} - */ - get hotkey(): import("socket:window/hotkey").Bindings; - /** - * The broadcast channel for this window. - * @type {BroadcastChannel} - */ - get channel(): BroadcastChannel; - /** - * Get the size of the window - * @return {{ width: number, height: number }} - the size of the window - */ - getSize(): { - width: number; - height: number; - }; + export const image: Database; + /** + * A database of MIME types for 'model/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} + */ + export const model: Database; + /** + * A database of MIME types for 'multipart/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} + */ + export const multipart: Database; + /** + * A database of MIME types for 'text/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} + */ + export const text: Database; + /** + * A database of MIME types for 'video/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} + */ + export const video: Database; + /** + * An array of known MIME databases. Custom databases can be added to this + * array in userspace for lookup with `mime.lookup()` + * @type {Database[]} + */ + export const databases: Database[]; + export class MIMEParams extends Map { + constructor(); + constructor(entries?: readonly (readonly [any, any])[]); + constructor(); + constructor(iterable?: Iterable); + } + export class MIMEType { + constructor(input: any); + set type(value: any); + get type(): any; + set subtype(value: any); + get subtype(): any; + get essence(): string; + toString(): string; + toJSON(): string; + #private; + } + namespace _default { + export { Database }; + export { databases }; + export { lookup }; + export { MIMEParams }; + export { MIMEType }; + export { application }; + export { audio }; + export { font }; + export { image }; + export { model }; + export { multipart }; + export { text }; + export { video }; + } + export default _default; +} +declare module "socket:mime" { + export * from "socket:mime/index"; + export default exports; + import * as exports from "socket:mime/index"; +} +declare module "socket:util" { + export function debug(section: any): { + (...args: any[]): void; + enabled: boolean; + }; + export function hasOwnProperty(object: any, property: any): any; + export function isDate(object: any): boolean; + export function isTypedArray(object: any): boolean; + export function isArrayLike(object: any): boolean; + export function isError(object: any): boolean; + export function isSymbol(value: any): boolean; + export function isNumber(value: any): boolean; + export function isBoolean(value: any): boolean; + export function isArrayBufferView(buf: any): boolean; + export function isAsyncFunction(object: any): boolean; + export function isArgumentsObject(object: any): boolean; + export function isEmptyObject(object: any): boolean; + export function isObject(object: any): boolean; + export function isUndefined(value: any): boolean; + export function isNull(value: any): boolean; + export function isNullOrUndefined(value: any): boolean; + export function isPrimitive(value: any): boolean; + export function isRegExp(value: any): boolean; + export function isPlainObject(object: any): boolean; + export function isArrayBuffer(object: any): boolean; + export function isBufferLike(object: any): boolean; + export function isFunction(value: any): boolean; + export function isErrorLike(error: any): boolean; + export function isClass(value: any): boolean; + export function isBuffer(value: any): boolean; + export function isPromiseLike(object: any): boolean; + export function toString(object: any): string; + export function toBuffer(object: any, encoding?: any): any; + export function toProperCase(string: any): any; + export function splitBuffer(buffer: any, highWaterMark: any): any[]; + export function InvertedPromise(): Promise; + export function clamp(value: any, min: any, max: any): number; + export function promisify(original: any): any; + export function inspect(value: any, options: any): any; + export namespace inspect { + let custom: symbol; + let ignore: symbol; + } + export function format(format: any, ...args: any[]): string; + export function parseJSON(string: any): any; + export function parseHeaders(headers: any): string[][]; + export function noop(): void; + export function isValidPercentageValue(input: any): boolean; + export function compareBuffers(a: any, b: any): any; + export function inherits(Constructor: any, Super: any): void; + export function deprecate(...args: any[]): void; + export const TextDecoder: { + new (label?: string, options?: TextDecoderOptions): TextDecoder; + prototype: TextDecoder; + }; + export const TextEncoder: { + new (): TextEncoder; + prototype: TextEncoder; + }; + export const isArray: any; + export class IllegalConstructor { + } + export const MIMEType: typeof mime.MIMEType; + export const MIMEParams: typeof mime.MIMEParams; + export default exports; + import mime from "socket:mime"; + import * as exports from "socket:util"; + +} +declare module "socket:window/constants" { + export const WINDOW_ERROR: -1; + export const WINDOW_NONE: 0; + export const WINDOW_CREATING: 10; + export const WINDOW_CREATED: 11; + export const WINDOW_HIDING: 20; + export const WINDOW_HIDDEN: 21; + export const WINDOW_SHOWING: 30; + export const WINDOW_SHOWN: 31; + export const WINDOW_CLOSING: 40; + export const WINDOW_CLOSED: 41; + export const WINDOW_EXITING: 50; + export const WINDOW_EXITED: 51; + export const WINDOW_KILLING: 60; + export const WINDOW_KILLED: 61; + export default exports; + import * as exports from "socket:window/constants"; + +} +declare module "socket:window/hotkey" { + /** + * Normalizes an expression string. + * @param {string} expression + * @return {string} + */ + export function normalizeExpression(expression: string): string; + /** + * Bind a global hotkey expression. + * @param {string} expression + * @param {{ passive?: boolean }} [options] + * @return {Promise} + */ + export function bind(expression: string, options?: { + passive?: boolean; + }): Promise; + /** + * Bind a global hotkey expression. + * @param {string} expression + * @param {object=} [options] + * @return {Promise} + */ + export function unbind(id: any, options?: object | undefined): Promise; + /** + * Get all known globally register hotkey bindings. + * @param {object=} [options] + * @return {Promise} + */ + export function getBindings(options?: object | undefined): Promise; + /** + * Get all known possible keyboard modifier and key mappings for + * expression bindings. + * @param {object=} [options] + * @return {Promise<{ keys: object, modifiers: object }>} + */ + export function getMappings(options?: object | undefined): Promise<{ + keys: object; + modifiers: object; + }>; + /** + * Adds an event listener to the global active bindings. This function is just + * proxy to `bindings.addEventListener`. + * @param {string} type + * @param {function(Event)} listener + * @param {(boolean|object)=} [optionsOrUseCapture] + */ + export function addEventListener(type: string, listener: (arg0: Event) => any, optionsOrUseCapture?: (boolean | object) | undefined): void; + /** + * Removes an event listener to the global active bindings. This function is + * just a proxy to `bindings.removeEventListener` + * @param {string} type + * @param {function(Event)} listener + * @param {(boolean|object)=} [optionsOrUseCapture] + */ + export function removeEventListener(type: string, listener: (arg0: Event) => any, optionsOrUseCapture?: (boolean | object) | undefined): void; + /** + * A high level bindings container map that dispatches events. + */ + export class Bindings extends EventTarget { /** - * Get the title of the window - * @return {string} - the title of the window + * `Bindings` class constructor. + * @ignore + * @param {EventTarget} [sourceEventTarget] */ - getTitle(): string; + constructor(sourceEventTarget?: EventTarget); /** - * Get the status of the window - * @return {string} - the status of the window + * Global `HotKeyEvent` event listener for `Binding` instance event dispatch. + * @ignore + * @param {import('../internal/events.js').HotKeyEvent} event */ - getStatus(): string; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** - * Close the window - * @return {Promise} - the options of the window + * The number of `Binding` instances in the mapping. + * @type {number} */ - close(): Promise; + get size(): number; /** - * Shows the window - * @return {Promise} + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} */ - show(): Promise; + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * Hides the window - * @return {Promise} + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - hide(): Promise; + get onerror(): (arg0: ErrorEvent) => any; /** - * Maximize the window - * @return {Promise} - */ - maximize(): Promise; + * Setter for the level 1 'hotkey'` event listener. + * @ignore + * @type {function(HotKeyEvent)?} + */ + set onhotkey(onhotkey: (arg0: hotkeyEvent) => any); /** - * Minimize the window - * @return {Promise} + * Level 1 'hotkey'` event listener. + * @type {function(hotkeyEvent)?} */ - minimize(): Promise; + get onhotkey(): (arg0: hotkeyEvent) => any; /** - * Restore the window - * @return {Promise} + * Initializes bindings from global context. + * @ignore + * @return {Promise} */ - restore(): Promise; + init(): Promise; /** - * Sets the title of the window - * @param {string} title - the title of the window - * @return {Promise} + * Get a binding by `id` + * @param {number} id + * @return {Binding} */ - setTitle(title: string): Promise; + get(id: number): Binding; /** - * Sets the size of the window - * @param {object} opts - an options object - * @param {(number|string)=} opts.width - the width of the window - * @param {(number|string)=} opts.height - the height of the window - * @return {Promise} - * @throws {Error} - if the width or height is invalid + * Set a `binding` a by `id`. + * @param {number} id + * @param {Binding} binding */ - setSize(opts: { - width?: (number | string) | undefined; - height?: (number | string) | undefined; - }): Promise; + set(id: number, binding: Binding): void; /** - * Navigate the window to a given path - * @param {object} path - file path - * @return {Promise} + * Delete a binding by `id` + * @param {number} id + * @return {boolean} */ - navigate(path: object): Promise; + delete(id: number): boolean; /** - * Opens the Web Inspector for the window - * @return {Promise} + * Returns `true` if a binding exists in the mapping, otherwise `false`. + * @return {boolean} */ - showInspector(): Promise; + has(id: any): boolean; /** - * Sets the background color of the window - * @param {object} opts - an options object - * @param {number} opts.red - the red value - * @param {number} opts.green - the green value - * @param {number} opts.blue - the blue value - * @param {number} opts.alpha - the alpha value - * @return {Promise} + * Known `Binding` values in the mapping. + * @return {{ next: function(): { value: Binding|undefined, done: boolean } }} */ - setBackgroundColor(opts: { - red: number; - green: number; - blue: number; - alpha: number; - }): Promise; + values(): { + next: () => { + value: Binding | undefined; + done: boolean; + }; + }; /** - * Opens a native context menu. - * @param {object} options - an options object - * @return {Promise} + * Known `Binding` keys in the mapping. + * @return {{ next: function(): { value: number|undefined, done: boolean } }} */ - setContextMenu(options: object): Promise; + keys(): { + next: () => { + value: number | undefined; + done: boolean; + }; + }; /** - * Shows a native open file dialog. - * @param {object} options - an options object - * @return {Promise} - an array of file paths + * Known `Binding` ids in the mapping. + * @return {{ next: function(): { value: number|undefined, done: boolean } }} */ - showOpenFilePicker(options: object): Promise; + ids(): { + next: () => { + value: number | undefined; + done: boolean; + }; + }; /** - * Shows a native save file dialog. - * @param {object} options - an options object - * @return {Promise} - an array of file paths + * Known `Binding` ids and values in the mapping. + * @return {{ next: function(): { value: [number, Binding]|undefined, done: boolean } }} */ - showSaveFilePicker(options: object): Promise; + entries(): { + next: () => { + value: [number, Binding] | undefined; + done: boolean; + }; + }; /** - * Shows a native directory dialog. - * @param {object} options - an options object - * @return {Promise} - an array of file paths + * Bind a global hotkey expression. + * @param {string} expression + * @return {Promise} */ - showDirectoryFilePicker(options: object): Promise; + bind(expression: string): Promise; /** - * This is a high-level API that you should use instead of `ipc.send` when - * you want to send a message to another window or to the backend. - * - * @param {object} options - an options object - * @param {number=} options.window - the window to send the message to - * @param {boolean=} [options.backend = false] - whether to send the message to the backend - * @param {string} options.event - the event to send - * @param {(string|object)=} options.value - the value to send - * @returns + * Bind a global hotkey expression. + * @param {string} expression + * @return {Promise} */ - send(options: { - window?: number | undefined; - backend?: boolean | undefined; - event: string; - value?: (string | object) | undefined; - }): Promise; + unbind(expression: string): Promise; /** - * Post a message to a window - * TODO(@jwerle): research using `BroadcastChannel` instead - * @param {object} message + * Returns an array of all active bindings for the application. + * @return {Promise} + */ + active(): Promise; + /** + * Resets all active bindings in the application. + * @param {boolean=} [currentContextOnly] * @return {Promise} */ - postMessage(message: object): Promise; + reset(currentContextOnly?: boolean | undefined): Promise; /** - * Opens an URL in the default browser. - * @param {object} options - * @returns {Promise} + * Implements the `Iterator` protocol for each currently registered + * active binding in this window context. The `AsyncIterator` protocol + * will probe for all gloally active bindings. + * @return {Iterator} */ - openExternal(options: object): Promise; + [Symbol.iterator](): Iterator; /** - * Adds a listener to the window. - * @param {string} event - the event to listen to - * @param {function(*): void} cb - the callback to call - * @returns {void} + * Implements the `AsyncIterator` protocol for each globally active + * binding registered to the application. This differs from the `Iterator` + * protocol as this will probe for _all_ active bindings in the entire + * application context. + * @return {AsyncGenerator} */ - addListener(event: string, cb: (arg0: any) => void): void; + [Symbol.asyncIterator](): AsyncGenerator; + #private; + } + /** + * An `EventTarget` container for a hotkey binding. + */ + export class Binding extends EventTarget { /** - * Adds a listener to the window. An alias for `addListener`. - * @param {string} event - the event to listen to - * @param {function(*): void} cb - the callback to call - * @returns {void} - * @see addListener + * `Binding` class constructor. + * @ignore + * @param {object} data */ - on(event: string, cb: (arg0: any) => void): void; + constructor(data: object); /** - * Adds a listener to the window. The listener is removed after the first call. - * @param {string} event - the event to listen to - * @param {function(*): void} cb - the callback to call - * @returns {void} + * `true` if the binding is valid, otherwise `false`. + * @type {boolean} */ - once(event: string, cb: (arg0: any) => void): void; + get isValid(): boolean; /** - * Removes a listener from the window. - * @param {string} event - the event to remove the listener from - * @param {function(*): void} cb - the callback to remove - * @returns {void} + * `true` if the binding is considered active, otherwise `false`. + * @type {boolean} */ - removeListener(event: string, cb: (arg0: any) => void): void; + get isActive(): boolean; /** - * Removes all listeners from the window. - * @param {string} event - the event to remove the listeners from - * @returns {void} + * The global unique ID for this binding. + * @type {number?} */ - removeAllListeners(event: string): void; + get id(): number; /** - * Removes a listener from the window. An alias for `removeListener`. - * @param {string} event - the event to remove the listener from - * @param {function(*): void} cb - the callback to remove - * @returns {void} - * @see removeListener + * The computed hash for this binding expression. + * @type {number?} */ - off(event: string, cb: (arg0: any) => void): void; + get hash(): number; + /** + * The normalized expression as a sequence of tokens. + * @type {string[]} + */ + get sequence(): string[]; + /** + * The original expression of the binding. + * @type {string?} + */ + get expression(): string; + /** + * Setter for the level 1 'hotkey'` event listener. + * @ignore + * @type {function(HotKeyEvent)?} + */ + set onhotkey(onhotkey: (arg0: hotkeyEvent) => any); + /** + * Level 1 'hotkey'` event listener. + * @type {function(hotkeyEvent)?} + */ + get onhotkey(): (arg0: hotkeyEvent) => any; + /** + * Binds this hotkey expression. + * @return {Promise} + */ + bind(): Promise; + /** + * Unbinds this hotkey expression. + * @return {Promise} + */ + unbind(): Promise; + /** + * Implements the `AsyncIterator` protocol for async 'hotkey' events + * on this binding instance. + * @return {AsyncGenerator} + */ + [Symbol.asyncIterator](): AsyncGenerator; #private; } - export { hotkey }; + /** + * A container for all the bindings currently bound + * by this window context. + * @type {Bindings} + */ + export const bindings: Bindings; + export default bindings; +} +declare module "socket:window" { + /** + * @param {string} url + * @return {string} + * @ignore + */ + export function formatURL(url: string): string; + /** + * @class ApplicationWindow + * Represents a window in the application + */ + export class ApplicationWindow { + static constants: typeof statuses; + static hotkey: import("socket:window/hotkey").Bindings; + constructor({ index, ...options }: { + [x: string]: any; + index: any; + }); + /** + * The unique ID of this window. + * @type {string} + */ + get id(): string; + /** + * Get the index of the window + * @return {number} - the index of the window + */ + get index(): number; + /** + * @type {import('./window/hotkey.js').default} + */ + get hotkey(): import("socket:window/hotkey").Bindings; + /** + * The broadcast channel for this window. + * @type {BroadcastChannel} + */ + get channel(): BroadcastChannel; + /** + * Get the size of the window + * @return {{ width: number, height: number }} - the size of the window + */ + getSize(): { + width: number; + height: number; + }; + /** + * Get the title of the window + * @return {string} - the title of the window + */ + getTitle(): string; + /** + * Get the status of the window + * @return {string} - the status of the window + */ + getStatus(): string; + /** + * Close the window + * @return {Promise} - the options of the window + */ + close(): Promise; + /** + * Shows the window + * @return {Promise} + */ + show(): Promise; + /** + * Hides the window + * @return {Promise} + */ + hide(): Promise; + /** + * Maximize the window + * @return {Promise} + */ + maximize(): Promise; + /** + * Minimize the window + * @return {Promise} + */ + minimize(): Promise; + /** + * Restore the window + * @return {Promise} + */ + restore(): Promise; + /** + * Sets the title of the window + * @param {string} title - the title of the window + * @return {Promise} + */ + setTitle(title: string): Promise; + /** + * Sets the size of the window + * @param {object} opts - an options object + * @param {(number|string)=} opts.width - the width of the window + * @param {(number|string)=} opts.height - the height of the window + * @return {Promise} + * @throws {Error} - if the width or height is invalid + */ + setSize(opts: { + width?: (number | string) | undefined; + height?: (number | string) | undefined; + }): Promise; + /** + * Navigate the window to a given path + * @param {object} path - file path + * @return {Promise} + */ + navigate(path: object): Promise; + /** + * Opens the Web Inspector for the window + * @return {Promise} + */ + showInspector(): Promise; + /** + * Sets the background color of the window + * @param {object} opts - an options object + * @param {number} opts.red - the red value + * @param {number} opts.green - the green value + * @param {number} opts.blue - the blue value + * @param {number} opts.alpha - the alpha value + * @return {Promise} + */ + setBackgroundColor(opts: { + red: number; + green: number; + blue: number; + alpha: number; + }): Promise; + /** + * Opens a native context menu. + * @param {object} options - an options object + * @return {Promise} + */ + setContextMenu(options: object): Promise; + /** + * Shows a native open file dialog. + * @param {object} options - an options object + * @return {Promise} - an array of file paths + */ + showOpenFilePicker(options: object): Promise; + /** + * Shows a native save file dialog. + * @param {object} options - an options object + * @return {Promise} - an array of file paths + */ + showSaveFilePicker(options: object): Promise; + /** + * Shows a native directory dialog. + * @param {object} options - an options object + * @return {Promise} - an array of file paths + */ + showDirectoryFilePicker(options: object): Promise; + /** + * This is a high-level API that you should use instead of `ipc.send` when + * you want to send a message to another window or to the backend. + * + * @param {object} options - an options object + * @param {number=} options.window - the window to send the message to + * @param {boolean=} [options.backend = false] - whether to send the message to the backend + * @param {string} options.event - the event to send + * @param {(string|object)=} options.value - the value to send + * @returns + */ + send(options: { + window?: number | undefined; + backend?: boolean | undefined; + event: string; + value?: (string | object) | undefined; + }): Promise; + /** + * Post a message to a window + * TODO(@jwerle): research using `BroadcastChannel` instead + * @param {object} message + * @return {Promise} + */ + postMessage(message: object): Promise; + /** + * Opens an URL in the default browser. + * @param {object} options + * @returns {Promise} + */ + openExternal(options: object): Promise; + /** + * Adds a listener to the window. + * @param {string} event - the event to listen to + * @param {function(*): void} cb - the callback to call + * @returns {void} + */ + addListener(event: string, cb: (arg0: any) => void): void; + /** + * Adds a listener to the window. An alias for `addListener`. + * @param {string} event - the event to listen to + * @param {function(*): void} cb - the callback to call + * @returns {void} + * @see addListener + */ + on(event: string, cb: (arg0: any) => void): void; + /** + * Adds a listener to the window. The listener is removed after the first call. + * @param {string} event - the event to listen to + * @param {function(*): void} cb - the callback to call + * @returns {void} + */ + once(event: string, cb: (arg0: any) => void): void; + /** + * Removes a listener from the window. + * @param {string} event - the event to remove the listener from + * @param {function(*): void} cb - the callback to remove + * @returns {void} + */ + removeListener(event: string, cb: (arg0: any) => void): void; + /** + * Removes all listeners from the window. + * @param {string} event - the event to remove the listeners from + * @returns {void} + */ + removeAllListeners(event: string): void; + /** + * Removes a listener from the window. An alias for `removeListener`. + * @param {string} event - the event to remove the listener from + * @param {function(*): void} cb - the callback to remove + * @returns {void} + * @see removeListener + */ + off(event: string, cb: (arg0: any) => void): void; + #private; + } + export { hotkey }; export default ApplicationWindow; /** * @ignore @@ -5301,3800 +5666,4132 @@ declare module "socket:bootstrap" { } import { EventEmitter } from "socket:events"; } -declare module "socket:constants" { - export * from "socket:fs/constants"; - export * from "socket:window/constants"; - const _default: { - WINDOW_ERROR: -1; - WINDOW_NONE: 0; - WINDOW_CREATING: 10; - WINDOW_CREATED: 11; - WINDOW_HIDING: 20; - WINDOW_HIDDEN: 21; - WINDOW_SHOWING: 30; - WINDOW_SHOWN: 31; - WINDOW_CLOSING: 40; - WINDOW_CLOSED: 41; - WINDOW_EXITING: 50; - WINDOW_EXITED: 51; - WINDOW_KILLING: 60; - WINDOW_KILLED: 61; - default: typeof window; - COPYFILE_EXCL: 1; - COPYFILE_FICLONE: 2; - COPYFILE_FICLONE_FORCE: 4; - UV_DIRENT_UNKNOWN: any; - UV_DIRENT_FILE: any; - UV_DIRENT_DIR: any; - UV_DIRENT_LINK: any; - UV_DIRENT_FIFO: any; - UV_DIRENT_SOCKET: any; - UV_DIRENT_CHAR: any; - UV_DIRENT_BLOCK: any; - UV_FS_SYMLINK_DIR: any; - UV_FS_SYMLINK_JUNCTION: any; - O_RDONLY: any; - O_WRONLY: any; - O_RDWR: any; - O_APPEND: any; - O_ASYNC: any; - O_CLOEXEC: any; - O_CREAT: any; - O_DIRECT: any; - O_DIRECTORY: any; - O_DSYNC: any; - O_EXCL: any; - O_LARGEFILE: any; - O_NOATIME: any; - O_NOCTTY: any; - O_NOFOLLOW: any; - O_NONBLOCK: any; - O_NDELAY: any; - O_PATH: any; - O_SYNC: any; - O_TMPFILE: any; - O_TRUNC: any; - S_IFMT: any; - S_IFREG: any; - S_IFDIR: any; - S_IFCHR: any; - S_IFBLK: any; - S_IFIFO: any; - S_IFLNK: any; - S_IFSOCK: any; - S_IRWXU: any; - S_IRUSR: any; - S_IWUSR: any; - S_IXUSR: any; - S_IRWXG: any; - S_IRGRP: any; - S_IWGRP: any; - S_IXGRP: any; - S_IRWXO: any; - S_IROTH: any; - S_IWOTH: any; - S_IXOTH: any; - F_OK: any; - R_OK: any; - W_OK: any; - X_OK: any; - }; - export default _default; - import window from "socket:window/constants"; -} -declare module "socket:ip" { +declare module "socket:internal/globals" { /** - * Normalizes input as an IPv4 address string - * @param {string|object|string[]|Uint8Array} input - * @return {string} + * Gets a runtime global value by name. + * @ignore + * @param {string} name + * @return {any|null} */ - export function normalizeIPv4(input: string | object | string[] | Uint8Array): string; + export function get(name: string): any | null; /** - * Determines if an input `string` is in IP address version 4 format. - * @param {string|object|string[]|Uint8Array} input - * @return {boolean} + * Symbolic global registry + * @ignore */ - export function isIPv4(input: string | object | string[] | Uint8Array): boolean; - namespace _default { - export { normalizeIPv4 }; - export { isIPv4 }; + export class GlobalsRegistry { + get global(): any; + symbol(name: any): symbol; + register(name: any, value: any): any; + get(name: any): any; } - export default _default; + export default registry; + const registry: any; } -declare module "socket:dns/promises" { +declare module "socket:internal/shared-worker" { + export function getSharedWorkerImplementationForPlatform(): { + new (scriptURL: string | URL, options?: string | WorkerOptions): SharedWorker; + prototype: SharedWorker; + } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; + export class SharedHybridWorkerProxy extends EventTarget { + constructor(url: any, options: any); + onChannelMessage(event: any): void; + get id(): any; + get port(): any; + #private; + } + export class SharedHybridWorker extends EventTarget { + constructor(url: any, nameOrOptions: any); + get port(): any; + #private; + } + export const SharedWorker: { + new (scriptURL: string | URL, options?: string | WorkerOptions): SharedWorker; + prototype: SharedWorker; + } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; + export default SharedWorker; +} +declare module "socket:vm" { /** - * @async - * @see {@link https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options} - * @param {string} hostname - The host name to resolve. - * @param {Object=} opts - An options object. - * @param {(number|string)=} [opts.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. - * @returns {Promise} + * @ignore + * @param {object[]} transfer + * @param {object} object + * @param {object=} [options] + * @return {object[]} */ - export function lookup(hostname: string, opts?: any | undefined): Promise; - export default exports; - import * as exports from "socket:dns/promises"; - -} -declare module "socket:dns/index" { + export function findMessageTransfers(transfers: any, object: object, options?: object | undefined): object[]; /** - * Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or - * AAAA (IPv6) record. All option properties are optional. If options is an - * integer, then it must be 4 or 6 – if options is 0 or not provided, then IPv4 - * and IPv6 addresses are both returned if found. - * - * From the node.js website... - * - * > With the all option set to true, the arguments for callback change to (err, - * addresses), with addresses being an array of objects with the properties - * address and family. - * - * > On error, err is an Error object, where err.code is the error code. Keep in - * mind that err.code will be set to 'ENOTFOUND' not only when the host name does - * not exist but also when the lookup fails in other ways such as no available - * file descriptors. dns.lookup() does not necessarily have anything to do with - * the DNS protocol. The implementation uses an operating system facility that - * can associate names with addresses and vice versa. This implementation can - * have subtle but important consequences on the behavior of any Node.js program. - * Please take some time to consult the Implementation considerations section - * before using dns.lookup(). - * - * @see {@link https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback} - * @param {string} hostname - The host name to resolve. - * @param {(object|intenumberger)=} [options] - An options object or record family. - * @param {(number|string)=} [options.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. - * @param {function} cb - The function to call after the method is complete. - * @returns {void} + * @ignore + * @param {object} context */ - export function lookup(hostname: string, options?: (object | intenumberger) | undefined, cb: Function): void; - export { promises }; - export default exports; - import * as promises from "socket:dns/promises"; - import * as exports from "socket:dns/index"; - -} -declare module "socket:dns" { - export * from "socket:dns/index"; - export default exports; - import * as exports from "socket:dns/index"; -} -declare module "socket:dgram" { - export function createSocket(options: string | any, callback?: Function | undefined): Socket; + export function applyInputContextReferences(context: object): void; /** - * New instances of dgram.Socket are created using dgram.createSocket(). - * The new keyword is not to be used to create dgram.Socket instances. + * @ignore + * @param {object} context */ - export class Socket extends EventEmitter { - constructor(options: any, callback: any); - id: any; - knownIdWasGivenInSocketConstruction: boolean; - type: any; - signal: any; - state: { - recvBufferSize: any; - sendBufferSize: any; - bindState: number; - connectState: number; - reuseAddr: boolean; - ipv6Only: boolean; - }; - /** - * Listen for datagram messages on a named port and optional address - * If the address is not specified, the operating system will attempt to - * listen on all addresses. Once the binding is complete, a 'listening' - * event is emitted and the optional callback function is called. - * - * If binding fails, an 'error' event is emitted. - * - * @param {number} port - The port to listen for messages on - * @param {string} address - The address to bind to (0.0.0.0) - * @param {function} callback - With no parameters. Called when binding is complete. - * @see {@link https://nodejs.org/api/dgram.html#socketbindport-address-callback} - */ - bind(arg1: any, arg2: any, arg3: any): this; - dataListener: ({ detail }: { - detail: any; - }) => any; - /** - * Associates the dgram.Socket to a remote address and port. Every message sent - * by this handle is automatically sent to that destination. Also, the socket - * will only receive messages from that remote peer. Trying to call connect() - * on an already connected socket will result in an ERR_SOCKET_DGRAM_IS_CONNECTED - * exception. If the address is not provided, '0.0.0.0' (for udp4 sockets) or '::1' - * (for udp6 sockets) will be used by default. Once the connection is complete, - * a 'connect' event is emitted and the optional callback function is called. - * In case of failure, the callback is called or, failing this, an 'error' event - * is emitted. - * - * @param {number} port - Port the client should connect to. - * @param {string=} host - Host the client should connect to. - * @param {function=} connectListener - Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. - * @see {@link https://nodejs.org/api/dgram.html#socketconnectport-address-callback} - */ - connect(arg1: any, arg2: any, arg3: any): void; - /** - * A synchronous function that disassociates a connected dgram.Socket from - * its remote address. Trying to call disconnect() on an unbound or already - * disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. - * - * @see {@link https://nodejs.org/api/dgram.html#socketdisconnect} - */ - disconnect(): void; - /** - * Broadcasts a datagram on the socket. For connectionless sockets, the - * destination port and address must be specified. Connected sockets, on the - * other hand, will use their associated remote endpoint, so the port and - * address arguments must not be set. - * - * > The msg argument contains the message to be sent. Depending on its type, - * different behavior can apply. If msg is a Buffer, any TypedArray, or a - * DataView, the offset and length specify the offset within the Buffer where - * the message begins and the number of bytes in the message, respectively. - * If msg is a String, then it is automatically converted to a Buffer with - * 'utf8' encoding. With messages that contain multi-byte characters, offset, - * and length will be calculated with respect to byte length and not the - * character position. If msg is an array, offset and length must not be - * specified. - * - * > The address argument is a string. If the value of the address is a hostname, - * DNS will be used to resolve the address of the host. If the address is not - * provided or otherwise nullish, '0.0.0.0' (for udp4 sockets) or '::1' - * (for udp6 sockets) will be used by default. - * - * > If the socket has not been previously bound with a call to bind, the socket - * is assigned a random port number and is bound to the "all interfaces" - * address ('0.0.0.0' for udp4 sockets, '::1' for udp6 sockets.) - * - * > An optional callback function may be specified as a way of reporting DNS - * errors or for determining when it is safe to reuse the buf object. DNS - * lookups delay the time to send for at least one tick of the Node.js event - * loop. - * - * > The only way to know for sure that the datagram has been sent is by using a - * callback. If an error occurs and a callback is given, the error will be - * passed as the first argument to the callback. If a callback is not given, - * the error is emitted as an 'error' event on the socket object. - * - * > Offset and length are optional but both must be set if either is used. - * They are supported only when the first argument is a Buffer, a TypedArray, - * or a DataView. - * - * @param {Buffer | TypedArray | DataView | string | Array} msg - Message to be sent. - * @param {integer=} offset - Offset in the buffer where the message starts. - * @param {integer=} length - Number of bytes in the message. - * @param {integer=} port - Destination port. - * @param {string=} address - Destination host name or IP address. - * @param {Function=} callback - Called when the message has been sent. - * @see {@link https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback} - */ - send(buffer: any, ...args: any[]): Promise; - /** - * Close the underlying socket and stop listening for data on it. If a - * callback is provided, it is added as a listener for the 'close' event. - * - * @param {function=} callback - Called when the connection is completed or on error. - * - * @see {@link https://nodejs.org/api/dgram.html#socketclosecallback} - */ - close(cb: any): this; - /** - * - * Returns an object containing the address information for a socket. For - * UDP sockets, this object will contain address, family, and port properties. - * - * This method throws EBADF if called on an unbound socket. - * @returns {Object} socketInfo - Information about the local socket - * @returns {string} socketInfo.address - The IP address of the socket - * @returns {string} socketInfo.port - The port of the socket - * @returns {string} socketInfo.family - The IP family of the socket - * - * @see {@link https://nodejs.org/api/dgram.html#socketaddress} - */ - address(): any; - /** - * Returns an object containing the address, family, and port of the remote - * endpoint. This method throws an ERR_SOCKET_DGRAM_NOT_CONNECTED exception - * if the socket is not connected. - * - * @returns {Object} socketInfo - Information about the remote socket - * @returns {string} socketInfo.address - The IP address of the socket - * @returns {string} socketInfo.port - The port of the socket - * @returns {string} socketInfo.family - The IP family of the socket - * @see {@link https://nodejs.org/api/dgram.html#socketremoteaddress} - */ - remoteAddress(): any; - /** - * Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in - * bytes. - * - * @param {number} size - The size of the new receive buffer - * @see {@link https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize} - */ - setRecvBufferSize(size: number): Promise; - /** - * Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in - * bytes. - * - * @param {number} size - The size of the new send buffer - * @see {@link https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize} - */ - setSendBufferSize(size: number): Promise; - /** - * @see {@link https://nodejs.org/api/dgram.html#socketgetrecvbuffersize} - */ - getRecvBufferSize(): any; - /** - * @returns {number} the SO_SNDBUF socket send buffer size in bytes. - * @see {@link https://nodejs.org/api/dgram.html#socketgetsendbuffersize} - */ - getSendBufferSize(): number; - setBroadcast(): void; - setTTL(): void; - setMulticastTTL(): void; - setMulticastLoopback(): void; - setMulticastMembership(): void; - setMulticastInterface(): void; - addMembership(): void; - dropMembership(): void; - addSourceSpecificMembership(): void; - dropSourceSpecificMembership(): void; - ref(): this; - unref(): this; - } + export function applyOutputContextReferences(context: object): void; /** - * Generic error class for an error occurring on a `Socket` instance. * @ignore + * @param {object} context */ - export class SocketError extends InternalError { - /** - * @type {string} - */ - get code(): string; - } - /** - * Thrown when a socket is already bound. - */ - export class ERR_SOCKET_ALREADY_BOUND extends exports.SocketError { - get message(): string; - } + export function filterNonTransferableValues(context: object): void; /** * @ignore + * @param {object=} [currentContext] + * @param {object=} [updatedContext] + * @param {object=} [contextReference] + * @return {{ deletions: string[], merges: string[] }} */ - export class ERR_SOCKET_BAD_BUFFER_SIZE extends exports.SocketError { - } + export function applyContextDifferences(currentContext?: object | undefined, updatedContext?: object | undefined, contextReference?: object | undefined, preserveScriptArgs?: boolean): { + deletions: string[]; + merges: string[]; + }; /** + * Wrap a JavaScript function source. * @ignore + * @param {string} source + * @param {object=} [options] */ - export class ERR_SOCKET_BUFFER_SIZE extends exports.SocketError { - } + export function wrapFunctionSource(source: string, options?: object | undefined): string; /** - * Thrown when the socket is already connected. + * Gets the VM context window. + * This function will create it if it does not already exist. + * The current window will be used on Android or iOS platforms as there can + * only be one window. + * @return {Promise; /** - * Thrown when the socket is not connected. + * Gets the `SharedWorker` that for the VM context. + * @return {Promise} */ - export class ERR_SOCKET_DGRAM_NOT_CONNECTED extends exports.SocketError { - syscall: string; - get message(): string; - } + export function getContextWorker(): Promise; /** - * Thrown when the socket is not running (not bound or connected). + * Terminates the VM script context window. + * @ignore */ - export class ERR_SOCKET_DGRAM_NOT_RUNNING extends exports.SocketError { - get message(): string; - } + export function terminateContextWindow(): Promise; /** - * Thrown when a bad socket type is used in an argument. + * Terminates the VM script context worker. + * @ignore */ - export class ERR_SOCKET_BAD_TYPE extends TypeError { - code: string; - get message(): string; - } + export function terminateContextWorker(): Promise; /** - * Thrown when a bad port is given. + * Creates a prototype object of known global reserved intrinsics. + * @ignore */ - export class ERR_SOCKET_BAD_PORT extends RangeError { - code: string; - } - export default exports; - export type SocketOptions = any; - import { EventEmitter } from "socket:events"; - import { InternalError } from "socket:errors"; - import * as exports from "socket:dgram"; - -} -declare module "socket:enumeration" { + export function createIntrinsics(options: any): any; /** - * @module enumeration - * This module provides a data structure for enumerated unique values. + * Returns `true` if value is an intrinsic, otherwise `false`. + * @param {any} value + * @return {boolean} */ + export function isIntrinsic(value: any): boolean; /** - * A container for enumerated values. + * Get the intrinsic type of a given `value`. + * @param {any} + * @return {function|object|null|undefined} */ - export class Enumeration extends Set { - /** - * Creates an `Enumeration` instance from arguments. - * @param {...any} values - * @return {Enumeration} - */ - static from(...values: any[]): Enumeration; - /** - * `Enumeration` class constructor. - * @param {any[]} values - * @param {object=} [options = {}] - * @param {number=} [options.start = 0] - */ - constructor(values: any[], options?: object | undefined); - /** - * @type {number} - */ - get length(): number; - /** - * Returns `true` if enumeration contains `value`. An alias - * for `Set.prototype.has`. - * @return {boolean} - */ - contains(value: any): boolean; - /** - * @ignore - */ - add(): void; - /** - * @ignore - */ - delete(): void; - /** - * JSON represenation of a `Enumeration` instance. - * @ignore - * @return {string[]} - */ - toJSON(): string[]; - /** - * Internal inspect function. - * @ignore - * @return {LanguageQueryResult} - */ - inspect(): LanguageQueryResult; - } - export default Enumeration; -} -declare module "socket:fs/web" { + export function getIntrinsicType(value: any): Function | object | null | undefined; /** - * Creates a new `File` instance from `filename`. - * @param {string} filename - * @param {{ fd: fs.FileHandle, highWaterMark?: number }=} [options] - * @return {File} + * Get the intrinsic type string of a given `value`. + * @param {any} + * @return {string|null} */ - export function createFile(filename: string, options?: { - fd: fs.FileHandle; - highWaterMark?: number; - }): File; + export function getIntrinsicTypeString(value: any): string | null; /** - * Creates a `FileSystemWritableFileStream` instance backed - * by `socket:fs:` module from a given `FileSystemFileHandle` instance. - * @param {string|File} file - * @return {Promise} + * Creates a global proxy object for context execution. + * @ignore + * @param {object} context + * @return {Proxy} */ - export function createFileSystemWritableFileStream(handle: any, options: any): Promise; + export function createGlobalObject(context: object, options: any): ProxyConstructor; /** - * Creates a `FileSystemFileHandle` instance backed by `socket:fs:` module from - * a given `File` instance or filename string. - * @param {string|File} file - * @param {object} [options] - * @return {Promise} + * @ignore + * @param {string} source + * @return {boolean} */ - export function createFileSystemFileHandle(file: string | File, options?: object): Promise; + export function detectFunctionSourceType(source: string): boolean; /** - * Creates a `FileSystemDirectoryHandle` instance backed by `socket:fs:` module - * from a given directory name string. - * @param {string} dirname - * @return {Promise} + * Compiles `source` with `options` into a function. + * @ignore + * @param {string} source + * @param {object=} [options] + * @return {function} */ - export function createFileSystemDirectoryHandle(dirname: string, options?: any): Promise; - export const File: { - new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; - prototype: File; - } | { - new (): { - readonly lastModifiedDate: Date; - readonly lastModified: number; - readonly name: any; - readonly size: number; - readonly type: string; - slice(): void; - arrayBuffer(): Promise; - text(): Promise; - stream(): void; - }; - }; - export const FileSystemHandle: { - new (): { - readonly name: any; - readonly kind: any; - }; - }; - export const FileSystemFileHandle: { - new (): FileSystemFileHandle; - prototype: FileSystemFileHandle; - } | { - new (): { - getFile(): void; - createWritable(options?: any): Promise; - createSyncAccessHandle(): Promise; - readonly name: any; - readonly kind: any; - }; - }; - export const FileSystemDirectoryHandle: { - new (): FileSystemDirectoryHandle; - prototype: FileSystemDirectoryHandle; - } | { - new (): { - entries(): AsyncGenerator; - values(): AsyncGenerator; - keys(): AsyncGenerator; - resolve(possibleDescendant: any): Promise; - removeEntry(name: any, options?: any): Promise; - getDirectoryHandle(name: any, options?: any): Promise; - getFileHandle(name: any, options?: any): Promise; - readonly name: any; - readonly kind: any; - }; - }; - export const FileSystemWritableFileStream: { - new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): { - seek(position: any): Promise; - truncate(size: any): Promise; - write(data: any): Promise; - readonly locked: boolean; - abort(reason?: any): Promise; - close(): Promise; - getWriter(): WritableStreamDefaultWriter; - }; - }; - namespace _default { - export { createFileSystemWritableFileStream }; - export { createFileSystemDirectoryHandle }; - export { createFileSystemFileHandle }; - export { createFile }; - } - export default _default; - import fs from "socket:fs/promises"; -} -declare module "socket:extension" { + export function compileFunction(source: string, options?: object | undefined): Function; /** - * Load an extension by name. - * @template {Record T} - * @param {string} name - * @param {ExtensionLoadOptions} [options] - * @return {Promise>} + * Run `source` JavaScript in given context. The script context execution + * context is preserved until the `context` object that points to it is + * garbage collected or there are no longer any references to it and its + * associated `Script` instance. + * @param {string|object|function} source + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} */ - export function load>(name: string, options?: ExtensionLoadOptions): Promise>; + export function runInContext(source: string | object | Function, options?: ScriptOptions | undefined, context?: object | undefined): Promise; /** - * Provides current stats about the loaded extensions. - * @return {Promise} + * Run `source` JavaScript in new context. The script context is destroyed after + * execution. This is typically a "one off" isolated run. + * @param {string} source + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} */ - export function stats(): Promise; + export function runInNewContext(source: string, options?: ScriptOptions | undefined, context?: object | undefined): Promise; /** - * @typedef {{ - * allow: string[] | string, - * imports?: object, - * type?: 'shared' | 'wasm32', - * path?: string, - * stats?: object, - * instance?: WebAssembly.Instance, - * adapter?: WebAssemblyExtensionAdapter - * }} ExtensionLoadOptions + * Run `source` JavaScript in this current context (`globalThis`). + * @param {string} source + * @param {ScriptOptions=} [options] + * @return {Promise} */ + export function runInThisContext(source: string, options?: ScriptOptions | undefined): Promise; /** - * @typedef {{ abi: number, version: string, description: string }} ExtensionInfo + * @ignore + * @param {Reference} reference */ + export function putReference(reference: Reference): void; /** - * @typedef {{ abi: number, loaded: number }} ExtensionStats + * Create a `Reference` for a `value` in a script `context`. + * @param {any} value + * @param {object} context + * @param {object=} [options] + * @return {Reference} */ + export function createReference(value: any, context: object, options?: object | undefined): Reference; /** - * A interface for a native extension. - * @template {Record T} + * Get a script context by ID or values + * @param {string|object|function} id + * @return {Reference?} */ - export class Extension> extends EventTarget { + export function getReference(id: string | object | Function): Reference | null; + /** + * Remove a script context reference by ID. + * @param {string} id + */ + export function removeReference(id: string): void; + /** + * Get all transferable values in the `object` hierarchy. + * @param {object} object + * @return {object[]} + */ + export function getTransferables(object: object): object[]; + /** + * @ignore + * @param {object} object + * @return {object} + */ + export function createContext(object: object): object; + /** + * Returns `true` if `object` is a "context" object. + * @param {object} + * @return {boolean} + */ + export function isContext(object: any): boolean; + /** + * A container for a context worker message channel that looks like a "worker". + * @ignore + */ + export class ContextWorkerInterface extends EventTarget { + get channel(): any; + get port(): any; + destroy(): void; + #private; + } + /** + * A container proxy for a context worker message channel that + * looks like a "worker". + * @ignore + */ + export class ContextWorkerInterfaceProxy extends EventTarget { + constructor(globals: any); + get port(): any; + #private; + } + /** + * Global reserved values that a script context may not modify. + * @type {string[]} + */ + export const RESERVED_GLOBAL_INTRINSICS: string[]; + /** + * A unique reference to a value owner by a "context object" and a + * `Script` instance. + */ + export class Reference { /** - * Load an extension by name. - * @template {Record T} - * @param {string} name - * @param {ExtensionLoadOptions} [options] - * @return {Promise>} + * Predicate function to determine if a `value` is an internal or external + * script reference value. + * @param {amy} value + * @return {boolean} */ - static load>(name: string, options?: ExtensionLoadOptions): Promise>; + static isReference(value: amy): boolean; /** - * Query type of extension by name. - * @param {string} name - * @return {Promise<'shared'|'wasm32'|'unknown'|null>} + * `Reference` class constructor. + * @param {string} id + * @param {any} value + * @param {object=} [context] + * @param {object=} [options] */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + constructor(id: string, value: any, context?: object | undefined, options?: object | undefined); /** - * Provides current stats about the loaded extensions or one by name. - * @param {?string} name - * @return {Promise} + * The unique id of the reference + * @type {string} */ - static stats(name: string | null): Promise; + get id(): string; /** - * `Extension` class constructor. - * @param {string} name - * @param {ExtensionInfo} info - * @param {ExtensionLoadOptions} [options] + * The underling primitive type of the reference value. + * @ignore + * @type {'undefined'|'object'|'number'|'boolean'|'function'|'symbol'} */ - constructor(name: string, info: ExtensionInfo, options?: ExtensionLoadOptions); + get type(): "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; /** - * The name of the extension - * @type {string?} + * The underlying value of the reference. + * @type {any?} */ - name: string | null; + get value(): any; /** - * The version of the extension + * The name of the type. * @type {string?} */ - version: string | null; + get name(): string; /** - * The description of the extension - * @type {string?} + * The `Script` this value belongs to, if available. + * @type {Script?} */ - description: string | null; + get script(): Script; /** - * The abi of the extension - * @type {number} + * The "context object" this reference value belongs to. + * @type {object?} */ - abi: number; + get context(): any; /** - * @type {object} + * A boolean value to indicate if the underlying reference value is an + * intrinsic value. + * @type {boolean} */ - options: object; + get isIntrinsic(): boolean; /** - * @type {T} + * A boolean value to indicate if the underlying reference value is an + * external reference value. + * @type {boolean} */ - binding: T; + get isExternal(): boolean; /** - * Not `null` if extension is of type 'wasm32' - * @type {?WebAssemblyExtensionAdapter} + * The intrinsic type this reference may be an instance of or directly refer to. + * @type {function|object} */ - adapter: WebAssemblyExtensionAdapter | null; + get intrinsicType(): any; /** - * `true` if the extension was loaded, otherwise `false` - * @type {boolean} + * Releases strongly held value and weak references + * to the "context object". */ - get loaded(): boolean; + release(): void; /** - * The extension type: 'shared' or 'wasm32' - * @type {'shared'|'wasm32'} + * Converts this `Reference` to a JSON object. + * @param {boolean=} [includeValue = false] */ - get type(): "shared" | "wasm32"; + toJSON(includeValue?: boolean | undefined): { + __vmScriptReference__: boolean; + id: string; + type: "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; + name: string; + isIntrinsic: boolean; + intrinsicType: string; + }; + #private; + } + /** + * @typedef {{ + * filename?: string, + * context?: object + * }} ScriptOptions + */ + /** + * A `Script` is a container for raw JavaScript to be executed in + * a completely isolated virtual machine context, optionally with + * user supplied context. Context objects references are not actually + * shared, but instead provided to the script execution context using the + * structured cloning algorithm used by the Message Channel API. Context + * differences are computed and applied after execution so the user supplied + * context object realizes context changes after script execution. All script + * sources run in an "async" context so a "top level await" should work. + */ + export class Script extends EventTarget { /** - * Unloads the loaded extension. - * @throws Error + * `Script` class constructor + * @param {string} source + * @param {ScriptOptions} [options] */ - unload(): Promise; - instance: any; - [$type]: "shared" | "wasm32"; - [$loaded]: boolean; + constructor(source: string, options?: ScriptOptions); + /** + * The script identifier. + */ + get id(): any; + /** + * The source for this script. + * @type {string} + */ + get source(): string; + /** + * The filename for this script. + * @type {string} + */ + get filename(): string; + /** + * A promise that resolves when the script is ready. + * @type {Promise} + */ + get ready(): Promise; + /** + * Destroy the script execution context. + * @return {Promise} + */ + destroy(): Promise; + /** + * Run `source` JavaScript in given context. The script context execution + * context is preserved until the `context` object that points to it is + * garbage collected or there are no longer any references to it and its + * associated `Script` instance. + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} + */ + runInContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + /** + * Run `source` JavaScript in new context. The script context is destroyed after + * execution. This is typically a "one off" isolated run. + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} + */ + runInNewContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + /** + * Run `source` JavaScript in this current context (`globalThis`). + * @param {ScriptOptions=} [options] + * @return {Promise} + */ + runInThisContext(options?: ScriptOptions | undefined): Promise; + #private; } namespace _default { - export { load }; - export { stats }; + export { createGlobalObject }; + export { compileFunction }; + export { createReference }; + export { getContextWindow }; + export { getContextWorker }; + export { getReference }; + export { getTransferables }; + export { putReference }; + export { Reference }; + export { removeReference }; + export { runInContext }; + export { runInNewContext }; + export { runInThisContext }; + export { Script }; + export { createContext }; + export { isContext }; } export default _default; - export type Pointer = number; - export type ExtensionLoadOptions = { - allow: string[] | string; - imports?: object; - type?: 'shared' | 'wasm32'; - path?: string; - stats?: object; - instance?: WebAssembly.Instance; - adapter?: WebAssemblyExtensionAdapter; - }; - export type ExtensionInfo = { - abi: number; - version: string; - description: string; - }; - export type ExtensionStats = { - abi: number; - loaded: number; + export type ScriptOptions = { + filename?: string; + context?: object; }; + import { SharedWorker } from "socket:internal/shared-worker"; +} +declare module "socket:worker_threads/init" { + export const SHARE_ENV: unique symbol; + export const isMainThread: boolean; + export namespace state { + export { isMainThread }; + export let parentPort: any; + export let mainPort: any; + export let workerData: any; + export let url: any; + export let env: {}; + export let id: number; + } + namespace _default { + export { state }; + } + export default _default; +} +declare module "socket:worker_threads" { /** - * An adapter for reading and writing various values from a WebAssembly instance's - * memory buffer. - * @ignore + * Set shared worker environment data. + * @param {string} key + * @param {any} value */ - class WebAssemblyExtensionAdapter { - constructor({ instance, module, table, memory, policies }: { - instance: any; - module: any; - table: any; - memory: any; - policies: any; - }); - view: any; - heap: any; - table: any; - stack: any; - buffer: any; - module: any; - memory: any; - context: any; - policies: any[]; - externalReferences: Map; - instance: any; - exitStatus: any; - textDecoder: TextDecoder; - textEncoder: TextEncoder; - errorMessagePointers: {}; - indirectFunctionTable: any; - get globalBaseOffset(): any; - destroy(): void; - init(): boolean; - get(pointer: any, size?: number): any; - set(pointer: any, value: any): void; - createExternalReferenceValue(value: any): any; - getExternalReferenceValue(pointer: any): any; - setExternalReferenceValue(pointer: any, value: any): Map; - removeExternalReferenceValue(pointer: any): void; - getExternalReferencePointer(value: any): any; - getFloat32(pointer: any): any; - setFloat32(pointer: any, value: any): boolean; - getFloat64(pointer: any): any; - setFloat64(pointer: any, value: any): boolean; - getInt8(pointer: any): any; - setInt8(pointer: any, value: any): boolean; - getInt16(pointer: any): any; - setInt16(pointer: any, value: any): boolean; - getInt32(pointer: any): any; - setInt32(pointer: any, value: any): boolean; - getUint8(pointer: any): any; - setUint8(pointer: any, value: any): boolean; - getUint16(pointer: any): any; - setUint16(pointer: any, value: any): boolean; - getUint32(pointer: any): any; - setUint32(pointer: any, value: any): boolean; - getString(pointer: any, buffer: any, size: any): string; - setString(pointer: any, string: any, buffer?: any): boolean; - } - const $type: unique symbol; + export function setEnvironmentData(key: string, value: any): void; /** - * @typedef {number} Pointer + * Get shared worker environment data. + * @param {string} key + * @return {any} */ - const $loaded: unique symbol; - import path from "socket:path"; -} -declare module "socket:fetch/fetch" { - export class DOMException { - private constructor(); - } - export function Headers(headers: any): void; - export class Headers { - constructor(headers: any); - map: {}; - append(name: any, value: any): void; - delete(name: any): void; - get(name: any): any; - has(name: any): boolean; - set(name: any, value: any): void; - forEach(callback: any, thisArg: any): void; - keys(): { - next: () => { - done: boolean; - value: any; - }; - }; - values(): { - next: () => { - done: boolean; - value: any; - }; - }; - entries(): { - next: () => { - done: boolean; - value: any; - }; - }; + export function getEnvironmentData(key: string): any; + /** + * A pool of known worker threads. + * @type {} + */ + export const workers: () => () => any; + /** + * `true` if this is the "main" thread, otherwise `false` + * The "main" thread is the top level webview window. + * @type {boolean} + */ + export const isMainThread: boolean; + /** + * The main thread `MessagePort` which is `null` when the + * current context is not the "main thread". + * @type {MessagePort?} + */ + export const mainPort: MessagePort | null; + /** + * A worker thread `BroadcastChannel` class. + */ + export class BroadcastChannel extends globalThis.BroadcastChannel { } - export function Request(input: any, options: any): void; - export class Request { - constructor(input: any, options: any); - url: string; - credentials: any; - headers: Headers; - method: any; - mode: any; - signal: any; - referrer: any; - clone(): Request; + /** + * A worker thread `MessageChannel` class. + */ + export class MessageChannel extends globalThis.MessageChannel { } - export function Response(bodyInit: any, options: any): void; - export class Response { - constructor(bodyInit: any, options: any); - type: string; - status: any; - ok: boolean; - statusText: string; - headers: Headers; - url: any; - clone(): Response; + /** + * A worker thread `MessagePort` class. + */ + export class MessagePort extends globalThis.MessagePort { } - export namespace Response { - function error(): Response; - function redirect(url: any, status: any): Response; + /** + * The current unique thread ID. + * @type {number} + */ + export const threadId: number; + /** + * The parent `MessagePort` instance + * @type {MessagePort?} + */ + export const parentPort: MessagePort | null; + /** + * Transferred "worker data" when creating a new `Worker` instance. + * @type {any?} + */ + export const workerData: any | null; + /** + * @typedef {{ + * env?: object, + * stdin?: boolean = false, + * stdout?: boolean = false, + * stderr?: boolean = false, + * workerData?: any, + * transferList?: any[], + * eval?: boolean = false + * }} WorkerOptions + + /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export class Worker extends EventEmitter { + /** + * `Worker` class constructor. + * @param {string} filename + * @param {WorkerOptions=} [options] + */ + constructor(filename: string, options?: WorkerOptions | undefined); + /** + * Handles incoming worker messages. + * @ignore + * @param {MessageEvent} event + */ + onWorkerMessage(event: MessageEvent): boolean; + /** + * Handles process environment change events + * @ignore + * @param {import('./process.js').ProcessEnvironmentEvent} event + */ + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + /** + * The unique ID for this `Worker` thread instace. + * @type {number} + */ + get id(): number; + get threadId(): number; + /** + * A `Writable` standard input stream if `{ stdin: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Writable?} + */ + get stdin(): Writable; + /** + * A `Readable` standard output stream if `{ stdout: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stdout(): Readable; + /** + * A `Readable` standard error stream if `{ stderr: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} + */ + get stderr(): Readable; + /** + * Terminates the `Worker` instance + */ + terminate(): void; + postMessage(...args: any[]): void; + #private; } - export function fetch(input: any, init: any): Promise; - export namespace fetch { - let polyfill: boolean; + namespace _default { + export { Worker }; + export { isMainThread }; + export { parentPort }; + export { setEnvironmentData }; + export { getEnvironmentData }; + export { workerData }; + export { threadId }; + export { SHARE_ENV }; } + export default _default; + /** + * /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export type WorkerOptions = { + env?: object; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + workerData?: any; + transferList?: any[]; + eval?: boolean; + }; + import { EventEmitter } from "socket:events"; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; + import { SHARE_ENV } from "socket:worker_threads/init"; + import init from "socket:worker_threads/init"; + import { env } from "socket:process"; + export { SHARE_ENV, init }; } -declare module "socket:fetch/index" { - export * from "socket:fetch/fetch"; - export default fetch; - import { fetch } from "socket:fetch/fetch"; +declare module "socket:child_process" { + /** + * Spawns a child process exeucting `command` with `args` + * @param {string} command + * @param {string[]|object=} [args] + * @param {object=} [options + * @return {ChildProcess} + */ + export function spawn(command: string, args?: (string[] | object) | undefined, options?: object | undefined): ChildProcess; + export function exec(command: any, options: any, callback: any): ChildProcess; + export function execFile(command: any, options: any, callback: any): ChildProcess; + namespace _default { + export { ChildProcess }; + export { spawn }; + export { execFile }; + export { exec }; + } + export default _default; + class ChildProcess extends EventEmitter { + constructor(options: any); + /** + * `true` if the child process was killed with kill()`, + * otherwise `false`. + * @type {boolean} + */ + get killed(): boolean; + /** + * The process identifier for the child process. This value is + * `> 0` if the process was spawned successfully, otherwise `0`. + * @type {number} + */ + get pid(): number; + /** + * The executable file name of the child process that is launched. This + * value is `null` until the child process has successfully been spawned. + * @type {string?} + */ + get spawnfile(): string; + /** + * The full list of command-line arguments the child process was spawned with. + * This value is an empty array until the child process has successfully been + * spawned. + * @type {string[]} + */ + get spawnargs(): string[]; + /** + * Always `false` as the IPC messaging is not supported. + * @type {boolean} + */ + get connected(): boolean; + /** + * The child process exit code. This value is `null` if the child process + * is still running, otherwise it is a positive integer. + * @type {number?} + */ + get exitCode(): number; + /** + * If available, the underlying `stdin` writable stream for + * the child process. + * @type {import('./stream').Writable?} + */ + get stdin(): import("socket:stream").Writable; + /** + * If available, the underlying `stdout` readable stream for + * the child process. + * @type {import('./stream').Readable?} + */ + get stdout(): import("socket:stream").Readable; + /** + * If available, the underlying `stderr` readable stream for + * the child process. + * @type {import('./stream').Readable?} + */ + get stderr(): import("socket:stream").Readable; + /** + * The underlying worker thread. + * @ignore + * @type {import('./worker_threads').Worker} + */ + get worker(): Worker; + /** + * This function does nothing, but is present for nodejs compat. + */ + disconnect(): boolean; + /** + * This function does nothing, but is present for nodejs compat. + * @return {boolean} + */ + send(): boolean; + /** + * This function does nothing, but is present for nodejs compat. + */ + ref(): boolean; + /** + * This function does nothing, but is present for nodejs compat. + */ + unref(): boolean; + /** + * Kills the child process. This function throws an error if the child + * process has not been spawned or is already killed. + * @param {number|string} signal + */ + kill(...args: any[]): this; + /** + * Spawns the child process. This function will thrown an error if the process + * is already spawned. + * @param {string} command + * @param {string[]=} [args] + * @return {ChildProcess} + */ + spawn(...args?: string[] | undefined): ChildProcess; + /** + * `EventTarget` based `addEventListener` method. + * @param {string} event + * @param {function(Event)} callback + * @param {{ once?: false }} [options] + */ + addEventListener(event: string, callback: (arg0: Event) => any, options?: { + once?: false; + }): void; + /** + * `EventTarget` based `removeEventListener` method. + * @param {string} event + * @param {function(Event)} callback + * @param {{ once?: false }} [options] + */ + removeEventListener(event: string, callback: (arg0: Event) => any): void; + #private; + } + import { EventEmitter } from "socket:events"; + import { Worker } from "socket:worker_threads"; } -declare module "socket:fetch" { - export * from "socket:fetch/index"; - export default fetch; - import fetch from "socket:fetch/index"; -} -declare module "socket:http" { - export function get(optionsOrURL: any, options: any, callback: any): Promise; - export const METHODS: string[]; - export const STATUS_CODES: { - 100: string; - 101: string; - 102: string; - 103: string; - 200: string; - 201: string; - 202: string; - 203: string; - 204: string; - 205: string; - 206: string; - 207: string; - 208: string; - 226: string; - 300: string; - 301: string; - 302: string; - 303: string; - 304: string; - 305: string; - 307: string; - 308: string; - 400: string; - 401: string; - 402: string; - 403: string; - 404: string; - 405: string; - 406: string; - 407: string; - 408: string; - 409: string; - 410: string; - 411: string; - 412: string; - 413: string; - 414: string; - 415: string; - 416: string; - 417: string; - 418: string; - 421: string; - 422: string; - 423: string; - 424: string; - 425: string; - 426: string; - 428: string; - 429: string; - 431: string; - 451: string; - 500: string; - 501: string; - 502: string; - 503: string; - 504: string; - 505: string; - 506: string; - 507: string; - 508: string; - 509: string; - 510: string; - 511: string; +declare module "socket:constants" { + export * from "socket:fs/constants"; + export * from "socket:window/constants"; + export const E2BIG: number; + export const EACCES: number; + export const EADDRINUSE: number; + export const EADDRNOTAVAIL: number; + export const EAFNOSUPPORT: number; + export const EAGAIN: number; + export const EALREADY: number; + export const EBADF: number; + export const EBADMSG: number; + export const EBUSY: number; + export const ECANCELED: number; + export const ECHILD: number; + export const ECONNABORTED: number; + export const ECONNREFUSED: number; + export const ECONNRESET: number; + export const EDEADLK: number; + export const EDESTADDRREQ: number; + export const EDOM: number; + export const EDQUOT: number; + export const EEXIST: number; + export const EFAULT: number; + export const EFBIG: number; + export const EHOSTUNREACH: number; + export const EIDRM: number; + export const EILSEQ: number; + export const EINPROGRESS: number; + export const EINTR: number; + export const EINVAL: number; + export const EIO: number; + export const EISCONN: number; + export const EISDIR: number; + export const ELOOP: number; + export const EMFILE: number; + export const EMLINK: number; + export const EMSGSIZE: number; + export const EMULTIHOP: number; + export const ENAMETOOLONG: number; + export const ENETDOWN: number; + export const ENETRESET: number; + export const ENETUNREACH: number; + export const ENFILE: number; + export const ENOBUFS: number; + export const ENODATA: number; + export const ENODEV: number; + export const ENOENT: number; + export const ENOEXEC: number; + export const ENOLCK: number; + export const ENOLINK: number; + export const ENOMEM: number; + export const ENOMSG: number; + export const ENOPROTOOPT: number; + export const ENOSPC: number; + export const ENOSR: number; + export const ENOSTR: number; + export const ENOSYS: number; + export const ENOTCONN: number; + export const ENOTDIR: number; + export const ENOTEMPTY: number; + export const ENOTSOCK: number; + export const ENOTSUP: number; + export const ENOTTY: number; + export const ENXIO: number; + export const EOPNOTSUPP: number; + export const EOVERFLOW: number; + export const EPERM: number; + export const EPIPE: number; + export const EPROTO: number; + export const EPROTONOSUPPORT: number; + export const EPROTOTYPE: number; + export const ERANGE: number; + export const EROFS: number; + export const ESPIPE: number; + export const ESRCH: number; + export const ESTALE: number; + export const ETIME: number; + export const ETIMEDOUT: number; + export const ETXTBSY: number; + export const EWOULDBLOCK: number; + export const EXDEV: number; + export const SIGHUP: number; + export const SIGINT: number; + export const SIGQUIT: number; + export const SIGILL: number; + export const SIGTRAP: number; + export const SIGABRT: number; + export const SIGIOT: number; + export const SIGBUS: number; + export const SIGFPE: number; + export const SIGKILL: number; + export const SIGUSR1: number; + export const SIGSEGV: number; + export const SIGUSR2: number; + export const SIGPIPE: number; + export const SIGALRM: number; + export const SIGTERM: number; + export const SIGCHLD: number; + export const SIGCONT: number; + export const SIGSTOP: number; + export const SIGTSTP: number; + export const SIGTTIN: number; + export const SIGTTOU: number; + export const SIGURG: number; + export const SIGXCPU: number; + export const SIGXFSZ: number; + export const SIGVTALRM: number; + export const SIGPROF: number; + export const SIGWINCH: number; + export const SIGIO: number; + export const SIGINFO: number; + export const SIGSYS: number; + const _default: { + SIGHUP: number; + SIGINT: number; + SIGQUIT: number; + SIGILL: number; + SIGTRAP: number; + SIGABRT: number; + SIGIOT: number; + SIGBUS: number; + SIGFPE: number; + SIGKILL: number; + SIGUSR1: number; + SIGSEGV: number; + SIGUSR2: number; + SIGPIPE: number; + SIGALRM: number; + SIGTERM: number; + SIGCHLD: number; + SIGCONT: number; + SIGSTOP: number; + SIGTSTP: number; + SIGTTIN: number; + SIGTTOU: number; + SIGURG: number; + SIGXCPU: number; + SIGXFSZ: number; + SIGVTALRM: number; + SIGPROF: number; + SIGWINCH: number; + SIGIO: number; + SIGINFO: number; + SIGSYS: number; + E2BIG: number; + EACCES: number; + EADDRINUSE: number; + EADDRNOTAVAIL: number; + EAFNOSUPPORT: number; + EAGAIN: number; + EALREADY: number; + EBADF: number; + EBADMSG: number; + EBUSY: number; + ECANCELED: number; + ECHILD: number; + ECONNABORTED: number; + ECONNREFUSED: number; + ECONNRESET: number; + EDEADLK: number; + EDESTADDRREQ: number; + EDOM: number; + EDQUOT: number; + EEXIST: number; + EFAULT: number; + EFBIG: number; + EHOSTUNREACH: number; + EIDRM: number; + EILSEQ: number; + EINPROGRESS: number; + EINTR: number; + EINVAL: number; + EIO: number; + EISCONN: number; + EISDIR: number; + ELOOP: number; + EMFILE: number; + EMLINK: number; + EMSGSIZE: number; + EMULTIHOP: number; + ENAMETOOLONG: number; + ENETDOWN: number; + ENETRESET: number; + ENETUNREACH: number; + ENFILE: number; + ENOBUFS: number; + ENODATA: number; + ENODEV: number; + ENOENT: number; + ENOEXEC: number; + ENOLCK: number; + ENOLINK: number; + ENOMEM: number; + ENOMSG: number; + ENOPROTOOPT: number; + ENOSPC: number; + ENOSR: number; + ENOSTR: number; + ENOSYS: number; + ENOTCONN: number; + ENOTDIR: number; + ENOTEMPTY: number; + ENOTSOCK: number; + ENOTSUP: number; + ENOTTY: number; + ENXIO: number; + EOPNOTSUPP: number; + EOVERFLOW: number; + EPERM: number; + EPIPE: number; + EPROTO: number; + EPROTONOSUPPORT: number; + EPROTOTYPE: number; + ERANGE: number; + EROFS: number; + ESPIPE: number; + ESRCH: number; + ESTALE: number; + ETIME: number; + ETIMEDOUT: number; + ETXTBSY: number; + EWOULDBLOCK: number; + EXDEV: number; + WINDOW_ERROR: -1; + WINDOW_NONE: 0; + WINDOW_CREATING: 10; + WINDOW_CREATED: 11; + WINDOW_HIDING: 20; + WINDOW_HIDDEN: 21; + WINDOW_SHOWING: 30; + WINDOW_SHOWN: 31; + WINDOW_CLOSING: 40; + WINDOW_CLOSED: 41; + WINDOW_EXITING: 50; + WINDOW_EXITED: 51; + WINDOW_KILLING: 60; + WINDOW_KILLED: 61; + default: typeof window; + COPYFILE_EXCL: 1; + COPYFILE_FICLONE: 2; + COPYFILE_FICLONE_FORCE: 4; + UV_DIRENT_UNKNOWN: any; + UV_DIRENT_FILE: any; + UV_DIRENT_DIR: any; + UV_DIRENT_LINK: any; + UV_DIRENT_FIFO: any; + UV_DIRENT_SOCKET: any; + UV_DIRENT_CHAR: any; + UV_DIRENT_BLOCK: any; + UV_FS_SYMLINK_DIR: any; + UV_FS_SYMLINK_JUNCTION: any; + O_RDONLY: any; + O_WRONLY: any; + O_RDWR: any; + O_APPEND: any; + O_ASYNC: any; + O_CLOEXEC: any; + O_CREAT: any; + O_DIRECT: any; + O_DIRECTORY: any; + O_DSYNC: any; + O_EXCL: any; + O_LARGEFILE: any; + O_NOATIME: any; + O_NOCTTY: any; + O_NOFOLLOW: any; + O_NONBLOCK: any; + O_NDELAY: any; + O_PATH: any; + O_SYNC: any; + O_TMPFILE: any; + O_TRUNC: any; + S_IFMT: any; + S_IFREG: any; + S_IFDIR: any; + S_IFCHR: any; + S_IFBLK: any; + S_IFIFO: any; + S_IFLNK: any; + S_IFSOCK: any; + S_IRWXU: any; + S_IRUSR: any; + S_IWUSR: any; + S_IXUSR: any; + S_IRWXG: any; + S_IRGRP: any; + S_IWGRP: any; + S_IXGRP: any; + S_IRWXO: any; + S_IROTH: any; + S_IWOTH: any; + S_IXOTH: any; + F_OK: any; + R_OK: any; + W_OK: any; + X_OK: any; }; - export class OutgoingMessage extends Writable { - headers: Headers; - get headersSent(): boolean; - get socket(): this; - get writableEnded(): boolean; - appendHeader(name: any, value: any): this; - setHeader(name: any, value: any): this; - flushHeaders(): void; - getHeader(name: any): string; - getHeaderNames(): string[]; - getHeaders(): { - [k: string]: string; - }; - hasHeader(name: any): boolean; - removeHeader(name: any): void; - } - export class ClientRequest extends OutgoingMessage { - url: any; - path: any; - host: any; - agent: any; - method: any; - protocol: string; - } - export class ServerResponse extends OutgoingMessage { - statusCode: number; - statusMessage: string; - req: any; - } - export class AgentOptions { - constructor(options: any); - keepAlive: boolean; - timeout: number; - } - export class Agent extends EventEmitter { - constructor(options: any); - defaultProtocol: string; - options: any; - createConnection(options: any, callback?: any): Duplex; - } - export const globalAgent: Agent; + export default _default; + import window from "socket:window/constants"; +} +declare module "socket:ip" { + /** + * Normalizes input as an IPv4 address string + * @param {string|object|string[]|Uint8Array} input + * @return {string} + */ + export function normalizeIPv4(input: string | object | string[] | Uint8Array): string; + /** + * Determines if an input `string` is in IP address version 4 format. + * @param {string|object|string[]|Uint8Array} input + * @return {boolean} + */ + export function isIPv4(input: string | object | string[] | Uint8Array): boolean; namespace _default { - export { METHODS }; - export { STATUS_CODES }; - export { AgentOptions }; - export { Agent }; - export { globalAgent }; - export { request }; - export { OutgoingMessage }; - export { ClientRequest }; - export { ServerResponse }; - export { get }; + export { normalizeIPv4 }; + export { isIPv4 }; } export default _default; - import { Writable } from "socket:stream"; - import { EventEmitter } from "socket:events"; - import { Duplex } from "socket:stream"; - function request(optionsOrURL: any, options: any, callback: any): Promise; } -declare module "socket:https" { - export function request(optionsOrURL: any, options: any, callback: any): Promise; - export function get(optionsOrURL: any, options: any, callback: any): Promise; - export const METHODS: string[]; - export const STATUS_CODES: { - 100: string; - 101: string; - 102: string; - 103: string; - 200: string; - 201: string; - 202: string; - 203: string; - 204: string; - 205: string; - 206: string; - 207: string; - 208: string; - 226: string; - 300: string; - 301: string; - 302: string; - 303: string; - 304: string; - 305: string; - 307: string; - 308: string; - 400: string; - 401: string; - 402: string; - 403: string; - 404: string; - 405: string; - 406: string; - 407: string; - 408: string; - 409: string; - 410: string; - 411: string; - 412: string; - 413: string; - 414: string; - 415: string; - 416: string; - 417: string; - 418: string; - 421: string; - 422: string; - 423: string; - 424: string; - 425: string; - 426: string; - 428: string; - 429: string; - 431: string; - 451: string; - 500: string; - 501: string; - 502: string; - 503: string; - 504: string; - 505: string; - 506: string; - 507: string; - 508: string; - 509: string; - 510: string; - 511: string; - }; - const AgentOptions_base: typeof import("socket:http").AgentOptions; - export class AgentOptions extends AgentOptions_base { - } - const Agent_base: typeof import("socket:http").Agent; - export class Agent extends Agent_base { - } - const OutgoingMessage_base: typeof import("socket:http").OutgoingMessage; - export class OutgoingMessage extends OutgoingMessage_base { - } - const ClientRequest_base: typeof import("socket:http").ClientRequest; - export class ClientRequest extends ClientRequest_base { - } - const ServerResponse_base: typeof import("socket:http").ServerResponse; - export class ServerResponse extends ServerResponse_base { - } - export const globalAgent: Agent; - namespace _default { - export { METHODS }; - export { STATUS_CODES }; - export { AgentOptions }; - export { Agent }; - export { globalAgent }; - export { request }; - export { OutgoingMessage }; - export { ClientRequest }; - export { ServerResponse }; - export { get }; - } - export default _default; -} -declare module "socket:language" { - /** - * Look up a language name or code by query. - * @param {string} query - * @param {object=} [options] - * @param {boolean=} [options.strict = false] - * @return {?LanguageQueryResult[]} - */ - export function lookup(query: string, options?: object | undefined, ...args: any[]): LanguageQueryResult[] | null; - /** - * Describe a language by tag - * @param {string} query - * @param {object=} [options] - * @param {boolean=} [options.strict = true] - * @return {?LanguageDescription[]} - */ - export function describe(query: string, options?: object | undefined): LanguageDescription[] | null; - /** - * A list of ISO 639-1 language names. - * @type {string[]} - */ - export const names: string[]; - /** - * A list of ISO 639-1 language codes. - * @type {string[]} - */ - export const codes: string[]; +declare module "socket:dns/promises" { /** - * A list of RFC 5646 language tag identifiers. - * @see {@link http://tools.ietf.org/html/rfc5646} + * @async + * @see {@link https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options} + * @param {string} hostname - The host name to resolve. + * @param {Object=} opts - An options object. + * @param {(number|string)=} [opts.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. + * @returns {Promise} */ - export const tags: Enumeration; + export function lookup(hostname: string, opts?: any | undefined): Promise; + export default exports; + import * as exports from "socket:dns/promises"; + +} +declare module "socket:dns/index" { /** - * A list of RFC 5646 language tag titles corresponding - * to language tags. - * @see {@link http://tools.ietf.org/html/rfc5646} + * Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or + * AAAA (IPv6) record. All option properties are optional. If options is an + * integer, then it must be 4 or 6 – if options is 0 or not provided, then IPv4 + * and IPv6 addresses are both returned if found. + * + * From the node.js website... + * + * > With the all option set to true, the arguments for callback change to (err, + * addresses), with addresses being an array of objects with the properties + * address and family. + * + * > On error, err is an Error object, where err.code is the error code. Keep in + * mind that err.code will be set to 'ENOTFOUND' not only when the host name does + * not exist but also when the lookup fails in other ways such as no available + * file descriptors. dns.lookup() does not necessarily have anything to do with + * the DNS protocol. The implementation uses an operating system facility that + * can associate names with addresses and vice versa. This implementation can + * have subtle but important consequences on the behavior of any Node.js program. + * Please take some time to consult the Implementation considerations section + * before using dns.lookup(). + * + * @see {@link https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback} + * @param {string} hostname - The host name to resolve. + * @param {(object|intenumberger)=} [options] - An options object or record family. + * @param {(number|string)=} [options.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. + * @param {function} cb - The function to call after the method is complete. + * @returns {void} */ - export const descriptions: Enumeration; + export function lookup(hostname: string, options?: (object | intenumberger) | undefined, cb: Function): void; + export { promises }; + export default exports; + import * as promises from "socket:dns/promises"; + import * as exports from "socket:dns/index"; + +} +declare module "socket:dns" { + export * from "socket:dns/index"; + export default exports; + import * as exports from "socket:dns/index"; +} +declare module "socket:dgram" { + export function createSocket(options: string | any, callback?: Function | undefined): Socket; /** - * A container for a language query response containing an ISO language - * name and code. - * @see {@link https://www.sitepoint.com/iso-2-letter-language-codes} + * New instances of dgram.Socket are created using dgram.createSocket(). + * The new keyword is not to be used to create dgram.Socket instances. */ - export class LanguageQueryResult { - /** - * `LanguageQueryResult` class constructor. - * @param {string} code - * @param {string} name - * @param {string[]} [tags] - */ - constructor(code: string, name: string, tags?: string[]); - /** - * The language code corresponding to the query. - * @type {string} - */ - get code(): string; - /** - * The language name corresponding to the query. - * @type {string} - */ - get name(): string; - /** - * The language tags corresponding to the query. - * @type {string[]} - */ - get tags(): string[]; + export class Socket extends EventEmitter { + constructor(options: any, callback: any); + id: any; + knownIdWasGivenInSocketConstruction: boolean; + type: any; + signal: any; + state: { + recvBufferSize: any; + sendBufferSize: any; + bindState: number; + connectState: number; + reuseAddr: boolean; + ipv6Only: boolean; + }; /** - * JSON represenation of a `LanguageQueryResult` instance. - * @return {{ - * code: string, - * name: string, - * tags: string[] - * }} + * Listen for datagram messages on a named port and optional address + * If the address is not specified, the operating system will attempt to + * listen on all addresses. Once the binding is complete, a 'listening' + * event is emitted and the optional callback function is called. + * + * If binding fails, an 'error' event is emitted. + * + * @param {number} port - The port to listen for messages on + * @param {string} address - The address to bind to (0.0.0.0) + * @param {function} callback - With no parameters. Called when binding is complete. + * @see {@link https://nodejs.org/api/dgram.html#socketbindport-address-callback} */ - toJSON(): { - code: string; - name: string; - tags: string[]; - }; + bind(arg1: any, arg2: any, arg3: any): this; + dataListener: ({ detail }: { + detail: any; + }) => any; /** - * Internal inspect function. - * @ignore - * @return {LanguageQueryResult} + * Associates the dgram.Socket to a remote address and port. Every message sent + * by this handle is automatically sent to that destination. Also, the socket + * will only receive messages from that remote peer. Trying to call connect() + * on an already connected socket will result in an ERR_SOCKET_DGRAM_IS_CONNECTED + * exception. If the address is not provided, '0.0.0.0' (for udp4 sockets) or '::1' + * (for udp6 sockets) will be used by default. Once the connection is complete, + * a 'connect' event is emitted and the optional callback function is called. + * In case of failure, the callback is called or, failing this, an 'error' event + * is emitted. + * + * @param {number} port - Port the client should connect to. + * @param {string=} host - Host the client should connect to. + * @param {function=} connectListener - Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. + * @see {@link https://nodejs.org/api/dgram.html#socketconnectport-address-callback} */ - inspect(): LanguageQueryResult; - #private; - } - /** - * A container for a language code, tag, and description. - */ - export class LanguageDescription { + connect(arg1: any, arg2: any, arg3: any): void; /** - * `LanguageDescription` class constructor. - * @param {string} code - * @param {string} tag - * @param {string} description + * A synchronous function that disassociates a connected dgram.Socket from + * its remote address. Trying to call disconnect() on an unbound or already + * disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. + * + * @see {@link https://nodejs.org/api/dgram.html#socketdisconnect} */ - constructor(code: string, tag: string, description: string); + disconnect(): void; /** - * The language code corresponding to the language - * @type {string} + * Broadcasts a datagram on the socket. For connectionless sockets, the + * destination port and address must be specified. Connected sockets, on the + * other hand, will use their associated remote endpoint, so the port and + * address arguments must not be set. + * + * > The msg argument contains the message to be sent. Depending on its type, + * different behavior can apply. If msg is a Buffer, any TypedArray, or a + * DataView, the offset and length specify the offset within the Buffer where + * the message begins and the number of bytes in the message, respectively. + * If msg is a String, then it is automatically converted to a Buffer with + * 'utf8' encoding. With messages that contain multi-byte characters, offset, + * and length will be calculated with respect to byte length and not the + * character position. If msg is an array, offset and length must not be + * specified. + * + * > The address argument is a string. If the value of the address is a hostname, + * DNS will be used to resolve the address of the host. If the address is not + * provided or otherwise nullish, '0.0.0.0' (for udp4 sockets) or '::1' + * (for udp6 sockets) will be used by default. + * + * > If the socket has not been previously bound with a call to bind, the socket + * is assigned a random port number and is bound to the "all interfaces" + * address ('0.0.0.0' for udp4 sockets, '::1' for udp6 sockets.) + * + * > An optional callback function may be specified as a way of reporting DNS + * errors or for determining when it is safe to reuse the buf object. DNS + * lookups delay the time to send for at least one tick of the Node.js event + * loop. + * + * > The only way to know for sure that the datagram has been sent is by using a + * callback. If an error occurs and a callback is given, the error will be + * passed as the first argument to the callback. If a callback is not given, + * the error is emitted as an 'error' event on the socket object. + * + * > Offset and length are optional but both must be set if either is used. + * They are supported only when the first argument is a Buffer, a TypedArray, + * or a DataView. + * + * @param {Buffer | TypedArray | DataView | string | Array} msg - Message to be sent. + * @param {integer=} offset - Offset in the buffer where the message starts. + * @param {integer=} length - Number of bytes in the message. + * @param {integer=} port - Destination port. + * @param {string=} address - Destination host name or IP address. + * @param {Function=} callback - Called when the message has been sent. + * @see {@link https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback} */ - get code(): string; + send(buffer: any, ...args: any[]): Promise; /** - * The language tag corresponding to the language. - * @type {string} + * Close the underlying socket and stop listening for data on it. If a + * callback is provided, it is added as a listener for the 'close' event. + * + * @param {function=} callback - Called when the connection is completed or on error. + * + * @see {@link https://nodejs.org/api/dgram.html#socketclosecallback} */ - get tag(): string; + close(cb: any): this; /** - * The language description corresponding to the language. - * @type {string} + * + * Returns an object containing the address information for a socket. For + * UDP sockets, this object will contain address, family, and port properties. + * + * This method throws EBADF if called on an unbound socket. + * @returns {Object} socketInfo - Information about the local socket + * @returns {string} socketInfo.address - The IP address of the socket + * @returns {string} socketInfo.port - The port of the socket + * @returns {string} socketInfo.family - The IP family of the socket + * + * @see {@link https://nodejs.org/api/dgram.html#socketaddress} */ - get description(): string; + address(): any; /** - * JSON represenation of a `LanguageDescription` instance. - * @return {{ - * code: string, - * tag: string, - * description: string - * }} + * Returns an object containing the address, family, and port of the remote + * endpoint. This method throws an ERR_SOCKET_DGRAM_NOT_CONNECTED exception + * if the socket is not connected. + * + * @returns {Object} socketInfo - Information about the remote socket + * @returns {string} socketInfo.address - The IP address of the socket + * @returns {string} socketInfo.port - The port of the socket + * @returns {string} socketInfo.family - The IP family of the socket + * @see {@link https://nodejs.org/api/dgram.html#socketremoteaddress} */ - toJSON(): { - code: string; - tag: string; - description: string; - }; + remoteAddress(): any; /** - * Internal inspect function. - * @ignore - * @return {LanguageDescription} + * Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in + * bytes. + * + * @param {number} size - The size of the new receive buffer + * @see {@link https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize} */ - inspect(): LanguageDescription; - #private; - } - namespace _default { - export { codes }; - export { describe }; - export { lookup }; - export { names }; - export { tags }; + setRecvBufferSize(size: number): Promise; + /** + * Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in + * bytes. + * + * @param {number} size - The size of the new send buffer + * @see {@link https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize} + */ + setSendBufferSize(size: number): Promise; + /** + * @see {@link https://nodejs.org/api/dgram.html#socketgetrecvbuffersize} + */ + getRecvBufferSize(): any; + /** + * @returns {number} the SO_SNDBUF socket send buffer size in bytes. + * @see {@link https://nodejs.org/api/dgram.html#socketgetsendbuffersize} + */ + getSendBufferSize(): number; + setBroadcast(): void; + setTTL(): void; + setMulticastTTL(): void; + setMulticastLoopback(): void; + setMulticastMembership(): void; + setMulticastInterface(): void; + addMembership(): void; + dropMembership(): void; + addSourceSpecificMembership(): void; + dropSourceSpecificMembership(): void; + ref(): this; + unref(): this; } - export default _default; - import Enumeration from "socket:enumeration"; -} -declare module "socket:i18n" { /** - * Get messages for `locale` pattern. This function could return many results - * for various locales given a `locale` pattern. such as `fr`, which could - * return results for `fr`, `fr-FR`, `fr-BE`, etc. + * Generic error class for an error occurring on a `Socket` instance. * @ignore - * @param {string} locale - * @return {object[]} */ - export function getMessagesForLocale(locale: string): object[]; - /** - * Returns user preferred ISO 639 language codes or RFC 5646 language tags. - * @return {string[]} - */ - export function getAcceptLanguages(): string[]; + export class SocketError extends InternalError { + /** + * @type {string} + */ + get code(): string; + } /** - * Returns the current user ISO 639 language code or RFC 5646 language tag. - * @return {?string} + * Thrown when a socket is already bound. */ - export function getUILanguage(): string | null; + export class ERR_SOCKET_ALREADY_BOUND extends exports.SocketError { + get message(): string; + } /** - * Gets a localized message string for the specified message name. - * @param {string} messageName - * @param {object|string[]=} [substitutions = []] - * @param {object=} [options] - * @param {string=} [options.locale = null] - * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} - * @see {@link https://www.ibm.com/docs/en/rbd/9.5.1?topic=syslib-getmessage} - * @return {?string} + * @ignore */ - export function getMessage(messageName: string, substitutions?: (object | string[]) | undefined, options?: object | undefined): string | null; + export class ERR_SOCKET_BAD_BUFFER_SIZE extends exports.SocketError { + } /** - * Gets a localized message description string for the specified message name. - * @param {string} messageName - * @param {object=} [options] - * @param {string=} [options.locale = null] - * @return {?string} + * @ignore */ - export function getMessageDescription(messageName: string, options?: object | undefined): string | null; + export class ERR_SOCKET_BUFFER_SIZE extends exports.SocketError { + } /** - * A cache of loaded locale messages. - * @type {Map} + * Thrown when the socket is already connected. */ - export const cache: Map; + export class ERR_SOCKET_DGRAM_IS_CONNECTED extends exports.SocketError { + get message(): string; + } /** - * Default location of i18n locale messages - * @type {string} + * Thrown when the socket is not connected. */ - export const DEFAULT_LOCALES_LOCATION: string; + export class ERR_SOCKET_DGRAM_NOT_CONNECTED extends exports.SocketError { + syscall: string; + get message(): string; + } /** - * An enumeration of supported ISO 639 language codes or RFC 5646 language tags. - * @type {Enumeration} - * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/LanguageCode} - * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + * Thrown when the socket is not running (not bound or connected). */ - export const LanguageCode: Enumeration; - namespace _default { - export { LanguageCode }; - export { getAcceptLanguages }; - export { getMessage }; - export { getUILanguage }; + export class ERR_SOCKET_DGRAM_NOT_RUNNING extends exports.SocketError { + get message(): string; } - export default _default; - import Enumeration from "socket:enumeration"; -} -declare module "socket:stream-relay/packets" { /** - * The magic bytes prefixing every packet. They are the - * 2nd, 3rd, 5th, and 7th, prime numbers. - * @type {number[]} + * Thrown when a bad socket type is used in an argument. */ - export const MAGIC_BYTES_PREFIX: number[]; + export class ERR_SOCKET_BAD_TYPE extends TypeError { + code: string; + get message(): string; + } /** - * The version of the protocol. + * Thrown when a bad port is given. */ - export const VERSION: 6; + export class ERR_SOCKET_BAD_PORT extends RangeError { + code: string; + } + export default exports; + export type SocketOptions = any; + import { EventEmitter } from "socket:events"; + import { InternalError } from "socket:errors"; + import * as exports from "socket:dgram"; + +} +declare module "socket:enumeration" { /** - * The size in bytes of the prefix magic bytes. + * @module enumeration + * This module provides a data structure for enumerated unique values. */ - export const MAGIC_BYTES: 4; /** - * The maximum size of the user message. + * A container for enumerated values. */ - export const MESSAGE_BYTES: 1024; - /** - * The cache TTL in milliseconds. - */ - export const CACHE_TTL: number; - export namespace PACKET_SPEC { - namespace type { - let bytes: number; - let encoding: string; - } - namespace version { - let bytes_1: number; - export { bytes_1 as bytes }; - let encoding_1: string; - export { encoding_1 as encoding }; - export { VERSION as default }; - } - namespace clock { - let bytes_2: number; - export { bytes_2 as bytes }; - let encoding_2: string; - export { encoding_2 as encoding }; - let _default: number; - export { _default as default }; - } - namespace hops { - let bytes_3: number; - export { bytes_3 as bytes }; - let encoding_3: string; - export { encoding_3 as encoding }; - let _default_1: number; - export { _default_1 as default }; - } - namespace index { - let bytes_4: number; - export { bytes_4 as bytes }; - let encoding_4: string; - export { encoding_4 as encoding }; - let _default_2: number; - export { _default_2 as default }; - export let signed: boolean; - } - namespace ttl { - let bytes_5: number; - export { bytes_5 as bytes }; - let encoding_5: string; - export { encoding_5 as encoding }; - export { CACHE_TTL as default }; - } - namespace clusterId { - let bytes_6: number; - export { bytes_6 as bytes }; - let encoding_6: string; - export { encoding_6 as encoding }; - let _default_3: number[]; - export { _default_3 as default }; - } - namespace subclusterId { - let bytes_7: number; - export { bytes_7 as bytes }; - let encoding_7: string; - export { encoding_7 as encoding }; - let _default_4: number[]; - export { _default_4 as default }; - } - namespace previousId { - let bytes_8: number; - export { bytes_8 as bytes }; - let encoding_8: string; - export { encoding_8 as encoding }; - let _default_5: number[]; - export { _default_5 as default }; - } - namespace packetId { - let bytes_9: number; - export { bytes_9 as bytes }; - let encoding_9: string; - export { encoding_9 as encoding }; - let _default_6: number[]; - export { _default_6 as default }; - } - namespace nextId { - let bytes_10: number; - export { bytes_10 as bytes }; - let encoding_10: string; - export { encoding_10 as encoding }; - let _default_7: number[]; - export { _default_7 as default }; - } - namespace usr1 { - let bytes_11: number; - export { bytes_11 as bytes }; - let _default_8: number[]; - export { _default_8 as default }; - } - namespace usr2 { - let bytes_12: number; - export { bytes_12 as bytes }; - let _default_9: number[]; - export { _default_9 as default }; - } - namespace usr3 { - let bytes_13: number; - export { bytes_13 as bytes }; - let _default_10: number[]; - export { _default_10 as default }; - } - namespace usr4 { - let bytes_14: number; - export { bytes_14 as bytes }; - let _default_11: number[]; - export { _default_11 as default }; - } - namespace message { - let bytes_15: number; - export { bytes_15 as bytes }; - let _default_12: number[]; - export { _default_12 as default }; - } - namespace sig { - let bytes_16: number; - export { bytes_16 as bytes }; - let _default_13: number[]; - export { _default_13 as default }; - } - } - /** - * The size in bytes of the total packet frame and message. - */ - export const PACKET_BYTES: number; - /** - * The maximum distance that a packet can be replicated. - */ - export const MAX_HOPS: 16; - export function validatePacket(o: any, constraints: { - [key: string]: { - required: boolean; - type: string; - }; - }): void; - /** - * Computes a SHA-256 hash of input returning a hex encoded string. - * @type {function(string|Buffer|Uint8Array): Promise} - */ - export const sha256: (arg0: string | Buffer | Uint8Array) => Promise; - export function decode(buf: Buffer): Packet; - export function getTypeFromBytes(buf: any): any; - export class Packet { - static ttl: number; - static maxLength: number; + export class Enumeration extends Set { /** - * Returns an empty `Packet` instance. - * @return {Packet} + * Creates an `Enumeration` instance from arguments. + * @param {...any} values + * @return {Enumeration} */ - static empty(): Packet; + static from(...values: any[]): Enumeration; /** - * @param {Packet|object} packet - * @return {Packet} + * `Enumeration` class constructor. + * @param {any[]} values + * @param {object=} [options = {}] + * @param {number=} [options.start = 0] */ - static from(packet: Packet | object): Packet; + constructor(values: any[], options?: object | undefined); /** - * Determines if input is a packet. - * @param {Buffer|Uint8Array|number[]|object|Packet} packet + * @type {number} + */ + get length(): number; + /** + * Returns `true` if enumeration contains `value`. An alias + * for `Set.prototype.has`. * @return {boolean} */ - static isPacket(packet: Buffer | Uint8Array | number[] | object | Packet): boolean; + contains(value: any): boolean; /** - */ - static encode(p: any): Promise; - static decode(buf: any): Packet; + * @ignore + */ + add(): void; /** - * `Packet` class constructor. - * @param {Packet|object?} options + * @ignore */ - constructor(options?: Packet | (object | null)); + delete(): void; /** - * @param {Packet} packet - * @return {Packet} + * JSON represenation of a `Enumeration` instance. + * @ignore + * @return {string[]} */ - copy(): Packet; - timestamp: any; - isComposed: any; - isReconciled: any; - meta: any; - } - export class PacketPing extends Packet { - static type: number; - constructor({ message, clusterId, subclusterId }: { - message: any; - clusterId: any; - subclusterId: any; - }); - } - export class PacketPong extends Packet { - static type: number; - constructor({ message, clusterId, subclusterId }: { - message: any; - clusterId: any; - subclusterId: any; - }); - } - export class PacketIntro extends Packet { - static type: number; - constructor({ clock, hops, clusterId, subclusterId, usr1, message }: { - clock: any; - hops: any; - clusterId: any; - subclusterId: any; - usr1: any; - message: any; - }); - } - export class PacketJoin extends Packet { - static type: number; - constructor({ clock, hops, clusterId, subclusterId, message }: { - clock: any; - hops: any; - clusterId: any; - subclusterId: any; - message: any; - }); - } - export class PacketPublish extends Packet { - static type: number; - constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }: { - message: any; - sig: any; - packetId: any; - clusterId: any; - subclusterId: any; - nextId: any; - clock: any; - hops: any; - usr1: any; - usr2: any; - ttl: any; - previousId: any; - }); + toJSON(): string[]; + /** + * Internal inspect function. + * @ignore + * @return {LanguageQueryResult} + */ + inspect(): LanguageQueryResult; } - export class PacketStream extends Packet { - static type: number; - constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }: { - message: any; - sig: any; - packetId: any; - clusterId: any; - subclusterId: any; - nextId: any; - clock: any; - ttl: any; - usr1: any; - usr2: any; - usr3: any; - usr4: any; - previousId: any; - }); - } - export class PacketSync extends Packet { - static type: number; - constructor({ packetId, message }: { - packetId: any; - message?: any; - }); - } - export class PacketQuery extends Packet { - static type: number; - constructor({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }: { - packetId: any; - previousId: any; - subclusterId: any; - usr1: any; - usr2: any; - usr3: any; - usr4: any; - message?: {}; - }); + export default Enumeration; +} +declare module "socket:fs/web" { + /** + * Creates a new `File` instance from `filename`. + * @param {string} filename + * @param {{ fd: fs.FileHandle, highWaterMark?: number }=} [options] + * @return {File} + */ + export function createFile(filename: string, options?: { + fd: fs.FileHandle; + highWaterMark?: number; + }): File; + /** + * Creates a `FileSystemWritableFileStream` instance backed + * by `socket:fs:` module from a given `FileSystemFileHandle` instance. + * @param {string|File} file + * @return {Promise} + */ + export function createFileSystemWritableFileStream(handle: any, options: any): Promise; + /** + * Creates a `FileSystemFileHandle` instance backed by `socket:fs:` module from + * a given `File` instance or filename string. + * @param {string|File} file + * @param {object} [options] + * @return {Promise} + */ + export function createFileSystemFileHandle(file: string | File, options?: object): Promise; + /** + * Creates a `FileSystemDirectoryHandle` instance backed by `socket:fs:` module + * from a given directory name string. + * @param {string} dirname + * @return {Promise} + */ + export function createFileSystemDirectoryHandle(dirname: string, options?: any): Promise; + export const File: { + new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; + prototype: File; + } | { + new (): { + readonly lastModifiedDate: Date; + readonly lastModified: number; + readonly name: any; + readonly size: number; + readonly type: string; + slice(): void; + arrayBuffer(): Promise; + text(): Promise; + stream(): void; + }; + }; + export const FileSystemHandle: { + new (): { + readonly name: any; + readonly kind: any; + }; + }; + export const FileSystemFileHandle: { + new (): FileSystemFileHandle; + prototype: FileSystemFileHandle; + } | { + new (): { + getFile(): void; + createWritable(options?: any): Promise; + createSyncAccessHandle(): Promise; + readonly name: any; + readonly kind: any; + }; + }; + export const FileSystemDirectoryHandle: { + new (): FileSystemDirectoryHandle; + prototype: FileSystemDirectoryHandle; + } | { + new (): { + entries(): AsyncGenerator; + values(): AsyncGenerator; + keys(): AsyncGenerator; + resolve(possibleDescendant: any): Promise; + removeEntry(name: any, options?: any): Promise; + getDirectoryHandle(name: any, options?: any): Promise; + getFileHandle(name: any, options?: any): Promise; + readonly name: any; + readonly kind: any; + }; + }; + export const FileSystemWritableFileStream: { + new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): { + seek(position: any): Promise; + truncate(size: any): Promise; + write(data: any): Promise; + readonly locked: boolean; + abort(reason?: any): Promise; + close(): Promise; + getWriter(): WritableStreamDefaultWriter; + }; + }; + namespace _default { + export { createFileSystemWritableFileStream }; + export { createFileSystemDirectoryHandle }; + export { createFileSystemFileHandle }; + export { createFile }; } - export default Packet; - import { Buffer } from "socket:buffer"; + export default _default; + import fs from "socket:fs/promises"; } -declare module "socket:stream-relay/encryption" { +declare module "socket:extension" { /** - * Class for handling encryption and key management. + * Load an extension by name. + * @template {Record T} + * @param {string} name + * @param {ExtensionLoadOptions} [options] + * @return {Promise>} */ - export class Encryption { + export function load>(name: string, options?: ExtensionLoadOptions): Promise>; + /** + * Provides current stats about the loaded extensions. + * @return {Promise} + */ + export function stats(): Promise; + /** + * @typedef {{ + * allow: string[] | string, + * imports?: object, + * type?: 'shared' | 'wasm32', + * path?: string, + * stats?: object, + * instance?: WebAssembly.Instance, + * adapter?: WebAssemblyExtensionAdapter + * }} ExtensionLoadOptions + */ + /** + * @typedef {{ abi: number, version: string, description: string }} ExtensionInfo + */ + /** + * @typedef {{ abi: number, loaded: number }} ExtensionStats + */ + /** + * A interface for a native extension. + * @template {Record T} + */ + export class Extension> extends EventTarget { /** - * Creates a shared key based on the provided seed or generates a random one. - * @param {Uint8Array|string} seed - Seed for key generation. - * @returns {Promise} - Shared key. + * Load an extension by name. + * @template {Record T} + * @param {string} name + * @param {ExtensionLoadOptions} [options] + * @return {Promise>} */ - static createSharedKey(seed: Uint8Array | string): Promise; + static load>(name: string, options?: ExtensionLoadOptions): Promise>; /** - * Creates a key pair for signing and verification. - * @param {Uint8Array|string} seed - Seed for key generation. - * @returns {Promise<{ publicKey: Uint8Array, privateKey: Uint8Array }>} - Key pair. + * Query type of extension by name. + * @param {string} name + * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static createKeyPair(seed: Uint8Array | string): Promise<{ - publicKey: Uint8Array; - privateKey: Uint8Array; - }>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** - * Creates an ID using SHA-256 hash. - * @param {string} str - String to hash. - * @returns {Promise} - SHA-256 hash. + * Provides current stats about the loaded extensions or one by name. + * @param {?string} name + * @return {Promise} */ - static createId(str: string): Promise; + static stats(name: string | null): Promise; /** - * Creates a cluster ID using SHA-256 hash with specified output size. - * @param {string} str - String to hash. - * @returns {Promise} - SHA-256 hash with specified output size. + * `Extension` class constructor. + * @param {string} name + * @param {ExtensionInfo} info + * @param {ExtensionLoadOptions} [options] */ - static createClusterId(str: string): Promise; + constructor(name: string, info: ExtensionInfo, options?: ExtensionLoadOptions); /** - * Signs a message using the given secret key. - * @param {Buffer} b - The message to sign. - * @param {Uint8Array} sk - The secret key to use. - * @returns {Uint8Array} - Signature. + * The name of the extension + * @type {string?} */ - static sign(b: Buffer, sk: Uint8Array): Uint8Array; + name: string | null; /** - * Verifies the signature of a message using the given public key. - * @param {Buffer} b - The message to verify. - * @param {Uint8Array} sig - The signature to check. - * @param {Uint8Array} pk - The public key to use. - * @returns {number} - Returns non-zero if the buffer could not be verified. + * The version of the extension + * @type {string?} */ - static verify(b: Buffer, sig: Uint8Array, pk: Uint8Array): number; + version: string | null; /** - * Mapping of public keys to key objects. - * @type {Object.} + * The description of the extension + * @type {string?} */ - keys: { - [x: string]: { - publicKey: Uint8Array; - privateKey: Uint8Array; - ts: number; - }; - }; + description: string | null; /** - * Adds a key pair to the keys mapping. - * @param {Uint8Array|string} publicKey - Public key. - * @param {Uint8Array} privateKey - Private key. + * The abi of the extension + * @type {number} */ - add(publicKey: Uint8Array | string, privateKey: Uint8Array): void; + abi: number; /** - * Removes a key from the keys mapping. - * @param {Uint8Array|string} publicKey - Public key. + * @type {object} */ - remove(publicKey: Uint8Array | string): void; + options: object; /** - * Checks if a key is in the keys mapping. - * @param {Uint8Array|string} to - Public key or Uint8Array. - * @returns {boolean} - True if the key is present, false otherwise. + * @type {T} */ - has(to: Uint8Array | string): boolean; + binding: T; /** - * Decrypts a sealed message for a specific receiver. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. + * Not `null` if extension is of type 'wasm32' + * @type {?WebAssemblyExtensionAdapter} */ - open(message: Buffer, v: any | string): Buffer; + adapter: WebAssemblyExtensionAdapter | null; /** - * Opens a sealed message using the specified key. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found. + * `true` if the extension was loaded, otherwise `false` + * @type {boolean} */ - openMessage(message: Buffer, v: any | string): Buffer; + get loaded(): boolean; /** - * Seals a message for a specific receiver using their public key. - * - * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on - * a plaintext `message` for a `receiver` identity. This prevents repudiation - * attacks and doesn't rely on packet chain guarantees. - * - * let ct = Seal(sender | pt, receiver) - * let sig = Sign(ct, sk) - * let out = Seal(sig | ct) - * - * In an setup between Alice & Bob, this means: - * - Only Bob sees the plaintext - * - Alice wrote the plaintext and the ciphertext - * - Only Bob can see that Alice wrote the plaintext and ciphertext - * - Bob cannot forward the message without invalidating Alice's signature. - * - The outer encryption serves to prevent an attacker from replacing Alice's - * signature. As with _sign-encrypt-sign (SES), ESE is a variant of - * including the recipient's name inside the plaintext, which is then signed - * and encrypted Alice signs her plaintext along with her ciphertext, so as - * to protect herself from a laintext-substitution attack. At the same time, - * Alice's signed plaintext gives Bob non-repudiation. - * - * @see https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html - * - * @param {Buffer} message - The message to seal. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Sealed message. - * @throws {Error} - Throws ENOKEY if the key is not found. + * The extension type: 'shared' or 'wasm32' + * @type {'shared'|'wasm32'} */ - seal(message: Buffer, v: any | string): Buffer; + get type(): "shared" | "wasm32"; + /** + * Unloads the loaded extension. + * @throws Error + */ + unload(): Promise; + instance: any; + [$type]: "shared" | "wasm32"; + [$loaded]: boolean; } - import Buffer from "socket:buffer"; -} -declare module "socket:stream-relay/cache" { - /** - * @typedef {Packet} CacheEntry - * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver - */ - /** - * Default cache sibling resolver that computes a delta between - * two entries clocks. - * @param {CacheEntry} a - * @param {CacheEntry} b - * @return {number} - */ - export function defaultSiblingResolver(a: CacheEntry, b: CacheEntry): number; - export function trim(buf: Buffer): any; - /** - * Default max size of a `Cache` instance. - */ - export const DEFAULT_MAX_SIZE: number; + namespace _default { + export { load }; + export { stats }; + } + export default _default; + export type Pointer = number; + export type ExtensionLoadOptions = { + allow: string[] | string; + imports?: object; + type?: 'shared' | 'wasm32'; + path?: string; + stats?: object; + instance?: WebAssembly.Instance; + adapter?: WebAssemblyExtensionAdapter; + }; + export type ExtensionInfo = { + abi: number; + version: string; + description: string; + }; + export type ExtensionStats = { + abi: number; + loaded: number; + }; /** - * Internal mapping of packet IDs to packet data used by `Cache`. + * An adapter for reading and writing various values from a WebAssembly instance's + * memory buffer. + * @ignore */ - export class CacheData extends Map { - constructor(); - constructor(entries?: readonly (readonly [any, any])[]); - constructor(); - constructor(iterable?: Iterable); + class WebAssemblyExtensionAdapter { + constructor({ instance, module, table, memory, policies }: { + instance: any; + module: any; + table: any; + memory: any; + policies: any; + }); + view: any; + heap: any; + table: any; + stack: any; + buffer: any; + module: any; + memory: any; + context: any; + policies: any[]; + externalReferences: Map; + instance: any; + exitStatus: any; + textDecoder: TextDecoder; + textEncoder: TextEncoder; + errorMessagePointers: {}; + indirectFunctionTable: any; + get globalBaseOffset(): any; + destroy(): void; + init(): boolean; + get(pointer: any, size?: number): any; + set(pointer: any, value: any): void; + createExternalReferenceValue(value: any): any; + getExternalReferenceValue(pointer: any): any; + setExternalReferenceValue(pointer: any, value: any): Map; + removeExternalReferenceValue(pointer: any): void; + getExternalReferencePointer(value: any): any; + getFloat32(pointer: any): any; + setFloat32(pointer: any, value: any): boolean; + getFloat64(pointer: any): any; + setFloat64(pointer: any, value: any): boolean; + getInt8(pointer: any): any; + setInt8(pointer: any, value: any): boolean; + getInt16(pointer: any): any; + setInt16(pointer: any, value: any): boolean; + getInt32(pointer: any): any; + setInt32(pointer: any, value: any): boolean; + getUint8(pointer: any): any; + setUint8(pointer: any, value: any): boolean; + getUint16(pointer: any): any; + setUint16(pointer: any, value: any): boolean; + getUint32(pointer: any): any; + setUint32(pointer: any, value: any): boolean; + getString(pointer: any, buffer: any, size: any): string; + setString(pointer: any, string: any, buffer?: any): boolean; } + const $type: unique symbol; /** - * A class for storing a cache of packets by ID. This class includes a scheme - * for reconciling disjointed packet caches in a large distributed system. The - * following are key design characteristics. - * - * Space Efficiency: This scheme can be space-efficient because it summarizes - * the cache's contents in a compact binary format. By sharing these summaries, - * two computers can quickly determine whether their caches have common data or - * differences. - * - * Bandwidth Efficiency: Sharing summaries instead of the full data can save - * bandwidth. If the differences between the caches are small, sharing summaries - * allows for more efficient data synchronization. - * - * Time Efficiency: The time efficiency of this scheme depends on the size of - * the cache and the differences between the two caches. Generating summaries - * and comparing them can be faster than transferring and comparing the entire - * dataset, especially for large caches. - * - * Complexity: The scheme introduces some complexity due to the need to encode - * and decode summaries. In some cases, the overhead introduced by this - * complexity might outweigh the benefits, especially if the caches are - * relatively small. In this case, you should be using a query. - * - * Data Synchronization Needs: The efficiency also depends on the data - * synchronization needs. If the data needs to be synchronized in real-time, - * this scheme might not be suitable. It's more appropriate for cases where - * periodic or batch synchronization is acceptable. - * - * Scalability: The scheme's efficiency can vary depending on the scalability - * of the system. As the number of cache entries or computers involved - * increases, the complexity of generating and comparing summaries will stay - * bound to a maximum of 16Mb. - * + * @typedef {number} Pointer */ - export class Cache { - static HASH_SIZE_BYTES: number; - /** - * The encodeSummary method provides a compact binary encoding of the output - * of summary() - * - * @param {Object} summary - the output of calling summary() - * @return {Buffer} - **/ - static encodeSummary(summary: any): Buffer; - /** - * The decodeSummary method decodes the output of encodeSummary() - * - * @param {Buffer} bin - the output of calling encodeSummary() - * @return {Object} summary - **/ - static decodeSummary(bin: Buffer): any; - /** - * `Cache` class constructor. - * @param {CacheData?} [data] - */ - constructor(data?: CacheData | null, siblingResolver?: typeof defaultSiblingResolver); - data: CacheData; - maxSize: number; - siblingResolver: typeof defaultSiblingResolver; - /** - * Readonly count of the number of cache entries. - * @type {number} - */ - get size(): number; - /** - * Readonly size of the cache in bytes. - * @type {number} - */ - get bytes(): number; - /** - * Inserts a `CacheEntry` value `v` into the cache at key `k`. - * @param {string} k - * @param {CacheEntry} v - * @return {boolean} - */ - insert(k: string, v: CacheEntry): boolean; - /** - * Gets a `CacheEntry` value at key `k`. - * @param {string} k - * @return {CacheEntry?} - */ - get(k: string): CacheEntry | null; - /** - * @param {string} k - * @return {boolean} - */ - delete(k: string): boolean; - /** - * Predicate to determine if cache contains an entry at key `k`. - * @param {string} k - * @return {boolean} - */ - has(k: string): boolean; - /** - * Composes an indexed packet into a new `Packet` - * @param {Packet} packet - */ - compose(packet: Packet, source?: CacheData): Promise; - sha1(value: any, toHex: any): Promise; - /** - * - * The summarize method returns a terse yet comparable summary of the cache - * contents. - * - * Think of the cache as a trie of hex characters, the summary returns a - * checksum for the current level of the trie and for its 16 children. - * - * This is similar to a merkel tree as equal subtrees can easily be detected - * without the need for further recursion. When the subtree checksums are - * inequivalent then further negotiation at lower levels may be required, this - * process continues until the two trees become synchonized. - * - * When the prefix is empty, the summary will return an array of 16 checksums - * these checksums provide a way of comparing that subtree with other peers. - * - * When a variable-length hexidecimal prefix is provided, then only cache - * member hashes sharing this prefix will be considered. - * - * For each hex character provided in the prefix, the trie will decend by one - * level, each level divides the 2^128 address space by 16. For exmaple... - * - * ``` - * Level 0 1 2 - * ---------------- - * 2b00 - * aa0e ━┓ ━┓ - * aa1b ┃ ┃ - * aae3 ┃ ┃ ━┓ - * aaea ┃ ┃ ┃ - * aaeb ┃ ━┛ ━┛ - * ab00 ┃ ━┓ - * ab1e ┃ ┃ - * ab2a ┃ ┃ - * abef ┃ ┃ - * abf0 ━┛ ━┛ - * bff9 - * ``` - * - * @param {string} prefix - a string of lowercased hexidecimal characters - * @return {Object} - * - */ - summarize(prefix?: string, predicate?: (o: any) => boolean): any; + const $loaded: unique symbol; + import path from "socket:path"; +} +declare module "socket:fetch/fetch" { + export class DOMException { + private constructor(); + } + export function Headers(headers: any): void; + export class Headers { + constructor(headers: any); + map: {}; + append(name: any, value: any): void; + delete(name: any): void; + get(name: any): any; + has(name: any): boolean; + set(name: any, value: any): void; + forEach(callback: any, thisArg: any): void; + keys(): { + next: () => { + done: boolean; + value: any; + }; + }; + values(): { + next: () => { + done: boolean; + value: any; + }; + }; + entries(): { + next: () => { + done: boolean; + value: any; + }; + }; + } + export function Request(input: any, options: any): void; + export class Request { + constructor(input: any, options: any); + url: string; + credentials: any; + headers: Headers; + method: any; + mode: any; + signal: any; + referrer: any; + clone(): Request; + } + export function Response(bodyInit: any, options: any): void; + export class Response { + constructor(bodyInit: any, options: any); + type: string; + status: any; + ok: boolean; + statusText: string; + headers: Headers; + url: any; + clone(): Response; + } + export namespace Response { + function error(): Response; + function redirect(url: any, status: any): Response; + } + export function fetch(input: any, init: any): Promise; + export namespace fetch { + let polyfill: boolean; } - export default Cache; - export type CacheEntry = Packet; - export type CacheEntrySiblingResolver = (arg0: CacheEntry, arg1: CacheEntry) => number; - import { Buffer } from "socket:buffer"; - import { Packet } from "socket:stream-relay/packets"; } -declare module "socket:stream-relay/nat" { - /** - * The NAT type is encoded using 5 bits: - * - * 0b00001 : the lsb indicates if endpoint dependence information is included - * 0b00010 : the second bit indicates the endpoint dependence value - * - * 0b00100 : the third bit indicates if firewall information is included - * 0b01000 : the fourth bit describes which requests can pass the firewall, only known IPs (0) or any IP (1) - * 0b10000 : the fifth bit describes which requests can pass the firewall, only known ports (0) or any port (1) - */ - /** - * Every remote will see the same IP:PORT mapping for this peer. - * - * :3333 ┌──────┐ - * :1111 ┌───▶ │ R1 │ - * ┌──────┐ ┌───────┐ │ └──────┘ - * │ P1 ├───▶│ NAT ├──┤ - * └──────┘ └───────┘ │ ┌──────┐ - * └───▶ │ R2 │ - * :3333 └──────┘ - */ - export const MAPPING_ENDPOINT_INDEPENDENT: 3; - /** - * Every remote will see a different IP:PORT mapping for this peer. - * - * :4444 ┌──────┐ - * :1111 ┌───▶ │ R1 │ - * ┌──────┐ ┌───────┐ │ └──────┘ - * │ P1 ├───▶│ NAT ├──┤ - * └──────┘ └───────┘ │ ┌──────┐ - * └───▶ │ R2 │ - * :5555 └──────┘ - */ - export const MAPPING_ENDPOINT_DEPENDENT: 1; - /** - * The firewall allows the port mapping to be accessed by: - * - Any IP:PORT combination (FIREWALL_ALLOW_ANY) - * - Any PORT on a previously connected IP (FIREWALL_ALLOW_KNOWN_IP) - * - Only from previously connected IP:PORT combinations (FIREWALL_ALLOW_KNOWN_IP_AND_PORT) - */ - export const FIREWALL_ALLOW_ANY: 28; - export const FIREWALL_ALLOW_KNOWN_IP: 12; - export const FIREWALL_ALLOW_KNOWN_IP_AND_PORT: 4; - /** - * The initial state of the nat is unknown and its value is 0 - */ - export const UNKNOWN: 0; - /** - * Full-cone NAT, also known as one-to-one NAT - * - * Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. - * - * @summary its a packet party at this mapping and everyone's invited - */ - export const UNRESTRICTED: number; - /** - * (Address)-restricted-cone NAT - * - * An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only - * if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter. - * - * @summary The NAT will drop your packets unless a peer within its network has previously messaged you from *any* port. - */ - export const ADDR_RESTRICTED: number; - /** - * Port-restricted cone NAT - * - * An external host (hAddr:hPort) can send packets to iAddr:iPort by sending - * packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to - * hAddr:hPort. - * - * @summary The NAT will drop your packets unless a peer within its network - * has previously messaged you from this *specific* port. - */ - export const PORT_RESTRICTED: number; - /** - * Symmetric NAT - * - * Only an external host that receives a packet from an internal host can send - * a packet back. - * - * @summary The NAT will only accept replies to a correspondence initialized - * by itself, the mapping it created is only valid for you. - */ - export const ENDPOINT_RESTRICTED: number; - export function isEndpointDependenceDefined(nat: any): boolean; - export function isFirewallDefined(nat: any): boolean; - export function isValid(nat: any): boolean; - export function toString(n: any): "UNRESTRICTED" | "ADDR_RESTRICTED" | "PORT_RESTRICTED" | "ENDPOINT_RESTRICTED" | "UNKNOWN"; - export function toStringStrategy(n: any): "STRATEGY_DEFER" | "STRATEGY_DIRECT_CONNECT" | "STRATEGY_TRAVERSAL_OPEN" | "STRATEGY_TRAVERSAL_CONNECT" | "STRATEGY_PROXY" | "STRATEGY_UNKNOWN"; - export const STRATEGY_DEFER: 0; - export const STRATEGY_DIRECT_CONNECT: 1; - export const STRATEGY_TRAVERSAL_OPEN: 2; - export const STRATEGY_TRAVERSAL_CONNECT: 3; - export const STRATEGY_PROXY: 4; - export function connectionStrategy(a: any, b: any): 0 | 1 | 2 | 3 | 4; +declare module "socket:fetch/index" { + export * from "socket:fetch/fetch"; + export default fetch; + import { fetch } from "socket:fetch/fetch"; } -declare module "socket:stream-relay/index" { - /** - * Computes rate limit predicate value for a port and address pair for a given - * threshold updating an input rates map. This method is accessed concurrently, - * the rates object makes operations atomic to avoid race conditions. - * - * @param {Map} rates - * @param {number} type - * @param {number} port - * @param {string} address - * @return {boolean} - */ - export function rateLimit(rates: Map, type: number, port: number, address: string, subclusterIdQuota: any): boolean; - export function debug(pid: any, ...args: any[]): void; - /** - * Retry delay in milliseconds for ping. - * @type {number} - */ - export const PING_RETRY: number; - /** - * Probe wait timeout in milliseconds. - * @type {number} - */ - export const PROBE_WAIT: number; - /** - * Default keep alive timeout. - * @type {number} +declare module "socket:fetch" { + export * from "socket:fetch/index"; + export default fetch; + import fetch from "socket:fetch/index"; +} +declare module "socket:http" { + export function get(optionsOrURL: any, options: any, callback: any): Promise; + export const METHODS: string[]; + export const STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + export class OutgoingMessage extends Writable { + headers: Headers; + get headersSent(): boolean; + get socket(): this; + get writableEnded(): boolean; + appendHeader(name: any, value: any): this; + setHeader(name: any, value: any): this; + flushHeaders(): void; + getHeader(name: any): string; + getHeaderNames(): string[]; + getHeaders(): { + [k: string]: string; + }; + hasHeader(name: any): boolean; + removeHeader(name: any): void; + } + export class ClientRequest extends OutgoingMessage { + url: any; + path: any; + host: any; + agent: any; + method: any; + protocol: string; + } + export class ServerResponse extends OutgoingMessage { + statusCode: number; + statusMessage: string; + req: any; + } + export class AgentOptions { + constructor(options: any); + keepAlive: boolean; + timeout: number; + } + export class Agent extends EventEmitter { + constructor(options: any); + defaultProtocol: string; + options: any; + createConnection(options: any, callback?: any): Duplex; + } + export const globalAgent: Agent; + namespace _default { + export { METHODS }; + export { STATUS_CODES }; + export { AgentOptions }; + export { Agent }; + export { globalAgent }; + export { request }; + export { OutgoingMessage }; + export { ClientRequest }; + export { ServerResponse }; + export { get }; + } + export default _default; + import { Writable } from "socket:stream"; + import { EventEmitter } from "socket:events"; + import { Duplex } from "socket:stream"; + function request(optionsOrURL: any, options: any, callback: any): Promise; +} +declare module "socket:https" { + export function request(optionsOrURL: any, options: any, callback: any): Promise; + export function get(optionsOrURL: any, options: any, callback: any): Promise; + export const METHODS: string[]; + export const STATUS_CODES: { + 100: string; + 101: string; + 102: string; + 103: string; + 200: string; + 201: string; + 202: string; + 203: string; + 204: string; + 205: string; + 206: string; + 207: string; + 208: string; + 226: string; + 300: string; + 301: string; + 302: string; + 303: string; + 304: string; + 305: string; + 307: string; + 308: string; + 400: string; + 401: string; + 402: string; + 403: string; + 404: string; + 405: string; + 406: string; + 407: string; + 408: string; + 409: string; + 410: string; + 411: string; + 412: string; + 413: string; + 414: string; + 415: string; + 416: string; + 417: string; + 418: string; + 421: string; + 422: string; + 423: string; + 424: string; + 425: string; + 426: string; + 428: string; + 429: string; + 431: string; + 451: string; + 500: string; + 501: string; + 502: string; + 503: string; + 504: string; + 505: string; + 506: string; + 507: string; + 508: string; + 509: string; + 510: string; + 511: string; + }; + const AgentOptions_base: typeof import("socket:http").AgentOptions; + export class AgentOptions extends AgentOptions_base { + } + const Agent_base: typeof import("socket:http").Agent; + export class Agent extends Agent_base { + } + const OutgoingMessage_base: typeof import("socket:http").OutgoingMessage; + export class OutgoingMessage extends OutgoingMessage_base { + } + const ClientRequest_base: typeof import("socket:http").ClientRequest; + export class ClientRequest extends ClientRequest_base { + } + const ServerResponse_base: typeof import("socket:http").ServerResponse; + export class ServerResponse extends ServerResponse_base { + } + export const globalAgent: Agent; + namespace _default { + export { METHODS }; + export { STATUS_CODES }; + export { AgentOptions }; + export { Agent }; + export { globalAgent }; + export { request }; + export { OutgoingMessage }; + export { ClientRequest }; + export { ServerResponse }; + export { get }; + } + export default _default; +} +declare module "socket:language" { + /** + * Look up a language name or code by query. + * @param {string} query + * @param {object=} [options] + * @param {boolean=} [options.strict = false] + * @return {?LanguageQueryResult[]} */ - export const DEFAULT_KEEP_ALIVE: number; + export function lookup(query: string, options?: object | undefined, ...args: any[]): LanguageQueryResult[] | null; /** - * Default rate limit threshold in milliseconds. - * @type {number} + * Describe a language by tag + * @param {string} query + * @param {object=} [options] + * @param {boolean=} [options.strict = true] + * @return {?LanguageDescription[]} */ - export const DEFAULT_RATE_LIMIT_THRESHOLD: number; - export function getRandomPort(ports: object, p: number | null): number; + export function describe(query: string, options?: object | undefined): LanguageDescription[] | null; /** - * A `RemotePeer` represents an initial, discovered, or connected remote peer. - * Typically, you will not need to create instances of this class directly. + * A list of ISO 639-1 language names. + * @type {string[]} */ - export class RemotePeer { + export const names: string[]; + /** + * A list of ISO 639-1 language codes. + * @type {string[]} + */ + export const codes: string[]; + /** + * A list of RFC 5646 language tag identifiers. + * @see {@link http://tools.ietf.org/html/rfc5646} + */ + export const tags: Enumeration; + /** + * A list of RFC 5646 language tag titles corresponding + * to language tags. + * @see {@link http://tools.ietf.org/html/rfc5646} + */ + export const descriptions: Enumeration; + /** + * A container for a language query response containing an ISO language + * name and code. + * @see {@link https://www.sitepoint.com/iso-2-letter-language-codes} + */ + export class LanguageQueryResult { /** - * `RemotePeer` class constructor. - * @param {{ - * peerId?: string, - * address?: string, - * port?: number, - * natType?: number, - * clusters: object, - * reflectionId?: string, - * distance?: number, - * publicKey?: string, - * privateKey?: string, - * clock?: number, - * lastUpdate?: number, - * lastRequest?: number - * }} o + * `LanguageQueryResult` class constructor. + * @param {string} code + * @param {string} name + * @param {string[]} [tags] */ - constructor(o: { - peerId?: string; - address?: string; - port?: number; - natType?: number; - clusters: object; - reflectionId?: string; - distance?: number; - publicKey?: string; - privateKey?: string; - clock?: number; - lastUpdate?: number; - lastRequest?: number; - }, peer: any); - peerId: any; - address: any; - port: number; - natType: any; - clusters: {}; - pingId: any; - distance: number; - connected: boolean; - opening: number; - probed: number; - proxy: any; - clock: number; - uptime: number; - lastUpdate: number; - lastRequest: number; - localPeer: any; - write(sharedKey: any, args: any): Promise; + constructor(code: string, name: string, tags?: string[]); + /** + * The language code corresponding to the query. + * @type {string} + */ + get code(): string; + /** + * The language name corresponding to the query. + * @type {string} + */ + get name(): string; + /** + * The language tags corresponding to the query. + * @type {string[]} + */ + get tags(): string[]; + /** + * JSON represenation of a `LanguageQueryResult` instance. + * @return {{ + * code: string, + * name: string, + * tags: string[] + * }} + */ + toJSON(): { + code: string; + name: string; + tags: string[]; + }; + /** + * Internal inspect function. + * @ignore + * @return {LanguageQueryResult} + */ + inspect(): LanguageQueryResult; + #private; } - export function wrap(dgram: any): { - new (persistedState?: object | null): { - port: any; - address: any; - natType: number; - nextNatType: number; - clusters: {}; - reflectionId: any; - reflectionTimeout: any; - reflectionStage: number; - reflectionRetry: number; - reflectionFirstResponder: any; - peerId: string; - isListening: boolean; - ctime: number; - lastUpdate: number; - lastSync: number; - closing: boolean; - clock: number; - unpublished: {}; - cache: any; - uptime: number; - maxHops: number; - bdpCache: number[]; - onListening: any; - onDelete: any; - sendQueue: any[]; - firewall: any; - rates: Map; - streamBuffer: Map; - gate: Map; - returnRoutes: Map; - metrics: { - i: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - REJECTED: number; - }; - o: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - }; - }; - peers: any; - encryption: Encryption; - config: any; - _onError: (err: any) => any; - socket: any; - probeSocket: any; - /** - * An implementation for clearning an interval that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearInterval(tid: any): undefined; - /** - * An implementation for clearning a timeout that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearTimeout(tid: any): undefined; - /** - * An implementation of an internal timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setInterval(fn: any, t: any): number; - /** - * An implementation of an timeout timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setTimeout(fn: any, t: any): number; - /** - * A method that encapsulates the listing procedure - * @return {undefined} - * @ignore - */ - _listen(): undefined; - init(cb: any): Promise; - onReady: any; - mainLoopTimer: number; - /** - * Continuously evaluate the state of the peer and its network - * @return {undefined} - * @ignore - */ - _mainLoop(ts: any): undefined; - /** - * Enqueue packets to be sent to the network - * @param {Buffer} data - An encoded packet - * @param {number} port - The desination port of the remote host - * @param {string} address - The destination address of the remote host - * @param {Socket=this.socket} socket - The socket to send on - * @return {undefined} - * @ignore - */ - send(data: Buffer, port: number, address: string, socket?: any): undefined; - /** - * @private - */ - _scheduleSend(): void; - sendTimeout: number; - /** - * @private - */ - _dequeue(): void; - /** - * Send any unpublished packets - * @return {undefined} - * @ignore - */ - sendUnpublished(): undefined; - /** - * Get the serializable state of the peer (can be passed to the constructor or create method) - * @return {undefined} - */ - getState(): undefined; - /** - * Get a selection of known peers - * @return {Array} - * @ignore - */ - getPeers(packet: any, peers: any, ignorelist: any, filter?: (o: any) => any): Array; - /** - * Send an eventually consistent packet to a selection of peers (fanout) - * @return {undefined} - * @ignore - */ - mcast(packet: any, ignorelist?: any[]): undefined; - /** - * The process of determining this peer's NAT behavior (firewall and dependentness) - * @return {undefined} - * @ignore - */ - requestReflection(): undefined; - probeReflectionTimeout: any; - /** - * Ping another peer - * @return {PacketPing} - * @ignore - */ - ping(peer: any, withRetry: any, props: any, socket: any): PacketPing; - getPeer(id: any): any; - /** - * This should be called at least once when an app starts to multicast - * this peer, and starts querying the network to discover peers. - * @param {object} keys - Created by `Encryption.createKeyPair()`. - * @param {object=} args - Options - * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. - * @return {RemotePeer} - */ - join(sharedKey: any, args?: object | undefined): RemotePeer; - /** - * @param {Packet} T - The constructor to be used to create packets. - * @param {Any} message - The message to be split and packaged. - * @return {Array>} - * @ignore - */ - _message2packets(T: Packet, message: Any, args: any): Array>; - /** - * Sends a packet into the network that will be replicated and buffered. - * Each peer that receives it will buffer it until TTL and then replicate - * it provided it has has not exceeded their maximum number of allowed hops. - * - * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. - * @param {object} args - The arguments to be applied. - * @param {Buffer} args.message - The message to be encrypted by keys and sent. - * @param {Packet=} args.packet - The previous packet in the packet chain. - * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. - * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. - * @return {Array} - */ - publish(sharedKey: any, args: { - message: Buffer; - packet?: Packet | undefined; - usr1: Buffer; - usr2: Buffer; - }): Array; - /** - * @return {undefined} - */ - sync(peer: any): undefined; - close(): void; - /** - * Deploy a query into the network - * @return {undefined} - * - */ - query(query: any): undefined; - /** - * - * This is a default implementation for deciding what to summarize - * from the cache when receiving a request to sync. that can be overridden - * - */ - cachePredicate(packet: any): boolean; - /** - * A connection was made, add the peer to the local list of known - * peers and call the onConnection if it is defined by the user. - * - * @return {undefined} - * @ignore - */ - _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; - connections: Map; - /** - * Received a Sync Packet - * @return {undefined} - * @ignore - */ - _onSync(packet: any, port: any, address: any): undefined; - /** - * Received a Query Packet - * - * a -> b -> c -> (d) -> c -> b -> a - * - * @return {undefined} - * @example - * - * ```js - * peer.onQuery = (packet) => { - * // - * // read a database or something - * // - * return { - * message: Buffer.from('hello'), - * publicKey: '', - * privateKey: '' - * } - * } - * ``` - */ - _onQuery(packet: any, port: any, address: any): undefined; - /** - * Received a Ping Packet - * @return {undefined} - * @ignore - */ - _onPing(packet: any, port: any, address: any): undefined; - /** - * Received a Pong Packet - * @return {undefined} - * @ignore - */ - _onPong(packet: any, port: any, address: any): undefined; - /** - * Received an Intro Packet - * @return {undefined} - * @ignore - */ - _onIntro(packet: any, port: any, address: any, _: any, opts?: { - attempts: number; - }): undefined; - socketPool: any[]; - /** - * Received an Join Packet - * @return {undefined} - * @ignore - */ - _onJoin(packet: any, port: any, address: any, data: any): undefined; - /** - * Received an Publish Packet - * @return {undefined} - * @ignore - */ - _onPublish(packet: any, port: any, address: any, data: any): undefined; - /** - * Received an Stream Packet - * @return {undefined} - * @ignore - */ - _onStream(packet: any, port: any, address: any, data: any): undefined; - /** - * Received any packet on the probe port to determine the firewall: - * are you port restricted, host restricted, or unrestricted. - * @return {undefined} - * @ignore - */ - _onProbeMessage(data: any, { port, address }: { - port: any; - address: any; - }): undefined; - /** - * When a packet is received it is decoded, the packet contains the type - * of the message. Based on the message type it is routed to a function. - * like WebSockets, don't answer queries unless we know its another SRP peer. - * - * @param {Buffer|Uint8Array} data - * @param {{ port: number, address: string }} info - */ - _onMessage(data: Buffer | Uint8Array, { port, address }: { - port: number; - address: string; - }): Promise; + /** + * A container for a language code, tag, and description. + */ + export class LanguageDescription { + /** + * `LanguageDescription` class constructor. + * @param {string} code + * @param {string} tag + * @param {string} description + */ + constructor(code: string, tag: string, description: string); + /** + * The language code corresponding to the language + * @type {string} + */ + get code(): string; + /** + * The language tag corresponding to the language. + * @type {string} + */ + get tag(): string; + /** + * The language description corresponding to the language. + * @type {string} + */ + get description(): string; + /** + * JSON represenation of a `LanguageDescription` instance. + * @return {{ + * code: string, + * tag: string, + * description: string + * }} + */ + toJSON(): { + code: string; + tag: string; + description: string; }; - }; - export default wrap; - import { Packet } from "socket:stream-relay/packets"; - import { sha256 } from "socket:stream-relay/packets"; - import { Cache } from "socket:stream-relay/cache"; - import { Encryption } from "socket:stream-relay/encryption"; - import * as NAT from "socket:stream-relay/nat"; - import { Buffer } from "socket:buffer"; - import { PacketPing } from "socket:stream-relay/packets"; - import { PacketPublish } from "socket:stream-relay/packets"; - export { Packet, sha256, Cache, Encryption, NAT }; -} -declare module "socket:stream-relay/sugar" { - function _default(dgram: object, events: object): Promise; + /** + * Internal inspect function. + * @ignore + * @return {LanguageDescription} + */ + inspect(): LanguageDescription; + #private; + } + namespace _default { + export { codes }; + export { describe }; + export { lookup }; + export { names }; + export { tags }; + } export default _default; + import Enumeration from "socket:enumeration"; } -declare module "socket:node/index" { - export default network; - export const network: Promise; - import { Cache } from "socket:stream-relay/index"; - import { sha256 } from "socket:stream-relay/index"; - import { Encryption } from "socket:stream-relay/index"; - import { Packet } from "socket:stream-relay/index"; - import { NAT } from "socket:stream-relay/index"; - export { Cache, sha256, Encryption, Packet, NAT }; -} -declare module "socket:index" { - import { network } from "socket:node/index"; - import { Cache } from "socket:node/index"; - import { sha256 } from "socket:node/index"; - import { Encryption } from "socket:node/index"; - import { Packet } from "socket:node/index"; - import { NAT } from "socket:node/index"; - export { network, Cache, sha256, Encryption, Packet, NAT }; -} -declare module "socket:string_decoder" { - export function StringDecoder(encoding: any): void; - export class StringDecoder { - constructor(encoding: any); - encoding: any; - text: typeof utf16Text | typeof base64Text; - end: typeof utf16End | typeof base64End | typeof simpleEnd; - fillLast: typeof utf8FillLast; - write: typeof simpleWrite; - lastNeed: number; - lastTotal: number; - lastChar: Uint8Array; - } - export default StringDecoder; - function utf16Text(buf: any, i: any): any; - class utf16Text { - constructor(buf: any, i: any); - lastNeed: number; - lastTotal: number; - } - function base64Text(buf: any, i: any): any; - class base64Text { - constructor(buf: any, i: any); - lastNeed: number; - lastTotal: number; - } - function utf16End(buf: any): any; - function base64End(buf: any): any; - function simpleEnd(buf: any): any; - function utf8FillLast(buf: any): any; - function simpleWrite(buf: any): any; -} -declare module "socket:test/context" { - export default function _default(GLOBAL_TEST_RUNNER: any): void; -} -declare module "socket:test/dom-helpers" { +declare module "socket:i18n" { /** - * Converts querySelector string to an HTMLElement or validates an existing HTMLElement. - * - * @export - * @param {string|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @returns {Element} The HTMLElement, Element, or Window that corresponds to the selector. - * @throws {Error} Throws an error if the `selector` is not a string that resolves to an HTMLElement or not an instance of HTMLElement, Element, or Window. - * + * Get messages for `locale` pattern. This function could return many results + * for various locales given a `locale` pattern. such as `fr`, which could + * return results for `fr`, `fr-FR`, `fr-BE`, etc. + * @ignore + * @param {string} locale + * @return {object[]} */ - export function toElement(selector: string | Element): Element; + export function getMessagesForLocale(locale: string): object[]; /** - * Waits for an element to appear in the DOM and resolves the promise when it does. - * - * @export - * @param {Object} args - Configuration arguments. - * @param {string} [args.selector] - The CSS selector to look for. - * @param {boolean} [args.visible=true] - Whether the element should be visible. - * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. - * @param {() => HTMLElement | Element | null | undefined} [lambda] - An optional function that returns the element. Used if the `selector` is not provided. - * @returns {Promise} - A promise that resolves to the found element. - * - * @throws {Error} - Throws an error if neither `lambda` nor `selector` is provided. - * @throws {Error} - Throws an error if the element is not found within the timeout. - * - * @example - * ```js - * waitFor({ selector: '#my-element', visible: true, timeout: 5000 }) - * .then(el => console.log('Element found:', el)) - * .catch(err => console.log('Element not found:', err)); - * ``` + * Returns user preferred ISO 639 language codes or RFC 5646 language tags. + * @return {string[]} */ - export function waitFor(args: { - selector?: string; - visible?: boolean; - timeout?: number; - }, lambda?: () => HTMLElement | Element | null | undefined): Promise; + export function getAcceptLanguages(): string[]; /** - * Waits for an element's text content to match a given string or regular expression. - * - * @export - * @param {Object} args - Configuration arguments. - * @param {Element} args.element - The root element from which to begin searching. - * @param {string} [args.text] - The text to search for within elements. - * @param {RegExp} [args.regex] - A regular expression to match against element text content. - * @param {boolean} [args.multipleTags=false] - Whether to look for text across multiple sibling elements. - * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. - * @returns {Promise} - A promise that resolves to the found element or null. - * - * @example - * ```js - * waitForText({ element: document.body, text: 'Hello', timeout: 5000 }) - * .then(el => console.log('Element found:', el)) - * .catch(err => console.log('Element not found:', err)); - * ``` + * Returns the current user ISO 639 language code or RFC 5646 language tag. + * @return {?string} */ - export function waitForText(args: { - element: Element; - text?: string; - regex?: RegExp; - multipleTags?: boolean; - timeout?: number; - }): Promise; + export function getUILanguage(): string | null; /** - * @export - * @param {Object} args - Arguments - * @param {string | Event} args.event - The event to dispatch. - * @param {HTMLElement | Element | window} [args.element=window] - The element to dispatch the event on. - * @returns {void} - * - * @throws {Error} Throws an error if the `event` is not a string that can be converted to a CustomEvent or not an instance of Event. + * Gets a localized message string for the specified message name. + * @param {string} messageName + * @param {object|string[]=} [substitutions = []] + * @param {object=} [options] + * @param {string=} [options.locale = null] + * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + * @see {@link https://www.ibm.com/docs/en/rbd/9.5.1?topic=syslib-getmessage} + * @return {?string} */ - export function event(args: { - event: string | Event; - element?: HTMLElement | Element | (Window & typeof globalThis); - }): void; + export function getMessage(messageName: string, substitutions?: (object | string[]) | undefined, options?: object | undefined): string | null; /** - * @export - * Copy pasted from https://raw.githubusercontent.com/testing-library/jest-dom/master/src/to-be-visible.js - * @param {Element | HTMLElement} element - * @param {Element | HTMLElement} [previousElement] - * @returns {boolean} + * Gets a localized message description string for the specified message name. + * @param {string} messageName + * @param {object=} [options] + * @param {string=} [options.locale = null] + * @return {?string} */ - export function isElementVisible(element: Element | HTMLElement, previousElement?: Element | HTMLElement): boolean; -} -declare module "socket:test/index" { + export function getMessageDescription(messageName: string, options?: object | undefined): string | null; /** - * @returns {number} - The default timeout for tests in milliseconds. + * A cache of loaded locale messages. + * @type {Map} */ - export function getDefaultTestRunnerTimeout(): number; + export const cache: Map; /** - * @param {string} name - * @param {TestFn} [fn] - * @returns {void} + * Default location of i18n locale messages + * @type {string} */ - export function only(name: string, fn?: TestFn): void; + export const DEFAULT_LOCALES_LOCATION: string; /** - * @param {string} _name - * @param {TestFn} [_fn] - * @returns {void} + * An enumeration of supported ISO 639 language codes or RFC 5646 language tags. + * @type {Enumeration} + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/LanguageCode} + * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} */ - export function skip(_name: string, _fn?: TestFn): void; + export const LanguageCode: Enumeration; + namespace _default { + export { LanguageCode }; + export { getAcceptLanguages }; + export { getMessage }; + export { getUILanguage }; + } + export default _default; + import Enumeration from "socket:enumeration"; +} +declare module "socket:stream-relay/packets" { /** - * @param {boolean} strict - * @returns {void} + * The magic bytes prefixing every packet. They are the + * 2nd, 3rd, 5th, and 7th, prime numbers. + * @type {number[]} */ - export function setStrict(strict: boolean): void; + export const MAGIC_BYTES_PREFIX: number[]; /** - * @typedef {{ - * (name: string, fn?: TestFn): void - * only(name: string, fn?: TestFn): void - * skip(name: string, fn?: TestFn): void - * }} testWithProperties - * @ignore + * The version of the protocol. */ + export const VERSION: 6; /** - * @type {testWithProperties} - * @param {string} name - * @param {TestFn} [fn] - * @returns {void} + * The size in bytes of the prefix magic bytes. */ - export function test(name: string, fn?: TestFn): void; - export namespace test { - export { only }; - export { skip }; - } + export const MAGIC_BYTES: 4; /** - * @typedef {(t: Test) => (void | Promise)} TestFn + * The maximum size of the user message. */ + export const MESSAGE_BYTES: 1024; /** - * @class + * The cache TTL in milliseconds. */ - export class Test { - /** - * @constructor - * @param {string} name - * @param {TestFn} fn - * @param {TestRunner} runner - */ - constructor(name: string, fn: TestFn, runner: TestRunner); - /** - * @type {string} - * @ignore - */ - name: string; - /** - * @type {null|number} - * @ignore - */ - _planned: null | number; - /** - * @type {null|number} - * @ignore - */ - _actual: null | number; - /** - * @type {TestFn} - * @ignore - */ - fn: TestFn; - /** - * @type {TestRunner} - * @ignore - */ - runner: TestRunner; - /** - * @type{{ pass: number, fail: number }} - * @ignore - */ - _result: { - pass: number; - fail: number; - }; - /** - * @type {boolean} - * @ignore - */ - done: boolean; - /** - * @type {boolean} - * @ignore - */ - strict: boolean; - /** - * @param {string} msg - * @returns {void} - */ - comment(msg: string): void; - /** - * Plan the number of assertions. - * - * @param {number} n - * @returns {void} - */ - plan(n: number): void; - /** - * @template T - * @param {T} actual - * @param {T} expected - * @param {string} [msg] - * @returns {void} - */ - deepEqual(actual: T, expected: T, msg?: string): void; - /** - * @template T - * @param {T} actual - * @param {T} expected - * @param {string} [msg] - * @returns {void} - */ - notDeepEqual(actual: T_1, expected: T_1, msg?: string): void; - /** - * @template T - * @param {T} actual - * @param {T} expected - * @param {string} [msg] - * @returns {void} - */ - equal(actual: T_2, expected: T_2, msg?: string): void; - /** - * @param {unknown} actual - * @param {unknown} expected - * @param {string} [msg] - * @returns {void} - */ - notEqual(actual: unknown, expected: unknown, msg?: string): void; - /** - * @param {string} [msg] - * @returns {void} - */ - fail(msg?: string): void; - /** - * @param {unknown} actual - * @param {string} [msg] - * @returns {void} - */ - ok(actual: unknown, msg?: string): void; + export const CACHE_TTL: number; + export namespace PACKET_SPEC { + namespace type { + let bytes: number; + let encoding: string; + } + namespace version { + let bytes_1: number; + export { bytes_1 as bytes }; + let encoding_1: string; + export { encoding_1 as encoding }; + export { VERSION as default }; + } + namespace clock { + let bytes_2: number; + export { bytes_2 as bytes }; + let encoding_2: string; + export { encoding_2 as encoding }; + let _default: number; + export { _default as default }; + } + namespace hops { + let bytes_3: number; + export { bytes_3 as bytes }; + let encoding_3: string; + export { encoding_3 as encoding }; + let _default_1: number; + export { _default_1 as default }; + } + namespace index { + let bytes_4: number; + export { bytes_4 as bytes }; + let encoding_4: string; + export { encoding_4 as encoding }; + let _default_2: number; + export { _default_2 as default }; + export let signed: boolean; + } + namespace ttl { + let bytes_5: number; + export { bytes_5 as bytes }; + let encoding_5: string; + export { encoding_5 as encoding }; + export { CACHE_TTL as default }; + } + namespace clusterId { + let bytes_6: number; + export { bytes_6 as bytes }; + let encoding_6: string; + export { encoding_6 as encoding }; + let _default_3: number[]; + export { _default_3 as default }; + } + namespace subclusterId { + let bytes_7: number; + export { bytes_7 as bytes }; + let encoding_7: string; + export { encoding_7 as encoding }; + let _default_4: number[]; + export { _default_4 as default }; + } + namespace previousId { + let bytes_8: number; + export { bytes_8 as bytes }; + let encoding_8: string; + export { encoding_8 as encoding }; + let _default_5: number[]; + export { _default_5 as default }; + } + namespace packetId { + let bytes_9: number; + export { bytes_9 as bytes }; + let encoding_9: string; + export { encoding_9 as encoding }; + let _default_6: number[]; + export { _default_6 as default }; + } + namespace nextId { + let bytes_10: number; + export { bytes_10 as bytes }; + let encoding_10: string; + export { encoding_10 as encoding }; + let _default_7: number[]; + export { _default_7 as default }; + } + namespace usr1 { + let bytes_11: number; + export { bytes_11 as bytes }; + let _default_8: number[]; + export { _default_8 as default }; + } + namespace usr2 { + let bytes_12: number; + export { bytes_12 as bytes }; + let _default_9: number[]; + export { _default_9 as default }; + } + namespace usr3 { + let bytes_13: number; + export { bytes_13 as bytes }; + let _default_10: number[]; + export { _default_10 as default }; + } + namespace usr4 { + let bytes_14: number; + export { bytes_14 as bytes }; + let _default_11: number[]; + export { _default_11 as default }; + } + namespace message { + let bytes_15: number; + export { bytes_15 as bytes }; + let _default_12: number[]; + export { _default_12 as default }; + } + namespace sig { + let bytes_16: number; + export { bytes_16 as bytes }; + let _default_13: number[]; + export { _default_13 as default }; + } + } + /** + * The size in bytes of the total packet frame and message. + */ + export const PACKET_BYTES: number; + /** + * The maximum distance that a packet can be replicated. + */ + export const MAX_HOPS: 16; + export function validatePacket(o: any, constraints: { + [key: string]: { + required: boolean; + type: string; + }; + }): void; + /** + * Computes a SHA-256 hash of input returning a hex encoded string. + * @type {function(string|Buffer|Uint8Array): Promise} + */ + export const sha256: (arg0: string | Buffer | Uint8Array) => Promise; + export function decode(buf: Buffer): Packet; + export function getTypeFromBytes(buf: any): any; + export class Packet { + static ttl: number; + static maxLength: number; /** - * @param {string} [msg] - * @returns {void} + * Returns an empty `Packet` instance. + * @return {Packet} */ - pass(msg?: string): void; + static empty(): Packet; /** - * @param {Error | null | undefined} err - * @param {string} [msg] - * @returns {void} + * @param {Packet|object} packet + * @return {Packet} */ - ifError(err: Error | null | undefined, msg?: string): void; + static from(packet: Packet | object): Packet; /** - * @param {Function} fn - * @param {RegExp | any} [expected] - * @param {string} [message] - * @returns {void} + * Determines if input is a packet. + * @param {Buffer|Uint8Array|number[]|object|Packet} packet + * @return {boolean} */ - throws(fn: Function, expected?: RegExp | any, message?: string): void; + static isPacket(packet: Buffer | Uint8Array | number[] | object | Packet): boolean; /** - * Sleep for ms with an optional msg - * - * @param {number} ms - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.sleep(100) - * ``` - */ - sleep(ms: number, msg?: string): Promise; + */ + static encode(p: any): Promise; + static decode(buf: any): Packet; /** - * Request animation frame with an optional msg. Falls back to a 0ms setTimeout when - * tests are run headlessly. - * - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.requestAnimationFrame() - * ``` + * `Packet` class constructor. + * @param {Packet|object?} options */ - requestAnimationFrame(msg?: string): Promise; + constructor(options?: Packet | (object | null)); /** - * Dispatch the `click`` method on an element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.click('.class button', 'Click a button') - * ``` + * @param {Packet} packet + * @return {Packet} */ - click(selector: string | HTMLElement | Element, msg?: string): Promise; + copy(): Packet; + timestamp: any; + isComposed: any; + isReconciled: any; + meta: any; + } + export class PacketPing extends Packet { + static type: number; + constructor({ message, clusterId, subclusterId }: { + message: any; + clusterId: any; + subclusterId: any; + }); + } + export class PacketPong extends Packet { + static type: number; + constructor({ message, clusterId, subclusterId }: { + message: any; + clusterId: any; + subclusterId: any; + }); + } + export class PacketIntro extends Packet { + static type: number; + constructor({ clock, hops, clusterId, subclusterId, usr1, message }: { + clock: any; + hops: any; + clusterId: any; + subclusterId: any; + usr1: any; + message: any; + }); + } + export class PacketJoin extends Packet { + static type: number; + constructor({ clock, hops, clusterId, subclusterId, message }: { + clock: any; + hops: any; + clusterId: any; + subclusterId: any; + message: any; + }); + } + export class PacketPublish extends Packet { + static type: number; + constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }: { + message: any; + sig: any; + packetId: any; + clusterId: any; + subclusterId: any; + nextId: any; + clock: any; + hops: any; + usr1: any; + usr2: any; + ttl: any; + previousId: any; + }); + } + export class PacketStream extends Packet { + static type: number; + constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }: { + message: any; + sig: any; + packetId: any; + clusterId: any; + subclusterId: any; + nextId: any; + clock: any; + ttl: any; + usr1: any; + usr2: any; + usr3: any; + usr4: any; + previousId: any; + }); + } + export class PacketSync extends Packet { + static type: number; + constructor({ packetId, message }: { + packetId: any; + message?: any; + }); + } + export class PacketQuery extends Packet { + static type: number; + constructor({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }: { + packetId: any; + previousId: any; + subclusterId: any; + usr1: any; + usr2: any; + usr3: any; + usr4: any; + message?: {}; + }); + } + export default Packet; + import { Buffer } from "socket:buffer"; +} +declare module "socket:stream-relay/encryption" { + /** + * Class for handling encryption and key management. + */ + export class Encryption { /** - * Dispatch the click window.MouseEvent on an element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.eventClick('.class button', 'Click a button with an event') - * ``` + * Creates a shared key based on the provided seed or generates a random one. + * @param {Uint8Array|string} seed - Seed for key generation. + * @returns {Promise} - Shared key. */ - eventClick(selector: string | HTMLElement | Element, msg?: string): Promise; + static createSharedKey(seed: Uint8Array | string): Promise; /** - * Dispatch an event on the target. - * - * @param {string | Event} event - The event name or Event instance to dispatch. - * @param {string|HTMLElement|Element} target - A CSS selector string, or an instance of HTMLElement, or Element to dispatch the event on. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.dispatchEvent('my-event', '#my-div', 'Fire the my-event event') - * ``` + * Creates a key pair for signing and verification. + * @param {Uint8Array|string} seed - Seed for key generation. + * @returns {Promise<{ publicKey: Uint8Array, privateKey: Uint8Array }>} - Key pair. */ - dispatchEvent(event: string | Event, target: string | HTMLElement | Element, msg?: string): Promise; + static createKeyPair(seed: Uint8Array | string): Promise<{ + publicKey: Uint8Array; + privateKey: Uint8Array; + }>; /** - * Call the focus method on element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.focus('#my-div') - * ``` + * Creates an ID using SHA-256 hash. + * @param {string} str - String to hash. + * @returns {Promise} - SHA-256 hash. */ - focus(selector: string | HTMLElement | Element, msg?: string): Promise; + static createId(str: string): Promise; /** - * Call the blur method on element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.blur('#my-div') - * ``` + * Creates a cluster ID using SHA-256 hash with specified output size. + * @param {string} str - String to hash. + * @returns {Promise} - SHA-256 hash with specified output size. */ - blur(selector: string | HTMLElement | Element, msg?: string): Promise; + static createClusterId(str: string): Promise; /** - * Consecutively set the str value of the element specified by selector to simulate typing. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} str - The string to type into the :focus element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.typeValue('#my-div', 'Hello World', 'Type "Hello World" into #my-div') - * ``` + * Signs a message using the given secret key. + * @param {Buffer} b - The message to sign. + * @param {Uint8Array} sk - The secret key to use. + * @returns {Uint8Array} - Signature. */ - type(selector: string | HTMLElement | Element, str: string, msg?: string): Promise; + static sign(b: Buffer, sk: Uint8Array): Uint8Array; /** - * appendChild an element el to a parent selector element. - * - * @param {string|HTMLElement|Element} parentSelector - A CSS selector string, or an instance of HTMLElement, or Element to appendChild on. - * @param {HTMLElement|Element} el - A element to append to the parent element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * const myElement = createElement('div') - * await t.appendChild('#parent-selector', myElement, 'Append myElement into #parent-selector') - * ``` + * Verifies the signature of a message using the given public key. + * @param {Buffer} b - The message to verify. + * @param {Uint8Array} sig - The signature to check. + * @param {Uint8Array} pk - The public key to use. + * @returns {number} - Returns non-zero if the buffer could not be verified. */ - appendChild(parentSelector: string | HTMLElement | Element, el: HTMLElement | Element, msg?: string): Promise; + static verify(b: Buffer, sig: Uint8Array, pk: Uint8Array): number; /** - * Remove an element from the DOM. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to remove from the DOM. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.removeElement('#dom-selector', 'Remove #dom-selector') - * ``` + * Mapping of public keys to key objects. + * @type {Object.} */ - removeElement(selector: string | HTMLElement | Element, msg?: string): Promise; + keys: { + [x: string]: { + publicKey: Uint8Array; + privateKey: Uint8Array; + ts: number; + }; + }; /** - * Test if an element is visible - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.elementVisible('#dom-selector','Element is visible') - * ``` + * Adds a key pair to the keys mapping. + * @param {Uint8Array|string} publicKey - Public key. + * @param {Uint8Array} privateKey - Private key. */ - elementVisible(selector: string | HTMLElement | Element, msg?: string): Promise; + add(publicKey: Uint8Array | string, privateKey: Uint8Array): void; /** - * Test if an element is invisible - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.elementInvisible('#dom-selector','Element is invisible') - * ``` + * Removes a key from the keys mapping. + * @param {Uint8Array|string} publicKey - Public key. */ - elementInvisible(selector: string | HTMLElement | Element, msg?: string): Promise; + remove(publicKey: Uint8Array | string): void; /** - * Test if an element is invisible - * - * @param {string|(() => HTMLElement|Element|null|undefined)} querySelectorOrFn - A query string or a function that returns an element. - * @param {Object} [opts] - * @param {boolean} [opts.visible] - The element needs to be visible. - * @param {number} [opts.timeout] - The maximum amount of time to wait. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.waitFor('#dom-selector', { visible: true },'#dom-selector is on the page and visible') - * ``` + * Checks if a key is in the keys mapping. + * @param {Uint8Array|string} to - Public key or Uint8Array. + * @returns {boolean} - True if the key is present, false otherwise. */ - waitFor(querySelectorOrFn: string | (() => HTMLElement | Element | null | undefined), opts?: { - visible?: boolean; - timeout?: number; - }, msg?: string): Promise; + has(to: Uint8Array | string): boolean; /** - * @typedef {Object} WaitForTextOpts - * @property {string} [text] - The text to wait for - * @property {number} [timeout] - * @property {Boolean} [multipleTags] - * @property {RegExp} [regex] The regex to wait for + * Decrypts a sealed message for a specific receiver. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. */ + open(message: Buffer, v: any | string): Buffer; /** - * Test if an element is invisible - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {WaitForTextOpts | string | RegExp} [opts] - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.waitForText('#dom-selector', 'Text to wait for') - * ``` - * - * @example - * ```js - * await t.waitForText('#dom-selector', /hello/i) - * ``` - * - * @example - * ```js - * await t.waitForText('#dom-selector', { - * text: 'Text to wait for', - * multipleTags: true - * }) - * ``` + * Opens a sealed message using the specified key. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found. */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { - /** - * - The text to wait for - */ - text?: string; - timeout?: number; - multipleTags?: boolean; - /** - * The regex to wait for - */ - regex?: RegExp; - }, msg?: string): Promise; + openMessage(message: Buffer, v: any | string): Buffer; /** - * Run a querySelector as an assert and also get the results + * Seals a message for a specific receiver using their public key. * - * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. - * @param {string} [msg] - * @returns {HTMLElement | Element} + * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on + * a plaintext `message` for a `receiver` identity. This prevents repudiation + * attacks and doesn't rely on packet chain guarantees. * - * @example - * ```js - * const element = await t.querySelector('#dom-selector') - * ``` - */ - querySelector(selector: string, msg?: string): HTMLElement | Element; - /** - * Run a querySelectorAll as an assert and also get the results + * let ct = Seal(sender | pt, receiver) + * let sig = Sign(ct, sk) + * let out = Seal(sig | ct) * - * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. - * @param {string} [msg] - @returns {Array} + * In an setup between Alice & Bob, this means: + * - Only Bob sees the plaintext + * - Alice wrote the plaintext and the ciphertext + * - Only Bob can see that Alice wrote the plaintext and ciphertext + * - Bob cannot forward the message without invalidating Alice's signature. + * - The outer encryption serves to prevent an attacker from replacing Alice's + * signature. As with _sign-encrypt-sign (SES), ESE is a variant of + * including the recipient's name inside the plaintext, which is then signed + * and encrypted Alice signs her plaintext along with her ciphertext, so as + * to protect herself from a laintext-substitution attack. At the same time, + * Alice's signed plaintext gives Bob non-repudiation. * - * @example - * ```js - * const elements = await t.querySelectorAll('#dom-selector', '') - * ``` + * @see https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html + * + * @param {Buffer} message - The message to seal. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Sealed message. + * @throws {Error} - Throws ENOKEY if the key is not found. */ - querySelectorAll(selector: string, msg?: string): Array; + seal(message: Buffer, v: any | string): Buffer; + } + import Buffer from "socket:buffer"; +} +declare module "socket:stream-relay/cache" { + /** + * @typedef {Packet} CacheEntry + * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver + */ + /** + * Default cache sibling resolver that computes a delta between + * two entries clocks. + * @param {CacheEntry} a + * @param {CacheEntry} b + * @return {number} + */ + export function defaultSiblingResolver(a: CacheEntry, b: CacheEntry): number; + export function trim(buf: Buffer): any; + /** + * Default max size of a `Cache` instance. + */ + export const DEFAULT_MAX_SIZE: number; + /** + * Internal mapping of packet IDs to packet data used by `Cache`. + */ + export class CacheData extends Map { + constructor(); + constructor(entries?: readonly (readonly [any, any])[]); + constructor(); + constructor(iterable?: Iterable); + } + /** + * A class for storing a cache of packets by ID. This class includes a scheme + * for reconciling disjointed packet caches in a large distributed system. The + * following are key design characteristics. + * + * Space Efficiency: This scheme can be space-efficient because it summarizes + * the cache's contents in a compact binary format. By sharing these summaries, + * two computers can quickly determine whether their caches have common data or + * differences. + * + * Bandwidth Efficiency: Sharing summaries instead of the full data can save + * bandwidth. If the differences between the caches are small, sharing summaries + * allows for more efficient data synchronization. + * + * Time Efficiency: The time efficiency of this scheme depends on the size of + * the cache and the differences between the two caches. Generating summaries + * and comparing them can be faster than transferring and comparing the entire + * dataset, especially for large caches. + * + * Complexity: The scheme introduces some complexity due to the need to encode + * and decode summaries. In some cases, the overhead introduced by this + * complexity might outweigh the benefits, especially if the caches are + * relatively small. In this case, you should be using a query. + * + * Data Synchronization Needs: The efficiency also depends on the data + * synchronization needs. If the data needs to be synchronized in real-time, + * this scheme might not be suitable. It's more appropriate for cases where + * periodic or batch synchronization is acceptable. + * + * Scalability: The scheme's efficiency can vary depending on the scalability + * of the system. As the number of cache entries or computers involved + * increases, the complexity of generating and comparing summaries will stay + * bound to a maximum of 16Mb. + * + */ + export class Cache { + static HASH_SIZE_BYTES: number; /** - * Retrieves the computed styles for a given element. - * - * @param {string|Element} selector - The CSS selector or the Element object for which to get the computed styles. - * @param {string} [msg] - An optional message to display when the operation is successful. Default message will be generated based on the type of selector. - * @returns {CSSStyleDeclaration} - The computed styles of the element. - * @throws {Error} - Throws an error if the element has no `ownerDocument` or if `ownerDocument.defaultView` is not available. + * The encodeSummary method provides a compact binary encoding of the output + * of summary() * - * @example - * ```js - * // Using CSS selector - * const style = getComputedStyle('.my-element', 'Custom success message'); - * ``` + * @param {Object} summary - the output of calling summary() + * @return {Buffer} + **/ + static encodeSummary(summary: any): Buffer; + /** + * The decodeSummary method decodes the output of encodeSummary() * - * @example - * ```js - * // Using Element object - * const el = document.querySelector('.my-element'); - * const style = getComputedStyle(el); - * ``` - */ - getComputedStyle(selector: string | Element, msg?: string): CSSStyleDeclaration; + * @param {Buffer} bin - the output of calling encodeSummary() + * @return {Object} summary + **/ + static decodeSummary(bin: Buffer): any; /** - * @param {boolean} pass - * @param {unknown} actual - * @param {unknown} expected - * @param {string} description - * @param {string} operator - * @returns {void} - * @ignore + * `Cache` class constructor. + * @param {CacheData?} [data] */ - _assert(pass: boolean, actual: unknown, expected: unknown, description: string, operator: string): void; + constructor(data?: CacheData | null, siblingResolver?: typeof defaultSiblingResolver); + data: CacheData; + maxSize: number; + siblingResolver: typeof defaultSiblingResolver; /** - * @returns {Promise<{ - * pass: number, - * fail: number - * }>} + * Readonly count of the number of cache entries. + * @type {number} */ - run(): Promise<{ - pass: number; - fail: number; - }>; - } - /** - * @class - */ - export class TestRunner { + get size(): number; /** - * @constructor - * @param {(lines: string) => void} [report] + * Readonly size of the cache in bytes. + * @type {number} */ - constructor(report?: (lines: string) => void); + get bytes(): number; /** - * @type {(lines: string) => void} - * @ignore - */ - report: (lines: string) => void; - /** - * @type {Test[]} - * @ignore - */ - tests: Test[]; - /** - * @type {Test[]} - * @ignore - */ - onlyTests: Test[]; - /** - * @type {boolean} - * @ignore - */ - scheduled: boolean; - /** - * @type {number} - * @ignore - */ - _id: number; - /** - * @type {boolean} - * @ignore - */ - completed: boolean; - /** - * @type {boolean} - * @ignore - */ - rethrowExceptions: boolean; - /** - * @type {boolean} - * @ignore - */ - strict: boolean; - /** - * @type {function | void} - * @ignore + * Inserts a `CacheEntry` value `v` into the cache at key `k`. + * @param {string} k + * @param {CacheEntry} v + * @return {boolean} */ - _onFinishCallback: Function | void; + insert(k: string, v: CacheEntry): boolean; /** - * @returns {string} + * Gets a `CacheEntry` value at key `k`. + * @param {string} k + * @return {CacheEntry?} */ - nextId(): string; + get(k: string): CacheEntry | null; /** - * @type {number} + * @param {string} k + * @return {boolean} */ - get length(): number; + delete(k: string): boolean; /** - * @param {string} name - * @param {TestFn} fn - * @param {boolean} only - * @returns {void} + * Predicate to determine if cache contains an entry at key `k`. + * @param {string} k + * @return {boolean} */ - add(name: string, fn: TestFn, only: boolean): void; + has(k: string): boolean; /** - * @returns {Promise} + * Composes an indexed packet into a new `Packet` + * @param {Packet} packet */ - run(): Promise; + compose(packet: Packet, source?: CacheData): Promise; + sha1(value: any, toHex: any): Promise; /** - * @param {(result: { total: number, success: number, fail: number }) => void} callback - * @returns {void} + * + * The summarize method returns a terse yet comparable summary of the cache + * contents. + * + * Think of the cache as a trie of hex characters, the summary returns a + * checksum for the current level of the trie and for its 16 children. + * + * This is similar to a merkel tree as equal subtrees can easily be detected + * without the need for further recursion. When the subtree checksums are + * inequivalent then further negotiation at lower levels may be required, this + * process continues until the two trees become synchonized. + * + * When the prefix is empty, the summary will return an array of 16 checksums + * these checksums provide a way of comparing that subtree with other peers. + * + * When a variable-length hexidecimal prefix is provided, then only cache + * member hashes sharing this prefix will be considered. + * + * For each hex character provided in the prefix, the trie will decend by one + * level, each level divides the 2^128 address space by 16. For exmaple... + * + * ``` + * Level 0 1 2 + * ---------------- + * 2b00 + * aa0e ━┓ ━┓ + * aa1b ┃ ┃ + * aae3 ┃ ┃ ━┓ + * aaea ┃ ┃ ┃ + * aaeb ┃ ━┛ ━┛ + * ab00 ┃ ━┓ + * ab1e ┃ ┃ + * ab2a ┃ ┃ + * abef ┃ ┃ + * abf0 ━┛ ━┛ + * bff9 + * ``` + * + * @param {string} prefix - a string of lowercased hexidecimal characters + * @return {Object} + * */ - onFinish(callback: (result: { - total: number; - success: number; - fail: number; - }) => void): void; + summarize(prefix?: string, predicate?: (o: any) => boolean): any; } + export default Cache; + export type CacheEntry = Packet; + export type CacheEntrySiblingResolver = (arg0: CacheEntry, arg1: CacheEntry) => number; + import { Buffer } from "socket:buffer"; + import { Packet } from "socket:stream-relay/packets"; +} +declare module "socket:stream-relay/nat" { /** - * @ignore + * The NAT type is encoded using 5 bits: + * + * 0b00001 : the lsb indicates if endpoint dependence information is included + * 0b00010 : the second bit indicates the endpoint dependence value + * + * 0b00100 : the third bit indicates if firewall information is included + * 0b01000 : the fourth bit describes which requests can pass the firewall, only known IPs (0) or any IP (1) + * 0b10000 : the fifth bit describes which requests can pass the firewall, only known ports (0) or any port (1) */ - export const GLOBAL_TEST_RUNNER: TestRunner; - export default test; - export type testWithProperties = { - (name: string, fn?: TestFn): void; - only(name: string, fn?: TestFn): void; - skip(name: string, fn?: TestFn): void; - }; - export type TestFn = (t: Test) => (void | Promise); -} -declare module "socket:test" { - export * from "socket:test/index"; - export default test; - import test from "socket:test/index"; -} -declare module "socket:timers/timer" { - export class Timer { - static from(...args: any[]): void; - constructor(create: any, destroy: any); - get id(): number; - init(...args: any[]): void; - close(): boolean; - [Symbol.toPrimitive](): number; - #private; - } - export class Timeout extends Timer { - constructor(); - } - export class Interval extends Timer { - constructor(); - } - export class Immediate extends Timeout { - } - namespace _default { - export { Timer }; - export { Immediate }; - export { Timeout }; - export { Interval }; - } - export default _default; -} -declare module "socket:timers/promises" { - export function setTimeout(delay?: number, value?: any, options?: any): Promise; - export function setInterval(delay?: number, value?: any, options?: any): AsyncGenerator; - export function setImmediate(value?: any, options?: any): Promise; - namespace _default { - export { setImmediate }; - export { setInterval }; - export { setTimeout }; - } - export default _default; -} -declare module "socket:timers/scheduler" { - export function wait(delay: any, options?: any): Promise; - export function postTask(callback: any, options?: any): Promise; - namespace _default { - export { postTask }; - export { setImmediate as yield }; - export { wait }; - } - export default _default; - import { setImmediate } from "socket:timers/promises"; -} -declare module "socket:timers/index" { - export function setTimeout(callback: any, delay: any, ...args: any[]): void; - export function clearTimeout(timeout: any): void; - export function setInterval(callback: any, delay: any, ...args: any[]): void; - export function clearInterval(interval: any): void; - export function setImmediate(callback: any, ...args: any[]): void; - export function clearImmediate(immediate: any): void; - namespace _default { - export { promises }; - export { scheduler }; - export { setTimeout }; - export { clearTimeout }; - export { setInterval }; - export { clearInterval }; - export { setImmediate }; - export { clearImmediate }; - } - export default _default; - import promises from "socket:timers/promises"; - import scheduler from "socket:timers/scheduler"; -} -declare module "socket:timers" { - export * from "socket:timers/index"; - export default exports; - import * as exports from "socket:timers/index"; -} -declare module "socket:internal/globals" { /** - * Gets a runtime global value by name. - * @ignore - * @param {string} name - * @return {any|null} + * Every remote will see the same IP:PORT mapping for this peer. + * + * :3333 ┌──────┐ + * :1111 ┌───▶ │ R1 │ + * ┌──────┐ ┌───────┐ │ └──────┘ + * │ P1 ├───▶│ NAT ├──┤ + * └──────┘ └───────┘ │ ┌──────┐ + * └───▶ │ R2 │ + * :3333 └──────┘ */ - export function get(name: string): any | null; + export const MAPPING_ENDPOINT_INDEPENDENT: 3; /** - * Symbolic global registry - * @ignore + * Every remote will see a different IP:PORT mapping for this peer. + * + * :4444 ┌──────┐ + * :1111 ┌───▶ │ R1 │ + * ┌──────┐ ┌───────┐ │ └──────┘ + * │ P1 ├───▶│ NAT ├──┤ + * └──────┘ └───────┘ │ ┌──────┐ + * └───▶ │ R2 │ + * :5555 └──────┘ */ - export class GlobalsRegistry { - get global(): any; - symbol(name: any): symbol; - register(name: any, value: any): any; - get(name: any): any; - } - export default registry; - const registry: any; + export const MAPPING_ENDPOINT_DEPENDENT: 1; + /** + * The firewall allows the port mapping to be accessed by: + * - Any IP:PORT combination (FIREWALL_ALLOW_ANY) + * - Any PORT on a previously connected IP (FIREWALL_ALLOW_KNOWN_IP) + * - Only from previously connected IP:PORT combinations (FIREWALL_ALLOW_KNOWN_IP_AND_PORT) + */ + export const FIREWALL_ALLOW_ANY: 28; + export const FIREWALL_ALLOW_KNOWN_IP: 12; + export const FIREWALL_ALLOW_KNOWN_IP_AND_PORT: 4; + /** + * The initial state of the nat is unknown and its value is 0 + */ + export const UNKNOWN: 0; + /** + * Full-cone NAT, also known as one-to-one NAT + * + * Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. + * + * @summary its a packet party at this mapping and everyone's invited + */ + export const UNRESTRICTED: number; + /** + * (Address)-restricted-cone NAT + * + * An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only + * if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter. + * + * @summary The NAT will drop your packets unless a peer within its network has previously messaged you from *any* port. + */ + export const ADDR_RESTRICTED: number; + /** + * Port-restricted cone NAT + * + * An external host (hAddr:hPort) can send packets to iAddr:iPort by sending + * packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to + * hAddr:hPort. + * + * @summary The NAT will drop your packets unless a peer within its network + * has previously messaged you from this *specific* port. + */ + export const PORT_RESTRICTED: number; + /** + * Symmetric NAT + * + * Only an external host that receives a packet from an internal host can send + * a packet back. + * + * @summary The NAT will only accept replies to a correspondence initialized + * by itself, the mapping it created is only valid for you. + */ + export const ENDPOINT_RESTRICTED: number; + export function isEndpointDependenceDefined(nat: any): boolean; + export function isFirewallDefined(nat: any): boolean; + export function isValid(nat: any): boolean; + export function toString(n: any): "UNRESTRICTED" | "ADDR_RESTRICTED" | "PORT_RESTRICTED" | "ENDPOINT_RESTRICTED" | "UNKNOWN"; + export function toStringStrategy(n: any): "STRATEGY_DEFER" | "STRATEGY_DIRECT_CONNECT" | "STRATEGY_TRAVERSAL_OPEN" | "STRATEGY_TRAVERSAL_CONNECT" | "STRATEGY_PROXY" | "STRATEGY_UNKNOWN"; + export const STRATEGY_DEFER: 0; + export const STRATEGY_DIRECT_CONNECT: 1; + export const STRATEGY_TRAVERSAL_OPEN: 2; + export const STRATEGY_TRAVERSAL_CONNECT: 3; + export const STRATEGY_PROXY: 4; + export function connectionStrategy(a: any, b: any): 0 | 1 | 2 | 3 | 4; } -declare module "socket:internal/shared-worker" { - export function getSharedWorkerImplementationForPlatform(): { - new (scriptURL: string | URL, options?: string | WorkerOptions): SharedWorker; - prototype: SharedWorker; - } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; - export class SharedHybridWorkerProxy extends EventTarget { - constructor(url: any, options: any); - onChannelMessage(event: any): void; - get id(): any; - get port(): any; - #private; - } - export class SharedHybridWorker extends EventTarget { - constructor(url: any, nameOrOptions: any); - get port(): any; - #private; +declare module "socket:stream-relay/index" { + /** + * Computes rate limit predicate value for a port and address pair for a given + * threshold updating an input rates map. This method is accessed concurrently, + * the rates object makes operations atomic to avoid race conditions. + * + * @param {Map} rates + * @param {number} type + * @param {number} port + * @param {string} address + * @return {boolean} + */ + export function rateLimit(rates: Map, type: number, port: number, address: string, subclusterIdQuota: any): boolean; + export function debug(pid: any, ...args: any[]): void; + /** + * Retry delay in milliseconds for ping. + * @type {number} + */ + export const PING_RETRY: number; + /** + * Probe wait timeout in milliseconds. + * @type {number} + */ + export const PROBE_WAIT: number; + /** + * Default keep alive timeout. + * @type {number} + */ + export const DEFAULT_KEEP_ALIVE: number; + /** + * Default rate limit threshold in milliseconds. + * @type {number} + */ + export const DEFAULT_RATE_LIMIT_THRESHOLD: number; + export function getRandomPort(ports: object, p: number | null): number; + /** + * A `RemotePeer` represents an initial, discovered, or connected remote peer. + * Typically, you will not need to create instances of this class directly. + */ + export class RemotePeer { + /** + * `RemotePeer` class constructor. + * @param {{ + * peerId?: string, + * address?: string, + * port?: number, + * natType?: number, + * clusters: object, + * reflectionId?: string, + * distance?: number, + * publicKey?: string, + * privateKey?: string, + * clock?: number, + * lastUpdate?: number, + * lastRequest?: number + * }} o + */ + constructor(o: { + peerId?: string; + address?: string; + port?: number; + natType?: number; + clusters: object; + reflectionId?: string; + distance?: number; + publicKey?: string; + privateKey?: string; + clock?: number; + lastUpdate?: number; + lastRequest?: number; + }, peer: any); + peerId: any; + address: any; + port: number; + natType: any; + clusters: {}; + pingId: any; + distance: number; + connected: boolean; + opening: number; + probed: number; + proxy: any; + clock: number; + uptime: number; + lastUpdate: number; + lastRequest: number; + localPeer: any; + write(sharedKey: any, args: any): Promise; } - export const SharedWorker: { - new (scriptURL: string | URL, options?: string | WorkerOptions): SharedWorker; - prototype: SharedWorker; - } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; - export default SharedWorker; -} -declare module "socket:service-worker/state" { - export const channel: BroadcastChannel; - export const state: any; - export default state; -} -declare module "socket:service-worker/instance" { - export function createServiceWorker(currentState?: any, options?: any): any; - export const SHARED_WORKER_URL: URL; - export const ServiceWorker: { - new (): ServiceWorker; - prototype: ServiceWorker; + export function wrap(dgram: any): { + new (persistedState?: object | null): { + port: any; + address: any; + natType: number; + nextNatType: number; + clusters: {}; + reflectionId: any; + reflectionTimeout: any; + reflectionStage: number; + reflectionRetry: number; + reflectionFirstResponder: any; + peerId: string; + isListening: boolean; + ctime: number; + lastUpdate: number; + lastSync: number; + closing: boolean; + clock: number; + unpublished: {}; + cache: any; + uptime: number; + maxHops: number; + bdpCache: number[]; + onListening: any; + onDelete: any; + sendQueue: any[]; + firewall: any; + rates: Map; + streamBuffer: Map; + gate: Map; + returnRoutes: Map; + metrics: { + i: { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; + 8: number; + REJECTED: number; + }; + o: { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; + 8: number; + }; + }; + peers: any; + encryption: Encryption; + config: any; + _onError: (err: any) => any; + socket: any; + probeSocket: any; + /** + * An implementation for clearning an interval that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearInterval(tid: any): undefined; + /** + * An implementation for clearning a timeout that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearTimeout(tid: any): undefined; + /** + * An implementation of an internal timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setInterval(fn: any, t: any): number; + /** + * An implementation of an timeout timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setTimeout(fn: any, t: any): number; + /** + * A method that encapsulates the listing procedure + * @return {undefined} + * @ignore + */ + _listen(): undefined; + init(cb: any): Promise; + onReady: any; + mainLoopTimer: number; + /** + * Continuously evaluate the state of the peer and its network + * @return {undefined} + * @ignore + */ + _mainLoop(ts: any): undefined; + /** + * Enqueue packets to be sent to the network + * @param {Buffer} data - An encoded packet + * @param {number} port - The desination port of the remote host + * @param {string} address - The destination address of the remote host + * @param {Socket=this.socket} socket - The socket to send on + * @return {undefined} + * @ignore + */ + send(data: Buffer, port: number, address: string, socket?: any): undefined; + /** + * @private + */ + _scheduleSend(): void; + sendTimeout: number; + /** + * @private + */ + _dequeue(): void; + /** + * Send any unpublished packets + * @return {undefined} + * @ignore + */ + sendUnpublished(): undefined; + /** + * Get the serializable state of the peer (can be passed to the constructor or create method) + * @return {undefined} + */ + getState(): undefined; + /** + * Get a selection of known peers + * @return {Array} + * @ignore + */ + getPeers(packet: any, peers: any, ignorelist: any, filter?: (o: any) => any): Array; + /** + * Send an eventually consistent packet to a selection of peers (fanout) + * @return {undefined} + * @ignore + */ + mcast(packet: any, ignorelist?: any[]): undefined; + /** + * The process of determining this peer's NAT behavior (firewall and dependentness) + * @return {undefined} + * @ignore + */ + requestReflection(): undefined; + probeReflectionTimeout: any; + /** + * Ping another peer + * @return {PacketPing} + * @ignore + */ + ping(peer: any, withRetry: any, props: any, socket: any): PacketPing; + getPeer(id: any): any; + /** + * This should be called at least once when an app starts to multicast + * this peer, and starts querying the network to discover peers. + * @param {object} keys - Created by `Encryption.createKeyPair()`. + * @param {object=} args - Options + * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. + * @return {RemotePeer} + */ + join(sharedKey: any, args?: object | undefined): RemotePeer; + /** + * @param {Packet} T - The constructor to be used to create packets. + * @param {Any} message - The message to be split and packaged. + * @return {Array>} + * @ignore + */ + _message2packets(T: Packet, message: Any, args: any): Array>; + /** + * Sends a packet into the network that will be replicated and buffered. + * Each peer that receives it will buffer it until TTL and then replicate + * it provided it has has not exceeded their maximum number of allowed hops. + * + * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. + * @param {object} args - The arguments to be applied. + * @param {Buffer} args.message - The message to be encrypted by keys and sent. + * @param {Packet=} args.packet - The previous packet in the packet chain. + * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. + * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. + * @return {Array} + */ + publish(sharedKey: any, args: { + message: Buffer; + packet?: Packet | undefined; + usr1: Buffer; + usr2: Buffer; + }): Array; + /** + * @return {undefined} + */ + sync(peer: any): undefined; + close(): void; + /** + * Deploy a query into the network + * @return {undefined} + * + */ + query(query: any): undefined; + /** + * + * This is a default implementation for deciding what to summarize + * from the cache when receiving a request to sync. that can be overridden + * + */ + cachePredicate(packet: any): boolean; + /** + * A connection was made, add the peer to the local list of known + * peers and call the onConnection if it is defined by the user. + * + * @return {undefined} + * @ignore + */ + _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; + connections: Map; + /** + * Received a Sync Packet + * @return {undefined} + * @ignore + */ + _onSync(packet: any, port: any, address: any): undefined; + /** + * Received a Query Packet + * + * a -> b -> c -> (d) -> c -> b -> a + * + * @return {undefined} + * @example + * + * ```js + * peer.onQuery = (packet) => { + * // + * // read a database or something + * // + * return { + * message: Buffer.from('hello'), + * publicKey: '', + * privateKey: '' + * } + * } + * ``` + */ + _onQuery(packet: any, port: any, address: any): undefined; + /** + * Received a Ping Packet + * @return {undefined} + * @ignore + */ + _onPing(packet: any, port: any, address: any): undefined; + /** + * Received a Pong Packet + * @return {undefined} + * @ignore + */ + _onPong(packet: any, port: any, address: any): undefined; + /** + * Received an Intro Packet + * @return {undefined} + * @ignore + */ + _onIntro(packet: any, port: any, address: any, _: any, opts?: { + attempts: number; + }): undefined; + socketPool: any[]; + /** + * Received an Join Packet + * @return {undefined} + * @ignore + */ + _onJoin(packet: any, port: any, address: any, data: any): undefined; + /** + * Received an Publish Packet + * @return {undefined} + * @ignore + */ + _onPublish(packet: any, port: any, address: any, data: any): undefined; + /** + * Received an Stream Packet + * @return {undefined} + * @ignore + */ + _onStream(packet: any, port: any, address: any, data: any): undefined; + /** + * Received any packet on the probe port to determine the firewall: + * are you port restricted, host restricted, or unrestricted. + * @return {undefined} + * @ignore + */ + _onProbeMessage(data: any, { port, address }: { + port: any; + address: any; + }): undefined; + /** + * When a packet is received it is decoded, the packet contains the type + * of the message. Based on the message type it is routed to a function. + * like WebSockets, don't answer queries unless we know its another SRP peer. + * + * @param {Buffer|Uint8Array} data + * @param {{ port: number, address: string }} info + */ + _onMessage(data: Buffer | Uint8Array, { port, address }: { + port: number; + address: string; + }): Promise; + }; }; - const _default: any; - export default _default; + export default wrap; + import { Packet } from "socket:stream-relay/packets"; + import { sha256 } from "socket:stream-relay/packets"; + import { Cache } from "socket:stream-relay/cache"; + import { Encryption } from "socket:stream-relay/encryption"; + import * as NAT from "socket:stream-relay/nat"; + import { Buffer } from "socket:buffer"; + import { PacketPing } from "socket:stream-relay/packets"; + import { PacketPublish } from "socket:stream-relay/packets"; + export { Packet, sha256, Cache, Encryption, NAT }; } -declare module "socket:worker_threads/init" { - export const SHARE_ENV: unique symbol; - export const isMainThread: boolean; - export namespace state { - export { isMainThread }; - export let parentPort: any; - export let mainPort: any; - export let workerData: any; - export let url: any; - export let env: {}; - export let id: number; - } - namespace _default { - export { state }; - } +declare module "socket:stream-relay/sugar" { + function _default(dgram: object, events: object): Promise; export default _default; } -declare module "socket:worker_threads" { - /** - * Set shared worker environment data. - * @param {string} key - * @param {any} value - */ - export function setEnvironmentData(key: string, value: any): void; - /** - * Get shared worker environment data. - * @param {string} key - * @return {any} - */ - export function getEnvironmentData(key: string): any; - /** - * A pool of known worker threads. - * @type {} - */ - export const workers: () => () => any; - /** - * `true` if this is the "main" thread, otherwise `false` - * The "main" thread is the top level webview window. - * @type {boolean} - */ - export const isMainThread: boolean; - /** - * The main thread `MessagePort` which is `null` when the - * current context is not the "main thread". - * @type {MessagePort?} - */ - export const mainPort: MessagePort | null; - /** - * A worker thread `BroadcastChannel` class. - */ - export class BroadcastChannel extends globalThis.BroadcastChannel { - } - /** - * A worker thread `MessageChannel` class. - */ - export class MessageChannel extends globalThis.MessageChannel { - } - /** - * A worker thread `MessagePort` class. - */ - export class MessagePort extends globalThis.MessagePort { - } - /** - * The current unique thread ID. - * @type {number} - */ - export const threadId: number; - /** - * The parent `MessagePort` instance - * @type {MessagePort?} - */ - export const parentPort: MessagePort | null; - /** - * Transferred "worker data" when creating a new `Worker` instance. - * @type {any?} - */ - export const workerData: any | null; - /** - * @typedef {{ - * env?: object, - * stdin?: boolean = false, - * stdout?: boolean = false, - * stderr?: boolean = false, - * workerData?: any, - * transferList?: any[], - * eval?: boolean = false - * }} WorkerOptions - - /** - * A worker thread that can communicate directly with a parent thread, - * share environment data, and process streamed data. - */ - export class Worker extends EventEmitter { - /** - * `Worker` class constructor. - * @param {string} filename - * @param {WorkerOptions=} [options] - */ - constructor(filename: string, options?: WorkerOptions | undefined); - /** - * Handles incoming worker messages. - * @ignore - * @param {MessageEvent} event - */ - onWorkerMessage(event: MessageEvent): boolean; - /** - * Handles process environment change events - * @ignore - * @param {import('./process.js').ProcessEnvironmentEvent} event - */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; - /** - * The unique ID for this `Worker` thread instace. - * @type {number} - */ - get id(): number; - /** - * A `Writable` standard input stream if `{ stdin: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Writable?} - */ - get stdin(): Writable; - /** - * A `Readable` standard output stream if `{ stdout: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Readable?} - */ - get stdout(): Readable; - /** - * A `Readable` standard error stream if `{ stderr: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Readable?} - */ - get stderr(): Readable; - /** - * Terminates the `Worker` instance - */ - terminate(): void; - #private; +declare module "socket:node/index" { + export default network; + export const network: Promise; + import { Cache } from "socket:stream-relay/index"; + import { sha256 } from "socket:stream-relay/index"; + import { Encryption } from "socket:stream-relay/index"; + import { Packet } from "socket:stream-relay/index"; + import { NAT } from "socket:stream-relay/index"; + export { Cache, sha256, Encryption, Packet, NAT }; +} +declare module "socket:index" { + import { network } from "socket:node/index"; + import { Cache } from "socket:node/index"; + import { sha256 } from "socket:node/index"; + import { Encryption } from "socket:node/index"; + import { Packet } from "socket:node/index"; + import { NAT } from "socket:node/index"; + export { network, Cache, sha256, Encryption, Packet, NAT }; +} +declare module "socket:string_decoder" { + export function StringDecoder(encoding: any): void; + export class StringDecoder { + constructor(encoding: any); + encoding: any; + text: typeof utf16Text | typeof base64Text; + end: typeof utf16End | typeof base64End | typeof simpleEnd; + fillLast: typeof utf8FillLast; + write: typeof simpleWrite; + lastNeed: number; + lastTotal: number; + lastChar: Uint8Array; } - namespace _default { - export { Worker }; - export { isMainThread }; - export { parentPort }; - export { setEnvironmentData }; - export { getEnvironmentData }; - export { workerData }; - export { threadId }; - export { SHARE_ENV }; + export default StringDecoder; + function utf16Text(buf: any, i: any): any; + class utf16Text { + constructor(buf: any, i: any); + lastNeed: number; + lastTotal: number; } - export default _default; - /** - * /** - * A worker thread that can communicate directly with a parent thread, - * share environment data, and process streamed data. - */ - export type WorkerOptions = { - env?: object; - stdin?: boolean; - stdout?: boolean; - stderr?: boolean; - workerData?: any; - transferList?: any[]; - eval?: boolean; - }; - import { EventEmitter } from "socket:events"; - import { Writable } from "socket:stream"; - import { Readable } from "socket:stream"; - import { SHARE_ENV } from "socket:worker_threads/init"; - import init from "socket:worker_threads/init"; - import { env } from "socket:process"; - export { SHARE_ENV, init }; + function base64Text(buf: any, i: any): any; + class base64Text { + constructor(buf: any, i: any); + lastNeed: number; + lastTotal: number; + } + function utf16End(buf: any): any; + function base64End(buf: any): any; + function simpleEnd(buf: any): any; + function utf8FillLast(buf: any): any; + function simpleWrite(buf: any): any; } -declare module "socket:worker" { - export default Worker; - import { SharedWorker } from "socket:internal/shared-worker"; - import { ServiceWorker } from "socket:service-worker/instance"; - import { Worker } from "socket:worker_threads"; - export { SharedWorker, ServiceWorker, Worker }; +declare module "socket:test/context" { + export default function _default(GLOBAL_TEST_RUNNER: any): void; } -declare module "socket:vm" { - /** - * @ignore - * @param {object[]} transfer - * @param {object} object - * @param {object=} [options] - * @return {object[]} - */ - export function findMessageTransfers(transfers: any, object: object, options?: object | undefined): object[]; - /** - * @ignore - * @param {object} context - */ - export function applyInputContextReferences(context: object): void; - /** - * @ignore - * @param {object} context - */ - export function applyOutputContextReferences(context: object): void; - /** - * @ignore - * @param {object} context - */ - export function filterNonTransferableValues(context: object): void; - /** - * @ignore - * @param {object=} [currentContext] - * @param {object=} [updatedContext] - * @param {object=} [contextReference] - * @return {{ deletions: string[], merges: string[] }} - */ - export function applyContextDifferences(currentContext?: object | undefined, updatedContext?: object | undefined, contextReference?: object | undefined, preserveScriptArgs?: boolean): { - deletions: string[]; - merges: string[]; - }; - /** - * Wrap a JavaScript function source. - * @ignore - * @param {string} source - * @param {object=} [options] - */ - export function wrapFunctionSource(source: string, options?: object | undefined): string; - /** - * Gets the VM context window. - * This function will create it if it does not already exist. - * The current window will be used on Android or iOS platforms as there can - * only be one window. - * @return {Promise; - /** - * Gets the `SharedWorker` that for the VM context. - * @return {Promise} - */ - export function getContextWorker(): Promise; - /** - * Terminates the VM script context window. - * @ignore - */ - export function terminateContextWindow(): Promise; - /** - * Terminates the VM script context worker. - * @ignore - */ - export function terminateContextWorker(): Promise; - /** - * Creates a prototype object of known global reserved intrinsics. - * @ignore - */ - export function createIntrinsics(options: any): any; - /** - * Returns `true` if value is an intrinsic, otherwise `false`. - * @param {any} value - * @return {boolean} - */ - export function isIntrinsic(value: any): boolean; - /** - * Get the intrinsic type of a given `value`. - * @param {any} - * @return {function|object|null|undefined} - */ - export function getIntrinsicType(value: any): Function | object | null | undefined; - /** - * Get the intrinsic type string of a given `value`. - * @param {any} - * @return {string|null} - */ - export function getIntrinsicTypeString(value: any): string | null; - /** - * Creates a global proxy object for context execution. - * @ignore - * @param {object} context - * @return {Proxy} - */ - export function createGlobalObject(context: object, options: any): ProxyConstructor; - /** - * @ignore - * @param {string} source - * @return {boolean} - */ - export function detectFunctionSourceType(source: string): boolean; - /** - * Compiles `source` with `options` into a function. - * @ignore - * @param {string} source - * @param {object=} [options] - * @return {function} - */ - export function compileFunction(source: string, options?: object | undefined): Function; - /** - * Run `source` JavaScript in given context. The script context execution - * context is preserved until the `context` object that points to it is - * garbage collected or there are no longer any references to it and its - * associated `Script` instance. - * @param {string|object|function} source - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} - */ - export function runInContext(source: string | object | Function, options?: ScriptOptions | undefined, context?: object | undefined): Promise; +declare module "socket:test/dom-helpers" { /** - * Run `source` JavaScript in new context. The script context is destroyed after - * execution. This is typically a "one off" isolated run. - * @param {string} source - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} + * Converts querySelector string to an HTMLElement or validates an existing HTMLElement. + * + * @export + * @param {string|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @returns {Element} The HTMLElement, Element, or Window that corresponds to the selector. + * @throws {Error} Throws an error if the `selector` is not a string that resolves to an HTMLElement or not an instance of HTMLElement, Element, or Window. + * */ - export function runInNewContext(source: string, options?: ScriptOptions | undefined, context?: object | undefined): Promise; + export function toElement(selector: string | Element): Element; /** - * Run `source` JavaScript in this current context (`globalThis`). - * @param {string} source - * @param {ScriptOptions=} [options] - * @return {Promise} + * Waits for an element to appear in the DOM and resolves the promise when it does. + * + * @export + * @param {Object} args - Configuration arguments. + * @param {string} [args.selector] - The CSS selector to look for. + * @param {boolean} [args.visible=true] - Whether the element should be visible. + * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. + * @param {() => HTMLElement | Element | null | undefined} [lambda] - An optional function that returns the element. Used if the `selector` is not provided. + * @returns {Promise} - A promise that resolves to the found element. + * + * @throws {Error} - Throws an error if neither `lambda` nor `selector` is provided. + * @throws {Error} - Throws an error if the element is not found within the timeout. + * + * @example + * ```js + * waitFor({ selector: '#my-element', visible: true, timeout: 5000 }) + * .then(el => console.log('Element found:', el)) + * .catch(err => console.log('Element not found:', err)); + * ``` */ - export function runInThisContext(source: string, options?: ScriptOptions | undefined): Promise; + export function waitFor(args: { + selector?: string; + visible?: boolean; + timeout?: number; + }, lambda?: () => HTMLElement | Element | null | undefined): Promise; /** - * @ignore - * @param {Reference} reference + * Waits for an element's text content to match a given string or regular expression. + * + * @export + * @param {Object} args - Configuration arguments. + * @param {Element} args.element - The root element from which to begin searching. + * @param {string} [args.text] - The text to search for within elements. + * @param {RegExp} [args.regex] - A regular expression to match against element text content. + * @param {boolean} [args.multipleTags=false] - Whether to look for text across multiple sibling elements. + * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. + * @returns {Promise} - A promise that resolves to the found element or null. + * + * @example + * ```js + * waitForText({ element: document.body, text: 'Hello', timeout: 5000 }) + * .then(el => console.log('Element found:', el)) + * .catch(err => console.log('Element not found:', err)); + * ``` */ - export function putReference(reference: Reference): void; + export function waitForText(args: { + element: Element; + text?: string; + regex?: RegExp; + multipleTags?: boolean; + timeout?: number; + }): Promise; /** - * Create a `Reference` for a `value` in a script `context`. - * @param {any} value - * @param {object} context - * @param {object=} [options] - * @return {Reference} + * @export + * @param {Object} args - Arguments + * @param {string | Event} args.event - The event to dispatch. + * @param {HTMLElement | Element | window} [args.element=window] - The element to dispatch the event on. + * @returns {void} + * + * @throws {Error} Throws an error if the `event` is not a string that can be converted to a CustomEvent or not an instance of Event. */ - export function createReference(value: any, context: object, options?: object | undefined): Reference; + export function event(args: { + event: string | Event; + element?: HTMLElement | Element | (Window & typeof globalThis); + }): void; /** - * Get a script context by ID or values - * @param {string|object|function} id - * @return {Reference?} + * @export + * Copy pasted from https://raw.githubusercontent.com/testing-library/jest-dom/master/src/to-be-visible.js + * @param {Element | HTMLElement} element + * @param {Element | HTMLElement} [previousElement] + * @returns {boolean} */ - export function getReference(id: string | object | Function): Reference | null; + export function isElementVisible(element: Element | HTMLElement, previousElement?: Element | HTMLElement): boolean; +} +declare module "socket:test/index" { /** - * Remove a script context reference by ID. - * @param {string} id + * @returns {number} - The default timeout for tests in milliseconds. */ - export function removeReference(id: string): void; + export function getDefaultTestRunnerTimeout(): number; /** - * Get all transferable values in the `object` hierarchy. - * @param {object} object - * @return {object[]} + * @param {string} name + * @param {TestFn} [fn] + * @returns {void} */ - export function getTransferables(object: object): object[]; + export function only(name: string, fn?: TestFn): void; /** - * @ignore - * @param {object} object - * @return {object} + * @param {string} _name + * @param {TestFn} [_fn] + * @returns {void} */ - export function createContext(object: object): object; + export function skip(_name: string, _fn?: TestFn): void; /** - * Returns `true` if `object` is a "context" object. - * @param {object} - * @return {boolean} + * @param {boolean} strict + * @returns {void} */ - export function isContext(object: any): boolean; + export function setStrict(strict: boolean): void; /** - * A container for a context worker message channel that looks like a "worker". + * @typedef {{ + * (name: string, fn?: TestFn): void + * only(name: string, fn?: TestFn): void + * skip(name: string, fn?: TestFn): void + * }} testWithProperties * @ignore */ - export class ContextWorkerInterface extends EventTarget { - get channel(): any; - get port(): any; - destroy(): void; - #private; - } /** - * A container proxy for a context worker message channel that - * looks like a "worker". - * @ignore + * @type {testWithProperties} + * @param {string} name + * @param {TestFn} [fn] + * @returns {void} */ - export class ContextWorkerInterfaceProxy extends EventTarget { - constructor(globals: any); - get port(): any; - #private; + export function test(name: string, fn?: TestFn): void; + export namespace test { + export { only }; + export { skip }; } /** - * Global reserved values that a script context may not modify. - * @type {string[]} + * @typedef {(t: Test) => (void | Promise)} TestFn */ - export const RESERVED_GLOBAL_INTRINSICS: string[]; /** - * A unique reference to a value owner by a "context object" and a - * `Script` instance. + * @class */ - export class Reference { + export class Test { + /** + * @constructor + * @param {string} name + * @param {TestFn} fn + * @param {TestRunner} runner + */ + constructor(name: string, fn: TestFn, runner: TestRunner); + /** + * @type {string} + * @ignore + */ + name: string; + /** + * @type {null|number} + * @ignore + */ + _planned: null | number; + /** + * @type {null|number} + * @ignore + */ + _actual: null | number; + /** + * @type {TestFn} + * @ignore + */ + fn: TestFn; + /** + * @type {TestRunner} + * @ignore + */ + runner: TestRunner; + /** + * @type{{ pass: number, fail: number }} + * @ignore + */ + _result: { + pass: number; + fail: number; + }; + /** + * @type {boolean} + * @ignore + */ + done: boolean; + /** + * @type {boolean} + * @ignore + */ + strict: boolean; + /** + * @param {string} msg + * @returns {void} + */ + comment(msg: string): void; + /** + * Plan the number of assertions. + * + * @param {number} n + * @returns {void} + */ + plan(n: number): void; + /** + * @template T + * @param {T} actual + * @param {T} expected + * @param {string} [msg] + * @returns {void} + */ + deepEqual(actual: T, expected: T, msg?: string): void; + /** + * @template T + * @param {T} actual + * @param {T} expected + * @param {string} [msg] + * @returns {void} + */ + notDeepEqual(actual: T_1, expected: T_1, msg?: string): void; + /** + * @template T + * @param {T} actual + * @param {T} expected + * @param {string} [msg] + * @returns {void} + */ + equal(actual: T_2, expected: T_2, msg?: string): void; + /** + * @param {unknown} actual + * @param {unknown} expected + * @param {string} [msg] + * @returns {void} + */ + notEqual(actual: unknown, expected: unknown, msg?: string): void; + /** + * @param {string} [msg] + * @returns {void} + */ + fail(msg?: string): void; + /** + * @param {unknown} actual + * @param {string} [msg] + * @returns {void} + */ + ok(actual: unknown, msg?: string): void; + /** + * @param {string} [msg] + * @returns {void} + */ + pass(msg?: string): void; + /** + * @param {Error | null | undefined} err + * @param {string} [msg] + * @returns {void} + */ + ifError(err: Error | null | undefined, msg?: string): void; + /** + * @param {Function} fn + * @param {RegExp | any} [expected] + * @param {string} [message] + * @returns {void} + */ + throws(fn: Function, expected?: RegExp | any, message?: string): void; + /** + * Sleep for ms with an optional msg + * + * @param {number} ms + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.sleep(100) + * ``` + */ + sleep(ms: number, msg?: string): Promise; + /** + * Request animation frame with an optional msg. Falls back to a 0ms setTimeout when + * tests are run headlessly. + * + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.requestAnimationFrame() + * ``` + */ + requestAnimationFrame(msg?: string): Promise; + /** + * Dispatch the `click`` method on an element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.click('.class button', 'Click a button') + * ``` + */ + click(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Dispatch the click window.MouseEvent on an element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.eventClick('.class button', 'Click a button with an event') + * ``` + */ + eventClick(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Dispatch an event on the target. + * + * @param {string | Event} event - The event name or Event instance to dispatch. + * @param {string|HTMLElement|Element} target - A CSS selector string, or an instance of HTMLElement, or Element to dispatch the event on. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.dispatchEvent('my-event', '#my-div', 'Fire the my-event event') + * ``` + */ + dispatchEvent(event: string | Event, target: string | HTMLElement | Element, msg?: string): Promise; /** - * Predicate function to determine if a `value` is an internal or external - * script reference value. - * @param {amy} value - * @return {boolean} + * Call the focus method on element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.focus('#my-div') + * ``` */ - static isReference(value: amy): boolean; + focus(selector: string | HTMLElement | Element, msg?: string): Promise; /** - * `Reference` class constructor. - * @param {string} id - * @param {any} value - * @param {object=} [context] - * @param {object=} [options] + * Call the blur method on element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.blur('#my-div') + * ``` */ - constructor(id: string, value: any, context?: object | undefined, options?: object | undefined); + blur(selector: string | HTMLElement | Element, msg?: string): Promise; /** - * The unique id of the reference - * @type {string} + * Consecutively set the str value of the element specified by selector to simulate typing. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} str - The string to type into the :focus element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.typeValue('#my-div', 'Hello World', 'Type "Hello World" into #my-div') + * ``` */ - get id(): string; + type(selector: string | HTMLElement | Element, str: string, msg?: string): Promise; /** - * The underling primitive type of the reference value. - * @ignore - * @type {'undefined'|'object'|'number'|'boolean'|'function'|'symbol'} + * appendChild an element el to a parent selector element. + * + * @param {string|HTMLElement|Element} parentSelector - A CSS selector string, or an instance of HTMLElement, or Element to appendChild on. + * @param {HTMLElement|Element} el - A element to append to the parent element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * const myElement = createElement('div') + * await t.appendChild('#parent-selector', myElement, 'Append myElement into #parent-selector') + * ``` */ - get type(): "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; + appendChild(parentSelector: string | HTMLElement | Element, el: HTMLElement | Element, msg?: string): Promise; /** - * The underlying value of the reference. - * @type {any?} + * Remove an element from the DOM. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to remove from the DOM. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.removeElement('#dom-selector', 'Remove #dom-selector') + * ``` */ - get value(): any; + removeElement(selector: string | HTMLElement | Element, msg?: string): Promise; /** - * The name of the type. - * @type {string?} + * Test if an element is visible + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.elementVisible('#dom-selector','Element is visible') + * ``` */ - get name(): string; + elementVisible(selector: string | HTMLElement | Element, msg?: string): Promise; /** - * The `Script` this value belongs to, if available. - * @type {Script?} + * Test if an element is invisible + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.elementInvisible('#dom-selector','Element is invisible') + * ``` */ - get script(): Script; + elementInvisible(selector: string | HTMLElement | Element, msg?: string): Promise; /** - * The "context object" this reference value belongs to. - * @type {object?} + * Test if an element is invisible + * + * @param {string|(() => HTMLElement|Element|null|undefined)} querySelectorOrFn - A query string or a function that returns an element. + * @param {Object} [opts] + * @param {boolean} [opts.visible] - The element needs to be visible. + * @param {number} [opts.timeout] - The maximum amount of time to wait. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.waitFor('#dom-selector', { visible: true },'#dom-selector is on the page and visible') + * ``` */ - get context(): any; + waitFor(querySelectorOrFn: string | (() => HTMLElement | Element | null | undefined), opts?: { + visible?: boolean; + timeout?: number; + }, msg?: string): Promise; /** - * A boolean value to indicate if the underlying reference value is an - * intrinsic value. - * @type {boolean} + * @typedef {Object} WaitForTextOpts + * @property {string} [text] - The text to wait for + * @property {number} [timeout] + * @property {Boolean} [multipleTags] + * @property {RegExp} [regex] The regex to wait for + */ + /** + * Test if an element is invisible + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {WaitForTextOpts | string | RegExp} [opts] + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.waitForText('#dom-selector', 'Text to wait for') + * ``` + * + * @example + * ```js + * await t.waitForText('#dom-selector', /hello/i) + * ``` + * + * @example + * ```js + * await t.waitForText('#dom-selector', { + * text: 'Text to wait for', + * multipleTags: true + * }) + * ``` + */ + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + /** + * - The text to wait for + */ + text?: string; + timeout?: number; + multipleTags?: boolean; + /** + * The regex to wait for + */ + regex?: RegExp; + }, msg?: string): Promise; + /** + * Run a querySelector as an assert and also get the results + * + * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. + * @param {string} [msg] + * @returns {HTMLElement | Element} + * + * @example + * ```js + * const element = await t.querySelector('#dom-selector') + * ``` + */ + querySelector(selector: string, msg?: string): HTMLElement | Element; + /** + * Run a querySelectorAll as an assert and also get the results + * + * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. + * @param {string} [msg] + @returns {Array} + * + * @example + * ```js + * const elements = await t.querySelectorAll('#dom-selector', '') + * ``` + */ + querySelectorAll(selector: string, msg?: string): Array; + /** + * Retrieves the computed styles for a given element. + * + * @param {string|Element} selector - The CSS selector or the Element object for which to get the computed styles. + * @param {string} [msg] - An optional message to display when the operation is successful. Default message will be generated based on the type of selector. + * @returns {CSSStyleDeclaration} - The computed styles of the element. + * @throws {Error} - Throws an error if the element has no `ownerDocument` or if `ownerDocument.defaultView` is not available. + * + * @example + * ```js + * // Using CSS selector + * const style = getComputedStyle('.my-element', 'Custom success message'); + * ``` + * + * @example + * ```js + * // Using Element object + * const el = document.querySelector('.my-element'); + * const style = getComputedStyle(el); + * ``` + */ + getComputedStyle(selector: string | Element, msg?: string): CSSStyleDeclaration; + /** + * @param {boolean} pass + * @param {unknown} actual + * @param {unknown} expected + * @param {string} description + * @param {string} operator + * @returns {void} + * @ignore + */ + _assert(pass: boolean, actual: unknown, expected: unknown, description: string, operator: string): void; + /** + * @returns {Promise<{ + * pass: number, + * fail: number + * }>} + */ + run(): Promise<{ + pass: number; + fail: number; + }>; + } + /** + * @class + */ + export class TestRunner { + /** + * @constructor + * @param {(lines: string) => void} [report] + */ + constructor(report?: (lines: string) => void); + /** + * @type {(lines: string) => void} + * @ignore */ - get isIntrinsic(): boolean; + report: (lines: string) => void; /** - * A boolean value to indicate if the underlying reference value is an - * external reference value. - * @type {boolean} + * @type {Test[]} + * @ignore */ - get isExternal(): boolean; + tests: Test[]; /** - * The intrinsic type this reference may be an instance of or directly refer to. - * @type {function|object} + * @type {Test[]} + * @ignore */ - get intrinsicType(): any; + onlyTests: Test[]; /** - * Releases strongly held value and weak references - * to the "context object". + * @type {boolean} + * @ignore */ - release(): void; + scheduled: boolean; /** - * Converts this `Reference` to a JSON object. - * @param {boolean=} [includeValue = false] + * @type {number} + * @ignore */ - toJSON(includeValue?: boolean | undefined): { - __vmScriptReference__: boolean; - id: string; - type: "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; - name: string; - isIntrinsic: boolean; - intrinsicType: string; - }; - #private; - } - /** - * @typedef {{ - * filename?: string, - * context?: object - * }} ScriptOptions - */ - /** - * A `Script` is a container for raw JavaScript to be executed in - * a completely isolated virtual machine context, optionally with - * user supplied context. Context objects references are not actually - * shared, but instead provided to the script execution context using the - * structured cloning algorithm used by the Message Channel API. Context - * differences are computed and applied after execution so the user supplied - * context object realizes context changes after script execution. All script - * sources run in an "async" context so a "top level await" should work. - */ - export class Script extends EventTarget { + _id: number; /** - * `Script` class constructor - * @param {string} source - * @param {ScriptOptions} [options] + * @type {boolean} + * @ignore */ - constructor(source: string, options?: ScriptOptions); + completed: boolean; /** - * The script identifier. + * @type {boolean} + * @ignore */ - get id(): any; + rethrowExceptions: boolean; /** - * The source for this script. - * @type {string} + * @type {boolean} + * @ignore */ - get source(): string; + strict: boolean; /** - * The filename for this script. - * @type {string} + * @type {function | void} + * @ignore */ - get filename(): string; + _onFinishCallback: Function | void; /** - * A promise that resolves when the script is ready. - * @type {Promise} + * @returns {string} */ - get ready(): Promise; + nextId(): string; /** - * Destroy the script execution context. - * @return {Promise} + * @type {number} */ - destroy(): Promise; + get length(): number; /** - * Run `source` JavaScript in given context. The script context execution - * context is preserved until the `context` object that points to it is - * garbage collected or there are no longer any references to it and its - * associated `Script` instance. - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} + * @param {string} name + * @param {TestFn} fn + * @param {boolean} only + * @returns {void} */ - runInContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + add(name: string, fn: TestFn, only: boolean): void; /** - * Run `source` JavaScript in new context. The script context is destroyed after - * execution. This is typically a "one off" isolated run. - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} + * @returns {Promise} */ - runInNewContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + run(): Promise; /** - * Run `source` JavaScript in this current context (`globalThis`). - * @param {ScriptOptions=} [options] - * @return {Promise} + * @param {(result: { total: number, success: number, fail: number }) => void} callback + * @returns {void} */ - runInThisContext(options?: ScriptOptions | undefined): Promise; + onFinish(callback: (result: { + total: number; + success: number; + fail: number; + }) => void): void; + } + /** + * @ignore + */ + export const GLOBAL_TEST_RUNNER: TestRunner; + export default test; + export type testWithProperties = { + (name: string, fn?: TestFn): void; + only(name: string, fn?: TestFn): void; + skip(name: string, fn?: TestFn): void; + }; + export type TestFn = (t: Test) => (void | Promise); +} +declare module "socket:test" { + export * from "socket:test/index"; + export default test; + import test from "socket:test/index"; +} +declare module "socket:timers/timer" { + export class Timer { + static from(...args: any[]): void; + constructor(create: any, destroy: any); + get id(): number; + init(...args: any[]): void; + close(): boolean; + [Symbol.toPrimitive](): number; #private; } + export class Timeout extends Timer { + constructor(); + } + export class Interval extends Timer { + constructor(); + } + export class Immediate extends Timeout { + } namespace _default { - export { createGlobalObject }; - export { compileFunction }; - export { createReference }; - export { getContextWindow }; - export { getContextWorker }; - export { getReference }; - export { getTransferables }; - export { putReference }; - export { Reference }; - export { removeReference }; - export { runInContext }; - export { runInNewContext }; - export { runInThisContext }; - export { Script }; - export { createContext }; - export { isContext }; + export { Timer }; + export { Immediate }; + export { Timeout }; + export { Interval }; } export default _default; - export type ScriptOptions = { - filename?: string; - context?: object; - }; - import { SharedWorker } from "socket:worker"; +} +declare module "socket:timers/promises" { + export function setTimeout(delay?: number, value?: any, options?: any): Promise; + export function setInterval(delay?: number, value?: any, options?: any): AsyncGenerator; + export function setImmediate(value?: any, options?: any): Promise; + namespace _default { + export { setImmediate }; + export { setInterval }; + export { setTimeout }; + } + export default _default; +} +declare module "socket:timers/scheduler" { + export function wait(delay: any, options?: any): Promise; + export function postTask(callback: any, options?: any): Promise; + namespace _default { + export { postTask }; + export { setImmediate as yield }; + export { wait }; + } + export default _default; + import { setImmediate } from "socket:timers/promises"; +} +declare module "socket:timers/index" { + export function setTimeout(callback: any, delay: any, ...args: any[]): void; + export function clearTimeout(timeout: any): void; + export function setInterval(callback: any, delay: any, ...args: any[]): void; + export function clearInterval(interval: any): void; + export function setImmediate(callback: any, ...args: any[]): void; + export function clearImmediate(immediate: any): void; + namespace _default { + export { promises }; + export { scheduler }; + export { setTimeout }; + export { clearTimeout }; + export { setInterval }; + export { clearInterval }; + export { setImmediate }; + export { clearImmediate }; + } + export default _default; + import promises from "socket:timers/promises"; + import scheduler from "socket:timers/scheduler"; +} +declare module "socket:timers" { + export * from "socket:timers/index"; + export default exports; + import * as exports from "socket:timers/index"; } declare module "socket:module" { export function isBuiltin(name: any): boolean; @@ -9128,6 +9825,120 @@ declare module "socket:module" { buffer: typeof buffer; console: import("socket:console").Console; constants: { + SIGHUP: number; + SIGINT: number; + SIGQUIT: number; + SIGILL: number; + SIGTRAP: number; + SIGABRT: number; + SIGIOT: number; + SIGBUS: number; + SIGFPE: number; + SIGKILL: number; + SIGUSR1: number; + SIGSEGV: number; + SIGUSR2: number; + SIGPIPE: number; + SIGALRM: number; + SIGTERM: number; + SIGCHLD: number; + SIGCONT: number; + SIGSTOP: number; + SIGTSTP: number; + SIGTTIN: number; + SIGTTOU: number; + SIGURG: number; + SIGXCPU: number; + SIGXFSZ: number; + SIGVTALRM: number; + SIGPROF: number; + SIGWINCH: number; + SIGIO: number; + SIGINFO: number; + SIGSYS: number; + E2BIG: number; + EACCES: number; + EADDRINUSE: number; + EADDRNOTAVAIL: number; + EAFNOSUPPORT: number; + EAGAIN: number; + EALREADY: number; + EBADF: number; + EBADMSG: number; + EBUSY: number; + ECANCELED: number; + ECHILD: number; + ECONNABORTED: number; + ECONNREFUSED: number; + ECONNRESET: number; + EDEADLK: number; + EDESTADDRREQ: number; + EDOM: number; + EDQUOT: number; + EEXIST: number; + EFAULT: number; + EFBIG: number; + EHOSTUNREACH: number; + EIDRM: number; + EILSEQ: number; + EINPROGRESS: number; + EINTR: number; + EINVAL: number; + EIO: number; + EISCONN: number; + EISDIR: number; + ELOOP: number; + EMFILE: number; + EMLINK: number; + EMSGSIZE: number; + EMULTIHOP: number; + ENAMETOOLONG: number; + ENETDOWN: number; + ENETRESET: number; + ENETUNREACH: number; + ENFILE: number; + ENOBUFS: number; + ENODATA: number; + ENODEV: number; + ENOENT: number; + ENOEXEC: number; + ENOLCK: number; + /** + * The parent module, if given. + * @type {Module?} + */ + ENOLINK: number; + ENOMEM: number; + ENOMSG: number; + ENOPROTOOPT: number; + ENOSPC: number; + ENOSR: number; + ENOSTR: number; + ENOSYS: number; + ENOTCONN: number; + ENOTDIR: number; + ENOTEMPTY: number; + ENOTSOCK: number; + ENOTSUP: number; + ENOTTY: number; + ENXIO: number; + EOPNOTSUPP: number; + EOVERFLOW: number; + EPERM: number; + EPIPE: number; + EPROTO: number; + EPROTONOSUPPORT: number; + EPROTOTYPE: number; + ERANGE: number; + EROFS: number; + ESPIPE: number; + ESRCH: number; + ESTALE: number; + ETIME: number; + ETIMEDOUT: number; + ETXTBSY: number; + EWOULDBLOCK: number; + EXDEV: number; WINDOW_ERROR: -1; WINDOW_NONE: 0; WINDOW_CREATING: 10; @@ -9456,6 +10267,120 @@ declare module "socket:module" { buffer: typeof buffer; console: import("socket:console").Console; constants: { + SIGHUP: number; + SIGINT: number; + SIGQUIT: number; + SIGILL: number; + SIGTRAP: number; + SIGABRT: number; + SIGIOT: number; + SIGBUS: number; + SIGFPE: number; + SIGKILL: number; + SIGUSR1: number; + SIGSEGV: number; + SIGUSR2: number; + SIGPIPE: number; + SIGALRM: number; + SIGTERM: number; + SIGCHLD: number; + SIGCONT: number; + SIGSTOP: number; + SIGTSTP: number; + SIGTTIN: number; + SIGTTOU: number; + SIGURG: number; + SIGXCPU: number; + SIGXFSZ: number; + SIGVTALRM: number; + SIGPROF: number; + SIGWINCH: number; + SIGIO: number; + SIGINFO: number; + SIGSYS: number; + E2BIG: number; + EACCES: number; + EADDRINUSE: number; + EADDRNOTAVAIL: number; + EAFNOSUPPORT: number; + EAGAIN: number; + EALREADY: number; + EBADF: number; + EBADMSG: number; + EBUSY: number; + ECANCELED: number; + ECHILD: number; + ECONNABORTED: number; + ECONNREFUSED: number; + ECONNRESET: number; + EDEADLK: number; + EDESTADDRREQ: number; + EDOM: number; + EDQUOT: number; + EEXIST: number; + EFAULT: number; + EFBIG: number; + EHOSTUNREACH: number; + EIDRM: number; + EILSEQ: number; + EINPROGRESS: number; + EINTR: number; + EINVAL: number; + EIO: number; + EISCONN: number; + EISDIR: number; + ELOOP: number; + EMFILE: number; + EMLINK: number; + EMSGSIZE: number; + EMULTIHOP: number; + ENAMETOOLONG: number; + ENETDOWN: number; + ENETRESET: number; + ENETUNREACH: number; + ENFILE: number; + ENOBUFS: number; + ENODATA: number; + ENODEV: number; + ENOENT: number; + ENOEXEC: number; + ENOLCK: number; + /** + * The parent module, if given. + * @type {Module?} + */ + ENOLINK: number; + ENOMEM: number; + ENOMSG: number; + ENOPROTOOPT: number; + ENOSPC: number; + ENOSR: number; + ENOSTR: number; + ENOSYS: number; + ENOTCONN: number; + ENOTDIR: number; + ENOTEMPTY: number; + ENOTSOCK: number; + ENOTSUP: number; + ENOTTY: number; + ENXIO: number; + EOPNOTSUPP: number; + EOVERFLOW: number; + EPERM: number; + EPIPE: number; + EPROTO: number; + EPROTONOSUPPORT: number; + EPROTOTYPE: number; + ERANGE: number; + EROFS: number; + ESPIPE: number; + ESRCH: number; + ESTALE: number; + ETIME: number; + ETIMEDOUT: number; + ETXTBSY: number; + EWOULDBLOCK: number; + EXDEV: number; WINDOW_ERROR: -1; WINDOW_NONE: 0; WINDOW_CREATING: 10; @@ -10417,6 +11342,31 @@ declare module "socket:stream-relay" { export default def; import def from "socket:stream-relay/index"; } +declare module "socket:service-worker/state" { + export const channel: BroadcastChannel; + export const state: any; + export default state; +} +declare module "socket:service-worker/instance" { + export function createServiceWorker(currentState?: any, options?: any): any; + export const SHARED_WORKER_URL: URL; + export const ServiceWorker: { + new (): ServiceWorker; + prototype: ServiceWorker; + }; + const _default: any; + export default _default; +} +declare module "socket:worker" { + export default Worker; + import { SharedWorker } from "socket:internal/shared-worker"; + import { ServiceWorker } from "socket:service-worker/instance"; + import { Worker } from "socket:worker_threads"; + export { SharedWorker, ServiceWorker, Worker }; +} +declare module "socket:child_process/worker" { + export {}; +} declare module "socket:internal/geolocation" { /** * Get the current position of the device. diff --git a/api/internal/events.js b/api/internal/events.js index 9aa394d74f..3804e19722 100644 --- a/api/internal/events.js +++ b/api/internal/events.js @@ -188,8 +188,54 @@ export class MenuItemEvent extends MessageEvent { } } +/** + * An event dispacted when the application receives an OS signal + */ +export class SignalEvent extends MessageEvent { + #code = 0 + #name = '' + #message = '' + + /** + * `SignalEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [options] + */ + constructor (type, options) { + super(type, options) + this.#code = options?.code ?? 0 + this.#name = type + this.#message = options?.message ?? '' + } + + /** + * The code of the signal. + * @type {import('../signal.js').signal} + */ + get code () { + return this.#code ?? 0 + } + + /** + * The name of the signal. + * @type {string} + */ + get name () { + return this.#name + } + + /** + * An optional message describing the signal + * @type {string} + */ + get message () { + return this.#message ?? '' + } +} export default { ApplicationURLEvent, MenuItemEvent, + SignalEvent, HotKeyEvent } diff --git a/api/internal/init.js b/api/internal/init.js index 660b5f0697..35d44e0617 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -497,7 +497,6 @@ if (typeof globalThis.XMLHttpRequest === 'function') { import hooks, { RuntimeInitEvent } from '../hooks.js' import { config } from '../application.js' import globals from './globals.js' - import '../console.js' class ConcurrentQueue extends EventTarget { @@ -641,11 +640,17 @@ hooks.onLoad(async () => { // async preload modules hooks.onReady(async () => { try { - // precache fs.constants + // precache 'fs.constants' and 'os.constants' await ipc.request('fs.constants', {}, { cache: true }) + await ipc.request('os.constants', {}, { cache: true }) await import('../diagnostics.js') + await import('../signal.js') await import('../fs/fds.js') - await import('../fs/constants.js') + await import('../constants.js') + const errors = await import('../errors.js') + // lazily install this + const errno = await import('../errno.js') + errors.ErrnoError.errno = errno } catch (err) { console.error(err.stack || err) } diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 4495dade7e..eeb641758a 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -14,6 +14,8 @@ import symbols from './symbols.js' import { ApplicationURLEvent, + MenuItemEvent, + SignalEvent, HotKeyEvent } from './events.js' @@ -181,6 +183,8 @@ export function init () { // events ApplicationURLEvent, + MenuItemEvent, + SignalEvent, HotKeyEvent, // file diff --git a/api/ipc.js b/api/ipc.js index 615d8b1d4a..d06976c29c 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -37,6 +37,7 @@ import { AbortError, InternalError, + ErrnoError, TimeoutError } from './errors.js' @@ -331,7 +332,9 @@ export function maybeMakeError (error, caller) { delete error.type - if (type in errors) { + if (code && ErrnoError.errno?.constants && -code in ErrnoError.errno.strings) { + err = new ErrnoError(-code) + } else if (type in errors) { err = new errors[type](error.message || '', error.code) } else { for (const E of Object.values(errors)) { diff --git a/api/os.js b/api/os.js index 43ea323bfb..e4efec5a2c 100644 --- a/api/os.js +++ b/api/os.js @@ -12,8 +12,11 @@ import ipc, { primordials } from './ipc.js' import { toProperCase } from './util.js' +import constants from './os/constants.js' import { HOME } from './path/well-known.js' +export { constants } + const UNKNOWN = 'unknown' // eslint-disable-next-line new-parens diff --git a/api/os/constants.js b/api/os/constants.js new file mode 100644 index 0000000000..0eb15c6c56 --- /dev/null +++ b/api/os/constants.js @@ -0,0 +1,842 @@ +import { sendSync } from '../ipc.js' + +const constants = sendSync('os.constants', {}, { cache: true })?.data || {} + +/** + * @typedef {number} errno + * @typedef {number} signal + */ + +/** + * A container for all known "errno" constant values. + * Unsupported values have a default value of `0`. + */ +export const errno = { + /** + * "Arg list too long" + * The number of bytes used for the argument and environment list of the + * new process exceeded the limit NCARGS (specified in ⟨sys/param.h⟩). + * @type {errno} + */ + E2BIG: constants.E2BIG ?? 0, + + /** + * "Permission denied" + * An attempt was made to access a file in a way forbidden by + * its file access permissions. + * @type {errno} + */ + EACCES: constants.EACCES ?? 0, + + /** + * "Address already in use" + * Only one usage of each address is normally permitted. + * @type {errno} + */ + EADDRINUSE: constants.EADDRINUSE ?? 0, + + /** + * "Cannot assign requested address" + * Normally results from an attempt to create a socket with an + * address not on this machine. + * @type {errno} + */ + EADDRNOTAVAIL: constants.EADDRNOTAVAIL ?? 0, + + /** + * "Address family not supported by protocol family" + * An address incompatible with the requested protocol was used. + * For example, you shouldn't necessarily expect to be able to use + * NS addresses with ARPA Internet protocols. + * @type {errno} + */ + EAFNOSUPPORT: constants.EAFNOSUPPORT ?? 0, + + /** + * "Resource temporarily unavailable" + * This is a temporary condition and later calls to the + * same routine may complete normally. + * @type {errno} + */ + EAGAIN: constants.EAGAIN ?? 0, + + /** + * "Operation already in progress" + * An operation was attempted on a non-blocking object that + * already had an operation in progress. + * @type {errno} + */ + EALREADY: constants.EALREADY ?? 0, + + /** + * "Bad file descriptor" + * A file descriptor argument was out of range, referred to no open file, + * or a read (write) request was made to a file that was only open + * for writing (reading). + * @type {errno} + */ + EBADF: constants.EBADF ?? 0, + + /** + * "Bad message" + * The message to be received is inapprorpiate for the operation + * being attempted. + * @type {errno} + */ + EBADMSG: constants.EBADMSG ?? 0, + + /** + * "Resource busy" + * An attempt to use a system resource which was in use at the time + * in a manner which would have conflicted with the request. + * @type {errno} + */ + EBUSY: constants.EBUSY ?? 0, + + /** + * "Operation canceled" + * The scheduled operation was canceled. + * @type {errno} + */ + ECANCELED: constants.ECANCELED ?? 0, + + /** + * "No child processes" + * A wait or waitpid function was executed by a process that had no existing + * or unwaited-for child processes. + * @type {errno} + */ + ECHILD: constants.ECHILD ?? 0, + + /** + * "Software caused connection abort" + * A connection abort was caused internal to your host machine. + * @type {errno} + */ + ECONNABORTED: constants.ECONNABORTED ?? 0, + + /** + * "Connection refused" + * No connection could be made because the target machine actively refused it. + * This usually results from trying to connect to a service that is inactive + * on the foreign host. + * @type {errno} + */ + ECONNREFUSED: constants.ECONNREFUSED ?? 0, + + /** + * "Connection reset by peer" + * A connection was forcibly closed by a peer. + * This normally results from a loss of the connection on the remote socket + * due to a timeout or a reboot. + * @type {errno} + */ + ECONNRESET: constants.ECONNRESET ?? 0, + + /** + * "Resource deadlock avoided" + * An attempt was made to lock a system resource that would have resulted in + * a deadlock situation. + * @type {errno} + */ + EDEADLK: constants.EDEADLK ?? 0, + + /** + * "Destination address required" + * A required address was omitted from an operation on a socket. + * @type {errno} + */ + EDESTADDRREQ: constants.EDESTADDRREQ ?? 0, + + /** + * "Numerical argument out of domain" + * A numerical input argument was outside the defined domain of the + * mathematical function. + * @type {errno} + */ + EDOM: constants.EDOM ?? 0, + + /** + * "Disc quota exceeded" + * A write to an ordinary file, the creation of a directory or symbolic link, + * or the creation of a directory entry failed because the user's quota of + * disk blocks was exhausted, or the allocation of an inode for a newly + * created file failed because the user's quota of inodes was exhausted. + * @type {errno} + */ + EDQUOT: constants.EDQUOT ?? 0, + + /** + * "File exists" + * An existing file was mentioned in an inappropriate context, for instance, + * as the new link name in a link function. + * @type {errno} + */ + EEXIST: constants.EEXIST ?? 0, + + /** + * "Bad address" + * The system detected an invalid address in attempting to use an + * argument of a call. + * @type {errno} + */ + EFAULT: constants.EFAULT ?? 0, + + /** + * "File too large" + * The size of a file exceeded the maximum. + * @type {errno} + */ + EFBIG: constants.EFBIG ?? 0, + + /** + * "No route to host" + * A socket operation was attempted to an unreachable host. + * @type {errno} + */ + EHOSTUNREACH: constants.EHOSTUNREACH ?? 0, + + /** + * "Identifier removed" + * An IPC identifier was removed while the current process was waiting on it. + * @type {errno} + */ + EIDRM: constants.EIDRM ?? 0, + + /** + * "Illegal byte sequence" + * While decoding a multibyte character the function came along an invalid + * or an incomplete sequence of bytes or the given wide character is invalid. + * @type {errno} + */ + EILSEQ: constants.EILSEQ ?? 0, + + /** + * "Operation now in progress" + * An operation that takes a long time to complete. + * @type {errno} + */ + EINPROGRESS: constants.EINPROGRESS ?? 0, + + /** + * "Interrupted function call" + * An asynchronous signal (such as SIGINT or SIGQUIT) was caught by the + * process during the execution of an interruptible function. If the signal + * handler performs a normal return, the interrupted function call will seem + * to have returned the error condition. + * @type {errno} + */ + EINTR: constants.EINTR ?? 0, + + /** + * "Invalid argument" + * Some invalid argument was supplied. + * (For example, specifying an undefined signal to a signal or kill function). + * @type {errno} + */ + EINVAL: constants.EINVAL ?? 0, + + /** + * "Input/output error" + * Some physical input or output error occurred. + * This error will not be reported until a subsequent operation on the same + * file descriptor and may be lost (over written) by any subsequent errors. + * @type {errno} + */ + EIO: constants.EIO ?? 0, + + /** + * "Socket is already connected" + * A connect or connectx request was made on an already connected socket; + * or, a sendto or sendmsg request on a connected socket specified a + * destination when already connected. + * @type {errno} + */ + EISCONN: constants.EISCONN ?? 0, + + /** + * "Is a directory" + * An attempt was made to open a directory with write mode specified. + * @type {errno} + */ + EISDIR: constants.EISDIR ?? 0, + + /** + * "Too many levels of symbolic links" + * A path name lookup involved more than 8 symbolic links. + * @type {errno} + */ + ELOOP: constants.ELOOP ?? 0, + + /** + * "Too many open files" + * @type {errno} + */ + EMFILE: constants.EMFILE ?? 0, + + /** + * "Too many links" + * Maximum allowable hard links to a single file + * has been exceeded (limit of 32767 hard links per file). + * @type {errno} + */ + EMLINK: constants.EMLINK ?? 0, + + /** + * "Message too long" + * A message sent on a socket was larger than the internal message + * buffer or some other network limit. + * @type {errno} + */ + EMSGSIZE: constants.EMSGSIZE ?? 0, + + /** + * "Reserved" + * This error is reserved for future use. + * @type {errno} + */ + EMULTIHOP: constants.EMULTIHOP ?? 0, + + /** + * "File name too long" + * A component of a path name exceeded 255 (MAXNAMELEN) characters, + * or an entire path name exceeded 1023 (MAXPATHLEN-1) characters. + * @type {errno} + */ + ENAMETOOLONG: constants.ENAMETOOLONG ?? 0, + + /** + * "Network is down" + * A socket operation encountered a dead network. + * @type {errno} + */ + ENETDOWN: constants.ENETDOWN ?? 0, + + /** + * "Network dropped connection on reset" + * The host you were connected to crashed and rebooted. + * @type {errno} + */ + ENETRESET: constants.ENETRESET ?? 0, + + /** + * "Network is unreachable" + * A socket operation was attempted to an unreachable network. + * @type {errno} + */ + ENETUNREACH: constants.ENETUNREACH ?? 0, + + /** + * "Too many open files in system" + * Maximum number of file descriptors allowable on the system has been reached + * and a requests for an open cannot be satisfied until at least one has + * been closed. + * @type {errno} + */ + ENFILE: constants.ENFILE ?? 0, + + /** + * "No buffer space available" + * An operation on a socket or pipe was not performed because the system + * lacked sufficient buffer space or because a queue was full. + * @type {errno} + */ + ENOBUFS: constants.ENOBUFS ?? 0, + + /** + * "No message available" + * No message was available to be received by the requested operation. + * @type {errno} + */ + ENODATA: constants.ENODATA ?? 0, + + /** + * "Operation not supported by device" + * An attempt was made to apply an inappropriate function to a device, + * for example, trying to read a write-only device such as a printer. + * @type {errno} + */ + ENODEV: constants.ENODEV ?? 0, + + /** + * "No such file or directory" + * A component of a specified pathname did not exist, + * or the pathname was an empty string. + * @type {errno} + */ + ENOENT: constants.ENOENT ?? 0, + + /** + * "Exec format error" + * A request was made to execute a file that, + * although it has the appropriate permissions, + * was not in the format required for an executable file. + * @type {errno} + */ + ENOEXEC: constants.ENOEXEC ?? 0, + + /** + * "No locks available" + * A system-imposed limit on the number of simultaneous + * file locks was reached. + * @type {errno} + */ + ENOLCK: constants.ENOLCK ?? 0, + + /** + * "Reserved" + * This error is reserved for future use. + * @type {errno} + */ + ENOLINK: constants.ENOLINK ?? 0, + + /** + * "Cannot allocate memory" + * The new process image required more memory than was allowed by the hardware + * or by system-imposed memory management constraints.A lack of swap space is + * normally temporary; however, a lack of core is not. + * Soft limits may be increased to their corresponding hard limits. + * @type {errno} + */ + ENOMEM: constants.ENOMEM ?? 0, + + /** + * "No message of desired type" + * An IPC message queue does not contain a message of the desired type, + * or a message catalog does not contain the requested message. + * @type {errno} + */ + ENOMSG: constants.ENOMSG ?? 0, + + /** + * "Protocol not available" + * A bad option or level was specified in a `getsockopt(2)` or + * `setsockopt(2)` call. + * @type {errno} + */ + ENOPROTOOPT: constants.ENOPROTOOPT ?? 0, + + /** + * "Device out of space" + * A write to an ordinary file, the creation of a directory or symbolic link, + * or the creation of a directory entry failed because no more disk blocks + * were available on the file system, or the allocation of an inode for a + * newly created file failed because no more inodes were available on + * the file system. + * @type {errno} + */ + ENOSPC: constants.ENOSPC ?? 0, + + /** + * "No STREAM resources" + * This error is reserved for future use. + * @type {errno} + */ + ENOSR: constants.ENOSR ?? 0, + + /** + * "Not a STREAM" + * This error is reserved for future use. + * @type {errno} + */ + ENOSTR: constants.ENOSTR ?? 0, + + /** + * "Function not implemented" + * Attempted a system call that is not available on this system. + * @type {errno} + */ + ENOSYS: constants.ENOSYS ?? 0, + + /** + * "Socket is not connected" + * An request to send or receive data was disallowed because the socket was + * not connected and (when sending on a datagram socket) no address was + * supplied. + * @type {errno} + */ + ENOTCONN: constants.ENOTCONN ?? 0, + + /** + * "Not a directory" + * A component of the specified pathname existed, but it was not a directory, + * when a directory was expected. + * @type {errno} + */ + ENOTDIR: constants.ENOTDIR ?? 0, + + /** + * "Directory not empty" + * A directory with entries other than ‘.’ and ‘..’ was supplied to a remove + * directory or rename call. + * @type {errno} + */ + ENOTEMPTY: constants.ENOTEMPTY ?? 0, + + /** + * "Socket operation on non-socket" + * Self-explanatory. + * @type {errno} + */ + ENOTSOCK: constants.ENOTSOCK ?? 0, + + /** + * "Not supported" + * The attempted operation is not supported for the type of object referenced. + * @type {errno} + */ + ENOTSUP: constants.ENOTSUP ?? 0, + + /** + * "Inappropriate ioctl for device" + * A control function (see `ioctl(2)`) was attempted for a file or special + * device for which the operation was inappropriate. + * @type {errno} + */ + ENOTTY: constants.ENOTTY ?? 0, + + /** + * "No such device or address" + * Input or output on a special file referred to a device that did not exist, + * or made a request beyond the limits of the device. + * This error may also occur when, for example, a tape drive is not online or + * no disk pack is loaded on a drive. + * @type {errno} + */ + ENXIO: constants.ENXIO ?? 0, + + /** + * "Operation not supported on socket" + * The attempted operation is not supported for the type of socket referenced; + * for example, trying to accept a connection on a datagram socket. + * @type {errno} + */ + EOPNOTSUPP: constants.EOPNOTSUPP ?? 0, + + /** + * "Value too large to be stored in data type" + * A numerical result of the function was too large to be stored + * in the caller provided space. + * @type {errno} + */ + EOVERFLOW: constants.EOVERFLOW ?? 0, + + /** + * "Operation not permitted" + * An attempt was made to perform an operation limited to processes with + * appropriate privileges or to the owner of a file or other resources. + * @type {errno} + */ + EPERM: constants.EPERM ?? 0, + + /** + * "Broken pipe" + * A write on a pipe, socket or FIFO for which there is no process to + * read the data. + * @type {errno} + */ + EPIPE: constants.EPIPE ?? 0, + + /** + * "Protocol error" + * Some protocol error occurred. + * This error is device-specific, but is generally not related to a + * hardware failure. + * @type {errno} + */ + EPROTO: constants.EPROTO ?? 0, + + /** + * "Protocol not supported" + * The protocol has not been configured into the system or + * no implementation for it exists. + * @type {errno} + */ + EPROTONOSUPPORT: constants.EPROTONOSUPPORT ?? 0, + + /** + * "Protocol wrong type for socket" + * A protocol was specified that does not support the semantics of the socket + * type requested. For example, you cannot use the ARPA Internet UDP protocol + * with type SOCK_STREAM. + * @type {errno} + */ + EPROTOTYPE: constants.EPROTOTYPE ?? 0, + + /** + * "Numerical result out of range" + * A numerical result of the function was too large to fit in the available + * space (perhaps exceeded precision). + * @type {errno} + */ + ERANGE: constants.ERANGE ?? 0, + + /** + * "Read-only file system" + * An attempt was made to modify a file or directory was made on a file + * system that was read-only at the time. + * @type {errno} + */ + EROFS: constants.EROFS ?? 0, + + /** + * "Illegal seek" + * An lseek function was issued on a socket, pipe or FIFO. + * @type {errno} + */ + ESPIPE: constants.ESPIPE ?? 0, + + /** + * "No such process" + * No process could be found corresponding to that specified + * by the given process ID. + * @type {errno} + */ + ESRCH: constants.ESRCH ?? 0, + + /** + * "Stale NFS file handle" + * An attempt was made to access an open file (on an NFS filesystem) + * which is now unavailable as referenced by the file descriptor. + * This may indicate the file was deleted on the NFS server or some other + * catastrophic event occurred. + * @type {errno} + */ + ESTALE: constants.ESTALE ?? 0, + + /** + * "STREAM ioctl() timeout" + * This error is reserved for future use. + * @type {errno} + */ + ETIME: constants.ETIME ?? 0, + + /** + * "Operation timed out" + * A connect, connectx or send request failed because the connected party did + * not properly respond after a period of time. + * (The timeout period is dependent on the communication protocol.) + * @type {errno} + */ + ETIMEDOUT: constants.ETIMEDOUT ?? 0, + + /** + * "Text file busy" + * The new process was a pure procedure (shared text) file which was open for + * writing by another process, or while the pure procedure file was being + * executed an open call requested write access. + * @type {errno} + */ + ETXTBSY: constants.ETXTBSY ?? 0, + + /** + * "Operation would block" + * (may be same value as EAGAIN) (POSIX.1-2001). + * @type {errno} + */ + EWOULDBLOCK: constants.EWOULDBLOCK ?? constants.EAGAIN ?? 0, + + /** + * "Improper link" + * A hard link to a file on another file system was attempted. + * @type {errno} + */ + EXDEV: constants.EXDEV ?? 0 +} + +/** + * A container for all known "signal" constant values. + * Unsupported values have a default value of `0`. + */ +export const signal = { + /** + * Terminal line hangup. + * @type {signal} + */ + SIGHUP: constants.SIGHUP ?? 0, + + /** + * Interrupt program. + * @type {signal} + */ + SIGINT: constants.SIGINT ?? 0, + + /** + * Quit program. + * @type {signal} + */ + SIGQUIT: constants.SIGQUIT ?? 0, + + /** + * Illegal instruction. + * @type {signal} + */ + SIGILL: constants.SIGILL ?? 0, + + /** + * Trace trap. + * @type {signal} + */ + SIGTRAP: constants.SIGTRAP ?? 0, + + /** + * Abort program. + * @type {signal} + */ + SIGABRT: constants.SIGABRT ?? 0, + + /** + * An alias to `SIGABRT` + * @type {signal} + */ + SIGIOT: constants.SIGIOT ?? constants.SIGABRT ?? 0, + + /** + * Bus error. + * @type {signal} + */ + SIGBUS: constants.SIGBUS ?? 0, + + /** + * Floating-point exception. + * @type {signal} + */ + SIGFPE: constants.SIGFPE ?? 0, + + /** + * Kill program. + * @type {signal} + */ + SIGKILL: constants.SIGKILL ?? 0, + + /** + * User defined signal 1. + * @type {signal} + */ + SIGUSR1: constants.SIGUSR1 ?? 0, + + /** + * Segmentation violation. + * @type {signal} + */ + SIGSEGV: constants.SIGSEGV ?? 0, + + /** + * User defined signal 2. + * @type {signal} + */ + SIGUSR2: constants.SIGUSR2 ?? 0, + + /** + * Write on a pipe with no reader. + * @type {signal} + */ + SIGPIPE: constants.SIGPIPE ?? 0, + + /** + * Real-time timer expired. + * @type {signal} + */ + SIGALRM: constants.SIGALRM ?? 0, + + /** + * Software termination signal. + * @type {signal} + */ + SIGTERM: constants.SIGTERM ?? 0, + + /** + * Child status has changed. + * @type {signal} + */ + SIGCHLD: constants.SIGCHLD ?? 0, + + /** + * Continue after stop. + * @type {signal} + */ + SIGCONT: constants.SIGCONT ?? 0, + + /** + * Stop signal (cannot be caught or ignored). + * @type {signal} + */ + SIGSTOP: constants.SIGSTOP ?? 0, + + /** + * Stop signal generated from keyboard. + * @type {signal} + */ + SIGTSTP: constants.SIGTSTP ?? 0, + + /** + * Background read attempted from control terminal. + * @type {signal} + */ + SIGTTIN: constants.SIGTTIN ?? 0, + + /** + * Background write attempted to control terminal. + * @type {signal} + */ + SIGTTOU: constants.SIGTTOU ?? 0, + + /** + * Urgent condition present on socket. + * @type {signal} + */ + SIGURG: constants.SIGURG ?? 0, + + /** + * CPU time limit exceeded (see `setrlimit(2)`) + * @type {signal} + */ + SIGXCPU: constants.SIGXCPU ?? 0, + + /** + * File size limit exceeded (see `setrlimit(2)`). + * @type {signal} + */ + SIGXFSZ: constants.SIGXFSZ ?? 0, + + /** + * Virtual time alarm (see `setitimer(2)`). + * @type {signal} + */ + SIGVTALRM: constants.SIGVTALRM ?? 0, + + /** + * Profiling timer alarm (see `setitimer(2)`). + * @type {signal} + */ + SIGPROF: constants.SIGPROF ?? 0, + + /** + * Window size change. + * @type {signal} + */ + SIGWINCH: constants.SIGWINCH ?? 0, + + /** + * I/O is possible on a descriptor (see `fcntl(2)`). + * @type {signal} + */ + SIGIO: constants.SIGIO ?? 0, + + /** + * Status request from keyboard. + * @type {signal} + */ + SIGINFO: constants.SIGINFO ?? 0, + + /** + * Non-existent system call invoked. + * @type {signal} + */ + SIGSYS: constants.SIGSYS ?? 0 +} + +export default { + errno, + signal +} diff --git a/api/path/well-known.js b/api/path/well-known.js index a1061a0050..4a95be6611 100644 --- a/api/path/well-known.js +++ b/api/path/well-known.js @@ -1,55 +1,55 @@ import ipc from '../ipc.js' -const paths = ipc.sendSync('os.paths') +const paths = ipc.sendSync('os.paths')?.data ?? {} /** * Well known path to the user's "Downloads" folder. * @type {?string} */ -export const DOWNLOADS = paths?.data?.downloads || null +export const DOWNLOADS = paths.downloads || null /** * Well known path to the user's "Documents" folder. * @type {?string} */ -export const DOCUMENTS = paths?.data?.documents || null +export const DOCUMENTS = paths.documents || null /** * Well known path to the user's "Pictures" folder. * @type {?string} */ -export const PICTURES = paths?.data?.pictures || null +export const PICTURES = paths.pictures || null /** * Well known path to the user's "Desktop" folder. * @type {?string} */ -export const DESKTOP = paths?.data?.desktop || null +export const DESKTOP = paths.desktop || null /** * Well known path to the user's "Videos" folder. * @type {?string} */ -export const VIDEOS = paths?.data?.videos || null +export const VIDEOS = paths.videos || null /** * Well known path to the user's "Music" folder. * @type {?string} */ -export const MUSIC = paths?.data?.music || null +export const MUSIC = paths.music || null /** * Well known path to the application's "resources" folder. * @type {?string} */ -export const RESOURCES = paths?.data?.resources || null +export const RESOURCES = paths.resources || null /** * Well known path to the application's "home" folder. * This may be the user's HOME directory or the application container sandbox. * @type {?string} */ -export const HOME = paths?.data?.home || null +export const HOME = paths.home || null export default { DOWNLOADS, diff --git a/api/process.js b/api/process.js index fac85a9abf..9da84954e3 100644 --- a/api/process.js +++ b/api/process.js @@ -8,6 +8,7 @@ */ import { primordials, send } from './ipc.js' import { EventEmitter } from './events.js' +import signal from './signal.js' import os from './os.js' let didEmitExitEvent = false @@ -124,6 +125,24 @@ if (!isNode) { EventEmitter.call(process) } +signal.channel.addEventListener('message', (event) => { + if (event.data.signal) { + const code = event.data.signal + const name = signal.getName(code) + const message = signal.getMessage(code) + process.emit(name, name, code, message) + } +}) + +globalThis.addEventListener('signal', (event) => { + if (event.detail.signal) { + const code = event.detail.signal + const name = signal.getName(code) + const message = signal.getMessage(code) + process.emit(name, name, code, message) + } +}) + export default process /** diff --git a/api/service-worker/container.js b/api/service-worker/container.js index db42a27f51..72d7e3e45f 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -21,7 +21,7 @@ class ServiceWorkerContainerInternalState { currentWindow = null sharedWorker = null controller = null - channel = new BroadcastChannel('ServiceWorkerContainer') + channel = new BroadcastChannel('socket.runtime.ServiceWorkerContainer') ready = new InvertedPromise() // level 1 events diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 1407e79417..6d1a133768 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -2,7 +2,7 @@ import application from '../application.js' import ipc from '../ipc.js' -export const channel = new BroadcastChannel('serviceWorker.state') +export const channel = new BroadcastChannel('socket.runtime.serviceWorker.state') const descriptors = { channel: { diff --git a/api/signal.js b/api/signal.js new file mode 100644 index 0000000000..2535ab1612 --- /dev/null +++ b/api/signal.js @@ -0,0 +1,169 @@ +import { signal as constants } from './os/constants.js' +import { SignalEvent } from './internal/events.js' +import os from './os.js' + +/** + * @typedef {import('./os/constants.js').signal} signal + */ + +export { constants } + +export const channel = new BroadcastChannel('socket.runtime.signal') + +export const SIGHUP = constants.SIGHUP +export const SIGINT = constants.SIGINT +export const SIGQUIT = constants.SIGQUIT +export const SIGILL = constants.SIGILL +export const SIGTRAP = constants.SIGTRAP +export const SIGABRT = constants.SIGABRT +export const SIGIOT = constants.SIGIOT +export const SIGBUS = constants.SIGBUS +export const SIGFPE = constants.SIGFPE +export const SIGKILL = constants.SIGKILL +export const SIGUSR1 = constants.SIGUSR1 +export const SIGSEGV = constants.SIGSEGV +export const SIGUSR2 = constants.SIGUSR2 +export const SIGPIPE = constants.SIGPIPE +export const SIGALRM = constants.SIGALRM +export const SIGTERM = constants.SIGTERM +export const SIGCHLD = constants.SIGCHLD +export const SIGCONT = constants.SIGCONT +export const SIGSTOP = constants.SIGSTOP +export const SIGTSTP = constants.SIGTSTP +export const SIGTTIN = constants.SIGTTIN +export const SIGTTOU = constants.SIGTTOU +export const SIGURG = constants.SIGURG +export const SIGXCPU = constants.SIGXCPU +export const SIGXFSZ = constants.SIGXFSZ +export const SIGVTALRM = constants.SIGVTALRM +export const SIGPROF = constants.SIGPROF +export const SIGWINCH = constants.SIGWINCH +export const SIGIO = constants.SIGIO +export const SIGINFO = constants.SIGINFO +export const SIGSYS = constants.SIGSYS + +export const strings = { + [SIGHUP]: 'Terminal line hangup', + [SIGINT]: 'Interrupt program', + [SIGQUIT]: 'Quit program', + [SIGILL]: 'Illegal instruction', + [SIGTRAP]: 'Trace trap', + [SIGABRT]: 'Abort program', + [SIGIOT]: 'Abort program', + [SIGBUS]: 'Bus error', + [SIGFPE]: 'Floating-point exception', + [SIGKILL]: 'Kill program', + [SIGUSR1]: 'User defined signal 1', + [SIGSEGV]: 'Segmentation violation', + [SIGUSR2]: 'User defined signal 2', + [SIGPIPE]: 'Write on a pipe with no reader', + [SIGALRM]: 'Real-time timer expired', + [SIGTERM]: 'Software termination signal', + [SIGCHLD]: 'Child status has changed', + [SIGCONT]: 'Continue after stop', + [SIGSTOP]: 'Stop signal', + [SIGTSTP]: 'Stop signal generated from keyboard', + [SIGTTIN]: ' Background read attempted from control terminal', + [SIGTTOU]: 'Background write attempted to control terminal', + [SIGURG]: 'Urgent condition present on socket', + [SIGXCPU]: 'Urgent condition present on socket', + [SIGXFSZ]: 'File size limit exceeded', + [SIGVTALRM]: 'Virtual time alarm', + [SIGPROF]: 'Profiling timer alarm', + [SIGWINCH]: 'Window size change', + [SIGIO]: 'I/O is possible on a descriptor', + [SIGINFO]: 'Status request from keyboard', + [SIGSYS]: 'Non-existent system call invoked' +} + +/** + * Converts an `signal` code to its corresponding string message. + * @param {import('./os/constants.js').signal} {code} + * @return {string} + */ +export function toString (code) { + return strings[code] ?? '' +} + +/** + * Gets the code for a given 'signal' name. + * @param {string|number} name + * @return {signal} + */ +export function getCode (name) { + if (typeof name !== 'string') { + name = name.toString() + } + + name = name.toUpperCase() + for (const key in constants) { + if (name === key) { + return constants[key] + } + } + + return 0 +} + +/** + * Gets the name for a given 'signal' code + * @return {string} + * @param {string|number} code + */ +export function getName (code) { + if (typeof code === 'string') { + code = getCode(code) + } + + for (const key in constants) { + const value = constants[key] + if (value === code) { + return key + } + } + + return '' +} + +/** + * Gets the message for a 'signal' code. + * @param {number|string} code + * @return {string} + */ +export function getMessage (code) { + if (typeof code === 'string') { + code = getCode(code) + } + + return toString(code) +} + +if (!/android|ios/i.test(os.platform())) { + channel.addEventListener('message', (event) => { + onSignal(event.data.signal) + }) + + globalThis.addEventListener('signal', (event) => { + onSignal(event.detail.signal) + channel.postMessage(event.detail) + }) +} + +function onSignal (code) { + const name = getName(code) + const message = getMessage(code) + globalThis.dispatchEvent(new SignalEvent(name, { + code, + message + })) +} + +export default { + constants, + channel, + strings, + toString, + getName, + getCode, + getMessage +} diff --git a/api/window.js b/api/window.js index 252e3ca32b..2867d1fa99 100644 --- a/api/window.js +++ b/api/window.js @@ -47,7 +47,7 @@ export class ApplicationWindow { this.#id = options?.id this.#index = index this.#options = options - this.#channel = new BroadcastChannel(`window.${this.#id}`) + this.#channel = new BroadcastChannel(`socket.runtime.window.${this.#id}`) } #updateOptions (response) { diff --git a/api/window/hotkey.js b/api/window/hotkey.js index ec3a33f2f6..21e462787f 100644 --- a/api/window/hotkey.js +++ b/api/window/hotkey.js @@ -34,7 +34,7 @@ export class Bindings extends EventTarget { * @ignore * @type {BroadcastChannel} */ - #channel = new BroadcastChannel('window.hotkey.bindings') + #channel = new BroadcastChannel('socket.runtime.window.hotkey.bindings') /** * The source `EventTarget` to listen for 'hotkey' events on diff --git a/src/core/child_process.cc b/src/core/child_process.cc index d58b448843..f56778e339 100644 --- a/src/core/child_process.cc +++ b/src/core/child_process.cc @@ -1,12 +1,17 @@ #include "core.hh" +#if SSC_PLATFORM_DESKTOP namespace SSC { - // - // TODO(@heapwolf): clean up all threads on process exit - // - void Core::ChildProcess::kill (const String seq, uint64_t id, int signal, Module::Callback cb) { + void Core::ChildProcess::kill ( + const String seq, + uint64_t id, + int signal, + Module::Callback cb + ) { this->core->dispatchEventLoop([=, this] { - if (!this->processes.contains(id)) { + Lock lock(mutex); + + if (!this->handles.contains(id)) { auto json = JSON::Object::Entries { {"err", JSON::Object::Entries { {"id", std::to_string(id)}, @@ -18,23 +23,29 @@ namespace SSC { return cb(seq, json, Post{}); } - auto p = this->processes.at(id); + auto process = this->handles.at(id); #ifdef _WIN32 - p->kill(); + process->kill(); #else - ::kill(p->id, signal); + ::kill(process->id, signal); #endif cb(seq, JSON::Object{}, Post{}); }); } - void Core::ChildProcess::spawn (const String seq, uint64_t id, const String cwd, Vector args, Module::Callback cb) { + void Core::ChildProcess::spawn ( + const String seq, + uint64_t id, + const Vector args, + const SpawnOptions options, + Module::Callback cb + ) { this->core->dispatchEventLoop([=, this] { - auto command = args.at(0); + Lock lock(mutex); - if (this->processes.contains(id)) { + if (this->handles.contains(id)) { auto json = JSON::Object::Entries { {"err", JSON::Object::Entries { {"id", std::to_string(id)}, @@ -45,101 +56,126 @@ namespace SSC { return cb(seq, json, Post{}); } + Process* process = nullptr; + + const auto command = args.size() > 0 ? args.at(0) : String(""); const auto argv = join(args.size() > 1 ? Vector{ args.begin() + 1, args.end() } : Vector{}, " "); - Process* p = new Process( + const auto onStdout = [=](const String& output) { + if (!options.stdout || output.size() == 0) { + return; + } + + const auto bytes = new char[output.size()]{0}; + const auto headers = Headers {{ + {"content-type" ,"application/octet-stream"}, + {"content-length", (int) output.size()} + }}; + + memcpy(bytes, output.c_str(), output.size()); + + Post post; + post.id = rand64(); + post.body = bytes; + post.length = (int) output.size(); + post.headers = headers.str(); + + const auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"source", "stdout"} + }} + }; + + cb("-1", json, post); + }; + + const auto onStderr = [=](const String& output) { + if (!options.stderr || output.size() == 0) { + return; + } + + const auto bytes = new char[output.size()]{0}; + const auto headers = Headers {{ + {"content-type" ,"application/octet-stream"}, + {"content-length", (int) output.size()} + }}; + + memcpy(bytes, output.c_str(), output.size()); + + Post post; + post.id = rand64(); + post.body = bytes; + post.length = (int) output.size(); + post.headers = headers.str(); + + const auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"source", "stderr"} + }} + }; + + cb("-1", json, post); + }; + + const auto onExit = [=, this](const String& output) { + const auto code = output.size() > 0 ? std::stoi(output) : 0; + const auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"status", "exit"}, + {"code", code} + }} + }; + + cb("-1", json, Post{}); + + this->core->dispatchEventLoop([=, this] { + Lock lock(mutex); + if (this->handles.contains(id)) { + auto process = this->handles.at(id); + this->handles.erase(id); + + const auto code = process->wait(); + + delete process; + + this->core->dispatchEventLoop([=, this] { + const auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"status", "close"}, + {"code", code} + }} + }; + cb("-1", json, Post{}); + }); + } + }); + }; + + process = new Process( command, argv, - cwd, - [=](SSC::String const &out) { - Post post; - - auto bytes = new char[out.size()]{0}; - memcpy(bytes, out.c_str(), out.size()); - - auto headers = Headers {{ - {"content-type" ,"application/octet-stream"}, - {"content-length", (int) out.size()} - }}; - - post.id = rand64(); - post.body = bytes; - post.length = (int) out.size(); - post.headers = headers.str(); - - auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"source", "stdout" } - }} - }; - - cb("-1", json, post); - }, - [=](SSC::String const &out) { - Post post; - - auto bytes = new char[out.size()]{0}; - memcpy(bytes, out.c_str(), out.size()); - - auto headers = Headers {{ - {"content-type" ,"application/octet-stream"}, - {"content-length", (int) out.size()} - }}; - - post.id = rand64(); - post.body = bytes; - post.length = (int) out.size(); - post.headers = headers.str(); - - auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"source", "stderr" } - }} - }; - - cb("-1", json, post); - }, - [=, this](SSC::String const &code) { - this->processes.erase(id); - - auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"status", "exit"}, - {"code", std::stoi(code)} - }} - }; - - cb("-1", json, Post{}); - - this->core->dispatchEventLoop([=, this] { - auto code = p->wait(); - delete p; - - auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"status", "close"}, - {"code", code} - }} - }; - - cb("-1", json, Post{}); - }); - } + options.cwd, + onStdout, + onStderr, + onExit, + options.stdin ); - this->processes.insert_or_assign(id, p); + do { + Lock lock(mutex); + this->handles.insert_or_assign(id, process); + } while (0); - auto pid = p->open(); - - auto json = JSON::Object::Entries { + const auto pid = process->open(); + const auto json = JSON::Object::Entries { {"source", "child_process.spawn"}, {"data", JSON::Object::Entries { {"id", std::to_string(id)}, @@ -150,4 +186,77 @@ namespace SSC { cb(seq, json, Post{}); }); } + + void Core::ChildProcess::write ( + const String seq, + uint64_t id, + char* buffer, + size_t size, + Module::Callback cb + ) { + this->core->dispatchEventLoop([=, this] { + Lock lock(mutex); + + if (!this->handles.contains(id)) { + auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "NotFoundError"}, + {"message", "A process with that id does not exist"} + }} + }; + + return cb(seq, json, Post{}); + } + + bool didWrite = false; + + auto process = this->handles.at(id); + + if (!process->open_stdin) { + auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "NotSupportedError"}, + {"message", "Child process stdin is not opened"} + }} + }; + + cb(seq, json, Post{}); + return; + } + + try { + didWrite = process->write(buffer, size); + } catch (std::exception& e) { + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "InternalError"}, + {"message", e.what()} + }} + }; + + cb(seq, json, Post{}); + return; + } + + if (!didWrite) { + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", process->lastWriteStatus != 0 ? "ErrnoError" : "InternalError"}, + {"message", process->lastWriteStatus != 0 ? strerror(process->lastWriteStatus) : "Failed to write to child process"} + }} + }; + + cb(seq, json, Post{}); + return; + } + + cb(seq, JSON::Object{}, Post{}); + return; + }); + } } +#endif diff --git a/src/core/core.cc b/src/core/core.cc index bccbf77783..f725c71894 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -20,152 +20,11 @@ namespace SSC { return r; } - void msleep (uint64_t ms) { std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } - Headers::Header::Header (const Header& header) { - this->key = header.key; - this->value = header.value; - } - - Headers::Header::Header (const String& key, const Value& value) { - this->key = trim(key); - this->value = trim(value.str()); - } - - Headers::Headers (const String& source) { - for (const auto& entry : split(source, '\n')) { - const auto tuple = split(entry, ':'); - if (tuple.size() == 2) { - set(trim(tuple.front()), trim(tuple.back())); - } - } - } - - Headers::Headers (const Headers& headers) { - this->entries = headers.entries; - } - - Headers::Headers (const Vector>& entries) { - for (const auto& entry : entries) { - for (const auto& pair : entry) { - this->entries.push_back(Header { pair.first, pair.second }); - } - } - } - - Headers::Headers (const Entries& entries) { - for (const auto& entry : entries) { - this->entries.push_back(entry); - } - } - - void Headers::set (const String& key, const String& value) { - set(Header{ key, value }); - } - - void Headers::set (const Header& header) { - for (auto& entry : entries) { - if (header.key == entry.key) { - entry.value = header.value; - return; - } - } - - entries.push_back(header); - } - - bool Headers::has (const String& name) const { - for (const auto& header : entries) { - if (header.key == name) { - return true; - } - } - - return false; - } - - const Headers::Header& Headers::get (const String& name) const { - static const auto empty = Header(); - - for (const auto& header : entries) { - if (header.key == name) { - return header; - } - } - - return empty; - } - - size_t Headers::size () const { - return this->entries.size(); - } - - String Headers::str () const { - StringStream headers; - auto count = this->size(); - for (const auto& entry : this->entries) { - headers << entry.key << ": " << entry.value.str();; - if (--count > 0) { - headers << "\n"; - } - } - return headers.str(); - } - - Headers::Value::Value (const String& value) { - this->string = trim(value); - } - - Headers::Value::Value (const char* value) { - this->string = value; - } - - Headers::Value::Value (const Value& value) { - this->string = value.string; - } - - Headers::Value::Value (bool value) { - this->string = value ? "true" : "false"; - } - - Headers::Value::Value (int value) { - this->string = std::to_string(value); - } - - Headers::Value::Value (float value) { - this->string = std::to_string(value); - } - - Headers::Value::Value (int64_t value) { - this->string = std::to_string(value); - } - - Headers::Value::Value (uint64_t value) { - this->string = std::to_string(value); - } - - Headers::Value::Value (double_t value) { - this->string = std::to_string(value); - } - -#if defined(__APPLE__) - Headers::Value::Value (ssize_t value) { - this->string = std::to_string(value); - } -#endif - - const String& Headers::Value::str () const { - return this->string; - } - - const char * Headers::Value::c_str() const { - return this->str().c_str(); - } - Post Core::getPost (uint64_t id) { Lock lock(postsMutex); if (posts->find(id) == posts->end()) return Post{}; @@ -284,666 +143,6 @@ namespace SSC { } } - void Core::OS::cpus ( - const String seq, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - #if defined(__ANDROID__) - { - auto json = JSON::Object::Entries { - {"source", "os.cpus"}, - {"data", JSON::Array::Entries {}} - }; - - cb(seq, json, Post{}); - return; - } - #endif - - uv_cpu_info_t* infos = nullptr; - int count = 0; - int status = uv_cpu_info(&infos, &count); - - if (status != 0) { - auto json = JSON::Object::Entries { - {"source", "os.cpus"}, - {"err", JSON::Object::Entries { - {"message", uv_strerror(status)} - }} - }; - - cb(seq, json, Post{}); - return; - } - - JSON::Array::Entries entries(count); - for (int i = 0; i < count; ++i) { - auto info = infos[i]; - entries[i] = JSON::Object::Entries { - {"model", info.model}, - {"speed", info.speed}, - {"times", JSON::Object::Entries { - {"user", info.cpu_times.user}, - {"nice", info.cpu_times.nice}, - {"sys", info.cpu_times.sys}, - {"idle", info.cpu_times.idle}, - {"irq", info.cpu_times.irq} - }} - }; - } - - auto json = JSON::Object::Entries { - {"source", "os.cpus"}, - {"data", entries} - }; - - uv_free_cpu_info(infos, count); - cb(seq, json, Post{}); - }); - } - - void Core::OS::networkInterfaces ( - const String seq, - Module::Callback cb - ) const { - uv_interface_address_t *infos = nullptr; - StringStream value; - StringStream v4; - StringStream v6; - int count = 0; - - int status = uv_interface_addresses(&infos, &count); - - if (status != 0) { - auto json = JSON::Object(JSON::Object::Entries { - {"source", "os.networkInterfaces"}, - {"err", JSON::Object::Entries { - {"type", "InternalError"}, - {"message", - String("Unable to get network interfaces: ") + String(uv_strerror(status)) - } - }} - }); - - return cb(seq, json, Post{}); - } - - JSON::Object::Entries ipv4; - JSON::Object::Entries ipv6; - JSON::Object::Entries data; - - for (int i = 0; i < count; ++i) { - uv_interface_address_t info = infos[i]; - struct sockaddr_in *addr = (struct sockaddr_in*) &info.address.address4; - char mac[18] = {0}; - snprintf(mac, 18, "%02x:%02x:%02x:%02x:%02x:%02x", - (unsigned char) info.phys_addr[0], - (unsigned char) info.phys_addr[1], - (unsigned char) info.phys_addr[2], - (unsigned char) info.phys_addr[3], - (unsigned char) info.phys_addr[4], - (unsigned char) info.phys_addr[5] - ); - - if (addr->sin_family == AF_INET) { - JSON::Object::Entries entries; - entries["internal"] = info.is_internal == 0 ? "false" : "true"; - entries["address"] = addrToIPv4(addr); - entries["mac"] = String(mac, 17); - ipv4[String(info.name)] = entries; - } - - if (addr->sin_family == AF_INET6) { - JSON::Object::Entries entries; - entries["internal"] = info.is_internal == 0 ? "false" : "true"; - entries["address"] = addrToIPv6((struct sockaddr_in6*) addr); - entries["mac"] = String(mac, 17); - ipv6[String(info.name)] = entries; - } - } - - uv_free_interface_addresses(infos, count); - - data["ipv4"] = ipv4; - data["ipv6"] = ipv6; - - auto json = JSON::Object::Entries { - {"source", "os.networkInterfaces"}, - {"data", data} - }; - - cb(seq, json, Post{}); - } - - void Core::OS::rusage ( - const String seq, - Module::Callback cb - ) { - uv_rusage_t usage; - auto status = uv_getrusage(&usage); - - if (status != 0) { - auto json = JSON::Object::Entries { - {"source", "os.rusage"}, - {"err", JSON::Object::Entries { - {"message", uv_strerror(status)} - }} - }; - - cb(seq, json, Post{}); - return; - } - - auto json = JSON::Object::Entries { - {"source", "os.rusage"}, - {"data", JSON::Object::Entries { - {"ru_maxrss", usage.ru_maxrss} - }} - }; - - cb(seq, json, Post{}); - } - - void Core::OS::uname ( - const String seq, - Module::Callback cb - ) { - uv_utsname_t uname; - auto status = uv_os_uname(&uname); - - if (status != 0) { - auto json = JSON::Object::Entries { - {"source", "os.uname"}, - {"err", JSON::Object::Entries { - {"message", uv_strerror(status)} - }} - }; - - cb(seq, json, Post{}); - return; - } - - auto json = JSON::Object::Entries { - {"source", "os.uname"}, - {"data", JSON::Object::Entries { - {"sysname", uname.sysname}, - {"release", uname.release}, - {"version", uname.version}, - {"machine", uname.machine} - }} - }; - - cb(seq, json, Post{}); - } - - void Core::OS::uptime ( - const String seq, - Module::Callback cb - ) { - double uptime; - auto status = uv_uptime(&uptime); - - if (status != 0) { - auto json = JSON::Object::Entries { - {"source", "os.uptime"}, - {"err", JSON::Object::Entries { - {"message", uv_strerror(status)} - }} - }; - - cb(seq, json, Post{}); - return; - } - - auto json = JSON::Object::Entries { - {"source", "os.uptime"}, - {"data", uptime * 1000} // in milliseconds - }; - - cb(seq, json, Post{}); - } - - void Core::OS::hrtime ( - const String seq, - Module::Callback cb - ) { - auto hrtime = uv_hrtime(); - auto bytes = toBytes(hrtime); - auto size = bytes.size(); - auto post = Post {}; - auto body = new char[size]{0}; - auto json = JSON::Object {}; - post.body = body; - post.length = size; - memcpy(body, bytes.data(), size); - cb(seq, json, post); - } - - void Core::OS::availableMemory ( - const String seq, - Module::Callback cb - ) { - auto memory = uv_get_available_memory(); - auto bytes = toBytes(memory); - auto size = bytes.size(); - auto post = Post {}; - auto body = new char[size]{0}; - auto json = JSON::Object {}; - post.body = body; - post.length = size; - memcpy(body, bytes.data(), size); - cb(seq, json, post); - } - - void Core::OS::bufferSize ( - const String seq, - uint64_t peerId, - size_t size, - int buffer, - Module::Callback cb - ) { - if (buffer == 0) { - buffer = Core::OS::SEND_BUFFER; - } else if (buffer == 1) { - buffer = Core::OS::RECV_BUFFER; - } - - this->core->dispatchEventLoop([=, this]() { - auto peer = this->core->getPeer(peerId); - - if (peer == nullptr) { - auto json = JSON::Object::Entries { - {"source", "bufferSize"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"code", "NOT_FOUND_ERR"}, - {"type", "NotFoundError"}, - {"message", "No peer with specified id"} - }} - }; - - cb(seq, json, Post{}); - return; - } - - Lock lock(peer->mutex); - auto handle = (uv_handle_t*) &peer->handle; - auto err = buffer == RECV_BUFFER - ? uv_recv_buffer_size(handle, (int *) &size) - : uv_send_buffer_size(handle, (int *) &size); - - if (err < 0) { - auto json = JSON::Object::Entries { - {"source", "bufferSize"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"code", "NOT_FOUND_ERR"}, - {"type", "NotFoundError"}, - {"message", String(uv_strerror(err))} - }} - }; - - cb(seq, json, Post{}); - return; - } - - auto json = JSON::Object::Entries { - {"source", "bufferSize"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"size", (int) size} - }} - }; - - cb(seq, json, Post{}); - }); - } - - void Core::Platform::event ( - const String seq, - const String event, - const String data, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - // init page - if (event == "domcontentloaded") { - Lock lock(this->core->fs.mutex); - - for (auto const &tuple : this->core->fs.descriptors) { - auto desc = tuple.second; - if (desc != nullptr) { - desc->stale = true; - } else { - this->core->fs.descriptors.erase(tuple.first); - } - } - - #if !defined(__ANDROID__) - for (auto const &tuple : this->core->fs.watchers) { - auto watcher = tuple.second; - if (watcher != nullptr) { - watcher->stop(); - delete watcher; - } - } - - this->core->fs.watchers.clear(); - #endif - } - - auto json = JSON::Object::Entries { - {"source", "platform.event"}, - {"data", JSON::Object::Entries{}} - }; - - cb(seq, json, Post{}); - }); - } - - void Core::Platform::notify ( - const String seq, - const String title, - const String body, - Module::Callback cb - ) { -#if defined(__APPLE__) - auto center = [UNUserNotificationCenter currentNotificationCenter]; - auto content = [[UNMutableNotificationContent alloc] init]; - content.body = [NSString stringWithUTF8String: body.c_str()]; - content.title = [NSString stringWithUTF8String: title.c_str()]; - content.sound = [UNNotificationSound defaultSound]; - - auto trigger = [UNTimeIntervalNotificationTrigger - triggerWithTimeInterval: 1.0f - repeats: NO - ]; - - auto request = [UNNotificationRequest - requestWithIdentifier: @"LocalNotification" - content: content - trigger: trigger - ]; - - auto options = ( - UNAuthorizationOptionAlert | - UNAuthorizationOptionBadge | - UNAuthorizationOptionSound - ); - - [center requestAuthorizationWithOptions: options - completionHandler: ^(BOOL granted, NSError* error) - { - #if !__has_feature(objc_arc) - [content release]; - [trigger release]; - #endif - - if (granted) { - auto json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"data", JSON::Object::Entries {}} - }; - - cb(seq, json, Post{}); - } else if (error) { - [center addNotificationRequest: request - withCompletionHandler: ^(NSError* error) - { - auto json = JSON::Object {}; - - #if !__has_feature(objc_arc) - [request release]; - #endif - - if (error) { - json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"err", JSON::Object::Entries { - {"message", [error.debugDescription UTF8String]} - }} - }; - - debug("Unable to create notification: %@", error.debugDescription); - } else { - json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"data", JSON::Object::Entries {}} - }; - } - - cb(seq, json, Post{}); - }]; - } else { - auto json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"err", JSON::Object::Entries { - {"message", "Failed to create notification"} - }} - }; - - cb(seq, json, Post{}); - } - - if (!error || granted) { - #if !__has_feature(objc_arc) - [request release]; - #endif - } - }]; -#endif - } - - void Core::Platform::openExternal ( - const String seq, - const String value, - Module::Callback cb - ) { -#if defined(__APPLE__) - auto string = [NSString stringWithUTF8String: value.c_str()]; - auto url = [NSURL URLWithString: string]; - - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - auto app = [UIApplication sharedApplication]; - [app openURL: url options: @{} completionHandler: ^(BOOL success) { - auto json = JSON::Object {}; - - if (!success) { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", "Failed to open external URL"} - }} - }; - } else { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"data", JSON::Object::Entries {}} - }; - } - - cb(seq, json, Post{}); - }]; - #else - auto workspace = [NSWorkspace sharedWorkspace]; - auto configuration = [NSWorkspaceOpenConfiguration configuration]; - [workspace openURL: url - configuration: configuration - completionHandler: ^(NSRunningApplication *app, NSError *error) - { - auto json = JSON::Object {}; - if (error) { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", [error.debugDescription UTF8String]} - }} - }; - } else { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"data", JSON::Object::Entries {}} - }; - } - - cb(seq, json, Post{}); - }]; - #endif -#elif defined(__linux__) && !defined(__ANDROID__) - auto list = gtk_window_list_toplevels(); - auto json = JSON::Object {}; - - // initial state is a failure - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", "Failed to open external URL"} - }} - }; - - if (list != nullptr) { - for (auto entry = list; entry != nullptr; entry = entry->next) { - auto window = GTK_WINDOW(entry->data); - - if (window != nullptr && gtk_window_is_active(window)) { - auto err = (GError*) nullptr; - auto uri = value.c_str(); - auto ts = GDK_CURRENT_TIME; - - /** - * GTK may print a error in the terminal that looks like: - * - * libva error: vaGetDriverNameByIndex() failed with unknown libva error, driver_name = (null) - * - * It doesn't prevent the URI from being opened. - * See https://github.com/intel/media-driver/issues/1349 for more info - */ - auto success = gtk_show_uri_on_window(window, uri, ts, &err); - - if (success) { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"data", JSON::Object::Entries {}} - }; - } else if (err != nullptr) { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", err->message} - }} - }; - } - - break; - } - } - - g_list_free(list); - } - - cb(seq, json, Post{}); -#elif defined(_WIN32) - auto uri = value.c_str(); - ShellExecute(nullptr, "Open", uri, nullptr, nullptr, SW_SHOWNORMAL); - // TODO how to detect success here. do we care? - cb(seq, JSON::Object{}, Post{}); -#endif - } - - void Core::DNS::lookup ( - const String seq, - LookupOptions options, - Core::Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - auto ctx = new Core::Module::RequestContext(seq, cb); - auto loop = this->core->getEventLoop(); - - struct addrinfo hints = {0}; - - if (options.family == 6) { - hints.ai_family = AF_INET6; - } else if (options.family == 4) { - hints.ai_family = AF_INET; - } else { - hints.ai_family = AF_UNSPEC; - } - - hints.ai_socktype = 0; // `0` for any - hints.ai_protocol = 0; // `0` for any - - auto resolver = new uv_getaddrinfo_t; - resolver->data = ctx; - - auto err = uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) { - auto ctx = (Core::DNS::RequestContext*) resolver->data; - - if (status < 0) { - auto result = JSON::Object::Entries { - {"source", "dns.lookup"}, - {"err", JSON::Object::Entries { - {"code", std::to_string(status)}, - {"message", String(uv_strerror(status))} - }} - }; - - ctx->cb(ctx->seq, result, Post{}); - uv_freeaddrinfo(res); - delete resolver; - delete ctx; - return; - } - - String address = ""; - - if (res->ai_family == AF_INET) { - char addr[17] = {'\0'}; - uv_ip4_name((struct sockaddr_in*)(res->ai_addr), addr, 16); - address = String(addr, 17); - } else if (res->ai_family == AF_INET6) { - char addr[40] = {'\0'}; - uv_ip6_name((struct sockaddr_in6*)(res->ai_addr), addr, 39); - address = String(addr, 40); - } - - address = address.erase(address.find('\0')); - - auto family = res->ai_family == AF_INET - ? 4 - : res->ai_family == AF_INET6 - ? 6 - : 0; - - auto result = JSON::Object::Entries { - {"source", "dns.lookup"}, - {"data", JSON::Object::Entries { - {"address", address}, - {"family", family} - }} - }; - - ctx->cb(ctx->seq, result, Post{}); - uv_freeaddrinfo(res); - delete resolver; - delete ctx; - }, options.hostname.c_str(), nullptr, &hints); - - if (err < 0) { - auto result = JSON::Object::Entries { - {"source", "dns.lookup"}, - {"err", JSON::Object::Entries { - {"code", std::to_string(err)}, - {"message", String(uv_strerror(err))} - }} - }; - - ctx->cb(seq, result, Post{}); - delete ctx; - } - }); - } - #if defined(__linux__) && !defined(__ANDROID__) struct UVSource { GSource base; // should ALWAYS be first member diff --git a/src/core/core.hh b/src/core/core.hh index 693f37a3c0..f7f5b9119d 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -627,6 +627,7 @@ namespace SSC { static const int SEND_BUFFER = 0; OS (auto core) : Module(core) {} + void constants (const String seq, Module::Callback cb); void bufferSize ( const String seq, uint64_t peerId, @@ -683,14 +684,45 @@ namespace SSC { ); }; + #if SSC_PLATFORM_DESKTOP class ChildProcess : public Module { public: + using Handles = std::map; + struct SpawnOptions { + String cwd; + bool stdin = true; + bool stdout = true; + bool stderr = true; + }; + + Handles handles; + Mutex mutex; + ChildProcess (auto core) : Module(core) {} - std::map processes; + void spawn ( + const String seq, + uint64_t id, + const Vector args, + const SpawnOptions options, + Module::Callback cb + ); - void kill (const String seq, uint64_t id, const int signal, Module::Callback cb); - void spawn (const String seq, uint64_t id, const String cwd, Vector args, Module::Callback cb); + void kill ( + const String seq, + uint64_t id, + int signal, + Module::Callback cb + ); + + void write ( + const String seq, + uint64_t id, + char* buffer, + size_t size, + Module::Callback cb + ); }; + #endif class UDP : public Module { public: diff --git a/src/core/dns.cc b/src/core/dns.cc new file mode 100644 index 0000000000..9087e029e0 --- /dev/null +++ b/src/core/dns.cc @@ -0,0 +1,96 @@ +#include "core.hh" + +namespace SSC { + void Core::DNS::lookup ( + const String seq, + LookupOptions options, + Core::Module::Callback cb + ) { + this->core->dispatchEventLoop([=, this]() { + auto ctx = new Core::Module::RequestContext(seq, cb); + auto loop = this->core->getEventLoop(); + + struct addrinfo hints = {0}; + + if (options.family == 6) { + hints.ai_family = AF_INET6; + } else if (options.family == 4) { + hints.ai_family = AF_INET; + } else { + hints.ai_family = AF_UNSPEC; + } + + hints.ai_socktype = 0; // `0` for any + hints.ai_protocol = 0; // `0` for any + + auto resolver = new uv_getaddrinfo_t; + resolver->data = ctx; + + auto err = uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) { + auto ctx = (Core::DNS::RequestContext*) resolver->data; + + if (status < 0) { + auto result = JSON::Object::Entries { + {"source", "dns.lookup"}, + {"err", JSON::Object::Entries { + {"code", std::to_string(status)}, + {"message", String(uv_strerror(status))} + }} + }; + + ctx->cb(ctx->seq, result, Post{}); + uv_freeaddrinfo(res); + delete resolver; + delete ctx; + return; + } + + String address = ""; + + if (res->ai_family == AF_INET) { + char addr[17] = {'\0'}; + uv_ip4_name((struct sockaddr_in*)(res->ai_addr), addr, 16); + address = String(addr, 17); + } else if (res->ai_family == AF_INET6) { + char addr[40] = {'\0'}; + uv_ip6_name((struct sockaddr_in6*)(res->ai_addr), addr, 39); + address = String(addr, 40); + } + + address = address.erase(address.find('\0')); + + auto family = res->ai_family == AF_INET + ? 4 + : res->ai_family == AF_INET6 + ? 6 + : 0; + + auto result = JSON::Object::Entries { + {"source", "dns.lookup"}, + {"data", JSON::Object::Entries { + {"address", address}, + {"family", family} + }} + }; + + ctx->cb(ctx->seq, result, Post{}); + uv_freeaddrinfo(res); + delete resolver; + delete ctx; + }, options.hostname.c_str(), nullptr, &hints); + + if (err < 0) { + auto result = JSON::Object::Entries { + {"source", "dns.lookup"}, + {"err", JSON::Object::Entries { + {"code", std::to_string(err)}, + {"message", String(uv_strerror(err))} + }} + }; + + ctx->cb(seq, result, Post{}); + delete ctx; + } + }); + } +} diff --git a/src/core/headers.cc b/src/core/headers.cc new file mode 100644 index 0000000000..b43b1dde5a --- /dev/null +++ b/src/core/headers.cc @@ -0,0 +1,143 @@ +#include "core.hh" + +namespace SSC { + Headers::Header::Header (const Header& header) { + this->key = header.key; + this->value = header.value; + } + + Headers::Header::Header (const String& key, const Value& value) { + this->key = trim(key); + this->value = trim(value.str()); + } + + Headers::Headers (const String& source) { + for (const auto& entry : split(source, '\n')) { + const auto tuple = split(entry, ':'); + if (tuple.size() == 2) { + set(trim(tuple.front()), trim(tuple.back())); + } + } + } + + Headers::Headers (const Headers& headers) { + this->entries = headers.entries; + } + + Headers::Headers (const Vector>& entries) { + for (const auto& entry : entries) { + for (const auto& pair : entry) { + this->entries.push_back(Header { pair.first, pair.second }); + } + } + } + + Headers::Headers (const Entries& entries) { + for (const auto& entry : entries) { + this->entries.push_back(entry); + } + } + + void Headers::set (const String& key, const String& value) { + set(Header{ key, value }); + } + + void Headers::set (const Header& header) { + for (auto& entry : entries) { + if (header.key == entry.key) { + entry.value = header.value; + return; + } + } + + entries.push_back(header); + } + + bool Headers::has (const String& name) const { + for (const auto& header : entries) { + if (header.key == name) { + return true; + } + } + + return false; + } + + const Headers::Header& Headers::get (const String& name) const { + static const auto empty = Header(); + + for (const auto& header : entries) { + if (header.key == name) { + return header; + } + } + + return empty; + } + + size_t Headers::size () const { + return this->entries.size(); + } + + String Headers::str () const { + StringStream headers; + auto count = this->size(); + for (const auto& entry : this->entries) { + headers << entry.key << ": " << entry.value.str();; + if (--count > 0) { + headers << "\n"; + } + } + return headers.str(); + } + + Headers::Value::Value (const String& value) { + this->string = trim(value); + } + + Headers::Value::Value (const char* value) { + this->string = value; + } + + Headers::Value::Value (const Value& value) { + this->string = value.string; + } + + Headers::Value::Value (bool value) { + this->string = value ? "true" : "false"; + } + + Headers::Value::Value (int value) { + this->string = std::to_string(value); + } + + Headers::Value::Value (float value) { + this->string = std::to_string(value); + } + + Headers::Value::Value (int64_t value) { + this->string = std::to_string(value); + } + + Headers::Value::Value (uint64_t value) { + this->string = std::to_string(value); + } + + Headers::Value::Value (double_t value) { + this->string = std::to_string(value); + } + +#if defined(__APPLE__) + Headers::Value::Value (ssize_t value) { + this->string = std::to_string(value); + } +#endif + + const String& Headers::Value::str () const { + return this->string; + } + + const char * Headers::Value::c_str() const { + return this->str().c_str(); + } +} diff --git a/src/core/os.cc b/src/core/os.cc new file mode 100644 index 0000000000..02015ef220 --- /dev/null +++ b/src/core/os.cc @@ -0,0 +1,683 @@ +#include "core.hh" + +namespace SSC { + #define SET_CONSTANT(c) constants[#c] = (c); + static std::map getOSConstantsMap () { + std::map constants; + + #if defined(E2BIG) + SET_CONSTANT(E2BIG) + #endif + #if defined(EACCES) + SET_CONSTANT(EACCES) + #endif + #if defined(EADDRINUSE) + SET_CONSTANT(EADDRINUSE) + #endif + #if defined(EADDRNOTAVAIL) + SET_CONSTANT(EADDRNOTAVAIL) + #endif + #if defined(EAFNOSUPPORT) + SET_CONSTANT(EAFNOSUPPORT) + #endif + #if defined(EAGAIN) + SET_CONSTANT(EAGAIN) + #endif + #if defined(EALREADY) + SET_CONSTANT(EALREADY) + #endif + #if defined(EBADF) + SET_CONSTANT(EBADF) + #endif + #if defined(EBADMSG) + SET_CONSTANT(EBADMSG) + #endif + #if defined(EBUSY) + SET_CONSTANT(EBUSY) + #endif + #if defined(ECANCELED) + SET_CONSTANT(ECANCELED) + #endif + #if defined(ECHILD) + SET_CONSTANT(ECHILD) + #endif + #if defined(ECONNABORTED) + SET_CONSTANT(ECONNABORTED) + #endif + #if defined(ECONNREFUSED) + SET_CONSTANT(ECONNREFUSED) + #endif + #if defined(ECONNRESET) + SET_CONSTANT(ECONNRESET) + #endif + #if defined(EDEADLK) + SET_CONSTANT(EDEADLK) + #endif + #if defined(EDESTADDRREQ) + SET_CONSTANT(EDESTADDRREQ) + #endif + #if defined(EDOM) + SET_CONSTANT(EDOM) + #endif + #if defined(EDQUOT) + SET_CONSTANT(EDQUOT) + #endif + #if defined(EEXIST) + SET_CONSTANT(EEXIST) + #endif + #if defined(EFAULT) + SET_CONSTANT(EFAULT) + #endif + #if defined(EFBIG) + SET_CONSTANT(EFBIG) + #endif + #if defined(EHOSTUNREACH) + SET_CONSTANT(EHOSTUNREACH) + #endif + #if defined(EIDRM) + SET_CONSTANT(EIDRM) + #endif + #if defined(EILSEQ) + SET_CONSTANT(EILSEQ) + #endif + #if defined(EINPROGRESS) + SET_CONSTANT(EINPROGRESS) + #endif + #if defined(EINTR) + SET_CONSTANT(EINTR) + #endif + #if defined(EINVAL) + SET_CONSTANT(EINVAL) + #endif + #if defined(EIO) + SET_CONSTANT(EIO) + #endif + #if defined(EISCONN) + SET_CONSTANT(EISCONN) + #endif + #if defined(EISDIR) + SET_CONSTANT(EISDIR) + #endif + #if defined(ELOOP) + SET_CONSTANT(ELOOP) + #endif + #if defined(EMFILE) + SET_CONSTANT(EMFILE) + #endif + #if defined(EMLINK) + SET_CONSTANT(EMLINK) + #endif + #if defined(EMSGSIZE) + SET_CONSTANT(EMSGSIZE) + #endif + #if defined(EMULTIHOP) + SET_CONSTANT(EMULTIHOP) + #endif + #if defined(ENAMETOOLONG) + SET_CONSTANT(ENAMETOOLONG) + #endif + #if defined(ENETDOWN) + SET_CONSTANT(ENETDOWN) + #endif + #if defined(ENETRESET) + SET_CONSTANT(ENETRESET) + #endif + #if defined(ENETUNREACH) + SET_CONSTANT(ENETUNREACH) + #endif + #if defined(ENFILE) + SET_CONSTANT(ENFILE) + #endif + #if defined(ENOBUFS) + SET_CONSTANT(ENOBUFS) + #endif + #if defined(ENODATA) + SET_CONSTANT(ENODATA) + #endif + #if defined(ENODEV) + SET_CONSTANT(ENODEV) + #endif + #if defined(ENOENT) + SET_CONSTANT(ENOENT) + #endif + #if defined(ENOEXEC) + SET_CONSTANT(ENOEXEC) + #endif + #if defined(ENOLCK) + SET_CONSTANT(ENOLCK) + #endif + #if defined(ENOLINK) + SET_CONSTANT(ENOLINK) + #endif + #if defined(ENOMEM) + SET_CONSTANT(ENOMEM) + #endif + #if defined(ENOMSG) + SET_CONSTANT(ENOMSG) + #endif + #if defined(ENOPROTOOPT) + SET_CONSTANT(ENOPROTOOPT) + #endif + #if defined(ENOSPC) + SET_CONSTANT(ENOSPC) + #endif + #if defined(ENOSR) + SET_CONSTANT(ENOSR) + #endif + #if defined(ENOSTR) + SET_CONSTANT(ENOSTR) + #endif + #if defined(ENOSYS) + SET_CONSTANT(ENOSYS) + #endif + #if defined(ENOTCONN) + SET_CONSTANT(ENOTCONN) + #endif + #if defined(ENOTDIR) + SET_CONSTANT(ENOTDIR) + #endif + #if defined(ENOTEMPTY) + SET_CONSTANT(ENOTEMPTY) + #endif + #if defined(ENOTSOCK) + SET_CONSTANT(ENOTSOCK) + #endif + #if defined(ENOTSUP) + SET_CONSTANT(ENOTSUP) + #endif + #if defined(ENOTTY) + SET_CONSTANT(ENOTTY) + #endif + #if defined(ENXIO) + SET_CONSTANT(ENXIO) + #endif + #if defined(EOPNOTSUPP) + SET_CONSTANT(EOPNOTSUPP) + #endif + #if defined(EOVERFLOW) + SET_CONSTANT(EOVERFLOW) + #endif + #if defined(EPERM) + SET_CONSTANT(EPERM) + #endif + #if defined(EPIPE) + SET_CONSTANT(EPIPE) + #endif + #if defined(EPROTO) + SET_CONSTANT(EPROTO) + #endif + #if defined(EPROTONOSUPPORT) + SET_CONSTANT(EPROTONOSUPPORT) + #endif + #if defined(EPROTOTYPE) + SET_CONSTANT(EPROTOTYPE) + #endif + #if defined(ERANGE) + SET_CONSTANT(ERANGE) + #endif + #if defined(EROFS) + SET_CONSTANT(EROFS) + #endif + #if defined(ESPIPE) + SET_CONSTANT(ESPIPE) + #endif + #if defined(ESRCH) + SET_CONSTANT(ESRCH) + #endif + #if defined(ESTALE) + SET_CONSTANT(ESTALE) + #endif + #if defined(ETIME) + SET_CONSTANT(ETIME) + #endif + #if defined(ETIMEDOUT) + SET_CONSTANT(ETIMEDOUT) + #endif + #if defined(ETXTBSY) + SET_CONSTANT(ETXTBSY) + #endif + #if defined(EWOULDBLOCK) + SET_CONSTANT(EWOULDBLOCK) + #endif + #if defined(EXDEV) + SET_CONSTANT(EXDEV) + #endif + + #if defined(SIGHUP) + SET_CONSTANT(SIGHUP) + #endif + #if defined(SIGINT) + SET_CONSTANT(SIGINT) + #endif + #if defined(SIGQUIT) + SET_CONSTANT(SIGQUIT) + #endif + #if defined(SIGILL) + SET_CONSTANT(SIGILL) + #endif + #if defined(SIGTRAP) + SET_CONSTANT(SIGTRAP) + #endif + #if defined(SIGABRT) + SET_CONSTANT(SIGABRT) + #endif + #if defined(SIGIOT) + SET_CONSTANT(SIGIOT) + #endif + #if defined(SIGBUS) + SET_CONSTANT(SIGBUS) + #endif + #if defined(SIGFPE) + SET_CONSTANT(SIGFPE) + #endif + #if defined(SIGKILL) + SET_CONSTANT(SIGKILL) + #endif + #if defined(SIGUSR1) + SET_CONSTANT(SIGUSR1) + #endif + #if defined(SIGSEGV) + SET_CONSTANT(SIGSEGV) + #endif + #if defined(SIGUSR2) + SET_CONSTANT(SIGUSR2) + #endif + #if defined(SIGPIPE) + SET_CONSTANT(SIGPIPE) + #endif + #if defined(SIGALRM) + SET_CONSTANT(SIGALRM) + #endif + #if defined(SIGTERM) + SET_CONSTANT(SIGTERM) + #endif + #if defined(SIGCHLD) + SET_CONSTANT(SIGCHLD) + #endif + #if defined(SIGCONT) + SET_CONSTANT(SIGCONT) + #endif + #if defined(SIGSTOP) + SET_CONSTANT(SIGSTOP) + #endif + #if defined(SIGTSTP) + SET_CONSTANT(SIGTSTP) + #endif + #if defined(SIGTTIN) + SET_CONSTANT(SIGTTIN) + #endif + #if defined(SIGTTOU) + SET_CONSTANT(SIGTTOU) + #endif + #if defined(SIGURG) + SET_CONSTANT(SIGURG) + #endif + #if defined(SIGXCPU) + SET_CONSTANT(SIGXCPU) + #endif + #if defined(SIGXFSZ) + SET_CONSTANT(SIGXFSZ) + #endif + #if defined(SIGVTALRM) + SET_CONSTANT(SIGVTALRM) + #endif + #if defined(SIGPROF) + SET_CONSTANT(SIGPROF) + #endif + #if defined(SIGWINCH) + SET_CONSTANT(SIGWINCH) + #endif + #if defined(SIGIO) + SET_CONSTANT(SIGIO) + #endif + #if defined(SIGINFO) + SET_CONSTANT(SIGINFO) + #endif + #if defined(SIGSYS) + SET_CONSTANT(SIGSYS) + #endif + + + return constants; + } + #undef SET_CONSTANT + + void Core::OS::cpus ( + const String seq, + Module::Callback cb + ) { + this->core->dispatchEventLoop([=, this]() { + #if defined(__ANDROID__) + { + auto json = JSON::Object::Entries { + {"source", "os.cpus"}, + {"data", JSON::Array::Entries {}} + }; + + cb(seq, json, Post{}); + return; + } + #endif + + uv_cpu_info_t* infos = nullptr; + int count = 0; + int status = uv_cpu_info(&infos, &count); + + if (status != 0) { + auto json = JSON::Object::Entries { + {"source", "os.cpus"}, + {"err", JSON::Object::Entries { + {"message", uv_strerror(status)} + }} + }; + + cb(seq, json, Post{}); + return; + } + + JSON::Array::Entries entries(count); + for (int i = 0; i < count; ++i) { + auto info = infos[i]; + entries[i] = JSON::Object::Entries { + {"model", info.model}, + {"speed", info.speed}, + {"times", JSON::Object::Entries { + {"user", info.cpu_times.user}, + {"nice", info.cpu_times.nice}, + {"sys", info.cpu_times.sys}, + {"idle", info.cpu_times.idle}, + {"irq", info.cpu_times.irq} + }} + }; + } + + auto json = JSON::Object::Entries { + {"source", "os.cpus"}, + {"data", entries} + }; + + uv_free_cpu_info(infos, count); + cb(seq, json, Post{}); + }); + } + + void Core::OS::networkInterfaces ( + const String seq, + Module::Callback cb + ) const { + uv_interface_address_t *infos = nullptr; + StringStream value; + StringStream v4; + StringStream v6; + int count = 0; + + int status = uv_interface_addresses(&infos, &count); + + if (status != 0) { + auto json = JSON::Object(JSON::Object::Entries { + {"source", "os.networkInterfaces"}, + {"err", JSON::Object::Entries { + {"type", "InternalError"}, + {"message", + String("Unable to get network interfaces: ") + String(uv_strerror(status)) + } + }} + }); + + return cb(seq, json, Post{}); + } + + JSON::Object::Entries ipv4; + JSON::Object::Entries ipv6; + JSON::Object::Entries data; + + for (int i = 0; i < count; ++i) { + uv_interface_address_t info = infos[i]; + struct sockaddr_in *addr = (struct sockaddr_in*) &info.address.address4; + char mac[18] = {0}; + snprintf(mac, 18, "%02x:%02x:%02x:%02x:%02x:%02x", + (unsigned char) info.phys_addr[0], + (unsigned char) info.phys_addr[1], + (unsigned char) info.phys_addr[2], + (unsigned char) info.phys_addr[3], + (unsigned char) info.phys_addr[4], + (unsigned char) info.phys_addr[5] + ); + + if (addr->sin_family == AF_INET) { + JSON::Object::Entries entries; + entries["internal"] = info.is_internal == 0 ? "false" : "true"; + entries["address"] = addrToIPv4(addr); + entries["mac"] = String(mac, 17); + ipv4[String(info.name)] = entries; + } + + if (addr->sin_family == AF_INET6) { + JSON::Object::Entries entries; + entries["internal"] = info.is_internal == 0 ? "false" : "true"; + entries["address"] = addrToIPv6((struct sockaddr_in6*) addr); + entries["mac"] = String(mac, 17); + ipv6[String(info.name)] = entries; + } + } + + uv_free_interface_addresses(infos, count); + + data["ipv4"] = ipv4; + data["ipv6"] = ipv6; + + auto json = JSON::Object::Entries { + {"source", "os.networkInterfaces"}, + {"data", data} + }; + + cb(seq, json, Post{}); + } + + void Core::OS::rusage ( + const String seq, + Module::Callback cb + ) { + uv_rusage_t usage; + auto status = uv_getrusage(&usage); + + if (status != 0) { + auto json = JSON::Object::Entries { + {"source", "os.rusage"}, + {"err", JSON::Object::Entries { + {"message", uv_strerror(status)} + }} + }; + + cb(seq, json, Post{}); + return; + } + + auto json = JSON::Object::Entries { + {"source", "os.rusage"}, + {"data", JSON::Object::Entries { + {"ru_maxrss", usage.ru_maxrss} + }} + }; + + cb(seq, json, Post{}); + } + + void Core::OS::uname ( + const String seq, + Module::Callback cb + ) { + uv_utsname_t uname; + auto status = uv_os_uname(&uname); + + if (status != 0) { + auto json = JSON::Object::Entries { + {"source", "os.uname"}, + {"err", JSON::Object::Entries { + {"message", uv_strerror(status)} + }} + }; + + cb(seq, json, Post{}); + return; + } + + auto json = JSON::Object::Entries { + {"source", "os.uname"}, + {"data", JSON::Object::Entries { + {"sysname", uname.sysname}, + {"release", uname.release}, + {"version", uname.version}, + {"machine", uname.machine} + }} + }; + + cb(seq, json, Post{}); + } + + void Core::OS::uptime ( + const String seq, + Module::Callback cb + ) { + double uptime; + auto status = uv_uptime(&uptime); + + if (status != 0) { + auto json = JSON::Object::Entries { + {"source", "os.uptime"}, + {"err", JSON::Object::Entries { + {"message", uv_strerror(status)} + }} + }; + + cb(seq, json, Post{}); + return; + } + + auto json = JSON::Object::Entries { + {"source", "os.uptime"}, + {"data", uptime * 1000} // in milliseconds + }; + + cb(seq, json, Post{}); + } + + void Core::OS::hrtime ( + const String seq, + Module::Callback cb + ) { + auto hrtime = uv_hrtime(); + auto bytes = toBytes(hrtime); + auto size = bytes.size(); + auto post = Post {}; + auto body = new char[size]{0}; + auto json = JSON::Object {}; + post.body = body; + post.length = size; + memcpy(body, bytes.data(), size); + cb(seq, json, post); + } + + void Core::OS::availableMemory ( + const String seq, + Module::Callback cb + ) { + auto memory = uv_get_available_memory(); + auto bytes = toBytes(memory); + auto size = bytes.size(); + auto post = Post {}; + auto body = new char[size]{0}; + auto json = JSON::Object {}; + post.body = body; + post.length = size; + memcpy(body, bytes.data(), size); + cb(seq, json, post); + } + + void Core::OS::bufferSize ( + const String seq, + uint64_t peerId, + size_t size, + int buffer, + Module::Callback cb + ) { + if (buffer == 0) { + buffer = Core::OS::SEND_BUFFER; + } else if (buffer == 1) { + buffer = Core::OS::RECV_BUFFER; + } + + this->core->dispatchEventLoop([=, this]() { + auto peer = this->core->getPeer(peerId); + + if (peer == nullptr) { + auto json = JSON::Object::Entries { + {"source", "bufferSize"}, + {"err", JSON::Object::Entries { + {"id", std::to_string(peerId)}, + {"code", "NOT_FOUND_ERR"}, + {"type", "NotFoundError"}, + {"message", "No peer with specified id"} + }} + }; + + cb(seq, json, Post{}); + return; + } + + Lock lock(peer->mutex); + auto handle = (uv_handle_t*) &peer->handle; + auto err = buffer == RECV_BUFFER + ? uv_recv_buffer_size(handle, (int *) &size) + : uv_send_buffer_size(handle, (int *) &size); + + if (err < 0) { + auto json = JSON::Object::Entries { + {"source", "bufferSize"}, + {"err", JSON::Object::Entries { + {"id", std::to_string(peerId)}, + {"code", "NOT_FOUND_ERR"}, + {"type", "NotFoundError"}, + {"message", String(uv_strerror(err))} + }} + }; + + cb(seq, json, Post{}); + return; + } + + auto json = JSON::Object::Entries { + {"source", "bufferSize"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(peerId)}, + {"size", (int) size} + }} + }; + + cb(seq, json, Post{}); + }); + } + + void Core::OS::constants (const String seq, Module::Callback cb) { + static auto constants = getOSConstantsMap(); + static auto data = JSON::Object {constants}; + static auto json = JSON::Object::Entries { + {"source", "os.constants"}, + {"data", data} + }; + + static auto headers = Headers {{ + Headers::Header {"Cache-Control", "public, max-age=86400"} + }}; + + static auto post = Post { + .id = 0, + .ttl = 0, + .body = nullptr, + .length = 0, + .headers = headers.str() + }; + + cb(seq, json, post); + } +} diff --git a/src/core/platform.cc b/src/core/platform.cc index b9c774f245..c9436db621 100644 --- a/src/core/platform.cc +++ b/src/core/platform.cc @@ -1,3 +1,4 @@ +#include "core.hh" #include "platform.hh" namespace SSC { @@ -69,4 +70,255 @@ namespace SSC { .unix = true #endif }; + + void Core::Platform::event ( + const String seq, + const String event, + const String data, + Module::Callback cb + ) { + this->core->dispatchEventLoop([=, this]() { + // init page + if (event == "domcontentloaded") { + Lock lock(this->core->fs.mutex); + + for (auto const &tuple : this->core->fs.descriptors) { + auto desc = tuple.second; + if (desc != nullptr) { + desc->stale = true; + } else { + this->core->fs.descriptors.erase(tuple.first); + } + } + + #if !defined(__ANDROID__) + for (auto const &tuple : this->core->fs.watchers) { + auto watcher = tuple.second; + if (watcher != nullptr) { + watcher->stop(); + delete watcher; + } + } + + this->core->fs.watchers.clear(); + #endif + } + + auto json = JSON::Object::Entries { + {"source", "platform.event"}, + {"data", JSON::Object::Entries{}} + }; + + cb(seq, json, Post{}); + }); + } + + void Core::Platform::notify ( + const String seq, + const String title, + const String body, + Module::Callback cb + ) { +#if defined(__APPLE__) + auto center = [UNUserNotificationCenter currentNotificationCenter]; + auto content = [[UNMutableNotificationContent alloc] init]; + content.body = [NSString stringWithUTF8String: body.c_str()]; + content.title = [NSString stringWithUTF8String: title.c_str()]; + content.sound = [UNNotificationSound defaultSound]; + + auto trigger = [UNTimeIntervalNotificationTrigger + triggerWithTimeInterval: 1.0f + repeats: NO + ]; + + auto request = [UNNotificationRequest + requestWithIdentifier: @"LocalNotification" + content: content + trigger: trigger + ]; + + auto options = ( + UNAuthorizationOptionAlert | + UNAuthorizationOptionBadge | + UNAuthorizationOptionSound + ); + + [center requestAuthorizationWithOptions: options + completionHandler: ^(BOOL granted, NSError* error) + { + #if !__has_feature(objc_arc) + [content release]; + [trigger release]; + #endif + + if (granted) { + auto json = JSON::Object::Entries { + {"source", "platform.notify"}, + {"data", JSON::Object::Entries {}} + }; + + cb(seq, json, Post{}); + } else if (error) { + [center addNotificationRequest: request + withCompletionHandler: ^(NSError* error) + { + auto json = JSON::Object {}; + + #if !__has_feature(objc_arc) + [request release]; + #endif + + if (error) { + json = JSON::Object::Entries { + {"source", "platform.notify"}, + {"err", JSON::Object::Entries { + {"message", [error.debugDescription UTF8String]} + }} + }; + + debug("Unable to create notification: %@", error.debugDescription); + } else { + json = JSON::Object::Entries { + {"source", "platform.notify"}, + {"data", JSON::Object::Entries {}} + }; + } + + cb(seq, json, Post{}); + }]; + } else { + auto json = JSON::Object::Entries { + {"source", "platform.notify"}, + {"err", JSON::Object::Entries { + {"message", "Failed to create notification"} + }} + }; + + cb(seq, json, Post{}); + } + + if (!error || granted) { + #if !__has_feature(objc_arc) + [request release]; + #endif + } + }]; +#endif + } + + void Core::Platform::openExternal ( + const String seq, + const String value, + Module::Callback cb + ) { +#if defined(__APPLE__) + auto string = [NSString stringWithUTF8String: value.c_str()]; + auto url = [NSURL URLWithString: string]; + + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + auto app = [UIApplication sharedApplication]; + [app openURL: url options: @{} completionHandler: ^(BOOL success) { + auto json = JSON::Object {}; + + if (!success) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", "Failed to open external URL"} + }} + }; + } else { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"data", JSON::Object::Entries {}} + }; + } + + cb(seq, json, Post{}); + }]; + #else + auto workspace = [NSWorkspace sharedWorkspace]; + auto configuration = [NSWorkspaceOpenConfiguration configuration]; + [workspace openURL: url + configuration: configuration + completionHandler: ^(NSRunningApplication *app, NSError *error) + { + auto json = JSON::Object {}; + if (error) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", [error.debugDescription UTF8String]} + }} + }; + } else { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"data", JSON::Object::Entries {}} + }; + } + + cb(seq, json, Post{}); + }]; + #endif +#elif defined(__linux__) && !defined(__ANDROID__) + auto list = gtk_window_list_toplevels(); + auto json = JSON::Object {}; + + // initial state is a failure + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", "Failed to open external URL"} + }} + }; + + if (list != nullptr) { + for (auto entry = list; entry != nullptr; entry = entry->next) { + auto window = GTK_WINDOW(entry->data); + + if (window != nullptr && gtk_window_is_active(window)) { + auto err = (GError*) nullptr; + auto uri = value.c_str(); + auto ts = GDK_CURRENT_TIME; + + /** + * GTK may print a error in the terminal that looks like: + * + * libva error: vaGetDriverNameByIndex() failed with unknown libva error, driver_name = (null) + * + * It doesn't prevent the URI from being opened. + * See https://github.com/intel/media-driver/issues/1349 for more info + */ + auto success = gtk_show_uri_on_window(window, uri, ts, &err); + + if (success) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"data", JSON::Object::Entries {}} + }; + } else if (err != nullptr) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", err->message} + }} + }; + } + + break; + } + } + + g_list_free(list); + } + + cb(seq, json, Post{}); +#elif defined(_WIN32) + auto uri = value.c_str(); + ShellExecute(nullptr, "Open", uri, nullptr, nullptr, SW_SHOWNORMAL); + // TODO how to detect success here. do we care? + cb(seq, JSON::Object{}, Post{}); +#endif + } } diff --git a/src/core/platform.hh b/src/core/platform.hh index 34d0112e72..0f4b202aa3 100644 --- a/src/core/platform.hh +++ b/src/core/platform.hh @@ -109,6 +109,111 @@ #include "config.hh" #include "types.hh" +#if defined(_WIN32) +# define SSC_PLATFORM_NAME "win32" +# define SSC_PLATFORM_OS "win32" +# define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_IOS 0 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_LINUX 0 +# define SSC_PLATFORM_MACOS 0 +# define SSC_PLATFORM_UXIX 0 +# define SSC_PLATFORM_WINDOWS 1 +#elif defined(__APPLE__) +# include +# define SSC_PLATFORM_NAME "darwin" +# define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_LINUX 0 +# define SSC_PLATFORM_WINDOWS 0 + +#if TARGET_OS_IPHONE +# define SSC_PLATFORM_MACOS 0 +# define SSC_PLATFORM_IOS 1 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_OS "ios" +#elif TARGET_IPHONE_SIMULATOR +# define SSC_PLATFORM_MACOS 0 +# define SSC_PLATFORM_IOS 1 +# define SSC_PLATFORM_IOS_SIMULATOR 1 +# define SSC_PLATFORM_OS "ios" +#else +# define SSC_PLATFORM_MACOS 1 +# define SSC_PLATFORM_IOS 0 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_OS "mac" +#endif + +#if defined(__unix__) || defined(unix) || defined(__unix) +# define SSC_PLATFORM_UXIX 0 +#else +# define SSC_PLATFORM_UXIX 0 +#endif + +#elif defined(__linux__) +# undef linux +# define SSC_PLATFORM_NAME "linux" +# define SSC_PLATFORM_IOS 0 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_LINUX 1 +# define SSC_PLATFORM_MACOS 0 +# define SSC_PLATFORM_WINDOWS 0 + +#ifdef __ANDROID__ +# define SSC_PLATFORM_OS "android" +# define SSC_PLATFORM_ANDROID 1 +#else +# define SSC_PLATFORM_OS "linux" +# define SSC_PLATFORM_ANDROID 0 +#endif + +#if defined(__unix__) || defined(unix) || defined(__unix) +# define SSC_PLATFORM_UXIX 1 +#else +# define SSC_PLATFORM_UXIX 0 +#endif + +#elif defined(__FreeBSD__) +# define SSC_PLATFORM_NAME "freebsd" +# define SSC_PLATFORM_OS "freebsd" +# define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_IOS 0 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_LINUX 0 +# define SSC_PLATFORM_MACOS 0 +# define SSC_PLATFORM_WINDOWS 0 + +#if defined(__unix__) || defined(unix) || defined(__unix) +# define SSC_PLATFORM_UXIX 0 +#else +# define SSC_PLATFORM_UXIX 1 +#endif + +#elif defined(BSD) +# define SSC_PLATFORM_NAME "openbsd" +# define SSC_PLATFORM_OS "openbsd" +# define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_IOS 0 +# define SSC_PLATFORM_IOS_SIMULATOR 0 +# define SSC_PLATFORM_LINUX 0 +# define SSC_PLATFORM_MACOS 0 +# define SSC_PLATFORM_WINDOWS 0 + +#if defined(__unix__) || defined(unix) || defined(__unix) +# define SSC_PLATFORM_UXIX 0 +#else +# define SSC_PLATFORM_UXIX 1 +#endif +#endif + +#if SSC_PLATFORM_ANDROID || SSC_PLATFORM_IOS || SSC_PLATFORM_IOS_SIMULATOR +#define SSC_PLATFORM_MOBILE 1 +#define SSC_PLATFORM_DESKTOP 0 +#else +#define SSC_PLATFORM_MOBILE 0 +#define SSC_PLATFORM_DESKTOP 1 +#endif + namespace SSC { struct RuntimePlatform { const String arch = ""; diff --git a/src/desktop/main.cc b/src/desktop/main.cc index b61a57b1bf..547a0ede7d 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -45,10 +45,46 @@ using namespace SSC; static App *app_ptr = nullptr; -std::function shutdownHandler; +static std::function shutdownHandler; + +// propagate signals to the default window which will use the +// 'socket.runtime.signal' broadcast channel to propagate to all +// other windows who may be subscribers +static void defaultWindowSignalHandler (int signal) { + auto app = App::instance(); + if (app != nullptr && app->windowManager != nullptr) { + auto defaultWindow = app->windowManager->getWindow(0); + if (defaultWindow != nullptr) { + if (defaultWindow->status < WindowManager::WindowStatus::WINDOW_CLOSING) { + const auto json = JSON::Object { + JSON::Object::Entries { + {"signal", signal} + } + }; + defaultWindow->eval(getEmitToRenderProcessJavaScript("signal", json.str())); + } + } + } +} + void signalHandler (int signal) { + static auto app = App::instance(); + static auto userConfig = SSC::getUserConfig(); + static const auto signalsDisabled = userConfig["application_signals"] == "false"; + static const auto signals = parseStringList(userConfig["application_signals"]); + const auto name = String(sys_signame[signal]); + + if ( + !signalsDisabled || + std::find(signals.begin(), signals.end(), name) != signals.end() + ) { + defaultWindowSignalHandler(signal); + } + if (shutdownHandler != nullptr) { - shutdownHandler(signal); + app->dispatch([signal] () { + shutdownHandler(signal); + }); } } @@ -1499,6 +1535,101 @@ MAIN { t.detach(); } + const auto signalsDisabled = userConfig["application_signals"] == "false"; + const auto signals = parseStringList(userConfig["application_signals"]); + +#define SET_DEFAULT_WINDOW_SIGNAL_HANDLER(sig) { \ + const auto name = String(CONVERT_TO_STRING(sig)); \ + if ( \ + !signalsDisabled || \ + std::find(signals.begin(), signals.end(), name) != signals.end() \ + ) { \ + signal(sig, defaultWindowSignalHandler); \ + } \ +} + +#if defined(SIGQUIT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGQUIT) +#endif +#if defined(SIGILL) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGILL) +#endif +#if defined(SIGTRAP) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTRAP) +#endif +#if defined(SIGABRT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGABRT) +#endif +#if defined(SIGIOT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGIOT) +#endif +#if defined(SIGBUS) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGBUS) +#endif +#if defined(SIGFPE) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGFPE) +#endif +#if defined(SIGKILL) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGKILL) +#endif +#if defined(SIGUSR1) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGUSR1) +#endif +#if defined(SIGUSR2) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGUSR2) +#endif +#if defined(SIGPIPE) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGPIPE) +#endif +#if defined(SIGALRM) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGALRM) +#endif +#if defined(SIGCHLD) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGCHLD) +#endif +#if defined(SIGCONT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGCONT) +#endif +#if defined(SIGSTOP) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSTOP) +#endif +#if defined(SIGTSTP) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTSTP) +#endif +#if defined(SIGTTIN) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTTIN) +#endif +#if defined(SIGTTOU) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTTOU) +#endif +#if defined(SIGURG) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGURG) +#endif +#if defined(SIGXCPU) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGXCPU) +#endif +#if defined(SIGXFSZ) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGXFSZ) +#endif +#if defined(SIGVTALRM) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGVTALRM) +#endif +#if defined(SIGPROF) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGPROF) +#endif +#if defined(SIGWINCH) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGWINCH) +#endif +#if defined(SIGIO) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGIO) +#endif +#if defined(SIGINFO) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGINFO) +#endif +#if defined(SIGSYS) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSYS) +#endif + // // # Event Loop // start the platform specific event loop for the main diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 03f2f24338..0339d66a74 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -252,32 +252,64 @@ static void initRouterTable (Router *router) { reply(Result { message.seq, message }); }); + /** + * Kills an already spawned child process. + * + * @param id + * @param signal + */ router->map("child_process.kill", [](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"signal"}); + #if SSC_PLATFORM_MOBILE + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"} + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else + auto err = validateMessageParameters(message, {"id", "signal"}); if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); } - + uint64_t id; REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); int signal; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(id, "signal", std::stoi); - router->core->childProcess.kill(message.seq, id, signal, [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); - }); + router->core->childProcess.kill( + message.seq, + id, + signal, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + #endif }); + /** + * Spawns a child process + * + * @param id + * @param args (command, ...args) + */ router->map("child_process.spawn", [](auto message, auto router, auto reply) { + #if SSC_PLATFORM_MOBILE + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"} + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else auto err = validateMessageParameters(message, {"args", "id"}); if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); } - auto args = split(message.get("args"), 0x001); + auto args = split(message.get("args"), 0x0001); if (args.size() == 0 || args.at(0).size() == 0) { auto json = JSON::Object::Entries { @@ -293,11 +325,56 @@ static void initRouterTable (Router *router) { uint64_t id; REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - auto cwd = message.get("cwd", getcwd()); + const auto options = Core::ChildProcess::SpawnOptions { + .cwd = message.get("cwd", getcwd()), + .stdin = message.get("stdin") != "false", + .stdout = message.get("stdout") != "false", + .stderr = message.get("stderr") != "false" + }; - router->core->childProcess.spawn(message.seq, id, cwd, args, [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); - }); + router->core->childProcess.spawn( + message.seq, + id, + args, + options, + [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + } + ); + #endif + }); + + /** + * Writes to an already spawned child process. + * + * @param id + */ + router->map("child_process.write", [](auto message, auto router, auto reply) { + #if SSC_PLATFORM_MOBILE + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"} + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->childProcess.write( + message.seq, + id, + message.buffer.bytes, + message.buffer.size, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + #endif }); /** @@ -1708,6 +1785,13 @@ static void initRouterTable (Router *router) { ); }); + /** + * Returns a mapping of operating system constants. + */ + router->map("os.constants", [](auto message, auto router, auto reply) { + router->core->os.constants(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + /** * Returns a mapping of network interfaces. */ diff --git a/src/process/process.hh b/src/process/process.hh index 8796196fd4..c03ad0ab50 100644 --- a/src/process/process.hh +++ b/src/process/process.hh @@ -116,13 +116,21 @@ namespace SSC { typedef SSC::String string_type; #endif - SSC::String command; - SSC::String argv; - SSC::String path; - std::atomic closed = true; - std::atomic status = -1; + String command; + String argv; + String path; + Atomic closed = true; + Atomic status = -1; + Atomic lastWriteStatus = 0; + bool open_stdin; id_type id = 0; + #ifdef _WIN32 + String shell = ""; + #else + String shell = "/bin/sh"; + #endif + private: class Data { @@ -204,7 +212,6 @@ namespace SSC { #else std::thread stdout_thread, stderr_thread; #endif - bool open_stdin; std::mutex stdin_mutex; std::mutex stdout_mutex; std::mutex stderr_mutex; diff --git a/src/process/unix.cc b/src/process/unix.cc index c87f7985cc..125c44d0b9 100644 --- a/src/process/unix.cc +++ b/src/process/unix.cc @@ -204,7 +204,7 @@ Process::id_type Process::open(const std::function &function) noexcept { } Process::id_type Process::open(const SSC::String &command, const SSC::String &path) noexcept { - return open([&command, &path] { + return open([&command, &path, this] { auto command_c_str = command.c_str(); SSC::String cd_path_and_command; @@ -213,7 +213,7 @@ Process::id_type Process::open(const SSC::String &command, const SSC::String &pa size_t pos = 0; // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 - while((pos = path_escaped.find('\'', pos)) != SSC::String::npos) { + while ((pos = path_escaped.find('\'', pos)) != SSC::String::npos) { path_escaped.replace(pos, 1, "'\\''"); pos += 4; } @@ -222,7 +222,11 @@ Process::id_type Process::open(const SSC::String &command, const SSC::String &pa command_c_str = cd_path_and_command.c_str(); } - return execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + if (this->shell.size() > 0) { + return execl(this->shell.c_str(), this->shell.c_str(), "-c", command_c_str, nullptr); + } else { + return execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + } }); } @@ -341,12 +345,19 @@ void Process::close_fds() noexcept { bool Process::write(const char *bytes, size_t n) { std::lock_guard lock(stdin_mutex); + this->lastWriteStatus = 0; + if (stdin_fd) { SSC::String b(bytes); while (true && (b.size() > 0)) { int bytesWritten = ::write(*stdin_fd, b.c_str(), b.size()); + if (bytesWritten == -1) { + this->lastWriteStatus = errno; + return false; + } + if (bytesWritten >= b.size()) { break; } @@ -355,6 +366,12 @@ bool Process::write(const char *bytes, size_t n) { } int bytesWritten = ::write(*stdin_fd, "\n", 1); + if (bytesWritten == -1) { + this->lastWriteStatus = errno; + return false; + } + + return true; } return false; diff --git a/src/process/win.cc b/src/process/win.cc index e2cc5f0e0e..e88b19c0d1 100644 --- a/src/process/win.cc +++ b/src/process/win.cc @@ -177,9 +177,19 @@ Process::id_type Process::open(const SSC::String &command, const SSC::String &pa process_command += "\""; #endif + auto comspec = Env::get("COMSPEC"); + auto shell = this->shell; + + if (shell == "cmd.exe" && comspec.size() > 0) { + shell = comspec; + } + BOOL bSuccess = CreateProcess( - nullptr, - process_command.empty() ? nullptr : &process_command[0], + shell.size() > 0 ? shell : nullptr, + ( + (this->shell == "cmd.exe" ? String("/d /s /c ") : "") + + (process_command.empty() ? "": &process_command[0]) + ).c_str(), nullptr, nullptr, stdin_fd || stdout_fd || stderr_fd || config.inherit_file_descriptors, // Cannot be false when stdout, stderr or stdin is used diff --git a/test/src/child_process/index.js b/test/src/child_process/index.js index f95f85daca..3303dc7be8 100644 --- a/test/src/child_process/index.js +++ b/test/src/child_process/index.js @@ -1,5 +1,7 @@ -import test from 'socket:test' import { spawn } from 'socket:child_process' +import process from 'socket:process' +import test from 'socket:test' +import os from 'socket:os' test('basic spawn', async t => { const command = 'ls' @@ -8,18 +10,35 @@ test('basic spawn', async t => { let hasDir = false - await new Promise((resolve, reject) => { - const c = spawn(command, args, options) + const pending = [] + const child = spawn(command, args, options) + + if (/linux|darwin/i.test(os.platform())) { + pending.push(new Promise((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error('Timed out aiting for SIGCHLD signal')), + 1000 + ) - c.stdout.on('data', data => { + process.once('SIGCHLD', (signal, code, message) => { + resolve() + clearTimeout(timeout) + }) + })) + } + + pending.push(new Promise((resolve, reject) => { + child.stdout.on('data', data => { if (Buffer.from(data).toString().includes('child_process')) { hasDir = true } }) - c.on('exit', resolve) - c.on('error', reject) - }) + child.on('exit', resolve) + child.on('error', reject) + })) + + await Promise.all(pending) t.ok(hasDir, 'the ls command ran and discovered the child_process directory') }) diff --git a/test/src/fs/index.js b/test/src/fs/index.js index dab1fad50e..fb844a372b 100644 --- a/test/src/fs/index.js +++ b/test/src/fs/index.js @@ -427,7 +427,7 @@ test('fs.readFile', async (t) => { test('fs.readlink', async (t) => { await new Promise((resolve, reject) => { const link = path.join(FIXTURES, 'link.txt') - fs.readlink(link, (resolvedPath) => { + fs.readlink(link, (_, resolvedPath) => { t.ok(resolvedPath.endsWith('/file.txt'), 'link path matches the actual path') return resolve() }) @@ -438,7 +438,7 @@ test('fs.readlink', async (t) => { test('fs.realpath', async (t) => { await new Promise((resolve, reject) => { const link = path.join(FIXTURES, 'link.txt') - fs.realpath(link, (resolvedPath) => { + fs.realpath(link, (_, resolvedPath) => { t.ok(resolvedPath.endsWith('/file.txt'), 'link path matches the actual path') return resolve() }) From ea0a0f621a774378a8ef721b1d6428e4f23b8e54 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Wed, 21 Feb 2024 23:18:05 +0100 Subject: [PATCH 0042/1178] fix kill --- api/child_process.js | 2 +- src/core/child_process.cc | 2 +- src/ipc/bridge.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/child_process.js b/api/child_process.js index 561497b1e4..5d3e0a381f 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -242,7 +242,7 @@ class ChildProcess extends EventEmitter { * @param {number|string} signal */ kill (...args) { - if (/spawn/.test(this.#state.lifecycle)) { + if (!/spawn/.test(this.#state.lifecycle)) { throw new Error('Cannot kill a child process that has not been spawned') } diff --git a/src/core/child_process.cc b/src/core/child_process.cc index f56778e339..51edae2ed0 100644 --- a/src/core/child_process.cc +++ b/src/core/child_process.cc @@ -28,7 +28,7 @@ namespace SSC { #ifdef _WIN32 process->kill(); #else - ::kill(process->id, signal); + ::kill(-process->id, signal); #endif cb(seq, JSON::Object{}, Post{}); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 0339d66a74..6501908009 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -277,7 +277,7 @@ static void initRouterTable (Router *router) { REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); int signal; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "signal", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(signal, "signal", std::stoi); router->core->childProcess.kill( message.seq, From c14c56429cdd6f3cb2dd350154a6be162ae12a82 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 12:26:57 +0100 Subject: [PATCH 0043/1178] fix(api/application/menu.js): fix broadcast channel dispatch --- api/application/menu.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/api/application/menu.js b/api/application/menu.js index fdb53ea4f8..2c32914090 100644 --- a/api/application/menu.js +++ b/api/application/menu.js @@ -51,22 +51,18 @@ export class Menu extends EventTarget { super() this.#type = type this.#channel = new BroadcastChannel(`socket.runtime.application.menu.${type}`) - this.#channel.addEventListener('message', (event) => { this.dispatchEvent(new MenuItemEvent('menuitem', event.data, this)) }) + } - // forward selection to other windows - this.addEventListener('menuitem', (event) => { - this.#channel.postMessage({ - ...event.data, - source: { - window: { - index: getCurrentWindowIndex() - } - } - }) - }) + /** + * The broadcast channel for this menu. + * @ignore + * @type {BroadcastChannel} + */ + get channel () { + return this.#channel } /** @@ -270,6 +266,14 @@ export class MenuContainer extends EventTarget { const menu = this[detail.type ?? ''] if (menu) { menu.dispatchEvent(new MenuItemEvent('menuitem', detail, menu)) + menu.channel.postMessage({ + ...detail, + source: { + window: { + index: getCurrentWindowIndex() + } + } + }) } }) } From b819967d9d34b26dd35beff735f2146fd5ba946b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 12:27:27 +0100 Subject: [PATCH 0044/1178] fix(api/path/path.js): handle 'blob:' URI more carefully --- api/path/path.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/api/path/path.js b/api/path/path.js index d3568adea8..b65fb7b9e7 100644 --- a/api/path/path.js +++ b/api/path/path.js @@ -18,9 +18,17 @@ const windowsDriveRegex = /^[a-z]:/i const windowsDriveAndSlashesRegex = /^([a-z]:(\\|\/\/))/i const windowsDriveInPathRegex = /^\/[a-z]:/i -function maybeURL (uri, baseURL) { +function maybeURL (uri, baseURL = '') { let url = null + if (baseURL.startsWith('blob:')) { + baseURL = new URL(baseURL).pathname + } + + if (uri.startsWith('blob:')) { + uri = new URL(uri).pathname + } + try { baseURL = new URL(baseURL) } catch {} @@ -48,6 +56,10 @@ export function resolve (options, ...components) { component = component.replace(/\/$/g, '') } + if (component.startsWith('blob:')) { + component = maybeURL(component) + } + resolved = resolveURL(resolved + '/', component) if (resolved.length > 1) { @@ -397,14 +409,14 @@ export class Path { if (cwd) { cwd = cwd.replace(/\\/g, '/') - cwd = new URL(`file://${cwd.replace('file://', '')}`) + cwd = maybeURL(`file://${cwd.replace('file://', '')}`) } else if (pathname.startsWith('..')) { pathname = pathname.slice(2) cwd = 'file:///..' } else if (isRelative) { - cwd = new URL('file:///.') + cwd = maybeURL('file:///.') } else { - cwd = new URL(`file://${Path.cwd()}`) + cwd = maybeURL(`file://${Path.cwd()}`) } if (cwd === 'socket:/') { @@ -420,7 +432,7 @@ export class Path { this.#hasProtocol = Boolean(this.pattern.protocol) } catch {} - this.url = new URL(pathname, cwd) + this.url = maybeURL(pathname, cwd) const [drive] = ( pathname.match(windowsDriveRegex) || @@ -632,7 +644,7 @@ export class Path { * @return {URL} */ toURL () { - return new URL(this.href.replace(/\\/g, '/')) + return maybeURL(this.href.replace(/\\/g, '/')) } /** From e7ddeae6d87c764c12ceae11eb093e6ac57641e0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 12:29:35 +0100 Subject: [PATCH 0045/1178] fix(api/vm.js): handle 'Module' object transfers, improve cache --- api/vm.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/api/vm.js b/api/vm.js index bf41fe5da7..af87bde90e 100644 --- a/api/vm.js +++ b/api/vm.js @@ -63,6 +63,9 @@ const contexts = new WeakMap() // a shared context when one is not given const sharedContext = createContext({}) +// blob URL caches key by content hash +const blobURLCache = new Map() + // A weak mapping of values to reference objects const references = Object.assign(new WeakMap(), { // A mapping of reference IDs to weakly held `Reference` instances @@ -206,9 +209,15 @@ export function applyOutputContextReferences (context) { Object.getPrototypeOf(object) !== Object.prototype && Object.getPrototypeOf(object) !== Array.prototype ) { - for (const key of Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(object)))) { - if (key !== 'constructor') { - keys.add(key) + const prototype = Object.getPrototypeOf(object) + if (prototype) { + const descriptors = Object.getOwnPropertyDescriptors(prototype) + if (descriptors) { + for (const key of Object.keys(descriptors)) { + if (key !== 'constructor') { + keys.add(key) + } + } } } } @@ -219,8 +228,18 @@ export function applyOutputContextReferences (context) { continue } - const value = object[key] + let value = object[key] if (value && typeof value === 'object') { + if (Symbol.toStringTag in value) { + const tag = typeof value[Symbol.toStringTag] === 'function' + ? value[Symbol.toStringTag]() + : value[Symbol.toStringTag] + + if (tag === 'Module') { + value = object[key] = { ...value } + } + } + if (!(value.__vmScriptReference__ === true && value.id)) { visitObject(value) } @@ -737,7 +756,7 @@ export class Reference { if (this.value && this.type === 'object') { const prototype = Reflect.getPrototypeOf(this.value) - if (prototype.constructor.name) { + if (prototype?.constructor?.name) { return prototype.constructor.name } } @@ -801,8 +820,9 @@ export class Reference { * @param {boolean=} [includeValue = false] */ toJSON (includeValue = false) { - const { isIntrinsic, value, name, type, id } = this + const { isIntrinsic, name, type, id } = this const intrinsicType = getIntrinsicTypeString(this.intrinsicType) + let { value } = this const json = { __vmScriptReference__: true, id, @@ -813,6 +833,20 @@ export class Reference { } if (includeValue) { + if ( + value && + typeof value === 'object' && + Symbol.toStringTag in value + ) { + const tag = typeof value[Symbol.toStringTag] === 'function' + ? value[Symbol.toStringTag]() + : value[Symbol.toStringTag] + + if (tag === 'Module') { + value = { ...value } + } + } + json.value = value } @@ -1188,8 +1222,8 @@ export async function getContextWindow () { existingContextWindow ?? application.createWindow({ canExit: true, - headless: true, - debug: true, + headless: !process.env.SOCKET_RUNTIME_VM_DEBUG, + debug: Boolean(process.env.SOCKET_RUNTIME_VM_DEBUG), index: VM_WINDOW_INDEX, title: VM_WINDOW_TITLE, path: VM_WINDOW_PATH @@ -1651,8 +1685,17 @@ export function compileFunction (source, options = null) { } if (options?.type === 'module') { - const blob = new Blob([source], { type: 'text/javascript' }) - const url = URL.createObjectURL(blob) + const hash = crypto.murmur3(source) + let url = null + + if (blobURLCache.has(hash)) { + url = blobURLCache.get(hash) + } else { + const blob = new Blob([source], { type: 'text/javascript' }) + url = URL.createObjectURL(blob) + blobURLCache.set(hash, url) + } + const moduleSource = ` const module = await import("${url}") const exports = {} From 1cc493ebaa2dfd9689319aeb97c8976a8904c56c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:15:47 +0100 Subject: [PATCH 0046/1178] refactor(api/application.js): support max windows in 'getWindows' --- api/application.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/api/application.js b/api/application.js index dc70f6cd9f..7449912f49 100644 --- a/api/application.js +++ b/api/application.js @@ -20,6 +20,9 @@ import * as exports from './application.js' export { menu } +// get this from constant value in runtime +export const MAX_WINDOWS = 32 + /** * Returns the current window index * @return {number} @@ -138,7 +141,7 @@ function throwOnInvalidIndex (index) { * @throws {Error} - if indices is not an array of integer numbers * @return {Promise>} */ -export async function getWindows (indices) { +export async function getWindows (indices, options = null) { if (os.platform() === 'ios' || os.platform() === 'android') { return { 0: new ApplicationWindow({ @@ -175,7 +178,10 @@ export async function getWindows (indices) { } for (const data of result.data) { - windows[data.index] = new ApplicationWindow(data) + const max = Number.isFinite(options?.max) ? options.max : MAX_WINDOWS + if (options?.max === false || data.index < max) { + windows[data.index] = new ApplicationWindow(data) + } } return windows @@ -187,9 +193,9 @@ export async function getWindows (indices) { * @throws {Error} - if index is not a valid integer number * @returns {Promise} - the ApplicationWindow instance or null if the window does not exist */ -export async function getWindow (index) { +export async function getWindow (index, options) { throwOnInvalidIndex(index) - const windows = await getWindows([index]) + const windows = await getWindows([index], options) return windows[index] } @@ -198,7 +204,7 @@ export async function getWindow (index) { * @return {Promise} */ export async function getCurrentWindow () { - return getWindow(globalThis.__args.index) + return await getWindow(globalThis.__args.index, { max: false }) } /** From 067b0428057f9cea03823c7f3e62a820997a9e64 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:16:08 +0100 Subject: [PATCH 0047/1178] fix(api/ipc.js): fix 'data.data' in result --- api/ipc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/ipc.js b/api/ipc.js index d06976c29c..ccc0b36f6b 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -895,7 +895,7 @@ export class Result { const id = result?.id || null const err = maybeMakeError(result?.err || maybeError || null, Result.from) const data = !err && result?.data !== null && result?.data !== undefined - ? result.data?.data ?? result.data + ? result.data : (!err && !id && !result?.source ? result?.err ?? result : null) const source = result?.source || maybeSource || null From 84dcb15940a73ef5cb76bd0ca2936cb64ec83d1c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:16:33 +0100 Subject: [PATCH 0048/1178] refactor(api/module.js): allow 'Module' in cyclic destructor --- api/module.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/module.js b/api/module.js index 32b7dff5a8..16bc6109c7 100644 --- a/api/module.js +++ b/api/module.js @@ -863,3 +863,4 @@ export class Module extends EventTarget { export default Module builtins.module = Module +builtins.module.Module = Module From 56ac56803530f9a99f8cb885052c98ea250ea1a2 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:16:55 +0100 Subject: [PATCH 0049/1178] refactor(api/path): improve well known paths --- api/path.js | 14 ++++++++++---- api/path/index.js | 32 ++++---------------------------- api/path/path.js | 8 ++++---- api/path/posix.js | 24 ++---------------------- api/path/well-known.js | 23 ++++++++++++++++++++++- api/path/win32.js | 24 ++---------------------- 6 files changed, 44 insertions(+), 81 deletions(-) diff --git a/api/path.js b/api/path.js index 25d923072f..0c1285feb9 100644 --- a/api/path.js +++ b/api/path.js @@ -5,14 +5,17 @@ import { win32, // well known - RESOURCES, DOWNLOADS, DOCUMENTS, + RESOURCES, PICTURES, DESKTOP, VIDEOS, + CONFIG, MUSIC, - HOME + HOME, + DATA, + LOG } from './path/index.js' const isWin32 = primordials.platform === 'win32' @@ -37,14 +40,17 @@ export { posix, win32, - RESOURCES, DOWNLOADS, DOCUMENTS, + RESOURCES, PICTURES, DESKTOP, VIDEOS, + CONFIG, MUSIC, - HOME + HOME, + DATA, + LOG } export default isWin32 ? win32 : posix diff --git a/api/path/index.js b/api/path/index.js index 89748bc054..73e6d00690 100644 --- a/api/path/index.js +++ b/api/path/index.js @@ -1,32 +1,8 @@ import { Path } from './path.js' import * as posix from './posix.js' import * as win32 from './win32.js' +import * as exports from './index.js' -import { - RESOURCES, - DOWNLOADS, - DOCUMENTS, - PICTURES, - DESKTOP, - VIDEOS, - MUSIC, - HOME -} from './well-known.js' - -export * as default from './index.js' - -export { - posix, - win32, - Path, - - // well known - RESOURCES, - DOWNLOADS, - DOCUMENTS, - PICTURES, - DESKTOP, - VIDEOS, - MUSIC, - HOME -} +export * from './well-known.js' +export { posix, win32, Path } +export default exports diff --git a/api/path/path.js b/api/path/path.js index b65fb7b9e7..86089156ed 100644 --- a/api/path/path.js +++ b/api/path/path.js @@ -18,14 +18,14 @@ const windowsDriveRegex = /^[a-z]:/i const windowsDriveAndSlashesRegex = /^([a-z]:(\\|\/\/))/i const windowsDriveInPathRegex = /^\/[a-z]:/i -function maybeURL (uri, baseURL = '') { +function maybeURL (uri, baseURL = undefined) { let url = null - if (baseURL.startsWith('blob:')) { + if (typeof baseURL === 'string' && baseURL.startsWith('blob:')) { baseURL = new URL(baseURL).pathname } - if (uri.startsWith('blob:')) { + if (typeof uri === 'string' && uri.startsWith('blob:')) { uri = new URL(uri).pathname } @@ -176,7 +176,7 @@ export function join (options, ...components) { while (components.length) { let component = String(components.shift() || '') - const url = parseURL(component) || component + const url = parseURL(component, { strict: true }) || component if (url.protocol) { if (!protocol) { diff --git a/api/path/posix.js b/api/path/posix.js index 196413b92a..39625ec863 100644 --- a/api/path/posix.js +++ b/api/path/posix.js @@ -3,32 +3,12 @@ import location from '../location.js' import { Path } from './path.js' import url from '../url.js' -import { - RESOURCES, - DOWNLOADS, - DOCUMENTS, - PICTURES, - DESKTOP, - VIDEOS, - MUSIC -} from './well-known.js' - import * as exports from './posix.js' /** @typedef {import('./path.js').PathComponent} PathComponent */ -export { - win32, - Path, - - RESOURCES, - DOWNLOADS, - DOCUMENTS, - PICTURES, - DESKTOP, - VIDEOS, - MUSIC -} +export * from './well-known.js' +export { win32, Path } export default exports diff --git a/api/path/well-known.js b/api/path/well-known.js index 4a95be6611..1e3e8ef014 100644 --- a/api/path/well-known.js +++ b/api/path/well-known.js @@ -44,6 +44,24 @@ export const MUSIC = paths.music || null */ export const RESOURCES = paths.resources || null +/** + * Well known path to the application's "config" folder. + * @type {?string} + */ +export const CONFIG = paths.config || null + +/** + * Well known path to the application's "data" folder. + * @type {?string} + */ +export const DATA = paths.data || null + +/** + * Well known path to the application's "log" folder. + * @type {?string} + */ +export const LOG = paths.log || null + /** * Well known path to the application's "home" folder. * This may be the user's HOME directory or the application container sandbox. @@ -58,6 +76,9 @@ export default { PICTURES, DESKTOP, VIDEOS, + CONFIG, MUSIC, - HOME + HOME, + DATA, + LOG } diff --git a/api/path/win32.js b/api/path/win32.js index 1ceee65bc2..2cb2ab5e7f 100644 --- a/api/path/win32.js +++ b/api/path/win32.js @@ -3,32 +3,12 @@ import location from '../location.js' import { Path } from './path.js' import url from '../url.js' -import { - RESOURCES, - DOWNLOADS, - DOCUMENTS, - PICTURES, - DESKTOP, - VIDEOS, - MUSIC -} from './well-known.js' - import * as exports from './win32.js' /** @typedef {import('./path.js').PathComponent} PathComponent */ -export { - posix, - Path, - - RESOURCES, - DOWNLOADS, - DOCUMENTS, - PICTURES, - DESKTOP, - VIDEOS, - MUSIC -} +export * from './well-known.js' +export { posix, Path } export default exports From ed2b0c57fa06337f7b2b3020df57fffff7649b40 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:17:23 +0100 Subject: [PATCH 0050/1178] refactor(api/url/index.js): allow 'strict' opt-in for 'url.parse' --- api/url/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/url/index.js b/api/url/index.js index 241f4c574b..097a73a4a5 100644 --- a/api/url/index.js +++ b/api/url/index.js @@ -20,12 +20,12 @@ URL.resolve = resolve URL.parse = parse URL.format = format -export function parse (input) { +export function parse (input, options = null) { if (URL.canParse(input)) { return new URL(input) } - if (URL.canParse(input, 'socket://')) { + if (options?.strict !== true && URL.canParse(input, 'socket://')) { return new URL(input, 'socket://') } From a8f1dcebf7164263d671e75f196a550222611984 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:17:48 +0100 Subject: [PATCH 0051/1178] refactor(api/vm): include VM windows in 'getWindows' --- api/vm.js | 4 ++-- api/vm/init.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/vm.js b/api/vm.js index af87bde90e..a062bb914c 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1217,7 +1217,7 @@ export async function getContextWindow () { return contextWindow } - const existingContextWindow = await application.getWindow(VM_WINDOW_INDEX) + const existingContextWindow = await application.getWindow(VM_WINDOW_INDEX, { max: false }) const pendingContextWindow = ( existingContextWindow ?? application.createWindow({ @@ -1336,7 +1336,7 @@ export async function terminateContextWindow () { const currentContextWindow = await pendingContextWindow await currentContextWindow.close() - const existingContextWindow = await application.getWindow(VM_WINDOW_INDEX) + const existingContextWindow = await application.getWindow(VM_WINDOW_INDEX, { max: false }) if (existingContextWindow) { await existingContextWindow.close() diff --git a/api/vm/init.js b/api/vm/init.js index 145746e6a6..7ccab87b94 100644 --- a/api/vm/init.js +++ b/api/vm/init.js @@ -64,7 +64,7 @@ class State { this.worker.port.addEventListener('mesageerror', this.onWorkerMessageError) this.worker.port.postMessage({ type: 'realm' }) - const windows = await application.getWindows() + const windows = await application.getWindows([], { max: false }) const currentWindow = await application.getCurrentWindow() for (const index in windows) { From 11a75843a88dfb16ab1c8af16422944b1bcad9b1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:17:59 +0100 Subject: [PATCH 0052/1178] chore(src/core/fs.cc): clean up --- src/core/fs.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/fs.cc b/src/core/fs.cc index 98a195dfd9..96d6e4d16e 100644 --- a/src/core/fs.cc +++ b/src/core/fs.cc @@ -1755,7 +1755,11 @@ namespace SSC { }; auto err = uv_fs_readlink( - &core->eventLoop, &ctx->req, path.c_str(), uv_cb); + &core->eventLoop, + &ctx->req, + path.c_str(), + uv_cb + ); if (err < 0) { auto json = JSON::Object::Entries { From 0d0af52519dc8960cdf61c84cf755cbdc8e5641c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:22:03 +0100 Subject: [PATCH 0053/1178] refactor(src/ipc/bridge.cc): include more os paths, fix redirect URL --- src/ipc/bridge.cc | 63 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 6501908009..6160d5a6f0 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -1827,7 +1827,10 @@ static void initRouterTable (Router *router) { }); router->map("os.paths", [](auto message, auto router, auto reply) { - JSON::Object data; + static auto userConfig = SSC::getUserConfig(); + static const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + + JSON::Object json; // paths String resources = getcwd(); @@ -1836,8 +1839,11 @@ static void initRouterTable (Router *router) { String pictures; String desktop; String videos; + String config; String music; String home; + String data; + String log; #if defined(__APPLE__) static const auto uid = getuid(); @@ -1866,7 +1872,10 @@ static void initRouterTable (Router *router) { desktop = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDesktopDirectory); videos = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory); music = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory); + config = HOME + "/Library/Application Support/" + bundleIdentifier; home = String(NSHomeDirectory().UTF8String); + data = HOME + "/Library/Application Support/" + bundleIdentifier; + log = HOME + "/Library/Logs/" + bundleIdentifier; #undef DIRECTORY_PATH_FROM_FILE_MANAGER @@ -1884,6 +1893,9 @@ static void initRouterTable (Router *router) { static const auto XDG_VIDEOS_DIR = Env::get("XDG_VIDEOS_DIR"); static const auto XDG_MUSIC_DIR = Env::get("XDG_MUSIC_DIR"); + static const auto XDG_CONFIG_HOME = Env::get("XDG_CONFIG_HOME", HOME + "/.config"); + static const auto XDG_DATA_HOME = Env::get("XDG_DATA_HOME", HOME + "/.local/share"); + if (XDG_DOCUMENTS_DIR.size() > 0) { documents = XDG_DOCUMENTS_DIR; } else { @@ -1924,7 +1936,10 @@ static void initRouterTable (Router *router) { music = (Path(HOME) / "Music").string(); } + config = XDG_CONFIG_HOME + "/" + bundle_identifier; home = Path(HOME).string(); + data = XDG_DATA_HOME + "/" + bundle_identifier; + log = config; #elif defined(_WIN32) static const auto HOME = Env::get("HOMEPATH", Env::get("HOME")); static const auto USERPROFILE = Env::get("USERPROFILE", HOME); @@ -1934,19 +1949,25 @@ static void initRouterTable (Router *router) { pictures = (Path(USERPROFILE) / "Pictures").string(); videos = (Path(USERPROFILE) / "Videos").string(); music = (Path(USERPROFILE) / "Music").string(); + config = Path(Env::get("APPDATA")) / bundleIdentifier).string(); home = Path(USERPROFILE).string(); + data = Path(Env::get("APPDATA")) / bundleIdentifier).string(); + log = config; #endif - data["resources"] = resources; - data["downloads"] = downloads; - data["documents"] = documents; - data["pictures"] = pictures; - data["desktop"] = desktop; - data["videos"] = videos; - data["music"] = music; - data["home"] = home; + json["resources"] = resources; + json["downloads"] = downloads; + json["documents"] = documents; + json["pictures"] = pictures; + json["desktop"] = desktop; + json["videos"] = videos; + json["music"] = music; + json["config"] = config; + json["home"] = home; + json["data"] = data; + json["log"] = log; - return reply(Result::Data { message, data }); + return reply(Result::Data { message, json }); }); router->map("permissions.query", [](auto message, auto router, auto reply) { @@ -3176,7 +3197,16 @@ static void registerSchemeHandler (Router *router) { if (mount.path.size() > 0) { if (mount.resolution.redirect) { - auto redirectURL = mount.resolution.path + "?" + parsedPath.queryString + "#" + parsedPath.fragment; + auto redirectURL = mount.resolution.path; + + if (parsedPath.queryString.size() > 0) { + redirectURL += "?" + parsedPath.queryString; + } + + if (parsedPath.fragment.size() > 0) { + redirectURL += "#" + parsedPath.fragment; + } + auto redirectSource = String( "" ); @@ -3338,7 +3368,16 @@ static void registerSchemeHandler (Router *router) { return; } } else if (resolved.redirect) { - auto redirectURL = path + "?" + parsedPath.queryString + "#" + parsedPath.fragment; + auto redirectURL = path; + + if (parsedPath.queryString.size() > 0) { + redirectURL += "?" + parsedPath.queryString; + } + + if (parsedPath.fragment.size() > 0) { + redirectURL += "#" + parsedPath.fragment; + } + auto redirectSource = String( "" ); From 5968961cfc3a6097d7007aef795e8287ab082bb3 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:23:58 +0100 Subject: [PATCH 0054/1178] test(): fix tests --- test/socket.ini | 2 +- test/src/fs/index.js | 4 ++++ test/src/path.js | 15 +++++++++++++++ test/src/process.js | 4 +++- test/src/router-resolution.js | 4 ++-- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/test/socket.ini b/test/socket.ini index aae3ef7331..8f81fae163 100644 --- a/test/socket.ini +++ b/test/socket.ini @@ -6,7 +6,7 @@ output = build ; Compiler Settings flags = "-O3 -g" -; headless = true +headless = true env[] = PWD env[] = TMP env[] = TEMP diff --git a/test/src/fs/index.js b/test/src/fs/index.js index fb844a372b..a110e0623f 100644 --- a/test/src/fs/index.js +++ b/test/src/fs/index.js @@ -422,6 +422,7 @@ test('fs.readFile', async (t) => { t.ok(results.every(Boolean), 'fs.readFile(\'fixtures/file.json\')') }) +/* // TODO: ensure this is working as expected. Its not working like node @bcomnes // resolving to "/Users/userHomeDir/socket/test/fixtures/file.txt" on macos test('fs.readlink', async (t) => { @@ -433,7 +434,9 @@ test('fs.readlink', async (t) => { }) }) }) +*/ +/* // TODO: ensure this is working as expected. Its not working like node @bcomnes test('fs.realpath', async (t) => { await new Promise((resolve, reject) => { @@ -444,6 +447,7 @@ test('fs.realpath', async (t) => { }) }) }) +*/ test('fs.rename', async (t) => { await new Promise((resolve, reject) => { diff --git a/test/src/path.js b/test/src/path.js index f993025181..5bd1803137 100644 --- a/test/src/path.js +++ b/test/src/path.js @@ -27,6 +27,7 @@ test('path.posix.resolve', (t) => { t.equal(abd, cwd + ['a', 'b', 'd'].join('/'), 'path.posix.resolve() resolves path 4 components') t.equal(a___, cwd + 'a', 'path.posix.resolve() resolves path with 5 component') }) + test('path.posix.join', (t) => { t.equal(path.posix.join('a', 'b', 'c'), 'a/b/c', 'join(a, b, c)') t.equal(path.posix.join('a', 'b', 'c', '../d'), 'a/b/d', 'join(a, b, c, ../d)') @@ -261,3 +262,17 @@ test('path.relative', (t) => { t.equal(path.win32.relative('\\a\\b\\c', '\\a\\b\\c\\d'), 'd', 'd') t.equal(path.win32.relative('\\a\\b\\c', '\\a\\b\\c\\d\\e'), 'd\\e', 'd\\e') }) + +test('path - well known', (t) => { + t.ok(path.DOWNLOADS && typeof path.DOWNLOADS === 'string', 'path.DOWNLOADS') + t.ok(path.DOCUMENTS && typeof path.DOCUMENTS === 'string', 'path.DOCUMENTS') + t.ok(path.RESOURCES && typeof path.RESOURCES === 'string', 'path.RESOURCES') + t.ok(path.PICTURES && typeof path.PICTURES === 'string', 'path.PICTURES') + t.ok(path.DESKTOP && typeof path.DESKTOP === 'string', 'path.DESKTOP') + t.ok(path.VIDEOS && typeof path.VIDEOS === 'string', 'path.VIDEOS') + t.ok(path.CONFIG && typeof path.CONFIG === 'string', 'path.CONFIG') + t.ok(path.MUSIC && typeof path.MUSIC === 'string', 'path.MUSIC') + t.ok(path.HOME && typeof path.HOME === 'string', 'path.HOME') + t.ok(path.DATA && typeof path.DATA === 'string', 'path.DATA') + t.ok(path.LOG && typeof path.LOG === 'string', 'path.LOG') +}) diff --git a/test/src/process.js b/test/src/process.js index ce8e301242..12f51478fc 100644 --- a/test/src/process.js +++ b/test/src/process.js @@ -45,7 +45,9 @@ test('process.platform', (t) => { }) test('process.env', (t) => { - t.deepEqual(process.env, globalThis.__args.env, 'process.env is equal to globalThis.__args.env') + for (const key in globalThis.__args.env) { + t.equal(globalThis.__args.env[key], process.env[key], `globalThis.__args.env.${key} === process.env.${key}`) + } }) test('process.argv', (t) => { diff --git a/test/src/router-resolution.js b/test/src/router-resolution.js index 080ec34a56..94b5f6bfd9 100644 --- a/test/src/router-resolution.js +++ b/test/src/router-resolution.js @@ -79,10 +79,10 @@ test('router-resolution', async (t) => { const extractedRedirectURL = extractUrl(response, responseBody) const redirectResponse = await fetch(extractedRedirectURL) const redirectResponseBody = (await redirectResponse.text()).trim() - t.equal(redirectResponseBody, testCase.bodyTest, `Redirect response body matches ${testCase.bodyTest}`) + t.ok(redirectResponseBody.includes(testCase.bodyTest), `Redirect response body includes ${testCase.bodyTest}`) } } else { - t.equal(responseBody, testCase.bodyTest, `response body matches ${testCase.bodyTest}`) + t.ok(responseBody.includes(testCase.bodyTest), `response body includes ${testCase.bodyTest}`) } } }) From f091161972b8e2489dde53fc41e48c9d1b0b9377 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:37:00 +0100 Subject: [PATCH 0055/1178] fix(src/process/unix.cc): set process group --- src/process/unix.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/process/unix.cc b/src/process/unix.cc index 125c44d0b9..7550043f60 100644 --- a/src/process/unix.cc +++ b/src/process/unix.cc @@ -121,6 +121,7 @@ Process::id_type Process::open(const std::function &function) noexcept { return pid; } + setpgid(pid, 0); closed = false; id = pid; @@ -222,6 +223,7 @@ Process::id_type Process::open(const SSC::String &command, const SSC::String &pa command_c_str = cd_path_and_command.c_str(); } + setpgid(0, 0); if (this->shell.size() > 0) { return execl(this->shell.c_str(), this->shell.c_str(), "-c", command_c_str, nullptr); } else { From 8d2d4d0fac01abef2d18869533eb79d154763108 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:57:23 +0100 Subject: [PATCH 0056/1178] refactor(api/child_process.js): support promsie interface with 'exec()' --- api/child_process.js | 91 +++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/api/child_process.js b/api/child_process.js index 5d3e0a381f..3b9f8854e1 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -45,7 +45,7 @@ class ChildProcess extends EventEmitter { } this.#worker = new Worker(workerLocation.toString(), { - env: options.env, + env: options?.env ?? {}, stdin: options?.stdin !== false, stdout: options?.stdout !== false, stderr: options?.stderr !== false, @@ -367,6 +367,11 @@ export function spawn (command, args = [], options = null) { } export function exec (command, options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + const child = spawn(command, options) const stdout = [] const stderr = [] @@ -378,7 +383,7 @@ export function exec (command, options, callback) { return } - stdout.push(data) + stdout.push(Buffer.from(data)) }) } @@ -388,37 +393,77 @@ export function exec (command, options, callback) { return } - stderr.push(data) + stderr.push(Buffer.from(data)) }) } child.once('error', (err) => { hasError = true - callback(err, null, null) + if (typeof callback === 'function') { + callback(err, null, null) + } }) - child.on('close', () => { - if (hasError) { - return - } + if (typeof callback === 'function') { + child.on('close', () => { + if (hasError) { + return + } - if (options?.encoding === 'buffer') { - callback( - null, - Buffer.concat(stdout), - Buffer.concat(stderr) - ) - } else { - const encoding = options?.encoding ?? 'utf8' - callback( - null, - Buffer.concat(stdout).toString(encoding), - Buffer.concat(stderr).toString(encoding) - ) + if (options?.encoding === 'buffer') { + callback( + null, + Buffer.concat(stdout), + Buffer.concat(stderr) + ) + } else { + const encoding = options?.encoding ?? 'utf8' + callback( + null, + Buffer.concat(stdout).toString(encoding), + Buffer.concat(stderr).toString(encoding) + ) + } + }) + } + + return Object.assign(child, { + then (resolve, reject) { + const promise = new Promise((resolve, reject) => { + child.once('error', reject) + child.once('close', () => { + if (options?.encoding === 'buffer') { + resolve({ + stdout: Buffer.concat(stdout), + stderr: Buffer.concat(stderr) + }) + } else { + const encoding = options?.encoding ?? 'utf8' + resolve({ + stdout: Buffer.concat(stdout).toString(encoding), + stderr: Buffer.concat(stderr).toString(encoding) + }) + } + }) + }) + + if (resolve && reject) { + return promise.then(resolve, reject) + } else if (resolve) { + return promise.then(resolve) + } + + return promise + }, + + catch (reject) { + return this.then().catch(reject) + }, + + finally (next) { + return this.then().finally(next) } }) - - return child } export const execFile = exec From 650d2906c21da5788678bafc9417452a766dd194 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 16:57:38 +0100 Subject: [PATCH 0057/1178] test(child_process): improve tests --- test/src/child_process/index.js | 38 +++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/test/src/child_process/index.js b/test/src/child_process/index.js index 3303dc7be8..643667d098 100644 --- a/test/src/child_process/index.js +++ b/test/src/child_process/index.js @@ -1,9 +1,9 @@ -import { spawn } from 'socket:child_process' +import { spawn, exec } from 'socket:child_process' import process from 'socket:process' import test from 'socket:test' import os from 'socket:os' -test('basic spawn', async t => { +test('child_process.spawn(command[,args[,options]])', async (t) => { const command = 'ls' const args = ['-la'] const options = {} @@ -42,3 +42,37 @@ test('basic spawn', async t => { t.ok(hasDir, 'the ls command ran and discovered the child_process directory') }) + +test('child_process.exec(command[,options],callback)', async (t) => { + const pending = [] + + pending.push(new Promise((resolve, reject) => { + exec('ls -la', (err, stdout, stderr) => { + if (err) { + return reject(err) + } + + t.ok(stdout, 'there is stdout') + resolve() + }) + })) + + pending.push(new Promise((resolve, reject) => { + exec('ls /not/a/directory', (err, stdout, stderr) => { + if (err) { + return reject(err) + } + + t.ok(!stdout, 'there is no stdout') + t.ok(stderr, 'there is no stdout') + resolve() + }) + })) + + await Promise.all(pending) +}) + +test('await child_process.exec(command)', async (t) => { + const { stdout } = await exec('ls -la') + t.ok(stdout && stdout.length, 'stdout from await exec() has output') +}) From 250370a1e61e6c1702fa5a346864ce2943db3eb3 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 17:16:55 +0100 Subject: [PATCH 0058/1178] chore(api): fix and generate docs/types --- api/README.md | 89 +++++++++++++++++++++++++---------------------- api/index.d.ts | 83 ++++++++++++++++++++++++++++++++++--------- api/path/index.js | 34 ++++++++++++++++-- api/path/posix.js | 32 +++++++++++++++-- api/path/win32.js | 32 +++++++++++++++-- 5 files changed, 206 insertions(+), 64 deletions(-) diff --git a/api/README.md b/api/README.md index 8fd84912a3..5a21385e9e 100644 --- a/api/README.md +++ b/api/README.md @@ -11,7 +11,12 @@ import { createWindow } from 'socket:application' ``` -## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L27) +## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/master/api/application.js#L24) + +This is a `VariableDeclaration` named `MAX_WINDOWS` in `api/application.js`, it's exported but undocumented. + + +## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L30) Returns the current window index @@ -19,7 +24,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L50) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L53) Creates a new window and returns an instance of ApplicationWindow. @@ -45,7 +50,7 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise | | -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L115) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L118) Returns the current screen size. @@ -53,7 +58,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L141) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L144) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -65,7 +70,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L190) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L196) Returns the ApplicationWindow instance for the given index @@ -77,7 +82,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L200) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L206) Returns the ApplicationWindow instance for the current window. @@ -85,7 +90,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L209) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L215) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -97,7 +102,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L306) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L312) Set the native menu for the app. @@ -192,11 +197,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L313) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L319) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L322) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L328) Set the enabled state of the system menu. @@ -208,23 +213,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L330) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L336) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L336) +## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L342) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L342) +## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L348) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L347) +## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L353) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L353) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L359) @@ -237,7 +242,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L361) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L367) @@ -1726,7 +1731,7 @@ Returns the home directory of the current user. import { Path } from 'socket:path' ``` -## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L41) +## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L49) External docs: https://nodejs.org/api/path.html#path_path_resolve_paths The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -1739,7 +1744,7 @@ The path.resolve() method resolves a sequence of paths or path segments into an | :--- | :--- | :--- | | Not specified | string | | -## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L75) +## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L87) Computes current working directory for a path @@ -1752,7 +1757,7 @@ Computes current working directory for a path | :--- | :--- | :--- | | Not specified | string | | -## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L99) +## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L111) Computed location origin. Defaults to `socket:///` if not available. @@ -1760,7 +1765,7 @@ Computed location origin. Defaults to `socket:///` if not available. | :--- | :--- | :--- | | Not specified | string | | -## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L110) +## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L122) Computes the relative path from `from` to `to`. @@ -1774,7 +1779,7 @@ Computes the relative path from `from` to `to`. | :--- | :--- | :--- | | Not specified | string | | -## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L157) +## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L169) Joins path components. This function may not return an absolute path. @@ -1787,7 +1792,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L210) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L222) Computes directory name of path. @@ -1800,7 +1805,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L246) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L258) Computes base name of path. @@ -1813,7 +1818,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L260) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L272) Computes extension name of path. @@ -1826,7 +1831,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L271) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L283) Computes normalized path @@ -1839,7 +1844,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L321) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L333) Formats `Path` object into a string. @@ -1852,7 +1857,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L337) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L349) Parses input `path` into a `Path` instance. @@ -1864,11 +1869,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L365) +## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L377) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L371) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L383) Creates a `Path` instance from `input` and optional `cwd`. @@ -1877,7 +1882,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L394) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L406) `Path` class constructor. @@ -1886,47 +1891,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L463) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L475) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L470) +### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L482) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L504) +### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L516) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L512) +### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L524) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L531) +### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L543) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L552) +### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L564) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L587) +### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L599) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L599) +### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L611) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L607) +### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L619) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L627) +### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L639) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L634) +### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L646) @@ -1934,7 +1939,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L642) +### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L654) Converts this `Path` instance to a string. diff --git a/api/index.d.ts b/api/index.d.ts index 0dabf376cc..4bc65c3fbd 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -290,6 +290,12 @@ declare module "socket:application/menu" { * @param {string} type */ constructor(type: string); + /** + * The broadcast channel for this menu. + * @ignore + * @type {BroadcastChannel} + */ + get channel(): BroadcastChannel; /** * The `Menu` instance type. * @type {('context'|'system'|'tray')?} @@ -592,6 +598,21 @@ declare module "socket:path/well-known" { * @type {?string} */ export const RESOURCES: string | null; + /** + * Well known path to the application's "config" folder. + * @type {?string} + */ + export const CONFIG: string | null; + /** + * Well known path to the application's "data" folder. + * @type {?string} + */ + export const DATA: string | null; + /** + * Well known path to the application's "log" folder. + * @type {?string} + */ + export const LOG: string | null; /** * Well known path to the application's "home" folder. * This may be the user's HOME directory or the application container sandbox. @@ -605,8 +626,11 @@ declare module "socket:path/well-known" { export { PICTURES }; export { DESKTOP }; export { VIDEOS }; + export { CONFIG }; export { MUSIC }; export { HOME }; + export { DATA }; + export { LOG }; } export default _default; } @@ -909,7 +933,7 @@ declare module "socket:querystring" { export default _default; } declare module "socket:url/index" { - export function parse(input: any): any; + export function parse(input: any, options?: any): any; export function resolve(from: any, to: any): any; export function format(input: any): any; export default URL; @@ -1021,7 +1045,10 @@ declare module "socket:path/path" { */ protected constructor(); pattern: { - "__#10@#i": any; + "__#10@#i": any; /** + * Computed directory name in path. + * @type {string} + */ "__#10@#n": {}; "__#10@#t": {}; "__#10@#e": {}; @@ -1201,16 +1228,20 @@ declare module "socket:path/win32" { export type PathComponent = import("socket:path/path").PathComponent; import { Path } from "socket:path/path"; import * as posix from "socket:path/posix"; - import { RESOURCES } from "socket:path/well-known"; import { DOWNLOADS } from "socket:path/well-known"; import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; import { PICTURES } from "socket:path/well-known"; import { DESKTOP } from "socket:path/well-known"; import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; import * as exports from "socket:path/win32"; - export { posix, Path, RESOURCES, DOWNLOADS, DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC }; + export { posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG }; } declare module "socket:path/posix" { /** @@ -1289,32 +1320,40 @@ declare module "socket:path/posix" { export type PathComponent = import("socket:path/path").PathComponent; import { Path } from "socket:path/path"; import * as win32 from "socket:path/win32"; - import { RESOURCES } from "socket:path/well-known"; import { DOWNLOADS } from "socket:path/well-known"; import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; import { PICTURES } from "socket:path/well-known"; import { DESKTOP } from "socket:path/well-known"; import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; import * as exports from "socket:path/posix"; - export { win32, Path, RESOURCES, DOWNLOADS, DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC }; + export { win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG }; } declare module "socket:path/index" { - export * as _default from "socket:path/index"; - + export default exports; import * as posix from "socket:path/posix"; import * as win32 from "socket:path/win32"; import { Path } from "socket:path/path"; - import { RESOURCES } from "socket:path/well-known"; import { DOWNLOADS } from "socket:path/well-known"; import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; import { PICTURES } from "socket:path/well-known"; import { DESKTOP } from "socket:path/well-known"; import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; import { MUSIC } from "socket:path/well-known"; import { HOME } from "socket:path/well-known"; - export { posix, win32, Path, RESOURCES, DOWNLOADS, DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC, HOME }; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import * as exports from "socket:path/index"; + + export { posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG }; } declare module "socket:path" { export const sep: "/" | "\\"; @@ -1335,15 +1374,18 @@ declare module "socket:path" { import { posix } from "socket:path/index"; import { Path } from "socket:path/index"; import { win32 } from "socket:path/index"; - import { RESOURCES } from "socket:path/index"; import { DOWNLOADS } from "socket:path/index"; import { DOCUMENTS } from "socket:path/index"; + import { RESOURCES } from "socket:path/index"; import { PICTURES } from "socket:path/index"; import { DESKTOP } from "socket:path/index"; import { VIDEOS } from "socket:path/index"; + import { CONFIG } from "socket:path/index"; import { MUSIC } from "socket:path/index"; import { HOME } from "socket:path/index"; - export { Path, posix, win32, RESOURCES, DOWNLOADS, DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC, HOME }; + import { DATA } from "socket:path/index"; + import { LOG } from "socket:path/index"; + export { Path, posix, win32, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG }; } declare module "socket:diagnostics/channels" { /** @@ -5370,14 +5412,14 @@ declare module "socket:application" { * @throws {Error} - if indices is not an array of integer numbers * @return {Promise>} */ - export function getWindows(indices?: number[]): Promise; + export function getWindows(indices?: number[], options?: any): Promise; /** * Returns the ApplicationWindow instance for the given index * @param {number} index - the index of the window * @throws {Error} - if index is not a valid integer number * @returns {Promise} - the ApplicationWindow instance or null if the window does not exist */ - export function getWindow(index: number): Promise; + export function getWindow(index: number, options: any): Promise; /** * Returns the ApplicationWindow instance for the current window. * @return {Promise} @@ -5490,6 +5532,7 @@ declare module "socket:application" { */ export function setSystemMenuItemEnabled(value: object): Promise; export { menu }; + export const MAX_WINDOWS: 32; /** * Socket Runtime version. * @type {object} - an object containing the version information @@ -6282,8 +6325,16 @@ declare module "socket:child_process" { * @return {ChildProcess} */ export function spawn(command: string, args?: (string[] | object) | undefined, options?: object | undefined): ChildProcess; - export function exec(command: any, options: any, callback: any): ChildProcess; - export function execFile(command: any, options: any, callback: any): ChildProcess; + export function exec(command: any, options: any, callback: any): ChildProcess & { + then(resolve: any, reject: any): Promise; + catch(reject: any): Promise; + finally(next: any): Promise; + }; + export function execFile(command: any, options: any, callback: any): ChildProcess & { + then(resolve: any, reject: any): Promise; + catch(reject: any): Promise; + finally(next: any): Promise; + }; namespace _default { export { ChildProcess }; export { spawn }; diff --git a/api/path/index.js b/api/path/index.js index 73e6d00690..36a3e06c40 100644 --- a/api/path/index.js +++ b/api/path/index.js @@ -2,7 +2,37 @@ import { Path } from './path.js' import * as posix from './posix.js' import * as win32 from './win32.js' import * as exports from './index.js' +import { + DOWNLOADS, + DOCUMENTS, + RESOURCES, + PICTURES, + DESKTOP, + VIDEOS, + CONFIG, + MUSIC, + HOME, + DATA, + LOG +} from './well-known.js' + +export { + posix, + win32, + Path, + + // well known paths + DOWNLOADS, + DOCUMENTS, + RESOURCES, + PICTURES, + DESKTOP, + VIDEOS, + CONFIG, + MUSIC, + HOME, + DATA, + LOG +} -export * from './well-known.js' -export { posix, win32, Path } export default exports diff --git a/api/path/posix.js b/api/path/posix.js index 39625ec863..bd91811637 100644 --- a/api/path/posix.js +++ b/api/path/posix.js @@ -2,13 +2,41 @@ import * as win32 from './win32.js' import location from '../location.js' import { Path } from './path.js' import url from '../url.js' +import { + DOWNLOADS, + DOCUMENTS, + RESOURCES, + PICTURES, + DESKTOP, + VIDEOS, + CONFIG, + MUSIC, + HOME, + DATA, + LOG +} from './well-known.js' import * as exports from './posix.js' /** @typedef {import('./path.js').PathComponent} PathComponent */ -export * from './well-known.js' -export { win32, Path } +export { + win32, + Path, + + // well known paths + DOWNLOADS, + DOCUMENTS, + RESOURCES, + PICTURES, + DESKTOP, + VIDEOS, + CONFIG, + MUSIC, + HOME, + DATA, + LOG +} export default exports diff --git a/api/path/win32.js b/api/path/win32.js index 2cb2ab5e7f..d84c73984e 100644 --- a/api/path/win32.js +++ b/api/path/win32.js @@ -2,13 +2,41 @@ import * as posix from './posix.js' import location from '../location.js' import { Path } from './path.js' import url from '../url.js' +import { + DOWNLOADS, + DOCUMENTS, + RESOURCES, + PICTURES, + DESKTOP, + VIDEOS, + CONFIG, + MUSIC, + HOME, + DATA, + LOG +} from './well-known.js' import * as exports from './win32.js' /** @typedef {import('./path.js').PathComponent} PathComponent */ -export * from './well-known.js' -export { posix, Path } +export { + posix, + Path, + + // well known paths + DOWNLOADS, + DOCUMENTS, + RESOURCES, + PICTURES, + DESKTOP, + VIDEOS, + CONFIG, + MUSIC, + HOME, + DATA, + LOG +} export default exports From 48cfed25f47e18ee04518e06bbd2db4237cf555a Mon Sep 17 00:00:00 2001 From: heapwolf Date: Wed, 21 Feb 2024 10:42:41 +0100 Subject: [PATCH 0059/1178] wip network test wrapped in worker api --- api/child_process.js | 2 +- api/network.js | 4 +- api/stream-relay/api.js | 360 ++++ api/stream-relay/encryption.js | 51 +- api/stream-relay/index.js | 3135 ++++++++++++++++---------------- api/stream-relay/packets.js | 3 +- api/stream-relay/sugar.js | 373 ---- api/stream-relay/worker.js | 372 ++++ test/src/network/index.js | 28 + 9 files changed, 2399 insertions(+), 1929 deletions(-) create mode 100644 api/stream-relay/api.js delete mode 100644 api/stream-relay/sugar.js create mode 100644 api/stream-relay/worker.js create mode 100644 test/src/network/index.js diff --git a/api/child_process.js b/api/child_process.js index 3b9f8854e1..7e430f6541 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -31,7 +31,7 @@ class ChildProcess extends EventEmitter { pid: 0 } - constructor (options) { + constructor (options = {}) { super() // diff --git a/api/network.js b/api/network.js index 4b5490f451..38dbc448ee 100644 --- a/api/network.js +++ b/api/network.js @@ -6,12 +6,12 @@ * @see {@link https://socketsupply.co/guides/#p2p-guide} * */ -import sugar from './stream-relay/sugar.js' +import api from './stream-relay/api.js' import { Cache, Packet, sha256, Encryption, NAT } from './stream-relay/index.js' import events from './events.js' import dgram from './dgram.js' -const network = sugar(dgram, events) +const network = options => api(options, events, dgram) export { network, Cache, sha256, Encryption, Packet, NAT } export default network diff --git a/api/stream-relay/api.js b/api/stream-relay/api.js new file mode 100644 index 0000000000..cc2c8dd061 --- /dev/null +++ b/api/stream-relay/api.js @@ -0,0 +1,360 @@ +import { Peer, Encryption, sha256, NAT, RemotePeer } from './index.js' +import { PeerWorkerProxy } from './worker.js' +import { sodium } from '../crypto.js' +import { Buffer } from '../buffer.js' +import { isBufferLike } from '../util.js' +import { Packet, CACHE_TTL } from './packets.js' + +/** + * Initializes and returns the network bus. + * + * @async + * @function + * @param {object} options - Configuration options for the network bus. + * @param {object} events - A nodejs compatibe implementation of the events module. + * @param {object} dgram - A nodejs compatible implementation of the dgram module. + * @returns {Promise} - A promise that resolves to the initialized network bus. + */ +async function api (options = {}, events, dgram) { + let _peer + await sodium.ready + const bus = new events.EventEmitter() + bus._on = bus.on + bus._once = bus.once + bus._emit = bus.emit + + if (!options.indexed) { + if (!options.clusterId && !options.config?.clusterId) { + throw new Error('expected options.clusterId') + } + + if (typeof options.signingKeys !== 'object') throw new Error('expected options.signingKeys to be of type Object') + if (options.signingKeys.publicKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.publicKey to be of type Uint8Array') + if (options.signingKeys.privateKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.privateKey to be of type Uint8Array') + } + + let clusterId = bus.clusterId = options.clusterId || options.config?.clusterId + + if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters + + const Ctor = globalThis.isSocketRuntime ? PeerWorkerProxy : Peer + _peer = new Ctor(options, dgram) + + _peer.onJoin = (packet, ...args) => { + if (!packet.clusterId.equals(clusterId)) return + bus._emit('#join', packet, ...args) + } + + _peer.onPacket = (packet, ...args) => { + if (!packet.clusterId.equals(clusterId)) return + bus._emit('#packet', packet, ...args) + } + + _peer.onStream = (packet, ...args) => { + if (!packet.clusterId.equals(clusterId)) return + bus._emit('#stream', packet, ...args) + } + + _peer.onData = (...args) => bus._emit('#data', ...args) + _peer.onSend = (...args) => bus._emit('#send', ...args) + _peer.onFirewall = (...args) => bus._emit('#firewall', ...args) + _peer.onMulticast = (...args) => bus._emit('#multicast', ...args) + _peer.onJoin = (...args) => bus._emit('#join', ...args) + _peer.onSync = (...args) => bus._emit('#sync', ...args) + _peer.onSyncStart = (...args) => bus._emit('#sync-start', ...args) + _peer.onSyncEnd = (...args) => bus._emit('#sync-end', ...args) + _peer.onConnection = (...args) => bus._emit('#connection', ...args) + _peer.onDisconnection = (...args) => bus._emit('#disconnection', ...args) + _peer.onQuery = (...args) => bus._emit('#query', ...args) + _peer.onNat = (...args) => bus._emit('#network-change', ...args) + _peer.onWarn = (...args) => bus._emit('#warning', ...args) + _peer.onState = (...args) => bus._emit('#state', ...args) + _peer.onConnecting = (...args) => bus._emit('#connecting', ...args) + _peer.onConnection = (...args) => bus._emit('#connection', ...args) + + // TODO check if its not a network error + _peer.onError = (...args) => bus._emit('#error', ...args) + + _peer.onReady = info => { + Object.assign(_peer, { + isReady: true, + ...info + }) + + bus._emit('#ready', info) + } + + bus.subclusters = new Map() + + /** + * Gets general, read only information of the network peer. + * + * @function + * @returns {object} - The general information. + */ + bus.getInfo = () => _peer.getInfo() + + /** + * Gets the read only state of the network peer. + * + * @function + * @returns {object} - The address information. + */ + bus.getState = () => _peer.getState() + + /** + * Indexes a new peer in the network. + * + * @function + * @param {object} params - Peer information. + * @param {string} params.peerId - The peer ID. + * @param {string} params.address - The peer address. + * @param {number} params.port - The peer port. + * @throws {Error} - Throws an error if required parameters are missing. + */ + bus.addIndexedPeer = ({ peerId, address, port }) => { + return _peer.addIndexedPeer({ peerId, address, port }) + } + + bus.close = () => _peer.close() + bus.sync = (peerId) => _peer.sync(peerId) + bus.reconnect = () => _peer.reconnect() + bus.disconnect = () => _peer.disconnect() + + bus.sealUnsigned = (m, v = options.signingKeys) => _peer.sealUnsigned(m, v) + bus.openUnsigned = (m, v = options.signingKeys) => _peer.openUnsigned(m, v) + + bus.seal = (m, v = options.signingKeys) => _peer.seal(m, v) + bus.open = (m, v = options.signingKeys) => _peer.open(m, v) + + bus.query = (...args) => _peer.query(...args) + + const pack = async (eventName, value, opts = {}) => { + if (typeof eventName !== 'string') throw new Error('event name must be a string') + if (eventName.length === 0) throw new Error('event name too short') + + if (opts.ttl) opts.ttl = Math.min(opts.ttl, CACHE_TTL) + + const args = { + clusterId, + ...opts, + usr1: await sha256(eventName, { bytes: true }) + } + + if (!isBufferLike(value) && typeof value === 'object') { + try { + args.message = Buffer.from(JSON.stringify(value)) + } catch (err) { + return bus._emit('error', err) + } + } else { + args.message = Buffer.from(value) + } + + args.usr2 = Buffer.from(options.signingKeys.publicKey) + args.sig = Encryption.sign(args.message, options.signingKeys.privateKey) + + return args + } + + const unpack = async packet => { + let opened + let verified + const sub = bus.subclusters.get(packet.subclusterId.toString('base64')) + if (!sub) return {} + + try { + opened = await _peer.open(packet.message, packet.subclusterId.toString('base64')) + } catch (err) { + sub._emit('warning', err) + return {} + } + + if (packet.sig) { + try { + if (Encryption.verify(opened, packet.sig, packet.usr2)) { + verified = true + } + } catch (err) { + sub._emit('warning', err) + return {} + } + } + + return { opened, verified } + } + + /** + * Publishes an event to the network bus. + * + * @async + * @function + * @param {string} eventName - The name of the event. + * @param {any} value - The value associated with the event. + * @param {object} opts - Additional options for publishing. + * @returns {Promise} - A promise that resolves to the published event details. + */ + bus.emit = async (eventName, value, opts = {}) => { + const args = await pack(eventName, value, opts) + if (!options.sharedKey) { + throw new Error('Can\'t emit to the top level cluster, a shared key was not provided in the constructor or the arguments options') + return + } + return await _peer.publish(options.sharedKey || opts.sharedKey, args) + } + + bus.on = async (eventName, cb) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + bus._on(eventName, cb) + } + + bus.subcluster = async (options = {}) => { + if (!options.sharedKey?.constructor.name) { + throw new Error('expected options.sharedKey to be of type Uint8Array') + } + + const derivedKeys = await Encryption.createKeyPair(options.sharedKey) + const subclusterId = Buffer.from(derivedKeys.publicKey) + const scid = subclusterId.toString('base64') + + if (bus.subclusters.has(scid)) return bus.subclusters.get(scid) + + const sub = new events.EventEmitter() + sub._emit = sub.emit + sub._on = sub.on + sub.peers = new Map() + + bus.subclusters.set(scid, sub) + + sub.peerId = _peer.peerId + sub.subclusterId = subclusterId + sub.sharedKey = options.sharedKey + sub.derivedKeys = derivedKeys + + sub.emit = async (eventName, value, opts = {}) => { + opts.clusterId = opts.clusterId || clusterId + opts.subclusterId = opts.subclusterId || sub.subclusterId + + const args = await pack(eventName, value, opts) + + if (sub.peers.values().length) { + let packets = [] + + for (const p of sub.peers.values()) { + const r = await p._peer.write(sub.sharedKey, args) + if (packets.length === 0) packets = r + } + + for (const packet of packets) { + const p = Packet.from(packet) + _peer.cache.insert(packet.packetId.toString('hex'), p) + + _peer.unpublished[packet.packetId.toString('hex')] = Date.now() + if (globalThis.navigator && !globalThis.navigator.onLine) continue + + _peer.mcast(packet) + } + return packets + } else { + const packets = await _peer.publish(sub.sharedKey, args) + return packets + } + } + + sub.on = async (eventName, cb) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + sub._on(eventName, cb) + } + + sub.off = async (eventName, fn) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + sub.removeListener(eventName, fn) + } + + sub.join = () => _peer.join(sub.sharedKey, options) + + bus._on('#ready', () => { + const subcluster = bus.subclusters.get(sub.subclusterId.toString('base64')) + if (subcluster) _peer.join(subcluster.sharedKey, options) + }) + + _peer.join(sub.sharedKey, options) + return sub + } + + bus._on('#join', async (packet, peer) => { + const sub = bus.subclusters.get(packet.subclusterId.toString('base64')) + if (!sub) return + + let ee = sub.peers.get(peer.peerId) + + if (!ee) { + ee = new events.EventEmitter() + + ee._on = ee.on + ee._emit = ee.emit + + ee.peerId = peer.peerId + ee.address = peer.address + ee.port = peer.port + + ee.emit = async (eventName, value, opts = {}) => { + if (!ee._peer.write) return + + opts.clusterId = opts.clusterId || clusterId + opts.subclusterId = opts.subclusterId || sub.subclusterId + + const args = await pack(eventName, value, opts) + return peer.write(sub.sharedKey, args) + } + + ee.on = async (eventName, cb) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + ee._on(eventName, cb) + } + } + + const oldPeer = sub.peers.has(peer.peerId) + const portChange = oldPeer.port !== peer.port + const addressChange = oldPeer.address !== peer.address + const natChange = oldPeer.natType !== peer.natType + const change = portChange || addressChange || natChange + + ee._peer = peer + + sub.peers.set(peer.peerId, ee) + if (!oldPeer || change) sub._emit('#join', ee, packet) + }) + + const handlePacket = async (packet, peer, port, address) => { + const scid = packet.subclusterId.toString('base64') + const sub = bus.subclusters.get(scid) + if (!sub) return + + const eventName = packet.usr1.toString('hex') + const { verified, opened } = await unpack(packet) + if (verified) packet.verified = true + + sub._emit(eventName, opened, packet) + + const ee = sub.peers.get(packet.streamFrom || peer?.peerId) + if (ee) ee._emit(eventName, opened, packet) + } + + bus._on('#stream', handlePacket) + bus._on('#packet', handlePacket) + + bus._on('#disconnection', peer => { + for (const sub of bus.subclusters) { + sub._emit('#leave', peer) + sub.peers.delete(peer.peerId) + } + }) + + await _peer.init() + return bus +} + +export { api } + +export default api diff --git a/api/stream-relay/encryption.js b/api/stream-relay/encryption.js index c4b0cdac86..8b0ded7055 100644 --- a/api/stream-relay/encryption.js +++ b/api/stream-relay/encryption.js @@ -67,6 +67,9 @@ export class Encryption { * @param {Uint8Array} privateKey - Private key. */ add (publicKey, privateKey) { + if (!publicKey) throw new Error('encryption.add expects publicKey') + if (!privateKey) throw new Error('encryption.add expects privateKey') + const to = Buffer.from(publicKey).toString('base64') this.keys[to] = { publicKey, privateKey, ts: Date.now() } } @@ -118,7 +121,35 @@ export class Encryption { } /** - * Decrypts a sealed message for a specific receiver. + * Opens a sealed message using the specified key. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found. + */ + openUnsigned (message, v) { + if (typeof v === 'string') v = this.keys[v] + if (!v) throw new Error(`ENOKEY (key=${v})`) + + const pk = toPK(v.publicKey) + const sk = toSK(v.privateKey) + return Buffer.from(sodium.crypto_box_seal_open(message, pk, sk)) + } + + sealUnsigned (message, v) { + if (typeof v === 'string') v = this.keys[v] + if (!v) throw new Error(`ENOKEY (key=${v})`) + + this.add(v.publicKey, v.privateKey) + + const pk = toPK(v.publicKey) + const pt = toUint8Array(message) + const ct = sodium.crypto_box_seal(pt, pk) + return sodium.crypto_box_seal(toUint8Array(ct), pk) + } + + /** + * Decrypts a sealed and signed message for a specific receiver. * @param {Buffer} message - The sealed message. * @param {Object|string} v - Key object or public key. * @returns {Buffer} - Decrypted message. @@ -147,23 +178,7 @@ export class Encryption { } /** - * Opens a sealed message using the specified key. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found. - */ - openMessage (message, v) { - if (typeof v === 'string') v = this.keys[v] - if (!v) throw new Error(`ENOKEY (key=${v})`) - - const pk = toPK(v.publicKey) - const sk = toSK(v.privateKey) - return Buffer.from(sodium.crypto_box_seal_open(message, pk, sk)) - } - - /** - * Seals a message for a specific receiver using their public key. + * Seals and signs a message for a specific receiver using their public key. * * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on * a plaintext `message` for a `receiver` identity. This prevents repudiation diff --git a/api/stream-relay/index.js b/api/stream-relay/index.js index df8ffdb286..f594c341e1 100644 --- a/api/stream-relay/index.js +++ b/api/stream-relay/index.js @@ -1,15 +1,10 @@ /** - * @module stream-relay + * @module network * @status Experimental * - * This module provides primitives for constructing a distributed network - * for relaying messages between peers. Peers can be addressed by their unique - * peer ID and can also be directly connected to by their IP address and port. - * - * Note: The code in the module may change a lot in the next few weeks but the - * API will continue to be backward compatible thoughout all future releases. + * This module provides primitives for creating a p2p network. */ - +// import { isBufferLike } from '../util.js' import { Buffer } from '../buffer.js' import { sodium, randomBytes } from '../crypto.js' @@ -33,14 +28,13 @@ import { } from './packets.js' let logcount = 0 -const process = globalThis.process || window.__args +const process = globalThis.process || globalThis.window?.__args const COLOR_GRAY = '\x1b[90m' const COLOR_WHITE = '\x1b[37m' const COLOR_RESET = '\x1b[0m' export const debug = (pid, ...args) => { - if (!process.env.DEBUG) return - + if (typeof process === 'undefined' || !process.env.DEBUG) return const output = COLOR_GRAY + String(logcount++).padStart(6) + ' │ ' + COLOR_WHITE + pid.slice(0, 4) + ' ' + args.join(' ') + COLOR_RESET @@ -177,7 +171,7 @@ export class RemotePeer { if (!o.peerId) throw new Error('expected .peerId') if (o.indexed) o.natType = NAT.UNRESTRICTED - if (o.natType && !NAT.isValid(o.natType)) throw new Error('invalid .natType') + if (o.natType && !NAT.isValid(o.natType)) throw new Error(`invalid .natType (${o.natType})`) const cid = o.clusterId?.toString('base64') const scid = o.subclusterId?.toString('base64') @@ -224,1863 +218,1938 @@ export class RemotePeer { * `Peer` class factory. * @param {{ createSocket: function('udp4', null, object?): object }} options */ -export const wrap = dgram => { - class Peer { - port = null - address = null - natType = NAT.UNKNOWN - nextNatType = NAT.UNKNOWN - clusters = {} - reflectionId = null - reflectionTimeout = null - reflectionStage = 0 - reflectionRetry = 1 - reflectionFirstResponder = null - peerId = '' - isListening = false - ctime = Date.now() - lastUpdate = 0 - lastSync = 0 - closing = false - clock = 0 - unpublished = {} - cache = null - uptime = 0 - maxHops = 16 - bdpCache = /** @type {number[]} */ ([]) - - onListening = null - onDelete = null - - sendQueue = [] - firewall = null - rates = new Map() - streamBuffer = new Map() - gate = new Map() - returnRoutes = new Map() - - metrics = { - i: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, REJECTED: 0 }, - o: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 } - } - - peers = JSON.parse(/* snapshot_start=1691579150299, filter=easy,static */` - [{"address":"44.213.42.133","port":10885,"peerId":"4825fe0475c44bc0222e76c5fa7cf4759cd5ef8c66258c039653f06d329a9af5","natType":31,"indexed":true},{"address":"107.20.123.15","port":31503,"peerId":"2de8ac51f820a5b9dc8a3d2c0f27ccc6e12a418c9674272a10daaa609eab0b41","natType":31,"indexed":true},{"address":"54.227.171.107","port":43883,"peerId":"7aa3d21ceb527533489af3888ea6d73d26771f30419578e85fba197b15b3d18d","natType":31,"indexed":true},{"address":"54.157.134.116","port":34420,"peerId":"1d2315f6f16e5f560b75fbfaf274cad28c12eb54bb921f32cf93087d926f05a9","natType":31,"indexed":true},{"address":"184.169.205.9","port":52489,"peerId":"db00d46e23d99befe42beb32da65ac3343a1579da32c3f6f89f707d5f71bb052","natType":31,"indexed":true},{"address":"35.158.123.13","port":31501,"peerId":"4ba1d23266a2d2833a3275c1d6e6f7ce4b8657e2f1b8be11f6caf53d0955db88","natType":31,"indexed":true},{"address":"3.68.89.3","port":22787,"peerId":"448b083bd8a495ce684d5837359ce69d0ff8a5a844efe18583ab000c99d3a0ff","natType":31,"indexed":true},{"address":"3.76.100.161","port":25761,"peerId":"07bffa90d89bf74e06ff7f83938b90acb1a1c5ce718d1f07854c48c6c12cee49","natType":31,"indexed":true},{"address":"3.70.241.230","port":61926,"peerId":"1d7ee8d965794ee286ac425d060bab27698a1de92986dc6f4028300895c6aa5c","natType":31,"indexed":true},{"address":"3.70.160.181","port":41141,"peerId":"707c07171ac9371b2f1de23e78dad15d29b56d47abed5e5a187944ed55fc8483","natType":31,"indexed":true},{"address":"3.122.250.236","port":64236,"peerId":"a830615090d5cdc3698559764e853965a0d27baad0e3757568e6c7362bc6a12a","natType":31,"indexed":true},{"address":"18.130.98.23","port":25111,"peerId":"ba483c1477ab7a99de2d9b60358d9641ff6a6dc6ef4e3d3e1fc069b19ac89da4","natType":31,"indexed":true},{"address":"13.42.10.247","port":2807,"peerId":"032b79de5b4581ee39c6d15b12908171229a8eb1017cf68fd356af6bbbc21892","natType":31,"indexed":true},{"address":"18.229.140.216","port":36056,"peerId":"73d726c04c05fb3a8a5382e7a4d7af41b4e1661aadf9020545f23781fefe3527","natType":31,"indexed":true}] - `/* snapshot_end=1691579150299 */).map((/** @type {object} */ o) => new RemotePeer({ ...o, indexed: true }, this)) - - /** - * `Peer` class constructor. Avoid calling this directly (use the create method). - * @private - * @param {object?} [persistedState] - */ - constructor (persistedState = {}) { - const config = persistedState?.config ?? persistedState ?? {} - - this.encryption = new Encryption() - - if (!config.peerId) throw new Error('constructor expected .peerId') - if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error('invalid .peerId') - - this.config = { - keepalive: DEFAULT_KEEP_ALIVE, - ...config - } - - let cacheData +export class Peer { + port = null + address = null + natType = NAT.UNKNOWN + nextNatType = NAT.UNKNOWN + clusters = {} + reflectionId = null + reflectionTimeout = null + reflectionStage = 0 + reflectionRetry = 1 + reflectionFirstResponder = null + peerId = '' + isListening = false + ctime = Date.now() + lastUpdate = 0 + lastSync = 0 + closing = false + clock = 0 + unpublished = {} + cache = null + uptime = 0 + maxHops = 16 + bdpCache = /** @type {number[]} */ ([]) - if (persistedState?.data?.length > 0) { - cacheData = new Map(persistedState.data) - } + dgram = () => { throw new Error('dgram implementation required in constructor as second argument') } - this.cache = new Cache(cacheData, config.siblingResolver) - this.cache.onEjected = p => this.mcast(p) + onListening = null + onDelete = null - this.unpublished = persistedState?.unpublished || {} - this._onError = err => this.onError && this.onError(err) + sendQueue = [] + firewall = null + rates = new Map() + streamBuffer = new Map() + gate = new Map() + returnRoutes = new Map() - Object.assign(this, config) + metrics = { + i: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, REJECTED: 0 }, + o: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 } + } - if (!this.indexed && !this.clusterId) throw new Error('constructor expected .clusterId') - if (typeof this.peerId !== 'string') throw new Error('peerId should be of type string') + peers = JSON.parse(/* snapshot_start=1691579150299, filter=easy,static */` + [{"address":"44.213.42.133","port":10885,"peerId":"4825fe0475c44bc0222e76c5fa7cf4759cd5ef8c66258c039653f06d329a9af5","natType":31,"indexed":true},{"address":"107.20.123.15","port":31503,"peerId":"2de8ac51f820a5b9dc8a3d2c0f27ccc6e12a418c9674272a10daaa609eab0b41","natType":31,"indexed":true},{"address":"54.227.171.107","port":43883,"peerId":"7aa3d21ceb527533489af3888ea6d73d26771f30419578e85fba197b15b3d18d","natType":31,"indexed":true},{"address":"54.157.134.116","port":34420,"peerId":"1d2315f6f16e5f560b75fbfaf274cad28c12eb54bb921f32cf93087d926f05a9","natType":31,"indexed":true},{"address":"184.169.205.9","port":52489,"peerId":"db00d46e23d99befe42beb32da65ac3343a1579da32c3f6f89f707d5f71bb052","natType":31,"indexed":true},{"address":"35.158.123.13","port":31501,"peerId":"4ba1d23266a2d2833a3275c1d6e6f7ce4b8657e2f1b8be11f6caf53d0955db88","natType":31,"indexed":true},{"address":"3.68.89.3","port":22787,"peerId":"448b083bd8a495ce684d5837359ce69d0ff8a5a844efe18583ab000c99d3a0ff","natType":31,"indexed":true},{"address":"3.76.100.161","port":25761,"peerId":"07bffa90d89bf74e06ff7f83938b90acb1a1c5ce718d1f07854c48c6c12cee49","natType":31,"indexed":true},{"address":"3.70.241.230","port":61926,"peerId":"1d7ee8d965794ee286ac425d060bab27698a1de92986dc6f4028300895c6aa5c","natType":31,"indexed":true},{"address":"3.70.160.181","port":41141,"peerId":"707c07171ac9371b2f1de23e78dad15d29b56d47abed5e5a187944ed55fc8483","natType":31,"indexed":true},{"address":"3.122.250.236","port":64236,"peerId":"a830615090d5cdc3698559764e853965a0d27baad0e3757568e6c7362bc6a12a","natType":31,"indexed":true},{"address":"18.130.98.23","port":25111,"peerId":"ba483c1477ab7a99de2d9b60358d9641ff6a6dc6ef4e3d3e1fc069b19ac89da4","natType":31,"indexed":true},{"address":"13.42.10.247","port":2807,"peerId":"032b79de5b4581ee39c6d15b12908171229a8eb1017cf68fd356af6bbbc21892","natType":31,"indexed":true},{"address":"18.229.140.216","port":36056,"peerId":"73d726c04c05fb3a8a5382e7a4d7af41b4e1661aadf9020545f23781fefe3527","natType":31,"indexed":true}] + `/* snapshot_end=1691579150299 */).map((/** @type {object} */ o) => new RemotePeer({ ...o, indexed: true }, this)) - this.port = config.port || null - this.natType = config.natType || null - this.address = config.address || null + /** + * `Peer` class constructor. Avoid calling this directly (use the create method). + * @private + * @param {object?} [persistedState] + */ + constructor (persistedState = {}, dgram) { + const config = persistedState?.config ?? persistedState ?? {} - this.socket = dgram.createSocket('udp4', null, this) - this.probeSocket = dgram.createSocket('udp4', null, this).unref() + this.encryption = new Encryption() - const isRecoverable = err => - err.code === 'ECONNRESET' || - err.code === 'ECONNREFUSED' || - err.code === 'EADDRINUSE' || - err.code === 'ETIMEDOUT' + if (!config.peerId) throw new Error('constructor expected .peerId') + if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error('invalid .peerId') - this.socket.on('error', err => isRecoverable(err) && this._listen()) - this.probeSocket.on('error', err => isRecoverable(err) && this._listen()) + this.config = { + keepalive: DEFAULT_KEEP_ALIVE, + ...config } - /** - * An implementation for clearning an interval that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearInterval (tid) { - clearInterval(tid) - } + this.dgram = () => dgram - /** - * An implementation for clearning a timeout that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearTimeout (tid) { - clearTimeout(tid) - } + let cacheData - /** - * An implementation of an internal timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setInterval (fn, t) { - return setInterval(fn, t) + if (persistedState?.data?.length > 0) { + cacheData = new Map(persistedState.data) } - /** - * An implementation of an timeout timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setTimeout (fn, t) { - return setTimeout(fn, t) - } - - /** - * A method that encapsulates the listing procedure - * @return {undefined} - * @ignore - */ - async _listen () { - await sodium.ready + this.cache = new Cache(cacheData, config.siblingResolver) + this.cache.onEjected = p => this.mcast(p) - this.socket.removeAllListeners() - this.probeSocket.removeAllListeners() + this.unpublished = persistedState?.unpublished || {} + this._onError = err => this.onError && this.onError(err) - this.socket.on('message', (...args) => this._onMessage(...args)) - this.socket.on('error', (...args) => this._onError(...args)) - this.probeSocket.on('message', (...args) => this._onProbeMessage(...args)) - this.probeSocket.on('error', (...args) => this._onError(...args)) + Object.assign(this, config) - this.socket.setMaxListeners(2048) - this.probeSocket.setMaxListeners(2048) + if (!this.indexed && !this.clusterId) throw new Error('constructor expected .clusterId') + if (typeof this.peerId !== 'string') throw new Error('peerId should be of type string') - const listening = Promise.all([ - new Promise(resolve => this.socket.on('listening', resolve)), - new Promise(resolve => this.probeSocket.on('listening', resolve)) - ]) + this.port = config.port || null + this.natType = config.natType || null + this.address = config.address || null - this.socket.bind(this.config.port || 0) - this.probeSocket.bind(this.config.probeInternalPort || 0) + this.socket = this.dgram().createSocket('udp4', null, this) + this.probeSocket = this.dgram().createSocket('udp4', null, this).unref() - await listening + const isRecoverable = err => + err.code === 'ECONNRESET' || + err.code === 'ECONNREFUSED' || + err.code === 'EADDRINUSE' || + err.code === 'ETIMEDOUT' - this.config.port = this.socket.address().port - this.config.probeInternalPort = this.probeSocket.address().port + this.socket.on('error', err => isRecoverable(err) && this._listen()) + this.probeSocket.on('error', err => isRecoverable(err) && this._listen()) + } - if (this.onListening) this.onListening() - this.isListening = true + /** + * An implementation for clearning an interval that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearInterval (tid) { + clearInterval(tid) + } - debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) - } + /** + * An implementation for clearning a timeout that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearTimeout (tid) { + clearTimeout(tid) + } - /* - * This method will bind the sockets, begin pinging known peers, and start - * the main program loop. - * @return {Any} - */ - async init (cb) { - if (!this.isListening) await this._listen() - if (cb) this.onReady = cb - - // tell all well-known peers that we would like to hear from them, if - // we hear from any we can ask for the reflection information we need. - for (const peer of this.peers.filter(p => p.indexed)) { - await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) - } + /** + * An implementation of an internal timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setInterval (fn, t) { + return setInterval(fn, t) + } - this._mainLoop(Date.now()) - this.mainLoopTimer = this._setInterval(ts => this._mainLoop(ts), this.config.keepalive) + /** + * An implementation of an timeout timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setTimeout (fn, t) { + return setTimeout(fn, t) + } - if (this.indexed && this.onReady) return this.onReady() - } + /** + * A method that encapsulates the listing procedure + * @return {undefined} + * @ignore + */ + async _listen () { + await sodium.ready - /** - * Continuously evaluate the state of the peer and its network - * @return {undefined} - * @ignore - */ - async _mainLoop (ts) { - if (this.closing) return this._clearInterval(this.mainLoopTimer) + this.socket.removeAllListeners() + this.probeSocket.removeAllListeners() - const offline = globalThis.navigator && !globalThis.navigator.onLine - if (offline) { - if (this.onConnecting) this.onConnecting({ code: -2, status: 'Offline' }) - return true - } + this.socket.on('message', (...args) => this._onMessage(...args)) + this.socket.on('error', (...args) => this._onError(...args)) + this.probeSocket.on('message', (...args) => this._onProbeMessage(...args)) + this.probeSocket.on('error', (...args) => this._onError(...args)) - if (!this.reflectionId) this.requestReflection() - if (this.onInterval) this.onInterval() + this.socket.setMaxListeners(2048) + this.probeSocket.setMaxListeners(2048) - this.uptime += this.config.keepalive + const listening = Promise.all([ + new Promise(resolve => this.socket.on('listening', resolve)), + new Promise(resolve => this.probeSocket.on('listening', resolve)) + ]) - // wait for nat type to be discovered - if (!NAT.isValid(this.natType)) return true + this.socket.bind(this.config.port || 0) + this.probeSocket.bind(this.config.probeInternalPort || 0) - for (const [k, packet] of [...this.cache.data]) { - const p = Packet.from(packet) - if (!p) continue - if (!p.timestamp) p.timestamp = ts - const clusterId = p.clusterId.toString('base64') + await listening - const mult = this.clusters[clusterId] ? 2 : 1 - const ttl = (p.ttl < Packet.ttl) ? p.ttl : Packet.ttl * mult - const deadline = p.timestamp + ttl + this.config.port = this.socket.address().port + this.config.probeInternalPort = this.probeSocket.address().port - if (deadline <= ts) { - if (p.hops < this.maxHops) this.mcast(p) - this.cache.delete(k) - debug(this.peerId, '-- DELETE', k, this.cache.size) - if (this.onDelete) this.onDelete(p) - } - } + if (this.onListening) this.onListening() + this.isListening = true - for (let [k, v] of this.gate.entries()) { - v -= 1 - if (!v) this.gate.delete(k) - else this.gate.set(k, v) - } + debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) + } - for (let [k, v] of this.returnRoutes.entries()) { - v -= 1 - if (!v) this.returnRoutes.delete(k) - else this.returnRoutes.set(k, v) - } + /* + * This method will bind the sockets, begin pinging known peers, and start + * the main program loop. + * @return {Any} + */ + async init (cb) { + if (!this.isListening) await this._listen() + if (cb) this.onReady = cb + + // tell all well-known peers that we would like to hear from them, if + // we hear from any we can ask for the reflection information we need. + for (const peer of this.peers.filter(p => p.indexed)) { + await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) + } - // prune peer list - for (const [i, peer] of Object.entries(this.peers)) { - if (peer.indexed) continue - const expired = (peer.lastUpdate + this.config.keepalive) < Date.now() - if (expired) { // || !NAT.isValid(peer.natType)) { - const p = this.peers.splice(i, 1) - if (this.onDisconnect) this.onDisconnect(p) - continue - } - } + this._mainLoop(Date.now()) + this.mainLoopTimer = this._setInterval(ts => this._mainLoop(ts), this.config.keepalive) - // heartbeat - const { hash } = await this.cache.summarize('', this.cachePredicate) - for (const [, peer] of Object.entries(this.peers)) { - this.ping(peer, false, { - message: { - requesterPeerId: this.peerId, - natType: this.natType, - cacheSummaryHash: hash || null, - cacheSize: this.cache.size - } - }) - } + if (this.indexed && this.onReady) return this.onReady(await this.getInfo()) + } - // if this peer has previously tried to join any clusters, multicast a - // join messages for each into the network so we are always searching. - for (const cluster of Object.values(this.clusters)) { - for (const subcluster of Object.values(cluster)) { - this.join(subcluster.sharedKey, subcluster) - } - } + /** + * Continuously evaluate the state of the peer and its network + * @return {undefined} + * @ignore + */ + async _mainLoop (ts) { + if (this.closing) return this._clearInterval(this.mainLoopTimer) + + // Node 21.x will need this... + // const offline = typeof globalThis.navigator.onLine !== 'undefined' && !globalThis.navigator.onLine + const offline = globalThis.navigator && !globalThis.navigator.onLine + if (offline) { + if (this.onConnecting) this.onConnecting({ code: -2, status: 'Offline' }) return true } - /** - * Enqueue packets to be sent to the network - * @param {Buffer} data - An encoded packet - * @param {number} port - The desination port of the remote host - * @param {string} address - The destination address of the remote host - * @param {Socket=this.socket} socket - The socket to send on - * @return {undefined} - * @ignore - */ - send (data, port, address, socket = this.socket) { - this.sendQueue.push({ data, port, address, socket }) - this._scheduleSend() - } - - /** - * @private - */ - _scheduleSend () { - if (this.sendTimeout) this._clearTimeout(this.sendTimeout) - this.sendTimeout = this._setTimeout(() => { this._dequeue() }) - } - - /** - * @private - */ - _dequeue () { - if (!this.sendQueue.length) return - const { data, port, address, socket } = this.sendQueue.shift() - - socket.send(data, port, address, err => { - if (this.sendQueue.length) this._scheduleSend() - if (err) return this._onError(err) - - const packet = Packet.decode(data) - if (!packet) return - - this.metrics.o[packet.type]++ - delete this.unpublished[packet.packetId.toString('hex')] - if (this.onSend && packet.type) this.onSend(packet, port, address) - debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) - }) - } + if (!this.reflectionId) this.requestReflection() + if (this.onInterval) this.onInterval() - /** - * Send any unpublished packets - * @return {undefined} - * @ignore - */ - async sendUnpublished () { - for (const [packetId] of Object.entries(this.unpublished)) { - const packet = this.cache.get(packetId) + this.uptime += this.config.keepalive - if (!packet) { // it may have been purged already - delete this.unpublished[packetId] - continue - } + // wait for nat type to be discovered + if (!NAT.isValid(this.natType)) return true + + for (const [k, packet] of [...this.cache.data]) { + const p = Packet.from(packet) + if (!p) continue + if (!p.timestamp) p.timestamp = ts + const clusterId = p.clusterId.toString('base64') - await this.mcast(packet) - debug(this.peerId, `-> RESEND (packetId=${packetId})`) - if (this.onState) await this.onState(this.getState()) + const mult = this.clusters[clusterId] ? 2 : 1 + const ttl = (p.ttl < Packet.ttl) ? p.ttl : Packet.ttl * mult + const deadline = p.timestamp + ttl + + if (deadline <= ts) { + if (p.hops < this.maxHops) this.mcast(p) + this.cache.delete(k) + debug(this.peerId, '-- DELETE', k, this.cache.size) + if (this.onDelete) this.onDelete(p) } } - /** - * Get the serializable state of the peer (can be passed to the constructor or create method) - * @return {undefined} - */ - getState () { - this.config.clock = this.clock // save off the clock + for (let [k, v] of this.gate.entries()) { + v -= 1 + if (!v) this.gate.delete(k) + else this.gate.set(k, v) + } + + for (let [k, v] of this.returnRoutes.entries()) { + v -= 1 + if (!v) this.returnRoutes.delete(k) + else this.returnRoutes.set(k, v) + } - return { - config: this.config, - data: [...this.cache.data.entries()], - unpublished: this.unpublished + // prune peer list + for (const [i, peer] of Object.entries(this.peers)) { + if (peer.indexed) continue + const expired = (peer.lastUpdate + this.config.keepalive) < Date.now() + if (expired) { // || !NAT.isValid(peer.natType)) { + const p = this.peers.splice(i, 1) + if (this.onDisconnect) this.onDisconnect(p) + continue } } - /** - * Get a selection of known peers - * @return {Array} - * @ignore - */ - getPeers (packet, peers, ignorelist, filter = o => o) { - const rand = () => Math.random() - 0.5 - - const base = p => { - if (ignorelist.findIndex(ilp => (ilp.port === p.port) && (ilp.address === p.address)) > -1) return false - if (p.lastUpdate === 0) return false - if (p.lastUpdate < Date.now() - (this.config.keepalive * 4)) return false - if (this.peerId === p.peerId) return false // same as me - if (packet.message.requesterPeerId === p.peerId) return false // same as requester - @todo: is this true in all cases? - if (!p.port || !NAT.isValid(p.natType)) return false - return true + // heartbeat + const { hash } = await this.cache.summarize('', this.cachePredicate) + for (const [, peer] of Object.entries(this.peers)) { + this.ping(peer, false, { + message: { + requesterPeerId: this.peerId, + natType: this.natType, + cacheSummaryHash: hash || null, + cacheSize: this.cache.size + } + }) + } + + // if this peer has previously tried to join any clusters, multicast a + // join messages for each into the network so we are always searching. + for (const cluster of Object.values(this.clusters)) { + for (const subcluster of Object.values(cluster)) { + this.join(subcluster.sharedKey, subcluster) } + } + return true + } - const candidates = peers - .filter(filter) - .filter(base) - .sort(rand) + /** + * Enqueue packets to be sent to the network + * @param {Buffer} data - An encoded packet + * @param {number} port - The desination port of the remote host + * @param {string} address - The destination address of the remote host + * @param {Socket=this.socket} socket - The socket to send on + * @return {undefined} + * @ignore + */ + send (data, port, address, socket = this.socket) { + this.sendQueue.push({ data, port, address, socket }) + this._scheduleSend() + } - const list = candidates.slice(0, 3) + /** + * @private + */ + _scheduleSend () { + if (this.sendTimeout) this._clearTimeout(this.sendTimeout) + this.sendTimeout = this._setTimeout(() => { this._dequeue() }) + } - if (!list.some(p => p.indexed)) { - const indexed = candidates.filter(p => p.indexed && !list.includes(p)) - if (indexed.length) list.push(indexed[0]) - } + /** + * @private + */ + _dequeue () { + if (!this.sendQueue.length) return + const { data, port, address, socket } = this.sendQueue.shift() - const clusterId = packet.clusterId.toString('base64') - const friends = candidates.filter(p => p.clusters && p.clusters[clusterId] && !list.includes(p)) - if (friends.length) { - list.unshift(friends[0]) - list.unshift(...candidates.filter(c => c.address === friends[0].address && c.peerId === friends[0].peerId)) - } + socket.send(data, port, address, err => { + if (this.sendQueue.length) this._scheduleSend() + if (err) return this._onError(err) - return list - } + const packet = Packet.decode(data) + if (!packet) return - /** - * Send an eventually consistent packet to a selection of peers (fanout) - * @return {undefined} - * @ignore - */ - async mcast (packet, ignorelist = []) { - const peers = this.getPeers(packet, this.peers, ignorelist) - const pid = packet.packetId.toString('hex') + this.metrics.o[packet.type]++ + delete this.unpublished[packet.packetId.toString('hex')] + if (this.onSend && packet.type) this.onSend(packet, port, address) + debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) + }) + } - packet.hops += 1 + /** + * Send any unpublished packets + * @return {undefined} + * @ignore + */ + async sendUnpublished () { + for (const [packetId] of Object.entries(this.unpublished)) { + const packet = this.cache.get(packetId) - for (const peer of peers) { - this.send(await Packet.encode(packet), peer.port, peer.address) + if (!packet) { // it may have been purged already + delete this.unpublished[packetId] + continue } - if (this.onMulticast) this.onMulticast(packet) - if (this.gate.has(pid)) return - this.gate.set(pid, 1) + await this.mcast(packet) + debug(this.peerId, `-> RESEND (packetId=${packetId})`) + if (this.onState) await this.onState(this.getState()) } + } - /** - * The process of determining this peer's NAT behavior (firewall and dependentness) - * @return {undefined} - * @ignore - */ - async requestReflection () { - if (this.closing || this.indexed || this.reflectionId) { - debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) - return - } + /** + * Get the serializable state of the peer (can be passed to the constructor or create method) + * @return {undefined} + */ + async getState () { + this.config.clock = this.clock // save off the clock + + const peers = this.peers.map(p => { + p = { ...p } + delete p.localPeer + return p + }) + + return { + peers, + config: this.config, + data: [...this.cache.data.entries()], + unpublished: this.unpublished + } + } - if (this.natType && (this.lastUpdate > 0 && (Date.now() - this.config.keepalive) < this.lastUpdate)) { - debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) - return - } + async getInfo () { + return { + address: this.address, + port: this.port, + clock: this.clock, + uptime: this.uptime, + natType: this.natType, + peerId: this.peerId + } + } - debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) - if (this.onConnecting) this.onConnecting({ code: -1, status: `Entering reflection (lastUpdate ${Date.now() - this.lastUpdate}ms)` }) + async cacheInsert (packet) { + this.cache.insert(packet.packetId.toString('hex'), Packet.from(packet)) + } - const peers = [...this.peers] - .filter(p => p.lastUpdate !== 0) - .filter(p => p.natType === NAT.UNRESTRICTED || p.natType === NAT.ADDR_RESTRICTED || p.indexed) + async addIndexedPeer (info) { + if (!info.peerId) throw new Error('options.peerId required') + if (!info.address) throw new Error('options.address required') + if (!info.port) throw new Error('options.port required') + info.indexed = true + this.peers.push(new RemotePeer(info)) + } - if (peers.length < 2) { - if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) - debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') + async reconnect () { + this.lastUpdate = 0 + this.requestReflection() + } - if (++this.reflectionRetry > 16) this.reflectionRetry = 1 - return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) - } + async disconnect () { + this.natType = null + this.reflectionStage = 0 + this.reflectionId = null + this.reflectionTimeout = null + this.probeReflectionTimeout = null + } - this.reflectionRetry = 1 + async sealUnsigned (...args) { + return this.encryption.sealUnsigned(...args) + } - const requesterPeerId = this.peerId - const opts = { requesterPeerId, isReflection: true } + async openUnsigned (...args) { + return this.encryption.openUnsigned(...args) + } - this.reflectionId = opts.reflectionId = randomBytes(6).toString('hex').padStart(12, '0') + async seal (...args) { + return this.encryption.seal(...args) + } - if (this.onConnecting) { - this.onConnecting({ code: 0.5, status: `Found ${peers.length} elegible peers for reflection` }) - } - // - // # STEP 1 - // The purpose of this step is strictily to discover the external port of - // the probe socket. - // - if (this.reflectionStage === 0) { - if (this.onConnecting) this.onConnecting({ code: 1, status: 'Discover External Port' }) - // start refelection with an zeroed NAT type - if (this.reflectionTimeout) this._clearTimeout(this.reflectionTimeout) - this.reflectionStage = 1 + async open (...args) { + return this.encryption.open(...args) + } - debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) - const list = peers.filter(p => p.probed).sort(() => Math.random() - 0.5) - const peer = list.length ? list[0] : peers[0] - peer.probed = Date.now() // mark this peer as being used to provide port info - this.ping(peer, false, { message: { ...opts, isProbe: true } }, this.probeSocket) + async addEncryptionKey (...args) { + return this.encryption.add(...args) + } - // we expect onMessageProbe to fire and clear this timer or it will timeout - this.probeReflectionTimeout = this._setTimeout(() => { - this.probeReflectionTimeout = null - if (this.reflectionStage !== 1) return - debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) - if (this.onConnecting) this.onConnecting({ code: 1, status: 'Timeout' }) + /** + * Get a selection of known peers + * @return {Array} + * @ignore + */ + getPeers (packet, peers, ignorelist, filter = o => o) { + const rand = () => Math.random() - 0.5 + + const base = p => { + if (ignorelist.findIndex(ilp => (ilp.port === p.port) && (ilp.address === p.address)) > -1) return false + if (p.lastUpdate === 0) return false + if (p.lastUpdate < Date.now() - (this.config.keepalive * 4)) return false + if (this.peerId === p.peerId) return false // same as me + if (packet.message.requesterPeerId === p.peerId) return false // same as requester - @todo: is this true in all cases? + if (!p.port || !NAT.isValid(p.natType)) return false + return true + } - this.reflectionStage = 1 - this.reflectionId = null - this.requestReflection() - }, 1024) + const candidates = peers + .filter(filter) + .filter(base) + .sort(rand) - debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) - return - } + const list = candidates.slice(0, 3) - // - // # STEP 2 - // - // The purpose of step 2 is twofold: - // - // 1) ask two different peers for the external port and address for our primary socket. - // If they are different, we can determine that our NAT is a `ENDPOINT_DEPENDENT`. - // - // 2) ask the peers to also reply to our probe socket from their probe socket. - // These packets will both be dropped for `FIREWALL_ALLOW_KNOWN_IP_AND_PORT` and will both - // arrive for `FIREWALL_ALLOW_ANY`. If one packet arrives (which will always be from the peer - // which was previously probed), this indicates `FIREWALL_ALLOW_KNOWN_IP`. - // - if (this.reflectionStage === 1) { - this.reflectionStage = 2 - const { probeExternalPort } = this.config - if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Discover NAT' }) - - // peer1 is the most recently probed (likely the same peer used in step1) - // using the most recent guarantees that the the NAT mapping is still open - const peer1 = peers.filter(p => p.probed).sort((a, b) => b.probed - a.probed)[0] - - // peer has NEVER previously been probed - const peer2 = peers.filter(p => !p.probed).sort(() => Math.random() - 0.5)[0] - - if (!peer1 || !peer2) { - debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') - if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Insufficent Peers' }) - return this._setTimeout(() => this.requestReflection(), 256) - } + if (!list.some(p => p.indexed)) { + const indexed = candidates.filter(p => p.indexed && !list.includes(p)) + if (indexed.length) list.push(indexed[0]) + } - debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) + const clusterId = packet.clusterId.toString('base64') + const friends = candidates.filter(p => p.clusters && p.clusters[clusterId] && !list.includes(p)) + if (friends.length) { + list.unshift(friends[0]) + list.unshift(...candidates.filter(c => c.address === friends[0].address && c.peerId === friends[0].peerId)) + } - // reset reflection variables to defaults - this.nextNatType = NAT.UNKNOWN - this.reflectionFirstResponder = null + return list + } - this.ping(peer1, false, { message: { ...opts, probeExternalPort } }) - this.ping(peer2, false, { message: { ...opts, probeExternalPort } }) + /** + * Send an eventually consistent packet to a selection of peers (fanout) + * @return {undefined} + * @ignore + */ + async mcast (packet, ignorelist = []) { + const peers = this.getPeers(packet, this.peers, ignorelist) + const pid = packet.packetId.toString('hex') - if (this.onConnecting) { - this.onConnecting({ code: 2, status: `Requesting reflection from ${peer1.address}` }) - this.onConnecting({ code: 2, status: `Requesting reflection from ${peer2.address}` }) - } + packet.hops += 1 - if (this.reflectionTimeout) { - this._clearTimeout(this.reflectionTimeout) - this.reflectionTimeout = null - } + for (const peer of peers) { + this.send(await Packet.encode(packet), peer.port, peer.address) + } - this.reflectionTimeout = this._setTimeout(ts => { - this.reflectionTimeout = null - if (this.reflectionStage !== 2) return - if (this.onConnecting) this.onConnecting({ code: 2, status: 'Timeout' }) - this.reflectionStage = 1 - this.reflectionId = null - debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) - return this.requestReflection() - }, 2048) - } + if (this.onMulticast) this.onMulticast(packet) + if (this.gate.has(pid)) return + this.gate.set(pid, 1) + } + + /** + * The process of determining this peer's NAT behavior (firewall and dependentness) + * @return {undefined} + * @ignore + */ + async requestReflection () { + if (this.closing || this.indexed || this.reflectionId) { + debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) + return } - /** - * Ping another peer - * @return {PacketPing} - * @ignore - */ - async ping (peer, withRetry, props, socket) { - if (!peer) { - return - } + if (this.natType && (this.lastUpdate > 0 && (Date.now() - this.config.keepalive) < this.lastUpdate)) { + debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) + return + } - props.message.requesterPeerId = this.peerId - props.message.uptime = this.uptime - props.message.timestamp = Date.now() + debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) + if (this.onConnecting) this.onConnecting({ code: -1, status: `Entering reflection (lastUpdate ${Date.now() - this.lastUpdate}ms)` }) - const packet = new PacketPing(props) - const data = await Packet.encode(packet) + const peers = [...this.peers] + .filter(p => p.lastUpdate !== 0) + .filter(p => p.natType === NAT.UNRESTRICTED || p.natType === NAT.ADDR_RESTRICTED || p.indexed) - const send = async () => { - if (this.closing) return false + if (peers.length < 2) { + if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) + debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') - const p = this.peers.find(p => p.peerId === peer.peerId) - // if (p?.reflectionId && p.reflectionId === packet.message.reflectionId) { - // return false - // } + if (++this.reflectionRetry > 16) this.reflectionRetry = 1 + return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) + } - this.send(data, peer.port, peer.address, socket) - if (p) p.lastRequest = Date.now() - } + this.reflectionRetry = 1 - send() + const requesterPeerId = this.peerId + const opts = { requesterPeerId, isReflection: true } - if (withRetry) { - this._setTimeout(send, PING_RETRY) - this._setTimeout(send, PING_RETRY * 4) - } + this.reflectionId = opts.reflectionId = randomBytes(6).toString('hex').padStart(12, '0') - return packet + if (this.onConnecting) { + this.onConnecting({ code: 0.5, status: `Found ${peers.length} elegible peers for reflection` }) } + // + // # STEP 1 + // The purpose of this step is strictily to discover the external port of + // the probe socket. + // + if (this.reflectionStage === 0) { + if (this.onConnecting) this.onConnecting({ code: 1, status: 'Discover External Port' }) + // start refelection with an zeroed NAT type + if (this.reflectionTimeout) this._clearTimeout(this.reflectionTimeout) + this.reflectionStage = 1 + + debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) + const list = peers.filter(p => p.probed).sort(() => Math.random() - 0.5) + const peer = list.length ? list[0] : peers[0] + peer.probed = Date.now() // mark this peer as being used to provide port info + this.ping(peer, false, { message: { ...opts, isProbe: true } }, this.probeSocket) + + // we expect onMessageProbe to fire and clear this timer or it will timeout + this.probeReflectionTimeout = this._setTimeout(() => { + this.probeReflectionTimeout = null + if (this.reflectionStage !== 1) return + debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) + if (this.onConnecting) this.onConnecting({ code: 1, status: 'Timeout' }) - getPeer (id) { - return this.peers.find(p => p.peerId === id) - } + this.reflectionStage = 1 + this.reflectionId = null + this.requestReflection() + }, 1024) - /** - * This should be called at least once when an app starts to multicast - * this peer, and starts querying the network to discover peers. - * @param {object} keys - Created by `Encryption.createKeyPair()`. - * @param {object=} args - Options - * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. - * @return {RemotePeer} - */ - async join (sharedKey, args = { rateLimit: MAX_BANDWIDTH }) { - const keys = await Encryption.createKeyPair(sharedKey) - this.encryption.add(keys.publicKey, keys.privateKey) + debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) + return + } - if (!this.port || !this.natType) return + // + // # STEP 2 + // + // The purpose of step 2 is twofold: + // + // 1) ask two different peers for the external port and address for our primary socket. + // If they are different, we can determine that our NAT is a `ENDPOINT_DEPENDENT`. + // + // 2) ask the peers to also reply to our probe socket from their probe socket. + // These packets will both be dropped for `FIREWALL_ALLOW_KNOWN_IP_AND_PORT` and will both + // arrive for `FIREWALL_ALLOW_ANY`. If one packet arrives (which will always be from the peer + // which was previously probed), this indicates `FIREWALL_ALLOW_KNOWN_IP`. + // + if (this.reflectionStage === 1) { + this.reflectionStage = 2 + const { probeExternalPort } = this.config + if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Discover NAT' }) + + // peer1 is the most recently probed (likely the same peer used in step1) + // using the most recent guarantees that the the NAT mapping is still open + const peer1 = peers.filter(p => p.probed).sort((a, b) => b.probed - a.probed)[0] + + // peer has NEVER previously been probed + const peer2 = peers.filter(p => !p.probed).sort(() => Math.random() - 0.5)[0] + + if (!peer1 || !peer2) { + debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') + if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Insufficent Peers' }) + return this._setTimeout(() => this.requestReflection(), 256) + } + + debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) + + // reset reflection variables to defaults + this.nextNatType = NAT.UNKNOWN + this.reflectionFirstResponder = null + + this.ping(peer1, false, { message: { ...opts, probeExternalPort } }) + this.ping(peer2, false, { message: { ...opts, probeExternalPort } }) - args.sharedKey = sharedKey + if (this.onConnecting) { + this.onConnecting({ code: 2, status: `Requesting reflection from ${peer1.address}` }) + this.onConnecting({ code: 2, status: `Requesting reflection from ${peer2.address}` }) + } - const clusterId = args.clusterId || this.config.clusterId - const subclusterId = Buffer.from(keys.publicKey) + if (this.reflectionTimeout) { + this._clearTimeout(this.reflectionTimeout) + this.reflectionTimeout = null + } - const cid = clusterId?.toString('base64') - const scid = subclusterId?.toString('base64') + this.reflectionTimeout = this._setTimeout(ts => { + this.reflectionTimeout = null + if (this.reflectionStage !== 2) return + if (this.onConnecting) this.onConnecting({ code: 2, status: 'Timeout' }) + this.reflectionStage = 1 + this.reflectionId = null + debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) + return this.requestReflection() + }, 2048) + } + } - this.clusters[cid] ??= {} - this.clusters[cid][scid] = args + /** + * Ping another peer + * @return {PacketPing} + * @ignore + */ + async ping (peer, withRetry, props, socket) { + if (!peer) { + return + } - this.clock += 1 + props.message.requesterPeerId = this.peerId + props.message.uptime = this.uptime + props.message.timestamp = Date.now() + props.clusterId = this.config.clusterId - const packet = new PacketJoin({ - clock: this.clock, - clusterId, - subclusterId, - message: { - requesterPeerId: this.peerId, - natType: this.natType, - address: this.address, - port: this.port - } - }) + const packet = new PacketPing(props) + const data = await Packet.encode(packet) - debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) - if (this.onState) await this.onState(this.getState()) - this.mcast(packet) - this.gate.set(packet.packetId.toString('hex'), 1) - } - - /** - * @param {Packet} T - The constructor to be used to create packets. - * @param {Any} message - The message to be split and packaged. - * @return {Array>} - * @ignore - */ - async _message2packets (T, message, args) { - const { clusterId, subclusterId, packet, nextId, meta = {}, usr1, usr2, sig } = args - - let messages = [message] - const len = message?.byteLength ?? message?.length ?? 0 - let clock = packet?.clock || 0 - - const siblings = [...this.cache.data.values()] - .filter(Boolean) - .filter(p => p?.previousId?.toString('hex') === packet?.packetId?.toString('hex')) - - if (siblings.length) { - // if there are siblings of the previous packet - // pick the highest clock value, the parent packet or the sibling - const sort = (a, b) => a.clock - b.clock - const sib = siblings.sort(sort).reverse()[0] - clock = Math.max(clock, sib.clock) + 1 - } + const send = async () => { + if (this.closing) return false - clock += 1 - - if (len > 1024) { // Split packets that have messages bigger than Packet.maxLength - messages = [{ - meta, - ts: Date.now(), - size: message.length, - indexes: Math.ceil(message.length / 1024) - }] - let pos = 0 - while (pos < message.length) messages.push(message.slice(pos, pos += 1024)) - } + const p = this.peers.find(p => p.peerId === peer.peerId) + // if (p?.reflectionId && p.reflectionId === packet.message.reflectionId) { + // return false + // } - // turn each message into an actual packet - const packets = messages.map(message => new T({ - ...args, - clusterId, - subclusterId, - clock, - message, - usr1, - usr2, - usr3: args.usr3, - usr4: args.usr4, - sig - })) - - if (packet) packets[0].previousId = packet.packetId - if (nextId) packets[packets.length - 1].nextId = nextId - - // set the .packetId (any maybe the .previousId and .nextId) - for (let i = 0; i < packets.length; i++) { - if (packets.length > 1) packets[i].index = i - - if (i === 0) { - packets[0].packetId = await sha256(packets[0].message, { bytes: true }) - } else { - // all fragments will have the same previous packetId - // the index is used to stitch them back together in order. - packets[i].previousId = packets[0].packetId - } + this.send(data, peer.port, peer.address, socket) + if (p) p.lastRequest = Date.now() + } - if (packets[i + 1]) { - packets[i + 1].packetId = await sha256( - Buffer.concat([ - await sha256(packets[i].packetId, { bytes: true }), - await sha256(packets[i + 1].message, { bytes: true }) - ]), - { bytes: true } - ) - packets[i].nextId = packets[i + 1].packetId - } - } + send() - return packets + if (withRetry) { + this._setTimeout(send, PING_RETRY) + this._setTimeout(send, PING_RETRY * 4) } - /** - * Sends a packet into the network that will be replicated and buffered. - * Each peer that receives it will buffer it until TTL and then replicate - * it provided it has has not exceeded their maximum number of allowed hops. - * - * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. - * @param {object} args - The arguments to be applied. - * @param {Buffer} args.message - The message to be encrypted by keys and sent. - * @param {Packet=} args.packet - The previous packet in the packet chain. - * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. - * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. - * @return {Array} - */ - async publish (sharedKey, args) { // wtf to do here, we need subclusterId and the actual user keys - if (!sharedKey) throw new Error('.publish() expected "sharedKey" argument in first position') - if (!isBufferLike(args.message)) throw new Error('.publish() will only accept a message of type buffer') + return packet + } + + /** + * Get a peer + * @return {RemotePeer} + * @ignore + */ + getPeer (id) { + return this.peers.find(p => p.peerId === id) + } + + /** + * This should be called at least once when an app starts to multicast + * this peer, and starts querying the network to discover peers. + * @param {object} keys - Created by `Encryption.createKeyPair()`. + * @param {object=} args - Options + * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. + * @return {RemotePeer} + */ + async join (sharedKey, args = { rateLimit: MAX_BANDWIDTH }) { + const keys = await Encryption.createKeyPair(sharedKey) + this.encryption.add(keys.publicKey, keys.privateKey) - const keys = await Encryption.createKeyPair(sharedKey) + if (!this.port || !this.natType) return - args.subclusterId = Buffer.from(keys.publicKey) - args.clusterId = args.clusterId || this.config.clusterId + args.sharedKey = sharedKey - const message = this.encryption.seal(args.message, keys) - const packets = await this._message2packets(PacketPublish, message, args) + const clusterId = args.clusterId || this.config.clusterId + const subclusterId = Buffer.from(keys.publicKey) - for (const packet of packets) { - const p = Packet.from(packet) - this.cache.insert(packet.packetId.toString('hex'), p) + const cid = clusterId?.toString('base64') + const scid = subclusterId?.toString('base64') - if (this.onPacket) this.onPacket(p, this.port, this.address, true) + this.clusters[cid] ??= {} + this.clusters[cid][scid] = args - this.unpublished[packet.packetId.toString('hex')] = Date.now() - if (globalThis.navigator && !globalThis.navigator.onLine) continue + this.clock += 1 - this.mcast(packet) + const packet = new PacketJoin({ + clock: this.clock, + clusterId, + subclusterId, + message: { + requesterPeerId: this.peerId, + natType: this.natType, + address: this.address, + port: this.port } + }) - return packets - } + debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) + if (this.onState) await this.onState(this.getState()) - /** - * @return {undefined} - */ - async sync (peer) { - const rinfo = peer?.proxy || peer + this.mcast(packet) + this.gate.set(packet.packetId.toString('hex'), 1) + } - this.lastSync = Date.now() - const summary = await this.cache.summarize('', this.cachePredicate) + /** + * @param {Packet} T - The constructor to be used to create packets. + * @param {Any} message - The message to be split and packaged. + * @return {Array>} + * @ignore + */ + async _message2packets (T, message, args) { + const { clusterId, subclusterId, packet, nextId, meta = {}, usr1, usr2, sig } = args + + let messages = [message] + const len = message?.byteLength ?? message?.length ?? 0 + let clock = packet?.clock || 0 + + const siblings = [...this.cache.data.values()] + .filter(Boolean) + .filter(p => p?.previousId?.toString('hex') === packet?.packetId?.toString('hex')) + + if (siblings.length) { + // if there are siblings of the previous packet + // pick the highest clock value, the parent packet or the sibling + const sort = (a, b) => a.clock - b.clock + const sib = siblings.sort(sort).reverse()[0] + clock = Math.max(clock, sib.clock) + 1 + } - debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) - if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) + clock += 1 + + if (len > 1024) { // Split packets that have messages bigger than Packet.maxLength + messages = [{ + meta, + ts: Date.now(), + size: message.length, + indexes: Math.ceil(message.length / 1024) + }] + let pos = 0 + while (pos < message.length) messages.push(message.slice(pos, pos += 1024)) + } - // if we are out of sync send our cache summary - const data = await Packet.encode(new PacketSync({ - message: Cache.encodeSummary(summary) - })) + // turn each message into an actual packet + const packets = messages.map(message => new T({ + ...args, + clusterId, + subclusterId, + clock, + message, + usr1, + usr2, + usr3: args.usr3, + usr4: args.usr4, + sig + })) + + if (packet) packets[0].previousId = packet.packetId + if (nextId) packets[packets.length - 1].nextId = nextId + + // set the .packetId (any maybe the .previousId and .nextId) + for (let i = 0; i < packets.length; i++) { + if (packets.length > 1) packets[i].index = i + + if (i === 0) { + packets[0].packetId = await sha256(packets[0].message, { bytes: true }) + } else { + // all fragments will have the same previous packetId + // the index is used to stitch them back together in order. + packets[i].previousId = packets[0].packetId + } - this.send(data, rinfo.port, rinfo.address, peer.socket) + if (packets[i + 1]) { + packets[i + 1].packetId = await sha256( + Buffer.concat([ + await sha256(packets[i].packetId, { bytes: true }), + await sha256(packets[i + 1].message, { bytes: true }) + ]), + { bytes: true } + ) + packets[i].nextId = packets[i + 1].packetId + } } - close () { - this._clearInterval(this.mainLoopTimer) + return packets + } - if (this.closing) return + /** + * Sends a packet into the network that will be replicated and buffered. + * Each peer that receives it will buffer it until TTL and then replicate + * it provided it has has not exceeded their maximum number of allowed hops. + * + * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. + * @param {object} args - The arguments to be applied. + * @param {Buffer} args.message - The message to be encrypted by keys and sent. + * @param {Packet=} args.packet - The previous packet in the packet chain. + * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. + * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. + * @return {Array} + */ + async publish (sharedKey, args) { // wtf to do here, we need subclusterId and the actual user keys + if (!sharedKey) throw new Error('.publish() expected "sharedKey" argument in first position') + if (!isBufferLike(args.message)) throw new Error('.publish() will only accept a message of type buffer') - this.closing = true - this.socket.close() + const keys = await Encryption.createKeyPair(sharedKey) - if (this.onClose) this.onClose() - } + args.subclusterId = Buffer.from(keys.publicKey) + args.clusterId = args.clusterId || this.config.clusterId - /** - * Deploy a query into the network - * @return {undefined} - * - */ - async query (query) { - const packet = new PacketQuery({ - message: query, - usr1: Buffer.from(String(Date.now())), - usr3: Buffer.from(randomBytes(32)), - usr4: Buffer.from(String(1)) - }) - const data = await Packet.encode(packet) + const message = this.encryption.seal(args.message, keys) + const packets = await this._message2packets(PacketPublish, message, args) - const p = Packet.decode(data) // finalize a packet - const pid = p.packetId.toString('hex') + for (const packet of packets) { + this.cacheInsert(packet) - if (this.gate.has(pid)) return - this.returnRoutes.set(p.usr3.toString('hex'), {}) - this.gate.set(pid, 1) // don't accidentally spam + if (this.onPacket) this.onPacket(p, this.port, this.address, true) - debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) + this.unpublished[packet.packetId.toString('hex')] = Date.now() + if (globalThis.navigator && !globalThis.navigator.onLine) continue - await this.mcast(p) + this.mcast(packet) } - /** - * - * This is a default implementation for deciding what to summarize - * from the cache when receiving a request to sync. that can be overridden - * - */ - cachePredicate (packet) { - return packet.version === VERSION && packet.timestamp > Date.now() - Packet.ttl + return packets + } + + /** + * @return {undefined} + */ + async sync (peer) { + if (typeof peer === 'string') { + peer = this.peers.find(p => p.peerId === peer) } - /** - * A connection was made, add the peer to the local list of known - * peers and call the onConnection if it is defined by the user. - * - * @return {undefined} - * @ignore - */ - async _onConnection (packet, peerId, port, address, proxy, socket) { - if (this.closing) return + const rinfo = peer?.proxy || peer - const natType = packet.message.natType + this.lastSync = Date.now() + const summary = await this.cache.summarize('', this.cachePredicate) - const { clusterId, subclusterId } = packet + debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) + if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) - let peer = this.getPeer(peerId) + // if we are out of sync send our cache summary + const data = await Packet.encode(new PacketSync({ + message: Cache.encodeSummary(summary) + })) - if (!peer) { - peer = new RemotePeer({ peerId }) + this.send(data, rinfo.port, rinfo.address, peer.socket) + } - if (this.peers.length >= 256) { - // TODO evicting an older peer definitely needs some more thought. - const oldPeerIndex = this.peers.findIndex(p => !p.lastUpdate && !p.indexed) - if (oldPeerIndex > -1) this.peers.splice(oldPeerIndex, 1) - } + close () { + this._clearInterval(this.mainLoopTimer) - this.peers.push(peer) - } + if (this.closing) return - peer.connected = true - peer.lastUpdate = Date.now() - peer.port = port - peer.natType = natType - peer.address = address - if (proxy) peer.proxy = proxy - if (socket) peer.socket = socket + this.closing = true + this.socket.close() - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') + if (this.onClose) this.onClose() + } - if (cid) peer.clusters[cid] ??= {} + /** + * Deploy a query into the network + * @return {undefined} + * + */ + async query (query) { + const packet = new PacketQuery({ + message: query, + usr1: Buffer.from(String(Date.now())), + usr3: Buffer.from(randomBytes(32)), + usr4: Buffer.from(String(1)) + }) + const data = await Packet.encode(packet) - if (cid && scid) { - const cluster = peer.clusters[cid] - cluster[scid] = { rateLimit: MAX_BANDWIDTH } - } + const p = Packet.decode(data) // finalize a packet + const pid = p.packetId.toString('hex') - if (!peer.localPeer) peer.localPeer = this - if (!this.connections) this.connections = new Map() + if (this.gate.has(pid)) return + this.returnRoutes.set(p.usr3.toString('hex'), {}) + this.gate.set(pid, 1) // don't accidentally spam - debug(this.peerId, '<- CONNECTION ( ' + - `peerId=${peer.peerId.slice(0, 6)}, ` + - `address=${address}:${port}, ` + - `type=${packet.type}, ` + - `cluster=${cid.slice(0, 8)}, ` + - `sub-cluster=${scid.slice(0, 8)})` - ) + debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) - if (this.onJoin && this.clusters[cid]) { - this.onJoin(packet, peer, port, address) - } + await this.mcast(p) + } - if (!this.connections.has(peer)) { - this.onConnection && this.onConnection(packet, peer, port, address) - this.connections.set(peer, packet.message.cacheSummaryHash) + /** + * + * This is a default implementation for deciding what to summarize + * from the cache when receiving a request to sync. that can be overridden + * + */ + cachePredicate (packet) { + return packet.version === VERSION && packet.timestamp > Date.now() - Packet.ttl + } + + /** + * A connection was made, add the peer to the local list of known + * peers and call the onConnection if it is defined by the user. + * + * @return {undefined} + * @ignore + */ + async _onConnection (packet, peerId, port, address, proxy, socket) { + if (this.closing) return + + const natType = packet.message.natType + + const { clusterId, subclusterId } = packet + + let peer = this.getPeer(peerId) + + if (!peer) { + peer = new RemotePeer({ peerId }) + + if (this.peers.length >= 256) { + // TODO evicting an older peer definitely needs some more thought. + const oldPeerIndex = this.peers.findIndex(p => !p.lastUpdate && !p.indexed) + if (oldPeerIndex > -1) this.peers.splice(oldPeerIndex, 1) } + + this.peers.push(peer) } - /** - * Received a Sync Packet - * @return {undefined} - * @ignore - */ - async _onSync (packet, port, address) { - this.metrics.i[packet.type]++ + peer.connected = true + peer.lastUpdate = Date.now() + peer.port = port + peer.natType = natType + peer.address = address + if (proxy) peer.proxy = proxy + if (socket) peer.socket = socket - this.lastSync = Date.now() - const pid = packet.packetId?.toString('hex') + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') - if (!isBufferLike(packet.message)) return - if (this.gate.has(pid)) return + if (cid) peer.clusters[cid] ??= {} - this.gate.set(pid, 1) + if (cid && scid) { + const cluster = peer.clusters[cid] + cluster[scid] = { rateLimit: MAX_BANDWIDTH } + } - const remote = Cache.decodeSummary(packet.message) - const local = await this.cache.summarize(remote.prefix, this.cachePredicate) + if (!peer.localPeer) peer.localPeer = this + if (!this.connections) this.connections = new Map() - if (!remote || !remote.hash || !local || !local.hash || local.hash === remote.hash) { - if (this.onSyncFinished) this.onSyncFinished(packet, port, address) - return - } + debug(this.peerId, '<- CONNECTION ( ' + + `peerId=${peer.peerId.slice(0, 6)}, ` + + `address=${address}:${port}, ` + + `type=${packet.type}, ` + + `cluster=${cid.slice(0, 8)}, ` + + `sub-cluster=${scid.slice(0, 8)})` + ) - if (this.onSync) this.onSync(packet, port, address, { remote, local }) + if (this.onJoin && this.clusters[cid]) { + this.onJoin(packet, peer, port, address) + } - const remoteBuckets = remote.buckets.filter(Boolean).length - debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) + if (!this.connections.has(peer)) { + this.onConnection && this.onConnection(packet, peer, port, address) + this.connections.set(peer, packet.message.cacheSummaryHash) + } + } - for (let i = 0; i < local.buckets.length; i++) { - // - // nothing to send/sync, expect peer to send everything they have - // - if (!local.buckets[i] && !remote.buckets[i]) continue + /** + * Received a Sync Packet + * @return {undefined} + * @ignore + */ + async _onSync (packet, port, address) { + this.metrics.i[packet.type]++ - // - // you dont have any of these, im going to send them to you - // - if (!remote.buckets[i]) { - for (const [key, p] of this.cache.data.entries()) { - if (!key.startsWith(local.prefix + i.toString(16))) continue + this.lastSync = Date.now() + const pid = packet.packetId?.toString('hex') - const packet = Packet.from(p) - if (!this.cachePredicate(packet)) continue + if (!isBufferLike(packet.message)) return + if (this.gate.has(pid)) return - const pid = packet.packetId.toString('hex') - debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) + this.gate.set(pid, 1) - this.send(await Packet.encode(packet), port, address) - } - } else { - // - // need more details about what exactly isn't synce'd - // - const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate) - const data = await Packet.encode(new PacketSync({ - message: Cache.encodeSummary(nextLevel) - })) - this.send(data, port, address) - } - } + const remote = Cache.decodeSummary(packet.message) + const local = await this.cache.summarize(remote.prefix, this.cachePredicate) + + if (!remote || !remote.hash || !local || !local.hash || local.hash === remote.hash) { + if (this.onSyncFinished) this.onSyncFinished(packet, port, address) + return } - /** - * Received a Query Packet - * - * a -> b -> c -> (d) -> c -> b -> a - * - * @return {undefined} - * @example - * - * ```js - * peer.onQuery = (packet) => { - * // - * // read a database or something - * // - * return { - * message: Buffer.from('hello'), - * publicKey: '', - * privateKey: '' - * } - * } - * ``` - */ - async _onQuery (packet, port, address) { - this.metrics.i[packet.type]++ - - const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) - - const queryTimestamp = parseInt(Buffer.from(packet.usr1).toString(), 10) - const queryId = Buffer.from(packet.usr3).toString('hex') - const queryType = parseInt(Buffer.from(packet.usr4).toString(), 10) - - // if the timestamp in usr1 is older than now - 2s, bail - if (queryTimestamp < (Date.now() - 2048)) return - - const type = queryType === 1 ? 'question' : 'answer' - debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) - - let rinfo = { port, address } + if (this.onSync) this.onSync(packet, port, address, { remote, local }) + + const remoteBuckets = remote.buckets.filter(Boolean).length + debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) + for (let i = 0; i < local.buckets.length; i++) { // - // receiving an answer + // nothing to send/sync, expect peer to send everything they have // - if (this.returnRoutes.has(queryId)) { - rinfo = this.returnRoutes.get(queryId) - - let p = packet.copy() - if (p.index > -1) p = await this.cache.compose(p) - - if (p?.index === -1) { - this.returnRoutes.delete(p.previousId.toString('hex')) - p.type = PacketPublish.type - delete p.usr3 - delete p.usr4 - if (this.onAnswer) return this.onAnswer(p.message, p, port, address) - } + if (!local.buckets[i] && !remote.buckets[i]) continue + + // + // you dont have any of these, im going to send them to you + // + if (!remote.buckets[i]) { + for (const [key, p] of this.cache.data.entries()) { + if (!key.startsWith(local.prefix + i.toString(16))) continue - if (!rinfo.address) return + const packet = Packet.from(p) + if (!this.cachePredicate(packet)) continue + + const pid = packet.packetId.toString('hex') + debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) + + this.send(await Packet.encode(packet), port, address) + } } else { // - // receiving a query + // need more details about what exactly isn't synce'd // - this.returnRoutes.set(queryId, { address, port }) + const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate) + const data = await Packet.encode(new PacketSync({ + message: Cache.encodeSummary(nextLevel) + })) + this.send(data, port, address) + } + } + } - const query = packet.message - const packets = [] + /** + * Received a Query Packet + * + * a -> b -> c -> (d) -> c -> b -> a + * + * @return {undefined} + * @example + * + * ```js + * peer.onQuery = (packet) => { + * // + * // read a database or something + * // + * return { + * message: Buffer.from('hello'), + * publicKey: '', + * privateKey: '' + * } + * } + * ``` + */ + async _onQuery (packet, port, address) { + this.metrics.i[packet.type]++ - // - // The requestor is looking for an exact packetId. In this case, - // the peer has a packet with a previousId or nextId that points - // to a packetId they don't have. There is no need to specify the - // index in the query, split packets will have a nextId. - // - // if cache packet = { nextId: 'deadbeef...' } - // then query = { packetId: packet.nextId } - // or query = { packetId: packet.previousId } - // - if (query.packetId && this.cache.has(query.packetId)) { - const p = this.cache.get(query.packetId) - if (p) packets.push(p) - } else if (this.onQuery) { - const q = await this.onQuery(query) - if (q) packets.push(...await this._message2packets(PacketQuery, q.message, q)) - } + const pid = packet.packetId.toString('hex') + if (this.gate.has(pid)) return + this.gate.set(pid, 1) - if (packets.length) { - for (const p of packets) { - p.type = PacketQuery.type // convert the type during transport - p.usr3 = packet.usr3 // ensure the packet has the queryId - p.usr4 = Buffer.from(String(2)) // mark it as an answer packet - this.send(await Packet.encode(p), rinfo.port, rinfo.address) - } - return - } - } + const queryTimestamp = parseInt(Buffer.from(packet.usr1).toString(), 10) + const queryId = Buffer.from(packet.usr3).toString('hex') + const queryType = parseInt(Buffer.from(packet.usr4).toString(), 10) - if (packet.hops >= this.maxHops) return - debug(this.peerId, '>> QUERY RELAY', port, address) - return await this.mcast(packet) - } + // if the timestamp in usr1 is older than now - 2s, bail + if (queryTimestamp < (Date.now() - 2048)) return - /** - * Received a Ping Packet - * @return {undefined} - * @ignore - */ - async _onPing (packet, port, address) { - this.metrics.i[packet.type]++ + const type = queryType === 1 ? 'question' : 'answer' + debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) - this.lastUpdate = Date.now() - const { reflectionId, isReflection, isConnection, isHeartbeat } = packet.message + let rinfo = { port, address } - if (packet.message.requesterPeerId === this.peerId) return + // + // receiving an answer + // + if (this.returnRoutes.has(queryId)) { + rinfo = this.returnRoutes.get(queryId) - const { probeExternalPort, isProbe, pingId } = packet.message + let p = packet.copy() + if (p.index > -1) p = await this.cache.compose(p) - if (isHeartbeat) { - // const peer = this.getPeer(packet.message.requesterPeerId) - // if (peer && natType) peer.natType = natType - return + if (p?.index === -1) { + this.returnRoutes.delete(p.previousId.toString('hex')) + p.type = PacketPublish.type + delete p.usr3 + delete p.usr4 + if (this.onAnswer) return this.onAnswer(p.message, p, port, address) } - // if (peer && reflectionId) peer.reflectionId = reflectionId - if (!port) port = packet.message.port - if (!address) address = packet.message.address + if (!rinfo.address) return + } else { + // + // receiving a query + // + this.returnRoutes.set(queryId, { address, port }) + + const query = packet.message + const packets = [] - const message = { - cacheSize: this.cache.size, - uptime: this.uptime, - responderPeerId: this.peerId, - requesterPeerId: packet.message.requesterPeerId, - port, - isProbe, - address + // + // The requestor is looking for an exact packetId. In this case, + // the peer has a packet with a previousId or nextId that points + // to a packetId they don't have. There is no need to specify the + // index in the query, split packets will have a nextId. + // + // if cache packet = { nextId: 'deadbeef...' } + // then query = { packetId: packet.nextId } + // or query = { packetId: packet.previousId } + // + if (query.packetId && this.cache.has(query.packetId)) { + const p = this.cache.get(query.packetId) + if (p) packets.push(p) + } else if (this.onQuery) { + const q = await this.onQuery(query) + if (q) packets.push(...await this._message2packets(PacketQuery, q.message, q)) + } + + if (packets.length) { + for (const p of packets) { + p.type = PacketQuery.type // convert the type during transport + p.usr3 = packet.usr3 // ensure the packet has the queryId + p.usr4 = Buffer.from(String(2)) // mark it as an answer packet + this.send(await Packet.encode(p), rinfo.port, rinfo.address) + } + return } + } - if (reflectionId) message.reflectionId = reflectionId - if (isHeartbeat) message.isHeartbeat = Date.now() - if (pingId) message.pingId = pingId + if (packet.hops >= this.maxHops) return + debug(this.peerId, '>> QUERY RELAY', port, address) + return await this.mcast(packet) + } - if (isReflection) { - message.isReflection = true - message.port = port - message.address = address - } else { - message.natType = this.natType - } + /** + * Received a Ping Packet + * @return {undefined} + * @ignore + */ + async _onPing (packet, port, address) { + this.metrics.i[packet.type]++ - if (isConnection) { - const peerId = packet.message.requesterPeerId - this._onConnection(packet, peerId, port, address) + this.lastUpdate = Date.now() + const { reflectionId, isReflection, isConnection, isHeartbeat } = packet.message - message.isConnection = true - delete message.address - delete message.port - delete message.isProbe - } + if (packet.message.requesterPeerId === this.peerId) return - const { hash } = await this.cache.summarize('', this.cachePredicate) - message.cacheSummaryHash = hash + const { probeExternalPort, isProbe, pingId } = packet.message - const packetPong = new PacketPong({ message }) - const buf = await Packet.encode(packetPong) + if (isHeartbeat) { + // const peer = this.getPeer(packet.message.requesterPeerId) + // if (peer && natType) peer.natType = natType + return + } - this.send(buf, port, address) + // if (peer && reflectionId) peer.reflectionId = reflectionId + if (!port) port = packet.message.port + if (!address) address = packet.message.address + + const message = { + cacheSize: this.cache.size, + uptime: this.uptime, + responderPeerId: this.peerId, + requesterPeerId: packet.message.requesterPeerId, + port, + isProbe, + address + } - if (probeExternalPort) { - message.port = probeExternalPort - const packetPong = new PacketPong({ message }) - const buf = await Packet.encode(packetPong) - this.send(buf, probeExternalPort, address, this.probeSocket) - } + if (reflectionId) message.reflectionId = reflectionId + if (isHeartbeat) message.isHeartbeat = Date.now() + if (pingId) message.pingId = pingId + + if (isReflection) { + message.isReflection = true + message.port = port + message.address = address + } else { + message.natType = this.natType } - /** - * Received a Pong Packet - * @return {undefined} - * @ignore - */ - async _onPong (packet, port, address) { - this.metrics.i[packet.type]++ + if (isConnection) { + const peerId = packet.message.requesterPeerId + this._onConnection(packet, peerId, port, address) - this.lastUpdate = Date.now() + message.isConnection = true + delete message.address + delete message.port + delete message.isProbe + } - const { reflectionId, pingId, isReflection, responderPeerId } = packet.message + const { hash } = await this.cache.summarize('', this.cachePredicate) + message.cacheSummaryHash = hash - debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) - const peer = this.getPeer(packet.message.responderPeerId) + const packetPong = new PacketPong({ message }) + const buf = await Packet.encode(packetPong) - if (packet.message.isConnection) { - if (pingId) peer.pingId = pingId - this._onConnection(packet, packet.message.responderPeerId, port, address) - return - } + this.send(buf, port, address) + + if (probeExternalPort) { + message.port = probeExternalPort + const packetPong = new PacketPong({ message }) + const buf = await Packet.encode(packetPong) + this.send(buf, probeExternalPort, address, this.probeSocket) + } + } - if (!peer) return + /** + * Received a Pong Packet + * @return {undefined} + * @ignore + */ + async _onPong (packet, port, address) { + this.metrics.i[packet.type]++ - if (isReflection && !this.indexed) { - if (reflectionId !== this.reflectionId) return + this.lastUpdate = Date.now() - this._clearTimeout(this.reflectionTimeout) + const { reflectionId, pingId, isReflection, responderPeerId } = packet.message - if (!this.reflectionFirstResponder) { - this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } - if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) - } else { - if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) - if (packet.message.address !== this.address) return - - this.nextNatType |= ( - packet.message.port === this.reflectionFirstResponder.packet.message.port - ) - ? NAT.MAPPING_ENDPOINT_INDEPENDENT - : NAT.MAPPING_ENDPOINT_DEPENDENT - - debug( - this.peerId, - `++ NAT REFLECT - STATE UPDATE (natType=${this.natType}, nextType=${this.nextNatType})`, - packet.message.port, - this.reflectionFirstResponder.packet.message.port - ) - - // wait PROBE_WAIT milliseconds for zero or more probe responses to arrive. - this._setTimeout(async () => { - // build the NAT type by combining information about the firewall with - // information about the endpoint independence - let natType = this.nextNatType - - // in the case where we recieved zero probe responses, we assume the firewall - // is of the hardest type 'FIREWALL_ALLOW_KNOWN_IP_AND_PORT'. - if (!NAT.isFirewallDefined(natType)) natType |= NAT.FIREWALL_ALLOW_KNOWN_IP_AND_PORT - - // if ((natType & NAT.MAPPING_ENDPOINT_DEPENDENT) === 1) natType = NAT.ENDPOINT_RESTRICTED - - if (NAT.isValid(natType)) { - // const oldType = this.natType - this.natType = natType - this.reflectionId = null - this.reflectionStage = 0 - - // if (natType !== oldType) { - // alert all connected peers of our new NAT type - for (const peer of this.peers) { - peer.lastRequest = Date.now() - - debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) - - await this.ping(peer, false, { - message: { - requesterPeerId: this.peerId, - natType: this.natType, - cacheSize: this.cache.size, - isConnection: true - } - }) - } - - if (this.onNat) this.onNat(this.natType) - - debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) - this.sendUnpublished() - // } - - if (this.onConnecting) this.onConnecting({ code: 3, status: `Discovered! (nat=${NAT.toString(this.natType)})` }) - if (this.onReady) this.onReady() - } + debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) + const peer = this.getPeer(packet.message.responderPeerId) + if (packet.message.isConnection) { + if (pingId) peer.pingId = pingId + this._onConnection(packet, packet.message.responderPeerId, port, address) + return + } + + if (!peer) return + + if (isReflection && !this.indexed) { + if (reflectionId !== this.reflectionId) return + + this._clearTimeout(this.reflectionTimeout) + + if (!this.reflectionFirstResponder) { + this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } + if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) + debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) + } else { + if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) + debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) + if (packet.message.address !== this.address) return + + this.nextNatType |= ( + packet.message.port === this.reflectionFirstResponder.packet.message.port + ) + ? NAT.MAPPING_ENDPOINT_INDEPENDENT + : NAT.MAPPING_ENDPOINT_DEPENDENT + + debug( + this.peerId, + `++ NAT REFLECT - STATE UPDATE (natType=${this.natType}, nextType=${this.nextNatType})`, + packet.message.port, + this.reflectionFirstResponder.packet.message.port + ) + + // wait PROBE_WAIT milliseconds for zero or more probe responses to arrive. + this._setTimeout(async () => { + // build the NAT type by combining information about the firewall with + // information about the endpoint independence + let natType = this.nextNatType + + // in the case where we recieved zero probe responses, we assume the firewall + // is of the hardest type 'FIREWALL_ALLOW_KNOWN_IP_AND_PORT'. + if (!NAT.isFirewallDefined(natType)) natType |= NAT.FIREWALL_ALLOW_KNOWN_IP_AND_PORT + + // if ((natType & NAT.MAPPING_ENDPOINT_DEPENDENT) === 1) natType = NAT.ENDPOINT_RESTRICTED + + if (NAT.isValid(natType)) { + // const oldType = this.natType + this.natType = natType this.reflectionId = null - this.reflectionFirstResponder = null - }, PROBE_WAIT) - } + this.reflectionStage = 0 + + // if (natType !== oldType) { + // alert all connected peers of our new NAT type + for (const peer of this.peers) { + peer.lastRequest = Date.now() + + debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) + + await this.ping(peer, false, { + message: { + requesterPeerId: this.peerId, + natType: this.natType, + cacheSize: this.cache.size, + isConnection: true + } + }) + } + + if (this.onNat) this.onNat(this.natType) + + debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) + this.sendUnpublished() + // } + + if (this.onConnecting) this.onConnecting({ code: 3, status: `Discovered! (nat=${NAT.toString(this.natType)})` }) + if (this.onReady) this.onReady(await this.getInfo()) + } - this.address = packet.message.address - this.port = packet.message.port - debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) + this.reflectionId = null + this.reflectionFirstResponder = null + }, PROBE_WAIT) } + + this.address = packet.message.address + this.port = packet.message.port + debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) } + } - /** - * Received an Intro Packet - * @return {undefined} - * @ignore - */ - async _onIntro (packet, port, address, _, opts = { attempts: 0 }) { - this.metrics.i[packet.type]++ - if (this.closing) return + /** + * Received an Intro Packet + * @return {undefined} + * @ignore + */ + async _onIntro (packet, port, address, _, opts = { attempts: 0 }) { + this.metrics.i[packet.type]++ + if (this.closing) return + + const pid = packet.packetId.toString('hex') + // the packet needs to be gated, but should allow for attempt + // recursion so that the fallback can still be selected. + if (this.gate.has(pid) && opts.attempts === 0) return + this.gate.set(pid, 1) + + const ts = packet.usr1.length && Number(packet.usr1.toString()) + + if (packet.hops >= this.maxHops) return + if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return + if (packet.message.requesterPeerId === this.peerId) return // intro to myself? + if (packet.message.responderPeerId === this.peerId) return // intro from myself? + + // this is the peer that is being introduced to the new peers + const peerId = packet.message.requesterPeerId + const peerPort = packet.message.port + const peerAddress = packet.message.address + const natType = packet.message.natType + const { clusterId, subclusterId, clock } = packet + + // already introduced in the laste minute, just drop the packet + if (opts.attempts === 0 && this.gate.has(peerId + peerAddress + peerPort)) return + this.gate.set(peerId + peerAddress + peerPort, 2) + + // we already know this peer, and we're even connected to them! + let peer = this.getPeer(peerId) + if (!peer) peer = new RemotePeer({ peerId, natType, port: peerPort, address: peerAddress, clock, clusterId, subclusterId }) + if (peer.connected) return // already connected + if (clock > 0 && clock < peer.clock) return + peer.clock = clock + + // a mutex per inbound peer to ensure that it's not connecting concurrently, + // the check of the attempts ensures its allowed to recurse before failing so + // it can still fall back + if (this.gate.has('CONN' + peer.peerId) && opts.attempts === 0) return + this.gate.set('CONN' + peer.peerId, 1) + + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') + + debug(this.peerId, '<- INTRO (' + + `isRendezvous=${packet.message.isRendezvous}, ` + + `from=${address}:${port}, ` + + `to=${packet.message.address}:${packet.message.port}, ` + + `clustering=${cid.slice(0, 4)}/${scid.slice(0, 4)}` + + ')') + + if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) + + const pingId = Math.random().toString(16).slice(2) + const { hash } = await this.cache.summarize('', this.cachePredicate) + + const props = { + clusterId, + subclusterId, + message: { + natType: this.natType, + isConnection: true, + cacheSummaryHash: hash || null, + pingId: packet.message.pingId, + requesterPeerId: this.peerId + } + } - const pid = packet.packetId.toString('hex') - // the packet needs to be gated, but should allow for attempt - // recursion so that the fallback can still be selected. - if (this.gate.has(pid) && opts.attempts === 0) return - this.gate.set(pid, 1) + const strategy = NAT.connectionStrategy(this.natType, packet.message.natType) + const proxyCandidate = this.peers.find(p => p.peerId === packet.message.responderPeerId) - const ts = packet.usr1.length && Number(packet.usr1.toString()) + if (opts.attempts >= 2) { + this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) + return false + } - if (packet.hops >= this.maxHops) return - if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return - if (packet.message.requesterPeerId === this.peerId) return // intro to myself? - if (packet.message.responderPeerId === this.peerId) return // intro from myself? + this._setTimeout(() => { + if (this.getPeer(peer.peerId)) return + opts.attempts = 2 + this._onIntro(packet, port, address, _, opts) + }, 1024 * 2) - // this is the peer that is being introduced to the new peers - const peerId = packet.message.requesterPeerId - const peerPort = packet.message.port - const peerAddress = packet.message.address - const natType = packet.message.natType - const { clusterId, subclusterId, clock } = packet - - // already introduced in the laste minute, just drop the packet - if (opts.attempts === 0 && this.gate.has(peerId + peerAddress + peerPort)) return - this.gate.set(peerId + peerAddress + peerPort, 2) - - // we already know this peer, and we're even connected to them! - let peer = this.getPeer(peerId) - if (!peer) peer = new RemotePeer({ peerId, natType, port: peerPort, address: peerAddress, clock, clusterId, subclusterId }) - if (peer.connected) return // already connected - if (clock > 0 && clock < peer.clock) return - peer.clock = clock - - // a mutex per inbound peer to ensure that it's not connecting concurrently, - // the check of the attempts ensures its allowed to recurse before failing so - // it can still fall back - if (this.gate.has('CONN' + peer.peerId) && opts.attempts === 0) return - this.gate.set('CONN' + peer.peerId, 1) - - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') - - debug(this.peerId, '<- INTRO (' + - `isRendezvous=${packet.message.isRendezvous}, ` + - `from=${address}:${port}, ` + - `to=${packet.message.address}:${packet.message.port}, ` + - `clustering=${cid.slice(0, 4)}/${scid.slice(0, 4)}` + - ')') - - if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) - - const pingId = Math.random().toString(16).slice(2) - const { hash } = await this.cache.summarize('', this.cachePredicate) - - const props = { - clusterId, - subclusterId, - message: { - natType: this.natType, - isConnection: true, - cacheSummaryHash: hash || null, - pingId: packet.message.pingId, - requesterPeerId: this.peerId - } - } + if (packet.message.isRendezvous) { + debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) + } + + debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) - const strategy = NAT.connectionStrategy(this.natType, packet.message.natType) - const proxyCandidate = this.peers.find(p => p.peerId === packet.message.responderPeerId) + if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { + debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) - if (opts.attempts >= 2) { - this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) - return false + let i = 0 + if (!this.socketPool) { + this.socketPool = Array.from({ length: 256 }, (_, index) => { + return this.dgram().createSocket('udp4', null, this, index).unref() + }) } - this._setTimeout(() => { - if (this.getPeer(peer.peerId)) return - opts.attempts = 2 - this._onIntro(packet, port, address, _, opts) - }, 1024 * 2) + // A probes 1 target port on B from 1024 source ports + // (this is 1.59% of the search clusterId) + // B probes 256 target ports on A from 1 source port + // (this is 0.40% of the search clusterId) + // + // Probability of successful traversal: 98.35% + // + const interval = this._setInterval(async () => { + // send messages until we receive a message from them. giveup after sending ±1024 + // packets and fall back to using the peer that sent this as the initial proxy. + if (i++ >= 1024) { + this._clearInterval(interval) + + opts.attempts++ + this._onIntro(packet, port, address, _, opts) + return false + } - if (packet.message.isRendezvous) { - debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) - } + const p = { + clusterId, + subclusterId, + message: { + requesterPeerId: this.peerId, + cacheSummaryHash: hash || null, + natType: this.natType, + uptime: this.uptime, + isConnection: true, + timestamp: Date.now(), + pingId + } + } + + const data = await Packet.encode(new PacketPing(p)) - debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) + const rand = () => Math.random() - 0.5 + const pooledSocket = this.socketPool.sort(rand).find(s => !s.active) + if (!pooledSocket) return // TODO recover from exausted socket pool + + // mark socket as active & deactivate it after timeout + pooledSocket.active = true + pooledSocket.reclaim = this._setTimeout(() => { + pooledSocket.active = false + pooledSocket.removeAllListeners() + }, 1024) - if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { - debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) + pooledSocket.on('message', async (msg, rinfo) => { + // if (rinfo.port !== peerPort || rinfo.address !== peerAddress) return - let i = 0 - if (!this.socketPool) { - this.socketPool = Array.from({ length: 256 }, (_, index) => { - return dgram.createSocket('udp4', null, this, index).unref() + // cancel scheduled events + this._clearInterval(interval) + this._clearTimeout(pooledSocket.reclaim) + + // remove any events currently bound on the socket + pooledSocket.removeAllListeners() + pooledSocket.on('message', (msg, rinfo) => { + this._onMessage(msg, rinfo) }) - } - // A probes 1 target port on B from 1024 source ports - // (this is 1.59% of the search clusterId) - // B probes 256 target ports on A from 1 source port - // (this is 0.40% of the search clusterId) - // - // Probability of successful traversal: 98.35% - // - const interval = this._setInterval(async () => { - // send messages until we receive a message from them. giveup after sending ±1024 - // packets and fall back to using the peer that sent this as the initial proxy. - if (i++ >= 1024) { - this._clearInterval(interval) - - opts.attempts++ - this._onIntro(packet, port, address, _, opts) - return false - } + this._onConnection(packet, peer.peerId, rinfo.port, rinfo.address, undefined, pooledSocket) const p = { clusterId, subclusterId, + clock: this.clock, message: { requesterPeerId: this.peerId, - cacheSummaryHash: hash || null, natType: this.natType, - uptime: this.uptime, - isConnection: true, - timestamp: Date.now(), - pingId + isConnection: true } } const data = await Packet.encode(new PacketPing(p)) - const rand = () => Math.random() - 0.5 - const pooledSocket = this.socketPool.sort(rand).find(s => !s.active) - if (!pooledSocket) return // TODO recover from exausted socket pool - - // mark socket as active & deactivate it after timeout - pooledSocket.active = true - pooledSocket.reclaim = this._setTimeout(() => { - pooledSocket.active = false - pooledSocket.removeAllListeners() - }, 1024) + pooledSocket.send(data, rinfo.port, rinfo.address) - pooledSocket.on('message', async (msg, rinfo) => { - // if (rinfo.port !== peerPort || rinfo.address !== peerAddress) return + // create a new socket to replace it in the pool + const oldIndex = this.socketPool.findIndex(s => s === pooledSocket) + this.socketPool[oldIndex] = this.dgram().createSocket('udp4', null, this).unref() - // cancel scheduled events - this._clearInterval(interval) - this._clearTimeout(pooledSocket.reclaim) - - // remove any events currently bound on the socket - pooledSocket.removeAllListeners() - pooledSocket.on('message', (msg, rinfo) => { - this._onMessage(msg, rinfo) - }) - - this._onConnection(packet, peer.peerId, rinfo.port, rinfo.address, undefined, pooledSocket) - - const p = { - clusterId, - subclusterId, - clock: this.clock, - message: { - requesterPeerId: this.peerId, - natType: this.natType, - isConnection: true - } - } + this._onMessage(msg, rinfo) + }) - const data = await Packet.encode(new PacketPing(p)) + try { + pooledSocket.send(data, peerPort, peerAddress) + } catch (err) { + console.error('STRATEGY_TRAVERSAL_CONNECT error', err) + } + }, 10) - pooledSocket.send(data, rinfo.port, rinfo.address) + return + } - // create a new socket to replace it in the pool - const oldIndex = this.socketPool.findIndex(s => s === pooledSocket) - this.socketPool[oldIndex] = dgram.createSocket('udp4', null, this).unref() + if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { + // TODO could allow multiple proxies + this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) + debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') + } - this._onMessage(msg, rinfo) - }) + if (strategy === NAT.STRATEGY_TRAVERSAL_OPEN) { + peer.opening = Date.now() - try { - pooledSocket.send(data, peerPort, peerAddress) - } catch (err) { - console.error('STRATEGY_TRAVERSAL_CONNECT error', err) - } - }, 10) + const portsCache = new Set() - return + if (!this.bdpCache.length) { + globalThis.bdpCache = this.bdpCache = Array.from({ length: 1024 }, () => getRandomPort(portsCache)) } - if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { - // TODO could allow multiple proxies - this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) - debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') + for (const port of this.bdpCache) { + this.send(Buffer.from([0x1]), port, packet.message.address) } - if (strategy === NAT.STRATEGY_TRAVERSAL_OPEN) { - peer.opening = Date.now() - - const portsCache = new Set() + return + } - if (!this.bdpCache.length) { - globalThis.bdpCache = this.bdpCache = Array.from({ length: 1024 }, () => getRandomPort(portsCache)) - } + if (strategy === NAT.STRATEGY_DIRECT_CONNECT) { + debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') + } - for (const port of this.bdpCache) { - this.send(Buffer.from([0x1]), port, packet.message.address) - } + if (strategy === NAT.STRATEGY_DEFER) { + debug(this.peerId, '++ NAT STRATEGY_DEFER') + } - return - } + this.ping(peer, true, props) + } - if (strategy === NAT.STRATEGY_DIRECT_CONNECT) { - debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') - } + /** + * Received an Join Packet + * @return {undefined} + * @ignore + */ + async _onJoin (packet, port, address, data) { + this.metrics.i[packet.type]++ + + const pid = packet.packetId.toString('hex') + if (packet.message.requesterPeerId === this.peerId) return + if (this.gate.has(pid)) return + if (packet.clusterId.length !== 32) return + + this.lastUpdate = Date.now() + + const peerId = packet.message.requesterPeerId + const rendezvousDeadline = packet.message.rendezvousDeadline + const clusterId = packet.clusterId + const subclusterId = packet.subclusterId + const peerAddress = packet.message.address + const peerPort = packet.message.port + + // prevents premature pruning; a peer is not directly connecting + const peer = this.peers.find(p => p.peerId === peerId) + if (peer) peer.lastUpdate = Date.now() + + // a rendezvous isn't relevant if it's too old, just drop the packet + if (rendezvousDeadline && rendezvousDeadline < Date.now()) return + + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') + + debug(this.peerId, '<- JOIN (' + + `peerId=${peerId.slice(0, 6)}, ` + + `clock=${packet.clock}, ` + + `hops=${packet.hops}, ` + + `clusterId=${cid}, ` + + `subclusterId=${scid}, ` + + `address=${address}:${port})` + ) + + // + // This packet represents a peer who wants to join the network and is a + // member of our cluster. The packet was replicated though the network + // and contains the details about where the peer can be reached, in this + // case we want to ping that peer so we can be introduced to them. + // + if (rendezvousDeadline && !this.indexed && this.clusters[cid]) { + if (!packet.message.rendezvousRequesterPeerId) { + const pid = packet.packetId.toString('hex') + this.gate.set(pid, 2) + + // TODO it would tighten up the transition time between dropped peers + // if we check strategy from (packet.message.natType, this.natType) and + // make introductions that create more mutually known peers. + debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) + + const data = await Packet.encode(new PacketJoin({ + clock: packet.clock, + subclusterId: packet.subclusterId, + clusterId: packet.clusterId, + message: { + requesterPeerId: this.peerId, + natType: this.natType, + address: this.address, + port: this.port, + rendezvousType: packet.message.natType, + rendezvousRequesterPeerId: packet.message.requesterPeerId + } + })) - if (strategy === NAT.STRATEGY_DEFER) { - debug(this.peerId, '++ NAT STRATEGY_DEFER') + this.send( + data, + packet.message.rendezvousPort, + packet.message.rendezvousAddress + ) } - - this.ping(peer, true, props) } - /** - * Received an Join Packet - * @return {undefined} - * @ignore - */ - async _onJoin (packet, port, address, data) { - this.metrics.i[packet.type]++ + const filter = p => ( + p.connected && // you can't intro peers who aren't connected + p.peerId !== packet.message.requesterPeerId && + p.peerId !== packet.message.rendezvousRequesterPeerId && + !p.indexed + ) - const pid = packet.packetId.toString('hex') - if (packet.message.requesterPeerId === this.peerId) return - if (this.gate.has(pid)) return - if (!packet.clusterId) return + let peers = this.getPeers(packet, this.peers, [{ port, address }], filter) - this.lastUpdate = Date.now() + // + // A peer who belongs to the same cluster as the peer who's replicated + // join was discovered, sent us a join that has a specification for who + // they want to be introduced to. + // + if (packet.message.rendezvousRequesterPeerId && this.peerId === packet.message.rendezvousPeerId) { + const peer = this.peers.find(p => p.peerId === packet.message.rendezvousRequesterPeerId) - const peerId = packet.message.requesterPeerId - const rendezvousDeadline = packet.message.rendezvousDeadline - const clusterId = packet.clusterId - const subclusterId = packet.subclusterId - const peerAddress = packet.message.address - const peerPort = packet.message.port - - // prevents premature pruning; a peer is not directly connecting - const peer = this.peers.find(p => p.peerId === peerId) - if (peer) peer.lastUpdate = Date.now() - - // a rendezvous isn't relevant if it's too old, just drop the packet - if (rendezvousDeadline && rendezvousDeadline < Date.now()) return - - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') - - debug(this.peerId, '<- JOIN (' + - `peerId=${peerId.slice(0, 6)}, ` + - `clock=${packet.clock}, ` + - `hops=${packet.hops}, ` + - `clusterId=${cid}, ` + - `subclusterId=${scid}, ` + - `address=${address}:${port})` - ) + if (!peer) { + debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) + return + } - // - // This packet represents a peer who wants to join the network and is a - // member of our cluster. The packet was replicated though the network - // and contains the details about where the peer can be reached, in this - // case we want to ping that peer so we can be introduced to them. - // - if (rendezvousDeadline && !this.indexed && this.clusters[cid]) { - if (!packet.message.rendezvousRequesterPeerId) { - const pid = packet.packetId.toString('hex') - this.gate.set(pid, 2) + // peer.natType = packet.message.rendezvousType + peers = [peer] - // TODO it would tighten up the transition time between dropped peers - // if we check strategy from (packet.message.natType, this.natType) and - // make introductions that create more mutually known peers. - debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) + debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + } - const data = await Packet.encode(new PacketJoin({ - clock: packet.clock, - subclusterId: packet.subclusterId, - clusterId: packet.clusterId, - message: { - requesterPeerId: this.peerId, - natType: this.natType, - address: this.address, - port: this.port, - rendezvousType: packet.message.natType, - rendezvousRequesterPeerId: packet.message.requesterPeerId - } - })) + for (const peer of peers) { + const message1 = { + requesterPeerId: peer.peerId, + responderPeerId: this.peerId, + isRendezvous: !!packet.message.rendezvousPeerId, + natType: peer.natType, + address: peer.address, + port: peer.port + } - this.send( - data, - packet.message.rendezvousPort, - packet.message.rendezvousAddress - ) - } + const message2 = { + requesterPeerId: packet.message.requesterPeerId, + responderPeerId: this.peerId, + isRendezvous: !!packet.message.rendezvousPeerId, + natType: packet.message.natType, + address: packet.message.address, + port: packet.message.port } - const filter = p => ( - p.connected && // you can't intro peers who aren't connected - p.peerId !== packet.message.requesterPeerId && - p.peerId !== packet.message.rendezvousRequesterPeerId && - !p.indexed - ) + const opts = { + hops: packet.hops + 1, + clusterId, + subclusterId, + usr1: String(Date.now()) + } - let peers = this.getPeers(packet, this.peers, [{ port, address }], filter) + const intro1 = await Packet.encode(new PacketIntro({ ...opts, message: message1 })) + const intro2 = await Packet.encode(new PacketIntro({ ...opts, message: message2 })) // - // A peer who belongs to the same cluster as the peer who's replicated - // join was discovered, sent us a join that has a specification for who - // they want to be introduced to. + // Send intro1 to the peer described in the message + // Send intro2 to the peer in this loop // - if (packet.message.rendezvousRequesterPeerId && this.peerId === packet.message.rendezvousPeerId) { - const peer = this.peers.find(p => p.peerId === packet.message.rendezvousRequesterPeerId) - - if (!peer) { - debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) - return - } + debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) + debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) - // peer.natType = packet.message.rendezvousType - peers = [peer] + peer.lastRequest = Date.now() - debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) - } - - for (const peer of peers) { - const message1 = { - requesterPeerId: peer.peerId, - responderPeerId: this.peerId, - isRendezvous: !!packet.message.rendezvousPeerId, - natType: peer.natType, - address: peer.address, - port: peer.port - } + this.send(intro2, peer.port, peer.address) + this.send(intro1, packet.message.port, packet.message.address) - const message2 = { - requesterPeerId: packet.message.requesterPeerId, - responderPeerId: this.peerId, - isRendezvous: !!packet.message.rendezvousPeerId, - natType: packet.message.natType, - address: packet.message.address, - port: packet.message.port - } - - const opts = { - hops: packet.hops + 1, - clusterId, - subclusterId, - usr1: String(Date.now()) - } - - const intro1 = await Packet.encode(new PacketIntro({ ...opts, message: message1 })) - const intro2 = await Packet.encode(new PacketIntro({ ...opts, message: message2 })) - - // - // Send intro1 to the peer described in the message - // Send intro2 to the peer in this loop - // - debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) - debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) - - peer.lastRequest = Date.now() - - this.send(intro2, peer.port, peer.address) - this.send(intro1, packet.message.port, packet.message.address) - - this.gate.set(Packet.decode(intro1).packetId.toString('hex'), 2) - this.gate.set(Packet.decode(intro2).packetId.toString('hex'), 2) - } + this.gate.set(Packet.decode(intro1).packetId.toString('hex'), 2) + this.gate.set(Packet.decode(intro2).packetId.toString('hex'), 2) + } - this.gate.set(packet.packetId.toString('hex'), 2) + this.gate.set(packet.packetId.toString('hex'), 2) - if (packet.hops >= this.maxHops) return - if (this.indexed && !packet.clusterId) return + if (packet.hops >= this.maxHops) return + if (this.indexed && !packet.clusterId) return - if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED && !packet.message.rendezvousDeadline) { - packet.message.rendezvousAddress = this.address - packet.message.rendezvousPort = this.port - packet.message.rendezvousType = this.natType - packet.message.rendezvousPeerId = this.peerId - packet.message.rendezvousDeadline = Date.now() + this.config.keepalive - } + if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED && !packet.message.rendezvousDeadline) { + packet.message.rendezvousAddress = this.address + packet.message.rendezvousPort = this.port + packet.message.rendezvousType = this.natType + packet.message.rendezvousPeerId = this.peerId + packet.message.rendezvousDeadline = Date.now() + this.config.keepalive + } - debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) - this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) + debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) + this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) - if (packet.hops <= 1) { - this._onConnection(packet, packet.message.requesterPeerId, port, address) - } + if (packet.hops <= 1) { + this._onConnection(packet, packet.message.requesterPeerId, port, address) } + } - /** - * Received an Publish Packet - * @return {undefined} - * @ignore - */ - async _onPublish (packet, port, address, data) { - this.metrics.i[packet.type]++ + /** + * Received an Publish Packet + * @return {undefined} + * @ignore + */ + async _onPublish (packet, port, address, data) { + this.metrics.i[packet.type]++ - // only cache if this packet if i am part of this subclusterId - // const cluster = this.clusters[packet.clusterId] - // if (cluster && cluster[packet.subclusterId]) { + // only cache if this packet if i am part of this subclusterId + // const cluster = this.clusters[packet.clusterId] + // if (cluster && cluster[packet.subclusterId]) { - const pid = packet.packetId.toString('hex') - if (this.cache.has(pid)) { - debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) - return - } + const pid = packet.packetId.toString('hex') + if (this.cache.has(pid)) { + debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) + return + } - debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) - this.cache.insert(pid, packet) + debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) + this.cacheInsert(packet) - const ignorelist = [{ address, port }] - const scid = packet.subclusterId.toString('base64') + const ignorelist = [{ address, port }] + const scid = packet.subclusterId.toString('base64') - if (!this.indexed && this.encryption.has(scid)) { - let p = packet.copy() - if (p.index > -1) p = await this.cache.compose(p) - if (p?.index === -1 && this.onPacket) this.onPacket(p, port, address) - } + if (!this.indexed && this.encryption.has(scid)) { + let p = packet.copy() + if (p.index > -1) p = await this.cache.compose(p) + if (p?.index === -1 && this.onPacket) this.onPacket(p, port, address) + } - if (packet.hops >= this.maxHops) return - this.mcast(packet, ignorelist) + if (packet.hops >= this.maxHops) return + this.mcast(packet, ignorelist) - // } - } + // } + } - /** - * Received an Stream Packet - * @return {undefined} - * @ignore - */ - async _onStream (packet, port, address, data) { - this.metrics.i[packet.type]++ + /** + * Received an Stream Packet + * @return {undefined} + * @ignore + */ + async _onStream (packet, port, address, data) { + this.metrics.i[packet.type]++ - const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) + const pid = packet.packetId.toString('hex') + if (this.gate.has(pid)) return + this.gate.set(pid, 1) - const streamTo = packet.usr3.toString('hex') - const streamFrom = packet.usr4.toString('hex') + const streamTo = packet.usr3.toString('hex') + const streamFrom = packet.usr4.toString('hex') - // only help packets with a higher hop count if they are in our cluster - // if (packet.hops > 2 && !this.clusters[packet.cluster]) return + // only help packets with a higher hop count if they are in our cluster + // if (packet.hops > 2 && !this.clusters[packet.cluster]) return - const peerFrom = this.peers.find(p => p.peerId.toString('hex') === streamFrom.toString('hex')) - if (!peerFrom) return + const peerFrom = this.peers.find(p => p.peerId.toString('hex') === streamFrom.toString('hex')) + if (!peerFrom) return - // stream message is for this peer - if (streamTo.toString('hex') === this.peerId.toString('hex')) { - const scid = packet.subclusterId.toString('base64') + // stream message is for this peer + if (streamTo.toString('hex') === this.peerId.toString('hex')) { + const scid = packet.subclusterId.toString('base64') - if (this.encryption.has(scid)) { - let p = packet.copy() // clone the packet so it's not modified + if (this.encryption.has(scid)) { + let p = packet.copy() // clone the packet so it's not modified - if (packet.index > -1) { // if it needs to be composed... - p.timestamp = Date.now() - this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial + if (packet.index > -1) { // if it needs to be composed... + p.timestamp = Date.now() + this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial - p = await this.cache.compose(p, this.streamBuffer) // try to compose - if (!p) return // could not compose + p = await this.cache.compose(p, this.streamBuffer) // try to compose + if (!p) return // could not compose - if (p) { // if successful, delete the artifacts - const previousId = p.index === 0 ? p.packetId : p.previousId - const pid = previousId.toString('hex') + if (p) { // if successful, delete the artifacts + const previousId = p.index === 0 ? p.packetId : p.previousId + const pid = previousId.toString('hex') - this.streamBuffer.forEach((v, k) => { - if (k === pid) this.streamBuffer.delete(k) - if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) - }) - } + this.streamBuffer.forEach((v, k) => { + if (k === pid) this.streamBuffer.delete(k) + if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) + }) } - - if (this.onStream) this.onStream(p, peerFrom, port, address) } - return + if (this.onStream) this.onStream(p, peerFrom, port, address) } - // stream message is for another peer - const peerTo = this.peers.find(p => p.peerId === streamTo) - if (!peerTo) { - debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) - return - } + return + } - if (packet.hops >= this.maxHops) { - debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) - return - } + // stream message is for another peer + const peerTo = this.peers.find(p => p.peerId === streamTo) + if (!peerTo) { + debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) + return + } - debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) - this.send(await Packet.encode(packet), peerTo.port, peerTo.address) - if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) + if (packet.hops >= this.maxHops) { + debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) + return } - /** - * Received any packet on the probe port to determine the firewall: - * are you port restricted, host restricted, or unrestricted. - * @return {undefined} - * @ignore - */ - _onProbeMessage (data, { port, address }) { - this._clearTimeout(this.probeReflectionTimeout) + debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) + this.send(await Packet.encode(packet), peerTo.port, peerTo.address) + if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) + } - const packet = Packet.decode(data) - if (!packet || packet.version !== VERSION) return - if (packet?.type !== 2) return + /** + * Received any packet on the probe port to determine the firewall: + * are you port restricted, host restricted, or unrestricted. + * @return {undefined} + * @ignore + */ + _onProbeMessage (data, { port, address }) { + this._clearTimeout(this.probeReflectionTimeout) - const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) + const packet = Packet.decode(data) + if (!packet || packet.version !== VERSION) return + if (packet?.type !== 2) return - const { reflectionId } = packet.message - debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) + const pid = packet.packetId.toString('hex') + if (this.gate.has(pid)) return + this.gate.set(pid, 1) - if (this.onProbe) this.onProbe(data, port, address) - if (this.reflectionId !== reflectionId || !this.reflectionId) return + const { reflectionId } = packet.message + debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) - // reflection stage is encoded in the last hex char of the reflectionId, or 0 if not available. - // const reflectionStage = reflectionId ? parseInt(reflectionId.slice(-1), 16) : 0 + if (this.onProbe) this.onProbe(data, port, address) + if (this.reflectionId !== reflectionId || !this.reflectionId) return - if (this.reflectionStage === 1) { - debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) - if (!packet.message?.port) return // message must include a port number + // reflection stage is encoded in the last hex char of the reflectionId, or 0 if not available. + // const reflectionStage = reflectionId ? parseInt(reflectionId.slice(-1), 16) : 0 - // successfully discovered the probe socket external port - this.config.probeExternalPort = packet.message.port + if (this.reflectionStage === 1) { + debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) + if (!packet.message?.port) return // message must include a port number - // move to next reflection stage - this.reflectionStage = 1 - this.reflectionId = null - this.requestReflection() - return - } + // successfully discovered the probe socket external port + this.config.probeExternalPort = packet.message.port - if (this.reflectionStage === 2) { - debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) - - // if we have previously sent an outbount message to this peer on the probe port - // then our NAT will have a mapping for their IP, but not their IP+Port. - if (!NAT.isFirewallDefined(this.nextNatType)) { - this.nextNatType |= NAT.FIREWALL_ALLOW_KNOWN_IP - debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) - } else { - this.nextNatType |= NAT.FIREWALL_ALLOW_ANY - debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) - } + // move to next reflection stage + this.reflectionStage = 1 + this.reflectionId = null + this.requestReflection() + return + } - // wait for all messages to arrive + if (this.reflectionStage === 2) { + debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) + + // if we have previously sent an outbount message to this peer on the probe port + // then our NAT will have a mapping for their IP, but not their IP+Port. + if (!NAT.isFirewallDefined(this.nextNatType)) { + this.nextNatType |= NAT.FIREWALL_ALLOW_KNOWN_IP + debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) + } else { + this.nextNatType |= NAT.FIREWALL_ALLOW_ANY + debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) } + + // wait for all messages to arrive } + } - /** - * When a packet is received it is decoded, the packet contains the type - * of the message. Based on the message type it is routed to a function. - * like WebSockets, don't answer queries unless we know its another SRP peer. - * - * @param {Buffer|Uint8Array} data - * @param {{ port: number, address: string }} info - */ - async _onMessage (data, { port, address }) { - const packet = Packet.decode(data) - if (!packet || packet.version !== VERSION) return + /** + * When a packet is received it is decoded, the packet contains the type + * of the message. Based on the message type it is routed to a function. + * like WebSockets, don't answer queries unless we know its another SRP peer. + * + * @param {Buffer|Uint8Array} data + * @param {{ port: number, address: string }} info + */ + async _onMessage (data, { port, address }) { + const packet = Packet.decode(data) + if (!packet || packet.version !== VERSION) return - const peer = this.peers.find(p => p.address === address && p.port === port) - if (peer) peer.lastUpdate = Date.now() + const peer = this.peers.find(p => p.address === address && p.port === port) + if (peer) peer.lastUpdate = Date.now() - const cid = packet.clusterId.toString('base64') - const scid = packet.subclusterId.toString('base64') + const cid = packet.clusterId.toString('base64') + const scid = packet.subclusterId.toString('base64') - // debug('<- PACKET', packet.type, port, address) - const clusters = this.clusters[cid] - const subcluster = clusters && clusters[scid] + // debug('<- PACKET', packet.type, port, address) + const clusters = this.clusters[cid] + const subcluster = clusters && clusters[scid] - if (!this.config.limitExempt) { - if (rateLimit(this.rates, packet.type, port, address, subcluster)) { - debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) - this.metrics.i.REJECTED++ - return - } - if (this.onLimit && !this.onLimit(packet, port, address)) return + if (!this.config.limitExempt) { + if (rateLimit(this.rates, packet.type, port, address, subcluster)) { + debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) + this.metrics.i.REJECTED++ + return } + if (this.onLimit && !this.onLimit(packet, port, address)) return + } - const args = [packet, port, address, data] + const args = [packet, port, address, data] - if (this.firewall) if (!this.firewall(...args)) return - if (this.onData) this.onData(...args) + if (this.firewall) if (!this.firewall(...args)) return + if (this.onData) this.onData(...args) - switch (packet.type) { - case PacketPing.type: return this._onPing(...args) - case PacketPong.type: return this._onPong(...args) - } + switch (packet.type) { + case PacketPing.type: return this._onPing(...args) + case PacketPong.type: return this._onPong(...args) + } - if (!this.natType && !this.indexed) return + if (!this.natType && !this.indexed) return - switch (packet.type) { - case PacketIntro.type: return this._onIntro(...args) - case PacketJoin.type: return this._onJoin(...args) - case PacketPublish.type: return this._onPublish(...args) - case PacketStream.type: return this._onStream(...args) - case PacketSync.type: return this._onSync(...args) - case PacketQuery.type: return this._onQuery(...args) - } + switch (packet.type) { + case PacketIntro.type: return this._onIntro(...args) + case PacketJoin.type: return this._onJoin(...args) + case PacketPublish.type: return this._onPublish(...args) + case PacketStream.type: return this._onStream(...args) + case PacketSync.type: return this._onSync(...args) + case PacketQuery.type: return this._onQuery(...args) } } - - return Peer } -export default wrap +export default Peer diff --git a/api/stream-relay/packets.js b/api/stream-relay/packets.js index 4f188815e3..c46ac43b50 100644 --- a/api/stream-relay/packets.js +++ b/api/stream-relay/packets.js @@ -1,7 +1,7 @@ import { randomBytes } from '../crypto.js' import { isBufferLike } from '../util.js' import { Buffer } from '../buffer.js' -import debug from './index.js' +import { debug } from './index.js' /** * Hash function factory. @@ -354,7 +354,6 @@ export class Packet { continue } - // console.log(k, value, spec) const encoded = Buffer.from(value || spec.default, spec.encoding) if (value?.length && encoded.length > spec.bytes) { diff --git a/api/stream-relay/sugar.js b/api/stream-relay/sugar.js deleted file mode 100644 index 0fef434235..0000000000 --- a/api/stream-relay/sugar.js +++ /dev/null @@ -1,373 +0,0 @@ -import { wrap, Encryption, sha256, NAT, RemotePeer } from './index.js' -import { sodium } from '../crypto.js' -import { Buffer } from '../buffer.js' -import { isBufferLike } from '../util.js' -import { Packet, CACHE_TTL } from './packets.js' - -/** - * Creates and manages a network bus for communication. - * - * @module Network - * @param {object} dgram - The dgram module for network communication. - * @param {object} events - The events module for event handling. - * @returns {Promise} - A promise that resolves to the network bus. - */ -export default (dgram, events) => { - let _peer = null - let bus = null - - /** - * Initializes and returns the network bus. - * - * @async - * @function - * @param {object} options - Configuration options for the network bus. - * @returns {Promise} - A promise that resolves to the initialized network bus. - */ - return async (options = {}) => { - if (bus) return bus - - await sodium.ready - bus = new events.EventEmitter() - bus._on = bus.on - bus._once = bus.once - bus._emit = bus.emit - - if (!options.indexed) { - if (!options.clusterId && !options.config?.clusterId) { - throw new Error('expected options.clusterId') - } - - if (typeof options.signingKeys !== 'object') throw new Error('expected options.signingKeys to be of type Object') - if (options.signingKeys.publicKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.publicKey to be of type Uint8Array') - if (options.signingKeys.privateKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.privateKey to be of type Uint8Array') - } - - let clusterId = bus.clusterId = options.clusterId || options.config?.clusterId - - if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters - - _peer = new (wrap(dgram))(options) // only one peer per process makes sense - - _peer.onConnection = (packet, ...args) => { - } - - _peer.onJoin = (packet, ...args) => { - if (!packet.clusterId.equals(clusterId)) return - bus._emit('#join', packet, ...args) - } - - _peer.onPacket = (packet, ...args) => { - if (!packet.clusterId.equals(clusterId)) return - bus._emit('#packet', packet, ...args) - } - - _peer.onStream = (packet, ...args) => { - if (!packet.clusterId.equals(clusterId)) return - bus._emit('#stream', packet, ...args) - } - - _peer.onData = (...args) => bus._emit('#data', ...args) - _peer.onSend = (...args) => bus._emit('#send', ...args) - _peer.onFirewall = (...args) => bus._emit('#firewall', ...args) - _peer.onMulticast = (...args) => bus._emit('#multicast', ...args) - _peer.onJoin = (...args) => bus._emit('#join', ...args) - _peer.onSync = (...args) => bus._emit('#sync', ...args) - _peer.onSyncStart = (...args) => bus._emit('#sync-start', ...args) - _peer.onSyncEnd = (...args) => bus._emit('#sync-end', ...args) - _peer.onConnection = (...args) => bus._emit('#connection', ...args) - _peer.onDisconnection = (...args) => bus._emit('#disconnection', ...args) - _peer.onQuery = (...args) => bus._emit('#query', ...args) - _peer.onNat = (...args) => bus._emit('#network-change', ...args) - _peer.onWarn = (...args) => bus._emit('#warning', ...args) - _peer.onState = (...args) => bus._emit('#state', ...args) - _peer.onConnecting = (...args) => bus._emit('#connecting', ...args) - - // TODO check if its not a network error - _peer.onError = (...args) => bus._emit('#error', ...args) - - _peer.onReady = () => { - _peer.isReady = true - bus._emit('#ready', bus.address()) - } - - bus.peer = _peer - bus.peerId = _peer.peerId - - bus.subclusters = new Map() - - /** - * Gets the address information of the network peer. - * - * @function - * @returns {object} - The address information. - */ - bus.address = () => ({ - address: _peer.address, - port: _peer.port, - natType: NAT.toString(_peer.natType) - }) - - /** - * Indexes a new peer in the network. - * - * @function - * @param {object} params - Peer information. - * @param {string} params.peerId - The peer ID. - * @param {string} params.address - The peer address. - * @param {number} params.port - The peer port. - * @throws {Error} - Throws an error if required parameters are missing. - */ - bus.indexPeer = ({ peerId, address, port }) => { - if (!peerId) throw new Error('options.peerId required') - if (!address) throw new Error('options.address required') - if (!port) throw new Error('options.port required') - - _peer.peers.push(new RemotePeer({ peerId, address, port, indexed: true })) - } - - bus.reconnect = () => { - _peer.lastUpdate = 0 - _peer.requestReflection() - } - - bus.disconnect = () => { - _peer.natType = null - _peer.reflectionStage = 0 - _peer.reflectionId = null - _peer.reflectionTimeout = null - _peer.probeReflectionTimeout = null - } - - bus.sealMessage = (m, v = options.signingKeys) => _peer.encryption.sealMessage(m, v) - bus.openMessage = (m, v = options.signingKeys) => _peer.encryption.openMessage(m, v) - - bus.seal = (m, v = options.signingKeys) => _peer.encryption.seal(m, v) - bus.open = (m, v = options.signingKeys) => _peer.encryption.open(m, v) - - bus.query = (...args) => _peer.query(...args) - bus.state = () => _peer.getState() - bus.cacheSize = () => _peer.cache.size - bus.cacheBytes = () => _peer.cache.bytes - - const pack = async (eventName, value, opts = {}) => { - if (typeof eventName !== 'string') throw new Error('event name must be a string') - if (eventName.length === 0) throw new Error('event name too short') - - if (opts.ttl) opts.ttl = Math.min(opts.ttl, CACHE_TTL) - - const args = { - clusterId, - ...opts, - usr1: await sha256(eventName, { bytes: true }) - } - - if (!isBufferLike(value) && typeof value === 'object') { - try { - args.message = Buffer.from(JSON.stringify(value)) - } catch (err) { - return bus._emit('error', err) - } - } else { - args.message = Buffer.from(value) - } - - args.usr2 = Buffer.from(options.signingKeys.publicKey) - args.sig = Encryption.sign(args.message, options.signingKeys.privateKey) - - return args - } - - const unpack = async packet => { - let opened - let verified - const sub = bus.subclusters.get(packet.subclusterId.toString('base64')) - if (!sub) return {} - - try { - opened = _peer.encryption.open(packet.message, packet.subclusterId.toString('base64')) - } catch (err) { - sub._emit('warning', err) - return {} - } - - if (packet.sig) { - try { - if (Encryption.verify(opened, packet.sig, packet.usr2)) { - verified = true - } - } catch (err) { - sub._emit('warning', err) - return {} - } - } - - return { opened, verified } - } - - /** - * Publishes an event to the network bus. - * - * @async - * @function - * @param {string} eventName - The name of the event. - * @param {any} value - The value associated with the event. - * @param {object} opts - Additional options for publishing. - * @returns {Promise} - A promise that resolves to the published event details. - */ - bus.emit = async (eventName, value, opts = {}) => { - return await _peer.publish(options.sharedKey, await pack(eventName, value, opts)) - } - - bus.on = async (eventName, cb) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - bus._on(eventName, cb) - } - - bus.subcluster = async (options = {}) => { - if (!options.sharedKey?.constructor.name) { - throw new Error('expected options.sharedKey to be of type Uint8Array') - } - - const derivedKeys = await Encryption.createKeyPair(options.sharedKey) - const subclusterId = Buffer.from(derivedKeys.publicKey) - const scid = subclusterId.toString('base64') - - if (bus.subclusters.has(scid)) return bus.subclusters.get(scid) - - const sub = new events.EventEmitter() - sub._emit = sub.emit - sub._on = sub.on - sub.peers = new Map() - - bus.subclusters.set(scid, sub) - - sub.peerId = _peer.peerId - sub.subclusterId = subclusterId - sub.sharedKey = options.sharedKey - sub.derivedKeys = derivedKeys - - sub.emit = async (eventName, value, opts = {}) => { - opts.clusterId = opts.clusterId || clusterId - opts.subclusterId = opts.subclusterId || sub.subclusterId - - const args = await pack(eventName, value, opts) - - if (sub.peers.values().length) { - let packets = [] - - for (const p of sub.peers.values()) { - const r = await p._peer.write(sub.sharedKey, args) - if (packets.length === 0) packets = r - } - - for (const packet of packets) { - const p = Packet.from(packet) - _peer.cache.insert(packet.packetId.toString('hex'), p) - - _peer.unpublished[packet.packetId.toString('hex')] = Date.now() - if (globalThis.navigator && !globalThis.navigator.onLine) continue - - _peer.mcast(packet) - } - return packets - } else { - const packets = await _peer.publish(sub.sharedKey, args) - return packets - } - } - - sub.on = async (eventName, cb) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - sub._on(eventName, cb) - } - - sub.off = async (eventName, fn) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - sub.removeListener(eventName, fn) - } - - sub.join = () => _peer.join(sub.sharedKey, options) - - bus._on('#ready', () => { - const subcluster = bus.subclusters.get(sub.subclusterId.toString('base64')) - if (subcluster) _peer.join(subcluster.sharedKey, options) - }) - - _peer.join(sub.sharedKey, options) - return sub - } - - bus._on('#join', async (packet, peer) => { - const sub = bus.subclusters.get(packet.subclusterId.toString('base64')) - if (!sub) return - - let ee = sub.peers.get(peer.peerId) - - if (!ee) { - ee = new events.EventEmitter() - - ee._on = ee.on - ee._emit = ee.emit - - ee.peerId = peer.peerId - ee.address = peer.address - ee.port = peer.port - - ee.emit = async (eventName, value, opts = {}) => { - if (!ee._peer.write) return - - opts.clusterId = opts.clusterId || clusterId - opts.subclusterId = opts.subclusterId || sub.subclusterId - - const args = await pack(eventName, value, opts) - return peer.write(sub.sharedKey, args) - } - - ee.on = async (eventName, cb) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - ee._on(eventName, cb) - } - } - - const oldPeer = sub.peers.has(peer.peerId) - const portChange = oldPeer.port !== peer.port - const addressChange = oldPeer.address !== peer.address - const natChange = oldPeer.natType !== peer.natType - const change = portChange || addressChange || natChange - - ee._peer = peer - - sub.peers.set(peer.peerId, ee) - if (!oldPeer || change) sub._emit('#join', ee, packet) - }) - - const handlePacket = async (packet, peer, port, address) => { - const scid = packet.subclusterId.toString('base64') - const sub = bus.subclusters.get(scid) - if (!sub) return - - const eventName = packet.usr1.toString('hex') - const { verified, opened } = await unpack(packet) - if (verified) packet.verified = true - - sub._emit(eventName, opened, packet) - - const ee = sub.peers.get(packet.streamFrom || peer?.peerId) - if (ee) ee._emit(eventName, opened, packet) - } - - bus._on('#stream', handlePacket) - bus._on('#packet', handlePacket) - - bus._on('#disconnection', peer => { - for (const sub of bus.subclusters) { - sub._emit('#leave', peer) - sub.peers.delete(peer.peerId) - } - }) - - await _peer.init() - return bus - } -} diff --git a/api/stream-relay/worker.js b/api/stream-relay/worker.js new file mode 100644 index 0000000000..865d777f4c --- /dev/null +++ b/api/stream-relay/worker.js @@ -0,0 +1,372 @@ +/** + * A utility to run run the protocol in a thread seperate from the UI. + * + * import { network } from + * + * Socket Node + * --- --- + * API API + * Proxy Protocol + * Protocol + * + */ +import { Peer } from './index.js' +import dgram from '../dgram.js' + +const { pathname } = new URL(import.meta.url) + +class Deferred { + constructor () { + this._promise = new Promise((resolve, reject) => { + this.resolve = resolve + this.reject = reject + }) + this.then = this._promise.then.bind(this._promise) + this.catch = this._promise.catch.bind(this._promise) + this.finally = this._promise.finally.bind(this._promise) + this[Symbol.toStringTag] = 'Promise' + } +} + +function deepClone (object, map = new Map()) { + if (map.has(object)) return map.get(object) + + const isNull = object === null + const isNotObject = typeof object !== 'object' + const isArrayBuffer = object instanceof ArrayBuffer + const isArray = Array.isArray(object) + const isUint8Array = object instanceof Uint8Array + const isMessagePort = object instanceof MessagePort + + if (isMessagePort || isNotObject || isNull || isArrayBuffer) return object + if (isUint8Array) return new Uint8Array(object) + if (isArrayBuffer) return object.slice(0) + + if (isArray) { + const clonedArray = [] + map.set(object, clonedArray) + for (const item of object) { + clonedArray.push(deepClone(item, map)) + } + return clonedArray + } + + const clonedObj = {} + map.set(object, clonedObj) + for (const key in object) { + clonedObj[key] = deepClone(object[key], map) + } + + return clonedObj +} + +function transferOwnership (...objects) { + const transfers = [] + + function add (value) { + if (!transfers.includes(value)) { + transfers.push(value) + } + } + + objects.forEach(object => { + if (object instanceof ArrayBuffer || ArrayBuffer.isView(object)) { + add(object.buffer) + } else if (Array.isArray(object) || (object && typeof object === 'object')) { + for (const value of Object.values(object)) { + if (value instanceof MessagePort) add(value) + } + } + }) + + return transfers +} + +const isWorkerThread = !globalThis.window && globalThis.self === globalThis + +/** + * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ +export class PeerWorkerProxy { + #promises = new Map() + #channel = null + #worker = null + #index = 0 + #port = null + + constructor (options, port, fn) { + if (!isWorkerThread) { + this.#channel = new MessageChannel() + this.#worker = new window.Worker(pathname) + + this.#worker.addEventListener('error', err => { + throw err + }) + + this.#worker.postMessage({ + port: this.#channel.port2 + }, [this.#channel.port2]) + + // when the main thread receives a message from the worker + this.#channel.port1.onmessage = ({ data: args }) => { + const { + err, + prop, + data, + seq + } = args + + if (!prop && err) { + throw new Error(err) + } + + if (prop && typeof this[prop] === 'function') { + try { + if (Array.isArray(data)) { + this[prop](...data) + } else { + this[prop](data) + } + } catch (err) { + throw new Error(err) + } + return + } + + const p = this.#promises.get(seq) + if (!p) return + + if (!p) { + console.warn(`No promise was found for the sequence (${seq})`) + return + } + + if (err) { + p.reject(err) + } else { + p.resolve(data) + } + + this.#promises.delete(seq) + } + + this.callWorkerThread('create', options) + return + } + + this.#port = port + this.#port.onmessage = fn.bind(this) + } + + async init () { + return await this.callWorkerThread('init') + } + + async reconnect () { + return await this.callWorkerThread('reconnect') + } + + async reconnect () { + return await this.callWorkerThread('disconnect') + } + + async getInfo () { + return await this.callWorkerThread('getInfo') + } + + async getState () { + return await this.callWorkerThread('getState') + } + + async open (...args) { + return await this.callWorkerThread('open', args) + } + + async seal (...args) { + return await this.callWorkerThread('seal', args) + } + + async sealUnsigned (...args) { + return await this.callWorkerThread('sealUnsigned', args) + } + + async openUnsigned (...args) { + return await this.callWorkerThread('openUnsigned', args) + } + + async addEncryptionKey (...args) { + return await this.callWorkerThread('addEncryptionKey', args) + } + + async send (...args) { + return await this.callWorkerThread('send', args) + } + + async sendUnpublished (...args) { + return await this.callWorkerThread('sendUnpublished', args) + } + + async cacheInsert (...args) { + return await this.callWorkerThread('cacheInsert', args) + } + + async mcast (...args) { + return await this.callWorkerThread('mcast', args) + } + + async requestReflection (...args) { + return await this.callWorkerThread('requestReflection', args) + } + + async join (...args) { + return await this.callWorkerThread('join', args) + } + + async publish (...args) { + return await this.callWorkerThread('publish', args) + } + + async sync (...args) { + return await this.callWorkerThread('sync', args) + } + + async close (...args) { + return await this.callWorkerThread('close', args) + } + + async query (...args) { + return await this.callWorkerThread('query', args) + } + + async compileCachePredicate (src) { + return await this.callWorkerThread('compileCachePredicate', src) + } + + callWorkerThread (prop, data) { + let transfer = [] + + if (data) { + data = deepClone(data) + transfer = transferOwnership(data) + } + + const seq = ++this.#index + const d = new Deferred() + + this.#channel.port1.postMessage( + { prop, data, seq }, + { transfer } + ) + + this.#promises.set(seq, d) + return d + } + + callMainThread (prop, args) { + for (const i in args) { + const arg = args[i] + if (!arg.peerId) continue + args[i] = { ...arg } + delete args[i].localPeer // don't copy this over + } + + try { + this.#port.postMessage( + { data: deepClone(args), prop }, + { transfer: transferOwnership(args) } + ) + } catch (err) { + this.#port.postMessage({ data: { err: err.message, prop } }) + } + } + + resolveMainThread (seq, data) { + try { + this.#port.postMessage( + { data: deepClone(data), seq }, + { transfer: transferOwnership(data) } + ) + } catch (err) { + this.#port.postMessage({ data: { err: err.message } }) + } + } +} + +if (isWorkerThread) { + let proxy + let peer + + globalThis.addEventListener('message', ({ data: source }) => { + if (proxy) return + + proxy = new PeerWorkerProxy(null, source.port, async function ({ data: args }) { + const { + prop, + data, + seq + } = args + + switch (prop) { + case 'create': { + peer = new Peer(data, dgram) + + peer.onConnecting = (...args) => this.callMainThread('onConnecting', args) + peer.onConnection = (...args) => this.callMainThread('onConnection', args) + peer.onDisconnection = (...args) => this.callMainThread('onDisconnection', args) + peer.onJoin = (...args) => this.callMainThread('onJoin', args) + peer.onPacket = (...args) => this.callMainThread('onPacket', args) + peer.onStream = (...args) => this.callMainThread('onStream', args) + peer.onData = (...args) => this.callMainThread('onData', args) + peer.onSend = (...args) => this.callMainThread('onSend', args) + peer.onFirewall = (...args) => this.callMainThread('onFirewall', args) + peer.onMulticast = (...args) => this.callMainThread('onMulticast', args) + peer.onJoin = (...args) => this.callMainThread('onJoin', args) + peer.onSync = (...args) => this.callMainThread('onSync', args) + peer.onSyncStart = (...args) => this.callMainThread('onSyncStart', args) + peer.onSyncEnd = (...args) => this.callMainThread('onSyncEnd', args) + peer.onQuery = (...args) => this.callMainThread('onQuery', args) + peer.onNat = (...args) => this.callMainThread('onNat', args) + peer.onState = (...args) => this.callMainThread('onState', args) + peer.onWarn = (...args) => this.callMainThread('onWarn', args) + peer.onError = (...args) => this.callMainThread('onError', args) + peer.onReady = (...args) => this.callMainThread('onReady', args) + break + } + + case 'compileCachePredicate': { + let predicate = new Function(`return ${data.toString()}`)() + predicate = predicate.bind(peer) + peer.cachePredicate = packet => predicate(packet) + break + } + + default: { + if (isNaN(seq) && peer[prop]) { + peer[prop] = data + return + } + + let r + + try { + if (typeof peer[prop] === 'function') { + if (Array.isArray(data)) { + r = await peer[prop](...data) + } else { + r = await peer[prop](data) + } + } else { + r = peer[prop] + } + } catch (err) { + console.error(err) + return this.resolveMainThread(seq, { err }) + } + + this.resolveMainThread(seq, { data: r }) + } + } + }) + }) +} diff --git a/test/src/network/index.js b/test/src/network/index.js new file mode 100644 index 0000000000..98b193073a --- /dev/null +++ b/test/src/network/index.js @@ -0,0 +1,28 @@ +import test from 'socket:test' +import { isIPv4 } from 'socket:ip' +import { network, Encryption } from 'socket:network' + +test('basic network constructor', async t => { + const sharedKey = await Encryption.createSharedKey('TEST') + const clusterId = await Encryption.createClusterId('TEST') + const peerId = await Encryption.createId() + const signingKeys = await Encryption.createKeyPair() + + const options = { + clusterId, + peerId, + signingKeys + } + + const socket = await network(options) + + await new Promise((resolve, reject) => { + socket.on('#ready', info => { + t.ok(isIPv4(info.address), 'got an ipv4 address') + t.ok(!isNaN(info.port), 'got a valid port') + t.equal(Buffer.from(info.peerId, 'hex').length, 32, 'valid peerid') + resolve() + }) + socket.on('#error', reject) + }) +}) From c10e7a3b26879cf0e32a24353fc79ef0e5322330 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Wed, 21 Feb 2024 14:08:22 +0100 Subject: [PATCH 0060/1178] fix missing checks in process callbacks, add options to spawn method --- api/child_process.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/child_process.js b/api/child_process.js index 7e430f6541..631c33ce1d 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -360,7 +360,7 @@ export function spawn (command, args = [], options = null) { } const child = new ChildProcess(options) - child.worker.on('online', () => child.spawn(command, args)) + child.worker.on('online', () => child.spawn(command, args, options)) // TODO signal // TODO timeout return child From 4539b5b35b8dbd5491746370e7679e8d77409f6a Mon Sep 17 00:00:00 2001 From: heapwolf Date: Wed, 21 Feb 2024 14:55:42 +0100 Subject: [PATCH 0061/1178] add worker-src to init template --- src/cli/templates.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/templates.hh b/src/cli/templates.hh index a4ed99fd25..116fbb6f12 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -199,6 +199,7 @@ constexpr auto gHelloWorld = R"HTML( content=" connect-src https: file: ipc: socket: ws://localhost:*; script-src https: socket: http://localhost:* 'unsafe-eval'; + worker-src blob: socket: 'unsafe-eval' 'unsafe-inline'; img-src https: data: file: http://localhost:*; child-src 'none'; object-src 'none'; From a4854c1454f9306638d0731395d237058e6c8082 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 17:32:15 +0100 Subject: [PATCH 0062/1178] refactor(app,core): kill spawned processes when app exits --- src/app/app.cc | 3 +++ src/core/child_process.cc | 12 ++++++++++++ src/core/core.hh | 2 ++ 3 files changed, 17 insertions(+) diff --git a/src/app/app.cc b/src/app/app.cc index d321f3b9dc..f8cbdd6514 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -1,3 +1,4 @@ +#include "../core/platform.hh" #include "../window/window.hh" #include "../ipc/ipc.hh" #include "app.hh" @@ -213,6 +214,8 @@ namespace SSC { } void App::kill () { + delete this->core; + this->core = nullptr; // Distinguish window closing with app exiting shouldExit = true; #if defined(__linux__) && !defined(__ANDROID__) diff --git a/src/core/child_process.cc b/src/core/child_process.cc index 51edae2ed0..12689d94b2 100644 --- a/src/core/child_process.cc +++ b/src/core/child_process.cc @@ -2,6 +2,18 @@ #if SSC_PLATFORM_DESKTOP namespace SSC { + Core::ChildProcess::~ChildProcess () { + Lock lock(this->mutex); + for (const auto& entry : this->handles) { + auto process = entry.second; + process->kill(); + process->wait(); + delete process; + } + + this->handles.clear(); + } + void Core::ChildProcess::kill ( const String seq, uint64_t id, diff --git a/src/core/core.hh b/src/core/core.hh index f7f5b9119d..74d714e607 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -699,6 +699,8 @@ namespace SSC { Mutex mutex; ChildProcess (auto core) : Module(core) {} + ~ChildProcess (); + void spawn ( const String seq, uint64_t id, From 4c4da6a6f30fbbb22a4089dab1792df0aeea5677 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 18:04:42 +0100 Subject: [PATCH 0063/1178] refactor(app,core,ipc): improve shutdown for app/core including child processes --- src/app/app.cc | 9 +++++++-- src/app/app.hh | 3 ++- src/core/child_process.cc | 3 +-- src/core/core.cc | 7 +++++++ src/core/core.hh | 22 ++++++++++++---------- src/ipc/bridge.cc | 24 ++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index f8cbdd6514..9b86f3a420 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -210,12 +210,17 @@ namespace SSC { } #endif + if (shouldExit) { + this->core->shuttingDown = true; + } + return shouldExit ? 1 : 0; } void App::kill () { - delete this->core; - this->core = nullptr; + this->killed = true; + this->core->shuttingDown = true; + this->core->shutdown(); // Distinguish window closing with app exiting shouldExit = true; #if defined(__linux__) && !defined(__ANDROID__) diff --git a/src/app/app.hh b/src/app/app.hh index 1dfc331a68..041e449df1 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -49,10 +49,11 @@ namespace SSC { WindowManager *windowManager = nullptr; ExitCallback onExit = nullptr; AtomicBool shouldExit = false; + AtomicBool killed = false; bool fromSSC = false; bool w32ShowConsole = false; Map appData; - Core *core; + Core *core = nullptr; #ifdef _WIN32 App (void *); diff --git a/src/core/child_process.cc b/src/core/child_process.cc index 12689d94b2..52d08ebca9 100644 --- a/src/core/child_process.cc +++ b/src/core/child_process.cc @@ -2,13 +2,12 @@ #if SSC_PLATFORM_DESKTOP namespace SSC { - Core::ChildProcess::~ChildProcess () { + void Core::ChildProcess::shutdown () { Lock lock(this->mutex); for (const auto& entry : this->handles) { auto process = entry.second; process->kill(); process->wait(); - delete process; } this->handles.clear(); diff --git a/src/core/core.cc b/src/core/core.cc index f725c71894..c4e33c5e11 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -31,6 +31,13 @@ namespace SSC { return posts->at(id); } + + void Core::shutdown () { + #if SSC_PLATFORM_DESKTOP + this->childProcess.shutdown(); + #endif + } + bool Core::hasPost (uint64_t id) { Lock lock(postsMutex); return posts->find(id) != posts->end(); diff --git a/src/core/core.hh b/src/core/core.hh index 74d714e607..362c0fc2f3 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -699,8 +699,8 @@ namespace SSC { Mutex mutex; ChildProcess (auto core) : Module(core) {} - ~ChildProcess (); + void shutdown (); void spawn ( const String seq, uint64_t id, @@ -788,16 +788,16 @@ namespace SSC { std::shared_ptr posts; std::map peers; - std::recursive_mutex loopMutex; - std::recursive_mutex peersMutex; - std::recursive_mutex postsMutex; - std::recursive_mutex timersMutex; + Mutex loopMutex; + Mutex peersMutex; + Mutex postsMutex; + Mutex timersMutex; - std::atomic didLoopInit = false; - std::atomic didTimersInit = false; - std::atomic didTimersStart = false; - - std::atomic isLoopRunning = false; + Atomic didLoopInit = false; + Atomic didTimersInit = false; + Atomic didTimersStart = false; + Atomic isLoopRunning = false; + Atomic shuttingDown = false; uv_loop_t eventLoop; uv_async_t eventLoopAsync; @@ -832,6 +832,8 @@ namespace SSC { initEventLoop(); } + void shutdown (); + void resumeAllPeers (); void pauseAllPeers (); bool hasPeer (uint64_t id); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 6160d5a6f0..61e0d6b0f9 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -4630,6 +4630,10 @@ namespace SSC::IPC { size_t size, ResultCallback callback ) { + if (this->core->shuttingDown) { + return false; + } + auto message = Message(uri, true); return this->invoke(message, bytes, size, callback); } @@ -4640,6 +4644,10 @@ namespace SSC::IPC { size_t size, ResultCallback callback ) { + if (this->core->shuttingDown) { + return false; + } + auto name = message.name; MessageCallbackContext ctx; @@ -4731,6 +4739,10 @@ namespace SSC::IPC { const String data, const Post post ) { + if (this->core->shuttingDown) { + return false; + } + if (post.body || seq == "-1") { auto script = this->core->createPost(seq, data, post); return this->evaluateJavaScript(script); @@ -4750,12 +4762,20 @@ namespace SSC::IPC { const String& name, const String data ) { + if (this->core->shuttingDown) { + return false; + } + auto value = encodeURIComponent(data); auto script = getEmitToRenderProcessJavaScript(name, value); return this->evaluateJavaScript(script); } bool Router::evaluateJavaScript (const String js) { + if (this->core->shuttingDown) { + return false; + } + if (this->evaluateJavaScriptFunction != nullptr) { this->evaluateJavaScriptFunction(js); return true; @@ -4765,6 +4785,10 @@ namespace SSC::IPC { } bool Router::dispatch (DispatchCallback callback) { + if (!this->core || this->core->shuttingDown) { + return false; + } + if (this->dispatchFunction != nullptr) { this->dispatchFunction(callback); return true; From c0cebaca6745153e68bb724fa37d4ef2cd74d2f1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 22 Feb 2024 18:05:22 +0100 Subject: [PATCH 0064/1178] chore(api): generate types --- api/index.d.ts | 735 +++++++++++++++++++++++++++---------------------- 1 file changed, 410 insertions(+), 325 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 4bc65c3fbd..f0a22f4fe5 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -6343,7 +6343,7 @@ declare module "socket:child_process" { } export default _default; class ChildProcess extends EventEmitter { - constructor(options: any); + constructor(options?: {}); /** * `true` if the child process was killed with kill()`, * otherwise `false`. @@ -8326,23 +8326,24 @@ declare module "socket:stream-relay/encryption" { */ has(to: Uint8Array | string): boolean; /** - * Decrypts a sealed message for a specific receiver. + * Opens a sealed message using the specified key. * @param {Buffer} message - The sealed message. * @param {Object|string} v - Key object or public key. * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. + * @throws {Error} - Throws ENOKEY if the key is not found. */ - open(message: Buffer, v: any | string): Buffer; + openUnsigned(message: Buffer, v: any | string): Buffer; + sealUnsigned(message: any, v: any): any; /** - * Opens a sealed message using the specified key. + * Decrypts a sealed and signed message for a specific receiver. * @param {Buffer} message - The sealed message. * @param {Object|string} v - Key object or public key. * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found. + * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. */ - openMessage(message: Buffer, v: any | string): Buffer; + open(message: Buffer, v: any | string): Buffer; /** - * Seals a message for a specific receiver using their public key. + * Seals and signs a message for a specific receiver using their public key. * * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on * a plaintext `message` for a `receiver` identity. This prevents repudiation @@ -8740,320 +8741,351 @@ declare module "socket:stream-relay/index" { localPeer: any; write(sharedKey: any, args: any): Promise; } - export function wrap(dgram: any): { - new (persistedState?: object | null): { - port: any; + /** + * `Peer` class factory. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ + export class Peer { + /** + * `Peer` class constructor. Avoid calling this directly (use the create method). + * @private + * @param {object?} [persistedState] + */ + private constructor(); + port: any; + address: any; + natType: number; + nextNatType: number; + clusters: {}; + reflectionId: any; + reflectionTimeout: any; + reflectionStage: number; + reflectionRetry: number; + reflectionFirstResponder: any; + peerId: string; + isListening: boolean; + ctime: number; + lastUpdate: number; + lastSync: number; + closing: boolean; + clock: number; + unpublished: {}; + cache: any; + uptime: number; + maxHops: number; + bdpCache: number[]; + dgram: () => never; + onListening: any; + onDelete: any; + sendQueue: any[]; + firewall: any; + rates: Map; + streamBuffer: Map; + gate: Map; + returnRoutes: Map; + metrics: { + i: { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; + 8: number; + REJECTED: number; + }; + o: { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; + 8: number; + }; + }; + peers: any; + encryption: Encryption; + config: any; + _onError: (err: any) => any; + socket: any; + probeSocket: any; + /** + * An implementation for clearning an interval that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearInterval(tid: any): undefined; + /** + * An implementation for clearning a timeout that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearTimeout(tid: any): undefined; + /** + * An implementation of an internal timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setInterval(fn: any, t: any): number; + /** + * An implementation of an timeout timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setTimeout(fn: any, t: any): number; + /** + * A method that encapsulates the listing procedure + * @return {undefined} + * @ignore + */ + _listen(): undefined; + init(cb: any): Promise; + onReady: any; + mainLoopTimer: number; + /** + * Continuously evaluate the state of the peer and its network + * @return {undefined} + * @ignore + */ + _mainLoop(ts: any): undefined; + /** + * Enqueue packets to be sent to the network + * @param {Buffer} data - An encoded packet + * @param {number} port - The desination port of the remote host + * @param {string} address - The destination address of the remote host + * @param {Socket=this.socket} socket - The socket to send on + * @return {undefined} + * @ignore + */ + send(data: Buffer, port: number, address: string, socket?: any): undefined; + /** + * @private + */ + private _scheduleSend; + sendTimeout: number; + /** + * @private + */ + private _dequeue; + /** + * Send any unpublished packets + * @return {undefined} + * @ignore + */ + sendUnpublished(): undefined; + /** + * Get the serializable state of the peer (can be passed to the constructor or create method) + * @return {undefined} + */ + getState(): undefined; + getInfo(): Promise<{ address: any; - natType: number; - nextNatType: number; - clusters: {}; - reflectionId: any; - reflectionTimeout: any; - reflectionStage: number; - reflectionRetry: number; - reflectionFirstResponder: any; - peerId: string; - isListening: boolean; - ctime: number; - lastUpdate: number; - lastSync: number; - closing: boolean; + port: any; clock: number; - unpublished: {}; - cache: any; uptime: number; - maxHops: number; - bdpCache: number[]; - onListening: any; - onDelete: any; - sendQueue: any[]; - firewall: any; - rates: Map; - streamBuffer: Map; - gate: Map; - returnRoutes: Map; - metrics: { - i: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - REJECTED: number; - }; - o: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - }; - }; - peers: any; - encryption: Encryption; - config: any; - _onError: (err: any) => any; - socket: any; - probeSocket: any; - /** - * An implementation for clearning an interval that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearInterval(tid: any): undefined; - /** - * An implementation for clearning a timeout that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearTimeout(tid: any): undefined; - /** - * An implementation of an internal timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setInterval(fn: any, t: any): number; - /** - * An implementation of an timeout timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setTimeout(fn: any, t: any): number; - /** - * A method that encapsulates the listing procedure - * @return {undefined} - * @ignore - */ - _listen(): undefined; - init(cb: any): Promise; - onReady: any; - mainLoopTimer: number; - /** - * Continuously evaluate the state of the peer and its network - * @return {undefined} - * @ignore - */ - _mainLoop(ts: any): undefined; - /** - * Enqueue packets to be sent to the network - * @param {Buffer} data - An encoded packet - * @param {number} port - The desination port of the remote host - * @param {string} address - The destination address of the remote host - * @param {Socket=this.socket} socket - The socket to send on - * @return {undefined} - * @ignore - */ - send(data: Buffer, port: number, address: string, socket?: any): undefined; - /** - * @private - */ - _scheduleSend(): void; - sendTimeout: number; - /** - * @private - */ - _dequeue(): void; - /** - * Send any unpublished packets - * @return {undefined} - * @ignore - */ - sendUnpublished(): undefined; - /** - * Get the serializable state of the peer (can be passed to the constructor or create method) - * @return {undefined} - */ - getState(): undefined; - /** - * Get a selection of known peers - * @return {Array} - * @ignore - */ - getPeers(packet: any, peers: any, ignorelist: any, filter?: (o: any) => any): Array; - /** - * Send an eventually consistent packet to a selection of peers (fanout) - * @return {undefined} - * @ignore - */ - mcast(packet: any, ignorelist?: any[]): undefined; - /** - * The process of determining this peer's NAT behavior (firewall and dependentness) - * @return {undefined} - * @ignore - */ - requestReflection(): undefined; - probeReflectionTimeout: any; - /** - * Ping another peer - * @return {PacketPing} - * @ignore - */ - ping(peer: any, withRetry: any, props: any, socket: any): PacketPing; - getPeer(id: any): any; - /** - * This should be called at least once when an app starts to multicast - * this peer, and starts querying the network to discover peers. - * @param {object} keys - Created by `Encryption.createKeyPair()`. - * @param {object=} args - Options - * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. - * @return {RemotePeer} - */ - join(sharedKey: any, args?: object | undefined): RemotePeer; - /** - * @param {Packet} T - The constructor to be used to create packets. - * @param {Any} message - The message to be split and packaged. - * @return {Array>} - * @ignore - */ - _message2packets(T: Packet, message: Any, args: any): Array>; - /** - * Sends a packet into the network that will be replicated and buffered. - * Each peer that receives it will buffer it until TTL and then replicate - * it provided it has has not exceeded their maximum number of allowed hops. - * - * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. - * @param {object} args - The arguments to be applied. - * @param {Buffer} args.message - The message to be encrypted by keys and sent. - * @param {Packet=} args.packet - The previous packet in the packet chain. - * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. - * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. - * @return {Array} - */ - publish(sharedKey: any, args: { - message: Buffer; - packet?: Packet | undefined; - usr1: Buffer; - usr2: Buffer; - }): Array; - /** - * @return {undefined} - */ - sync(peer: any): undefined; - close(): void; - /** - * Deploy a query into the network - * @return {undefined} - * - */ - query(query: any): undefined; - /** - * - * This is a default implementation for deciding what to summarize - * from the cache when receiving a request to sync. that can be overridden - * - */ - cachePredicate(packet: any): boolean; - /** - * A connection was made, add the peer to the local list of known - * peers and call the onConnection if it is defined by the user. - * - * @return {undefined} - * @ignore - */ - _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; - connections: Map; - /** - * Received a Sync Packet - * @return {undefined} - * @ignore - */ - _onSync(packet: any, port: any, address: any): undefined; - /** - * Received a Query Packet - * - * a -> b -> c -> (d) -> c -> b -> a - * - * @return {undefined} - * @example - * - * ```js - * peer.onQuery = (packet) => { - * // - * // read a database or something - * // - * return { - * message: Buffer.from('hello'), - * publicKey: '', - * privateKey: '' - * } - * } - * ``` - */ - _onQuery(packet: any, port: any, address: any): undefined; - /** - * Received a Ping Packet - * @return {undefined} - * @ignore - */ - _onPing(packet: any, port: any, address: any): undefined; - /** - * Received a Pong Packet - * @return {undefined} - * @ignore - */ - _onPong(packet: any, port: any, address: any): undefined; - /** - * Received an Intro Packet - * @return {undefined} - * @ignore - */ - _onIntro(packet: any, port: any, address: any, _: any, opts?: { - attempts: number; - }): undefined; - socketPool: any[]; - /** - * Received an Join Packet - * @return {undefined} - * @ignore - */ - _onJoin(packet: any, port: any, address: any, data: any): undefined; - /** - * Received an Publish Packet - * @return {undefined} - * @ignore - */ - _onPublish(packet: any, port: any, address: any, data: any): undefined; - /** - * Received an Stream Packet - * @return {undefined} - * @ignore - */ - _onStream(packet: any, port: any, address: any, data: any): undefined; - /** - * Received any packet on the probe port to determine the firewall: - * are you port restricted, host restricted, or unrestricted. - * @return {undefined} - * @ignore - */ - _onProbeMessage(data: any, { port, address }: { - port: any; - address: any; - }): undefined; - /** - * When a packet is received it is decoded, the packet contains the type - * of the message. Based on the message type it is routed to a function. - * like WebSockets, don't answer queries unless we know its another SRP peer. - * - * @param {Buffer|Uint8Array} data - * @param {{ port: number, address: string }} info - */ - _onMessage(data: Buffer | Uint8Array, { port, address }: { - port: number; - address: string; - }): Promise; - }; - }; - export default wrap; + natType: number; + peerId: string; + }>; + cacheInsert(packet: any): Promise; + addIndexedPeer(info: any): Promise; + reconnect(): Promise; + disconnect(): Promise; + probeReflectionTimeout: any; + sealUnsigned(...args: any[]): Promise; + openUnsigned(...args: any[]): Promise; + seal(...args: any[]): Promise; + open(...args: any[]): Promise; + addEncryptionKey(...args: any[]): Promise; + /** + * Get a selection of known peers + * @return {Array} + * @ignore + */ + getPeers(packet: any, peers: any, ignorelist: any, filter?: (o: any) => any): Array; + /** + * Send an eventually consistent packet to a selection of peers (fanout) + * @return {undefined} + * @ignore + */ + mcast(packet: any, ignorelist?: any[]): undefined; + /** + * The process of determining this peer's NAT behavior (firewall and dependentness) + * @return {undefined} + * @ignore + */ + requestReflection(): undefined; + /** + * Ping another peer + * @return {PacketPing} + * @ignore + */ + ping(peer: any, withRetry: any, props: any, socket: any): PacketPing; + /** + * Get a peer + * @return {RemotePeer} + * @ignore + */ + getPeer(id: any): RemotePeer; + /** + * This should be called at least once when an app starts to multicast + * this peer, and starts querying the network to discover peers. + * @param {object} keys - Created by `Encryption.createKeyPair()`. + * @param {object=} args - Options + * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. + * @return {RemotePeer} + */ + join(sharedKey: any, args?: object | undefined): RemotePeer; + /** + * @param {Packet} T - The constructor to be used to create packets. + * @param {Any} message - The message to be split and packaged. + * @return {Array>} + * @ignore + */ + _message2packets(T: Packet, message: Any, args: any): Array>; + /** + * Sends a packet into the network that will be replicated and buffered. + * Each peer that receives it will buffer it until TTL and then replicate + * it provided it has has not exceeded their maximum number of allowed hops. + * + * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. + * @param {object} args - The arguments to be applied. + * @param {Buffer} args.message - The message to be encrypted by keys and sent. + * @param {Packet=} args.packet - The previous packet in the packet chain. + * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. + * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. + * @return {Array} + */ + publish(sharedKey: any, args: { + message: Buffer; + packet?: Packet | undefined; + usr1: Buffer; + usr2: Buffer; + }): Array; + /** + * @return {undefined} + */ + sync(peer: any): undefined; + close(): void; + /** + * Deploy a query into the network + * @return {undefined} + * + */ + query(query: any): undefined; + /** + * + * This is a default implementation for deciding what to summarize + * from the cache when receiving a request to sync. that can be overridden + * + */ + cachePredicate(packet: any): boolean; + /** + * A connection was made, add the peer to the local list of known + * peers and call the onConnection if it is defined by the user. + * + * @return {undefined} + * @ignore + */ + _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; + connections: Map; + /** + * Received a Sync Packet + * @return {undefined} + * @ignore + */ + _onSync(packet: any, port: any, address: any): undefined; + /** + * Received a Query Packet + * + * a -> b -> c -> (d) -> c -> b -> a + * + * @return {undefined} + * @example + * + * ```js + * peer.onQuery = (packet) => { + * // + * // read a database or something + * // + * return { + * message: Buffer.from('hello'), + * publicKey: '', + * privateKey: '' + * } + * } + * ``` + */ + _onQuery(packet: any, port: any, address: any): undefined; + /** + * Received a Ping Packet + * @return {undefined} + * @ignore + */ + _onPing(packet: any, port: any, address: any): undefined; + /** + * Received a Pong Packet + * @return {undefined} + * @ignore + */ + _onPong(packet: any, port: any, address: any): undefined; + /** + * Received an Intro Packet + * @return {undefined} + * @ignore + */ + _onIntro(packet: any, port: any, address: any, _: any, opts?: { + attempts: number; + }): undefined; + socketPool: any[]; + /** + * Received an Join Packet + * @return {undefined} + * @ignore + */ + _onJoin(packet: any, port: any, address: any, data: any): undefined; + /** + * Received an Publish Packet + * @return {undefined} + * @ignore + */ + _onPublish(packet: any, port: any, address: any, data: any): undefined; + /** + * Received an Stream Packet + * @return {undefined} + * @ignore + */ + _onStream(packet: any, port: any, address: any, data: any): undefined; + /** + * Received any packet on the probe port to determine the firewall: + * are you port restricted, host restricted, or unrestricted. + * @return {undefined} + * @ignore + */ + _onProbeMessage(data: any, { port, address }: { + port: any; + address: any; + }): undefined; + /** + * When a packet is received it is decoded, the packet contains the type + * of the message. Based on the message type it is routed to a function. + * like WebSockets, don't answer queries unless we know its another SRP peer. + * + * @param {Buffer|Uint8Array} data + * @param {{ port: number, address: string }} info + */ + _onMessage(data: Buffer | Uint8Array, { port, address }: { + port: number; + address: string; + }): Promise; + } + export default Peer; import { Packet } from "socket:stream-relay/packets"; import { sha256 } from "socket:stream-relay/packets"; import { Cache } from "socket:stream-relay/cache"; @@ -9064,13 +9096,9 @@ declare module "socket:stream-relay/index" { import { PacketPublish } from "socket:stream-relay/packets"; export { Packet, sha256, Cache, Encryption, NAT }; } -declare module "socket:stream-relay/sugar" { - function _default(dgram: object, events: object): Promise; - export default _default; -} declare module "socket:node/index" { export default network; - export const network: Promise; + export const network: any; import { Cache } from "socket:stream-relay/index"; import { sha256 } from "socket:stream-relay/index"; import { Encryption } from "socket:stream-relay/index"; @@ -10919,9 +10947,66 @@ declare module "socket:module" { import timers from "socket:timers"; import window from "socket:window"; } +declare module "socket:stream-relay/worker" { + /** + * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ + export class PeerWorkerProxy { + constructor(options: any, port: any, fn: any); + init(): Promise; + reconnect(): Promise; + getInfo(): Promise; + getState(): Promise; + open(...args: any[]): Promise; + seal(...args: any[]): Promise; + sealUnsigned(...args: any[]): Promise; + openUnsigned(...args: any[]): Promise; + addEncryptionKey(...args: any[]): Promise; + send(...args: any[]): Promise; + sendUnpublished(...args: any[]): Promise; + cacheInsert(...args: any[]): Promise; + mcast(...args: any[]): Promise; + requestReflection(...args: any[]): Promise; + join(...args: any[]): Promise; + publish(...args: any[]): Promise; + sync(...args: any[]): Promise; + close(...args: any[]): Promise; + query(...args: any[]): Promise; + compileCachePredicate(src: any): Promise; + callWorkerThread(prop: any, data: any): Deferred; + callMainThread(prop: any, args: any): void; + resolveMainThread(seq: any, data: any): void; + #private; + } + class Deferred { + _promise: Promise; + resolve: (value: any) => void; + reject: (reason?: any) => void; + then: any; + catch: any; + finally: any; + [Symbol.toStringTag]: string; + } + export {}; +} +declare module "socket:stream-relay/api" { + export default api; + /** + * Initializes and returns the network bus. + * + * @async + * @function + * @param {object} options - Configuration options for the network bus. + * @param {object} events - A nodejs compatibe implementation of the events module. + * @param {object} dgram - A nodejs compatible implementation of the dgram module. + * @returns {Promise} - A promise that resolves to the initialized network bus. + */ + export function api(options: object, events: object, dgram: object): Promise; +} declare module "socket:network" { export default network; - export const network: Promise; + export function network(options: any): Promise; import { Cache } from "socket:stream-relay/index"; import { sha256 } from "socket:stream-relay/index"; import { Encryption } from "socket:stream-relay/index"; From 77b1e5047b0a624f4a9fec4ebee00950d4067db5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 23 Feb 2024 11:33:19 +0100 Subject: [PATCH 0065/1178] refactor(api/internal/primitives.js): ensure 'ServiceWorker' exists globally --- api/internal/primitives.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index eeb641758a..a95de52660 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -2,6 +2,7 @@ import { fetch, Headers, Request, Response } from '../fetch.js' import { URL, URLPattern, URLSearchParams } from '../url.js' import { setImmediate, clearImmediate } from './timers.js' +import { ServiceWorker } from '../service-worker/instance.js' import serviceWorker from './service-worker.js' import SharedWorker from './shared-worker.js' import Notification from '../notification.js' @@ -199,6 +200,7 @@ export function init () { // workers SharedWorker, + ServiceWorker, // timers setImmediate, From f18520322ab1192fd08e0723e6c1927154f09971 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 23 Feb 2024 11:33:52 +0100 Subject: [PATCH 0066/1178] refactor(api/service-worker/instance.js): 'ServiceWorker' fallback --- api/service-worker/instance.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 5e3e3be46c..a3db06068b 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -3,7 +3,17 @@ import state from './state.js' export const SHARED_WORKER_URL = new URL('./shared-worker.js', import.meta.url) -export const ServiceWorker = globalThis.ServiceWorker +export const ServiceWorker = globalThis.ServiceWorker ?? class ServiceWorker extends EventTarget { + get onmessage () { return null } + set onmessage (_) {} + get onerror () { return null } + set onerror (_) {} + get onstatechange () { return null } + set onstatechange (_) {} + get state () { return null } + get scriptURL () { return null } + postMessage () {} +} export function createServiceWorker ( currentState = state.serviceWorker.state, From 560caeddd7ceab8e3f04915a83dda442723ba62c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 23 Feb 2024 11:34:23 +0100 Subject: [PATCH 0067/1178] refactor(core,ipc): fix ChildProcess platform guards --- src/core/core.hh | 4 ++++ src/ipc/bridge.cc | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/core.hh b/src/core/core.hh index 362c0fc2f3..f4696fa916 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -783,7 +783,9 @@ namespace SSC { OS os; Platform platform; UDP udp; + #if SSC_PLATFORM_DESKTOP ChildProcess childProcess; + #endif std::shared_ptr posts; std::map peers; @@ -825,7 +827,9 @@ namespace SSC { os(this), platform(this), udp(this), + #if SSC_PLATFORM_DESKTOP childProcess(this), + #endif serviceWorker(this) { this->posts = std::shared_ptr(new Posts()); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 61e0d6b0f9..77aa4027ea 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -261,7 +261,7 @@ static void initRouterTable (Router *router) { router->map("child_process.kill", [](auto message, auto router, auto reply) { #if SSC_PLATFORM_MOBILE auto err = JSON::Object::Entries { - {"type", "NotSupportedError"} + {"type", "NotSupportedError"}, {"message", "Operation is not supported on this platform"} }; @@ -297,7 +297,7 @@ static void initRouterTable (Router *router) { router->map("child_process.spawn", [](auto message, auto router, auto reply) { #if SSC_PLATFORM_MOBILE auto err = JSON::Object::Entries { - {"type", "NotSupportedError"} + {"type", "NotSupportedError"}, {"message", "Operation is not supported on this platform"} }; @@ -352,7 +352,7 @@ static void initRouterTable (Router *router) { router->map("child_process.write", [](auto message, auto router, auto reply) { #if SSC_PLATFORM_MOBILE auto err = JSON::Object::Entries { - {"type", "NotSupportedError"} + {"type", "NotSupportedError"}, {"message", "Operation is not supported on this platform"} }; From b455e0a7ee41e225a238366def08b9be3a37f2f3 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 24 Feb 2024 17:05:54 +0100 Subject: [PATCH 0068/1178] refactor(): service worker hybrid mode improvements --- api/hooks.js | 13 +- api/internal/primitives.js | 5 +- api/node/index.js | 1 - api/service-worker/container.js | 177 ++++++++++++++++++++++++--- api/service-worker/index.html | 8 ++ api/service-worker/init.js | 16 ++- api/stream-relay/api.js | 6 +- api/stream-relay/index.js | 5 +- api/stream-relay/worker.js | 3 +- api/util.js | 3 + api/vm.js | 10 +- src/cli/cli.cc | 4 +- src/core/core.hh | 46 +++++-- src/core/ini.cc | 6 + src/core/service_worker_container.cc | 14 ++- src/core/service_worker_container.hh | 2 + src/core/timers.cc | 92 ++++++++++++++ src/desktop/main.cc | 9 +- src/ipc/bridge.cc | 56 +++++++-- src/window/linux.cc | 1 + src/window/win.cc | 1 + test/src/network/index.js | 1 + 22 files changed, 418 insertions(+), 61 deletions(-) create mode 100644 src/core/timers.cc diff --git a/api/hooks.js b/api/hooks.js index 027e3fc9a3..dba8d25149 100644 --- a/api/hooks.js +++ b/api/hooks.js @@ -80,8 +80,11 @@ function addEventListenerOnce (target, type, callback) { target.addEventListener(type, callback, { once: true }) } -async function waitForEvent (target, type) { +async function waitForEvent (target, type, timeout = -1) { return await new Promise((resolve) => { + if (timeout > -1) { + setTimeout(resolve, timeout) + } addEventListenerOnce(target, type, resolve) }) } @@ -345,7 +348,13 @@ export class Hooks extends EventTarget { }) if (!isWorkerContext && readyState !== 'complete') { - await waitForEvent(global, 'load') + const pending = [] + pending.push(waitForEvent(global, 'load', 500)) + if (document) { + pending.push(waitForEvent(document, 'DOMContentLoaded')) + } + + await Promise.race(pending) } isGlobalLoaded = true diff --git a/api/internal/primitives.js b/api/internal/primitives.js index a95de52660..b89d765e2c 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -230,7 +230,10 @@ export function init () { // manually initialize `ServiceWorkerContainer` instance with the // runtime implementations - serviceWorker.init.call(globalThis.navigator.serviceWorker) + if (typeof serviceWorker.init === 'function') { + serviceWorker.init.call(globalThis.navigator.serviceWorker) + delete serviceWorker.init + } // TODO(@jwerle): handle 'popstate' for service workers // globalThis.addEventListener('popstate', (event) => { }) diff --git a/api/node/index.js b/api/node/index.js index 0f6803e3f2..2895b74c65 100644 --- a/api/node/index.js +++ b/api/node/index.js @@ -1,6 +1,5 @@ import sugar from '../stream-relay/sugar.js' import { Cache, Packet, sha256, Encryption, NAT } from '../stream-relay/index.js' -import worker from 'node:worker_threads' import events from 'node:events' import dgram from 'node:dgram' diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 72d7e3e45f..bbd7060bcb 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -4,8 +4,12 @@ import { ServiceWorkerRegistration } from './registration.js' import { InvertedPromise } from '../util.js' import { SharedWorker } from '../internal/shared-worker.js' import application from '../application.js' +import hooks from '../hooks.js' import state from './state.js' import ipc from '../ipc.js' +import os from '../os.js' + +const SERVICE_WINDOW_PATH = `${globalThis.origin}/socket/service-worker/index.html` class ServiceWorkerContainerInternalStateMap extends Map { define (container, property, descriptor) { @@ -23,6 +27,10 @@ class ServiceWorkerContainerInternalState { controller = null channel = new BroadcastChannel('socket.runtime.ServiceWorkerContainer') ready = new InvertedPromise() + init = new InvertedPromise() + + isRegistering = false + isRegistered = false // level 1 events oncontrollerchange = null @@ -31,6 +39,79 @@ class ServiceWorkerContainerInternalState { onerror = null } +class ServiceWorkerContainerRealm { + static instance = null + + frame = null + static async init (container) { + const realm = new ServiceWorkerContainerRealm() + return await realm.init(container) + } + + async init (container) { + if (ServiceWorkerContainerRealm.instance) { + return + } + + ServiceWorkerContainerRealm.instance = this + + const frameId = `__${os.platform()}-service-worker-frame__` + const existingFrame = globalThis.top.document.querySelector( + `iframe[id="${frameId}"]` + ) + + if (existingFrame) { + this.frame = existingFrame + return + } + + this.frame = globalThis.top.document.createElement('iframe') + this.frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') + this.frame.src = SERVICE_WINDOW_PATH + this.frame.id = frameId + + Object.assign(this.frame.style, { + display: 'none', + height: 0, + width: 0 + }) + + const target = ( + globalThis.top.document.head ?? + globalThis.top.document.body ?? + globalThis.top.document + ) + + target.appendChild(this.frame) + + await new Promise((resolve, reject) => { + setTimeout(resolve, 500) + this.frame.addEventListener('load', resolve) + this.frame.onerror = (event) => { + reject(new Error('Failed to load ServiceWorker context window frame', { + cause: event.error ?? event + })) + } + }) + + if ( + globalThis.window && + globalThis.top && + globalThis.window === globalThis.top && + application.getCurrentWindowIndex() === 0 + ) { + console.log('RESET') + // reset serviceWorker state on bridge if we are in a + // realm (hybrid) context setup so register already service worker + // registrations that need to be reinitialized when the page is reloaded + await ipc.request('serviceWorker.reset') + await new Promise((resolve, reject) => { + setTimeout(resolve, 500) + }) + } + } +} + const internal = new ServiceWorkerContainerInternalStateMap() async function preloadExistingRegistration (container) { @@ -71,7 +152,32 @@ async function activateRegistrationFromClaim (container, claim) { await preloadExistingRegistration(container) } +/** + * Predicate to determine if service workers are allowed + * @return {boolean} + */ +export function isServiceWorkerAllowed () { + const { config } = application + + if (globalThis.__RUNTIME_SERVICE_WORKER_CONTEXT__) { + return false + } + + return String(config.permissions_allow_service_worker) !== 'false' +} + +/** + * TODO + */ export class ServiceWorkerContainer extends EventTarget { + get ready () { + return internal.get(this).ready + } + + get controller () { + return internal.get(this).controller + } + /** * A special initialization function for augmenting the global * `globalThis.navigator.serviceWorker` platform `ServiceWorkerContainer` @@ -85,7 +191,13 @@ export class ServiceWorkerContainer extends EventTarget { * @private */ async init () { + if (internal.get(this)) { + internal.get(this).init.resolve() + return + } + internal.set(this, new ServiceWorkerContainerInternalState()) + internal.get(this).currentWindow = await application.getCurrentWindow() this.register = this.register.bind(this) this.getRegistration = this.getRegistration.bind(this) @@ -173,35 +285,46 @@ export class ServiceWorkerContainer extends EventTarget { } }) - preloadExistingRegistration(this) - internal.get(this).ready.then(async (registration) => { if (registration) { internal.get(this).controller = registration.active internal.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) - internal.get(this).currentWindow = await application.getCurrentWindow() } }) - state.channel.addEventListener('message', (event) => { - if (event.data?.clients?.claim?.scope) { - const { scope } = event.data.clients.claim - if (globalThis.location.pathname.startsWith(scope)) { - activateRegistrationFromClaim(this, event.data.clients.claim) + if (isServiceWorkerAllowed()) { + state.channel.addEventListener('message', (event) => { + if (event.data?.clients?.claim?.scope) { + const { scope } = event.data.clients.claim + if (globalThis.location.pathname.startsWith(scope)) { + activateRegistrationFromClaim(this, event.data.clients.claim) + } } + }) + + if ( + application.config.webview_service_worker_mode === 'hybrid' || + /android|ios/i.test(os.platform()) + ) { + console.log('before realm init') + await ServiceWorkerContainerRealm.init(this) + console.log('after realm init') } - }) - } - get ready () { - return internal.get(this).ready - } + console.log('preloading') + await preloadExistingRegistration(this) + console.log('after preloadExistingRegistration') + } - get controller () { - return internal.get(this).controller + console.log('resolve init', globalThis.location.href) + internal.get(this).init.resolve() } async getRegistration (clientURL) { + if (globalThis.top && globalThis.window && globalThis.top !== globalThis.window) { + return await globalThis.top.navigator.serviceWorker.getRegistration(clientURL) + } + let scope = clientURL let currentScope = null @@ -248,6 +371,10 @@ export class ServiceWorkerContainer extends EventTarget { } async getRegistrations () { + if (globalThis.top && globalThis.window && globalThis.top !== globalThis.window) { + return await globalThis.top.navigator.serviceWorker.getRegistrations() + } + const result = await ipc.request('serviceWorker.getRegistrations') if (result.err) { @@ -271,6 +398,14 @@ export class ServiceWorkerContainer extends EventTarget { } async register (scriptURL, options = null) { + if (globalThis.top && globalThis.window && globalThis.top !== globalThis.window) { + return await globalThis.top.navigator.serviceWorker.register(scriptURL, options) + } + + console.log('before init', globalThis.location.href, internal.get(this)) + await internal.get(this).init + console.log('after init') + scriptURL = new URL(scriptURL, globalThis.location.href).toString() if (!options || typeof options !== 'object') { @@ -281,18 +416,26 @@ export class ServiceWorkerContainer extends EventTarget { options.scope = new URL('./', scriptURL).pathname } + internal.get(this).isRegistered = false + internal.get(this).isRegistering = true + const result = await ipc.request('serviceWorker.register', { ...options, scriptURL }) + internal.get(this).isRegistering = false + if (result.err) { throw result.err } + internal.get(this).isRegistered = true + const info = result.data const url = new URL(scriptURL) + console.log({ result }) if (url.pathname.startsWith(options.scope)) { state.serviceWorker.state = info.registration.state.replace('registered', 'installing') state.serviceWorker.scriptURL = info.registration.scriptURL @@ -308,6 +451,10 @@ export class ServiceWorkerContainer extends EventTarget { } startMessages () { + if (globalThis.top && globalThis.window && globalThis.top !== globalThis.window) { + return globalThis.top.navigator.serviceWorker.startMessages() + } + internal.get(this).ready.then(() => { internal.get(this).sharedWorker.port.start() internal.get(this).sharedWorker.port.addEventListener('message', (event) => { diff --git a/api/service-worker/index.html b/api/service-worker/index.html index abebbe1524..09117ea4bb 100644 --- a/api/service-worker/index.html +++ b/api/service-worker/index.html @@ -14,6 +14,14 @@ " > + diff --git a/api/service-worker/init.js b/api/service-worker/init.js index 92244b92b5..98c006da17 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -23,12 +23,15 @@ class ServiceWorkerInfo { } } -globalThis.addEventListener('serviceWorker.register', onRegister) -globalThis.addEventListener('serviceWorker.skipWaiting', onSkipWaiting) -globalThis.addEventListener('serviceWorker.activate', onActivate) -globalThis.addEventListener('serviceWorker.fetch', onFetch) +console.log(globalThis.top === globalThis) +globalThis.top.addEventListener('serviceWorker.register', onRegister) +globalThis.top.addEventListener('serviceWorker.skipWaiting', onSkipWaiting) +globalThis.top.addEventListener('serviceWorker.activate', onActivate) +globalThis.top.addEventListener('serviceWorker.fetch', onFetch) +console.log('init service worker frame') async function onRegister (event) { + console.log('onRegister', event) const info = new ServiceWorkerInfo(event.detail) if (!info.id || workers.has(info.hash)) { @@ -51,10 +54,12 @@ async function onRegister (event) { } async function onSkipWaiting (event) { + console.log('onSkipWaiting', event) onActivate(event) } async function onActivate (event) { + console.log('onActivate', event) const info = new ServiceWorkerInfo(event.detail) if (!workers.has(info.hash)) { @@ -67,6 +72,7 @@ async function onActivate (event) { } async function onFetch (event) { + console.log('onFetch', event) const info = new ServiceWorkerInfo(event.detail) if (!workers.has(info.hash)) { @@ -78,7 +84,7 @@ async function onFetch (event) { id: event.detail.fetch.id, url: new URL( event.detail.fetch.pathname + '?' + event.detail.fetch.query, - globalThis.origin + globalThis.top.origin ).toString(), headers: event.detail.fetch.headers diff --git a/api/stream-relay/api.js b/api/stream-relay/api.js index cc2c8dd061..32dc5d87a4 100644 --- a/api/stream-relay/api.js +++ b/api/stream-relay/api.js @@ -1,4 +1,4 @@ -import { Peer, Encryption, sha256, NAT, RemotePeer } from './index.js' +import { Peer, Encryption, sha256 } from './index.js' import { PeerWorkerProxy } from './worker.js' import { sodium } from '../crypto.js' import { Buffer } from '../buffer.js' @@ -16,7 +16,6 @@ import { Packet, CACHE_TTL } from './packets.js' * @returns {Promise} - A promise that resolves to the initialized network bus. */ async function api (options = {}, events, dgram) { - let _peer await sodium.ready const bus = new events.EventEmitter() bus._on = bus.on @@ -38,7 +37,7 @@ async function api (options = {}, events, dgram) { if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters const Ctor = globalThis.isSocketRuntime ? PeerWorkerProxy : Peer - _peer = new Ctor(options, dgram) + const _peer = new Ctor(options, dgram) _peer.onJoin = (packet, ...args) => { if (!packet.clusterId.equals(clusterId)) return @@ -198,7 +197,6 @@ async function api (options = {}, events, dgram) { const args = await pack(eventName, value, opts) if (!options.sharedKey) { throw new Error('Can\'t emit to the top level cluster, a shared key was not provided in the constructor or the arguments options') - return } return await _peer.publish(options.sharedKey || opts.sharedKey, args) } diff --git a/api/stream-relay/index.js b/api/stream-relay/index.js index f594c341e1..14be03ac53 100644 --- a/api/stream-relay/index.js +++ b/api/stream-relay/index.js @@ -4,7 +4,6 @@ * * This module provides primitives for creating a p2p network. */ -// import { isBufferLike } from '../util.js' import { Buffer } from '../buffer.js' import { sodium, randomBytes } from '../crypto.js' @@ -579,7 +578,7 @@ export class Peer { }) return { - peers, + peers, config: this.config, data: [...this.cache.data.entries()], unpublished: this.unpublished @@ -1043,7 +1042,7 @@ export class Peer { for (const packet of packets) { this.cacheInsert(packet) - if (this.onPacket) this.onPacket(p, this.port, this.address, true) + if (this.onPacket) this.onPacket(packet, this.port, this.address, true) this.unpublished[packet.packetId.toString('hex')] = Date.now() if (globalThis.navigator && !globalThis.navigator.onLine) continue diff --git a/api/stream-relay/worker.js b/api/stream-relay/worker.js index 865d777f4c..9a9bb5fb51 100644 --- a/api/stream-relay/worker.js +++ b/api/stream-relay/worker.js @@ -167,7 +167,7 @@ export class PeerWorkerProxy { return await this.callWorkerThread('reconnect') } - async reconnect () { + async disconnect () { return await this.callWorkerThread('disconnect') } @@ -335,6 +335,7 @@ if (isWorkerThread) { } case 'compileCachePredicate': { + // eslint-disable-next-line let predicate = new Function(`return ${data.toString()}`)() predicate = predicate.bind(peer) peer.cachePredicate = packet => predicate(packet) diff --git a/api/util.js b/api/util.js index 49cc7b859e..90578425e4 100644 --- a/api/util.js +++ b/api/util.js @@ -578,6 +578,9 @@ export function inspect (value, options) { for (let i = 0; i < size; ++i) { const key = String(i) if (value instanceof Set || hasOwnProperty(value, key)) { + if (key === 'length' && Array.isArray(value)) { + continue + } output.push(formatProperty( ctx, value, diff --git a/api/vm.js b/api/vm.js index a062bb914c..213c68c9a6 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1175,7 +1175,7 @@ export async function getContextWindow () { if (!contextWindow.frame) { const frameId = `__${os.platform()}-vm-frame__` - const existingFrame = globalThis.document.querySelector( + const existingFrame = globalThis.top.document.querySelector( `iframe[id="${frameId}"]` ) @@ -1183,7 +1183,7 @@ export async function getContextWindow () { existingFrame.parentElement.removeChild(existingFrame) } - const frame = globalThis.document.createElement('iframe') + const frame = globalThis.top.document.createElement('iframe') frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') frame.src = VM_WINDOW_PATH @@ -1196,9 +1196,9 @@ export async function getContextWindow () { }) const target = ( - globalThis.document.head ?? - globalThis.document.body ?? - globalThis.document + globalThis.top.document.head ?? + globalThis.top.document.body ?? + globalThis.top.document ) target.appendChild(frame) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index e7fce7e085..7d8642b452 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2286,12 +2286,12 @@ int main (const int argc, const char* argv[]) { fs::copy(trim(prefixFile("assets/icon.png")), targetPath / "src" / "icon.png", fs::copy_options::overwrite_existing); log("icon.png created in " + targetPath.string() + "/src"); - if (platform.win) { + if (platform.win) { // copy icon.ico fs::copy(trim(prefixFile("assets/icon.ico")), targetPath / "src" / "icon.ico", fs::copy_options::overwrite_existing); log("icon.ico created in " + targetPath.string() + "/src"); - } + } } else { log("Current directory was not empty. Assuming index.html and icon are already in place."); } diff --git a/src/core/core.hh b/src/core/core.hh index f4696fa916..c1204f6ba0 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -726,6 +726,36 @@ namespace SSC { }; #endif + class Timers : public Module { + public: + using ID = uint64_t; + using Callback = std::function; + + struct Timeout { + ID id = 0; + Callback callback; + bool repeat = false; + bool cancelled = false; + uv_timer_t timer; + }; + + struct Interval : public Timeout { + bool repeat = true; + }; + + using Handles = std::map; + + Handles handles; + Mutex mutex; + + Timers (auto core): Module (core) {} + + const ID setTimeout (uint64_t timeout, const Callback callback); + bool clearTimeout (const ID id); + const ID setInterval (uint64_t interval, const Callback callback); + bool clearInterval (const ID id); + }; + class UDP : public Module { public: UDP (auto core) : Module(core) {} @@ -776,16 +806,17 @@ namespace SSC { ); }; - ServiceWorkerContainer serviceWorker; + #if SSC_PLATFORM_DESKTOP + ChildProcess childProcess; + #endif Diagnostics diagnostics; DNS dns; FS fs; OS os; Platform platform; + ServiceWorkerContainer serviceWorker; + Timers timers; UDP udp; - #if SSC_PLATFORM_DESKTOP - ChildProcess childProcess; - #endif std::shared_ptr posts; std::map peers; @@ -821,15 +852,16 @@ namespace SSC { #endif Core () : + #if SSC_PLATFORM_DESKTOP + childProcess(this), + #endif diagnostics(this), dns(this), fs(this), os(this), platform(this), + timers(this), udp(this), - #if SSC_PLATFORM_DESKTOP - childProcess(this), - #endif serviceWorker(this) { this->posts = std::shared_ptr(new Posts()); diff --git a/src/core/ini.cc b/src/core/ini.cc index 404ffc434e..e8ac5e82e0 100644 --- a/src/core/ini.cc +++ b/src/core/ini.cc @@ -48,6 +48,12 @@ namespace SSC::INI { quoted_value = true; value = trim(value.substr(1, closing_quote_index - 1)); } + } else if (value[0] == '\'') { + closing_quote_index = value.find_first_of('\'', 1); + if (closing_quote_index != std::string::npos) { + quoted_value = true; + value = trim(value.substr(1, closing_quote_index - 1)); + } } if (!quoted_value) { diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 3134cb771f..a8bc79af22 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -43,11 +43,22 @@ namespace SSC { this->core = nullptr; this->bridge = nullptr; this->registrations.clear(); + this->fetchRequests.clear(); + this->fetchCallbacks.clear(); + } + + void ServiceWorkerContainer::reset () { + Lock lock(this->mutex); + + this->registrations.clear(); + this->fetchRequests.clear(); + this->fetchCallbacks.clear(); } void ServiceWorkerContainer::init (IPC::Bridge* bridge) { Lock lock(this->mutex); + this->reset(); this->bridge = bridge; this->bridge->router.map("serviceWorker.fetch.request.body", [this](auto message, auto router, auto reply) mutable { uint64_t id = 0; @@ -156,7 +167,7 @@ namespace SSC { } this->registrations.insert_or_assign(options.scope, Registration { - rand64(), + options.id > 0 ? options.id : rand64(), options.scriptURL, Registration::State::Registered, options @@ -165,6 +176,7 @@ namespace SSC { const auto& registration = this->registrations.at(options.scope); if (this->bridge != nullptr) { + debug("register: %s", registration.json().str().c_str()); this->bridge->router.emit("serviceWorker.register", registration.json().str()); } diff --git a/src/core/service_worker_container.hh b/src/core/service_worker_container.hh index 9d19ea0801..29e9001849 100644 --- a/src/core/service_worker_container.hh +++ b/src/core/service_worker_container.hh @@ -18,6 +18,7 @@ namespace SSC { Type type = Type::Module; String scope; String scriptURL; + uint64_t id = 0; }; struct Client { @@ -80,6 +81,7 @@ namespace SSC { ~ServiceWorkerContainer (); void init (IPC::Bridge* bridge); + void reset (); const Registration& registerServiceWorker (const RegistrationOptions& options); bool unregisterServiceWorker (uint64_t id); bool unregisterServiceWorker (String scopeOrScriptURL); diff --git a/src/core/timers.cc b/src/core/timers.cc new file mode 100644 index 0000000000..dfd2cf413b --- /dev/null +++ b/src/core/timers.cc @@ -0,0 +1,92 @@ +#include "core.hh" + +namespace SSC { + const Core::Timers::ID Core::Timers::setTimeout (uint64_t timeout, const Callback callback) { + Lock lock(this->mutex); + + auto id = rand64(); + auto handle = Timeout { rand64(), callback }; + auto loop = this->core->getEventLoop(); + + this->handles.insert_or_assign(handle.id, Timeout { + id, + callback + }); + + this->core->dispatchEventLoop([=, this]() { + Lock lock(this->mutex); + if (this->handles.contains(id)) { + auto& handle = this->handles.at(id); + uv_handle_set_data((uv_handle_t*) &handle.timer, &handle); + uv_timer_init(loop, &handle.timer); + uv_timer_start( + &handle.timer, + [](uv_timer_t* timer) { + auto handle = reinterpret_cast(uv_handle_get_data((uv_handle_t*) timer)); + if (handle != nullptr) { + } + }, + timeout, + 0 + ); + } + }); + + return id; + } + + bool Core::Timers::clearTimeout (const ID id) { + Lock lock(this->mutex); + + if (!this->handles.contains(id)) { + return false; + } + + auto& handle = this->handles.at(id); + handle.cancelled = true; + uv_timer_stop(&handle.timer); + this->handles.erase(id); + + return true; + } + + const Core::Timers::ID Core::Timers::setInterval (uint64_t interval, const Callback callback) { + Lock lock(this->mutex); + + auto id = rand64(); + auto handle = Timeout { rand64(), callback }; + auto loop = this->core->getEventLoop(); + + this->handles.insert_or_assign(handle.id, Timeout { + id, + callback, + true + }); + + this->core->dispatchEventLoop([=, this]() { + Lock lock(this->mutex); + if (this->handles.contains(id)) { + auto& handle = this->handles.at(id); + uv_handle_set_data((uv_handle_t*) &handle.timer, &handle); + uv_timer_init(loop, &handle.timer); + uv_timer_start( + &handle.timer, + [](uv_timer_t* timer) { + auto handle = reinterpret_cast(uv_handle_get_data((uv_handle_t*) timer)); + if (handle != nullptr) { + handle->callback(); + } + }, + interval, + interval + ); + } + }); + + return id; + } + + bool Core::Timers::clearInterval (const ID id) { + return this->clearTimeout(id); + } +} diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 547a0ede7d..1500b90998 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1469,11 +1469,14 @@ MAIN { defaultWindow->show(EMPTY_SEQ); - if (app.appData["permissions_allow_service_worker"] != "false") { + if ( + app.appData["webview_service_worker_mode"] != "hybrid" && + app.appData["permissions_allow_service_worker"] != "false" + ) { auto serviceWorkerWindow = windowManager.createWindow({ .canExit = false, .index = SSC_SERVICE_WORKER_CONTAINER_WINDOW_INDEX, - // .headless = true, + .headless = Env::get("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG").size() == 0, }); app.core->serviceWorker.init(serviceWorkerWindow->bridge); @@ -1482,6 +1485,8 @@ MAIN { EMPTY_SEQ, "socket://" + app.appData["meta_bundle_identifier"] + "/socket/service-worker/index.html" ); + } else if (userConfig["webview_service_worker_mode"] == "hybrid") { + app.core->serviceWorker.init(defaultWindow->bridge); } if (_port > 0) { diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 77aa4027ea..9cfb4349f8 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2300,6 +2300,7 @@ static void initRouterTable (Router *router) { /** * Prints incoming message value to stdout. + * @param value */ router->map("stdout", [](auto message, auto router, auto reply) { #if defined(__APPLE__) @@ -2311,6 +2312,7 @@ static void initRouterTable (Router *router) { /** * Prints incoming message value to stderr. + * @param value */ router->map("stderr", [](auto message, auto router, auto reply) { #if defined(__APPLE__) @@ -2322,12 +2324,8 @@ static void initRouterTable (Router *router) { /** * TODO - */ - router->map("serviceWorker.clients.openWindow", [](auto message, auto router, auto reply) { - }); - - /** - * TODO + * @param scriptURL + * @param scope */ router->map("serviceWorker.register", [](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"scriptURL", "scope"}); @@ -2355,6 +2353,15 @@ static void initRouterTable (Router *router) { /** * TODO */ + router->map("serviceWorker.reset", [](auto message, auto router, auto reply) { + router->core->serviceWorker.reset(); + reply(Result::Data { message, JSON::Object {}}); + }); + + /** + * TODO + * @param scope + */ router->map("serviceWorker.unregister", [](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"scope"}); @@ -2370,6 +2377,7 @@ static void initRouterTable (Router *router) { /** * TODO + * @param scope */ router->map("serviceWorker.getRegistration", [](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"scope"}); @@ -2413,6 +2421,7 @@ static void initRouterTable (Router *router) { /** * TODO + * @param id */ router->map("serviceWorker.skipWaiting", [](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"id"}); @@ -2431,12 +2440,8 @@ static void initRouterTable (Router *router) { /** * TODO - */ - router->map("serviceWorker.startMessages", [](auto message, auto router, auto reply) { - }); - - /** - * TODO + * @param id + * @param state */ router->map("serviceWorker.updateState", [](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"id", "state"}); @@ -3326,7 +3331,34 @@ static void registerSchemeHandler (Router *router) { headers[key] = value; } + const NSString* contentType = headers[@"content-type"]; + const auto absoluteURL = String(request.URL.absoluteString.UTF8String); + const auto absoluteURLPathExtension = request.URL.pathExtension != nullptr + ? String(request.URL.pathExtension.UTF8String) + : String(""); + auto data = [NSData dataWithBytes: res.buffer.bytes length: res.buffer.size]; + + if ( + absoluteURLPathExtension.ends_with("html") || + (contentType != nullptr && String(contentType.UTF8String) == "text/html") + ) { + const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; + auto html = String(string.UTF8String); + const auto script = self.router->bridge->preload; + + if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else { + html = script + html; + } + + data = [@(html.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; + } auto response = [[NSHTTPURLResponse alloc] initWithURL: request.URL statusCode: res.statusCode diff --git a/src/window/linux.cc b/src/window/linux.cc index 21b8fd43e5..7418e930fd 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -19,6 +19,7 @@ namespace SSC { opts(opts), hotkey(this) { + static auto userConfig = SSC::getUserConfig(); setenv("GTK_OVERLAY_SCROLLING", "1", 1); this->accelGroup = gtk_accel_group_new(); this->popupId = 0; diff --git a/src/window/win.cc b/src/window/win.cc index 735af03a45..7948951f9a 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -664,6 +664,7 @@ namespace SSC { this->bridge = new IPC::Bridge(app.core); this->hotkey.init(this->bridge); + this->bridge->router.dispatchFunction = [&app] (auto callback) { app.dispatch([callback] { callback(); }); }; diff --git a/test/src/network/index.js b/test/src/network/index.js index 98b193073a..0001108404 100644 --- a/test/src/network/index.js +++ b/test/src/network/index.js @@ -3,6 +3,7 @@ import { isIPv4 } from 'socket:ip' import { network, Encryption } from 'socket:network' test('basic network constructor', async t => { + // eslint-disable-next-line const sharedKey = await Encryption.createSharedKey('TEST') const clusterId = await Encryption.createClusterId('TEST') const peerId = await Encryption.createId() From f34609d3b0186a0d832076793abb62816b1fe79b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 24 Feb 2024 17:08:35 +0100 Subject: [PATCH 0069/1178] fix(src/core/timers.cc): fix typo --- src/core/timers.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/timers.cc b/src/core/timers.cc index dfd2cf413b..1934442243 100644 --- a/src/core/timers.cc +++ b/src/core/timers.cc @@ -24,6 +24,7 @@ namespace SSC { [](uv_timer_t* timer) { auto handle = reinterpret_cast(uv_handle_get_data((uv_handle_t*) timer)); if (handle != nullptr) { + handle->callback(); } }, timeout, From f22345c9a69a275a096d717b3b9c64b82287557b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:08:08 +0100 Subject: [PATCH 0070/1178] fix(api/application.js): improve 'socket:application' usage from workers and android --- api/application.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/application.js b/api/application.js index 7449912f49..8af3c5272b 100644 --- a/api/application.js +++ b/api/application.js @@ -116,10 +116,10 @@ export async function createWindow (opts) { * @returns {Promise<{ width: number, height: number }>} */ export async function getScreenSize () { - if (os.platform() === 'ios') { + if (os.platform() === 'ios' || os.platform() === 'android') { return { - width: globalThis.screen.availWidth, - height: globalThis.screen.availHeight + width: globalThis.screen?.availWidth ?? 0, + height: globalThis.screen?.availHeight ?? 0 } } const { data, err } = await ipc.send('application.getScreenSize', { index: globalThis.__args.index }) @@ -146,8 +146,8 @@ export async function getWindows (indices, options = null) { return { 0: new ApplicationWindow({ index: 0, - width: globalThis.screen.availWidth, - height: globalThis.screen.availHeight, + width: globalThis.screen?.availWidth ?? 0, + height: globalThis.screen?.availHeight ?? 0, title: document.title, status: 31 }) From 673175bf56f55ca91e2ca6995706c35c142407be Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:08:19 +0100 Subject: [PATCH 0071/1178] refactor(api/console.js): improve 'console.debug' --- api/console.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/api/console.js b/api/console.js index c29a0a5ceb..a1fbc88bfb 100644 --- a/api/console.js +++ b/api/console.js @@ -135,8 +135,21 @@ export class Console { } async write (destination, ...args) { - const value = encodeURIComponent(format(...args)) - const uri = `ipc://${destination}?value=${value}` + let extra = '' + let value = '' + + value = format(...args) + + if (destination === 'debug') { + destination = 'stderr' + extra = 'debug=true' + value = `(${globalThis.location.pathname}): ${value}` + } + + value = encodeURIComponent(value) + + const uri = `ipc://${destination}?value=${value}&${extra}` + try { return await this.postMessage?.(uri) } catch (err) { @@ -175,7 +188,7 @@ export class Console { debug (...args) { this.console?.debug?.(...args) if (!isPatched(this.console)) { - this.write('stderr', ...args) + this.write('debug', ...args) } } From db0f4ec62db95b078cfb97d0ffdc2675ea889172 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:08:27 +0100 Subject: [PATCH 0072/1178] chore(api/fs/index.js): clean up --- api/fs/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/fs/index.js b/api/fs/index.js index afd81073be..aef1c5de20 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -893,7 +893,6 @@ export function readlink (path, callback) { } ipc.request('fs.readlink', { path }).then((result) => { - console.log({ path, readLink: result }) result?.err ? callback(result.err) : callback(null, result.data.path) }).catch(callback) } From 2b7d7e2277c143affdf7c0b5dfeecb3e3ecabf40 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:08:35 +0100 Subject: [PATCH 0073/1178] chore(api/http.js): clean up --- api/http.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/http.js b/api/http.js index fdecc5d75a..ad44ed5a6a 100644 --- a/api/http.js +++ b/api/http.js @@ -220,7 +220,6 @@ export class Agent extends EventEmitter { const stream = new Duplex({ signal: abortController.signal, write (data, cb) { - console.log('write') controller.enqueue(data) cb(null) }, From 68e91d554039e1be6abd34ff4a647b4d2f91b443 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:08:47 +0100 Subject: [PATCH 0074/1178] refactor(api/internal/init.js): emit 'beforeruntimeinit' event --- api/internal/init.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/internal/init.js b/api/internal/init.js index 35d44e0617..766623c156 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -12,6 +12,9 @@ console.assert( 'This could lead to undefined behavior.' ) +import ipc from '../ipc.js' +ipc.send('platform.event', 'beforeruntimeinit').catch(reportError) + import './primitives.js' import { IllegalConstructor, InvertedPromise } from '../util.js' @@ -21,7 +24,6 @@ import location from '../location.js' import { URL } from '../url.js' import mime from '../mime.js' import path from '../path.js' -import ipc from '../ipc.js' import fs from '../fs/promises.js' import { createFileSystemDirectoryHandle, From 79fc39f81f6e40635557c7cc6b9a3734b6fb248a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:09:03 +0100 Subject: [PATCH 0075/1178] refactor(api/ipc.js): include 'runtime-frame-source' if given --- api/ipc.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/ipc.js b/api/ipc.js index ccc0b36f6b..52d795e99d 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1041,6 +1041,27 @@ class IPCSearchParams extends URLSearchParams { if (globalThis.RUNTIME_WORKER_ID) { this.set('runtime-worker-id', globalThis.RUNTIME_WORKER_ID) } + + const runtimeFrameSource = globalThis.document + ? globalThis.document.querySelector('meta[name=runtime-frame-source]')?.content + : '' + + if (globalThis.top && globalThis.top !== globalThis) { + this.set('runtime-frame-type', 'nested') + } else if (!globalThis.window && globalThis.self === globalThis) { + this.set('runtime-frame-type', 'worker') + if (globalThis.clients && globalThis.FetchEvent) { + this.set('runtime-worker-type', 'serviceworker') + } else { + this.set('runtime-worker-type', 'worker') + } + } else { + this.set('runtime-frame-type', 'top-level') + } + + if (runtimeFrameSource) { + this.set('runtime-frame-source', runtimeFrameSource) + } } toString () { From fa978e43c6d625bfa486925bb6903aa2ac158713 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:09:18 +0100 Subject: [PATCH 0076/1178] refactor(api/module.js): improve error bubbling --- api/module.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api/module.js b/api/module.js index 16bc6109c7..2ded57f8a7 100644 --- a/api/module.js +++ b/api/module.js @@ -518,17 +518,15 @@ export class Module extends EventTarget { } this.addEventListener('error', (event) => { - // @ts-ignore - const { error } = event if (this.isMain) { // bubble error to globalThis, if possible if (typeof globalThis.dispatchEvent === 'function') { // @ts-ignore - globalThis.dispatchEvent(new ErrorEvent('error', { error })) + globalThis.dispatchEvent(new ErrorEvent('error', event)) } } else { // bubble errors to main module - Module.main.dispatchEvent(new ErrorEvent('error', { error })) + Module.main.dispatchEvent(new ErrorEvent('error', event)) } }) } From 0a5121a3e49ec170e17b81b6028181db99a6a80a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 26 Feb 2024 18:10:00 +0100 Subject: [PATCH 0077/1178] refactor(api/service-worker): improve lifecycle and hybrid state --- api/service-worker/clients.js | 1 - api/service-worker/container.js | 109 ++++++++++++++++++-------------- api/service-worker/events.js | 6 ++ api/service-worker/index.html | 2 +- api/service-worker/init.js | 45 ++++++++----- api/service-worker/worker.js | 2 +- 6 files changed, 98 insertions(+), 67 deletions(-) diff --git a/api/service-worker/clients.js b/api/service-worker/clients.js index 84e151809e..fba3e7dd59 100644 --- a/api/service-worker/clients.js +++ b/api/service-worker/clients.js @@ -2,7 +2,6 @@ import { SHARED_WORKER_URL } from './instance.js' import { SharedWorker } from '../internal/shared-worker.js' import application from '../application.js' import state from './state.js' -// import ipc from '../ipc.js' const MAX_WINDOWS = 32 const CLIENT_GET_TIMEOUT = 100 diff --git a/api/service-worker/container.js b/api/service-worker/container.js index bbd7060bcb..e1e2172e77 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -4,7 +4,6 @@ import { ServiceWorkerRegistration } from './registration.js' import { InvertedPromise } from '../util.js' import { SharedWorker } from '../internal/shared-worker.js' import application from '../application.js' -import hooks from '../hooks.js' import state from './state.js' import ipc from '../ipc.js' import os from '../os.js' @@ -55,6 +54,10 @@ class ServiceWorkerContainerRealm { ServiceWorkerContainerRealm.instance = this + if (!globalThis.top || !globalThis.top.document) { + return + } + const frameId = `__${os.platform()}-service-worker-frame__` const existingFrame = globalThis.top.document.querySelector( `iframe[id="${frameId}"]` @@ -65,7 +68,19 @@ class ServiceWorkerContainerRealm { return } + const pending = [] + this.frame = globalThis.top.document.createElement('iframe') + + pending.push(new Promise((resolve) => { + globalThis.top.addEventListener('message', function onMessage (event) { + if (event.data.__service_worker_frame_init === true) { + globalThis.top.removeEventListener('message', onMessage) + resolve() + } + }) + })) + this.frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') this.frame.src = SERVICE_WINDOW_PATH this.frame.id = frameId @@ -84,31 +99,7 @@ class ServiceWorkerContainerRealm { target.appendChild(this.frame) - await new Promise((resolve, reject) => { - setTimeout(resolve, 500) - this.frame.addEventListener('load', resolve) - this.frame.onerror = (event) => { - reject(new Error('Failed to load ServiceWorker context window frame', { - cause: event.error ?? event - })) - } - }) - - if ( - globalThis.window && - globalThis.top && - globalThis.window === globalThis.top && - application.getCurrentWindowIndex() === 0 - ) { - console.log('RESET') - // reset serviceWorker state on bridge if we are in a - // realm (hybrid) context setup so register already service worker - // registrations that need to be reinitialized when the page is reloaded - await ipc.request('serviceWorker.reset') - await new Promise((resolve, reject) => { - setTimeout(resolve, 500) - }) - } + await Promise.all(pending) } } @@ -118,13 +109,25 @@ async function preloadExistingRegistration (container) { const registration = await container.getRegistration() if (registration) { if (registration.active) { - queueMicrotask(() => { - container.dispatchEvent(new Event('controllerchange')) - }) + if ( + application.config.webview_service_worker_mode === 'hybrid' || + /android|ios/i.test(os.platform()) + ) { + if ( + !internal.get(container).isRegistered && + !internal.get(container).isRegistering + ) { + container.register(registration.active.scriptURL, { scope: registration.scope }) + } + } else { + queueMicrotask(() => { + container.dispatchEvent(new Event('controllerchange')) + }) - queueMicrotask(() => { - internal.get(container).ready.resolve(registration) - }) + queueMicrotask(() => { + internal.get(container).ready.resolve(registration) + }) + } } else { const serviceWorker = registration.installing || registration.waiting serviceWorker.addEventListener('statechange', function onStateChange (event) { @@ -159,7 +162,10 @@ async function activateRegistrationFromClaim (container, claim) { export function isServiceWorkerAllowed () { const { config } = application - if (globalThis.__RUNTIME_SERVICE_WORKER_CONTEXT__) { + if ( + globalThis.__RUNTIME_SERVICE_WORKER_CONTEXT__ || + globalThis.location.pathname === '/socket/service-worker/index.html' + ) { return false } @@ -167,7 +173,8 @@ export function isServiceWorkerAllowed () { } /** - * TODO + * A `ServiceWorkerContainer` implementation that is attached to the global + * `globalThis.navigator.serviceWorker` object. */ export class ServiceWorkerContainer extends EventTarget { get ready () { @@ -192,12 +199,10 @@ export class ServiceWorkerContainer extends EventTarget { */ async init () { if (internal.get(this)) { - internal.get(this).init.resolve() - return + return internal.get(this).init } internal.set(this, new ServiceWorkerContainerInternalState()) - internal.get(this).currentWindow = await application.getCurrentWindow() this.register = this.register.bind(this) this.getRegistration = this.getRegistration.bind(this) @@ -289,6 +294,7 @@ export class ServiceWorkerContainer extends EventTarget { if (registration) { internal.get(this).controller = registration.active internal.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) + internal.get(this).currentWindow = await application.getCurrentWindow() } }) @@ -306,17 +312,12 @@ export class ServiceWorkerContainer extends EventTarget { application.config.webview_service_worker_mode === 'hybrid' || /android|ios/i.test(os.platform()) ) { - console.log('before realm init') await ServiceWorkerContainerRealm.init(this) - console.log('after realm init') } - console.log('preloading') await preloadExistingRegistration(this) - console.log('after preloadExistingRegistration') } - console.log('resolve init', globalThis.location.href) internal.get(this).init.resolve() } @@ -398,14 +399,12 @@ export class ServiceWorkerContainer extends EventTarget { } async register (scriptURL, options = null) { + await internal.get(this).init + if (globalThis.top && globalThis.window && globalThis.top !== globalThis.window) { return await globalThis.top.navigator.serviceWorker.register(scriptURL, options) } - console.log('before init', globalThis.location.href, internal.get(this)) - await internal.get(this).init - console.log('after init') - scriptURL = new URL(scriptURL, globalThis.location.href).toString() if (!options || typeof options !== 'object') { @@ -434,8 +433,8 @@ export class ServiceWorkerContainer extends EventTarget { const info = result.data const url = new URL(scriptURL) + const container = this - console.log({ result }) if (url.pathname.startsWith(options.scope)) { state.serviceWorker.state = info.registration.state.replace('registered', 'installing') state.serviceWorker.scriptURL = info.registration.scriptURL @@ -445,6 +444,22 @@ export class ServiceWorkerContainer extends EventTarget { }) const registration = new ServiceWorkerRegistration(info, serviceWorker) + serviceWorker.addEventListener('statechange', function onStateChange (event) { + if ( + serviceWorker.state === 'activating' || + serviceWorker.state === 'activated' + ) { + serviceWorker.removeEventListener('statechange', onStateChange) + + queueMicrotask(() => { + container.dispatchEvent(new Event('controllerchange')) + }) + + queueMicrotask(() => { + internal.get(container).ready.resolve(registration) + }) + } + }) return registration } diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 0e5b479cb1..fd590266ff 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -106,6 +106,12 @@ export class FetchEvent extends ExtendableEvent { const statusCode = response.status ?? 200 const headers = Array.from(response.headers.entries()) .map((entry) => entry.join(':')) + .concat('Runtime-Response-Source:serviceworker') + .concat('Access-Control-Allow-Credentials:true') + .concat('Access-Control-Allow-Origin:*') + .concat('Access-Control-Allow-Methods:*') + .concat('Access-Control-Allow-Headers:*') + .concat(`Content-Length:${arrayBuffer.byteLength}`) .join('\n') const options = { statusCode, clientId, headers, id } diff --git a/api/service-worker/index.html b/api/service-worker/index.html index 09117ea4bb..9356d0553f 100644 --- a/api/service-worker/index.html +++ b/api/service-worker/index.html @@ -13,7 +13,7 @@ object-src 'none'; " > - + Socket Runtime Service Worker Context \n") + + script + ); + } + } + auto html = String(string.UTF8String); if (html.find("") != String::npos) { @@ -3467,9 +3535,6 @@ static void registerSchemeHandler (Router *router) { components.host = request.URL.host; } else { isModule = true; - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - } auto prefix = String( path.starts_with(bundleIdentifier) @@ -3479,6 +3544,15 @@ static void registerSchemeHandler (Router *router) { path = replace(path, bundleIdentifier + "/", ""); + if (scheme == "node") { + const auto specifier = replace(path, ".js", ""); + // TODO + } + + if (ext.size() == 0 && !path.ends_with(".js")) { + path += ".js"; + } + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR components.path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: [NSString @@ -3548,24 +3622,38 @@ static void registerSchemeHandler (Router *router) { if (absoluteURLPathExtension.ends_with("html")) { const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; - const auto script = self.router->bridge->preload; - /* - const auto importmapFilename = userConfig["webview_importmap"]; + auto script = self.router->bridge->preload; - const auto importmapURL = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", "importmap.json" - #else - stringWithFormat: @"/%s", "importmap.json" - #endif - ] - ]]; + if (userConfig["webview_importmap"].size() > 0) { + const auto filename = Path(userConfig["webview_importmap"]).filename(); + const auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath + stringByAppendingPathComponent: [NSString + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + stringWithFormat: @"/ui/%s", filename.c_str() + #else + stringWithFormat: @"/%s", filename.c_str() + #endif + ] + ]]; - const auto importmapURL = userConfig.contains("webview_importmap") + const auto data = [NSData + dataWithContentsOfURL: [NSURL fileURLWithPath: url.path] + ]; - url = [NSURL fileURLWithPath: url.path]; - */ + if (data && data.length > 0) { + const auto string = [NSString.alloc + initWithData: data + encoding: NSUTF8StringEncoding + ]; + + script = ( + String("\n") + + script + ); + } + } auto html = String(string.UTF8String); From 047bbc2716c619fd2d44dc692ee18bf7b7a5f7c5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 1 Mar 2024 14:01:48 +0100 Subject: [PATCH 0127/1178] refactor(src/desktop/main.cc): support 'userScript' in 'window.create' --- src/desktop/main.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 0a8b1a5aa0..182921fb35 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1005,6 +1005,7 @@ MAIN { options.frameless = message.get("frameless") == "true" ? true : false; options.utility = message.get("utility") == "true" ? true : false; options.debug = message.get("debug") == "true" ? true : false; + options.userScript = message.get("userScript"); options.index = targetWindowIndex; targetWindow = windowManager.createWindow(options); From d041980db29b6f22562ca7b999f3111e763fc993 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 1 Mar 2024 14:02:13 +0100 Subject: [PATCH 0128/1178] refactor(src/core/preload): support 'userScript' preload option --- src/core/preload.cc | 16 ++++++++++++++-- src/core/preload.hh | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/core/preload.cc b/src/core/preload.cc index a5a4c2e8af..ad783de973 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -232,17 +232,29 @@ namespace SSC { "import 'socket:internal/init' \n" ); + if (preloadOptions.userScript.size() > 0) { + preload += preloadOptions.userScript; + } + if (preloadOptions.wrap) { preload += "\n"; } } else { preload += ( "if (document.readyState === 'complete') { \n" - " import('socket:internal/init').catch(console.error); \n" + " import('socket:internal/init') \n" + " .then(async () => { \n" + " " + preloadOptions.userScript + " \n" + " }) \n" + " .catch(console.error); \n" "} else { \n" " document.addEventListener('readystatechange', () => { \n" " if (/interactive|complete/.test(document.readyState)) { \n" - " import('socket:internal/init').catch(console.error); \n" + " import('socket:internal/init') \n" + " .then(async () => { \n" + " " + preloadOptions.userScript + " \n" + " }) \n" + " .catch(console.error); \n" " } \n" " }, { once: true }); \n" "} \n" diff --git a/src/core/preload.hh b/src/core/preload.hh index 6c40a507ff..b9a4b70ab2 100644 --- a/src/core/preload.hh +++ b/src/core/preload.hh @@ -7,6 +7,7 @@ namespace SSC { struct PreloadOptions { bool module = false; bool wrap = false; + String userScript = ""; }; String createPreload ( From d6a76d13a21a948800b85d60e3a540dd80cd4b54 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 1 Mar 2024 14:02:33 +0100 Subject: [PATCH 0129/1178] refactor(src/cli/templates.hh): clean up CSP, include 'UTF-8' charset in template --- src/cli/templates.hh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cli/templates.hh b/src/cli/templates.hh index ae999340de..fa91ddf153 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -193,16 +193,18 @@ constexpr auto gHelloWorld = R"HTML( + diff --git a/src/window/apple.mm b/src/window/apple.mm index 95f62edb97..2aadf7bea5 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -1068,10 +1068,10 @@ - (void) webView: (WKWebView*) webView webview.wantsLayer = YES; webview.layer.backgroundColor = [NSColor clearColor].CGColor; - /* [webview + [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground" - ]; */ + ]; // [webview registerForDraggedTypes: // [NSArray arrayWithObject:NSPasteboardTypeFileURL]]; @@ -1541,24 +1541,13 @@ - (void) webView: (WKWebView*) webView String Window::getBackgroundColor () { if (!this->window) return std::string(""); - CALayer *contentLayer = this->window.contentView.layer; - CGColorRef backgroundColorRef = contentLayer.backgroundColor; - NSColor *backgroundColor = [NSColor colorWithCGColor:backgroundColorRef]; - + NSColor *backgroundColor = [this->window backgroundColor]; NSColor *rgbColor = [backgroundColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; - CGFloat red, green, blue, alpha; - [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha]; - - NSString *colorString = [NSString - stringWithFormat: - @"rgba(%d, %d, %d, %f)", - (int)(red * 255), - (int)(green * 255), - (int)(blue * 255), - alpha - ]; + CGFloat r, g, b, a; + [rgbColor getRed:&r green:&g blue:&b alpha:&a]; + NSString *colorString = [NSString stringWithFormat: @"rgba(%.0f,%.0f,%.0f,%.1f)", r * 255, g * 255, b * 255, a]; return [colorString UTF8String]; } From 89795d2725be1b2cd7e28b90fe51c568f6077ddd Mon Sep 17 00:00:00 2001 From: heapwolf Date: Fri, 22 Mar 2024 23:55:05 +0100 Subject: [PATCH 0381/1178] fix(router): no need to release poll timer, improves readability --- src/ipc/bridge.cc | 65 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index f6f2572134..2ff3ad59cd 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -5002,14 +5002,14 @@ namespace SSC::IPC { instances.erase(cursor); } - #if SSC_PLATFORM_DESKTOP - if (instances.size() == 0) { - if (fileSystemWatcher) { - fileSystemWatcher->stop(); - delete fileSystemWatcher; + #if SSC_PLATFORM_DESKTOP + if (instances.size() == 0) { + if (fileSystemWatcher) { + fileSystemWatcher->stop(); + delete fileSystemWatcher; + } } - } - #endif + #endif } bool Router::hasMappedBuffer (int index, const Message::Seq seq) { @@ -5348,37 +5348,34 @@ namespace SSC::IPC { } Router::~Router () { - #if defined(__APPLE__) - if (this->networkStatusObserver != nullptr) { - #if !__has_feature(objc_arc) - [this->networkStatusObserver release]; - #endif - } + #if defined(__APPLE__) + if (this->networkStatusObserver != nullptr) { + #if !__has_feature(objc_arc) + [this->networkStatusObserver release]; + #endif + } - if (this->locationObserver != nullptr) { - #if !__has_feature(objc_arc) - [this->locationObserver release]; - #endif - } + if (this->locationObserver != nullptr) { + #if !__has_feature(objc_arc) + [this->locationObserver release]; + #endif + } - if (this->schemeHandler != nullptr) { - #if !__has_feature(objc_arc) - [this->schemeHandler release]; - #endif - } + if (this->schemeHandler != nullptr) { + #if !__has_feature(objc_arc) + [this->schemeHandler release]; + #endif + } - if (this->notificationPollTimer) { - [this->notificationPollTimer invalidate]; - #if !__has_feature(objc_arc) - [this->notificationPollTimer release]; - #endif - } + if (this->notificationPollTimer != nullptr) { + [this->notificationPollTimer invalidate]; + } - this->notificationPollTimer = nullptr; - this->networkStatusObserver = nullptr; - this->locationObserver = nullptr; - this->schemeHandler = nullptr; - #endif + this->notificationPollTimer = nullptr; + this->networkStatusObserver = nullptr; + this->locationObserver = nullptr; + this->schemeHandler = nullptr; + #endif } void Router::preserveCurrentTable () { From 3f9096a05c38303e632206e4098830c32def73be Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 23 Mar 2024 19:25:50 +0100 Subject: [PATCH 0382/1178] fix(window): fixes multiple issues with setting window properties, adds initial config for background for light and dark modes --- api/stream-relay/index.js | 18 ++++++++++-- src/cli/templates.hh | 8 +++++ src/desktop/main.cc | 6 +++- src/window/apple.mm | 62 ++++++++++++++++++++++++++++++++++----- src/window/linux.cc | 17 ++++++++++- src/window/options.hh | 3 +- src/window/window.hh | 4 +++ 7 files changed, 104 insertions(+), 14 deletions(-) diff --git a/api/stream-relay/index.js b/api/stream-relay/index.js index aab958a093..b164a1fd45 100644 --- a/api/stream-relay/index.js +++ b/api/stream-relay/index.js @@ -263,9 +263,18 @@ export class Peer { `/* snapshot_end=1691579150299 */).map((/** @type {object} */ o) => new RemotePeer({ ...o, indexed: true }, this)) /** - * `Peer` class constructor. Avoid calling this directly (use the create method). - * @private - * @param {object?} [persistedState] + * `Peer` class constructor. + * @param {object=} opts - Options + * @param {Buffer} opts.peerId - A 32 byte buffer (ie, `Encryption.createId()`). + * @param {Buffer} opts.clusterId - A 32 byte buffer (ie, `Encryption.createClusterId()`). + * @param {number=} opts.port - A port number. + * @param {number=} opts.probeInternalPort - An internal port number (semi-private for testing). + * @param {number=} opts.probeExternalPort - An external port number (semi-private for testing). + * @param {number=} opts.natType - A nat type. + * @param {string=} opts.address - An ipv4 address. + * @param {number=} opts.keepalive - The interval of the main loop. + * @param {function=} opts.siblingResolver - A function that can be used to determine canonical data in case two packets have concurrent clock values. + * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). */ constructor (persistedState = {}, dgram) { const config = persistedState?.config ?? persistedState ?? {} @@ -275,6 +284,9 @@ export class Peer { if (!config.peerId) throw new Error('constructor expected .peerId') if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error('invalid .peerId') + // + // The purpose of this.config is to seperate transitioned state from initial state. + // this.config = { keepalive: DEFAULT_KEEP_ALIVE, ...config diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 583c491494..1e14f5744e 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1988,6 +1988,14 @@ height = 50% ; The initial width of the first window in pixels or as a percentage of the screen. width = 50% +; The initial color of the window in dark mode. If not provided, matches the current theme. +; default value: "" +; backgroundColorDark = "rgba(0, 0, 0, 1)" + +; The initial color of the window in light mode. If not provided, matches the current theme. +; default value: "" +; backgroundColorLight = "rgba(255, 255, 255, 1)" + ; Determine if the titlebar style (hidden, hiddenInset) ; default value: "" ; titleBarStyle = "hiddenInset" diff --git a/src/desktop/main.cc b/src/desktop/main.cc index bb9cc48547..a74e8d6e72 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1047,6 +1047,8 @@ MAIN { options.titleBarStyle = message.get("titleBarStyle"); options.title = message.get("title"); options.trafficLightPosition = message.get("trafficLightPosition"); + options.backgroundColorLight = message.get("backgroundColorLight"); + options.backgroundColorDark = message.get("backgroundColorDark"); options.utility = message.get("utility") == "true" ? true : false; options.debug = message.get("debug") == "true" ? true : false; options.userScript = message.get("userScript"); @@ -1554,10 +1556,12 @@ MAIN { .maximizable = (isMaximizable == "" || isMaximizable == "true") ? true : false, .closable = (isClosable == "" || isClosable == "true") ? true : false, .frameless = getProperty("window_frameless") == "true" ? true : false, - .utility = userConfig["window_utility"] == "true" ? true : false, + .utility = getProperty("window_utility") == "true" ? true : false, .canExit = true, .titleBarStyle = getProperty("window_titleBarStyle"), .trafficLightPosition = getProperty("mac_trafficLightPosition"), + .backgroundColorLight = getProperty("window_backgroundColorLight"), + .backgroundColorDark = getProperty("window_backgroundColorDark"), .userConfig = userConfig, .onExit = shutdownHandler }); diff --git a/src/window/apple.mm b/src/window/apple.mm index 2aadf7bea5..9861cecb5f 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -863,7 +863,6 @@ - (void) webView: (WKWebView*) webView }); }); - // Initialize WKWebView WKWebViewConfiguration* config = [WKWebViewConfiguration new]; // https://webkit.org/blog/10882/app-bound-domains/ @@ -1066,12 +1065,50 @@ - (void) webView: (WKWebView*) webView ]; webview.wantsLayer = YES; - webview.layer.backgroundColor = [NSColor clearColor].CGColor; - [webview - setValue: [NSNumber numberWithBool: YES] - forKey: @"drawsTransparentBackground" - ]; + // + // Initial setup of the window background color + // + NSTextCheckingResult *rgbaMatch = nil; + + if (@available(macOS 10.14, *) && (opts.backgroundColorDark.size() || opts.backgroundColorLight.size())) { + NSString *rgba; + NSRegularExpression *regex = + [NSRegularExpression regularExpressionWithPattern: @"rgba\\((\\d+),\\s*(\\d+),\\s*(\\d+),\\s*([\\d.]+)\\)" + options: NSRegularExpressionCaseInsensitive + error: nil]; + + NSAppearance *appearance = [NSAppearance currentAppearance]; + + if ([appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameDarkAqua]]) { + if (opts.backgroundColorDark.size()) rgba = @(opts.backgroundColorDark.c_str()); + } else { + if (opts.backgroundColorLight.size()) rgba = @(opts.backgroundColorLight.c_str()); + } + + if (rgba) { + NSTextCheckingResult *rgbaMatch = + [regex firstMatchInString: rgba + options: 0 + range: NSMakeRange(0, [rgba length])]; + + if (rgbaMatch) { + int r = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:1]] intValue]; + int g = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:2]] intValue]; + int b = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:3]] intValue]; + float a = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:4]] floatValue]; + + this->setBackgroundColor(r, g, b, a); + } else { + debug("invalid arguments for window background color"); + } + } + } + + if (rgbaMatch == nil) { + webview.layer.backgroundColor = [NSColor clearColor].CGColor; + [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground"]; + } // [webview registerForDraggedTypes: // [NSArray arrayWithObject:NSPasteboardTypeFileURL]]; @@ -1246,12 +1283,16 @@ - (void) webView: (WKWebView*) webView if (opts.frameless) { [window setTitlebarAppearsTransparent: YES]; [window setMovableByWindowBackground: YES]; + style = NSWindowStyleMaskFullSizeContentView; style |= NSWindowStyleMaskBorderless; + style |= NSWindowStyleMaskResizable; + [window setStyleMask: style]; } else if (opts.utility) { style |= NSWindowStyleMaskBorderless; style |= NSWindowStyleMaskUtilityWindow; + [window setStyleMask: style]; } // @@ -1259,6 +1300,9 @@ - (void) webView: (WKWebView*) webView // if (opts.titleBarStyle == "hidden") { style |= NSWindowStyleMaskFullSizeContentView; + style |= NSWindowStyleMaskResizable; + [window setStyleMask: style]; + [window setTitleVisibility: NSWindowTitleHidden]; } // @@ -1761,14 +1805,16 @@ - (void) webView: (WKWebView*) webView } if (title.find("Quit") == 0) { - // nssSelector = [NSString stringWithUTF8String:"terminate:"]; + nssSelector = [NSString stringWithUTF8String:"terminate:"]; } if (title.compare("Minimize") == 0) { nssSelector = [NSString stringWithUTF8String:"performMiniaturize:"]; } - // if (title.compare("Zoom") == 0) nssSelector = [NSString stringWithUTF8String:"performZoom:"]; + if (title.compare("Maximize") == 0) { + nssSelector = [NSString stringWithUTF8String:"performZoom:"]; + } if (title.find("---") != -1) { [ctx addItem: [NSMenuItem separatorItem]]; diff --git a/src/window/linux.cc b/src/window/linux.cc index bcda700b8a..2f88fb0873 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -921,7 +921,22 @@ namespace SSC { userConfig["permissions_allow_data_access"] != "false" ); - GdkRGBA rgba = {0}; + if (this->opts.backgroundColorDark.size() || this->opts.backgroundColorLight.size()) { + GdkRGBA color = {0}; + + GSettings *settings = g_settings_new("org.gnome.desktop.interface"); + gboolean darkMode = g_settings_get_boolean(settings, "gtk-application-prefer-dark-theme"); + + if (darkMode && this->opts.backgroundColorDark.size()) { + gdk_rgba_parse(&color, this->opts.backgroundColorDark.c_str()); + } else if (this->opts.backgroundColorLight.size()) { + gdk_rgba_parse(&color, this->opts.backgroundColorLight.c_str()); + } + + gtk_widget_override_background_color(window, GTK_STATE_FLAG_NORMAL, &color); + } + + GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0}; webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(webview), &rgba); if (this->opts.debug) { diff --git a/src/window/options.hh b/src/window/options.hh index 72429d3fcb..5333e63654 100644 --- a/src/window/options.hh +++ b/src/window/options.hh @@ -29,6 +29,8 @@ namespace SSC { String aspectRatio = ""; String titleBarStyle = ""; String trafficLightPosition = ""; + String backgroundColorLight = ""; + String backgroundColorDark = ""; String cwd = ""; String title = ""; String url = "data:text/html,"; @@ -42,7 +44,6 @@ namespace SSC { String userScript = ""; String runtimePrimordialOverrides = ""; bool preloadCommonJS = true; - }; } #endif diff --git a/src/window/window.hh b/src/window/window.hh index 8803f48451..79f8b3c830 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -660,6 +660,8 @@ namespace SSC { .aspectRatio = opts.aspectRatio, .titleBarStyle = opts.titleBarStyle, .trafficLightPosition = opts.trafficLightPosition, + .backgroundColorLight = opts.backgroundColorLight, + .backgroundColorDark = opts.backgroundColorDark, .cwd = this->options.cwd, .title = opts.title.size() > 0 ? opts.title : "", .url = opts.url.size() > 0 ? opts.url : "data:text/html,", @@ -698,6 +700,8 @@ namespace SSC { .minimizable = opts.minimizable, .maximizable = opts.maximizable, .closable = opts.closable, + .backgroundColorLight = opts.backgroundColorLight, + .backgroundColorDark = opts.backgroundColorDark, .frameless = opts.frameless, .utility = opts.utility, .canExit = true, From d9d7bb5cac38d9aadfcd13a3fc553920f17ef1c1 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 23 Mar 2024 19:37:18 +0100 Subject: [PATCH 0383/1178] fix(window): adjust designators --- src/window/window.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/window/window.hh b/src/window/window.hh index 79f8b3c830..dbc7dfde95 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -700,8 +700,6 @@ namespace SSC { .minimizable = opts.minimizable, .maximizable = opts.maximizable, .closable = opts.closable, - .backgroundColorLight = opts.backgroundColorLight, - .backgroundColorDark = opts.backgroundColorDark, .frameless = opts.frameless, .utility = opts.utility, .canExit = true, @@ -714,6 +712,8 @@ namespace SSC { .headless = opts.userConfig["build_headless"] == "true", .titleBarStyle = opts.titleBarStyle, .trafficLightPosition = opts.trafficLightPosition, + .backgroundColorLight = opts.backgroundColorLight, + .backgroundColorDark = opts.backgroundColorDark, .userConfig = opts.userConfig }); } From be47e97daedf097cb6576d6dd4c74a6cc720942d Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 23 Mar 2024 22:22:24 +0100 Subject: [PATCH 0384/1178] fix(window): to determine which background to apply, check which desktop env is running --- src/window/linux.cc | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 2f88fb0873..5eed887cee 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -921,23 +921,37 @@ namespace SSC { userConfig["permissions_allow_data_access"] != "false" ); - if (this->opts.backgroundColorDark.size() || this->opts.backgroundColorLight.size()) { + GdkRGBA webviewBackground = {0.0, 0.0, 0.0, 0.0}; + bool hasDarkValue = this->opts.backgroundColorDark.size(); + bool hasLightValue = this->opts.backgroundColorLight.size(); + bool isDarkMode = false; + + if (hasDarkValue || hasLightValue) { GdkRGBA color = {0}; - GSettings *settings = g_settings_new("org.gnome.desktop.interface"); - gboolean darkMode = g_settings_get_boolean(settings, "gtk-application-prefer-dark-theme"); + if (getenv("GNOME_DESKTOP_SESSION_ID") != NULL) { + GSettings *settings = g_settings_new("org.gnome.desktop.interface"); + isDarkMode = g_settings_get_boolean(settings, "gtk-application-prefer-dark-theme"); + } else if (getenv("KDE_SESSION_VERSION") != NULL) { + // + // KDE doesnt have a dark or light mode, just themes, but we can check the brightness... + // + GdkRGBA bg = {0}; + gtk_style_context_get_background_color(gtk_widget_get_style_context(window), GTK_STATE_FLAG_NORMAL, &bg); + double brightness = sqrt(0.299 * pow(bg->red, 2) + 0.587 * pow(bg->green, 2) + 0.114 * pow(bg->blue, 2)); + if (brightness < 0.5) isDarkMode = true; + } - if (darkMode && this->opts.backgroundColorDark.size()) { + if (isDarkMode && hasDarkValue) { gdk_rgba_parse(&color, this->opts.backgroundColorDark.c_str()); - } else if (this->opts.backgroundColorLight.size()) { + } else if (hasLightValue) { gdk_rgba_parse(&color, this->opts.backgroundColorLight.c_str()); } gtk_widget_override_background_color(window, GTK_STATE_FLAG_NORMAL, &color); } - GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0}; - webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(webview), &rgba); + webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(webview), &webviewBackground); if (this->opts.debug) { webkit_settings_set_enable_developer_extras(settings, true); From 50c1b2c3e01ee987f2a6793fa91a4bd8126610db Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 25 Mar 2024 08:29:10 +0100 Subject: [PATCH 0385/1178] padd background colors in window arguments --- api/application.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/application.js b/api/application.js index 47a4994b72..5cd2041b48 100644 --- a/api/application.js +++ b/api/application.js @@ -212,6 +212,8 @@ export async function createWindow (opts) { aspectRatio: opts.aspectRatio ?? '', titleBarStyle: opts.titleBarStyle ?? '', trafficLightPosition: opts.trafficLightPosition ?? '', + backgroundColorDark: opts.backgroundColorDark ?? '', + backgroundColorLight: opts.backgroundColorLight ?? '', utility: opts.utility ?? false, canExit: opts.canExit ?? false, /** From cc7caac4f588b00301b4d44e09be87f095137751 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 25 Mar 2024 10:17:08 +0100 Subject: [PATCH 0386/1178] fix(platform): use decoded version of value for reveal file --- src/ipc/bridge.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 2ff3ad59cd..ab7124e6a3 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2336,7 +2336,7 @@ static void initRouterTable (Router *router) { router->core->platform.revealFile( message.seq, - message.value, + message.get("value"), RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) ); }); From 8d6228313724200ae8ad10cc2ad439e23fb87e4f Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 25 Mar 2024 10:22:55 +0100 Subject: [PATCH 0387/1178] fix(platform): call decodeURLComponent on value and ensure Windows paths are quoted --- src/core/platform.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/platform.cc b/src/core/platform.cc index 94f47bb84f..a7efcbb088 100644 --- a/src/core/platform.cc +++ b/src/core/platform.cc @@ -213,19 +213,21 @@ namespace SSC { void Core::Platform::revealFile (const String seq, const String value, Module::Callback cb) { auto json = JSON::Object {}; bool success; + String pathToFile = decodeURIComponent(value); String message = "Failed to open external file"; #if TARGET_OS_MAC && !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR - NSString *directoryPath = @(value.c_str()); + NSString *directoryPath = @(pathToFile.c_str()); NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + NSLog(@"wtf %@", directoryPath); success = [workspace selectFile:nil inFileViewerRootedAtPath:directoryPath]; #elif defined(__linux__) && !defined(__ANDROID__) - std::string command = "xdg-open " + value; + std::string command = "xdg-open " + pathToFile; auto result = exec(command.c_str()); success = result.exitCode == 0; message = result.output; #elif defined(_WIN32) - std::string command = "explorer.exe " + value; + std::string command = "explorer.exe \"" + pathToFile + "\""; auto result = exec(command.c_str()); success = result.exitCode == 0; message = result.output; From d972702f2819ba3e19a9a6a2dd756938a542dddd Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 25 Mar 2024 02:26:17 -0700 Subject: [PATCH 0388/1178] refactor(window): table KDE mode detection --- src/window/linux.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 5eed887cee..91dcaa3eb0 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -934,12 +934,9 @@ namespace SSC { isDarkMode = g_settings_get_boolean(settings, "gtk-application-prefer-dark-theme"); } else if (getenv("KDE_SESSION_VERSION") != NULL) { // - // KDE doesnt have a dark or light mode, just themes, but we can check the brightness... + // TODO(@heapwolf): KDE doesnt have a dark or light mode, it just has dark and light themes. + // the only reliable option to determine how to report this is to read the ini file ~/.config/kdeglobals // - GdkRGBA bg = {0}; - gtk_style_context_get_background_color(gtk_widget_get_style_context(window), GTK_STATE_FLAG_NORMAL, &bg); - double brightness = sqrt(0.299 * pow(bg->red, 2) + 0.587 * pow(bg->green, 2) + 0.114 * pow(bg->blue, 2)); - if (brightness < 0.5) isDarkMode = true; } if (isDarkMode && hasDarkValue) { From 0ef6abb5dbb3b19eef3bbd6ae1b639d976e09833 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 25 Mar 2024 05:10:38 -0700 Subject: [PATCH 0389/1178] fix(platform): fix incorrect value for context menu --- src/window/linux.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 91dcaa3eb0..2ed3c24ea1 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1541,7 +1541,7 @@ namespace SSC { } auto pair = split(itemData, ':'); - auto meta = String(seq + ";" + pair[0].c_str()); + auto meta = String(seq + ";" + itemData); auto *item = gtk_menu_item_new_with_label(pair[0].c_str()); g_signal_connect( @@ -1549,13 +1549,15 @@ namespace SSC { "activate", G_CALLBACK(+[](GtkWidget *t, gpointer arg) { auto window = static_cast(arg); - auto label = gtk_menu_item_get_label(GTK_MENU_ITEM(t)); - auto title = String(label); + if (!window) return; + auto meta = gtk_widget_get_name(t); auto pair = split(meta, ';'); auto seq = pair[0]; + auto items = split(pair[1], ":"); - window->eval(getResolveMenuSelectionJavaScript(seq, title, "contextMenu", "context")); + if (items.size() != 2) return; + window->eval(getResolveMenuSelectionJavaScript(seq, trim(items[0]), trim(items[1]), "context")); }), this ); From d6e8c2ee4330687def1ab19061462059fde0d14c Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sun, 31 Mar 2024 09:47:51 +0200 Subject: [PATCH 0390/1178] chore(api): generate types, docs, fix lint errors --- api/CONFIG.md | 2 ++ api/README.md | 36 +++++++++++++++++++----------------- api/application.js | 2 ++ api/index.d.ts | 23 ++++++++++++++++++----- api/stream-relay/api.js | 20 ++++++++++++-------- 5 files changed, 53 insertions(+), 30 deletions(-) diff --git a/api/CONFIG.md b/api/CONFIG.md index b2d542134f..a34975a8f2 100644 --- a/api/CONFIG.md +++ b/api/CONFIG.md @@ -205,6 +205,8 @@ Key | Default Value | Description :--- | :--- | :--- height | | The initial height of the first window in pixels or as a percentage of the screen. width | | The initial width of the first window in pixels or as a percentage of the screen. +backgroundColorDark | "" | The initial color of the window in dark mode. If not provided, matches the current theme. +backgroundColorLight | "" | The initial color of the window in light mode. If not provided, matches the current theme. titleBarStyle | "" | Determine if the titlebar style (hidden, hiddenInset) max_height | 100% | Maximum height of the window in pixels or as a percentage of the screen. max_width | 100% | Maximum width of the window in pixels or as a percentage of the screen. diff --git a/api/README.md b/api/README.md index b967018ade..7d0ecf3d30 100644 --- a/api/README.md +++ b/api/README.md @@ -29,7 +29,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L196) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L198) Creates a new window and returns an instance of ApplicationWindow. @@ -47,6 +47,8 @@ Creates a new window and returns an instance of ApplicationWindow. | opts.title | string | | true | the title of the window. | | opts.titleBarStyle | string | | true | determines the style of the titlebar (MacOS only). | | opts.trafficLightPosition | string | | true | a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). | +| opts.backgroundColorDark | string | | true | determines the background color of the window in dark mode. | +| opts.backgroundColorLight | string | | true | determines the background color of the window in light mode. | | opts.width | number \| string | | true | the width of the window. If undefined, the window will have the main window width. | | opts.height | number \| string | | true | the height of the window. If undefined, the window will have the main window height. | | opts.minWidth | number \| string | 0 | true | the minimum width of the window | @@ -64,15 +66,15 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise | | -### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L221) +### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L225) -### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L226) +### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L230) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L294) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L298) Returns the current screen size. @@ -80,7 +82,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L320) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L324) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -92,7 +94,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L377) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L381) Returns the ApplicationWindow instance for the given index @@ -104,7 +106,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L387) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L391) Returns the ApplicationWindow instance for the current window. @@ -112,7 +114,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L396) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L400) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -124,7 +126,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L493) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L497) Set the native menu for the app. @@ -219,11 +221,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L500) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L504) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L509) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L513) Set the enabled state of the system menu. @@ -235,23 +237,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L517) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L521) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L523) +## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L527) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L529) +## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L533) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L534) +## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L538) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L540) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L544) @@ -264,7 +266,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L548) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L552) diff --git a/api/application.js b/api/application.js index 5cd2041b48..5c6ad5d3ff 100644 --- a/api/application.js +++ b/api/application.js @@ -179,6 +179,8 @@ export function getCurrentWindowIndex () { * @param {string=} opts.title - the title of the window. * @param {string=} opts.titleBarStyle - determines the style of the titlebar (MacOS only). * @param {string=} opts.trafficLightPosition - a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). + * @param {string=} opts.backgroundColorDark - determines the background color of the window in dark mode. + * @param {string=} opts.backgroundColorLight - determines the background color of the window in light mode. * @param {(number|string)=} opts.width - the width of the window. If undefined, the window will have the main window width. * @param {(number|string)=} opts.height - the height of the window. If undefined, the window will have the main window height. * @param {(number|string)=} [opts.minWidth = 0] - the minimum width of the window diff --git a/api/index.d.ts b/api/index.d.ts index 111e79266b..905e32fb0f 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -6056,6 +6056,8 @@ declare module "socket:application" { * @param {string=} opts.title - the title of the window. * @param {string=} opts.titleBarStyle - determines the style of the titlebar (MacOS only). * @param {string=} opts.trafficLightPosition - a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). + * @param {string=} opts.backgroundColorDark - determines the background color of the window in dark mode. + * @param {string=} opts.backgroundColorLight - determines the background color of the window in light mode. * @param {(number|string)=} opts.width - the width of the window. If undefined, the window will have the main window width. * @param {(number|string)=} opts.height - the height of the window. If undefined, the window will have the main window height. * @param {(number|string)=} [opts.minWidth = 0] - the minimum width of the window @@ -6082,6 +6084,8 @@ declare module "socket:application" { title?: string | undefined; titleBarStyle?: string | undefined; trafficLightPosition?: string | undefined; + backgroundColorDark?: string | undefined; + backgroundColorLight?: string | undefined; width?: (number | string) | undefined; height?: (number | string) | undefined; minWidth?: (number | string) | undefined; @@ -10694,11 +10698,20 @@ declare module "socket:stream-relay/index" { */ export class Peer { /** - * `Peer` class constructor. Avoid calling this directly (use the create method). - * @private - * @param {object?} [persistedState] - */ - private constructor(); + * `Peer` class constructor. + * @param {object=} opts - Options + * @param {Buffer} opts.peerId - A 32 byte buffer (ie, `Encryption.createId()`). + * @param {Buffer} opts.clusterId - A 32 byte buffer (ie, `Encryption.createClusterId()`). + * @param {number=} opts.port - A port number. + * @param {number=} opts.probeInternalPort - An internal port number (semi-private for testing). + * @param {number=} opts.probeExternalPort - An external port number (semi-private for testing). + * @param {number=} opts.natType - A nat type. + * @param {string=} opts.address - An ipv4 address. + * @param {number=} opts.keepalive - The interval of the main loop. + * @param {function=} opts.siblingResolver - A function that can be used to determine canonical data in case two packets have concurrent clock values. + * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). + */ + constructor(persistedState: {}, dgram: object); port: any; address: any; natType: number; diff --git a/api/stream-relay/api.js b/api/stream-relay/api.js index fd17fdc9d4..3437662e57 100644 --- a/api/stream-relay/api.js +++ b/api/stream-relay/api.js @@ -160,11 +160,12 @@ async function api (options = {}, events, dgram) { const unpack = async packet => { let opened let verified - const sub = bus.subclusters.get(packet.subclusterId.toString('base64')) + const scid = Buffer.from(packet.subclusterId).toString('base64') + const sub = bus.subclusters.get(scid) if (!sub) return {} try { - opened = await _peer.open(packet.message, packet.subclusterId.toString('base64')) + opened = await _peer.open(packet.message, scid) } catch (err) { sub._emit('warning', err) return {} @@ -247,9 +248,10 @@ async function api (options = {}, events, dgram) { for (const packet of packets) { const p = Packet.from(packet) - _peer.cache.insert(packet.packetId.toString('hex'), p) + const pid = Buffer.from(packet.packetId).toString('hex') + _peer.cache.insert(pid, p) - _peer.unpublished[packet.packetId.toString('hex')] = Date.now() + _peer.unpublished[pid] = Date.now() if (globalThis.navigator && !globalThis.navigator.onLine) continue _peer.mcast(packet) @@ -274,7 +276,8 @@ async function api (options = {}, events, dgram) { sub.join = () => _peer.join(sub.sharedKey, options) bus._on('#ready', () => { - const subcluster = bus.subclusters.get(sub.subclusterId.toString('base64')) + const scid = Buffer.from(sub.subclusterId).toString('base64') + const subcluster = bus.subclusters.get(scid) if (subcluster) _peer.join(subcluster.sharedKey, options) }) @@ -283,7 +286,8 @@ async function api (options = {}, events, dgram) { } bus._on('#join', async (packet, peer) => { - const sub = bus.subclusters.get(packet.subclusterId.toString('base64')) + const scid = Buffer.from(packet.subclusterId).toString('base64') + const sub = bus.subclusters.get(scid) if (!sub) return let ee = sub.peers.get(peer.peerId) @@ -327,11 +331,11 @@ async function api (options = {}, events, dgram) { }) const handlePacket = async (packet, peer, port, address) => { - const scid = packet.subclusterId.toString('base64') + const scid = Buffer.from(packet.subclusterId).toString('base64') const sub = bus.subclusters.get(scid) if (!sub) return - const eventName = packet.usr1.toString('hex') + const eventName = Buffer.from(packet.usr1).toString('hex') const { verified, opened } = await unpack(packet) if (verified) packet.verified = true From ca73774e49054727346542007a2f1e00d9aa6167 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sun, 31 Mar 2024 10:03:18 +0200 Subject: [PATCH 0391/1178] fix(api): fix multiple failures reported by linter --- api/index.d.ts | 3 ++- api/service-worker/init.js | 6 +++--- api/stream-relay/api.js | 8 +++----- api/stream-relay/index.js | 2 +- api/stream-relay/proxy.js | 2 +- api/stream-relay/worker.js | 1 + 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 905e32fb0f..eb64a2832d 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -11089,6 +11089,7 @@ declare module "socket:stream-relay/proxy" { constructor(options: any, port: any, fn: any); init(): Promise; reconnect(): Promise; + disconnect(): Promise; getInfo(): Promise; getState(): Promise; open(...args: any[]): Promise; @@ -13246,7 +13247,7 @@ declare module "socket:service-worker/global" { declare module "socket:service-worker/init" { export function onRegister(event: any): Promise; - export function onUnregister(): Promise; + export function onUnregister(event: any): Promise; export function onSkipWaiting(event: any): Promise; export function onActivate(event: any): Promise; export function onFetch(event: any): Promise; diff --git a/api/service-worker/init.js b/api/service-worker/init.js index a02b470164..4c9d5f6e9e 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -49,7 +49,7 @@ export async function onRegister (event) { } } -export async function onUnregister () { +export async function onUnregister (event) { const info = new ServiceWorkerInfo(event.detail) if (!workers.has(info.hash)) { @@ -86,7 +86,7 @@ export async function onFetch (event) { // 32*16 milliseconds for the worker to be available or // the 'activate' event before generating a 404 response await Promise.race([ - async function () { + (async function () { let retries = 16 while (!workers.has(info.hash) && --retries > 0) { await sleep(32) @@ -95,7 +95,7 @@ export async function onFetch (event) { if (!exists) { await sleep(256) } - }(), + }()), new Promise((resolve) => { globalThis.top.addEventListener('serviceWorker.activate', async function onActivate (event) { const { hash } = new ServiceWorkerInfo(event.detail) diff --git a/api/stream-relay/api.js b/api/stream-relay/api.js index 3437662e57..54b9a5ba45 100644 --- a/api/stream-relay/api.js +++ b/api/stream-relay/api.js @@ -1,4 +1,4 @@ -import { Peer, Encryption, sha256, NAT, RemotePeer } from './index.js' +import { Peer, Encryption, sha256 } from './index.js' import { PeerWorkerProxy } from './proxy.js' import { sodium } from '../crypto.js' import { Buffer } from '../buffer.js' @@ -16,7 +16,6 @@ import { Packet, CACHE_TTL } from './packets.js' * @returns {Promise} - A promise that resolves to the initialized network bus. */ async function api (options = {}, events, dgram) { - let _peer await sodium.ready const bus = new events.EventEmitter() bus._on = bus.on @@ -38,7 +37,7 @@ async function api (options = {}, events, dgram) { if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters const Ctor = globalThis.isSocketRuntime ? PeerWorkerProxy : Peer - _peer = new Ctor(options, dgram) + const _peer = new Ctor(options, dgram) _peer.onJoin = (packet, ...args) => { if (!Buffer.from(packet.clusterId).equals(clusterId)) return @@ -199,8 +198,8 @@ async function api (options = {}, events, dgram) { const args = await pack(eventName, value, opts) if (!options.sharedKey) { throw new Error('Can\'t emit to the top level cluster, a shared key was not provided in the constructor or the arguments options') - return } + return await _peer.publish(options.sharedKey || opts.sharedKey, args) } @@ -360,5 +359,4 @@ async function api (options = {}, events, dgram) { } export { api } - export default api diff --git a/api/stream-relay/index.js b/api/stream-relay/index.js index b164a1fd45..f24af29817 100644 --- a/api/stream-relay/index.js +++ b/api/stream-relay/index.js @@ -590,7 +590,7 @@ export class Peer { }) return { - peers, + peers, config: this.config, data: [...this.cache.data.entries()], unpublished: this.unpublished diff --git a/api/stream-relay/proxy.js b/api/stream-relay/proxy.js index ed9f62ca7a..dc6850e9c2 100644 --- a/api/stream-relay/proxy.js +++ b/api/stream-relay/proxy.js @@ -151,7 +151,7 @@ class PeerWorkerProxy { return await this.callWorkerThread('reconnect') } - async reconnect () { + async disconnect () { return await this.callWorkerThread('disconnect') } diff --git a/api/stream-relay/worker.js b/api/stream-relay/worker.js index b18ab02221..c4b1845b7e 100644 --- a/api/stream-relay/worker.js +++ b/api/stream-relay/worker.js @@ -45,6 +45,7 @@ globalThis.addEventListener('message', ({ data: source }) => { } case 'compileCachePredicate': { + // eslint-disable-next-line let predicate = new Function(`return ${data.toString()}`)() predicate = predicate.bind(peer) peer.cachePredicate = packet => predicate(packet) From bf0d4931c574c92d312493fbdeebc3835080e60d Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sun, 31 Mar 2024 11:31:33 +0200 Subject: [PATCH 0392/1178] fix(network): ensure correct data type is used if it went through the worker --- api/stream-relay/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/stream-relay/api.js b/api/stream-relay/api.js index 54b9a5ba45..1c1210a4a2 100644 --- a/api/stream-relay/api.js +++ b/api/stream-relay/api.js @@ -172,7 +172,7 @@ async function api (options = {}, events, dgram) { if (packet.sig) { try { - if (Encryption.verify(opened, packet.sig, packet.usr2)) { + if (Encryption.verify(opened.data || opened, packet.sig, packet.usr2)) { verified = true } } catch (err) { From 36f30906508cdb8a17f2c0ee77c2e3c41d817962 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 21 Mar 2024 19:23:02 -0400 Subject: [PATCH 0393/1178] feat(core/protocol_handlers): introduce 'ProtocolHandlers' while improving service worker scoping --- src/core/core.hh | 4 +- src/core/protocol_handlers.cc | 69 ++++++++++++++ src/core/protocol_handlers.hh | 40 ++++++++ src/core/service_worker_container.cc | 137 +++++++++++++++------------ 4 files changed, 189 insertions(+), 61 deletions(-) create mode 100644 src/core/protocol_handlers.cc create mode 100644 src/core/protocol_handlers.hh diff --git a/src/core/core.hh b/src/core/core.hh index 9163c235a1..e21d5dc6db 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -11,6 +11,7 @@ #include "json.hh" #include "platform.hh" #include "preload.hh" +#include "protocol_handlers.hh" #include "service_worker_container.hh" #include "string.hh" #include "types.hh" @@ -18,7 +19,6 @@ #include "../process/process.hh" - #if defined(__APPLE__) @interface SSCBluetoothController : NSObject< CBCentralManagerDelegate, @@ -844,6 +844,7 @@ namespace SSC { FS fs; OS os; Platform platform; + ProtocolHandlers protocolHandlers; ServiceWorkerContainer serviceWorker; Timers timers; UDP udp; @@ -890,6 +891,7 @@ namespace SSC { fs(this), os(this), platform(this), + protocolHandlers(this), timers(this), udp(this), serviceWorker(this) diff --git a/src/core/protocol_handlers.cc b/src/core/protocol_handlers.cc new file mode 100644 index 0000000000..32ec8cdea9 --- /dev/null +++ b/src/core/protocol_handlers.cc @@ -0,0 +1,69 @@ +#include "protocol_handlers.hh" +#include "core.hh" + +namespace SSC { + static Vector reserved = { + "socket", + "ipc", + "node", + "npm" + }; + + ProtocolHandlers::ProtocolHandlers (Core* core) + : core(core) + {} + + ProtocolHandlers::~ProtocolHandlers () {} + + bool ProtocolHandlers::registerHandler (const String& scheme, const Data data) { + Lock lock(this->mutex); + + if (std::find(reserved.begin(), reserved.end(), scheme) != reserved.end()) { + return false; + } + + if (this->mapping.contains(scheme)) { + return false; + } + + this->mapping.insert_or_assign(scheme, Protocol { scheme, data }); + return true; + } + + bool ProtocolHandlers::unregisterHandler (const String& scheme) { + Lock lock(this->mutex); + + if (!this->mapping.contains(scheme)) { + return false; + } + + this->mapping.erase(scheme); + return true; + } + + const ProtocolHandlers::Data ProtocolHandlers::getHandlerData (const String& scheme) { + Lock lock(this->mutex); + + if (!this->mapping.contains(scheme)) { + return Data {}; + } + + return this->mapping.at(scheme).data; + } + + bool ProtocolHandlers::setHandlerData (const String& scheme, const Data data) { + Lock lock(this->mutex); + + if (!this->mapping.contains(scheme)) { + return false; + } + + this->mapping.at(scheme).data = data; + return true; + } + + bool ProtocolHandlers::hasHandler (const String& scheme) { + Lock lock(this->mutex); + return this->mapping.contains(scheme); + } +} diff --git a/src/core/protocol_handlers.hh b/src/core/protocol_handlers.hh new file mode 100644 index 0000000000..511169b193 --- /dev/null +++ b/src/core/protocol_handlers.hh @@ -0,0 +1,40 @@ +#ifndef SSC_PROTOCOL_HANDLERS_H +#define SSC_PROTOCOL_HANDLERS_H + +#include "platform.hh" +#include "types.hh" +#include "json.hh" + +namespace SSC { + // forward + namespace IPC { class Bridge; } + class Core; + + class ProtocolHandlers { + public: + struct Data { + String json = ""; // we store JSON as a string here + }; + + struct Protocol { + String scheme; + Data data; + }; + + using Mapping = std::map; + + Mapping mapping; + Mutex mutex; + Core* core = nullptr; + + ProtocolHandlers (Core* core); + ~ProtocolHandlers (); + + bool registerHandler (const String& scheme, const Data data = { "" }); + bool unregisterHandler (const String& scheme); + const Data getHandlerData (const String& scheme); + bool setHandlerData (const String& scheme, const Data data = { "" }); + bool hasHandler (const String& scheme); + }; +} +#endif diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 70e88a6cdf..cde3a474a7 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -218,6 +218,7 @@ namespace SSC { } this->bridge->router.map("serviceWorker.fetch.request.body", [this](auto message, auto router, auto reply) mutable { + Lock lock(this->mutex); uint64_t id = 0; try { @@ -228,24 +229,16 @@ namespace SSC { }}); } - do { - Lock lock(this->mutex); - if (!this->fetchRequests.contains(id)) { - return reply(IPC::Result::Err { message, JSON::Object::Entries { - {"type", "NotFoundError"}, - {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} - }}); - } - } while (0); + if (!this->fetchRequests.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} + }}); + } const auto& request = this->fetchRequests.at(id); const auto post = Post { 0, 0, request.buffer.bytes, request.buffer.size }; reply(IPC::Result { message.seq, message, JSON::Object {}, post }); - - do { - Lock lock(this->mutex); - this->fetchRequests.erase(id); - } while (0); }); this->bridge->router.map("serviceWorker.fetch.response", [this](auto message, auto router, auto reply) mutable { @@ -278,6 +271,13 @@ namespace SSC { }}); } + if (!this->fetchRequests.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} + }}); + } + const auto callback = this->fetchCallbacks.at(id); try { @@ -335,6 +335,15 @@ namespace SSC { html.erase(x, (y - x) + end.size()); } + Vector protocolHandlers = { "npm:", "node:" }; + for (const auto& entry : router->core->protocolHandlers.mapping) { + protocolHandlers.push_back(String(entry.first) + ":"); + } + + html = tmpl(html, Map { + {"protocol_handlers", join(protocolHandlers, " ")} + }); + if (html.find("") != String::npos) { html = replace(html, "", String("" + preload)); } else if (html.find("") != String::npos) { @@ -515,63 +524,71 @@ namespace SSC { } } + String scope; for (const auto& entry : this->registrations) { const auto& registration = entry.second; if (request.pathname.starts_with(registration.options.scope)) { - if (!registration.isActive() && registration.state == Registration::State::Registered) { - this->core->dispatchEventLoop([this, request, callback, ®istration]() { - const auto interval = this->core->setInterval(8, [this, request, callback, ®istration] (auto cancel) { - if (registration.state == Registration::State::Activated) { - cancel(); - if (!this->fetch(request, callback)) { - debug( - "ServiceWorkerContainer: Failed to dispatch fetch request '%s %s%s' for client '%llu'", - request.method.c_str(), - request.pathname.c_str(), - (request.query.size() > 0 ? String("?") + request.query : String("")).c_str(), - request.client.id - ); - } - } - }); + if (entry.first.size() > scope.size()) { + scope = entry.first; + } + } + } - this->core->setTimeout(32000, [this, interval] { - this->core->clearInterval(interval); - }); + if (scope.size() > 0 && this->registrations.contains(scope)) { + auto& registration = this->registrations.at(scope); + if (!registration.isActive() && registration.state == Registration::State::Registered) { + this->core->dispatchEventLoop([this, request, callback, ®istration]() { + const auto interval = this->core->setInterval(8, [this, request, callback, ®istration] (auto cancel) { + if (registration.state == Registration::State::Activated) { + cancel(); + if (!this->fetch(request, callback)) { + debug( + "ServiceWorkerContainer: Failed to dispatch fetch request '%s %s%s' for client '%llu'", + request.method.c_str(), + request.pathname.c_str(), + (request.query.size() > 0 ? String("?") + request.query : String("")).c_str(), + request.client.id + ); + } + } }); - return true; - } + this->core->setTimeout(32000, [this, interval] { + this->core->clearInterval(interval); + }); + }); - auto headers = JSON::Array {}; + return true; + } - for (const auto& header : request.headers) { - headers.push(header); - } + auto headers = JSON::Array {}; - const auto id = rand64(); - const auto client = JSON::Object::Entries { - {"id", std::to_string(request.client.id)} - }; - - const auto fetch = JSON::Object::Entries { - {"id", std::to_string(id)}, - {"method", request.method}, - {"pathname", request.pathname}, - {"query", request.query}, - {"headers", headers}, - {"client", client} - }; - - auto json = registration.json(); - json.set("fetch", fetch); - - this->fetchCallbacks.insert_or_assign(id, callback); - this->fetchRequests.insert_or_assign(id, request); - - return this->bridge->router.emit("serviceWorker.fetch", json.str()); + for (const auto& header : request.headers) { + headers.push(header); } + + const auto id = rand64(); + const auto client = JSON::Object::Entries { + {"id", std::to_string(request.client.id)} + }; + + const auto fetch = JSON::Object::Entries { + {"id", std::to_string(id)}, + {"method", request.method}, + {"pathname", request.pathname}, + {"query", request.query}, + {"headers", headers}, + {"client", client} + }; + + auto json = registration.json(); + json.set("fetch", fetch); + + this->fetchCallbacks.insert_or_assign(id, callback); + this->fetchRequests.insert_or_assign(id, request); + + return this->bridge->router.emit("serviceWorker.fetch", json.str()); } return false; From ec2c271aeceda4231ae92912eb706650ad1ee98c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:15:53 -0400 Subject: [PATCH 0394/1178] refactor(api/application.js): handle 'protocolHandlers' config --- api/application.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/api/application.js b/api/application.js index 5c6ad5d3ff..580747db2b 100644 --- a/api/application.js +++ b/api/application.js @@ -193,6 +193,7 @@ export function getCurrentWindowIndex () { * @param {boolean=} [opts.canExit=false] - whether the window can exit the app * @param {boolean=} [opts.headless=false] - whether the window will be headless or not (no frame) * @param {string=} [opts.userScript=null] - A user script that will be injected into the window (desktop only) + * @param {string[]=} [opts.protocolHandlers] - An array of protocol handler schemes to register with the new window (requires service worker) * @return {Promise} */ export async function createWindow (opts) { @@ -254,6 +255,20 @@ export async function createWindow (opts) { : (serializeConfig(opts?.config) ?? '') } + if (Array.isArray(opts?.protocolHandlers)) { + for (const protocolHandler of opts.protocolHandlers) { + opts.config[`webview_protocol-handlers_${protocolHandler}`] = '' + } + } else if (opts?.protocolHandlers && typeof opts.protocolHandlers === 'object') { + for (const key in opts.protocolHandlers) { + if (opts.protocolHandlers[key] && typeof opts.protocolHandlers[key] === 'object') { + opts.config[`webview_protocol-handlers_${key}`] = JSON.stringify(opts.protocolHandlers[key]) + } else if (typeof opts.protocolHandlers[key] === 'string') { + opts.config[`webview_protocol-handlers_${key}`] = opts.protocolHandlers[key] + } + } + } + if ((opts.width != null && typeof opts.width !== 'number' && typeof opts.width !== 'string') || (typeof opts.width === 'string' && !isValidPercentageValue(opts.width)) || (typeof opts.width === 'number' && !(Number.isInteger(opts.width) && opts.width > 0))) { From 10ca1f01737d63bdf62f5b19c29b45a345d5af6b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:16:24 -0400 Subject: [PATCH 0395/1178] fix(api/child_process.js): fix 'exec()' memory leak --- api/child_process.js | 66 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/api/child_process.js b/api/child_process.js index ddc3f29049..d4f18b91ac 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -64,6 +64,13 @@ export class Pipe extends AsyncResource { return this.#reading } + /** + * @type {import('./process')} + */ + get process () { + return this.#process + } + /** * Destroys the pipe */ @@ -91,12 +98,21 @@ export class ChildProcess extends EventEmitter { pid: 0 } - constructor (options = {}) { + /** + * `ChildProcess` class constructor. + * @param {{ + * env?: object, + * stdin?: boolean, + * stdout?: boolean, + * stderr?: boolean, + * signal?: AbortSigal, + * }=} [options] + */ + constructor (options = null) { super() - // - // This does not implement disconnect or message because this is not node! - // + // this does not implement disconnect or message because this is not node + // @ts-ignore const workerLocation = new URL('./child_process/worker.js', import.meta.url) // TODO(@jwerle): support environment variable inject @@ -384,6 +400,7 @@ export class ChildProcess extends EventEmitter { callback.listener = (...args) => { if (event === 'error') { callback(new ErrorEvent('error', { + // @ts-ignore target: this, error: args[0] })) @@ -416,7 +433,7 @@ export class ChildProcess extends EventEmitter { */ [gc.finalizer] () { return { - args: [this.id], + args: [this.#id], async handle (id) { const result = await ipc.send('child_process.kill', { id, @@ -449,13 +466,12 @@ export function spawn (command, args = [], options = null) { } if (args && typeof args === 'string') { + // @ts-ignore args = args.split(' ') } const child = new ChildProcess(options) child.worker.on('online', () => child.spawn(command, args, options)) - // TODO signal - // TODO timeout return child } @@ -468,11 +484,12 @@ export function exec (command, options, callback) { const child = spawn(command, options) const stdout = [] const stderr = [] + let closed = false let hasError = false if (child.stdout) { child.stdout.on('data', (data) => { - if (hasError) { + if (hasError || closed) { return } @@ -483,7 +500,7 @@ export function exec (command, options, callback) { if (child.stderr) { child.stderr.on('data', (data) => { - if (hasError) { + if (hasError || closed) { return } @@ -494,13 +511,17 @@ export function exec (command, options, callback) { child.once('error', (err) => { hasError = true + stdout.splice(0, stdout.length) + stderr.splice(0, stderr.length) if (typeof callback === 'function') { callback(err, null, null) } }) if (typeof callback === 'function') { - child.on('close', () => { + child.once('close', () => { + closed = true + if (hasError) { return } @@ -515,18 +536,30 @@ export function exec (command, options, callback) { const encoding = options?.encoding ?? 'utf8' callback( null, + // @ts-ignore Buffer.concat(stdout).toString(encoding), + // @ts-ignore Buffer.concat(stderr).toString(encoding) ) } + + stdout.splice(0, stdout.length) + stderr.splice(0, stderr.length) }) } return Object.assign(child, { then (resolve, reject) { const promise = new Promise((resolve, reject) => { - child.once('error', reject) + child.once('error', (err) => { + hasError = true + stdout.splice(0, stdout.length) + stderr.splice(0, stderr.length) + }) + child.once('close', () => { + closed = true + if (options?.encoding === 'buffer') { resolve({ stdout: Buffer.concat(stdout), @@ -535,10 +568,15 @@ export function exec (command, options, callback) { } else { const encoding = options?.encoding ?? 'utf8' resolve({ + // @ts-ignore stdout: Buffer.concat(stdout).toString(encoding), + // @ts-ignore stderr: Buffer.concat(stderr).toString(encoding) }) } + + stdout.splice(0, stdout.length) + stderr.splice(0, stderr.length) }) }) @@ -574,10 +612,12 @@ export function execSync (command, options) { }) if (result.err) { + // @ts-ignore if (!result.err.code) { throw result.err } + // @ts-ignore const { stdout, stderr, signal: errorSignal, code, pid } = result.err const message = code === 'ETIMEDOUT' ? 'execSync ETIMEDOUT' @@ -593,9 +633,11 @@ export function execSync (command, options) { output: [null, stdout.join('\n'), stderr.join('\n')] }) + // @ts-ignore error.error = error if (typeof code === 'string') { + // @ts-ignore error.errno = -os.constants.errno[code] } @@ -619,9 +661,11 @@ export function execSync (command, options) { output: [null, stdout.join('\n'), stderr.join('\n')] }) + // @ts-ignore error.error = error if (typeof code === 'string') { + // @ts-ignore error.errno = -os.constants.errno[code] } From 2f800d05c340799a75688fa08e1b78b967ac0a01 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:16:38 -0400 Subject: [PATCH 0396/1178] refactor(api/console.js): improve 'debug()' output --- api/console.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/console.js b/api/console.js index 57e83501c4..32f99d50e3 100644 --- a/api/console.js +++ b/api/console.js @@ -161,7 +161,11 @@ export class Console { if (destination === 'debug') { destination = 'stderr' extra = 'debug=true' - value = `(${globalThis.location.pathname}): ${value}` + if (globalThis.location && !globalThis.window) { + value = `[${globalThis.name || globalThis.location.pathname}]: ${value}` + } else { + value = `[${globalThis.location.pathname}]: ${value}` + } } if (/ios|darwin/i.test(os.platform())) { From f8eadf570ffc2491e8b285d5f2d922f56f11f380 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:16:51 -0400 Subject: [PATCH 0397/1178] chore(api/diagnostics/channels.js): clean up --- api/diagnostics/channels.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/diagnostics/channels.js b/api/diagnostics/channels.js index cbbc2ada4a..1db13e5949 100644 --- a/api/diagnostics/channels.js +++ b/api/diagnostics/channels.js @@ -163,11 +163,11 @@ export class Channel { /** * A no-op for `Channel` instances. This function always returns `false`. - * @param {string} name - * @param {object} message + * @param {string|object} name + * @param {object=} [message] * @return Promise */ - async publish (name, message) { + async publish (name, message = undefined) { return false } @@ -433,11 +433,11 @@ export class ChannelGroup extends Channel { /** * Publish a message to named subscribers in this group where `targets` is an * object mapping channel names to messages. - * @param {string} name - * @param {object} message + * @param {string|object} name + * @param {object=} [message] * @return Promise */ - async publish (name, message) { + async publish (name, message = undefined) { const pending = [] const targets = name && message ? { [name]: message } : name const entries = Object.entries(targets).map((e) => normalizeEntry(this, e)) From 78940ff43a2b6bbccabc8299f14fa7c718f1ec0b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:17:06 -0400 Subject: [PATCH 0398/1178] doc(api/errors.js) add missing docs --- api/errors.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/errors.js b/api/errors.js index fc91b40646..9836b4bfe5 100644 --- a/api/errors.js +++ b/api/errors.js @@ -474,6 +474,7 @@ export class ModuleNotFoundError extends NotFoundError { /** * `ModuleNotFoundError` class constructor. * @param {string} message + * @param {string[]=} [requireStack] */ constructor (message, requireStack) { super(message) From 6a146290a5b9350844e3a588ecc8fd1671ed18e1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:17:19 -0400 Subject: [PATCH 0399/1178] fix(api/events.js): fix default export --- api/events.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/events.js b/api/events.js index 36e6edf791..711b7c9fcb 100644 --- a/api/events.js +++ b/api/events.js @@ -19,8 +19,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -import * as exports from './events.js' - const R = typeof Reflect === 'object' ? Reflect : null const ReflectApply = R && typeof R.apply === 'function' ? R.apply @@ -551,5 +549,4 @@ export { EventEmitter, once } - -export default exports +export default EventEmitter From 8ce4e8adfaecf6bcb511dfb88a98be56d73c752e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:17:47 -0400 Subject: [PATCH 0400/1178] refactor(api/fetch/index.js): improve 'Response/Request' stream support --- api/fetch/index.js | 151 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/api/fetch/index.js b/api/fetch/index.js index 98af2920cb..42d77473a7 100644 --- a/api/fetch/index.js +++ b/api/fetch/index.js @@ -4,6 +4,7 @@ import { fetch, Headers, Request, Response } from './fetch.js' import { Deferred } from '../async/deferred.js' import { Buffer } from '../buffer.js' +import http from '../http.js' export { fetch, @@ -22,10 +23,24 @@ Response.json = function (json, options) { }) } -const { _initBody } = Response.prototype +const initResponseBody = Response.prototype._initBody +const initRequestBody = Request.prototype._initBody +const textEncoder = new TextEncoder() -Response.prototype._initBody = async function (body) { +Response.prototype._initBody = initBody +Request.prototype._initBody = initBody + +async function initBody (body) { this.body = null + + if ( + typeof this.statusText !== 'string' && + Number.isFinite(this.status) && + this.status in http.STATUS_CODES + ) { + this.statusText = http.STATUS_CODES[this.status] + } + if (typeof globalThis.ReadableStream === 'function') { if (body && body instanceof globalThis.ReadableStream) { let controller = null @@ -72,16 +87,98 @@ Response.prototype._initBody = async function (body) { } } - return _initBody.call(this, body) + if (this instanceof Request) { + initRequestBody.call(this, body) + } else if (this instanceof Response) { + initResponseBody.call(this, body) + } + + if (!this.body && !this._noBody) { + if (this._bodyArrayBuffer) { + const arrayBuffer = this._bodyArrayBuffer + this.body = new ReadableStream({ + start (controller) { + controller.enqueue(arrayBuffer) + controller.close() + } + }) + } else if (this._bodyBlob) { + const blob = this._bodyBlob + this.body = new ReadableStream({ + async start (controller) { + controller.enqueue(await blob.arrayBuffer()) + controller.close() + } + }) + } else if (this._bodyText) { + const text = this._bodyText + this.body = new ReadableStream({ + start (controller) { + controller.enqueue(textEncoder.encode(text)) + controller.close() + } + }) + } else { + this.body = null + } + } } Object.defineProperties(Request.prototype, { + _bodyArrayBuffer: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyFormData: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyText: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyInit: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyBlob: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _noBody: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + url: { configurable: true, writable: true, value: undefined }, + body: { + configurable: true, + writable: true, + value: undefined + }, + credentials: { configurable: true, writable: true, @@ -120,6 +217,48 @@ Object.defineProperties(Request.prototype, { }) Object.defineProperties(Response.prototype, { + _bodyArrayBuffer: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyFormData: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyText: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyInit: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _bodyBlob: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + + _noBody: { + configurable: true, + enumerable: false, + writable: true, + value: undefined + }, + body: { configurable: true, writable: true, @@ -160,6 +299,12 @@ Object.defineProperties(Response.prototype, { configurable: true, writable: true, value: undefined + }, + + redirected: { + configurable: true, + writable: true, + value: undefined } }) From fe71df53563b7de3f07b8d983f67492c1890a85a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:18:00 -0400 Subject: [PATCH 0401/1178] refactor(api/fs/index.js): add missing 'fs.write' API --- api/fs/index.js | 61 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/api/fs/index.js b/api/fs/index.js index 66fffa509c..d4fed69cea 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -79,7 +79,7 @@ async function visit (path, options = null, callback) { /** * Asynchronously check access a file for a given mode calling `callback` * upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} * @param {string | Buffer | URL} path * @param {string?|function(Error?)?} [mode = F_OK(0)] * @param {function(Error?)?} [callback] @@ -103,7 +103,7 @@ export function access (path, mode, callback) { /** * Synchronously check access a file for a given mode calling `callback` * upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} * @param {string | Buffer | URL} path * @param {string?} [mode = F_OK(0)] */ @@ -256,7 +256,7 @@ export function chownSync (path, uid, gid) { /** * Asynchronously close a file descriptor calling `callback` upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsclosefd-callback} + * @see {@link https://nodejs.org/api/fs.html#fsclosefd-callback} * @param {number} fd * @param {function(Error?)?} [callback] */ @@ -282,7 +282,7 @@ export function close (fd, callback) { * @param {string} dest - The destination file path. * @param {number} flags - Modifiers for copy operation. * @param {function(Error=)=} [callback] - The function to call after completion. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFile (src, dest, flags, callback) { if (typeof src !== 'string') { @@ -311,7 +311,7 @@ export function copyFile (src, dest, flags, callback) { * @param {string} src - The source file path. * @param {string} dest - The destination file path. * @param {number} flags - Modifiers for copy operation. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFileSync (src, dest, flags) { if (typeof src !== 'string') { @@ -334,7 +334,7 @@ export function copyFileSync (src, dest, flags) { } /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options} + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path * @param {object?} [options] * @returns {ReadStream} @@ -377,7 +377,7 @@ export function createReadStream (path, options) { } /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options} + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path * @param {object?} [options] * @returns {WriteStream} @@ -422,7 +422,7 @@ export function createWriteStream (path, options) { * Invokes the callback with the for the file descriptor. See * the POSIX fstat(2) documentation for more detail. * - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsfstatfd-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fsfstatfd-options-callback} * * @param {number} fd - A file descriptor. * @param {object?|function?} [options] - An options object. @@ -608,7 +608,7 @@ export function mkdirSync (path, options) { /** * Asynchronously open a file calling `callback` upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} * @param {string | Buffer | URL} path * @param {string?} [flags = 'r'] * @param {string?} [mode = 0o666] @@ -661,7 +661,7 @@ export function open (path, flags = 'r', mode = 0o666, options = null, callback) /** * Asynchronously open a directory calling `callback` upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} * @param {string | Buffer | URL} path * @param {object?|function(Error?, Dir?)} [options] * @param {string?} [options.encoding = 'utf8'] @@ -686,7 +686,7 @@ export function opendir (path, options = {}, callback) { /** * Asynchronously read from an open file descriptor. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreadfd-buffer-offset-length-position-callback} + * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} * @param {number} fd * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. * @param {number} offset - The position in buffer to write the data to. @@ -719,9 +719,44 @@ export function read (fd, buffer, offset, length, position, options, callback) { } } +/** + * Asynchronously write to an open file descriptor. + * @see {@link https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback} + * @param {number} fd + * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. + * @param {number} offset - The position in buffer to write the data to. + * @param {number} length - The number of bytes to read. + * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. + * @param {function(Error?, number?, Buffer?)} callback + */ +export function write (fd, buffer, offset, length, position, options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + + if (typeof buffer === 'object' && !isBufferLike(buffer)) { + options = buffer + } + + if (typeof callback !== 'function') { + throw new TypeError('callback must be a function.') + } + + try { + FileHandle + .from(fd) + .write({ ...options, buffer, offset, length, position }) + .then(({ bytesWritten, buffer }) => callback(null, bytesWritten, buffer)) + .catch((err) => callback(err)) + } catch (err) { + callback(err) + } +} + /** * Asynchronously read all entries in a directory. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} * @param {string | Buffer | URL } path * @param {object?|function(Error?, object[])} [options] * @param {string?} [options.encoding ? 'utf8'] @@ -1112,7 +1147,7 @@ export function unlink (path, callback) { } /** - * @see {@url https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fswritefilefile-data-options-callback} + * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} * @param {string | Buffer | URL | number } path - filename or file descriptor * @param {string | Buffer | TypedArray | DataView | object } data * @param {object?} options From 018ad2342b1cea55abcbea4f23228af7a2c5dfab Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:18:26 -0400 Subject: [PATCH 0402/1178] refactor(api/http.js,api/https.js): add status code constants --- api/http.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ api/https.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/api/http.js b/api/http.js index c7984b98c4..1720ae5845 100644 --- a/api/http.js +++ b/api/http.js @@ -120,6 +120,70 @@ export const STATUS_CODES = { 511: 'Network Authentication Required' } +export const CONTINUE = 100 +export const SWITCHING_PROTOCOLS = 101 +export const PROCESSING = 102 +export const EARLY_HINTS = 103 +export const OK = 200 +export const CREATED = 201 +export const ACCEPTED = 202 +export const NONAUTHORITATIVE_INFORMATION = 203 +export const NO_CONTENT = 204 +export const RESET_CONTENT = 205 +export const PARTIAL_CONTENT = 206 +export const MULTISTATUS = 207 +export const ALREADY_REPORTED = 208 +export const IM_USED = 226 +export const MULTIPLE_CHOICES = 300 +export const MOVED_PERMANENTLY = 301 +export const FOUND = 302 +export const SEE_OTHER = 303 +export const NOT_MODIFIED = 304 +export const USE_PROXY = 305 +export const TEMPORARY_REDIRECT = 307 +export const PERMANENT_REDIRECT = 308 +export const BAD_REQUEST = 400 +export const UNAUTHORIZED = 401 +export const PAYMENT_REQUIRED = 402 +export const FORBIDDEN = 403 +export const NOT_FOUND = 404 +export const METHOD_NOT_ALLOWED = 405 +export const NOT_ACCEPTABLE = 406 +export const PROXY_AUTHENTICATION_REQUIRED = 407 +export const REQUEST_TIMEOUT = 408 +export const CONFLICT = 409 +export const GONE = 410 +export const LENGTH_REQUIRED = 411 +export const PRECONDITION_FAILED = 412 +export const PAYLOAD_TOO_LARGE = 413 +export const URI_TOO_LONG = 414 +export const UNSUPPORTED_MEDIA_TYPE = 415 +export const RANGE_NOT_SATISFIABLE = 416 +export const EXPECTATION_FAILED = 417 +export const IM_A_TEAPOT = 418 +export const MISDIRECTED_REQUEST = 421 +export const UNPROCESSABLE_ENTITY = 422 +export const LOCKED = 423 +export const FAILED_DEPENDENCY = 424 +export const TOO_EARLY = 425 +export const UPGRADE_REQUIRED = 426 +export const PRECONDITION_REQUIRED = 428 +export const TOO_MANY_REQUESTS = 429 +export const REQUEST_HEADER_FIELDS_TOO_LARGE = 431 +export const UNAVAILABLE_FOR_LEGAL_REASONS = 451 +export const INTERNAL_SERVER_ERROR = 500 +export const NOT_IMPLEMENTED = 501 +export const BAD_GATEWAY = 502 +export const SERVICE_UNAVAILABLE = 503 +export const GATEWAY_TIMEOUT = 504 +export const HTTP_VERSION_NOT_SUPPORTED = 505 +export const VARIANT_ALSO_NEGOTIATES = 506 +export const INSUFFICIENT_STORAGE = 507 +export const LOOP_DETECTED = 508 +export const BANDWIDTH_LIMIT_EXCEEDED = 509 +export const NOT_EXTENDED = 510 +export const NETWORK_AUTHENTICATION_REQUIRED = 511 + /** * The parent class of `ClientRequest` and `ServerResponse`. * It is an abstract outgoing message from the perspective of the diff --git a/api/https.js b/api/https.js index 82d942ff77..3fd8feca17 100644 --- a/api/https.js +++ b/api/https.js @@ -2,6 +2,70 @@ import http from './http.js' // re-export import * as exports from './http.js' +export const CONTINUE = http.CONTINUE +export const SWITCHING_PROTOCOLS = http.SWITCHING_PROTOCOLS +export const PROCESSING = http.PROCESSING +export const EARLY_HINTS = http.EARLY_HINTS +export const OK = http.OK +export const CREATED = http.CREATED +export const ACCEPTED = http.ACCEPTED +export const NONAUTHORITATIVE_INFORMATION = http.NONAUTHORITATIVE_INFORMATION +export const NO_CONTENT = http.NO_CONTENT +export const RESET_CONTENT = http.RESET_CONTENT +export const PARTIAL_CONTENT = http.PARTIAL_CONTENT +export const MULTISTATUS = http.MULTISTATUS +export const ALREADY_REPORTED = http.ALREADY_REPORTED +export const IM_USED = http.IM_USED +export const MULTIPLE_CHOICES = http.MULTIPLE_CHOICES +export const MOVED_PERMANENTLY = http.MOVED_PERMANENTLY +export const FOUND = http.FOUND +export const SEE_OTHER = http.SEE_OTHER +export const NOT_MODIFIED = http.NOT_MODIFIED +export const USE_PROXY = http.USE_PROXY +export const TEMPORARY_REDIRECT = http.TEMPORARY_REDIRECT +export const PERMANENT_REDIRECT = http.PERMANENT_REDIRECT +export const BAD_REQUEST = http.BAD_REQUEST +export const UNAUTHORIZED = http.UNAUTHORIZED +export const PAYMENT_REQUIRED = http.PAYMENT_REQUIRED +export const FORBIDDEN = http.FORBIDDEN +export const NOT_FOUND = http.NOT_FOUND +export const METHOD_NOT_ALLOWED = http.METHOD_NOT_ALLOWED +export const NOT_ACCEPTABLE = http.NOT_ACCEPTABLE +export const PROXY_AUTHENTICATION_REQUIRED = http.PROXY_AUTHENTICATION_REQUIRED +export const REQUEST_TIMEOUT = http.REQUEST_TIMEOUT +export const CONFLICT = http.CONFLICT +export const GONE = http.GONE +export const LENGTH_REQUIRED = http.LENGTH_REQUIRED +export const PRECONDITION_FAILED = http.PRECONDITION_FAILED +export const PAYLOAD_TOO_LARGE = http.PAYLOAD_TOO_LARGE +export const URI_TOO_LONG = http.URI_TOO_LONG +export const UNSUPPORTED_MEDIA_TYPE = http.UNSUPPORTED_MEDIA_TYPE +export const RANGE_NOT_SATISFIABLE = http.RANGE_NOT_SATISFIABLE +export const EXPECTATION_FAILED = http.EXPECTATION_FAILED +export const IM_A_TEAPOT = http.IM_A_TEAPOT +export const MISDIRECTED_REQUEST = http.MISDIRECTED_REQUEST +export const UNPROCESSABLE_ENTITY = http.UNPROCESSABLE_ENTITY +export const LOCKED = http.LOCKED +export const FAILED_DEPENDENCY = http.FAILED_DEPENDENCY +export const TOO_EARLY = http.TOO_EARLY +export const UPGRADE_REQUIRED = http.UPGRADE_REQUIRED +export const PRECONDITION_REQUIRED = http.PRECONDITION_REQUIRED +export const TOO_MANY_REQUESTS = http.TOO_MANY_REQUESTS +export const REQUEST_HEADER_FIELDS_TOO_LARGE = http.REQUEST_HEADER_FIELDS_TOO_LARGE +export const UNAVAILABLE_FOR_LEGAL_REASONS = http.UNAVAILABLE_FOR_LEGAL_REASONS +export const INTERNAL_SERVER_ERROR = http.INTERNAL_SERVER_ERROR +export const NOT_IMPLEMENTED = http.NOT_IMPLEMENTED +export const BAD_GATEWAY = http.BAD_GATEWAY +export const SERVICE_UNAVAILABLE = http.SERVICE_UNAVAILABLE +export const GATEWAY_TIMEOUT = http.GATEWAY_TIMEOUT +export const HTTP_VERSION_NOT_SUPPORTED = http.HTTP_VERSION_NOT_SUPPORTED +export const VARIANT_ALSO_NEGOTIATES = http.VARIANT_ALSO_NEGOTIATES +export const INSUFFICIENT_STORAGE = http.INSUFFICIENT_STORAGE +export const LOOP_DETECTED = http.LOOP_DETECTED +export const BANDWIDTH_LIMIT_EXCEEDED = http.BANDWIDTH_LIMIT_EXCEEDED +export const NOT_EXTENDED = http.NOT_EXTENDED +export const NETWORK_AUTHENTICATION_REQUIRED = http.NETWORK_AUTHENTICATION_REQUIRED + /** * All known possible HTTP methods. * @type {string[]} From 298263fb7e0b08bbfd5f4e4dbdb1541b859ffa8b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:18:35 -0400 Subject: [PATCH 0403/1178] chore(api/ipc.js): clean up --- api/ipc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/ipc.js b/api/ipc.js index c8fb0d81d2..2342d17837 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -53,7 +53,6 @@ import { import * as errors from './errors.js' import { Buffer } from './buffer.js' import { rand64 } from './crypto.js' -import { URL } from './url.js' let nextSeq = 1 const cache = {} From 1307fcf75b6c50b8c445748ef8c8ff5ef34507d6 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:18:49 -0400 Subject: [PATCH 0404/1178] fix(api/mime/index.js): fix missing params values --- api/mime/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/mime/index.js b/api/mime/index.js index c40fc3e546..f808867e41 100644 --- a/api/mime/index.js +++ b/api/mime/index.js @@ -253,8 +253,8 @@ export class MIMEType { const [type, subtype] = types - this.#type = type - this.#params = new MIMEParams() + this.#type = type.toLowerCase() + this.#params = new MIMEParams(args.map((a) => a.trim().split('=').map((v) => v.trim()))) this.#subtype = subtype } From 7be78ad82fc9bfce59e4de94261f7faa4e1172ca Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:19:07 -0400 Subject: [PATCH 0405/1178] refactor(api/path/path.js): throw error on missing 'dirname' path --- api/path/path.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/path/path.js b/api/path/path.js index 0892925634..b08eebcdb7 100644 --- a/api/path/path.js +++ b/api/path/path.js @@ -220,6 +220,12 @@ export function join (options, ...components) { * @return {string} */ export function dirname (options, path) { + if (typeof path !== 'string') { + throw Object.assign(new Error(`The "path" argument must be of type string. Received: ${path}`), { + code: 'ERR_INVALID_ARG_TYPE' + }) + } + if (windowsDriveInPathRegex.test(path)) { path = path.slice(1) } From 44ad794cc33685d7b2670e911f60889230828ae4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:19:27 -0400 Subject: [PATCH 0406/1178] refactor(api/util.js): handle 'stylize' function --- api/util.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/api/util.js b/api/util.js index d3bfccc837..be6dcfb22b 100644 --- a/api/util.js +++ b/api/util.js @@ -386,7 +386,12 @@ export function inspect (value, options) { ), ...options, - options + options: { + stylize (label, style) { + return label + }, + ...options + } } return formatValue(ctx, value, ctx.depth) @@ -619,6 +624,25 @@ export function inspect (value, options) { )) } } + } else if (typeof value === 'function') { + for (const key of keys) { + if ( + !/^\d+$/.test(key) && + key !== 'name' && + key !== 'length' && + key !== 'prototype' && + key !== 'constructor' + ) { + output.push(formatProperty( + ctx, + value, + depth, + enumerableKeys, + key, + false + )) + } + } } else { output.push(...Array.from(keys).map((key) => formatProperty( ctx, From de4974d7a3715ff7040a2eeb000caa792b8efd1f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:20:19 -0400 Subject: [PATCH 0407/1178] refactor(api/url/index.js): handle more standard protocols and handlers --- api/url/index.js | 86 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/api/url/index.js b/api/url/index.js index a0ca61c073..ca1575b7b7 100644 --- a/api/url/index.js +++ b/api/url/index.js @@ -1,4 +1,4 @@ -import { URLPattern } from './urlpattern/urlpattern.js' +import { URLPattern as URLPatternImplementation } from './urlpattern/urlpattern.js' import url from './url/url.js' import qs from '../querystring.js' @@ -20,14 +20,90 @@ URL.resolve = resolve URL.parse = parse URL.format = format +export class URLPattern extends URLPatternImplementation {} + +const URLPatternDescriptors = Object.getOwnPropertyDescriptors(URLPattern.prototype) +Object.defineProperties(URLPatternDescriptors, { + hash: { ...URLPatternDescriptors.hash, enumerable: true }, + hostname: { ...URLPatternDescriptors.hostname, enumerable: true }, + password: { ...URLPatternDescriptors.password, enumerable: true }, + pathname: { ...URLPatternDescriptors.pathname, enumerable: true }, + protocol: { ...URLPatternDescriptors.protocol, enumerable: true }, + username: { ...URLPatternDescriptors.username, enumerable: true }, + search: { ...URLPatternDescriptors.search, enumerable: true } +}) + export const protocols = new Set([ 'socket:', - 'https:', - 'http:', + 'node:', + 'npm:', + 'ipc:', + + // web standard & reserved + 'bitcoin:', 'file:', - 'ipc:' + 'ftp:', + 'ftps:', + 'geo:', + 'git:', + 'http:', + 'https:', + 'im:', + 'ipfs:', + 'irc:', + 'ircs:', + 'magnet:', + 'mailto:', + 'matrix:', + 'mms:', + 'news:', + 'nntp:', + 'openpgp4fpr:', + 'sftp:', + 'sip:', + 'sms:', + 'smsto:', + 'ssh:', + 'tel:', + 'urn:', + 'webcal:', + 'wtai:', + 'xmpp:' ]) +if (globalThis.__args?.config && typeof globalThis.__args.config === 'object') { + const protocolHandlers = String(globalThis.__args.config['webview_protocol-handlers'] || '') + .split(' ') + .filter(Boolean) + + const webviewURLProtocols = String(globalThis.__args.config.webview_url_protocols || '') + .split(' ') + .filter(Boolean) + + for (const value of webviewURLProtocols) { + const scheme = value.replace(':', '') + if (scheme) { + protocols.add(scheme + ':') + } + } + + for (const value of protocolHandlers) { + const scheme = value.replace(':', '') + if (scheme) { + protocols.add(scheme + ':') + } + } + + for (const key in globalThis.__args.config) { + if (key.startsWith('webview_protocol-handlers_')) { + const scheme = key.replace('webview_protocol-handlers_', '').replace(':', '') + if (scheme) { + protocols.add(scheme + ':') + } + } + } +} + export function parse (input, options = null) { if (URL.canParse(input)) { return new URL(input) @@ -158,4 +234,4 @@ Object.defineProperties(URL.prototype, { }) export default URL -export { URLPattern, URL, URLSearchParams, parseURL } +export { URL, URLSearchParams, parseURL } From b50e137ebcf4d94ea8c1f31bd4c885755d649e95 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:20:45 -0400 Subject: [PATCH 0408/1178] fix(api/test/context.js): log error only in tests --- api/test/context.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/test/context.js b/api/test/context.js index c87c04e6ac..3cd7a483a3 100644 --- a/api/test/context.js +++ b/api/test/context.js @@ -31,7 +31,9 @@ export default function (GLOBAL_TEST_RUNNER) { function onerror (e) { const err = e.error || e.stack || e.reason || e.message || e if (err.ignore || err[Symbol.for('socket.test.error.ignore')]) return - console.error(err) + if (globalThis.RUNTIME_TEST_FILENAME || GLOBAL_TEST_RUNNER.length > 0) { + console.error(err) + } if (finishing || process.env.DEBUG) { return From 132bdb42e1d3b73736eb0c96ed8a38faa8ee4c02 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:21:01 -0400 Subject: [PATCH 0409/1178] chore(tsconfig.json): handle 'npm:' prefixes --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 68d363d7da..7b089f0bae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "checkJs": false, "allowJs": true, "paths": { + "npm:*": ["node_modules/*"], "socket:*": ["api/*"] } } From 3c81a4093b1802ee0beadb4e9a3ca0f1135575fd Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:21:49 -0400 Subject: [PATCH 0410/1178] refactor(api/service-worker): handle 'POST/PUT' body buffers better, improve debugging --- api/service-worker/context.js | 7 ++++++ api/service-worker/events.js | 3 +++ api/service-worker/index.html | 6 ++--- api/service-worker/init.js | 35 +++++++++++++++++++++++----- api/service-worker/worker.js | 43 +++++++++++++++++++++++++++++++---- 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/api/service-worker/context.js b/api/service-worker/context.js index 1f9469358f..88bf2694ff 100644 --- a/api/service-worker/context.js +++ b/api/service-worker/context.js @@ -8,6 +8,13 @@ import clients from './clients.js' export class Context { #event = null + /** + * Context data. This may be a custom protocol handler scheme data + * by default, if available. + * @type {any?} + */ + data = null + /** * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 3f3bc509af..55d28c7a52 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -117,6 +117,8 @@ export class ExtendableEvent extends Event { * request and how the receiver will treat the response. */ export class FetchEvent extends ExtendableEvent { + static defaultHeaders = new Headers() + #handled = new Deferred() #request = null #clientId = null @@ -237,6 +239,7 @@ export class FetchEvent extends ExtendableEvent { if (response.type === 'error') { const statusCode = 0 const headers = Array.from(response.headers.entries()) + .concat(FetchEvent.defaultHeaders.entries()) .map((entry) => entry.join(':')) .concat('Runtime-Response-Source:serviceworker') .concat('Access-Control-Allow-Credentials:true') diff --git a/api/service-worker/index.html b/api/service-worker/index.html index bf89e9905f..f8df84ac9b 100644 --- a/api/service-worker/index.html +++ b/api/service-worker/index.html @@ -5,9 +5,9 @@ Module.main.exports, \n" " }, \n" + " process: { \n" + " configurable: true, \n" + " enumerable: false, \n" + " get: () => process, \n" + " }, \n" " __dirname: { \n" " configurable: true, \n" " enumerable: false, \n" @@ -322,7 +328,7 @@ namespace SSC { " enumerable: false, \n" " writable: false, \n" " value: require, \n" - " }, \n" + " } \n" " }); \n" " \n" " globalThis.addEventListener('popstate', preloadCommonJSScope); \n" From 656a98284c880f22ae7ef9526cc6dfd107371020 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:23:20 -0400 Subject: [PATCH 0414/1178] feat(api/commonjs): introduce new commonjs core --- api/commonjs/builtins.js | 198 ++++++++++++ api/commonjs/loader.js | 578 +++++++++++++++++++++++++++++++++ api/commonjs/module.js | 670 +++++++++++++++++++++++++++++++++++++++ api/commonjs/package.js | 501 +++++++++++++++++++++++++++++ api/commonjs/require.js | 208 ++++++++++++ 5 files changed, 2155 insertions(+) create mode 100644 api/commonjs/builtins.js create mode 100644 api/commonjs/loader.js create mode 100644 api/commonjs/module.js create mode 100644 api/commonjs/package.js create mode 100644 api/commonjs/require.js diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js new file mode 100644 index 0000000000..bf269346e9 --- /dev/null +++ b/api/commonjs/builtins.js @@ -0,0 +1,198 @@ +import { ModuleNotFoundError } from '../errors.js' + +// eslint-disable-next-line +import _async, { + AsyncLocalStorage, + AsyncResource, + executionAsyncResource, + executionAsyncId, + triggerAsyncId, + createHook, + AsyncHook +} from '../async.js' + +// eslint-disable-next-line +import application from '../application.js' +import assert from '../assert.js' +import * as buffer from '../buffer.js' +// eslint-disable-next-line +import child_process from '../child_process.js' +import console from '../console.js' +import constants from '../constants.js' +import crypto from '../crypto.js' +import dgram from '../dgram.js' +import dns from '../dns.js' +import events from '../events.js' +import extension from '../extension.js' +import fs from '../fs.js' +import gc from '../gc.js' +import http from '../http.js' +import https from '../https.js' +import ipc from '../ipc.js' +import language from '../language.js' +import location from '../location.js' +import mime from '../mime.js' +import network from '../network.js' +import os from '../os.js' +import { posix as path } from '../path.js' +import process from '../process.js' +import querystring from '../querystring.js' +import stream from '../stream.js' +// eslint-disable-next-line +import string_decoder from '../string_decoder.js' +import test from '../test.js' +import timers from '../timers.js' +import url from '../url.js' +import util from '../util.js' +import vm from '../vm.js' +import window from '../window.js' +// eslint-disable-next-line +import worker_threads from '../worker_threads.js' + +/** + * A mapping of builtin modules + * @type {object} + */ +export const builtins = { + // eslint-disable-next-line + 'async': _async, + // eslint-disable-next-line + async_context: { + AsyncLocalStorage, + AsyncResource + }, + // eslint-disable-next-line + async_hooks: { + AsyncLocalStorage, + AsyncResource, + executionAsyncResource, + executionAsyncId, + triggerAsyncId, + createHook, + AsyncHook + }, + application, + assert, + buffer, + console, + constants, + // eslint-disable-next-line + child_process, + crypto, + dgram, + dns, + 'dns/promises': dns.promises, + events, + extension, + fs, + 'fs/promises': fs.promises, + http, + https, + gc, + ipc, + language, + location, + mime, + net: {}, + network, + os, + path, + // eslint-disable-next-line + perf_hooks: { + performance: globalThis.performance + }, + process, + querystring, + stream, + 'stream/web': stream.web, + // eslint-disable-next-line + string_decoder, + sys: util, + test, + timers, + 'timers/promises': timers.promises, + tty: { + isatty: () => false, + WriteStream: util.IllegalConstructor, + ReadStream: util.IllegalConstructor + }, + util, + url, + vm, + window, + // eslint-disable-next-line + worker_threads +} + +/** + * Known runtime specific builtin modules. + * @type {string[]} + */ +export const runtimeModules = [ + 'async', + 'application', + 'extension', + 'gc', + 'ipc', + 'language', + 'location', + 'mime', + 'network', + 'window' +] + +/** + * Predicate to determine if a given module name is a builtin module. + * @param {string} name + * @param {{ builtins?: object }} + * @return {boolean} + */ +export function isBuiltin (name, options = null) { + const originalName = name + name = name.replace(/^(socket|node):/, '') + + if ( + runtimeModules.includes(name) && + !originalName.startsWith('socket:') + ) { + return false + } + + if (options?.builtins && typeof options.builtins === 'object') { + return name in builtins + } else if (name in builtins) { + return true + } + + return false +} + +/** + * Gets a builtin module by name. + * @param {string} name + * @param {{ builtins?: object }} + * @return {any} + */ +export function getBuiltin (name, options = null) { + const originalName = name + name = name.replace(/^(socket|node):/, '') + + if ( + runtimeModules.includes(name) && + !originalName.startsWith('socket:') + ) { + throw new ModuleNotFoundError( + `Cannnot find builtin module '${originalName}` + ) + } + + if (name in builtins) { + return builtins[name] + } + + throw new ModuleNotFoundError( + `Cannnot find builtin module '${originalName}` + ) +} + +export default builtins diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js new file mode 100644 index 0000000000..352ca06cde --- /dev/null +++ b/api/commonjs/loader.js @@ -0,0 +1,578 @@ +/* global XMLHttpRequest */ +import { Headers } from '../ipc.js' +import location from '../location.js' +import path from '../path.js' + +const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' + +/** + * @typedef {{ + * extensions?: string[] | Set + * origin?: URL | string, + * statuses?: Map + * cache?: Map + * }} LoaderOptions + */ + +/** + * @typedef {{ + * loader?: Loader, + * origin?: URL | string + * }} RequestOptions + */ + +/** + * @typedef {{ + * headers?: Headers | object + * }} RequestLoadOptions + */ + +/** + * @typedef {{ + * request?: Request, + * headers?: Headers, + * status?: number, + * text?: string + * }} ResponseOptions + */ + +/** + * A container for the status of a CommonJS resource. A `RequestStatus` object + * represents meta data for a `Request` that comes from a preflight + * HTTP HEAD request. + */ +export class RequestStatus { + #status = undefined + #request = null + #headers = new Headers() + + /** + * `RequestStatus` class constructor. + * @param {Request} request + */ + constructor (request) { + if (!request || !(request instanceof Request)) { + throw new TypeError( + `Expecting 'request' to be a Request object. Received: ${request}` + ) + } + + this.#request = request + } + + /** + * The unique ID of this `RequestStatus`, which is the absolute URL as a string. + * @type {string} + */ + get id () { + return this.#request.id + } + + /** + * The origin for this `RequestStatus` object. + * @type {string} + */ + get origin () { + return this.#request.origin + } + + /** + * A HTTP status code for this `RequestStatus` object. + * @type {number|undefined} + */ + get status () { + return this.#status + } + + /** + * An alias for `status`. + * @type {number|undefined} + */ + get value () { + return this.#status + } + + /** + * @ignore + */ + get valueOf () { + return this.status + } + + /** + * The HTTP headers for this `RequestStatus` object. + * @type {Headers} + */ + get headers () { + return this.#headers + } + + /** + * The resource location for this `RequestStatus` object. This value is + * determined from the 'Content-Location' header, if available, otherwise + * it is derived from the request URL pathname (including the query string). + * @type {string} + */ + get location () { + const contentLocation = this.#headers.get('content-location') + if (contentLocation) { + return contentLocation + } + + return this.#request.url.pathname + this.#request.url.search + } + + /** + * `true` if the response status is considered OK, otherwise `false`. + * @type {boolean} + */ + get ok () { + return this.#status >= 200 && this.#status < 400 + } + + /** + * Loads the internal state for this `RequestStatus` object. + * @param {RequestLoadOptions|boolean} [options] + * @return {RequestStatus} + */ + load (options = null) { + // allow `load(true)` to force a reload of the state + if (this.#status && options !== true) { + return this + } + + const request = new XMLHttpRequest() + + request.open('HEAD', this.#request.id, false) + request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') + + if (options?.headers && typeof options?.headers === 'object') { + const entries = typeof options.headers.entries === 'function' + ? options.headers.entries() + : Object.entries(options.headers) + + for (const entry of entries) { + request.setRequestHeader(entry[0], entry[1]) + } + } + + request.send(null) + + this.#headers = Headers.from(request) + this.#status = request.status + + const contentLocation = this.#headers.get('content-location') + + // verify 'Content-Location' header if given in response + if (contentLocation && URL.canParse(contentLocation, this.origin)) { + const url = new URL(contentLocation, this.origin) + const extension = path.extname(url.pathname) + + if (!this.#request.loader.extensions.has(extension)) { + this.#status = 404 + return this + } + } + + return this + } +} + +/** + * A container for a synchronous CommonJS request to local resource or + * over the network. + */ +export class Request { + #id = null + #url = null + #loader = null + #status = null + + /** + * `Request` class constructor. + * @param {URL|string} url + * @param {URL|string=} [origin] + * @param {RequestOptions=} [options] + */ + constructor (url, origin, options = null) { + if (origin && typeof origin === 'object' && !(origin instanceof URL)) { + options = origin + origin = options.origin ?? null + } + + if (!origin) { + origin = location.origin + } + + if (String(origin).startsWith('blob:')) { + origin = new URL(origin).pathname + } + + this.#url = new URL(url, origin) + this.#loader = options?.loader ?? null + this.#status = new RequestStatus(this) + } + + /** + * The unique ID of this `Request`, which is the absolute URL as a string. + * @type {string} + */ + get id () { + return this.url.href + } + + /** + * The absolute `URL` of this `Request` object. + * @type {URL} + */ + get url () { + return this.#url + } + + /** + * The origin for this `Request`. + * @type {string} + */ + get origin () { + return this.url.origin + } + + /** + * The `Loader` for this `Request` object. + * @type {Loader?} + */ + get loader () { + return this.#loader + } + + /** + * The `RequestStatus` for this `Request` + * @type {RequestStatus} + */ + get status () { + return this.#status.load() + } + + /** + * Loads the CommonJS source file, optionally checking the `Loader` cache + * first, unless ignored when `options.cache` is `false`. + * @param {RequestLoadOptions=} [options] + * @return {Response} + */ + load (options = null) { + // check loader cache first + if (options?.cache !== false && this.#loader !== null) { + if (this.#loader.cache.has(this.id)) { + return this.#loader.cache.get(this.id) + } + } + + if (this.status.value >= 400) { + return new Response(this, { + status: this.status.value + }) + } + + const request = new XMLHttpRequest() + request.open('GET', this.id, false) + request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') + + if (options?.headers && typeof options?.headers === 'object') { + const entries = typeof options.headers.entries === 'function' + ? options.headers.entries() + : Object.entries(options.headers) + + for (const entry of entries) { + request.setRequestHeader(entry[0], entry[1]) + } + } + + request.send(null) + + let responseText = null + + try { + // @ts-ignore + responseText = request.responseText // can throw `InvalidStateError` error + } catch { + responseText = request.response + } + + return new Response(this, { + headers: Headers.from(request), + status: request.status, + text: responseText + }) + } +} + +/** + * A container for a synchronous CommonJS request response for a local resource + * or over the network. + */ +export class Response { + #request = null + #headers = new Headers() + #status = 404 + #text = '' + + /** + * `Response` class constructor. + * @param {Request|ResponseOptions} request + * @param {ResponseOptions=} [options] + */ + constructor (request, options = null) { + options = { ...options } + + if (!typeof request === 'object' && !(request instanceof Request)) { + options = request + request = options.request + } + + if (!request || !(request instanceof Request)) { + throw new TypeError( + `Expecting 'request' to be a Request object. Received: ${request}` + ) + } + + this.#request = request + this.#headers = options.headers + this.#status = options.status || 404 + this.#text = options.text || '' + + if (request.loader) { + // cache request response in the loader + request.loader.cache.set(request.id, this) + } + } + + /** + * The unique ID of this `Response`, which is the absolute + * URL of the request as a string. + * @type {string} + */ + get id () { + return this.#request.id + } + + /** + * The `Request` object associated with this `Response` object. + * @type {Request} + */ + get request () { + return this.#request + } + + /** + * The `Loader` associated with this `Response` object. + * @type {Loader?} + */ + get loader () { + return this.request.loader + } + + /** + * The `Response` status code from the associated `Request` object. + * @type {number} + */ + get status () { + return this.#status + } + + /** + * The `Response` string from the associated `Request` + * @type {string} + */ + get text () { + return this.#text + } + + /** + * `true` if the response is considered OK, otherwise `false`. + * @type {boolean} + */ + get ok () { + return this.status >= 200 && this.status < 400 + } +} + +/** + * A container for loading CommonJS module sources + */ +export class Loader { + /** + * A request class used by `Loader` objects. + * @type {typeof Request} + */ + static Request = Request + + /** + * A response class used by `Loader` objects. + * @type {typeof Request} + */ + static Response = Response + + /** + * Resolves a given module URL to an absolute URL with an optional `origin`. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} + */ + static resolve (url, origin = null) { + if (!origin) { + origin = location.origin + } + + if (String(origin).startsWith('blob:')) { + origin = new URL(origin).pathname + } + + if (String(url).startsWith('blob:')) { + url = new URL(url).pathname + } + + return String(new URL(url, origin)) + } + + /** + * Default extensions for a loader. + * @type {Set} + */ + static defaultExtensions = new Set(['.js', '.json', '.mjs', '.cjs', '.jsx', '.ts', '.tsx']) + + #cache = null + #origin = null + #statuses = null + #extensions = Loader.defaultExtensions + + /** + * `Loader` class constructor. + * @param {string|URL|LoaderOptions} origin + * @param {LoaderOptions=} [options] + */ + constructor (origin, options = null) { + if (origin && typeof origin === 'object' && !(origin instanceof URL)) { + options = origin + origin = options.origin + } + + this.#cache = options?.cache instanceof Map ? options.cache : new Map() + this.#origin = Loader.resolve('.', origin) + this.#statuses = options?.statuses instanceof Map + ? options.statuses + : new Map() + + if (options?.extensions && typeof options.extensions === 'object') { + if (Array.isArray(options.extensions) || options instanceof Set) { + for (const value of options.extensions) { + const extension = !value.startsWith('.') ? `.${value}` : value + this.#extensions.add(extension.trim()) + } + } + } + } + + /** + * The internal cache for this `Loader` object. + * @type {Map} + */ + get cache () { + return this.#cache + } + + /** + * The internal statuses for this `Loader` object. + * @type {Map} + */ + get statuses () { + return this.#statuses + } + + /** + * A set of supported `Loader` extensions. + * @type {Set} + */ + get extensions () { + return this.#extensions + } + + /** + * The origin of this `Loader` object. + * @type {string} + */ + get origin () { return this.#origin } + set origin (origin) { + this.#origin = Loader.resolve(origin, location.origin) + } + + /** + * Loads a CommonJS module source file at `url` with an optional `origin`, which + * defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {Response} + */ + load (url, origin, options) { + if (origin && typeof origin === 'object' && !(origin instanceof URL)) { + options = origin + origin = this.origin + } + + if (!origin) { + origin = this.origin + } + + const request = new Request(url, { + loader: this, + origin + }) + + return request.load(options) + } + + /** + * Queries the status of a CommonJS module source file at `url` with an + * optional `origin`, which defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {RequestStatus} + */ + status (url, origin, options) { + if (origin && typeof origin === 'object' && !(origin instanceof URL)) { + options = origin + origin = this.origin + } + + if (!origin) { + origin = this.origin + } + + url = this.resolve(url, origin) + + if (this.#statuses.has(url)) { + return this.statuses.get(url) + } + + const request = new Request(url, { + loader: this, + origin + }) + + this.#statuses.set(url, request.status) + return request.status + } + + /** + * Resolves a given module URL to an absolute URL based on the loader origin. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} + */ + resolve (url, origin) { + return Loader.resolve(url, origin || this.origin) + } +} + +export default Loader diff --git a/api/commonjs/module.js b/api/commonjs/module.js new file mode 100644 index 0000000000..c711423124 --- /dev/null +++ b/api/commonjs/module.js @@ -0,0 +1,670 @@ +/* global ErrorEvent */ +/* eslint-disable no-void, no-sequences */ +import { globalPaths, createRequire as createRequireImplementation } from './require.js' +import { DEFAULT_PACKAGE_PREFIX, Package } from './package.js' +import application from '../application.js' +import { Loader } from './loader.js' +import location from '../location.js' +import builtins from './builtins.js' +import process from '../process.js' +import path from '../path.js' + +/** + * @typedef {import('./require.js').RequireResolver[]} ModuleResolver + */ + +/** + * @typedef {import('./require.js').RequireFunction} RequireFunction + */ + +/** + * @typedef {import('./package.js').PackageOptions} PackageOptions + */ + +/** + * @typedef {{ + * prefix?: string, + * request?: import('./loader.js').RequestOptions, + * builtins?: object + * } CreateRequireOptions + */ + +/** + * @typedef {{ + * resolvers?: ModuleResolver[], + * importmap?: ImportMap, + * loader?: Loader | object, + * package?: Package | PackageOptions + * parent?: Module, + * state?: State + * }} ModuleOptions + */ + +export const builtinModules = builtins + +/** + * CommonJS module scope with module scoped globals. + * @ignore + * @param {object} exports + * @param {function(string): any} require + * @param {Module} module + * @param {string} __filename + * @param {string} __dirname + */ +export function CommonJSModuleScope ( + exports, + require, + module, + __filename, + __dirname +) { + // eslint-disable-next-line no-unused-vars + const process = require('socket:process') + // eslint-disable-next-line no-unused-vars + const console = require('socket:console') + // eslint-disable-next-line no-unused-vars + const crypto = require('socket:crypto') + // eslint-disable-next-line no-unused-vars + const { Buffer } = require('socket:buffer') + // eslint-disable-next-line no-unused-vars + const global = new Proxy(globalThis, { + get (target, key, receiver) { + if (key === 'process') { + return process + } + + if (key === 'console') { + return console + } + + if (key === 'crypto') { + return crypto + } + + if (key === 'Buffer') { + return Buffer + } + + if (key === 'global') { + return global + } + + return Reflect.get(target, key, receiver) + } + }) + + // eslint-disable-next-line no-unused-expressions + void exports, require, module, __filename, __dirname + // eslint-disable-next-line no-unused-expressions + void process, console, global, crypto, Buffer + + return (async function () { + 'module code' + })() +} + +/** + * CommonJS module scope source wrapper. + * @type {string} + */ +export const COMMONJS_WRAPPER = CommonJSModuleScope + .toString() + .split(/'module code'/) + +/** + * A container for imports. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap} + */ +export class ImportMap { + #imports = {} + + /** + * The imports object for the importmap. + * @type {object} + */ + get imports () { return this.#imports } + set imports (imports) { + if (imports && typeof imports === 'object' && !Array.isArray(imports)) { + this.#imports = {} + for (const key in imports) { + this.#imports[key] = imports[key] + } + } + } + + /** + * Extends the current imports object. + * @param {object} imports + * @return {ImportMap} + */ + extend (importmap) { + this.imports = { ...this.#imports, ...importmap?.imports ?? null } + return this + } +} + +/** + * A container for `Module` instance state. + */ +export class State { + loading = false + loaded = false + error = null + + /** + * `State` class constructor. + * @ignore + * @param {object|State=} [state] + */ + constructor (state = null) { + if (state && typeof state === 'object') { + for (const key in state) { + if (key in this && typeof state[key] === typeof this[key]) { + this[key] = state[key] + } + } + } + } +} + +/** + * The module scope for a loaded module. + * This is a special object that is seal, frozen, and only exposes an + * accessor the 'exports' field. + */ +export class Scope { + #exports = Object.create(null) + + /** + * `Scope` class constructor. + */ + constructor () { + Object.freeze(this) + } + + get exports () { + return this.#exports + } + + set exports (exports) { + this.#exports = exports + } + + toJSON () { + return { + exports: this.#exports + } + } +} + +/** + * A container for a loaded CommonJS module. All errors bubble + * to the "main" module and global object (if possible). + */ +export class Module extends EventTarget { + /** + * A reference to the currently scoped module. + * @type {Module?} + */ + static current = null + + /** + * A reference to the previously scoped module. + * @type {Module?} + */ + static previous = null + + /** + * A cache of loaded modules + * @type {Map} + */ + static cache = new Map() + + /** + * An array of globally available module loader resolvers. + * @type {ModuleResolver[]} + */ + static resolvers = [] + + /** + * Globally available 'importmap' for all loaded modules. + * @type {ImportMap} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap} + */ + static importmap = new ImportMap() + + /** + * A limited set of builtins exposed to CommonJS modules. + * @type {object} + */ + static builtins = builtins + + /** + * A limited set of builtins exposed to CommonJS modules. + * @type {object} + */ + static builtinModules = builtinModules + + /** + * CommonJS module scope source wrapper components. + * @type {string[]} + */ + static wrapper = COMMONJS_WRAPPER + + /** + * An array of global require paths, relative to the origin. + * @type {string[]} + */ + static globalPaths = globalPaths + + /** + * The main entry module, lazily created. + * @type {Module} + */ + static get main () { + if (this.cache.has(location.origin)) { + return this.cache.get(location.origin) + } + + const main = new Module(location.origin, { + state: new State({ loaded: true }), + parent: null, + package: new Package(location.origin, { + id: location.href, + type: 'commonjs', + info: application.config, + index: '', + prefix: '', + manifest: '', + + // eslint-disable-next-line + name: application.config.build_name, + // eslint-disable-next-line + version: application.config.meta_version, + // eslint-disable-next-line + description: application.config.meta_description, + exports: { + '.': { + default: location.pathname + } + } + }) + }) + + this.cache.set(location.origin, main) + + if (globalThis.window && globalThis === globalThis.window) { + try { + const importmapScriptElement = globalThis.document.querySelector('script[type=importmap]') + if (importmapScriptElement && importmapScriptElement.textContent) { + const importmap = JSON.parse(importmapScriptElement.textContent) + Module.importmap.extend(importmap) + } + } catch (err) { + globalThis.reportError(err) + } + } + + return main + } + + /** + * Wraps source in a CommonJS module scope. + * @param {string} source + */ + static wrap (source) { + const [head, tail] = this.wrapper + const body = String(source || '') + return [head, body, tail].join('\n') + } + + /** + * Creates a `Module` from source URL and optionally a parent module. + * @param {string|URL|Module} url + * @param {ModuleOptions=} [options] + */ + static from (url, options = null) { + if (typeof url === 'object' && url instanceof Module) { + return this.from(url.id, options) + } + + if (options instanceof Module) { + options = { parent: options } + } else { + options = { ...options } + } + + if (!options.parent) { + options.parent = Module.current + } + + url = Loader.resolve(url, options.parent?.id) + + if (this.cache.has(url)) { + return this.cache.get(url) + } + + const module = new Module(url, options) + return module + } + + /** + * Creates a `require` function from a given module URL. + * @param {string|URL} url + * @param {ModuleOptions=} [options] + */ + static createRequire (url, options = null) { + const module = this.from(url, { + package: { info: Module.main.package.info }, + ...options + }) + + return module.createRequire(options) + } + + #id = null + #scope = new Scope() + #state = new State() + #cache = Object.create(null) + #source = null + #loader = null + #parent = null + #package = null + #children = [] + #resolvers = [] + #importmap = new ImportMap() + + /** + * `Module` class constructor. + * @param {string|URL} url + * @param {ModuleOptions=} [options] + */ + constructor (url, options = null) { + super() + + options = { ...options } + + if (options.parent && options.parent instanceof Module) { + this.#parent = options.parent + } else if (options.parent === undefined) { + this.#parent = Module.main + } + + if (this.#parent !== null) { + this.#id = Loader.resolve(url, this.#parent.id) + this.#cache = this.#parent.cache + } else { + this.#id = Loader.resolve(url) + } + + if (options.state && options.state instanceof State) { + this.#state = options.state + } + + this.#loader = options?.loader instanceof Loader + ? options.loader + : new Loader(this.#id, options?.loader) + + this.#package = options.package instanceof Package + ? options.package + : this.#parent?.package ?? new Package(options.name ?? this.#id, options.package) + + this.addEventListener('error', (event) => { + if (Module.main === this) { + // bubble error to globalThis, if possible + if (typeof globalThis.dispatchEvent === 'function') { + // @ts-ignore + globalThis.dispatchEvent(new ErrorEvent('error', event)) + } + } else { + // bubble errors to main module + Module.main.dispatchEvent(new ErrorEvent('error', event)) + } + }) + + this.#importmap.extend(Module.importmap) + + if (options.importmap) { + this.#importmap.extend(options.importmap) + } + + this.#resolvers = Array.from(Module.resolvers) + + if (Array.isArray(options.resolvers)) { + for (const resolver of options.resolvers) { + if (typeof resolver === 'function') { + this.#resolvers.push(resolver) + } + } + } + + if (this.#parent) { + if (Array.isArray(options?.resolvers)) { + for (const resolver of options.resolvers) { + if (typeof resolver === 'function') { + this.#resolvers.push(resolver) + } + } + } + + this.#importmap.extend(this.#parent.importmap) + } + + Module.cache.set(this.id, this) + } + + /** + * A unique ID for this module. + * @type {string} + */ + get id () { + return this.#id + } + + /** + * A reference to the "main" module. + * @type {Module} + */ + get main () { + return Module.main + } + + /** + * Child modules of this module. + * @type {Module[]} + */ + get children () { + return this.#children + } + + /** + * A reference to the module cache. Possibly shared with all + * children modules. + * @type {object} + */ + get cache () { + return this.#cache + } + + /** + * A reference to the module package. + * @type {Package} + */ + get package () { + return this.#package + } + + /** + * The `ImportMap` for this module. + * @type {ImportMap} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap} + */ + get importmap () { + return this.#importmap + } + + /** + * The module level resolvers. + * @type {ModuleResolver[]} + */ + get resolvers () { + return this.#resolvers + } + + /** + * `true` if the module is currently loading, otherwise `false`. + * @type {boolean} + */ + get loading () { + return this.#state.loading + } + + /** + * `true` if the module is currently loaded, otherwise `false`. + * @type {boolean} + */ + get loaded () { + return this.#state.loaded + } + + /** + * An error associated with the module if it failed to load. + * @type {Error?} + */ + get error () { + return this.#state.error + } + + /** + * The exports of the module + * @type {object} + */ + get exports () { + return this.#scope.exports + } + + /** + * The scope of the module given to parsed modules. + * @type {Scope} + */ + get scope () { + return this.#scope + } + + /** + * The origin of the loaded module. + * @type {string} + */ + get origin () { + return this.#loader.origin + } + + /** + * The parent module for this module. + * @type {Module?} + */ + get parent () { + return this.#parent + } + + /** + * The `Loader` for this module. + * @type {Loader} + */ + get loader () { + return this.#loader + } + + /** + * Factory for creating a `require()` function based on a module context. + * @param {CreateRequireOptions=} [options] + * @return {RequireFunction} + */ + createRequire (options = null) { + return createRequireImplementation({ + builtins, + // TODO(@jwerle): make the 'prefix' value configurable somehow + prefix: DEFAULT_PACKAGE_PREFIX, + ...options, + module: this + }) + } + + /** + * Creates a `Module` from source the URL with this module as + * the parent. + * @param {string|URL|Module} url + * @param {ModuleOptions=} [options] + */ + createModule (url, options = null) { + return Module.from(url, { + parent: this, + ...options + }) + } + + /** + * @param {object=} [options] + * @return {boolean} + */ + load (options = null) { + this.#source = Module.wrap(this.loader.load(this.id).text) + // eslint-disable-next-line + const define = new Function(`return ${this.#source}`)() + const __filename = this.id + const __dirname = path.dirname(__filename) + + try { + this.#state.loading = true + // eslint-disable-next-line no-useless-call + const promise = define.call(null, + this.scope.exports, + this.createRequire(options), + this.scope, + __filename, + __dirname, + process, + globalThis + ) + + promise.catch((error) => { + error.module = this + this.dispatchEvent(new ErrorEvent('error', { error })) + }) + + if (this.parent) { + this.parent.children.push(this) + } + + this.#state.loaded = true + } catch (error) { + error.module = this + this.#state.error = error + throw error + } finally { + this.#state.loading = false + } + + return this.#state.loaded + } + + resolve (input) { + return this.package.resolve(input, { load: false, origin: this.id }) + } + + /** + * @ignore + */ + [Symbol.toStringTag] () { + return 'Module' + } +} + +export function createRequire (url, options = null) { + return Module.createRequire(url, options) +} + +builtins.module = Module +builtins.module.Module = Module + +export default Module diff --git a/api/commonjs/package.js b/api/commonjs/package.js new file mode 100644 index 0000000000..44dda4cbed --- /dev/null +++ b/api/commonjs/package.js @@ -0,0 +1,501 @@ +import { ModuleNotFoundError } from '../errors.js' +import { Loader } from './loader.js' +import location from '../location.js' +import path from '../path.js' + +/** + * @typedef {{ + * prefix?: string, + * manifest?: string, + * index?: string, + * description?: string, + * version?: string, + * license?: string, + * exports?: object, + * type?: 'commonjs' | 'module', + * info?: object + * }} PackageOptions + */ + +/** + * @typedef {import('./loader.js').RequestOptions & { + * type?: 'commonjs' | 'module' + * }} PackageLoadOptions + */ + +/** + * {import('./loader.js').RequestOptions & { + * load?: boolean, + * type?: 'commonjs' | 'module', + * browser?: boolean, + * children?: string[] + * extensions?: string[] | Set + * }} PackageResolveOptions + */ + +/** + * The default package index file such as 'index.js' + * @type {string} + */ +export const DEFAULT_PACKAGE_INDEX = 'index.js' + +/** + * The default package manifest file name such as 'package.json' + * @type {string} + */ +export const DEFAULT_PACKAGE_MANIFEST_FILE_NAME = 'package.json' + +/** + * The default package path prefix such as 'node_modules/' + * @type {string} + */ +export const DEFAULT_PACKAGE_PREFIX = 'node_modules/' + +/** + * The default package version, when one is not provided + * @type {string} + */ +export const DEFAULT_PACKAGE_VERSION = '0.0.1' + +/** + * The default license for a package' + * @type {string} + */ +export const DEFAULT_LICENSE = 'Unlicensed' + +/** + * A container for CommonJS module metadata, often in a `package.json` file. + */ +export class Package { + /** + * @param {string} input + * @param {PackageOptions&PackageLoadOptions} [options] + * @return {Package?} + */ + static find (input, options = null) { + const url = URL.canParse(input) + ? new URL(input) + : new URL(input, options?.origin ?? location.origin) + + const components = url.pathname.split('/') + + if (path.extname(url.pathname)) { + components.pop() + } + + while (components.length) { + const name = components.join('/') + + if (!name) { + return null + } + + const manifest = new Package(name, { + ...options, + prefix: '', + loader: new Loader( + new URL(components.join('/'), url.origin), + options?.loader + ) + }) + + if (path.extname(manifest.name)) { + continue + } + + if (manifest.load(options)) { + return manifest + } + + components.pop() + } + + return null + } + + #info = null + #index = null + #prefix = null + #manifest = null + #loader = null + + #id = null + #name = null + #type = 'commonjs' + #exports = {} + #license = null + #version = null + #description = null + + /** + * `Package` class constructor. + * @param {string} name + * @param {PackageOptions} [options] + */ + constructor (name, options = null) { + options = { ...options } + + if (!name || typeof name !== 'string') { + throw new TypeError(`Expecting 'name' to be a string. Received: ${name}`) + } + + // early meta data + this.#id = options.id ?? null + this.#name = name + this.#info = options.info ?? null + this.#exports = options.exports ?? {} + this.#license = options.license ?? DEFAULT_LICENSE + this.#version = options.version ?? DEFAULT_PACKAGE_VERSION + this.#description = options.description ?? '' + + // request paths + this.#index = options.index ?? DEFAULT_PACKAGE_INDEX + this.#prefix = options.prefix ?? DEFAULT_PACKAGE_PREFIX + this.#manifest = options.manifest ?? DEFAULT_PACKAGE_MANIFEST_FILE_NAME + + // the module loader + this.#loader = new Loader(options.loader) + } + + /** + * The unique ID of this `Package`, which is the absolute + * URL of the directory that contains its manifest file. + * @type {string} + */ + get id () { + return this.#id + } + + /** + * The absolute URL to the package manifest file + * @type {string} + */ + get url () { + return new URL(this.#manifest, this.#id).href + } + + /** + * The package module path prefix. + * @type {string} + */ + get prefix () { + return this.#prefix + } + + /** + * A loader for this package, if available. This value may be `null`. + * @type {Loader} + */ + get loader () { + return this.#loader + } + + /** + * The name of the package. + * @type {string} + */ + get name () { + return this.#name ?? '' + } + + /** + * The description of the package. + * @type {string} + */ + get description () { + return this.#description ?? '' + } + + /** + * The license of the package. + * @type {string} + */ + get license () { + return this.#license ?? DEFAULT_LICENSE + } + + /** + * The version of the package. + * @type {string} + */ + get version () { + return this.#version ?? DEFAULT_PACKAGE_VERSION + } + + /** + * The origin for this package. + * @type {string} + */ + get origin () { + return new URL(this.#prefix, this.loader.origin).href + } + + /** + * The exports mappings for the package + * @type {object} + */ + get exports () { + return this.#exports + } + + /** + * The package type. + * @type {'commonjs'|'module'} + */ + get type () { + return this.#type + } + + /** + * The raw package metadata object. + * @type {object?} + */ + get info () { + return this.#info ?? null + } + + /** + * The entry to the package + * @type {string?} + */ + get entry () { + let entry = null + if (this.type === 'commonjs') { + entry = this.#exports['.'].require + } else if (this.type === 'module') { + entry = this.#exports['.'].import + } else if (this.#exports['.'].default) { + entry = this.#exports['.'].default + } + + if (entry) { + if (!entry.startsWith('./')) { + entry = `./${entry}` + } else if (!entry.startsWith('.')) { + entry = `.${entry}` + } + + return new URL(entry, this.id).href + } + + return null + } + + /** + * Load the package information at an optional `origin` with + * optional request `options`. + * @param {string|PackageLoadOptions=} [origin] + * @param {PackageLoadOptions=} [options] + * @throws SyntaxError + * @return {boolean} + */ + load (origin, options) { + if (this.#info) { + return true + } + + if (origin && typeof origin === 'object' && !(origin instanceof URL)) { + options = origin + origin = options.origin + } + + if (!origin) { + origin = this.origin.replace(this.#prefix, '') + } + + const pathname = `${this.name}/${this.#manifest}` + const response = this.loader.load(pathname, origin, options) + + if (!response.text) { + return false + } + + const info = JSON.parse(response.text) + const type = options?.type ?? info.type ?? this.type + + this.#info = info + this.#exports['.'] = {} + + this.#id = new URL('./', response.id).href + this.#name = info.name + this.#type = info.type ?? this.#type + this.#license = info.license ?? 'Unlicensed' + this.#version = info.version + this.#description = info.description + + if (info.main) { + if (type === 'commonjs') { + this.#exports['.'].require = info.main + } else if (type === 'module') { + this.#exports['.'].import = info.main + } + } + + if (info.module) { + this.#exports['.'].import = info.module + } + + if (typeof info.exports === 'string') { + if (type === 'commonjs') { + this.#exports['.'].require = info.exports + } else if (type === 'module') { + this.#exports['.'].import = info.exports + } + } + + if (info.exports && typeof info.exports === 'object') { + for (const key in info.exports) { + const exports = info.exports[key] + if (!exports) { + continue + } + + if (typeof exports === 'string') { + this.#exports[key] = {} + if (this.#type === 'commonjs') { + this.#exports[key].require = info.exports + } else if (this.#type === 'module') { + this.#exports[key].import = info.exports + } + } else if (typeof exports === 'object') { + this.#exports[key] = exports + } + } + } + + return true + } + + /** + * Resolve a file's `pathname` within the package. + * @param {string|URL} pathname + * @param {PackageResolveOptions=} [options] + * @return {string} + */ + resolve (pathname, options = null) { + if (options?.load !== false && !this.load(options)) { + throw new ModuleNotFoundError( + `Cannnot find module '${pathname}`, + options?.children?.map?.((mod) => mod.id) + ) + } + + const { info } = this + const origin = options?.origin || this.id || this.origin + const type = options?.type ?? this.type + + if (pathname instanceof URL) { + pathname = pathname.pathname + } + + if (pathname === '.') { + pathname = './' + } else if (pathname.startsWith('/')) { + pathname = `.${pathname}` + } else if (!pathname.startsWith('./')) { + pathname = `./${pathname}` + } + + if (pathname.endsWith('/')) { + pathname += 'index.js' + } + + const extname = path.extname(pathname) + const extensions = extname !== '' + ? new Set([extname]) + : new Set(Array + .from(options?.extensions ?? []) + .concat(Array.from(this.loader.extensions)) + .filter((e) => typeof e === 'string' && e.length > 0) + ) + + for (const extension of extensions) { + for (const key in this.#exports) { + const exports = this.#exports[key] + const query = pathname !== '.' && pathname !== './' + ? pathname + extension + : pathname + + if ((pathname === './' && key === '.') || key === query) { + if (Array.isArray(exports)) { + for (const filename of exports) { + if (typeof filename === 'string') { + const response = this.loader.load(filename, origin, options) + if (response.ok) { + return interpolateBrowserResolution(response.id) + } + } + } + } else { + let filename = null + if (type === 'commonjs' && exports.require) { + filename = exports.require + } else if (type === 'module' && exports.import) { + filename = exports.import + } else if (exports.default) { + filename = exports.default + } else { + filename = exports.require || exports.import || exports.default + } + + if (filename) { + const response = this.loader.load(filename, origin, options) + if (response.ok) { + return interpolateBrowserResolution(response.id) + } + } + } + } + } + + if (!extname) { + let response = null + response = this.loader.load(pathname + extension, origin, options) + + if (response.ok) { + return interpolateBrowserResolution(response.id) + } + + response = this.loader.load(`${pathname}/index${extension}`, origin, options) + if (response.ok) { + return interpolateBrowserResolution(response.id) + } + } + } + + const response = this.loader.load(pathname, origin, options) + + if (response.ok) { + return interpolateBrowserResolution(response.id) + } + + throw new ModuleNotFoundError( + `Cannnot find module '${pathname}`, + options?.children?.map?.((mod) => mod.id) + ) + + function interpolateBrowserResolution (id) { + if (options?.browser === false) { + return id + } + + const url = new URL(id) + const prefix = new URL('.', origin).href + const pathname = `./${url.href.replace(prefix, '')}` + + if (info.browser && typeof info.browser === 'object') { + for (const key in info.browser) { + const value = info.browser[key] + const filename = !key.startsWith('./') ? `./${key}` : key + if (filename === pathname) { + return new URL(value, prefix).href + } + } + } + + return id + } + } +} + +export default Package diff --git a/api/commonjs/require.js b/api/commonjs/require.js new file mode 100644 index 0000000000..76c513251f --- /dev/null +++ b/api/commonjs/require.js @@ -0,0 +1,208 @@ +import { DEFAULT_PACKAGE_PREFIX, Package } from './package.js' +import { getBuiltin, isBuiltin } from './builtins.js' +import { ModuleNotFoundError } from '../errors.js' +import { Loader } from './loader.js' +import location from '../location.js' + +/** + * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver + */ + +/** + * @typedef {{ + * module: import('./module.js').Module, + * prefix?: string, + * request?: import('./loader.js').RequestOptions, + * builtins?: object + * }} CreateRequireOptions + */ + +/** + * @typedef {function(string): any} RequireFunction + */ + +/** + * @typedef {import('./package.js').PackageOptions} PackageOptions + */ + +/** + * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions + */ + +/** + * @typedef {PackageResolveOptions & PackageOptions} ResolveOptions + */ + +/** + * @typedef {ResolveOptions & { + * resolvers?: RequireResolver[], + * importmap?: import('./module.js').ImportMap, + * }} RequireOptions + */ + +/** + * An array of global require paths, relative to the origin. + * @type {string[]} + */ +export const globalPaths = [ + new URL(DEFAULT_PACKAGE_PREFIX, location.origin).href +] + +/** + * Factory for creating a `require()` function based on a module context. + * @param {CreateRequireOptions} options + * @return {RequireFunction} + */ +export function createRequire (options) { + const { builtins, module } = options + const { cache, main } = module + + Object.assign(resolve, { + paths + }) + + return Object.assign(require, { + resolve, + cache, + main + }) + + /** + * Requires a module at for a given `input` which can be a relative file, + * named module, or an absolute URL. + * @param {string|URL} athname + * @param {RequireOptions=} [options] + * @return {string} + */ + function require (input, options = null) { + if (isBuiltin(input, { builtins: options?.builtins ?? builtins })) { + return getBuiltin(input, { builtins: options?.builtins ?? builtins }) + } + + const resolved = resolve(input, { + type: module.package.type, + ...options + }) + + if (cache[input]) { + return cache[input].exports + } + + if (!resolved) { + throw new ModuleNotFoundError( + `Cannnot find module '${input}`, + module.children.map((mod) => mod.id) + ) + } + + const child = module.createModule(resolved, { + ...options, + package: input.startsWith('.') || input.startsWith('/') + ? module.package + : Package.find(resolved, { + loader: new Loader(module.loader), + ...options + }) + }) + + if (child.load(options)) { + cache[resolved] = child + return child.exports + } + } + + /** + * Resolve a module `input` to an absolute URL. + * @param {string|URL} pathname + * @param {ResolveOptions=} [options] + * @return {string} + */ + function resolve (input, options = null) { + if (isBuiltin(input, builtins)) { + return input + } + + const url = URL.canParse(input) + ? new URL(input) + : null + + const origins = url !== null + ? [url.origin] + : resolve.paths(input) + + if (url !== null) { + input = `.${url.pathname + url.search}` + } + + for (const origin of origins) { + if (url) { + const status = module.loader.status(input, origin, options) + + if (status.ok) { + return status.id + } + } else if (input.startsWith('.') || input.startsWith('/')) { + return module.resolve(input) + } else { + const components = input.split('/') + const manifest = new Package(components.shift(), { + prefix: '', + loader: new Loader(origin, module.loader) + }) + + const loaded = manifest.load({ + type: module.package.type, + ...options + }) + + if (loaded) { + const pathname = components.join('/') || '.' + return manifest.resolve(pathname, { + type: module.package.type, + ...options + }) + } + } + } + } + + /** + * Computes possible `require()` origin paths for an input module URL + * @param {string|URL} pathname + * @return {string[]?} + */ + function paths (input) { + if (isBuiltin(input, builtins)) { + return null + } + + if (URL.canParse(input)) { + return [new URL(input).origin] + } + + if (input.startsWith('.') || input.startsWith('/')) { + return [module.origin] + } + + const origins = new Set(globalPaths.map((path) => new URL(path, location.origin).href)) + let origin = module.origin + + while (true) { + const url = new URL(origin) + origins.add(origin) + + if (url.pathname === '/') { + break + } + + origin = new URL('..', origin).href + } + + return Array + .from(origins) + .map((origin) => new URL(options.prefix, origin)) + .map((url) => url.href) + } +} + +export default createRequire From 8beaaa9eaed391144a5d28197daf07e0991051ce Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:23:35 -0400 Subject: [PATCH 0415/1178] refactor(api/module.js): use new commonjs core --- api/module.js | 964 +------------------------------------------------- 1 file changed, 9 insertions(+), 955 deletions(-) diff --git a/api/module.js b/api/module.js index 7d6621f880..b9ae591d4f 100644 --- a/api/module.js +++ b/api/module.js @@ -1,963 +1,17 @@ -/* global XMLHttpRequest */ -/* eslint-disable import/no-duplicates, import/first, no-void, no-sequences */ /** - * @module module - * A module for loading CommonJS modules with a `require` function in an - * ESM context. + * @module Module */ -import { ModuleNotFoundError } from './errors.js' -import { ErrorEvent, Event } from './events.js' -import { Headers } from './ipc.js' -import { URL } from './url.js' +import builtins, { isBuiltin } from './commonjs/builtins.js' +import { createRequire } from './commonjs/require.js' +import { Module } from './commonjs/module.js' -// builtins -import { - AsyncLocalStorage, - AsyncResource, - executionAsyncResource, - executionAsyncId, - triggerAsyncId, - createHook, - AsyncHook -} from './async.js' - -// eslint-disable-next-line -import _async from './async.js' - -// eslint-disable-next-line -import application from './application.js' -import assert from './assert.js' -import * as buffer from './buffer.js' -// eslint-disable-next-line -import child_process from './child_process.js' -import console from './console.js' -import constants from './constants.js' -import crypto from './crypto.js' -import dgram from './dgram.js' -import dns from './dns.js' -import events from './events.js' -import extension from './extension.js' -import fs from './fs.js' -import gc from './gc.js' -import http from './http.js' -import https from './https.js' -import ipc from './ipc.js' -import language from './language.js' -import location from './location.js' -import mime from './mime.js' -import network from './network.js' -import os from './os.js' -import { posix as path } from './path.js' -import process from './process.js' -import querystring from './querystring.js' -import stream from './stream.js' -// eslint-disable-next-line -import string_decoder from './string_decoder.js' -import test from './test.js' -import timers from './timers.js' -import url from './url.js' -import util from './util.js' -import vm from './vm.js' -import window from './window.js' -// eslint-disable-next-line -import worker_threads from './worker_threads.js' - -/** - * @typedef {function(string, Module, function): undefined} ModuleResolver - */ - -const cache = new Map() - -function maybeParseJSON (string) { - try { - return JSON.parse(string) - } catch { - return null - } -} - -class ModuleRequest { - id = null - url = null - - static load (pathname, parent) { - const request = new this(pathname, parent) - return request.load() - } - - static verifyRequestContentLocation (request) { - const headers = Headers.from(request) - const contentLocation = headers.get('content-location') - // if a content location was given, check extension name - if (contentLocation && URL.canParse(contentLocation, globalThis.location.href)) { - const url = new URL(contentLocation, globalThis.location.href) - if (!/js|json|mjs|cjs|jsx|ts|tsx/.test(path.extname(url.pathname))) { - return false - } - } - - return true - } - - constructor (pathname, parent) { - const origin = globalThis.location.origin.startsWith('blob:') - ? new URL(new URL(globalThis.location.href).pathname).origin - : globalThis.location.origin - - this.url = new URL(pathname, parent || origin || '/') - this.id = this.url.toString() - } - - status () { - const { id } = this - const request = new XMLHttpRequest() - request.open('HEAD', id, false) - request.send(null) - - if (!ModuleRequest.verifyRequestContentLocation(request)) { - return 404 - } - - return request.status - } - - load () { - const { id } = this - - if (cache.has(id)) { - return cache.get(id) - } - - if (this.status() >= 400) { - return new ModuleResponse(this) - } - - const request = new XMLHttpRequest() - - request.open('GET', id, false) - request.send(null) - - if (!ModuleRequest.verifyRequestContentLocation(request)) { - return null - } - - const headers = Headers.from(request) - let responseText = null - - try { - // @ts-ignore - responseText = request.responseText // can throw `InvalidStateError` error - } catch { - responseText = request.response - } - - const response = new ModuleResponse(this, headers, responseText) - - if (request.status < 400) { - cache.set(id, response) - } - - return response - } -} - -class ModuleResponse { - request = null - headers = null - data = null - - constructor (request, headers, data) { - this.request = request - this.headers = headers ?? null - this.data = data ?? null - } -} - -// sync request for files -function request (pathname, parent) { - return ModuleRequest.load(pathname, parent) +export { + Module, + builtins, + isBuiltin, + createRequire } -/** - * CommonJS module scope with module scoped globals. - * @ignore - */ -function CommonJSModuleScope ( - exports, - require, - module, - __filename, - __dirname -) { - // eslint-disable-next-line no-unused-vars - const process = require('socket:process') - // eslint-disable-next-line no-unused-vars - const console = require('socket:console') - // eslint-disable-next-line no-unused-vars - const crypto = require('socket:crypto') - // eslint-disable-next-line no-unused-vars - const { Buffer } = require('socket:buffer') - // eslint-disable-next-line no-unused-vars - const global = new Proxy(globalThis, { - get (target, key, receiver) { - if (key === 'process') { - return process - } - - if (key === 'console') { - return console - } - - if (key === 'crypto') { - return crypto - } - - if (key === 'Buffer') { - return Buffer - } - - if (key === 'global') { - return global - } - - if (key === 'module') { - return Module.main - } - - if (key === 'exports') { - return Module.main.exports - } - - if (key === 'require') { - return Module.main.require - } - - return Reflect.get(target, key, receiver) - } - }) - - // eslint-disable-next-line no-unused-expressions - void exports, require, module, __filename, __dirname - // eslint-disable-next-line no-unused-expressions - void process, console, global, crypto, Buffer - - return (async function () { - 'module code' - })() -} - -/** - * A limited set of builtins exposed to CommonJS modules. - */ -export const builtins = { - // eslint-disable-next-line - 'async': _async, - // eslint-disable-next-line - async_context: { - AsyncLocalStorage, - AsyncResource - }, - // eslint-disable-next-line - async_hooks: { - AsyncLocalStorage, - AsyncResource, - executionAsyncResource, - executionAsyncId, - triggerAsyncId, - createHook, - AsyncHook - }, - application, - assert, - buffer, - console, - constants, - // eslint-disable-next-line - child_process, - crypto, - dgram, - dns, - 'dns/promises': dns.promises, - events, - extension, - fs, - 'fs/promises': fs.promises, - http, - https, - gc, - ipc, - language, - location, - mime, - net: {}, - network, - os, - path, - // eslint-disable-next-line - perf_hooks: { - performance: globalThis.performance - }, - process, - querystring, - stream, - 'stream/web': stream.web, - // eslint-disable-next-line - string_decoder, - sys: util, - test, - timers, - 'timers/promises': timers.promises, - tty: { - isatty: () => false, - WriteStream: util.IllegalConstructor, - ReadStream: util.IllegalConstructor - }, - util, - url, - vm, - window, - // eslint-disable-next-line - worker_threads -} - -const socketRuntimeModules = [ - 'async', - 'application', - 'extension', - 'gc', - 'ipc', - 'language', - 'location', - 'mime', - 'network', - 'window' -] - -// alias export const builtinModules = builtins -export function isBuiltin (name) { - const originalName = name - name = name.replace(/^(socket|node):/, '') - - if ( - socketRuntimeModules.includes(name) && - !originalName.startsWith('socket:') - ) { - return false - } - - if (name in builtins) { - return true - } - - return false -} - -/** - * CommonJS module scope source wrapper. - * @type {string} - */ -export const COMMONJS_WRAPPER = CommonJSModuleScope - .toString() - .split(/'module code'/) - -/** - * The main entry source origin. - * @type {string} - */ -export const MAIN_SOURCE_ORIGIN = globalThis.location.href.startsWith('blob:') - ? new URL(new URL(globalThis.location.href).pathname).href - : globalThis.location.href - -/** - * Creates a `require` function from a source URL. - * @param {URL|string} sourcePath - * @return {function} - */ -export function createRequire (sourcePath) { - return Module.createRequire(sourcePath) -} - -export const scope = { - current: null, - previous: null -} - -/** - * A container for a loaded CommonJS module. All errors bubble - * to the "main" module and global object (if possible). - */ -export class Module extends EventTarget { - /** - * A reference to the currently scoped module. - * @type {Module?} - */ - static get current () { return scope.current } - static set current (module) { scope.current = module } - - /** - * A reference to the previously scoped module. - * @type {Module?} - */ - static get previous () { return scope.previous } - static set previous (module) { scope.previous = module } - - /** - * Module cache. - * @ignore - */ - static cache = Object.create(null) - - /** - * Custom module resolvers. - * @type {Array} - */ - static resolvers = [] - - /** - * CommonJS module scope source wrapper. - * @ignore - */ - static wrapper = COMMONJS_WRAPPER - - /** - * A limited set of builtins exposed to CommonJS modules. - * @type {object} - */ - static builtins = builtins - - /** - * Creates a `require` function from a source URL. - * @param {URL|string} sourcePath - * @return {function} - */ - static createRequire (sourcePath) { - if (!sourcePath) { - return Module.main.createRequire() - } - - return Module.from(sourcePath).createRequire() - } - - /** - * The main entry module, lazily created. - * @type {Module} - */ - static get main () { - if (MAIN_SOURCE_ORIGIN in this.cache) { - return this.cache[MAIN_SOURCE_ORIGIN] - } - - const main = this.cache[MAIN_SOURCE_ORIGIN] = new Module(MAIN_SOURCE_ORIGIN) - main.filename = main.id - main.loaded = true - return main - } - - /** - * Wraps source in a CommonJS module scope. - */ - static wrap (source) { - const [head, tail] = this.wrapper - const body = String(source || '') - return [head, body, tail].join('\n') - } - - /** - * Creates a `Module` from source URL and optionally a parent module. - * @param {string|URL|Module} [sourcePath] - * @param {string|URL|Module} [parent] - */ - static from (sourcePath, parent = null) { - if (!sourcePath) { - return Module.main - } else if (sourcePath?.id) { - return this.from(sourcePath.id, parent) - } else if (sourcePath instanceof URL) { - return this.from(String(sourcePath), parent) - } - - if (!parent) { - parent = Module.current - } - - const id = String(parent - ? new URL(sourcePath, parent.id) - : new URL(sourcePath, MAIN_SOURCE_ORIGIN) - ) - - if (id in this.cache) { - return this.cache[id] - } - - const module = new Module(id, parent, sourcePath) - this.cache[module.id] = module - return module - } - - /** - * The module id, most likely a file name. - * @type {string} - */ - id = '' - - /** - * The parent module, if given. - * @type {Module?} - */ - parent = null - - /** - * `true` if the module did load successfully. - * @type {boolean} - */ - loaded = false - - /** - * `true` if the module is currently being loaded. - * @type {boolean} - */ - loading = false - - /** - * The module's exports. - * @type {any} - */ - exports = {} - - /** - * The filename of the module. - * @type {string} - */ - filename = '' - - /** - * Modules children to this one, as in they were required in this - * module scope context. - * @type {Array} - */ - children = [] - - /** - * The original source URL to load this module. - * @type {string} - */ - sourcePath = '' - - /** - * `Module` class constructor. - * @ignore - */ - constructor (id, parent = null, sourcePath = null) { - super() - - this.id = new URL(id || '', parent?.id || MAIN_SOURCE_ORIGIN).toString() - this.parent = parent || null - this.sourcePath = sourcePath || id - - if (!scope.previous && id !== MAIN_SOURCE_ORIGIN) { - scope.previous = Module.main - } - - if (!scope.current) { - scope.current = this - } - - this.addEventListener('error', (event) => { - if (this.isMain) { - // bubble error to globalThis, if possible - if (typeof globalThis.dispatchEvent === 'function') { - // @ts-ignore - globalThis.dispatchEvent(new ErrorEvent('error', event)) - } - } else { - // bubble errors to main module - Module.main.dispatchEvent(new ErrorEvent('error', event)) - } - }) - } - - /** - * `true` if the module is the main module. - * @type {boolean} - */ - get isMain () { - return this.id === MAIN_SOURCE_ORIGIN - } - - /** - * `true` if the module was loaded by name, not file path. - * @type {boolean} - */ - get isNamed () { - return !this.isMain && !this.sourcePath?.startsWith('.') && !this.sourcePath?.startsWith('/') - } - - /** - * @type {URL} - */ - get url () { - return new URL(this.id) - } - - /** - * @type {string} - */ - get pathname () { - return new URL(this.id).pathname - } - - /** - * @type {string} - */ - get path () { - return path.dirname(this.pathname) - } - - /** - * Loads the module, synchronously returning `true` upon success, - * otherwise `false`. - * @return {boolean} - */ - load () { - const { isNamed, sourcePath } = this - const prefixes = (process.env.SOCKET_MODULE_PATH_PREFIX || 'node_modules').split(':') - const urls = [] - - if (this.loaded || this.loading) { - return true - } - - this.loading = true - Module.previous = Module.current - Module.current = this - - // if module is named, and `prefixes` contains entries then - // build possible search paths of `/` starting - // from `.` up until `/` of the origin - if (isNamed) { - const name = sourcePath - for (const prefix of prefixes) { - let current = new URL('./', this.id).toString() - - do { - const prefixURL = new URL(prefix, current) - urls.push(new URL(name, prefixURL + '/').toString()) - current = new URL('..', current).toString() - } while (new URL(current).pathname !== '/') - - const prefixURL = new URL(prefix, current) - urls.push(new URL(name, prefixURL + '/').toString()) - } - } else { - urls.push(this.id) - } - - for (const url of urls) { - if (loadPackage(this, url)) { - break - } - } - - if (this.loaded) { - Module.previous = this - Module.current = null - } - - this.loading = false - return this.loaded - - function loadPackage (module, url) { - const hasTrailingSlash = url.endsWith('/') - const urls = [] - const extname = path.extname(url) - - if (/\.(js|json|mjs|cjs)/.test(extname) && !hasTrailingSlash) { - urls.push(url) - } else { - if (hasTrailingSlash) { - urls.push( - new URL('./index.js', url).toString(), - new URL('./index.json', url).toString() - ) - } else { - urls.push( - url + '.js', - url + '.json', - new URL('./index.js', url + '/').toString(), - new URL('./index.json', url + '/').toString() - ) - } - } - - while (urls.length) { - const filename = urls.shift() - const response = request(filename) - - if (response.data !== null) { - try { - evaluate(module, filename, response.data) - } catch (error) { - error.module = module - module.dispatchEvent(new ErrorEvent('error', { error })) - return false - } - } - - if (module.loaded) { - return true - } - } - - const response = request( - url + (hasTrailingSlash ? 'package.json' : '/package.json') - ) - - if (response.data) { - try { - // @ts-ignore - const packageJSON = JSON.parse(response.data) - let filename = null - if (packageJSON.main || packageJSON.browser) { - filename = path.resolve('/', url, packageJSON.main || packageJSON.browser) - } else if (packageJSON.exports) { - if (typeof packageJSON.exports === 'object') { - for (const key in packageJSON.exports) { - const value = packageJSON.exports[key] - // try to find main entry - if (key === '.' || key === './index.js' || key === 'index.js') { - if (!filename) { - if (typeof value === 'string') { - filename = path.resolve(url, value) - } else if (Array.isArray(value)) { - for (const entry of value) { - if (request(path.join(url, entry)).data) { - filename = path.resosolve(url, entry) - break - } - } - } else if (value && typeof value === 'object') { - if (typeof value.require === 'string') { - filename = path.resolve(url, value.require) - } - } - } - } else { - // TODO - } - } - } else if (typeof packageJSON.exports === 'string') { - filename = path.resolve(url, packageJSON.exports) - } - } - - loadPackage(module, filename) - } catch (error) { - error.module = module - module.dispatchEvent(new ErrorEvent('error', { error })) - } - } - - return module.loaded - } - - function evaluate (module, filename, moduleSource) { - const { protocol } = path.Path.from(filename) - let dirname = path.dirname(filename) - - if (filename.startsWith(protocol) && !dirname.startsWith(protocol)) { - dirname = protocol + '//' + dirname - } - - if (path.extname(filename) === '.json') { - module.id = new URL(filename, module.parent.id).toString() - module.filename = filename - - try { - module.exports = JSON.parse(moduleSource) - } catch (error) { - error.module = module - module.dispatchEvent(new ErrorEvent('error', { error })) - return false - } finally { - module.loaded = true - } - - if (module.parent) { - module.parent.children.push(module) - } - - module.dispatchEvent(new Event('load')) - return true - } - - try { - const source = Module.wrap(moduleSource) - // eslint-disable-next-line no-new-func - const define = new Function(`return ${source}`)() - - const oldId = module.id - module.id = new URL(filename, module.parent.id).toString() - module.filename = filename - - if (oldId !== module.id && Module.cache[module.id]) { - module.exports = Module.cache[module.id].exports - return true - } - - Module.cache[module.id] = module - - // eslint-disable-next-line no-useless-call - const promise = define.call(null, - module.exports, - module.createRequire(), - module, - filename, - dirname, - process, - globalThis - ) - - promise.catch((error) => { - error.module = module - module.dispatchEvent(new ErrorEvent('error', { error })) - }) - - if (module.parent) { - module.parent.children.push(module) - } - - module.dispatchEvent(new Event('load')) - return true - } catch (error) { - error.module = module - module.dispatchEvent(new ErrorEvent('error', { error })) - return false - } finally { - module.loaded = true - } - } - } - - /** - * Creates a require function for loaded CommonJS modules - * child to this module. - * @return {function(string): any} - */ - createRequire () { - const module = this - - Object.assign(require, { - cache: Module.cache, - resolve (filename) { - return resolve(filename, Array.from(Module.resolvers)) - } - }) - - return require - - function require (filename) { - if (filename.startsWith('/') || filename.startsWith('\\')) { - filename = filename.replace(process.cwd(), '') - } - - const imports = globalThis.document - ? maybeParseJSON(globalThis.document.querySelector('script[type=importmap')?.textContent) - : null - - if ( - (imports && typeof imports === 'object') && - (!filename.startsWith('.') && filename.startsWith('/')) - ) { - for (const key in imports) { - const value = imports[key] - if (!value || typeof value !== 'string') { - continue - } - - if (filename.startsWith(key) && key.endsWith('/') && filename.includes('/')) { - filename = filename.replace(filename, value) - break - } else if (filename === key) { - filename = value - break - } - } - } - - const resolvers = Array.from(Module.resolvers) - const result = resolve(filename, resolvers) - - if (typeof result === 'string' && result.length < 256 && !/^[\s|\n]+/.test(result)) { - const name = result.replace(/^(socket|node):/, '') - - if ( - socketRuntimeModules.includes(name) && - !result.startsWith('socket:') - ) { - throw new ModuleNotFoundError( - `Cannot require module "${filename}" without 'socket:' prefix`, - module.children - ) - } - - if (isBuiltin(result)) { - return builtins[name] - } - - return module.require(name) - } - - if (result !== undefined) { - return result - } - - throw new ModuleNotFoundError( - `Cannot find module ${filename}`, - module.children - ) - } - - function resolve (filename, resolvers) { - return next(filename) - function next (specifier) { - if (resolvers.length === 0) return specifier - const resolver = resolvers.shift() - return resolver(specifier, module, next) - } - } - } - - /** - * Requires a module at `filename` that will be loaded as a child - * to this module. - * @param {string} filename - * @return {any} - */ - require (filename) { - // @ts-ignore - const module = Module.from(filename, this) - - if (!module.load()) { - throw new ModuleNotFoundError( - `Cannot find module ${filename}`, - this.children - ) - } - - return module.exports - } - - /** - * @ignore - */ - [Symbol.toStringTag] () { - return 'Module' - } -} - export default Module - -builtins.module = Module -builtins.module.Module = Module From 47c0059d00c777a62ffe8355e29b41feefcf0b7f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:24:18 -0400 Subject: [PATCH 0416/1178] refactor(src/core/protocol_handlers.*): introduce service worker scope helper --- src/core/protocol_handlers.cc | 12 ++++++++++++ src/core/protocol_handlers.hh | 1 + 2 files changed, 13 insertions(+) diff --git a/src/core/protocol_handlers.cc b/src/core/protocol_handlers.cc index 32ec8cdea9..78037d613b 100644 --- a/src/core/protocol_handlers.cc +++ b/src/core/protocol_handlers.cc @@ -66,4 +66,16 @@ namespace SSC { Lock lock(this->mutex); return this->mapping.contains(scheme); } + + const String ProtocolHandlers::getServiceWorkerScope (const String& scheme) { + for (const auto& entry : this->core->serviceWorker.registrations) { + const auto& scope = entry.first; + const auto& registration = entry.second; + if (registration.options.scheme == scheme) { + return scope; + } + } + + return ""; + } } diff --git a/src/core/protocol_handlers.hh b/src/core/protocol_handlers.hh index 511169b193..d1d5e8235d 100644 --- a/src/core/protocol_handlers.hh +++ b/src/core/protocol_handlers.hh @@ -35,6 +35,7 @@ namespace SSC { const Data getHandlerData (const String& scheme); bool setHandlerData (const String& scheme, const Data data = { "" }); bool hasHandler (const String& scheme); + const String getServiceWorkerScope (const String& scheme); }; } #endif From 9ac1932229e5dc1d8df1818161ae944afb3b417d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:25:00 -0400 Subject: [PATCH 0417/1178] refactor(src/core/service_worker_container): improve auto registration state and protocol handler bindings --- src/core/service_worker_container.cc | 225 ++++++++++++++++++++++----- src/core/service_worker_container.hh | 7 +- 2 files changed, 188 insertions(+), 44 deletions(-) diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index cde3a474a7..9887788a2f 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -10,6 +10,30 @@ namespace SSC { static IPC::Bridge* serviceWorkerBridge = nullptr; + static const String normalizeScope (const String& scope) { + auto normalized = trim(scope); + + if (normalized.size() == 0) { + return "/"; + } + + if (normalized.ends_with("/") && normalized.size() > 1) { + normalized = normalized.substr(0, normalized.size() - 1); + } + + return normalized; + } + + const String ServiceWorkerContainer::FetchRequest::str () const { + auto string = this->scheme + "://" + this->host + this->pathname; + + if (this->query.size() > 0) { + string += String("?") + this->query; + } + + return string; + } + ServiceWorkerContainer::Registration::Registration ( const ID id, const String& scriptURL, @@ -166,7 +190,7 @@ namespace SSC { scriptURL += value; - const auto scope = join(parts, "/"); + const auto scope = normalizeScope(join(parts, "/")); const auto id = rand64(); this->registrations.insert_or_assign(scope, Registration { id, @@ -176,6 +200,7 @@ namespace SSC { RegistrationOptions::Type::Module, scope, scriptURL, + "*", id } }); @@ -188,7 +213,7 @@ namespace SSC { if (key.starts_with("webview_service-workers_")) { const auto id = rand64(); - const auto scope = replace(key, "webview_service-workers_", ""); + const auto scope = normalizeScope(replace(key, "webview_service-workers_", "")); #if defined(__ANDROID__) auto scriptURL = String("https://"); @@ -211,15 +236,61 @@ namespace SSC { RegistrationOptions::Type::Module, scope, scriptURL, + "*", id } }); } } + for (const auto& entry : this->bridge->userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + auto value = entry.second; + if (value.starts_with(".") || value.starts_with("/")) { + if (value.starts_with(".")) { + value = value.substr(1, value.size()); + } + + const auto id = rand64(); + auto parts = split(value, "/"); + parts = Vector(parts.begin(), parts.end() - 1); + auto scope = normalizeScope(join(parts, "/")); + + #if defined(__ANDROID__) + auto scriptURL = String("https://"); + #else + auto scriptURL = String("socket://"); + #endif + + scriptURL += bridge->userConfig["meta_bundle_identifier"]; + + if (!value.starts_with("/")) { + scriptURL += "/"; + } + + scriptURL += trim(value); + + this->registrations.insert_or_assign(scope, Registration { + id, + scriptURL, + Registration::State::Registered, + RegistrationOptions { + RegistrationOptions::Type::Module, + scope, + scriptURL, + scheme, + id + } + }); + } + } + } + this->bridge->router.map("serviceWorker.fetch.request.body", [this](auto message, auto router, auto reply) mutable { - Lock lock(this->mutex); - uint64_t id = 0; + FetchRequest request; + ID id = 0; try { id = std::stoull(message.get("id")); @@ -229,23 +300,29 @@ namespace SSC { }}); } - if (!this->fetchRequests.contains(id)) { - return reply(IPC::Result::Err { message, JSON::Object::Entries { - {"type", "NotFoundError"}, - {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} - }}); - } + do { + Lock lock(this->mutex); + + if (!this->fetchRequests.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} + }}); + } + + request = this->fetchRequests.at(id); + } while (0); - const auto& request = this->fetchRequests.at(id); const auto post = Post { 0, 0, request.buffer.bytes, request.buffer.size }; reply(IPC::Result { message.seq, message, JSON::Object {}, post }); }); this->bridge->router.map("serviceWorker.fetch.response", [this](auto message, auto router, auto reply) mutable { - Lock lock(this->mutex); + FetchCallback callback; + FetchRequest request; + ID clientId = 0; + ID id = 0; - uint64_t clientId = 0; - uint64_t id = 0; int statusCode = 200; try { @@ -264,21 +341,28 @@ namespace SSC { }}); } - if (!this->fetchCallbacks.contains(id)) { - return reply(IPC::Result::Err { message, JSON::Object::Entries { - {"type", "NotFoundError"}, - {"message", "Callback 'id' given in parameters does not have a 'FetchCallback'"} - }}); - } + do { + Lock lock(this->mutex); + if (!this->fetchCallbacks.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchCallback'"} + }}); + } - if (!this->fetchRequests.contains(id)) { - return reply(IPC::Result::Err { message, JSON::Object::Entries { - {"type", "NotFoundError"}, - {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} - }}); - } + if (!this->fetchRequests.contains(id)) { + return reply(IPC::Result::Err { message, JSON::Object::Entries { + {"type", "NotFoundError"}, + {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} + }}); + } + } while (0); - const auto callback = this->fetchCallbacks.at(id); + do { + Lock lock(this->mutex); + callback = this->fetchCallbacks.at(id); + request = this->fetchRequests.at(id); + } while (0); try { statusCode = std::stoi(message.get("statusCode")); @@ -289,7 +373,6 @@ namespace SSC { } const auto headers = split(message.get("headers"), '\n'); - const auto request = this->fetchRequests.at(id); auto response = FetchResponse { id, statusCode, @@ -371,10 +454,13 @@ namespace SSC { delete response.buffer.bytes; } - this->fetchCallbacks.erase(id); - if (this->fetchRequests.contains(id)) { - this->fetchRequests.erase(id); - } + do { + Lock lock(this->mutex); + this->fetchCallbacks.erase(id); + if (this->fetchRequests.contains(id)) { + this->fetchRequests.erase(id); + } + } while (0); }); } @@ -382,9 +468,34 @@ namespace SSC { const RegistrationOptions& options ) { Lock lock(this->mutex); + auto scope = options.scope; + auto scriptURL = options.scriptURL; + + if (scope.size() == 0) { + auto tmp = options.scriptURL; + tmp = replace(tmp, "https://", ""); + tmp = replace(tmp, "socket://", ""); + tmp = replace(tmp, this->bridge->userConfig["meta_bundle_identifier"], ""); + + auto parts = split(tmp, "/"); + parts = Vector(parts.begin(), parts.end() - 1); + + #if defined(__ANDROID__) + scriptURL = String("https://"); + #else + scriptURL = String("socket://"); + #endif + + scriptURL += bridge->userConfig["meta_bundle_identifier"]; + scriptURL += tmp; + + scope = join(parts, "/"); + } + + scope = normalizeScope(scope); - if (this->registrations.contains(options.scope)) { - const auto& registration = this->registrations.at(options.scope); + if (this->registrations.contains(scope)) { + const auto& registration = this->registrations.at(scope); if (this->bridge != nullptr) { this->bridge->router.emit("serviceWorker.register", registration.json().str()); @@ -393,14 +504,21 @@ namespace SSC { return registration; } - this->registrations.insert_or_assign(options.scope, Registration { - options.id > 0 ? options.id : rand64(), + const auto id = options.id > 0 ? options.id : rand64(); + this->registrations.insert_or_assign(scope, Registration { + id, options.scriptURL, Registration::State::Registered, - options + RegistrationOptions { + options.type, + scope, + options.scriptURL, + options.scheme, + id + } }); - const auto& registration = this->registrations.at(options.scope); + const auto& registration = this->registrations.at(scope); if (this->bridge != nullptr) { this->core->setImmediate([&, this]() { @@ -414,7 +532,7 @@ namespace SSC { bool ServiceWorkerContainer::unregisterServiceWorker (String scopeOrScriptURL) { Lock lock(this->mutex); - const auto& scope = scopeOrScriptURL; + const auto& scope = normalizeScope(scopeOrScriptURL); const auto& scriptURL = scopeOrScriptURL; if (this->registrations.contains(scope)) { @@ -508,6 +626,8 @@ namespace SSC { bool ServiceWorkerContainer::fetch (const FetchRequest& request, FetchCallback callback) { Lock lock(this->mutex); + String pathname = request.pathname; + String scope; if (this->bridge == nullptr || !this->isReady) { return false; @@ -518,26 +638,39 @@ namespace SSC { if (parts.size() == 2) { const auto key = trim(parts[0]); const auto value = trim(parts[1]); + + if (key == "Runtime-ServiceWorker-Fetch-Mode" && value == "ignore") { + return false; + } + if (key == "runtime-worker-type" && value == "serviceworker") { return false; } } } - String scope; for (const auto& entry : this->registrations) { const auto& registration = entry.second; - if (request.pathname.starts_with(registration.options.scope)) { + if ( + (registration.options.scheme == "*" || registration.options.scheme == request.scheme) && + request.pathname.starts_with(registration.options.scope) + ) { if (entry.first.size() > scope.size()) { scope = entry.first; } } } + scope = normalizeScope(scope); + if (scope.size() > 0 && this->registrations.contains(scope)) { auto& registration = this->registrations.at(scope); - if (!registration.isActive() && registration.state == Registration::State::Registered) { + if ( + (registration.options.scheme == "*" || registration.options.scheme == request.scheme) && + !registration.isActive() && + registration.state == Registration::State::Registered + ) { this->core->dispatchEventLoop([this, request, callback, ®istration]() { const auto interval = this->core->setInterval(8, [this, request, callback, ®istration] (auto cancel) { if (registration.state == Registration::State::Activated) { @@ -573,10 +706,16 @@ namespace SSC { {"id", std::to_string(request.client.id)} }; + if (this->core->protocolHandlers.hasHandler(request.scheme)) { + pathname = replace(pathname, scope, ""); + } + const auto fetch = JSON::Object::Entries { {"id", std::to_string(id)}, {"method", request.method}, - {"pathname", request.pathname}, + {"scheme", request.scheme}, + {"host", request.host}, + {"pathname", pathname}, {"query", request.query}, {"headers", headers}, {"client", client} diff --git a/src/core/service_worker_container.hh b/src/core/service_worker_container.hh index bd42c147d1..d3557bfe18 100644 --- a/src/core/service_worker_container.hh +++ b/src/core/service_worker_container.hh @@ -20,6 +20,7 @@ namespace SSC { Type type = Type::Module; String scope; String scriptURL; + String scheme = "*"; ID id = 0; }; @@ -71,12 +72,16 @@ namespace SSC { }; struct FetchRequest { - String pathname; String method; + String scheme = "*"; + String host = ""; + String pathname; String query; Vector headers; FetchBuffer buffer; Client client; + + const String str () const; }; struct FetchResponse { From cdc7969e6c8796a3d3f9af755072715d8a96da89 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:26:06 -0400 Subject: [PATCH 0418/1178] refactor(src/window/apple.mm): register user protocol handlers and 'npm' scheme --- src/window/apple.mm | 53 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 9861cecb5f..350815be72 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -869,15 +869,6 @@ - (void) webView: (WKWebView*) webView // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/3585117-limitsnavigationstoappbounddomai config.limitsNavigationsToAppBoundDomains = YES; - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"ipc"]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"socket"]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"node"]; - [config setValue: @NO forKey: @"crossOriginAccessControlCheckEnabled"]; WKPreferences* prefs = [config preferences]; @@ -1012,6 +1003,40 @@ - (void) webView: (WKWebView*) webView forKey: @"allowFileAccessFromFileURLs" ]; + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"npm"]; + + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"ipc"]; + + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"socket"]; + + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"node"]; + + for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (app.core->protocolHandlers.registerHandler(scheme)) { + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @(replace(trim(scheme), ":", "").c_str())]; + } + } + + for (const auto& entry : opts.userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + const auto data = entry.second; + if (app.core->protocolHandlers.registerHandler(scheme, { data })) { + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @(scheme.c_str())]; + } + } + } + +/* +TODO(@jwerle): figure out if this is even needed anymore? [config.processPool performSelector: @selector(_registerURLSchemeAsSecure:) withObject: @"socket" @@ -1022,6 +1047,12 @@ - (void) webView: (WKWebView*) webView withObject: @"ipc" ]; + [config.processPool + performSelector: @selector(_registerURLSchemeAsSecure:) + withObject: @"node" + ]; +*/ + static const auto devHost = SSC::getDevHost(); if (devHost.starts_with("http:")) { [config.processPool @@ -1049,8 +1080,8 @@ - (void) webView: (WKWebView*) webView webview = [SSCBridgedWebView.alloc initWithFrame: NSZeroRect configuration: config - radius: (CGFloat) opts.radius - margin: (CGFloat) opts.margin + radius: (CGFloat) opts.radius + margin: (CGFloat) opts.margin ]; window.webview = webview; From 6bb59b597a4b024fbe724679afff31a3483fe7be Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:26:31 -0400 Subject: [PATCH 0419/1178] refactor(src/ipc/bridge.cc): handle 'PUT/POST' for custom protocols on apple --- src/ipc/bridge.cc | 445 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 429 insertions(+), 16 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index ab7124e6a3..fd8fe87054 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -52,7 +52,7 @@ static const Vector allowedNodeCoreModules = { "sys", "test", "timers", - "timers/promsies", + "timers/promises", "tty", "util", "url", @@ -2326,7 +2326,10 @@ static void initRouterTable (Router *router) { ); }); - + /** + * Reveal a file in the native operating system file system explorer. + * @param value + */ router->map("platform.revealFile", [=](auto message, auto router, auto reply) mutable { auto err = validateMessageParameters(message, {"value"}); @@ -2450,6 +2453,104 @@ static void initRouterTable (Router *router) { router->core->removePost(id); }); + /** + * Registers a custom protocol handler scheme. Custom protocols MUST be handled in service workers. + * @param scheme + * @param data + */ + router->map("protocol.register", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + const auto data = message.get("data"); + + if (data.size() > 0 && router->core->protocolHandlers.hasHandler(scheme)) { + router->core->protocolHandlers.setHandlerData(scheme, { data }); + } else { + router->core->protocolHandlers.registerHandler(scheme, { data }); + } + + reply(Result { message.seq, message }); + }); + + /** + * Unregister a custom protocol handler scheme. + * @param scheme + */ + router->map("protocol.unregister", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + + if (!router->core->protocolHandlers.hasHandler(scheme)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Protocol handler scheme is not registered."} + }}); + } + + router->core->protocolHandlers.unregisterHandler(scheme); + + reply(Result { message.seq, message }); + }); + + /** + * Gets protocol handler data + * @param scheme + */ + router->map("protocol.getData", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + + if (!router->core->protocolHandlers.hasHandler(scheme)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Protocol handler scheme is not registered."} + }}); + } + + const auto data = router->core->protocolHandlers.getHandlerData(scheme); + + reply(Result { message.seq, message, JSON::Raw(data.json) }); + }); + + /** + * Sets protocol handler data + * @param scheme + * @param data + */ + router->map("protocol.setData", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme", "data"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + const auto data = message.get("data"); + + if (!router->core->protocolHandlers.hasHandler(scheme)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Protocol handler scheme is not registered."} + }}); + } + + router->core->protocolHandlers.setHandlerData(scheme, { data }); + + reply(Result { message.seq, message }); + }); + /** * Prints incoming message value to stdout. * @param value @@ -3226,15 +3327,16 @@ static void registerSchemeHandler (Router *router) { path = userConfig["webview_default_index"]; } else { if (router->core->serviceWorker.registrations.size() > 0) { - auto fetchRequest = ServiceWorkerContainer::FetchRequest { - parsedPath.path, - method - }; + auto requestHeaders = webkit_uri_scheme_request_get_http_headers(request); + auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; fetchRequest.client.id = clientId; fetchRequest.client.preload = router->bridge->preload; - auto requestHeaders = webkit_uri_scheme_request_get_http_headers(request); + fetchRequest.method = method; + fetchRequest.scheme = "socket"; + fetchRequest.host = userConfig["meta_bundle_identifier"]; + fetchRequest.pathname = parsedPath.path; fetchRequest.query = parsedPath.queryString; soup_message_headers_foreach( @@ -3247,6 +3349,31 @@ static void registerSchemeHandler (Router *router) { &fetchRequest ); + if ((method == "POST" || method == "PUT")) { + auto body = webkit_uri_scheme_request_get_http_body(request); + if (body) { + GError* error = nullptr; + fetchRequest.buffer.bytes = bytes; + + const auto success = g_input_stream_read_all( + body, + bytes, + MAX_BODY_BYTES, + &fetchRequest.buffer.size, + nullptr, + &error + ); + + if (!success) { + webkit_uri_scheme_request_finish_error( + request, + error + ); + return; + } + } + } + const auto fetched = router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { if (res.statusCode == 0) { webkit_uri_scheme_request_finish_error( @@ -3423,6 +3550,14 @@ static void registerSchemeHandler (Router *router) { gsize size = 0; if (g_file_get_contents(file.c_str(), &contents, &size, &error)) { String html = contents; + Vector protocolHandlers = { "npm:", "node:" }; + for (const auto& entry : router->core->protocolHandlers.mapping) { + protocolHandlers.push_back(String(entry.first) + ":"); + } + + html = tmpl(html, Map { + {"protocol_handlers", join(protocolHandlers, " ")} + }); if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); @@ -3672,7 +3807,10 @@ static void registerSchemeHandler (Router *router) { headers[key] = value; } - headers[@"cache-control"] = @"no-cache"; + if (SSC::isDebugEnabled()) { + headers[@"cache-control"] = @"no-cache"; + } + headers[@"access-control-allow-origin"] = @"*"; headers[@"access-control-allow-methods"] = @"*"; headers[@"access-control-allow-headers"] = @"*"; @@ -3695,6 +3833,207 @@ static void registerSchemeHandler (Router *router) { return; } + // handle 'npm:' and custom protocol schemes - POST/PUT bodies are ignored + if (scheme == "npm" || self.router->core->protocolHandlers.hasHandler(scheme)) { + auto absoluteURL = String(request.URL.absoluteString.UTF8String); + auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; + + fetchRequest.client.id = clientId; + fetchRequest.client.preload = self.router->bridge->preload; + + fetchRequest.method = String(request.HTTPMethod.UTF8String); + fetchRequest.scheme = scheme; + + if (request.URL.path != nullptr) { + fetchRequest.pathname = String(request.URL.path.UTF8String); + fetchRequest.host = userConfig["meta_bundle_identifier"]; + } else if (request.URL.host != nullptr) { + fetchRequest.pathname = String("/") + String(request.URL.host.UTF8String); + fetchRequest.host = userConfig["meta_bundle_identifier"]; + } else { + fetchRequest.host = userConfig["meta_bundle_identifier"]; + if (absoluteURL.starts_with(scheme + "://")) { + fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + "://", ""); + } else if (absoluteURL.starts_with(scheme + ":/")) { + fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + ":/", ""); + } else { + fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + ":", ""); + } + } + + if (request.URL.host != nullptr && request.URL.path != nullptr) { + fetchRequest.host = String(request.URL.host.UTF8String); + } else { + fetchRequest.host = userConfig["meta_bundle_identifier"]; + } + + if (scheme == "npm") { + fetchRequest.pathname = String("/socket/npm") + fetchRequest.pathname; + } + + if (request.URL.query != nullptr) { + fetchRequest.query = String(request.URL.query.UTF8String); + } else { + auto cursor = absoluteURL.find_first_of("?"); + if (cursor != String::npos && cursor < absoluteURL.size()) { + Vectorcomponents; + + // re-encode all URI components as this part of the URL may be sitting + // in the "auth" position of the URI with non-encoded characters + for (const auto entry : split(absoluteURL.substr(cursor + 1, absoluteURL.size()), "&")) { + const auto parts = split(entry, "="); + if (parts.size() == 2) { + const auto component = encodeURIComponent(parts[0]) + "=" + encodeURIComponent(parts[1]); + components.push_back(component); + } + } + + fetchRequest.query = join(components, "&"); + auto cursor = fetchRequest.pathname.find_first_of("?"); + if (cursor != String::npos) { + fetchRequest.pathname = fetchRequest.pathname.substr(0, cursor); + } + } + } + + if (request.allHTTPHeaderFields != nullptr) { + for (NSString* key in request.allHTTPHeaderFields) { + const auto value = [request.allHTTPHeaderFields objectForKey: key]; + if (value != nullptr) { + const auto entry = String(key.UTF8String) + ": " + String(value.UTF8String); + fetchRequest.headers.push_back(entry); + } + } + } + + if (request.HTTPBody && request.HTTPBody.bytes && request.HTTPBody.length > 0) { + fetchRequest.buffer.size = request.HTTPBody.length; + fetchRequest.buffer.bytes = new char[fetchRequest.buffer.size]{0}; + memcpy( + fetchRequest.buffer.bytes, + request.HTTPBody.bytes, + fetchRequest.buffer.size + ); + } + + const auto scope = self.router->core->protocolHandlers.getServiceWorkerScope(fetchRequest.scheme); + + if (scope.size() > 0) { + fetchRequest.pathname = scope + fetchRequest.pathname; + } + + const auto requestURL = scheme == "npm" + ? replace(fetchRequest.str(), "npm://", "socket://") + : fetchRequest.str(); + + const auto fetched = self.router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { + if (![self waitingForTask: task]) { + return; + } + + if (res.statusCode == 0) { + @try { + [task didFailWithError: [NSError + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + code: 1 + userInfo: @{NSLocalizedDescriptionKey: @(res.buffer.bytes)} + ]]; + } @catch (id e) { + // ignore possible 'NSInternalInconsistencyException' + } + return; + } + + auto headers = [NSMutableDictionary dictionary]; + + for (const auto& entry : res.headers) { + auto pair = split(trim(entry), ':'); + auto key = @(trim(pair[0]).c_str()); + auto value = @(trim(pair[1]).c_str()); + headers[key] = value; + } + + @try { + if (![self waitingForTask: task]) { + return; + } + + const auto response = [[NSHTTPURLResponse alloc] + initWithURL: [NSURL URLWithString: @(requestURL.c_str())] + statusCode: res.statusCode + HTTPVersion: @"HTTP/1.1" + headerFields: headers + ]; + + if (![self waitingForTask: task]) { + #if !__has_feature(objc_arc) + [response release]; + #endif + return; + } + + [task didReceiveResponse: response]; + + if (![self waitingForTask: task]) { + #if !__has_feature(objc_arc) + [response release]; + #endif + return; + } + + const auto data = [NSData + dataWithBytes: res.buffer.bytes + length: res.buffer.size + ]; + + if (res.buffer.size && data.length > 0) { + [task didReceiveData: data]; + } + + [task didFinish]; + [self finalizeTask: task]; + #if !__has_feature(objc_arc) + [response release]; + #endif + } @catch (id e) { + // ignore possible 'NSInternalInconsistencyException' + } + }); + + if (fetched) { + [self enqueueTask: task withMessage: message]; + self.router->bridge->core->setTimeout(32000, [=] () mutable { + if ([self waitingForTask: task]) { + @try { + [self finalizeTask: task]; + [task didFailWithError: [NSError + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + code: 1 + userInfo: @{NSLocalizedDescriptionKey: @"ServiceWorker request timed out."} + ]]; + } @catch (id e) { + } + } + }); + return; + } + + auto response = [[NSHTTPURLResponse alloc] + initWithURL: request.URL + statusCode: 404 + HTTPVersion: @"HTTP/1.1" + headerFields: headers + ]; + + [task didReceiveResponse: response]; + [task didFinish]; + + #if !__has_feature(objc_arc) + [response release]; + #endif + return; + } + if (scheme == "socket" || scheme == "node") { auto host = request.URL.host; auto components = [NSURLComponents @@ -3842,7 +4181,14 @@ static void registerSchemeHandler (Router *router) { } } - auto html = String(string.UTF8String); + Vector protocolHandlers = { "npm:", "node:" }; + for (const auto& entry : self.router->core->protocolHandlers.mapping) { + protocolHandlers.push_back(String(entry.first) + ":"); + } + + auto html = tmpl(String(string.UTF8String), Map { + {"protocol_handlers", join(protocolHandlers, " ")} + }); if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); @@ -3878,14 +4224,22 @@ static void registerSchemeHandler (Router *router) { path = userConfig["webview_default_index"]; } else if (!path.starts_with("/socket/service-worker")) { if (self.router->core->serviceWorker.registrations.size() > 0) { - auto fetchRequest = ServiceWorkerContainer::FetchRequest { - String(request.URL.path.UTF8String), - String(request.HTTPMethod.UTF8String) - }; + auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; fetchRequest.client.id = clientId; fetchRequest.client.preload = self.router->bridge->preload; + fetchRequest.method = String(request.HTTPMethod.UTF8String); + fetchRequest.scheme = scheme; + + if (request.URL.host != nullptr) { + fetchRequest.host = String(request.URL.host.UTF8String); + } else { + fetchRequest.host = userConfig["meta_bundle_identifier"]; + } + + fetchRequest.pathname = String(request.URL.path.UTF8String); + if (request.URL.query != nullptr) { fetchRequest.query = String(request.URL.query.UTF8String); } @@ -3900,6 +4254,16 @@ static void registerSchemeHandler (Router *router) { } } + if (request.HTTPBody && request.HTTPBody.bytes && request.HTTPBody.length > 0) { + fetchRequest.buffer.size = request.HTTPBody.length; + fetchRequest.buffer.bytes = new char[fetchRequest.buffer.size]{0}; + memcpy( + fetchRequest.buffer.bytes, + request.HTTPBody.bytes, + fetchRequest.buffer.size + ); + } + const auto requestURL = String(request.URL.absoluteString.UTF8String); const auto fetched = self.router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { if (![self waitingForTask: task]) { @@ -4213,7 +4577,14 @@ static void registerSchemeHandler (Router *router) { } } - auto html = String(string.UTF8String); + Vector protocolHandlers = { "npm:", "node:" }; + for (const auto& entry : self.router->core->protocolHandlers.mapping) { + protocolHandlers.push_back(String(entry.first) + ":"); + } + + auto html = tmpl(String(string.UTF8String), Map { + {"protocol_handlers", join(protocolHandlers, " ")} + }); if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); @@ -4450,8 +4821,7 @@ static void registerSchemeHandler (Router *router) { #if !__has_feature(objc_arc) [response release]; #endif - } @catch (::id e) { - } + } @catch (::id e) {} }); if (!invoked) { @@ -5282,6 +5652,10 @@ namespace SSC::IPC { void Router::init (Bridge* bridge) { this->bridge = bridge; + if (bridge->userConfig["permissions_allow_service_worker"] != "false") { + bridge->userConfig["webview_service-workers_/socket/npm/"] = "/socket/npm/service-worker.js"; + } + #if defined(__APPLE__) this->networkStatusObserver = [SSCIPCNetworkStatusObserver new]; this->locationObserver = [SSCLocationObserver new]; @@ -5295,6 +5669,45 @@ namespace SSC::IPC { initRouterTable(this); registerSchemeHandler(this); + #if SSC_PLATFORM_LINUX + ProtocolHandlers::Mapping protocolHandlerMappings; + + for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + protocolHandlerMappings.insert_or_assign(scheme, { scheme }); + } + + for (const auto& entry : opts.userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + const auto data = entry.second; + if (data.starts_with(".") || data.starts_with("/")) { + protocolHandlerMappings.insert_or_assign(scheme, { scheme }); + } else { + protocolHandlerMappings.insert_or_assign(scheme, { scheme, { data } }); + } + } + } + + for (const auto& entry : protocolHandlerMappings) { + const auto& scheme = entry.first; + const auto& data = entry.second.data; + // manually handle NPM here + if (scheme == "npm" || app.core->protocolHandlers.registerHandler(scheme, data)) { + webkit_security_manager_register_uri_scheme_as_display_isolated(security, scheme.c_str()); + webkit_security_manager_register_uri_scheme_as_cors_enabled(security, scheme.c_str()); + webkit_security_manager_register_uri_scheme_as_secure(security, scheme.c_str()); + webkit_security_manager_register_uri_scheme_as_local(security, scheme.c_str()); + webkit_web_context_register_uri_scheme(ctx, scheme.c_str(), [](auto request, auto ptr) { + // auto protocol = ... + }, + nullptr, + 0); + } + } +#endif + this->preserveCurrentTable(); #if defined(__APPLE__) From 170ae1f4fc1ca6ca4d14ae863d236eeb090a0ae9 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:26:57 -0400 Subject: [PATCH 0420/1178] refactor(src/android): initial setup for handling byte array in PUT/POST in SW --- src/android/runtime.cc | 12 +++++++++++- src/android/runtime.kt | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/android/runtime.cc b/src/android/runtime.cc index 4685de9504..37eed07d98 100644 --- a/src/android/runtime.cc +++ b/src/android/runtime.cc @@ -227,7 +227,8 @@ extern "C" { jstring pathnameString, jstring methodString, jstring queryString, - jstring headersString + jstring headersString, + jbyteArray byteArray, ) { auto runtime = Runtime::from(env, self); @@ -254,6 +255,15 @@ extern "C" { StringWrap(env, methodString).str() }; + auto size = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0; + auto input = size > 0 ? new char[size]{0} : nullptr; + + if (size > 0 && input != nullptr) { + env->GetByteArrayRegion(byteArray, 0, size, (jbyte*) input); + } + + request.buffer.bytes = input; + request.buffer.size = size; request.query = StringWrap(env, queryString).str(); request.headers = SSC::split(StringWrap(env, headersString).str(), '\n'); diff --git a/src/android/runtime.kt b/src/android/runtime.kt index e323f500b4..63a8e52c52 100644 --- a/src/android/runtime.kt +++ b/src/android/runtime.kt @@ -54,7 +54,8 @@ open class RuntimeServiceWorkerContainer ( method: String, path: String, query: String, - requestHeaders: MutableMap + requestHeaders: MutableMap, + bytes: ByteArray? = null ): android.webkit.WebResourceResponse? { val activity = runtime.activity.get() ?: return null var pathname = path @@ -203,7 +204,8 @@ open class RuntimeServiceWorkerContainer ( pathname, method, query, - headers + headers, + bytes ) if (!fetched) { From f22fe89f94d7d51248a7eb281757fa2bde236285 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:27:13 -0400 Subject: [PATCH 0421/1178] feat(api/npm): initial 'npm:' worker support --- api/npm/module.js | 222 ++++++++++++++++++++++++++++++++++++++ api/npm/service-worker.js | 151 ++++++++++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 api/npm/module.js create mode 100644 api/npm/service-worker.js diff --git a/api/npm/module.js b/api/npm/module.js new file mode 100644 index 0000000000..6969cc372e --- /dev/null +++ b/api/npm/module.js @@ -0,0 +1,222 @@ +import { Module, createRequire } from '../module.js' +import { MIMEType } from '../mime.js' +import location from '../location.js' +import path from '../path.js' + +export async function resolve (specifier, origin = null) { + if (!origin) { + origin = location.origin + } + + const parts = specifier.split('/') + const prefix = './node_modules' + const pathname = parts.slice(1).join('/').trim() + const moduleName = parts[0] + let packageJSON = null + let resolved = null + + const require = createRequire(origin, { + headers: { + 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' + } + }) + + try { + packageJSON = require(`${prefix}/${moduleName}/package.json`) + } catch { + try { + require(`${prefix}/${moduleName}/index.js`) + packageJSON = { + type: 'commonjs', + main: `${prefix}/${moduleName}/index.js` + } + } catch (err) { + if ( + (err.name === 'SyntaxError' && err.message.includes('import call')) || + (err.name === 'SyntaxError' && /unexpected keyword '(export|import|default|from)'/i.test(err.message)) || + (err.message.includes('import.meta')) + ) { + return { + origin, + type: 'module', + path: `${prefix}/${moduleName}/index.js` + } + } else { + throw err + } + } + } + + // top level import + if (pathname.length === 0) { + // legacy + if (packageJSON.module) { + resolved = { + type: 'module', + path: path.join(`${prefix}/${moduleName}`, packageJSON.module) + } + } + + // simple + if (typeof packageJSON.exports === 'string') { + resolved = { + type: packageJSON.type, + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports) + } + } + + // exports object + if (packageJSON.exports && typeof packageJSON.exports === 'object') { + if (typeof packageJSON.exports.import === 'string') { + resolved = { + type: 'module', + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports.import) + } + } else if (typeof packageJSON.exports.require === 'string') { + resolved = { + type: 'commonjs', + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports.require) + } + } else if (typeof packageJSON.exports['.'] === 'string') { + resolved = { + type: packageJSON.type, + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports['.']) + } + } else if (typeof packageJSON.exports['.']?.import === 'string') { + resolved = { + type: 'module', + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports['.'].import) + } + } else if (typeof packageJSON.exports['.']?.require === 'string') { + resolved = { + type: 'commonjs', + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports['.'].require) + } + } + } else if (typeof packageJSON.main === 'string') { + resolved = { + type: 'commonjs', + path: path.join(`${prefix}/${moduleName}`, packageJSON.main) + } + } else { + resolved = { + type: packageJSON.type ?? 'commonjs', + path: path.join(`${prefix}/${moduleName}`, 'index.js') + } + } + } + + if (!resolved && pathname.length) { + // no exports, just join paths + if (!packageJSON.exports || typeof packageJSON.exports === 'string') { + resolved = { + type: packageJSON.type ?? 'commonjs', + path: path.join(`${prefix}/${moduleName}`, pathname) + } + } else if (packageJSON.exports && typeof packageJSON.exports === 'object') { + const extensions = [ + '', + '.js', + '.mjs', + '.cjs', + '.json' + ] + + do { + const name = `./${pathname}` + extensions.shift() + if (typeof packageJSON.exports[name] === 'string') { + resolved = { + type: packageJSON.type ?? 'commonjs', + path: path.join(`${prefix}/${moduleName}`, packageJSON.exports[name]) + } + break + } else if (packageJSON.exports[name] && typeof packageJSON.exports[name] === 'object') { + const exports = packageJSON.exports[name] + if (packageJSON.type === 'module' && exports.import) { + resolved = { + type: 'module', + path: path.join(`${prefix}/${moduleName}`, exports.import) + } + break + } else if (exports.require) { + resolved = { + type: 'commonjs', + path: path.join(`${prefix}/${moduleName}`, exports.require) + } + + break + } + } + } while (extensions.length) + } + } + + if (!resolved) { + const filename = path.join(`${prefix}/${moduleName}`, pathname) + try { + const module = Module.from(filename, Module.main) + const loaded = module.load({ + evaluate: false, + headers: { + 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' + } + }) + + if (loaded) { + resolved = { + type: 'commonjs', + path: new URL(module.id).pathname + } + } + } catch {} + } + + if (!resolved) { + return null + } + + if (resolved.path.endsWith('/')) { + resolved.path += 'index.js' + } + + if (!path.extname(resolved.path)) { + const extensions = [ + '.js', + '.mjs', + '.cjs', + '.json', + '' // maybe an extension was omitted for the actual file, try that _last_ + ] + + do { + resolved.path = resolved.path + extensions.shift() + + try { + const response = await fetch(new URL(resolved.path, origin)) + + if (response.ok) { + const { essence } = new MIMEType(response.headers.get('content-type')) + const contentLength = parseInt(response.headers.get('content-length')) + if (contentLength > 0) { + if (essence === 'text/javascript' || essence === 'application/javascript') { + break + } + } + } + } catch { + // ignore + } + + if (extensions.length === 0) { + return null + } + } while (extensions.length) + } + + resolved.origin = origin + return resolved +} + +export default { + resolve +} diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js new file mode 100644 index 0000000000..d286738766 --- /dev/null +++ b/api/npm/service-worker.js @@ -0,0 +1,151 @@ +import { createRequire } from '../module.js' +import process from '../process.js' +import module from './module.js' +import http from '../http.js' +import util from '../util.js' + +export async function onRequest (request, env, ctx) { + if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { + console.debug(request.url) + } + + const url = new URL(request.url) + const origin = url.origin.replace('npm://', 'socket://') + const specifier = url.pathname.replace('/socket/npm/', '') + const importOrigins = url.searchParams.getAll('origin').concat(url.searchParams.getAll('origin[]')) + + let resolved = null + let origins = [] + + for (const value of importOrigins) { + if (value.startsWith('npm:')) { + origins.push(value) + } else if (value.startsWith('.')) { + origins.push(new URL(value, `socket://${url.host}${url.pathname}`).href.replace(/\/$/, '')) + } else if (value.startsWith('/')) { + origins.push(new URL(value, origin).href) + } else if (URL.canParse(value)) { + origins.push(value) + } else if (URL.canParse(`socket://${value}`)) { + origins.push(`socket://${value}`) + } + } + + origins.push(origin) + origins = Array.from(new Set(origins)) + + if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { + console.debug('resolving: npm:%s', specifier) + } + + while (origins.length && resolved === null) { + const potentialOrigins = [] + const origin = origins.shift() + + if (origin.startsWith('npm:')) { + const potentialSpeciifier = new URL(origin).pathname + for (const potentialOrigin of origins) { + try { + const resolution = await module.resolve(potentialSpeciifier, potentialOrigin) + if (resolution) { + potentialOrigins.push(new URL(resolution.path, resolution.origin).href) + break + } + } catch { } + } + } else { + potentialOrigins.push(origin) + } + + while (potentialOrigins.length && resolved === null) { + const importOrigin = new URL('./', potentialOrigins.shift()).href + try { + resolved = await module.resolve(specifier, importOrigin) + } catch (err) { + globalThis.reportError(err) + return new Response(util.inspect(err), { + status: 500 + }) + } + } + } + + // not found + if (!resolved) { + if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { + console.debug('not found: npm:%s', specifier) + } + return + } + + const source = new URL(resolved.path, resolved.origin) + + if (resolved.type === 'module') { + const response = await fetch(source.href) + const text = await response.text() + const proxy = text.includes('export default') + ? ` + import module from '${source.href}' + export * from '${source.href}' + export default module + ` + : ` + import * as module from '${source.href}' + export * from '${source.href}' + export default module + ` + + return new Response(proxy, { + headers: { + 'content-type': 'text/javascript' + } + }) + } + + if (resolved.type === 'commonjs') { + const pathspec = (source.pathname + source.search).replace(/^\/node_modules\//, '') + console.log({ origin: source.origin, pathspec }) + const proxy = ` + import { createRequire } from 'socket:module' + const require = createRequire('${source.origin + '/'}', { + headers: { + 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' + } + }) + const exports = require('${pathspec}') + console.log('${pathspec}', exports) + export default exports?.default ?? exports ?? null + ` + + return new Response(proxy, { + headers: { + 'content-type': 'text/javascript' + } + }) + } +} + +/** + * Handles incoming 'npm:///' requests. + * @param {Request} request + * @param {object} env + * @param {import('../service-worker/context.js').Context} ctx + * @return {Response?} + */ +export default async function (request, env, ctx) { + if (request.method !== 'GET') { + return new Response('Invalid HTTP method', { + status: http.BAD_REQUEST + }) + } + + try { + return await onRequest(request, env, ctx) + } catch (err) { + globalThis.reportError(err) + + if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { + console.debug(err) + } + } +} From d3232f94235fa33dc61e048de1508c557d3af6c7 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 26 Mar 2024 01:29:15 -0400 Subject: [PATCH 0422/1178] chorE(api): generate types + docs --- api/README.md | 147 +++-- api/application.js | 6 + api/index.d.ts | 1450 +++++++++++++++++++++++++++++++------------- 3 files changed, 1109 insertions(+), 494 deletions(-) diff --git a/api/README.md b/api/README.md index 7d0ecf3d30..1cca6de1eb 100644 --- a/api/README.md +++ b/api/README.md @@ -29,7 +29,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L198) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L199) Creates a new window and returns an instance of ApplicationWindow. @@ -61,20 +61,21 @@ Creates a new window and returns an instance of ApplicationWindow. | opts.canExit | boolean | false | true | whether the window can exit the app | | opts.headless | boolean | false | true | whether the window will be headless or not (no frame) | | opts.userScript | string | null | true | A user script that will be injected into the window (desktop only) | +| opts.protocolHandlers | string | | true | An array of protocol handler schemes to register with the new window (requires service worker) | | Return Value | Type | Description | | :--- | :--- | :--- | | Not specified | Promise | | -### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L225) +### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L226) -### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L230) +### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L231) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L298) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L319) Returns the current screen size. @@ -82,7 +83,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L324) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L345) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -94,7 +95,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L381) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L402) Returns the ApplicationWindow instance for the given index @@ -106,7 +107,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L391) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L412) Returns the ApplicationWindow instance for the current window. @@ -114,7 +115,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L400) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L421) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -126,7 +127,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L497) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L518) Set the native menu for the app. @@ -221,11 +222,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L504) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L525) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L513) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L534) Set the enabled state of the system menu. @@ -237,23 +238,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L521) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L542) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L527) +## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L548) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L533) +## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L554) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L538) +## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L559) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L544) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L565) @@ -266,7 +267,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L552) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L573) @@ -812,7 +813,7 @@ External docs: https://nodejs.org/api/events.html ## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L87) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback +External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` upon success or error. @@ -824,7 +825,7 @@ Asynchronously check access a file for a given mode calling `callback` ## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L110) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback +External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` upon success or error. @@ -899,7 +900,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. ## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L263) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsclosefd-callback +External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. | Argument | Type | Default | Optional | Description | @@ -909,7 +910,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. ## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L287) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback +External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. | Argument | Type | Default | Optional | Description | @@ -921,7 +922,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. ## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L316) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback +External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. | Argument | Type | Default | Optional | Description | @@ -932,7 +933,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. ## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L342) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options +External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | Argument | Type | Default | Optional | Description | @@ -946,7 +947,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewri ## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L385) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options +External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | Argument | Type | Default | Optional | Description | @@ -960,7 +961,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewri ## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L431) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsfstatfd-options-callback +External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the for the file descriptor. See the POSIX fstat(2) documentation for more detail. @@ -1015,7 +1016,7 @@ Creates a link to `dest` from `src`. ## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L618) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback +External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. | Argument | Type | Default | Optional | Description | @@ -1028,7 +1029,7 @@ Asynchronously open a file calling `callback` upon success or error. ## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L671) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback +External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. | Argument | Type | Default | Optional | Description | @@ -1041,7 +1042,7 @@ Asynchronously open a directory calling `callback` upon success or error. ## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L697) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreadfd-buffer-offset-length-position-callback +External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. | Argument | Type | Default | Optional | Description | @@ -1053,9 +1054,23 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L731) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L732) -External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback +External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback +Asynchronously write to an open file descriptor. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| fd | number | | false | | +| buffer | object \| Buffer \| TypedArray | | false | The buffer that the data will be written to. | +| offset | number | | false | The position in buffer to write the data to. | +| length | number | | false | The number of bytes to read. | +| position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | +| callback | function(Error?, number?, Buffer?) | | false | | + +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L766) + +External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. | Argument | Type | Default | Optional | Description | @@ -1066,7 +1081,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L782) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L817) @@ -1079,7 +1094,7 @@ Asynchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L827) +## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L862) @@ -1091,7 +1106,7 @@ Asynchronously read all entries in a directory. | options.flag ? r | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L885) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L920) Reads link at `path` @@ -1100,7 +1115,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L904) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L939) Computes real path for `path` @@ -1109,7 +1124,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L924) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L959) Renames file or directory at `src` to `dest`. @@ -1119,7 +1134,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L947) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L982) Removes directory at `path`. @@ -1128,7 +1143,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L968) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1003) Synchronously get the stats of a file @@ -1139,7 +1154,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L987) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1022) Get the stats of a file @@ -1152,7 +1167,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1025) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1060) Get the stats of a symbolic link @@ -1165,7 +1180,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1059) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1094) Creates a symlink of `src` at `dest`. @@ -1174,7 +1189,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1100) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1135) Unlinks (removes) file at `path`. @@ -1183,7 +1198,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1125) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1160) @@ -1198,7 +1213,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1170) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1205) Watch for changes at `path` calling `callback` @@ -1574,12 +1589,12 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L271) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L270) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1128) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1127) Emit event to be dispatched on `window` object. @@ -1590,7 +1605,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1187) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1186) Sends an async IPC command request with parameters. @@ -1829,7 +1844,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L258) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L264) Computes base name of path. @@ -1842,7 +1857,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L272) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L278) Computes extension name of path. @@ -1855,7 +1870,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L283) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L289) Computes normalized path @@ -1868,7 +1883,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L333) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L339) Formats `Path` object into a string. @@ -1881,7 +1896,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L349) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L355) Parses input `path` into a `Path` instance. @@ -1893,11 +1908,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L377) +## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L383) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L383) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L389) Creates a `Path` instance from `input` and optional `cwd`. @@ -1906,7 +1921,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L406) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L412) `Path` class constructor. @@ -1915,47 +1930,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L479) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L485) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L486) +### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L492) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L520) +### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L526) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L528) +### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L534) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L547) +### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L553) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L568) +### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L574) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L603) +### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L609) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L615) +### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L621) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L623) +### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L629) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L643) +### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L649) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L650) +### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L656) @@ -1963,7 +1978,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L658) +### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L664) Converts this `Path` instance to a string. diff --git a/api/application.js b/api/application.js index 580747db2b..a7e8fe898f 100644 --- a/api/application.js +++ b/api/application.js @@ -257,13 +257,19 @@ export async function createWindow (opts) { if (Array.isArray(opts?.protocolHandlers)) { for (const protocolHandler of opts.protocolHandlers) { + // @ts-ignore opts.config[`webview_protocol-handlers_${protocolHandler}`] = '' } } else if (opts?.protocolHandlers && typeof opts.protocolHandlers === 'object') { + // @ts-ignore for (const key in opts.protocolHandlers) { + // @ts-ignore if (opts.protocolHandlers[key] && typeof opts.protocolHandlers[key] === 'object') { + // @ts-ignore opts.config[`webview_protocol-handlers_${key}`] = JSON.stringify(opts.protocolHandlers[key]) + // @ts-ignore } else if (typeof opts.protocolHandlers[key] === 'string') { + // @ts-ignore opts.config[`webview_protocol-handlers_${key}`] = opts.protocolHandlers[key] } } diff --git a/api/index.d.ts b/api/index.d.ts index eb64a2832d..bcd55b8068 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -207,7 +207,7 @@ declare module "socket:events" { readonly error: any; }; }; - export default exports; + export default EventEmitter; export function EventEmitter(): void; export class EventEmitter { _events: any; @@ -237,8 +237,6 @@ declare module "socket:events" { export { once }; } export function once(emitter: any, name: any): Promise; - import * as exports from "socket:events"; - } declare module "socket:async/context" { @@ -629,11 +627,11 @@ declare module "socket:diagnostics/channels" { unsubscribe(_: any, onMessage: Function): boolean; /** * A no-op for `Channel` instances. This function always returns `false`. - * @param {string} name - * @param {object} message + * @param {string|object} name + * @param {object=} [message] * @return Promise */ - publish(name: string, message: object): Promise; + publish(name: string | object, message?: object | undefined): Promise; /** * Returns a string representation of the `ChannelRegistry`. * @ignore @@ -2044,13 +2042,35 @@ declare module "socket:url/index" { export function parse(input: any, options?: any): any; export function resolve(from: any, to: any): any; export function format(input: any): any; + const URLPattern_base: { + new (t: {}, r: any, n: any): { + "__#21@#i": any; + "__#21@#n": {}; + "__#21@#t": {}; + "__#21@#e": {}; + "__#21@#s": {}; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + }; + compareComponent(t: any, r: any, n: any): number; + }; + export class URLPattern extends URLPattern_base { + } export const protocols: Set; export default URL; export const URL: any; - import { URLPattern } from "socket:url/urlpattern/urlpattern"; export const URLSearchParams: any; export const parseURL: any; - export { URLPattern }; } declare module "socket:url" { @@ -2155,25 +2175,7 @@ declare module "socket:path/path" { * @param {string} [cwd = Path.cwd()] */ protected constructor(); - pattern: { - "__#21@#i": any; - "__#21@#n": {}; - "__#21@#t": {}; - "__#21@#e": {}; - "__#21@#s": {}; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - }; + pattern: URLPattern; url: any; get pathname(): any; get protocol(): any; @@ -2258,6 +2260,7 @@ declare module "socket:path/path" { } | { url: string; }); + import { URLPattern } from "socket:url/index"; import { URL } from "socket:url/index"; } @@ -3987,7 +3990,7 @@ declare module "socket:fs/index" { /** * Asynchronously check access a file for a given mode calling `callback` * upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} * @param {string | Buffer | URL} path * @param {string?|function(Error?)?} [mode = F_OK(0)] * @param {function(Error?)?} [callback] @@ -3996,7 +3999,7 @@ declare module "socket:fs/index" { /** * Synchronously check access a file for a given mode calling `callback` * upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} * @param {string | Buffer | URL} path * @param {string?} [mode = F_OK(0)] */ @@ -4049,7 +4052,7 @@ declare module "socket:fs/index" { export function chownSync(path: string, uid: number, gid: number): void; /** * Asynchronously close a file descriptor calling `callback` upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsclosefd-callback} + * @see {@link https://nodejs.org/api/fs.html#fsclosefd-callback} * @param {number} fd * @param {function(Error?)?} [callback] */ @@ -4060,7 +4063,7 @@ declare module "socket:fs/index" { * @param {string} dest - The destination file path. * @param {number} flags - Modifiers for copy operation. * @param {function(Error=)=} [callback] - The function to call after completion. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFile(src: string, dest: string, flags: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; /** @@ -4068,18 +4071,18 @@ declare module "socket:fs/index" { * @param {string} src - The source file path. * @param {string} dest - The destination file path. * @param {number} flags - Modifiers for copy operation. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscopyfilesrc-dest-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFileSync(src: string, dest: string, flags: number): void; /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options} + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path * @param {object?} [options] * @returns {ReadStream} */ export function createReadStream(path: string | Buffer | URL, options?: object | null): ReadStream; /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fscreatewritestreampath-options} + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path * @param {object?} [options] * @returns {WriteStream} @@ -4089,7 +4092,7 @@ declare module "socket:fs/index" { * Invokes the callback with the for the file descriptor. See * the POSIX fstat(2) documentation for more detail. * - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsfstatfd-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fsfstatfd-options-callback} * * @param {number} fd - A file descriptor. * @param {object?|function?} [options] - An options object. @@ -4135,7 +4138,7 @@ declare module "socket:fs/index" { export function mkdirSync(path: any, options: any): void; /** * Asynchronously open a file calling `callback` upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsopenpath-flags-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} * @param {string | Buffer | URL} path * @param {string?} [flags = 'r'] * @param {string?} [mode = 0o666] @@ -4145,7 +4148,7 @@ declare module "socket:fs/index" { export function open(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any, callback?: ((arg0: Error | null, arg1: number | null) => any) | null): void; /** * Asynchronously open a directory calling `callback` upon success or error. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} * @param {string | Buffer | URL} path * @param {object?|function(Error?, Dir?)} [options] * @param {string?} [options.encoding = 'utf8'] @@ -4155,7 +4158,7 @@ declare module "socket:fs/index" { export function opendir(path: string | Buffer | URL, options: {}, callback: ((arg0: Error | null, arg1: Dir | null) => any) | null): void; /** * Asynchronously read from an open file descriptor. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreadfd-buffer-offset-length-position-callback} + * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} * @param {number} fd * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. * @param {number} offset - The position in buffer to write the data to. @@ -4164,9 +4167,20 @@ declare module "socket:fs/index" { * @param {function(Error?, number?, Buffer?)} callback */ export function read(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; + /** + * Asynchronously write to an open file descriptor. + * @see {@link https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback} + * @param {number} fd + * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. + * @param {number} offset - The position in buffer to write the data to. + * @param {number} length - The number of bytes to read. + * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. + * @param {function(Error?, number?, Buffer?)} callback + */ + export function write(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; /** * Asynchronously read all entries in a directory. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fsreaddirpath-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} * @param {string | Buffer | URL } path * @param {object?|function(Error?, object[])} [options] * @param {string?} [options.encoding ? 'utf8'] @@ -4257,7 +4271,7 @@ declare module "socket:fs/index" { */ export function unlink(path: string, callback: Function): void; /** - * @see {@url https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fswritefilefile-data-options-callback} + * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} * @param {string | Buffer | URL | number } path - filename or file descriptor * @param {string | Buffer | TypedArray | DataView | object } data * @param {object?} options @@ -4518,13 +4532,11 @@ declare module "socket:ipc" { [k: string]: string; }; } - const Message_base: any; /** * A container for a IPC message based on a `ipc://` URI scheme. * @ignore */ - export class Message extends Message_base { - [x: string]: any; + export class Message extends URL { /** * The expected protocol for an IPC message. * @ignore @@ -4630,7 +4642,7 @@ declare module "socket:ipc" { * @param {any} value * @ignore */ - set(key: string, value: any): any; + set(key: string, value: any): void; /** * Get a parameter value by `key`. * @param {string} key @@ -4761,7 +4773,6 @@ declare module "socket:ipc" { export const primordials: any; export default exports; import { Buffer } from "socket:buffer"; - import { URL } from "socket:url/index"; import * as exports from "socket:ipc"; } @@ -5179,9 +5190,10 @@ declare module "socket:errors" { /** * `ModuleNotFoundError` class constructor. * @param {string} message + * @param {string[]=} [requireStack] */ - constructor(message: string, requireStack: any); - requireStack: any; + constructor(message: string, requireStack?: string[] | undefined); + requireStack: string[]; } /** * An `OperationError` is an error type thrown when an internal exception @@ -6070,6 +6082,7 @@ declare module "socket:application" { * @param {boolean=} [opts.canExit=false] - whether the window can exit the app * @param {boolean=} [opts.headless=false] - whether the window will be headless or not (no frame) * @param {string=} [opts.userScript=null] - A user script that will be injected into the window (desktop only) + * @param {string[]=} [opts.protocolHandlers] - An array of protocol handler schemes to register with the new window (requires service worker) * @return {Promise} */ export function createWindow(opts: { @@ -6098,6 +6111,7 @@ declare module "socket:application" { canExit?: boolean | undefined; headless?: boolean | undefined; userScript?: string | undefined; + protocolHandlers?: string[] | undefined; }): Promise; /** * Returns the current screen size. @@ -7128,6 +7142,10 @@ declare module "socket:child_process" { * @type {boolean} */ get reading(): boolean; + /** + * @type {import('./process')} + */ + get process(): typeof import("socket:process"); /** * Destroys the pipe */ @@ -7135,7 +7153,23 @@ declare module "socket:child_process" { #private; } export class ChildProcess extends EventEmitter { - constructor(options?: {}); + /** + * `ChildProcess` class constructor. + * @param {{ + * env?: object, + * stdin?: boolean, + * stdout?: boolean, + * stderr?: boolean, + * signal?: AbortSigal, + * }=} [options] + */ + constructor(options?: { + env?: object; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + signal?: AbortSigal; + } | undefined); /** * @ignore * @type {Pipe} @@ -7265,6 +7299,7 @@ declare module "socket:child_process" { import { AsyncResource } from "socket:async/resource"; import { EventEmitter } from "socket:events"; import { Worker } from "socket:worker_threads"; + import signal from "socket:signal"; } declare module "socket:constants" { @@ -8144,21 +8179,6 @@ declare module "socket:fetch/fetch" { } } -declare module "socket:fetch/index" { - export default fetch; - import { fetch } from "socket:fetch/fetch"; - import { Headers } from "socket:fetch/fetch"; - import { Request } from "socket:fetch/fetch"; - import { Response } from "socket:fetch/fetch"; - export { fetch, Headers, Request, Response }; -} - -declare module "socket:fetch" { - export * from "socket:fetch/index"; - export default fetch; - import fetch from "socket:fetch/index"; -} - declare module "socket:service-worker/database" { /** * A typed container for optional options given to the `Database` @@ -8689,6 +8709,12 @@ declare module "socket:service-worker/context" { * @param {import('./events.js').ExtendableEvent} event */ constructor(event: import('./events.js').ExtendableEvent); + /** + * Context data. This may be a custom protocol handler scheme data + * by default, if available. + * @type {any?} + */ + data: any | null; /** * The `ExtendableEvent` for this `Context` instance. * @type {ExtendableEvent} @@ -8786,6 +8812,7 @@ declare module "socket:service-worker/events" { * request and how the receiver will treat the response. */ export class FetchEvent extends ExtendableEvent { + static defaultHeaders: Headers; /** * `FetchEvent` class constructor. * @ignore @@ -8956,6 +8983,69 @@ declare module "socket:http" { * @type {object} */ export const STATUS_CODES: object; + export const CONTINUE: 100; + export const SWITCHING_PROTOCOLS: 101; + export const PROCESSING: 102; + export const EARLY_HINTS: 103; + export const OK: 200; + export const CREATED: 201; + export const ACCEPTED: 202; + export const NONAUTHORITATIVE_INFORMATION: 203; + export const NO_CONTENT: 204; + export const RESET_CONTENT: 205; + export const PARTIAL_CONTENT: 206; + export const MULTISTATUS: 207; + export const ALREADY_REPORTED: 208; + export const IM_USED: 226; + export const MULTIPLE_CHOICES: 300; + export const MOVED_PERMANENTLY: 301; + export const FOUND: 302; + export const SEE_OTHER: 303; + export const NOT_MODIFIED: 304; + export const USE_PROXY: 305; + export const TEMPORARY_REDIRECT: 307; + export const PERMANENT_REDIRECT: 308; + export const BAD_REQUEST: 400; + export const UNAUTHORIZED: 401; + export const PAYMENT_REQUIRED: 402; + export const FORBIDDEN: 403; + export const NOT_FOUND: 404; + export const METHOD_NOT_ALLOWED: 405; + export const NOT_ACCEPTABLE: 406; + export const PROXY_AUTHENTICATION_REQUIRED: 407; + export const REQUEST_TIMEOUT: 408; + export const CONFLICT: 409; + export const GONE: 410; + export const LENGTH_REQUIRED: 411; + export const PRECONDITION_FAILED: 412; + export const PAYLOAD_TOO_LARGE: 413; + export const URI_TOO_LONG: 414; + export const UNSUPPORTED_MEDIA_TYPE: 415; + export const RANGE_NOT_SATISFIABLE: 416; + export const EXPECTATION_FAILED: 417; + export const IM_A_TEAPOT: 418; + export const MISDIRECTED_REQUEST: 421; + export const UNPROCESSABLE_ENTITY: 422; + export const LOCKED: 423; + export const FAILED_DEPENDENCY: 424; + export const TOO_EARLY: 425; + export const UPGRADE_REQUIRED: 426; + export const PRECONDITION_REQUIRED: 428; + export const TOO_MANY_REQUESTS: 429; + export const REQUEST_HEADER_FIELDS_TOO_LARGE: 431; + export const UNAVAILABLE_FOR_LEGAL_REASONS: 451; + export const INTERNAL_SERVER_ERROR: 500; + export const NOT_IMPLEMENTED: 501; + export const BAD_GATEWAY: 502; + export const SERVICE_UNAVAILABLE: 503; + export const GATEWAY_TIMEOUT: 504; + export const HTTP_VERSION_NOT_SUPPORTED: 505; + export const VARIANT_ALSO_NEGOTIATES: 506; + export const INSUFFICIENT_STORAGE: 507; + export const LOOP_DETECTED: 508; + export const BANDWIDTH_LIMIT_EXCEEDED: 509; + export const NOT_EXTENDED: 510; + export const NETWORK_AUTHENTICATION_REQUIRED: 511; /** * The parent class of `ClientRequest` and `ServerResponse`. * It is an abstract outgoing message from the perspective of the @@ -9585,6 +9675,21 @@ declare module "socket:http" { } +declare module "socket:fetch/index" { + export default fetch; + import { fetch } from "socket:fetch/fetch"; + import { Headers } from "socket:fetch/fetch"; + import { Request } from "socket:fetch/fetch"; + import { Response } from "socket:fetch/fetch"; + export { fetch, Headers, Request, Response }; +} + +declare module "socket:fetch" { + export * from "socket:fetch/index"; + export default fetch; + import fetch from "socket:fetch/index"; +} + declare module "socket:https" { /** * Makes a HTTPS request, optionally a `socket://` for relative paths when @@ -9613,6 +9718,69 @@ declare module "socket:https" { * @return {Server} */ export function createServer(...args: any[]): Server; + export const CONTINUE: 100; + export const SWITCHING_PROTOCOLS: 101; + export const PROCESSING: 102; + export const EARLY_HINTS: 103; + export const OK: 200; + export const CREATED: 201; + export const ACCEPTED: 202; + export const NONAUTHORITATIVE_INFORMATION: 203; + export const NO_CONTENT: 204; + export const RESET_CONTENT: 205; + export const PARTIAL_CONTENT: 206; + export const MULTISTATUS: 207; + export const ALREADY_REPORTED: 208; + export const IM_USED: 226; + export const MULTIPLE_CHOICES: 300; + export const MOVED_PERMANENTLY: 301; + export const FOUND: 302; + export const SEE_OTHER: 303; + export const NOT_MODIFIED: 304; + export const USE_PROXY: 305; + export const TEMPORARY_REDIRECT: 307; + export const PERMANENT_REDIRECT: 308; + export const BAD_REQUEST: 400; + export const UNAUTHORIZED: 401; + export const PAYMENT_REQUIRED: 402; + export const FORBIDDEN: 403; + export const NOT_FOUND: 404; + export const METHOD_NOT_ALLOWED: 405; + export const NOT_ACCEPTABLE: 406; + export const PROXY_AUTHENTICATION_REQUIRED: 407; + export const REQUEST_TIMEOUT: 408; + export const CONFLICT: 409; + export const GONE: 410; + export const LENGTH_REQUIRED: 411; + export const PRECONDITION_FAILED: 412; + export const PAYLOAD_TOO_LARGE: 413; + export const URI_TOO_LONG: 414; + export const UNSUPPORTED_MEDIA_TYPE: 415; + export const RANGE_NOT_SATISFIABLE: 416; + export const EXPECTATION_FAILED: 417; + export const IM_A_TEAPOT: 418; + export const MISDIRECTED_REQUEST: 421; + export const UNPROCESSABLE_ENTITY: 422; + export const LOCKED: 423; + export const FAILED_DEPENDENCY: 424; + export const TOO_EARLY: 425; + export const UPGRADE_REQUIRED: 426; + export const PRECONDITION_REQUIRED: 428; + export const TOO_MANY_REQUESTS: 429; + export const REQUEST_HEADER_FIELDS_TOO_LARGE: 431; + export const UNAVAILABLE_FOR_LEGAL_REASONS: 451; + export const INTERNAL_SERVER_ERROR: 500; + export const NOT_IMPLEMENTED: 501; + export const BAD_GATEWAY: 502; + export const SERVICE_UNAVAILABLE: 503; + export const GATEWAY_TIMEOUT: 504; + export const HTTP_VERSION_NOT_SUPPORTED: 505; + export const VARIANT_ALSO_NEGOTIATES: 506; + export const INSUFFICIENT_STORAGE: 507; + export const LOOP_DETECTED: 508; + export const BANDWIDTH_LIMIT_EXCEEDED: 509; + export const NOT_EXTENDED: 510; + export const NETWORK_AUTHENTICATION_REQUIRED: 511; /** * All known possible HTTP methods. * @type {string[]} @@ -11940,330 +12108,627 @@ declare module "socket:timers" { import * as exports from "socket:timers/index"; } -declare module "socket:module" { - export function isBuiltin(name: any): boolean; +declare module "socket:commonjs/builtins" { /** - * Creates a `require` function from a source URL. - * @param {URL|string} sourcePath - * @return {function} + * Predicate to determine if a given module name is a builtin module. + * @param {string} name + * @param {{ builtins?: object }} + * @return {boolean} */ - export function createRequire(sourcePath: URL | string): Function; + export function isBuiltin(name: string, options?: any): boolean; /** - * A limited set of builtins exposed to CommonJS modules. + * Gets a builtin module by name. + * @param {string} name + * @param {{ builtins?: object }} + * @return {any} */ - export const builtins: { - async: typeof _async; - async_context: { - AsyncLocalStorage: typeof AsyncLocalStorage; - AsyncResource: typeof AsyncResource; - }; - async_hooks: { - AsyncLocalStorage: typeof AsyncLocalStorage; - AsyncResource: typeof AsyncResource; - executionAsyncResource: typeof executionAsyncResource; - executionAsyncId: typeof executionAsyncId; - triggerAsyncId: typeof triggerAsyncId; - createHook: typeof createHook; - AsyncHook: typeof AsyncHook; - }; - application: typeof application; - assert: typeof import("socket:assert").assert & { - AssertionError: typeof import("socket:assert").AssertionError; - ok: typeof import("socket:assert").ok; - equal: typeof import("socket:assert").equal; - notEqual: typeof import("socket:assert").notEqual; - strictEqual: typeof import("socket:assert").strictEqual; - notStrictEqual: typeof import("socket:assert").notStrictEqual; - deepEqual: typeof import("socket:assert").deepEqual; - notDeepEqual: typeof import("socket:assert").notDeepEqual; - }; - buffer: typeof buffer; - console: import("socket:console").Console; - constants: any; - child_process: { - ChildProcess: typeof import("socket:child_process").ChildProcess; - spawn: typeof import("socket:child_process").spawn; - execFile: typeof import("socket:child_process").exec; - exec: typeof import("socket:child_process").exec; - }; - crypto: typeof crypto; - dgram: typeof dgram; - dns: typeof dns; - 'dns/promises': typeof dns.promises; - events: typeof events; - extension: { - load: typeof import("socket:extension").load; - stats: typeof import("socket:extension").stats; - }; - fs: typeof fs; - 'fs/promises': typeof fs.promises; - http: typeof http; - https: typeof http; - gc: any; - ipc: typeof ipc; - language: { - codes: string[]; - describe: typeof import("socket:language").describe; - lookup: typeof import("socket:language").lookup; - names: string[]; - tags: import("socket:enumeration").Enumeration; - }; - location: { - origin: string; - href: string; - protocol: string; - hostname: string; - host: string; - search: string; - pathname: string; - toString: typeof import("socket:location").toString; - }; - mime: typeof mime; - net: {}; - network: (options: any) => Promise; - os: typeof os; - path: typeof path; - perf_hooks: { - performance: Performance; - }; - process: any; - querystring: { - decode: typeof import("socket:querystring").parse; - encode: typeof import("socket:querystring").stringify; - parse: typeof import("socket:querystring").parse; - stringify: typeof import("socket:querystring").stringify; - escape: typeof import("socket:querystring").escape; - unescape: typeof import("socket:querystring").unescape; - }; - stream: typeof stream; - 'stream/web': typeof stream.web; - string_decoder: typeof string_decoder; - sys: typeof util; - test: typeof test; - timers: typeof timers; - 'timers/promises': any; - tty: { - isatty: () => boolean; - WriteStream: typeof util.IllegalConstructor; - ReadStream: typeof util.IllegalConstructor; - }; - util: typeof util; - url: any; - vm: { - createGlobalObject: typeof import("socket:vm").createGlobalObject; - compileFunction: typeof import("socket:vm").compileFunction; - createReference: typeof import("socket:vm").createReference; - getContextWindow: typeof import("socket:vm").getContextWindow; - getContextWorker: typeof import("socket:vm").getContextWorker; - getReference: typeof import("socket:vm").getReference; - getTransferables: typeof import("socket:vm").getTransferables; - putReference: typeof import("socket:vm").putReference; - Reference: typeof import("socket:vm").Reference; - removeReference: typeof import("socket:vm").removeReference; - runInContext: typeof import("socket:vm").runInContext; - runInNewContext: typeof import("socket:vm").runInNewContext; - runInThisContext: typeof import("socket:vm").runInThisContext; - Script: typeof import("socket:vm").Script; - createContext: typeof import("socket:vm").createContext; - isContext: typeof import("socket:vm").isContext; - }; - window: typeof window; - worker_threads: { - Worker: typeof import("socket:worker_threads").Worker; - isMainThread: boolean; - parentPort: import("socket:worker_threads").MessagePort; - setEnvironmentData: typeof import("socket:worker_threads").setEnvironmentData; - getEnvironmentData: typeof import("socket:worker_threads").getEnvironmentData; - workerData: any; - threadId: number; - SHARE_ENV: symbol; - }; + export function getBuiltin(name: string, options?: any): any; + /** + * A mapping of builtin modules + * @type {object} + */ + export const builtins: object; + /** + * Known runtime specific builtin modules. + * @type {string[]} + */ + export const runtimeModules: string[]; + export default builtins; +} + +declare module "socket:commonjs/loader" { + /** + * @typedef {{ + * extensions?: string[] | Set + * origin?: URL | string, + * statuses?: Map + * cache?: Map + * }} LoaderOptions + */ + /** + * @typedef {{ + * loader?: Loader, + * origin?: URL | string + * }} RequestOptions + */ + /** + * @typedef {{ + * headers?: Headers | object + * }} RequestLoadOptions + */ + /** + * @typedef {{ + * request?: Request, + * headers?: Headers, + * status?: number, + * text?: string + * }} ResponseOptions + */ + /** + * A container for the status of a CommonJS resource. A `RequestStatus` object + * represents meta data for a `Request` that comes from a preflight + * HTTP HEAD request. + */ + export class RequestStatus { + /** + * `RequestStatus` class constructor. + * @param {Request} request + */ + constructor(request: Request); + /** + * The unique ID of this `RequestStatus`, which is the absolute URL as a string. + * @type {string} + */ + get id(): string; + /** + * The origin for this `RequestStatus` object. + * @type {string} + */ + get origin(): string; + /** + * A HTTP status code for this `RequestStatus` object. + * @type {number|undefined} + */ + get status(): number; + /** + * An alias for `status`. + * @type {number|undefined} + */ + get value(): number; + /** + * @ignore + */ + get valueOf(): number; + /** + * The HTTP headers for this `RequestStatus` object. + * @type {Headers} + */ + get headers(): Headers; + /** + * The resource location for this `RequestStatus` object. This value is + * determined from the 'Content-Location' header, if available, otherwise + * it is derived from the request URL pathname (including the query string). + * @type {string} + */ + get location(): string; + /** + * `true` if the response status is considered OK, otherwise `false`. + * @type {boolean} + */ + get ok(): boolean; + /** + * Loads the internal state for this `RequestStatus` object. + * @param {RequestLoadOptions|boolean} [options] + * @return {RequestStatus} + */ + load(options?: RequestLoadOptions | boolean): RequestStatus; + #private; + } + /** + * A container for a synchronous CommonJS request to local resource or + * over the network. + */ + export class Request { + /** + * `Request` class constructor. + * @param {URL|string} url + * @param {URL|string=} [origin] + * @param {RequestOptions=} [options] + */ + constructor(url: URL | string, origin?: (URL | string) | undefined, options?: RequestOptions | undefined); + /** + * The unique ID of this `Request`, which is the absolute URL as a string. + * @type {string} + */ + get id(): string; + /** + * The absolute `URL` of this `Request` object. + * @type {URL} + */ + get url(): URL; + /** + * The origin for this `Request`. + * @type {string} + */ + get origin(): string; + /** + * The `Loader` for this `Request` object. + * @type {Loader?} + */ + get loader(): Loader; + /** + * The `RequestStatus` for this `Request` + * @type {RequestStatus} + */ + get status(): RequestStatus; + /** + * Loads the CommonJS source file, optionally checking the `Loader` cache + * first, unless ignored when `options.cache` is `false`. + * @param {RequestLoadOptions=} [options] + * @return {Response} + */ + load(options?: RequestLoadOptions | undefined): Response; + #private; + } + /** + * A container for a synchronous CommonJS request response for a local resource + * or over the network. + */ + export class Response { + /** + * `Response` class constructor. + * @param {Request|ResponseOptions} request + * @param {ResponseOptions=} [options] + */ + constructor(request: Request | ResponseOptions, options?: ResponseOptions | undefined); + /** + * The unique ID of this `Response`, which is the absolute + * URL of the request as a string. + * @type {string} + */ + get id(): string; + /** + * The `Request` object associated with this `Response` object. + * @type {Request} + */ + get request(): Request; + /** + * The `Loader` associated with this `Response` object. + * @type {Loader?} + */ + get loader(): Loader; + /** + * The `Response` status code from the associated `Request` object. + * @type {number} + */ + get status(): number; + /** + * The `Response` string from the associated `Request` + * @type {string} + */ + get text(): string; + /** + * `true` if the response is considered OK, otherwise `false`. + * @type {boolean} + */ + get ok(): boolean; + #private; + } + /** + * A container for loading CommonJS module sources + */ + export class Loader { + /** + * A request class used by `Loader` objects. + * @type {typeof Request} + */ + static Request: typeof Request; + /** + * A response class used by `Loader` objects. + * @type {typeof Request} + */ + static Response: typeof Request; + /** + * Resolves a given module URL to an absolute URL with an optional `origin`. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} + */ + static resolve(url: URL | string, origin?: URL | string): string; + /** + * Default extensions for a loader. + * @type {Set} + */ + static defaultExtensions: Set; + /** + * `Loader` class constructor. + * @param {string|URL|LoaderOptions} origin + * @param {LoaderOptions=} [options] + */ + constructor(origin: string | URL | LoaderOptions, options?: LoaderOptions | undefined); + /** + * The internal cache for this `Loader` object. + * @type {Map} + */ + get cache(): Map; + /** + * The internal statuses for this `Loader` object. + * @type {Map} + */ + get statuses(): Map; + /** + * A set of supported `Loader` extensions. + * @type {Set} + */ + get extensions(): Set; + set origin(origin: string); + /** + * The origin of this `Loader` object. + * @type {string} + */ + get origin(): string; + /** + * Loads a CommonJS module source file at `url` with an optional `origin`, which + * defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {Response} + */ + load(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): Response; + /** + * Queries the status of a CommonJS module source file at `url` with an + * optional `origin`, which defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {RequestStatus} + */ + status(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): RequestStatus; + /** + * Resolves a given module URL to an absolute URL based on the loader origin. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} + */ + resolve(url: URL | string, origin?: URL | string): string; + #private; + } + export default Loader; + export type LoaderOptions = { + extensions?: string[] | Set; + origin?: URL | string; + statuses?: Map; + cache?: Map; }; - export const builtinModules: { - async: typeof _async; - async_context: { - AsyncLocalStorage: typeof AsyncLocalStorage; - AsyncResource: typeof AsyncResource; - }; - async_hooks: { - AsyncLocalStorage: typeof AsyncLocalStorage; - AsyncResource: typeof AsyncResource; - executionAsyncResource: typeof executionAsyncResource; - executionAsyncId: typeof executionAsyncId; - triggerAsyncId: typeof triggerAsyncId; - createHook: typeof createHook; - AsyncHook: typeof AsyncHook; - }; - application: typeof application; - assert: typeof import("socket:assert").assert & { - AssertionError: typeof import("socket:assert").AssertionError; - ok: typeof import("socket:assert").ok; - equal: typeof import("socket:assert").equal; - notEqual: typeof import("socket:assert").notEqual; - strictEqual: typeof import("socket:assert").strictEqual; - notStrictEqual: typeof import("socket:assert").notStrictEqual; - deepEqual: typeof import("socket:assert").deepEqual; - notDeepEqual: typeof import("socket:assert").notDeepEqual; - }; - buffer: typeof buffer; - console: import("socket:console").Console; - constants: any; - child_process: { - ChildProcess: typeof import("socket:child_process").ChildProcess; - spawn: typeof import("socket:child_process").spawn; - execFile: typeof import("socket:child_process").exec; - exec: typeof import("socket:child_process").exec; - }; - crypto: typeof crypto; - dgram: typeof dgram; - dns: typeof dns; - 'dns/promises': typeof dns.promises; - events: typeof events; - extension: { - load: typeof import("socket:extension").load; - stats: typeof import("socket:extension").stats; - }; - fs: typeof fs; - 'fs/promises': typeof fs.promises; - http: typeof http; - https: typeof http; - gc: any; - ipc: typeof ipc; - language: { - codes: string[]; - describe: typeof import("socket:language").describe; - lookup: typeof import("socket:language").lookup; - names: string[]; - tags: import("socket:enumeration").Enumeration; - }; - location: { - origin: string; - href: string; - protocol: string; - hostname: string; - host: string; - search: string; - pathname: string; - toString: typeof import("socket:location").toString; - }; - mime: typeof mime; - net: {}; - network: (options: any) => Promise; - os: typeof os; - path: typeof path; - perf_hooks: { - performance: Performance; - }; - process: any; - querystring: { - decode: typeof import("socket:querystring").parse; - encode: typeof import("socket:querystring").stringify; - parse: typeof import("socket:querystring").parse; - stringify: typeof import("socket:querystring").stringify; - escape: typeof import("socket:querystring").escape; - unescape: typeof import("socket:querystring").unescape; - }; - stream: typeof stream; - 'stream/web': typeof stream.web; - string_decoder: typeof string_decoder; - sys: typeof util; - test: typeof test; - timers: typeof timers; - 'timers/promises': any; - tty: { - isatty: () => boolean; - WriteStream: typeof util.IllegalConstructor; - ReadStream: typeof util.IllegalConstructor; - }; - util: typeof util; - url: any; - vm: { - createGlobalObject: typeof import("socket:vm").createGlobalObject; - compileFunction: typeof import("socket:vm").compileFunction; - createReference: typeof import("socket:vm").createReference; - getContextWindow: typeof import("socket:vm").getContextWindow; - getContextWorker: typeof import("socket:vm").getContextWorker; - getReference: typeof import("socket:vm").getReference; - getTransferables: typeof import("socket:vm").getTransferables; - putReference: typeof import("socket:vm").putReference; - Reference: typeof import("socket:vm").Reference; - removeReference: typeof import("socket:vm").removeReference; - runInContext: typeof import("socket:vm").runInContext; - runInNewContext: typeof import("socket:vm").runInNewContext; - runInThisContext: typeof import("socket:vm").runInThisContext; - Script: typeof import("socket:vm").Script; - createContext: typeof import("socket:vm").createContext; - isContext: typeof import("socket:vm").isContext; - }; - window: typeof window; - worker_threads: { - Worker: typeof import("socket:worker_threads").Worker; - isMainThread: boolean; - parentPort: import("socket:worker_threads").MessagePort; - setEnvironmentData: typeof import("socket:worker_threads").setEnvironmentData; - getEnvironmentData: typeof import("socket:worker_threads").getEnvironmentData; - workerData: any; - threadId: number; - SHARE_ENV: symbol; - }; + export type RequestOptions = { + loader?: Loader; + origin?: URL | string; + }; + export type RequestLoadOptions = { + headers?: Headers | object; + }; + export type ResponseOptions = { + request?: Request; + headers?: Headers; + status?: number; + text?: string; + }; + import { Headers } from "socket:ipc"; +} + +declare module "socket:commonjs/package" { + /** + * @typedef {{ + * prefix?: string, + * manifest?: string, + * index?: string, + * description?: string, + * version?: string, + * license?: string, + * exports?: object, + * type?: 'commonjs' | 'module', + * info?: object + * }} PackageOptions + */ + /** + * @typedef {import('./loader.js').RequestOptions & { + * type?: 'commonjs' | 'module' + * }} PackageLoadOptions + */ + /** + * {import('./loader.js').RequestOptions & { + * load?: boolean, + * type?: 'commonjs' | 'module', + * browser?: boolean, + * children?: string[] + * extensions?: string[] | Set + * }} PackageResolveOptions + */ + /** + * The default package index file such as 'index.js' + * @type {string} + */ + export const DEFAULT_PACKAGE_INDEX: string; + /** + * The default package manifest file name such as 'package.json' + * @type {string} + */ + export const DEFAULT_PACKAGE_MANIFEST_FILE_NAME: string; + /** + * The default package path prefix such as 'node_modules/' + * @type {string} + */ + export const DEFAULT_PACKAGE_PREFIX: string; + /** + * The default package version, when one is not provided + * @type {string} + */ + export const DEFAULT_PACKAGE_VERSION: string; + /** + * The default license for a package' + * @type {string} + */ + export const DEFAULT_LICENSE: string; + /** + * A container for CommonJS module metadata, often in a `package.json` file. + */ + export class Package { + /** + * @param {string} input + * @param {PackageOptions&PackageLoadOptions} [options] + * @return {Package?} + */ + static find(input: string, options?: PackageOptions & PackageLoadOptions): Package | null; + /** + * `Package` class constructor. + * @param {string} name + * @param {PackageOptions} [options] + */ + constructor(name: string, options?: PackageOptions); + /** + * The unique ID of this `Package`, which is the absolute + * URL of the directory that contains its manifest file. + * @type {string} + */ + get id(): string; + /** + * The absolute URL to the package manifest file + * @type {string} + */ + get url(): string; + /** + * The package module path prefix. + * @type {string} + */ + get prefix(): string; + /** + * A loader for this package, if available. This value may be `null`. + * @type {Loader} + */ + get loader(): Loader; + /** + * The name of the package. + * @type {string} + */ + get name(): string; + /** + * The description of the package. + * @type {string} + */ + get description(): string; + /** + * The license of the package. + * @type {string} + */ + get license(): string; + /** + * The version of the package. + * @type {string} + */ + get version(): string; + /** + * The origin for this package. + * @type {string} + */ + get origin(): string; + /** + * The exports mappings for the package + * @type {object} + */ + get exports(): any; + /** + * The package type. + * @type {'commonjs'|'module'} + */ + get type(): "module" | "commonjs"; + /** + * The raw package metadata object. + * @type {object?} + */ + get info(): any; + /** + * The entry to the package + * @type {string?} + */ + get entry(): string; + /** + * Load the package information at an optional `origin` with + * optional request `options`. + * @param {string|PackageLoadOptions=} [origin] + * @param {PackageLoadOptions=} [options] + * @throws SyntaxError + * @return {boolean} + */ + load(origin?: (string | PackageLoadOptions) | undefined, options?: PackageLoadOptions | undefined): boolean; + /** + * Resolve a file's `pathname` within the package. + * @param {string|URL} pathname + * @param {PackageResolveOptions=} [options] + * @return {string} + */ + resolve(pathname: string | URL, options?: PackageResolveOptions): string; + #private; + } + export default Package; + export type PackageOptions = { + prefix?: string; + manifest?: string; + index?: string; + description?: string; + version?: string; + license?: string; + exports?: object; + type?: 'commonjs' | 'module'; + info?: object; + }; + export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { + type?: 'commonjs' | 'module'; }; + import { Loader } from "socket:commonjs/loader"; +} + +declare module "socket:commonjs/module" { + /** + * CommonJS module scope with module scoped globals. + * @ignore + * @param {object} exports + * @param {function(string): any} require + * @param {Module} module + * @param {string} __filename + * @param {string} __dirname + */ + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string): Promise; + export function createRequire(url: any, options?: any): any; + /** + * @typedef {import('./require.js').RequireResolver[]} ModuleResolver + */ + /** + * @typedef {import('./require.js').RequireFunction} RequireFunction + */ + /** + * @typedef {import('./package.js').PackageOptions} PackageOptions + */ + /** + * @typedef {{ + * prefix?: string, + * request?: import('./loader.js').RequestOptions, + * builtins?: object + * } CreateRequireOptions + */ + /** + * @typedef {{ + * resolvers?: ModuleResolver[], + * importmap?: ImportMap, + * loader?: Loader | object, + * package?: Package | PackageOptions + * parent?: Module, + * state?: State + * }} ModuleOptions + */ + export const builtinModules: any; /** * CommonJS module scope source wrapper. * @type {string} */ export const COMMONJS_WRAPPER: string; /** - * The main entry source origin. - * @type {string} + * A container for imports. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap} */ - export const MAIN_SOURCE_ORIGIN: string; - export namespace scope { - let current: any; - let previous: any; + export class ImportMap { + set imports(imports: any); + /** + * The imports object for the importmap. + * @type {object} + */ + get imports(): any; + /** + * Extends the current imports object. + * @param {object} imports + * @return {ImportMap} + */ + extend(importmap: any): ImportMap; + #private; + } + /** + * A container for `Module` instance state. + */ + export class State { + /** + * `State` class constructor. + * @ignore + * @param {object|State=} [state] + */ + constructor(state?: (object | State) | undefined); + loading: boolean; + loaded: boolean; + error: any; + } + /** + * The module scope for a loaded module. + * This is a special object that is seal, frozen, and only exposes an + * accessor the 'exports' field. + */ + export class Scope { + set exports(exports: any); + get exports(): any; + toJSON(): { + exports: any; + }; + #private; } /** * A container for a loaded CommonJS module. All errors bubble * to the "main" module and global object (if possible). */ export class Module extends EventTarget { - static set current(module: Module); /** * A reference to the currently scoped module. * @type {Module?} */ - static get current(): Module; - static set previous(module: Module); + static current: Module | null; /** * A reference to the previously scoped module. * @type {Module?} */ - static get previous(): Module; + static previous: Module | null; /** - * Module cache. - * @ignore + * A cache of loaded modules + * @type {Map} */ - static cache: any; + static cache: Map; /** - * Custom module resolvers. - * @type {Array} + * An array of globally available module loader resolvers. + * @type {ModuleResolver[]} */ - static resolvers: Array; + static resolvers: ModuleResolver[]; /** - * CommonJS module scope source wrapper. - * @ignore + * Globally available 'importmap' for all loaded modules. + * @type {ImportMap} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap} */ - static wrapper: string; + static importmap: ImportMap; /** * A limited set of builtins exposed to CommonJS modules. * @type {object} */ static builtins: object; /** - * Creates a `require` function from a source URL. - * @param {URL|string} sourcePath - * @return {function} + * A limited set of builtins exposed to CommonJS modules. + * @type {object} + */ + static builtinModules: object; + /** + * CommonJS module scope source wrapper components. + * @type {string[]} */ - static createRequire(sourcePath: URL | string): Function; + static wrapper: string[]; + /** + * An array of global require paths, relative to the origin. + * @type {string[]} + */ + static globalPaths: string[]; /** * The main entry module, lazily created. * @type {Module} @@ -12271,135 +12736,218 @@ declare module "socket:module" { static get main(): Module; /** * Wraps source in a CommonJS module scope. + * @param {string} source */ - static wrap(source: any): string; + static wrap(source: string): string; /** * Creates a `Module` from source URL and optionally a parent module. - * @param {string|URL|Module} [sourcePath] - * @param {string|URL|Module} [parent] + * @param {string|URL|Module} url + * @param {ModuleOptions=} [options] */ - static from(sourcePath?: string | URL | Module, parent?: string | URL | Module): any; + static from(url: string | URL | Module, options?: ModuleOptions | undefined): any; + /** + * Creates a `require` function from a given module URL. + * @param {string|URL} url + * @param {ModuleOptions=} [options] + */ + static createRequire(url: string | URL, options?: ModuleOptions | undefined): any; /** * `Module` class constructor. - * @ignore + * @param {string|URL} url + * @param {ModuleOptions=} [options] */ - constructor(id: any, parent?: any, sourcePath?: any); + constructor(url: string | URL, options?: ModuleOptions | undefined); /** - * The module id, most likely a file name. + * A unique ID for this module. * @type {string} */ - id: string; + get id(): string; /** - * The parent module, if given. - * @type {Module?} + * A reference to the "main" module. + * @type {Module} */ - parent: Module | null; + get main(): Module; /** - * `true` if the module did load successfully. - * @type {boolean} + * Child modules of this module. + * @type {Module[]} */ - loaded: boolean; + get children(): Module[]; /** - * `true` if the module is currently being loaded. - * @type {boolean} + * A reference to the module cache. Possibly shared with all + * children modules. + * @type {object} */ - loading: boolean; + get cache(): any; /** - * The module's exports. - * @type {any} + * A reference to the module package. + * @type {Package} */ - exports: any; + get package(): Package; /** - * The filename of the module. - * @type {string} + * The `ImportMap` for this module. + * @type {ImportMap} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap} */ - filename: string; + get importmap(): ImportMap; /** - * Modules children to this one, as in they were required in this - * module scope context. - * @type {Array} + * The module level resolvers. + * @type {ModuleResolver[]} */ - children: Array; + get resolvers(): ModuleResolver[]; /** - * The original source URL to load this module. - * @type {string} + * `true` if the module is currently loading, otherwise `false`. + * @type {boolean} */ - sourcePath: string; + get loading(): boolean; /** - * `true` if the module is the main module. + * `true` if the module is currently loaded, otherwise `false`. * @type {boolean} */ - get isMain(): boolean; + get loaded(): boolean; /** - * `true` if the module was loaded by name, not file path. - * @type {boolean} + * An error associated with the module if it failed to load. + * @type {Error?} */ - get isNamed(): boolean; + get error(): Error; /** - * @type {URL} + * The exports of the module + * @type {object} */ - get url(): URL; + get exports(): any; /** - * @type {string} + * The scope of the module given to parsed modules. + * @type {Scope} */ - get pathname(): string; + get scope(): Scope; /** + * The origin of the loaded module. * @type {string} */ - get path(): string; + get origin(): string; /** - * Loads the module, synchronously returning `true` upon success, - * otherwise `false`. - * @return {boolean} + * The parent module for this module. + * @type {Module?} */ - load(): boolean; + get parent(): Module; /** - * Creates a require function for loaded CommonJS modules - * child to this module. - * @return {function(string): any} + * The `Loader` for this module. + * @type {Loader} */ - createRequire(): (arg0: string) => any; + get loader(): Loader; /** - * Requires a module at `filename` that will be loaded as a child - * to this module. - * @param {string} filename - * @return {any} + * Factory for creating a `require()` function based on a module context. + * @param {CreateRequireOptions=} [options] + * @return {RequireFunction} + */ + createRequire(options?: CreateRequireOptions | undefined): RequireFunction; + /** + * Creates a `Module` from source the URL with this module as + * the parent. + * @param {string|URL|Module} url + * @param {ModuleOptions=} [options] */ - require(filename: string): any; + createModule(url: string | URL | Module, options?: ModuleOptions | undefined): any; + /** + * @param {object=} [options] + * @return {boolean} + */ + load(options?: object | undefined): boolean; + resolve(input: any): string; /** * @ignore */ [Symbol.toStringTag](): string; + #private; } export default Module; - export type ModuleResolver = (arg0: string, arg1: Module, arg2: Function) => undefined; - import { URL } from "socket:url/index"; - import _async from "socket:async"; - import { AsyncLocalStorage } from "socket:async"; - import { AsyncResource } from "socket:async"; - import { executionAsyncResource } from "socket:async"; - import { executionAsyncId } from "socket:async"; - import { triggerAsyncId } from "socket:async"; - import { createHook } from "socket:async"; - import { AsyncHook } from "socket:async"; - import application from "socket:application"; - import * as buffer from "socket:buffer"; - import crypto from "socket:crypto"; - import dgram from "socket:dgram"; - import dns from "socket:dns"; - import events from "socket:events"; - import fs from "socket:fs"; - import http from "socket:http"; - import ipc from "socket:ipc"; - import mime from "socket:mime"; - import os from "socket:os"; - import { posix as path } from "socket:path"; - import stream from "socket:stream"; - import string_decoder from "socket:string_decoder"; - import util from "socket:util"; - import test from "socket:test"; - import timers from "socket:timers"; - import window from "socket:window"; + export type ModuleResolver = import("socket:commonjs/require").RequireResolver[]; + export type RequireFunction = import("socket:commonjs/require").RequireFunction; + export type PackageOptions = import("socket:commonjs/package").PackageOptions; + export type CreateRequireOptions = { + prefix?: string; + request?: import("socket:commonjs/loader").RequestOptions; + builtins?: object; + }; + export type ModuleOptions = { + resolvers?: import("socket:commonjs/require").RequireResolver[][]; + importmap?: ImportMap; + loader?: Loader | object; + package?: Package | PackageOptions; + parent?: Module; + state?: State; + }; + import { Package } from "socket:commonjs/package"; + import { Loader } from "socket:commonjs/loader"; + import builtins from "socket:commonjs/builtins"; +} + +declare module "socket:commonjs/require" { + /** + * Factory for creating a `require()` function based on a module context. + * @param {CreateRequireOptions} options + * @return {RequireFunction} + */ + export function createRequire(options: CreateRequireOptions): RequireFunction; + /** + * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver + */ + /** + * @typedef {{ + * module: import('./module.js').Module, + * prefix?: string, + * request?: import('./loader.js').RequestOptions, + * builtins?: object + * }} CreateRequireOptions + */ + /** + * @typedef {function(string): any} RequireFunction + */ + /** + * @typedef {import('./package.js').PackageOptions} PackageOptions + */ + /** + * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions + */ + /** + * @typedef {PackageResolveOptions & PackageOptions} ResolveOptions + */ + /** + * @typedef {ResolveOptions & { + * resolvers?: RequireResolver[], + * importmap?: import('./module.js').ImportMap, + * }} RequireOptions + */ + /** + * An array of global require paths, relative to the origin. + * @type {string[]} + */ + export const globalPaths: string[]; + export default createRequire; + export type RequireResolver = (arg0: string, arg1: import("socket:commonjs/module").Module, arg2: (arg0: string) => any) => any; + export type CreateRequireOptions = { + module: import("socket:commonjs/module").Module; + prefix?: string; + request?: import("socket:commonjs/loader").RequestOptions; + builtins?: object; + }; + export type RequireFunction = (arg0: string) => any; + export type PackageOptions = import("socket:commonjs/package").PackageOptions; + export type PackageResolveOptions = import("socket:commonjs/package").PackageResolveOptions; + export type ResolveOptions = PackageResolveOptions & PackageOptions; + export type RequireOptions = ResolveOptions & { + resolvers?: RequireResolver[]; + importmap?: import("socket:commonjs/module").ImportMap; + }; +} + +declare module "socket:module" { + export const builtinModules: any; + export default Module; + import { Module } from "socket:commonjs/module"; + import builtins from "socket:commonjs/builtins"; + import { isBuiltin } from "socket:commonjs/builtins"; + import { createRequire } from "socket:commonjs/require"; + export { Module, builtins, isBuiltin, createRequire }; } declare module "socket:node-esm-loader" { @@ -12864,6 +13412,23 @@ declare module "socket:notification" { import URL from "socket:url"; } +declare module "socket:service-worker" { + namespace _default { + export { ExtendableEvent }; + export { FetchEvent }; + export { Environment }; + export { Database }; + export { Context }; + } + export default _default; + import { ExtendableEvent } from "socket:service-worker/events"; + import { FetchEvent } from "socket:service-worker/events"; + import { Environment } from "socket:service-worker/env"; + import { Database } from "socket:service-worker/database"; + import { Context } from "socket:service-worker/context"; + export { ExtendableEvent, FetchEvent, Environment, Database, Context }; +} + declare module "socket:stream-relay" { export * from "socket:stream-relay/index"; export default def; @@ -13220,6 +13785,34 @@ declare module "socket:internal/worker" { export default _default; } +declare module "socket:npm/module" { + export function resolve(specifier: any, origin?: any): Promise<{ + origin: any; + type: string; + path: string; + } | { + type: any; + path: string; + origin?: undefined; + }>; + namespace _default { + export { resolve }; + } + export default _default; +} + +declare module "socket:npm/service-worker" { + export function onRequest(request: any, env: any, ctx: any): Promise; + /** + * Handles incoming 'npm:///' requests. + * @param {Request} request + * @param {object} env + * @param {import('../service-worker/context.js').Context} ctx + * @return {Response?} + */ + export default function _default(request: Request, env: object, ctx: import("socket:service-worker/context").Context): Response | null; +} + declare module "socket:service-worker/global" { export class ServiceWorkerGlobalScope { get isServiceWorkerScope(): boolean; @@ -13259,6 +13852,7 @@ declare module "socket:service-worker/init" { hash: any; scope: any; scriptURL: any; + get pathname(): string; } const _default: any; export default _default; From 7a57eddafaec750e8bf28c132711a061739cf764 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:32:58 -0400 Subject: [PATCH 0423/1178] feat(api/tty.js): introduce simple 'tty' module --- api/tty.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 api/tty.js diff --git a/api/tty.js b/api/tty.js new file mode 100644 index 0000000000..77f68a751a --- /dev/null +++ b/api/tty.js @@ -0,0 +1,77 @@ +import { Writable, Readable } from './stream.js' +import ipc from './ipc.js' + +const textEncoder = new TextEncoder() +const textDecoder = new TextDecoder() + +export function WriteStream (fd) { + if (fd !== 1 && fd !== 2) { + throw new TypeError('"fd" must be 1 or 2.') + } + + const stream = new Writable({ + async write (data, cb) { + if (data && typeof data === 'object') { + data = textDecoder.decode(data) + } + + for (const value of data.split('\n')) { + if (fd === 1) { + ipc.send('stdout', { resolve: false, value }) + } else if (fd === 2) { + ipc.send('stderr', { resolve: false, value }) + } + } + + cb(null) + } + }) + + return stream +} + +export function ReadStream (fd) { + if (fd !== 0) { + throw new TypeError('"fd" must be 0.') + } + + const stream = new Readable() + + if (!globalThis.process?.versions?.node) { + globalThis.addEventListener('process.stdin', function onData (event) { + if (stream.destroyed) { + return globalThis.removeEventListener('process.stdin', onData) + } + + if (event.detail && typeof event.detail === 'object') { + process.stdin.push(Buffer.from(JSON.stringify(event.detail))) + } else if (event.detail) { + process.stdin.push(Buffer.from(textEncoder.encode(event.detail))) + } + }) + } + + return stream +} + +export function isatty (fd) { + if (fd === 0) { + return globalThis.__args?.argv?.includes?.('--stdin') !== true + } + + if (fd === 1) { + return true + } + + if (fd === 2) { + return true + } + + return false +} + +export default { + WriteStream, + ReadStream, + isatty +} From c48fd29fdd61200afd5cd8dc061fb8b5c2eaea86 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:33:21 -0400 Subject: [PATCH 0424/1178] refactor(api/commonjs/cache.js): introduce commonjs shared cache --- api/commonjs/cache.js | 277 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 api/commonjs/cache.js diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js new file mode 100644 index 0000000000..3e108d16aa --- /dev/null +++ b/api/commonjs/cache.js @@ -0,0 +1,277 @@ +import gc from '../gc.js' + +/** + * @typedef {{ + * types?: object, + * loader?: import('./loader.js').Loader + * }} CacheOptions + */ + +export const CACHE_CHANNEL_MESSAGE_ID = 'id' +export const CACHE_CHANNEL_MESSAGE_REPLICATE = 'replicate' + +/** + * A container for a shared cache that lives for the life time of + * application execution. Updates to this storage are replicated to other + * instances in the application context, including windows and workers. + */ +export class Cache { + static data = new Map() + static types = new Map() + + #onmessage = null + #channel = null + #loader = null + #name = '' + #types = Cache.types + #data = Cache.data + #id = null + + /** + * `Cache` class constructor. + * @param {string} name + * @param {CacheOptions=} [options] + */ + constructor (name, options) { + if (!name || typeof name !== 'string') { + throw new TypeError(`Expecting 'name' to be a string. Received: ${name}`) + } + + this.#id = Math.random().toString(16).slice(2) + this.#name = name + this.#loader = options?.loader ?? null + this.#channel = new BroadcastChannel(`socket.runtime.commonjs.cache.${name}`) + + if (options?.types && typeof options.types === 'object') { + for (const key in options.types) { + const value = options.types[key] + if (typeof value === 'function') { + this.#types.set(key, value) + } + } + } + + this.#onmessage = (event) => { + const { data } = event + if (!data || typeof data !== 'object') { + return + } + + if (data[CACHE_CHANNEL_MESSAGE_ID] === this.#id) { + return + } + + // recv 'replicate' + if (Array.isArray(data[CACHE_CHANNEL_MESSAGE_REPLICATE])) { + for (const entry of data[CACHE_CHANNEL_MESSAGE_REPLICATE]) { + if (!this.has(...entry)) { + const [key, value] = entry + if (value?.__type__) { + console.log(key) + this.#data.set(key, this.#types.get(value.__type__).from(value, { + loader: this.#loader + })) + } else { + this.#data.set(key, value) + } + } + } + } + } + + this.#channel.addEventListener('message', this.#onmessage) + + gc.ref(this) + } + + /** + * The unique ID for this cache. + * @type {string} + */ + get id () { + return this.#id + } + + /** + * The loader associated with this cache. + * @type {import('./loader.js').Loader} + */ + get loader () { + return this.#loader + } + + /** + * The cache name + * @type {string} + */ + get name () { + return this.#name + } + + /** + * The underlying cache data map. + * @type {Map} + */ + get data () { + return this.#data + } + + /** + * The broadcast channel associated with this cach. + * @type {BroadcastChannel} + */ + get channel () { + return this.#channel + } + + /** + * The size of the cache. + * @type {number} + */ + get size () { + return this.#data.size + } + + /** + * Get a value at `key`. + * @param {string} key + * @return {object|undefined} + */ + get (key) { + return this.#data.get(key) + } + + /** + * Set `value` at `key`. + * @param {string} key + * @param {object} value + * @return {Cache} + */ + set (key, value) { + this.#data.set(key, value) + return this + } + + /** + * Returns `true` if `key` is in cache, otherwise `false`. + * @param {string} + * @return {boolean} + */ + has (key) { + return this.#data.has(key) + } + + /** + * Delete a value at `key`. + * This does not replicate to shared caches. + * @param {string} key + * @return {object|undefined} + */ + delete (key) { + return this.#data.delete(key) + } + + /** + * Returns an iterator for all cache keys. + * @return {object} + */ + keys () { + return this.#data.keys() + } + + /** + * Returns an iterator for all cache values. + * @return {object} + */ + values () { + return this.#data.values() + } + + /** + * Returns an iterator for all cache entries. + * @return {object} + */ + entries () { + return this.#data.entries() + } + + /** + * Clears all entries in the cache. + * This does not replicate to shared caches. + * @return {undefined} + */ + clear () { + this.#data.clear() + } + + /** + * Enumerates entries in map calling `callback(value, key + * @param {function(object, string, Cache): any} callback + */ + forEach (callback) { + if (!callback || typeof callback !== 'function') { + throw new TypeError(`${callback} is not a function`) + } + + this.#data.forEach((value, key) => { + callback(value, key, this) + }) + } + + /** + * Broadcasts a replication to other shared caches. + */ + replicate () { + const entries = Array.from(this.#data.entries()) + + if (entries.length === 0) { + return this + } + + const message = { + [CACHE_CHANNEL_MESSAGE_ID]: this.#id, + [CACHE_CHANNEL_MESSAGE_REPLICATE]: entries + } + + this.#channel.postMessage(message) + + return this + } + + /** + * Destroys the cache. This function stops the broadcast channel and removes + * and listeners + */ + destroy () { + this.#channel.removeEventListener('message', this.#onmessage) + this.#data.clear() + this.#data = null + this.#channel = null + gc.unref(this) + } + + /** + * @ignore + */ + [Symbol.iterator] () { + return this.#data.entries() + } + + /** + * Implements `gc.finalizer` for gc'd resource cleanup. + * @return {gc.Finalizer} + * @ignore + */ + [gc.finalizer] () { + return { + args: [this.#data, this.#channel, this.#onmessage], + handle (data, channel, onmessage) { + console.log('gc') + data.clear() + channel.removeEventListener('message', onmessage) + } + } + } +} + +export default Cache From 87b15f8f881582cd3cca2d98987857732c03446e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:33:46 -0400 Subject: [PATCH 0425/1178] feat(api/internal/post-message.js): implement 'Symbol.serialize' mapper --- api/internal/post-message.js | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 api/internal/post-message.js diff --git a/api/internal/post-message.js b/api/internal/post-message.js new file mode 100644 index 0000000000..6fd7cd0f8f --- /dev/null +++ b/api/internal/post-message.js @@ -0,0 +1,53 @@ +const platform = { + BroadcastChannelPostMessage: globalThis.BroadcastChannel.prototype.postMessage, + MessageChannelPostMessage: globalThis.MessageChannel.prototype.postMessage, + GlobalPostMessage: globalThis.postMessage +} + +globalThis.BroadcastChannel.prototype.postMessage = function (message, ...args) { + message = handlePostMessage(message) + return platform.BroadcastChannelPostMessage.call(this, message, ...args) +} + +globalThis.MessageChannel.prototype.postMessage = function (message, ...args) { + return platform.MessageChannelPostMessage.call(this, handlePostMessage(message), ...args) +} + +globalThis.postMessage = function (message, ...args) { + return platform.GlobalPostMessage.call(this, handlePostMessage(message), ...args) +} + +function handlePostMessage (message) { + if (!message || typeof message !== 'object') { + return message + } + + return map(message, (value) => { + if (typeof value[Symbol.serialize] !== 'function') { + return value + } + + return value[Symbol.serialize]() + }) +} + +function map (object, callback) { + if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + object[i] = map(object[i], callback) + } + } else if (object && typeof object === 'object') { + object = callback(object) + for (const key in object) { + object[key] = map(object[key], callback) + } + } + + if (object && typeof object === 'object') { + return callback(object) + } else { + return object + } +} + +export default null From 3eb5dc45221f0c0a7c399cd5f5c498a092082c78 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:34:04 -0400 Subject: [PATCH 0426/1178] refactor(api/gc.js): improve 'gc.finalizer' symbol name --- api/gc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/gc.js b/api/gc.js index c033cbad4f..dd2568f625 100644 --- a/api/gc.js +++ b/api/gc.js @@ -18,7 +18,7 @@ const dc = diagnostics.channels.group('gc', [ ]) export const finalizers = new WeakMap() -export const kFinalizer = Symbol.for('gc.finalizer') +export const kFinalizer = Symbol.for('socket.runtime.gc.finalizer') export const finalizer = kFinalizer export const pool = new Set() From c63ee32d63dcdcc160df19b1c820541d7d6f3f2c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:34:29 -0400 Subject: [PATCH 0427/1178] feat(api/process.js): introduce read/write streams on process --- api/process.js | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/api/process.js b/api/process.js index 93d4456e94..510253e720 100644 --- a/api/process.js +++ b/api/process.js @@ -8,7 +8,9 @@ */ import { primordials, send } from './ipc.js' import { EventEmitter } from './events.js' +import { Buffer } from './buffer.js' import signal from './signal.js' +import tty from './tty.js' import os from './os.js' let didEmitExitEvent = false @@ -68,8 +70,12 @@ export const env = Object.defineProperties(new EventTarget(), { }) class Process extends EventEmitter { + stdin = new tty.ReadStream(0) + stdout = new tty.WriteStream(1) + stderr = new tty.WriteStream(2) + get version () { - return primordials.version + return primordials.version.short } get platform () { @@ -136,23 +142,25 @@ if (!isNode) { EventEmitter.call(process) } -signal.channel.addEventListener('message', (event) => { - if (event.data.signal) { - const code = event.data.signal - const name = signal.getName(code) - const message = signal.getMessage(code) - process.emit(name, name, code, message) - } -}) - -globalThis.addEventListener('signal', (event) => { - if (event.detail.signal) { - const code = event.detail.signal - const name = signal.getName(code) - const message = signal.getMessage(code) - process.emit(name, name, code, message) - } -}) +if (!isNode) { + signal.channel.addEventListener('message', (event) => { + if (event.data.signal) { + const code = event.data.signal + const name = signal.getName(code) + const message = signal.getMessage(code) + process.emit(name, name, code, message) + } + }) + + globalThis.addEventListener('signal', (event) => { + if (event.detail.signal) { + const code = event.detail.signal + const name = signal.getName(code) + const message = signal.getMessage(code) + process.emit(name, name, code, message) + } + }) +} export default process From c901fe49a4fb39b37c1eeb070afb7a2eddfaffd1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:34:47 -0400 Subject: [PATCH 0428/1178] refactor(api/util.js): handle 'symbol' in inspect --- api/util.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/util.js b/api/util.js index be6dcfb22b..f0a2f6a7d3 100644 --- a/api/util.js +++ b/api/util.js @@ -397,6 +397,10 @@ export function inspect (value, options) { return formatValue(ctx, value, ctx.depth) function formatValue (ctx, value, depth) { + if (value instanceof Symbol || typeof value === 'symbol') { + return String(value) + } + // nodejs `value.inspect()` parity if ( ctx.customInspect && From d8f6bde55df1fc1aef6ae9228d17f7672a78ed73 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:35:08 -0400 Subject: [PATCH 0429/1178] refactor(api/service-worker): improve commonjs global context --- api/service-worker/instance.js | 2 +- api/service-worker/worker.js | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 9564db7593..bed9f0cb61 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -112,7 +112,7 @@ export function createServiceWorker ( if (options?.subscribe !== false) { state.channel.addEventListener('message', (event) => { const { data } = event - if (data.serviceWorker) { + if (data?.serviceWorker) { if (data.serviceWorker.state && data.serviceWorker.state !== currentState) { const scope = new URL(globalThis.location.href).pathname if (scope.startsWith(data.serviceWorker.scope)) { diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 62aee08436..f0e127185a 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -5,6 +5,7 @@ import { STATUS_CODES } from '../http.js' import { Environment } from './env.js' import { Deferred } from '../async.js' import { Buffer } from '../buffer.js' +import process from '../process.js' import clients from './clients.js' import hooks from '../hooks.js' import state from './state.js' @@ -86,6 +87,18 @@ async function onMessage (event) { configurable: false, enumerable: false, get: () => module.exports + }, + + process: { + configurable: false, + enumerable: false, + get: () => process + }, + + global: { + configurable: false, + enumerable: false, + get: () => globalThis } }) @@ -99,9 +112,11 @@ async function onMessage (event) { Object.assign(module.exports, result) } } catch (err) { + console.error(err) + globalThis.reportError(err) state.serviceWorker.state = 'error' await state.notify('serviceWorker') - return state.reportError(err) + return } await Environment.open({ id, scope }) From 716dfdb459b0a7c18a1e3dc7f45f58302cfde1d3 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:35:40 -0400 Subject: [PATCH 0430/1178] refactor(api/internal/async/hooks.js): call 'runInAsyncScope' on given optional 'resource' --- api/internal/async/hooks.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/internal/async/hooks.js b/api/internal/async/hooks.js index 35beb2e4c2..440d8d4c74 100644 --- a/api/internal/async/hooks.js +++ b/api/internal/async/hooks.js @@ -251,14 +251,14 @@ export function wrap ( dispatch('init', asyncId, type, triggerAsyncId, resource) callback = asyncWrap(callback) return function (...args) { - dispatch('before', asyncId) + dispatch('before', asyncId, type, triggerAsyncId) try { - return topLevelAsyncResource.runInAsyncScope(() => { + return (resource || topLevelAsyncResource).runInAsyncScope(() => { // eslint-disable-next-line return callback(...args) }) } finally { - dispatch('after', asyncId) + dispatch('after', asyncId, type, triggerAsyncId) } } } From 30b4ecaf17fb14968aa71de7dc3e91c78fec52bf Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:35:59 -0400 Subject: [PATCH 0431/1178] refactor(api/internal/init.js): import primitives right away --- api/internal/init.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 621646611e..7fbb26ada6 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -12,11 +12,10 @@ console.assert( 'This could lead to undefined behavior.' ) +import './primitives.js' import ipc from '../ipc.js' ipc.sendSync('platform.event', 'beforeruntimeinit') -import './primitives.js' - import { CustomEvent, ErrorEvent } from '../events.js' import { IllegalConstructor } from '../util.js' import * as asyncHooks from './async/hooks.js' From e6024f3ddf3e957f2dcd83ec7a1c0a9564402be1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:36:23 -0400 Subject: [PATCH 0432/1178] refactor(api/internal/primitives.js): introduce 'Symbol.seralize' --- api/internal/primitives.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 113683cc2c..0309d11070 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -1,4 +1,8 @@ /* global MutationObserver */ +import './post-message.js' +import './promise.js' +import './error.js' + import { fetch, Headers, Request, Response } from '../fetch.js' import { URL, URLPattern, URLSearchParams } from '../url.js' import { ReadableStream } from './streams.js' @@ -53,8 +57,6 @@ import { import ipc from '../ipc.js' -import './promise.js' - let applied = false const natives = {} const patches = {} @@ -173,6 +175,7 @@ export function init () { try { Symbol.dispose = symbols.dispose + Symbol.serialize = symbols.serialize } catch {} if ( From b601d86cd23a73837ab214506ba2f6d2ac61886b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:36:44 -0400 Subject: [PATCH 0433/1178] refactor(api/internal/symbols.js): export 'Symbol.serialize' --- api/internal/symbols.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/internal/symbols.js b/api/internal/symbols.js index 74f679d614..9b82e21acc 100644 --- a/api/internal/symbols.js +++ b/api/internal/symbols.js @@ -1,5 +1,7 @@ -export const dispose = Symbol.dispose ?? Symbol.for('dispose') +export const dispose = Symbol.dispose ?? Symbol.for('socket.runtime.gc.finalizer') +export const serialize = Symbol.serialize ?? Symbol.for('socket.runtime.serialize') export default { - dispose + dispose, + serialize } From 185368c8c2679977bda0151b835fb16539e17e40 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:37:12 -0400 Subject: [PATCH 0434/1178] refactor(api/internal/promise.js): make 'Promise' an async resource --- api/internal/promise.js | 92 +++++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/api/internal/promise.js b/api/internal/promise.js index cf58379cda..1ea1c41d39 100644 --- a/api/internal/promise.js +++ b/api/internal/promise.js @@ -1,11 +1,73 @@ import * as asyncHooks from './async/hooks.js' +const resourceSymbol = Symbol('PromiseResource') + +export const NativePromise = globalThis.Promise export const NativePromisePrototype = { then: globalThis.Promise.prototype.then, catch: globalThis.Promise.prototype.catch, finally: globalThis.Promise.prototype.finally } +export const NativePromiseAll = globalThis.Promise.all.bind(globalThis.Promise) +export const NativePromiseAny = globalThis.Promise.any.bind(globalThis.Promise) + +globalThis.Promise = class Promise extends NativePromise { + constructor (...args) { + super(...args) + // eslint-disable-next-line + this[resourceSymbol] = new class Promise extends asyncHooks.CoreAsyncResource { + constructor () { + super('Promise') + } + } + } +} + +globalThis.Promise.all = function (iterable) { + return NativePromiseAll(...Array.from(iterable).map((promise, index) => { + return promise.catch((err) => { + throw Object.defineProperties(err, { + [Symbol.for('socket.runtime.CallSite.PromiseElementIndex')]: { + configurable: false, + enumerable: false, + writable: false, + value: index + }, + + [Symbol.for('socket.runtime.CallSite.PromiseAll')]: { + configurable: false, + enumerable: false, + writable: false, + value: true + } + }) + }) + })) +} + +globalThis.Promise.any = function (iterable) { + return NativePromiseAny(...Array.from(iterable).map((promise, index) => { + return promise.catch((err) => { + throw Object.defineProperties(err, { + [Symbol.for('socket.runtime.CallSite.PromiseElementIndex')]: { + configurable: false, + enumerable: false, + writable: false, + value: index + }, + + [Symbol.for('socket.runtime.CallSite.PromiseAny')]: { + configurable: false, + enumerable: false, + writable: false, + value: true + } + }) + }) + })) +} + function wrapNativePromiseFunction (name) { const prototype = globalThis.Promise.prototype if (prototype[name].__async_wrapped__) { @@ -19,20 +81,24 @@ function wrapNativePromiseFunction (name) { return nativeFunction.call(this, ...args) } - if (name === 'then') { - return nativeFunction.call( - this, - ...args.map((arg) => { - if (typeof arg === 'function') { - return asyncHooks.wrap(arg, 'Promise') - } - - return arg - }) - ) - } + const resource = this[resourceSymbol] + + return nativeFunction.call( + this, + ...args.map((arg) => { + if (typeof arg === 'function') { + return asyncHooks.wrap( + arg, + 'Promise', + resource?.asyncId?.() ?? asyncHooks.getNextAsyncResourceId(), + resource?.triggerAsyncId?.() ?? asyncHooks.getDefaultExecutionAsyncId(), + resource ?? undefined + ) + } - return nativeFunction.call(this, ...args) + return arg + }) + ) } Object.defineProperty(prototype[name], '__async_wrapped__', { From 385be8d1a098154e333714af141b29e433d49b67 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:37:49 -0400 Subject: [PATCH 0435/1178] refactor(api/commonjs/builtins.js): use shallow copy of builtin exports --- api/commonjs/builtins.js | 202 ++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 99 deletions(-) diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index bf269346e9..5ffd5a960c 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -12,118 +12,122 @@ import _async, { } from '../async.js' // eslint-disable-next-line -import application from '../application.js' -import assert from '../assert.js' +import * as application from '../application.js' +import * as assert from '../assert.js' import * as buffer from '../buffer.js' // eslint-disable-next-line -import child_process from '../child_process.js' -import console from '../console.js' -import constants from '../constants.js' -import crypto from '../crypto.js' -import dgram from '../dgram.js' -import dns from '../dns.js' -import events from '../events.js' -import extension from '../extension.js' -import fs from '../fs.js' -import gc from '../gc.js' -import http from '../http.js' -import https from '../https.js' -import ipc from '../ipc.js' -import language from '../language.js' -import location from '../location.js' -import mime from '../mime.js' -import network from '../network.js' -import os from '../os.js' +import * as child_process from '../child_process.js' +import * as console from '../console.js' +import * as constants from '../constants.js' +import * as crypto from '../crypto.js' +import * as dgram from '../dgram.js' +import * as dns from '../dns.js' +import * as events from '../events.js' +import * as extension from '../extension.js' +import * as fs from '../fs.js' +import * as gc from '../gc.js' +import * as http from '../http.js' +import * as https from '../https.js' +import * as ipc from '../ipc.js' +import * as language from '../language.js' +import * as location from '../location.js' +import * as mime from '../mime.js' +import * as network from '../network.js' +import * as os from '../os.js' import { posix as path } from '../path.js' import process from '../process.js' -import querystring from '../querystring.js' -import stream from '../stream.js' +import * as querystring from '../querystring.js' +import * as stream from '../stream.js' // eslint-disable-next-line -import string_decoder from '../string_decoder.js' -import test from '../test.js' -import timers from '../timers.js' -import url from '../url.js' -import util from '../util.js' -import vm from '../vm.js' -import window from '../window.js' +import * as string_decoder from '../string_decoder.js' +import * as test from '../test.js' +import * as timers from '../timers.js' +import * as tty from '../tty.js' +import * as url from '../url.js' +import * as util from '../util.js' +import * as vm from '../vm.js' +import * as window from '../window.js' // eslint-disable-next-line -import worker_threads from '../worker_threads.js' +import * as worker_threads from '../worker_threads.js' /** * A mapping of builtin modules * @type {object} */ -export const builtins = { - // eslint-disable-next-line - 'async': _async, - // eslint-disable-next-line - async_context: { - AsyncLocalStorage, - AsyncResource - }, - // eslint-disable-next-line - async_hooks: { - AsyncLocalStorage, - AsyncResource, - executionAsyncResource, - executionAsyncId, - triggerAsyncId, - createHook, - AsyncHook - }, - application, - assert, - buffer, - console, - constants, - // eslint-disable-next-line - child_process, - crypto, - dgram, - dns, - 'dns/promises': dns.promises, - events, - extension, - fs, - 'fs/promises': fs.promises, - http, - https, - gc, - ipc, - language, - location, - mime, - net: {}, - network, - os, - path, - // eslint-disable-next-line - perf_hooks: { - performance: globalThis.performance - }, - process, - querystring, - stream, - 'stream/web': stream.web, - // eslint-disable-next-line - string_decoder, - sys: util, - test, - timers, - 'timers/promises': timers.promises, - tty: { - isatty: () => false, - WriteStream: util.IllegalConstructor, - ReadStream: util.IllegalConstructor - }, - util, - url, - vm, - window, - // eslint-disable-next-line - worker_threads +export const builtins = {} + +/** + * Defines a builtin module by name making a shallow copy of the + * module exports. + * @param {string} + * @param {object} exports + */ +export function define (name, exports) { + builtins[name] = { ...exports } } +// eslint-disable-next-line +define('async', _async) +define('async_context', { + AsyncLocalStorage, + AsyncResource +}) +define('async_hooks', { + AsyncLocalStorage, + AsyncResource, + executionAsyncResource, + executionAsyncId, + triggerAsyncId, + createHook, + AsyncHook +}) + +define('application', application) +define('assert', assert) +define('buffer', buffer) +define('console', console) +define('constants', constants) +// eslint-disable-next-line +define('child_process', child_process) +define('crypto', crypto) +define('dgram', dgram) +define('dns', dns) +define('dns/promises', dns.promises) +define('events', events) +define('extension', extension) +define('fs', fs) +define('fs/promises', fs.promises) +define('http', http) +define('https', https) +define('gc', gc) +define('ipc', ipc) +define('language', language) +define('location', location) +define('mime', mime) +define('net', {}) +define('network', network) +define('os', os) +define('path', path) +define('perf_hooks', { performance: globalThis.performance }) +define('process', process) +define('querystring', querystring) +define('stream', stream) +define('stream/web', stream.web) +// eslint-disable-next-line +define('string_decoder', string_decoder) +define('sys', util) +define('test', test) +define('timers', timers) +define('timers/promises', timers.promises) +define('tty', tty) +define('util', util) +define('url', url) +define('v8', {}) +define('vm', vm) +define('window', window) +// eslint-disable-next-line +define('worker_threads', worker_threads) + /** * Known runtime specific builtin modules. * @type {string[]} From dc138849f3301aae556416a175791b25b09d1a06 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:38:25 -0400 Subject: [PATCH 0436/1178] refactor(api/commonjs/loader.js): use improved cache, handle buffer outputs --- api/commonjs/loader.js | 331 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 291 insertions(+), 40 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 352ca06cde..69619b81a1 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -1,16 +1,23 @@ /* global XMLHttpRequest */ +/** + * @module CommonJS.Loader + */ +import InternalSymbols from '../internal/symbols.js' import { Headers } from '../ipc.js' +import { Cache } from './cache.js' import location from '../location.js' import path from '../path.js' +const RUNTIME_SERVICE_WORKER_FETCH_MODE = 'Runtime-ServiceWorker-Fetch-Mode' const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' +const textDecoder = new TextDecoder() /** * @typedef {{ * extensions?: string[] | Set * origin?: URL | string, - * statuses?: Map - * cache?: Map + * statuses?: Cache + * cache?: { response?: Cache, status?: Cache } * }} LoaderOptions */ @@ -21,6 +28,13 @@ const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' * }} RequestOptions */ +/** + * @typedef {{ + * headers?: Headers | object | array[], + * status?: number + * }} RequestStatusOptions + */ + /** * @typedef {{ * headers?: Headers | object @@ -32,6 +46,7 @@ const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' * request?: Request, * headers?: Headers, * status?: number, + * buffer?: ArrayBuffer, * text?: string * }} ResponseOptions */ @@ -42,6 +57,15 @@ const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' * HTTP HEAD request. */ export class RequestStatus { + /** + * Creates a `RequestStatus` from JSON input. + * @param {object} json + * @return {RequestStatus} + */ + static from (json) { + return new this(null, json) + } + #status = undefined #request = null #headers = new Headers() @@ -49,15 +73,34 @@ export class RequestStatus { /** * `RequestStatus` class constructor. * @param {Request} request + * @param {RequestStatusOptions} [options] */ - constructor (request) { - if (!request || !(request instanceof Request)) { + constructor (request, options = null) { + if (request && !(request instanceof Request)) { throw new TypeError( `Expecting 'request' to be a Request object. Received: ${request}` ) } + if (request) { + this.request = request + } + + this.#headers = options?.headers ? Headers.from(options.headers) : this.#headers + this.#status = options?.status ? options.status : undefined + } + + /** + * The `Request` object associated with this `RequestStatus` object. + * @type {Request} + */ + get request () { return this.#request } + set request (request) { this.#request = request + + if (request.loader) { + this.#status = request.loader.cache.status.get(request.id)?.value + } } /** @@ -65,7 +108,7 @@ export class RequestStatus { * @type {string} */ get id () { - return this.#request.id + return this.#request?.id ?? null } /** @@ -73,7 +116,7 @@ export class RequestStatus { * @type {string} */ get origin () { - return this.#request.origin + return this.#request?.origin ?? null } /** @@ -119,7 +162,11 @@ export class RequestStatus { return contentLocation } - return this.#request.url.pathname + this.#request.url.search + if (this.#request) { + return this.#request.url.pathname + this.#request.url.search + } + + return '' } /** @@ -145,6 +192,11 @@ export class RequestStatus { request.open('HEAD', this.#request.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') + request.withCredentials = true + + if (globalThis.isServiceWorkerScope) { + request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') + } if (options?.headers && typeof options?.headers === 'object') { const entries = typeof options.headers.entries === 'function' @@ -164,7 +216,8 @@ export class RequestStatus { const contentLocation = this.#headers.get('content-location') // verify 'Content-Location' header if given in response - if (contentLocation && URL.canParse(contentLocation, this.origin)) { + // @ts-ignore + if (this.#request && contentLocation && URL.canParse(contentLocation, this.origin)) { const url = new URL(contentLocation, this.origin) const extension = path.extname(url.pathname) @@ -176,6 +229,52 @@ export class RequestStatus { return this } + + /** + * Converts this `RequestStatus` to JSON. + * @ignore + * @return {{ + * id: string, + * origin: string | null, + * status: number, + * headers: Array + * request: object | null | undefined + * }} + */ + toJSON (includeRequest = true) { + if (includeRequest) { + return { + id: this.id, + origin: this.origin, + status: this.status, + headers: Array.from(this.headers.entries()), + request: this.#request ? this.#request.toJSON(false) : null + } + } else { + return { + id: this.id, + origin: this.origin, + status: this.status, + headers: Array.from(this.headers.entries()) + } + } + } + + /** + * Serializes this `Response`, suitable for `postMessage()` transfers. + * @ignore + * @return {{ + * __type__: 'RequestStatus', + * id: string, + * origin: string | null, + * status: number, + * headers: Array + * request: object | null + * }} + */ + [InternalSymbols.serialize] () { + return { __type__: 'RequestStatus', ...this.toJSON() } + } } /** @@ -183,7 +282,21 @@ export class RequestStatus { * over the network. */ export class Request { - #id = null + /** + * Creates a `Request` instance from JSON input + * @param {object} json + * @param {RequestOptions=} [options] + * @return {Request} + */ + static from (json, options) { + return new this(json.url, { + status: json.status && typeof json.status === 'object' + ? RequestStatus.from(json.status) + : options?.status, + ...options + }) + } + #url = null #loader = null #status = null @@ -210,7 +323,11 @@ export class Request { this.#url = new URL(url, origin) this.#loader = options?.loader ?? null - this.#status = new RequestStatus(this) + this.#status = options?.status instanceof RequestStatus + ? options.status + : new RequestStatus(this) + + this.#status.request = this } /** @@ -262,8 +379,8 @@ export class Request { load (options = null) { // check loader cache first if (options?.cache !== false && this.#loader !== null) { - if (this.#loader.cache.has(this.id)) { - return this.#loader.cache.get(this.id) + if (this.#loader.cache.response.has(this.id)) { + return this.#loader.cache.response.get(this.id) } } @@ -276,6 +393,15 @@ export class Request { const request = new XMLHttpRequest() request.open('GET', this.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') + request.withCredentials = true + + if (globalThis.isServiceWorkerScope) { + request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') + } + + if (typeof options?.responseType === 'string') { + request.responseType = options.responseType + } if (options?.headers && typeof options?.headers === 'object') { const entries = typeof options.headers.entries === 'function' @@ -301,9 +427,44 @@ export class Request { return new Response(this, { headers: Headers.from(request), status: request.status, - text: responseText + buffer: request.response, + text: responseText ?? null }) } + + /** + * Converts this `Request` to JSON. + * @ignore + * @return {{ + * url: string, + * status: object | undefined + * }} + */ + toJSON (includeStatus = true) { + if (includeStatus) { + return { + url: this.url.href, + status: this.status.toJSON(false) + } + } else { + return { + url: this.url.href + } + } + } + + /** + * Serializes this `Response`, suitable for `postMessage()` transfers. + * @ignore + * @return {{ + * __type__: 'Request', + * url: string, + * status: object | undefined + * }} + */ + [InternalSymbols.serialize] () { + return { __type__: 'Request', ...this.toJSON() } + } } /** @@ -311,9 +472,23 @@ export class Request { * or over the network. */ export class Response { + /** + * Creates a `Response` from JSON input + * @param {obejct} json + * @param {ResponseOptions=} [options] + * @return {Response} + */ + static from (json, options) { + return new this({ + request: Request.from({ url: json.id }, options), + ...json + }, options) + } + #request = null - #headers = new Headers() + #headers = null #status = 404 + #buffer = null #text = '' /** @@ -324,7 +499,7 @@ export class Response { constructor (request, options = null) { options = { ...options } - if (!typeof request === 'object' && !(request instanceof Request)) { + if (typeof request === 'object' && !(request instanceof Request)) { options = request request = options.request } @@ -336,13 +511,14 @@ export class Response { } this.#request = request - this.#headers = options.headers + this.#headers = Headers.from(options.headers) this.#status = options.status || 404 + this.#buffer = options.buffer ? new Uint8Array(options.buffer).buffer : null this.#text = options.text || '' if (request.loader) { // cache request response in the loader - request.loader.cache.set(request.id, this) + request.loader.cache.response.set(request.id, this) } } @@ -363,6 +539,14 @@ export class Response { return this.#request } + /** + * The response headers from the associated request. + * @type {Headers} + */ + get headers () { + return this.#headers + } + /** * The `Loader` associated with this `Response` object. * @type {Loader?} @@ -384,7 +568,21 @@ export class Response { * @type {string} */ get text () { - return this.#text + if (this.#text) { + return this.#text + } else if (this.#buffer) { + return textDecoder.decode(this.#buffer) + } + + return '' + } + + /** + * The `Response` array buffer from the associated `Request` + * @type {ArrayBuffer?} + */ + get buffer () { + return this.#buffer ?? null } /** @@ -394,6 +592,43 @@ export class Response { get ok () { return this.status >= 200 && this.status < 400 } + + /** + * Converts this `Response` to JSON. + * @ignore + * @return {{ + * id: string, + * text: string, + * status: number, + * buffer: number[] | null, + * headers: Array + * }} + */ + toJSON () { + return { + id: this.id, + text: this.text, + status: this.status, + buffer: this.#buffer ? Array.from(new Uint8Array(this.#buffer)) : null, + headers: Array.from(this.#headers.entries()) + } + } + + /** + * Serializes this `Response`, suitable for `postMessage()` transfers. + * @ignore + * @return {{ + * __type__: 'Response', + * id: string, + * text: string, + * status: number, + * buffer: number[] | null, + * headers: Array + * }} + */ + [InternalSymbols.serialize] () { + return { __type__: 'Response', ...this.toJSON() } + } } /** @@ -438,11 +673,30 @@ export class Loader { * Default extensions for a loader. * @type {Set} */ - static defaultExtensions = new Set(['.js', '.json', '.mjs', '.cjs', '.jsx', '.ts', '.tsx']) + static defaultExtensions = new Set([ + '.js', + '.json', + '.mjs', + '.cjs', + '.jsx', + '.ts', + '.tsx', + '.wasm' + ]) + + #cache = { + /** + * @type {Cache?} + */ + response: null, + + /** + * @type {Cache?} + */ + status: null + } - #cache = null #origin = null - #statuses = null #extensions = Loader.defaultExtensions /** @@ -456,11 +710,15 @@ export class Loader { origin = options.origin } - this.#cache = options?.cache instanceof Map ? options.cache : new Map() this.#origin = Loader.resolve('.', origin) - this.#statuses = options?.statuses instanceof Map - ? options.statuses - : new Map() + + this.#cache.response = options?.cache?.response instanceof Cache + ? options.cache.response + : new Cache('loader.response', { loader: this, types: { Response } }) + + this.#cache.status = options?.cache?.status instanceof Cache + ? options.cache.status + : new Cache('loader.status', { loader: this, types: { RequestStatus } }) if (options?.extensions && typeof options.extensions === 'object') { if (Array.isArray(options.extensions) || options instanceof Set) { @@ -473,21 +731,13 @@ export class Loader { } /** - * The internal cache for this `Loader` object. - * @type {Map} + * The internal caches for this `Loader` object. + * @type {{ response: Cache, status: Cache }} */ get cache () { return this.#cache } - /** - * The internal statuses for this `Loader` object. - * @type {Map} - */ - get statuses () { - return this.#statuses - } - /** * A set of supported `Loader` extensions. * @type {Set} @@ -539,7 +789,7 @@ export class Loader { * @param {RequestOptions=} [options] * @return {RequestStatus} */ - status (url, origin, options) { + status (url, origin, options = null) { if (origin && typeof origin === 'object' && !(origin instanceof URL)) { options = origin origin = this.origin @@ -551,16 +801,17 @@ export class Loader { url = this.resolve(url, origin) - if (this.#statuses.has(url)) { - return this.statuses.get(url) + if (this.#cache.status.has(url)) { + return this.#cache.status.get(url) } const request = new Request(url, { loader: this, - origin + origin, + ...options }) - this.#statuses.set(url, request.status) + this.#cache.status.set(url, request.status) return request.status } From bce358048bf4f5e29d6e281ee57cd2e359f43665 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:39:14 -0400 Subject: [PATCH 0437/1178] refactor(api/commonjs/package.js): improve package name, manifest loading, and file resolutions --- api/commonjs/package.js | 737 ++++++++++++++++++++++++++++++++++------ 1 file changed, 636 insertions(+), 101 deletions(-) diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 44dda4cbed..2658372834 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -1,11 +1,20 @@ +/** + * @module CommonJS.Package + */ import { ModuleNotFoundError } from '../errors.js' import { Loader } from './loader.js' import location from '../location.js' import path from '../path.js' +/** + * `true` if in a worker scope. + * @type {boolean} + * @ignore + */ +const isWorkerScope = globalThis.self === globalThis && !globalThis.window + /** * @typedef {{ - * prefix?: string, * manifest?: string, * index?: string, * description?: string, @@ -13,13 +22,16 @@ import path from '../path.js' * license?: string, * exports?: object, * type?: 'commonjs' | 'module', - * info?: object + * info?: object, + * origin?: string, + * dependencies?: Dependencies | object | Map * }} PackageOptions */ /** * @typedef {import('./loader.js').RequestOptions & { * type?: 'commonjs' | 'module' + * prefix?: string * }} PackageLoadOptions */ @@ -33,6 +45,16 @@ import path from '../path.js' * }} PackageResolveOptions */ +/** + * @typedef {{ + * organization: string | null, + * name: string, + * version: string | null, + * pathname: string, + * url: URL, + * isRelative: boolean, + * hasManifest: boolean + * }} ParsedPackageName /** * The default package index file such as 'index.js' * @type {string} @@ -64,97 +86,470 @@ export const DEFAULT_PACKAGE_VERSION = '0.0.1' export const DEFAULT_LICENSE = 'Unlicensed' /** - * A container for CommonJS module metadata, often in a `package.json` file. + * A container for a package name that includes a package organization identifier, + * its fully qualified name, or for relative package names, its pathname */ -export class Package { +export class Name { /** - * @param {string} input - * @param {PackageOptions&PackageLoadOptions} [options] - * @return {Package?} + * Parses a package name input resolving the actual module name, including an + * organization name given. If a path includes a manifest file + * ('package.json'), then the directory containing that file is considered a + * valid package and it will be included in the returned value. If a relative + * path is given, then the path is returned if it is a valid pathname. This + * function returns `null` for bad input. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {ParsedPackageName?} */ - static find (input, options = null) { - const url = URL.canParse(input) - ? new URL(input) - : new URL(input, options?.origin ?? location.origin) + static parse (input, options = null) { + if (typeof input === 'string') { + input = input.trim() + } + + if (!input) { + return null + } - const components = url.pathname.split('/') + const origin = options?.origin ?? location.origin + const manifest = options?.manifest ?? DEFAULT_PACKAGE_MANIFEST_FILE_NAME + // relative or absolute path given, which is still relative to the origin + const isRelative = ( + typeof input === 'string' && + (input.startsWith('.') || input.startsWith('/')) + ) + + let hasManifest = false + let url = null + + // URL already given, ignore the origin + // @ts-ignore + if (URL.canParse(input) || input instanceof URL) { + url = new URL(input) + } else { + url = new URL(input, origin) + } - if (path.extname(url.pathname)) { - components.pop() + // invalid input if a URL was unable to be determined + if (!url) { + return null } - while (components.length) { - const name = components.join('/') + let pathname = url.pathname.replace(new URL(origin).pathname, '') - if (!name) { + // manifest was given in name, just use the directory name + if (pathname.endsWith(`/${manifest}`)) { + hasManifest = true + pathname = pathname.split('/').slice(0, -1).join('/') + } + + // name included organization + if (pathname.startsWith('@')) { + const components = pathname.split('/') + const organization = components[0] + let [name, version = null] = components[1].split('@') + pathname = [organization || '', name, ...components.slice(2)].filter(Boolean).join('/') + // manifest was given, this could be a nested package + if (hasManifest) { + name = [organization, name].filter(Boolean).concat(components.slice(2)).join('/') + return { + name, + version, + organization, + pathname, + url, + isRelative, + hasManifest + } + } + + // only a organization was given, return `null` + if (components.length === 1) { return null } - const manifest = new Package(name, { - ...options, - prefix: '', - loader: new Loader( - new URL(components.join('/'), url.origin), - options?.loader - ) - }) + name = `${organization}/${name}` + + // only `@/package>` was given + if (components.length === 2) { + return { + name, + version, + organization, + pathname, + url, + isRelative, + hasManifest + } + } - if (path.extname(manifest.name)) { - continue + // `@//...` was given + return { + name, + version, + organization, + pathname, + url, + isRelative, + hasManifest } + } - if (manifest.load(options)) { - return manifest + // a valid relative path was given, just return it normalized + if (isRelative) { + if (input.startsWith('/')) { + pathname = `/${pathname}` + } else { + pathname = `./${pathname}` } - components.pop() + return { + organization: null, + version: null, + name: pathname, + pathname, + url, + isRelative, + hasManifest + } } - return null + // at this point, a named module was given + const components = pathname.split('/') + const [name, version = null] = components[0].split('@') + pathname = [name, ...components.slice(1)].filter(Boolean).join('/') + + // manifest was given, this could be a nested package + if (hasManifest) { + return { + organization: null, + name: pathname, + pathname, + url, + isRelative, + hasManifest + } + } + + return { + organization: null, + name, + version, + pathname, + url, + isRelative, + hasManifest + } } - #info = null - #index = null - #prefix = null - #manifest = null - #loader = null + /** + * Returns `true` if the given `input` can be parsed by `Name.parse` or given + * as input to the `Name` class constructor. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {boolean} + */ + static canParse (input, options = null) { + const origin = options?.origin ?? location.origin + + if (typeof input === 'string') { + input = input.trim() + } + + if (!input) { + return null + } + + // URL already given, ignore the origin + // @ts-ignore + return input instanceof URL || URL.canParse(input, origin) + } + + /** + * Creates a new `Name` from input. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {Name} + */ + static from (input, options = null) { + if (!Name.canParse(input, options)) { + throw new TypeError( + `Cannot create new 'Name'. Invalid 'input' given. Received: ${input}` + ) + } + + return new Name(Name.parse(input, options)) + } + + #name = null + #origin = null + #version = null + #pathname = null + #organization = null + + #isRelative = false + + /** + * `Name` class constructor. + * @param {string|URL|NameOptions|Name} name + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @throws TypeError + */ + constructor (name, options = null) { + /** @type {ParsedPackageName?} */ + let parsed = null + if (typeof name === 'string' || name instanceof URL) { + parsed = Name.parse(name, options) + } else if (name && typeof name === 'object') { + parsed = { + organization: name.organization || null, + name: name.name || null, + pathname: name.pathname || null, + version: name.version || null, + // @ts-ignore + url: name.url instanceof URL || URL.canParse(name.url) + ? new URL(name.url) + : null, + isRelative: name.isRelative || false, + hasManifest: name.hasManifest || false + } + } + + if (parsed === null) { + throw new TypeError(`Invalid 'name' given. Received: ${name}`) + } + + this.#name = parsed.name + this.#origin = parsed.url?.origin ?? location.origin + this.#version = parsed.version ?? null + this.#pathname = parsed.pathname + this.#organization = parsed.organization + + this.#isRelative = parsed.isRelative + } + + /** + * The id of this package name. + * @type {string} + */ + get id () { + return this.#pathname + } + + /** + * The actual package name. + * @type {string} + */ + get name () { return this.#name } + + /** + * The origin of the package, if available. + * @type {string?} + */ + get origin () { return this.#origin } + + /** + * The package version if available. + * @type {string} + */ + get version () { return this.#version } + + /** + * The actual package pathname. + * @type {string} + */ + get pathname () { return this.#pathname } + + /** + * The organization name. This value may be `null`. + * @type {string?} + */ + get organization () { return this.#organization } + + /** + * `true` if the package name was relative, otherwise `false`. + * @type {boolean} + */ + get isRelative () { return this.#isRelative } + + /** + * Converts this package name to a string. + * @ignore + * @return {string} + */ + toString () { + return this.id + } + + /** + * Converts this `Name` instance to JSON. + * @ignore + * @return {object} + */ + toJSON () { + return { + name: this.name, + origin: this.origin, + version: this.version, + pathname: this.pathname, + organization: this.organization + } + } +} + +/** + * A container for package dependencies that map a package name to a `Package` instance. + */ +export class Dependencies { + #map = new Map() + #origin = null + #package = null + + constructor (parent, options = null) { + this.#package = parent + this.#origin = options?.origin ?? parent?.origin + } + + get map () { + return this.#map + } + + get origin () { + return this.#origin ?? null + } + + add (name, info = null) { + if (info instanceof Package) { + this.#map.set(name, info) + } else { + this.#map.set(name, new Package(name, { + loader: this.#package.loader, + origin: this.#origin, + info + })) + } + + this.#map.get(name).load({ force: true }) + } + + get (name) { + return this.#map.get(name) + } + + entries () { + return this.#map.entries() + } + + keys () { + return this.#map.keys() + } + + values () { + return this.#map.values() + } + + load (options = null) { + for (const dependency of this.values()) { + dependency.load(options) + } + } + + [Symbol.iterator] () { + return this.#map.entries() + } +} + +/** + * A container for CommonJS module metadata, often in a `package.json` file. + */ +export class Package { + /** + * A high level class for a package name. + * @type {typeof Name} + */ + static Name = Name + + /** + * A high level container for package dependencies. + * @type {typeof Dependencies} + */ + static Dependencies = Dependencies #id = null #name = null #type = 'commonjs' - #exports = {} #license = null #version = null #description = null + #dependencies = null + + #info = null + #loader = null + + #exports = { + '.': { + require: './index.js', + import: './index.js', + default: './index.js' + } + } /** * `Package` class constructor. - * @param {string} name - * @param {PackageOptions} [options] + * @param {string|URL|NameOptions|Name} name + * @param {PackageOptions=} [options] */ constructor (name, options = null) { - options = { ...options } + options = /** @type {PackageOptions} */ ({ ...options }) - if (!name || typeof name !== 'string') { - throw new TypeError(`Expecting 'name' to be a string. Received: ${name}`) + if (typeof name !== 'string' && !(name instanceof Name) && !(name instanceof URL)) { + throw new TypeError(`Expecting 'name' to be a string or URL. Received: ${name}`) } - // early meta data + // the module loader + this.#loader = options.loader instanceof Loader + ? options.loader + : new Loader(options.loader) + this.#id = options.id ?? null - this.#name = name + this.#name = Name.from(name, { + origin: options.origin ?? options.loader?.origin ?? this.#loader.origin ?? null, + manifest: options.manifest ?? DEFAULT_PACKAGE_MANIFEST_FILE_NAME + }) + + // early meta data this.#info = options.info ?? null - this.#exports = options.exports ?? {} + this.#exports = options.exports ?? this.#exports this.#license = options.license ?? DEFAULT_LICENSE - this.#version = options.version ?? DEFAULT_PACKAGE_VERSION + this.#version = options.version ?? this.#name.version ?? DEFAULT_PACKAGE_VERSION this.#description = options.description ?? '' + this.#dependencies = new Dependencies(this) - // request paths - this.#index = options.index ?? DEFAULT_PACKAGE_INDEX - this.#prefix = options.prefix ?? DEFAULT_PACKAGE_PREFIX - this.#manifest = options.manifest ?? DEFAULT_PACKAGE_MANIFEST_FILE_NAME + if (options.dependencies && typeof options.dependencies === 'object') { + if (options.dependencies instanceof Dependencies || typeof options.dependencies.entries === 'function') { + for (const [key, value] of options.dependencies.entries()) { + this.#dependencies.add(key, value) + } + } else { + for (const key in options.dependencies) { + const value = options.dependencies[key] + this.#dependencies.add(key, value) + } + } + } - // the module loader - this.#loader = new Loader(options.loader) + if (!this.#exports || typeof this.#exports !== 'object') { + this.#exports = { '.': null } + } + + if (!this.#exports['.']) { + this.#exports = { + '.': { + require: options.index ?? DEFAULT_PACKAGE_INDEX, + import: options.index ?? DEFAULT_PACKAGE_INDEX, + default: options.index ?? DEFAULT_PACKAGE_INDEX + } + } + } } /** @@ -171,15 +566,7 @@ export class Package { * @type {string} */ get url () { - return new URL(this.#manifest, this.#id).href - } - - /** - * The package module path prefix. - * @type {string} - */ - get prefix () { - return this.#prefix + return new URL(this.#id).href } /** @@ -195,7 +582,7 @@ export class Package { * @type {string} */ get name () { - return this.#name ?? '' + return this.#name?.id ?? '' } /** @@ -206,6 +593,14 @@ export class Package { return this.#description ?? '' } + /** + * The organization of the package. This value may be `null`. + * @type {string?} + */ + get organization () { + return this.#name.organization ?? null + } + /** * The license of the package. * @type {string} @@ -227,7 +622,7 @@ export class Package { * @type {string} */ get origin () { - return new URL(this.#prefix, this.loader.origin).href + return this.loader.origin } /** @@ -254,20 +649,48 @@ export class Package { return this.#info ?? null } + /** + * @type {Dependencies} + */ + get dependencies () { + return this.#dependencies + } + + /** + * An alias for `entry` + * @type {string?} + */ + get main () { + return this.entry + } + /** * The entry to the package * @type {string?} */ get entry () { let entry = null + if (this.type === 'commonjs') { entry = this.#exports['.'].require } else if (this.type === 'module') { entry = this.#exports['.'].import - } else if (this.#exports['.'].default) { + } + + if (!entry && this.#exports['.'].default) { entry = this.#exports['.'].default } + if (isWorkerScope) { + if (!entry && this.#exports['.'].entry) { + entry = this.#exports['.'].entry + } + } + + if (!entry && this.#exports['.'].browser) { + entry = this.#exports['.'].browser + } + if (entry) { if (!entry.startsWith('./')) { entry = `./${entry}` @@ -284,26 +707,27 @@ export class Package { /** * Load the package information at an optional `origin` with * optional request `options`. - * @param {string|PackageLoadOptions=} [origin] * @param {PackageLoadOptions=} [options] * @throws SyntaxError * @return {boolean} */ - load (origin, options) { - if (this.#info) { - return true - } - + load (origin = null, options = null) { if (origin && typeof origin === 'object' && !(origin instanceof URL)) { options = origin origin = options.origin } + if (options?.force !== true && this.#info) { + return true + } + if (!origin) { - origin = this.origin.replace(this.#prefix, '') + origin = this.origin } - const pathname = `${this.name}/${this.#manifest}` + const prefix = options?.prefix ?? '' + const manifest = options?.manifest ?? DEFAULT_PACKAGE_MANIFEST_FILE_NAME + const pathname = `${prefix}${this.name}/${manifest}` const response = this.loader.load(pathname, origin, options) if (!response.text) { @@ -311,26 +735,38 @@ export class Package { } const info = JSON.parse(response.text) - const type = options?.type ?? info.type ?? this.type + + if (!info || typeof info !== 'object') { + return false + } + + const type = options?.type ?? info.type ?? this.#type this.#info = info - this.#exports['.'] = {} + this.#type = type this.#id = new URL('./', response.id).href - this.#name = info.name - this.#type = info.type ?? this.#type + this.#name = Name.from(info.name, { origin }) this.#license = info.license ?? 'Unlicensed' this.#version = info.version this.#description = info.description - if (info.main) { - if (type === 'commonjs') { - this.#exports['.'].require = info.main - } else if (type === 'module') { - this.#exports['.'].import = info.main + if (info.dependencies && typeof info.dependencies === 'object') { + for (const name in info.dependencies) { + const version = info.dependencies[name] + if (typeof version === 'string') { + this.#dependencies.add(name, { + version + }) + } } } + if (info.main) { + this.#exports['.'].require = info.main + this.#exports['.'].import = info.main + } + if (info.module) { this.#exports['.'].import = info.module } @@ -353,9 +789,9 @@ export class Package { if (typeof exports === 'string') { this.#exports[key] = {} if (this.#type === 'commonjs') { - this.#exports[key].require = info.exports + this.#exports[key].require = exports } else if (this.#type === 'module') { - this.#exports[key].import = info.exports + this.#exports[key].import = exports } } else if (typeof exports === 'object') { this.#exports[key] = exports @@ -363,6 +799,43 @@ export class Package { } } + for (const key in this.#exports) { + const exports = this.#exports[key] + if (Array.isArray(exports)) { + for (let i = 0; i < exports.length; ++i) { + const value = exports[i] + if (typeof value === 'string') { + if (value.startsWith('/')) { + exports[i] = `.${value}` + } else if (!value.startsWith('.')) { + exports[i] = `./${value}` + } + } + } + } else { + for (const condition in exports) { + const value = exports[condition] + if (Array.isArray(value)) { + for (let i = 0; i < value.length; ++i) { + if (typeof value[i] === 'string') { + if (value[i].startsWith('/')) { + value[i] = `.${value[i]}` + } else if (!value[i].startsWith('.')) { + value[i] = `./${value[i]}` + } + } + } + } else if (typeof value === 'string') { + if (value.startsWith('/')) { + exports[condition] = `.${value}` + } else if (!value.startsWith('.')) { + exports[condition] = `./${value}` + } + } + } + } + } + return true } @@ -373,42 +846,76 @@ export class Package { * @return {string} */ resolve (pathname, options = null) { - if (options?.load !== false && !this.load(options)) { - throw new ModuleNotFoundError( - `Cannnot find module '${pathname}`, - options?.children?.map?.((mod) => mod.id) - ) + if (options?.load !== false) { + this.load(options) } const { info } = this - const origin = options?.origin || this.id || this.origin + const manifest = options?.manifest ?? DEFAULT_PACKAGE_MANIFEST_FILE_NAME + const extname = path.extname(pathname) const type = options?.type ?? this.type - if (pathname instanceof URL) { - pathname = pathname.pathname + let origin = this.id + + // an absolute URL was given, just try to resolve it + // @ts-ignore + if (pathname instanceof URL || URL.canParse(pathname)) { + const url = new URL(pathname) + const response = this.loader.status(url.href, options) + + if (response.ok) { + return interpolateBrowserResolution(response.id) + } + + pathname = url.pathname + origin = url.origin } if (pathname === '.') { pathname = './' } else if (pathname.startsWith('/')) { pathname = `.${pathname}` - } else if (!pathname.startsWith('./')) { + } else if (!pathname.startsWith('.')) { pathname = `./${pathname}` } - if (pathname.endsWith('/')) { - pathname += 'index.js' + if (options?.origin) { + origin = options.origin + } else if (!origin) { + origin = new URL(this.name, this.origin).href + if (!origin.endsWith('/')) { + origin += '/' + } + } + + // if the pathname ends with the manifest file ('package.json'), then + // construct a new `Package` with the this packages loader and resolve it + if (pathname.endsWith(`/${manifest}`)) { + const url = new URL(`${this.name}/${pathname}`, origin) + const childPackage = new Package(url, { + loader: this.loader, + origin + }) + + // if it loaded, then just return the URL + if (childPackage.load()) { + return url.href + } } - const extname = path.extname(pathname) const extensions = extname !== '' ? new Set([extname]) : new Set(Array .from(options?.extensions ?? []) + .concat('') .concat(Array.from(this.loader.extensions)) - .filter((e) => typeof e === 'string' && e.length > 0) + .filter((e) => typeof e === 'string') ) + if (pathname.endsWith('/')) { + pathname += 'index.js' + } + for (const extension of extensions) { for (const key in this.#exports) { const exports = this.#exports[key] @@ -416,7 +923,13 @@ export class Package { ? pathname + extension : pathname - if ((pathname === './' && key === '.') || key === query) { + if ( + key === query || + key === pathname.replace(extname, '') || + (pathname === './' && key === '.') || + (pathname === './index' && key === '.') || + (pathname === './index.js' && key === '.') + ) { if (Array.isArray(exports)) { for (const filename of exports) { if (typeof filename === 'string') { @@ -428,14 +941,23 @@ export class Package { } } else { let filename = null - if (type === 'commonjs' && exports.require) { + if (isWorkerScope && exports.worker) { + filename = exports.worker + } if (type === 'commonjs' && exports.require) { filename = exports.require } else if (type === 'module' && exports.import) { filename = exports.import + } else if (exports.browser) { + filename = exports.browser } else if (exports.default) { filename = exports.default } else { - filename = exports.require || exports.import || exports.default + filename = ( + exports.require || + exports.import || + exports.browser || + exports.default + ) } if (filename) { @@ -450,6 +972,7 @@ export class Package { if (!extname) { let response = null + response = this.loader.load(pathname + extension, origin, options) if (response.ok) { @@ -469,13 +992,25 @@ export class Package { return interpolateBrowserResolution(response.id) } + // try to load 'package.json' + const url = new URL(`${this.name}/${pathname}/${manifest}`, origin) + const childPackage = new Package(url, { + loader: this.loader, + origin + }) + + // if it loaded, then return the package entry + if (childPackage.load()) { + return childPackage.entry + } + throw new ModuleNotFoundError( - `Cannnot find module '${pathname}`, + `Cannot find module '${pathname}'`, options?.children?.map?.((mod) => mod.id) ) function interpolateBrowserResolution (id) { - if (options?.browser === false) { + if (!info || options?.browser === false) { return id } From 44154e4a7c9fb6e5d33446fb975d19f2cbf788d2 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:39:38 -0400 Subject: [PATCH 0438/1178] fix(api/commonjs/require.js): fix module resolution, use 'Package' --- api/commonjs/require.js | 186 ++++++++++++++++++++++++++++++---------- 1 file changed, 142 insertions(+), 44 deletions(-) diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 76c513251f..b2bfa13f2b 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -48,6 +48,40 @@ export const globalPaths = [ new URL(DEFAULT_PACKAGE_PREFIX, location.origin).href ] +/** + * An object attached to a `require()` function that contains metadata + * about the current module context. + */ +export class Meta { + #referrer = null + #url = null + + /** + * `Meta` class constructor. + * @param {import('./module.js').Module} module + */ + constructor (module) { + this.#referrer = (module.parent ?? module.main).id ?? location.origin + this.#url = module.id + } + + /** + * The referrer (parent) of this module. + * @type {string} + */ + get referrer () { + return this.#referrer + } + + /** + * The referrer (parent) of this module. + * @type {string} + */ + get url () { + return this.#url + } +} + /** * Factory for creating a `require()` function based on a module context. * @param {CreateRequireOptions} options @@ -55,115 +89,174 @@ export const globalPaths = [ */ export function createRequire (options) { const { builtins, module } = options - const { cache, main } = module + const { cache, loaders, main } = module + + // non-standard 'require.meta' object + const meta = new Meta(module) Object.assign(resolve, { paths }) return Object.assign(require, { + extensions: loaders, resolve, + loaders, cache, + meta, main }) + /** + * @param {string} input + * @param {ResolveOptions & RequireOptions=} [options + * @ignore + */ + function applyResolvers (input, options = null) { + const resolvers = Array + .from(module.resolvers) + .concat(options?.resolvers) + .filter(Boolean) + + return next(input) + + function next (specifier) { + if (resolvers.length === 0) return specifier + const resolver = resolvers.shift() + return resolver(specifier, module, next) + } + } + /** * Requires a module at for a given `input` which can be a relative file, * named module, or an absolute URL. - * @param {string|URL} athname + * @param {string|URL} input * @param {RequireOptions=} [options] - * @return {string} + * @throws ModuleNotFoundError + * @throws ReferenceError + * @throws SyntaxError + * @throws TypeError + * @return {any} */ function require (input, options = null) { + const resolvedInput = applyResolvers(input, options) + + if (resolvedInput && typeof resolvedInput !== 'string') { + return resolvedInput + } + + if (resolvedInput.includes('\n') || resolvedInput.length > 256) { + return resolvedInput + } + + input = resolvedInput + if (isBuiltin(input, { builtins: options?.builtins ?? builtins })) { return getBuiltin(input, { builtins: options?.builtins ?? builtins }) } + if (cache[input]) { + return cache[input].exports + } + const resolved = resolve(input, { type: module.package.type, ...options }) - if (cache[input]) { - return cache[input].exports - } - - if (!resolved) { - throw new ModuleNotFoundError( - `Cannnot find module '${input}`, - module.children.map((mod) => mod.id) - ) + if (cache[resolved]) { + return cache[resolved].exports } const child = module.createModule(resolved, { ...options, package: input.startsWith('.') || input.startsWith('/') ? module.package - : Package.find(resolved, { + : new Package(resolved, { loader: new Loader(module.loader), ...options }) }) + cache[resolved] = child + cache[input] = child + if (child.load(options)) { - cache[resolved] = child return child.exports } + + throw new ModuleNotFoundError( + `Cannnot find module '${input}'`, + module.children.map((mod) => mod.id) + ) } /** * Resolve a module `input` to an absolute URL. * @param {string|URL} pathname * @param {ResolveOptions=} [options] + * @throws ModuleNotFoundError * @return {string} */ function resolve (input, options = null) { - if (isBuiltin(input, builtins)) { + if (input instanceof URL) { + input = String(input) + } + + const resolvedInput = applyResolvers(input, options) + + if (resolvedInput && typeof resolvedInput !== 'string') { + return input + } + + if (resolvedInput.includes('\n') || resolvedInput.length > 256) { return input } - const url = URL.canParse(input) - ? new URL(input) - : null + input = resolvedInput - const origins = url !== null - ? [url.origin] - : resolve.paths(input) + if (isBuiltin(input, { builtins: options?.builtins ?? builtins })) { + return input + } - if (url !== null) { - input = `.${url.pathname + url.search}` + // A URL was given, try to resolve it as a package + if (URL.canParse(input)) { + return module.package.resolve(input, { + type: module.package.type, + ...options + }) } - for (const origin of origins) { - if (url) { - const status = module.loader.status(input, origin, options) + const origins = resolve.paths(input) - if (status.ok) { - return status.id - } - } else if (input.startsWith('.') || input.startsWith('/')) { + for (const origin of origins) { + // relative require + if (input.startsWith('.') || input.startsWith('/')) { return module.resolve(input) - } else { - const components = input.split('/') - const manifest = new Package(components.shift(), { - prefix: '', - loader: new Loader(origin, module.loader) - }) - - const loaded = manifest.load({ - type: module.package.type, - ...options + } else { // named module + const moduleName = Package.Name.from(input) + const pathname = moduleName.pathname.replace(moduleName.name, '.') + const manifest = new Package(moduleName.name, { + loader: new Loader(origin) }) - if (loaded) { - const pathname = components.join('/') || '.' + try { return manifest.resolve(pathname, { type: module.package.type, ...options }) + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw err + } } } } + + throw new ModuleNotFoundError( + `Cannnot find module '${input}'`, + module.children.map((mod) => mod.id) + ) } /** @@ -198,10 +291,15 @@ export function createRequire (options) { origin = new URL('..', origin).href } - return Array + const results = Array .from(origins) - .map((origin) => new URL(options.prefix, origin)) + .map((origin) => origin.endsWith(options.prefix) + ? new URL(origin) + : new URL(options.prefix, origin) + ) .map((url) => url.href) + + return Array.from(new Set(results)) } } From 9ab2456efbb6acf1328aed3de4e65798060fef66 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:40:30 -0400 Subject: [PATCH 0439/1178] refactor(api/commonjs/module.js): use extension based loaders, support JSON and WASM --- api/commonjs/module.js | 299 ++++++++++++++++++++++++++++++++--------- 1 file changed, 237 insertions(+), 62 deletions(-) diff --git a/api/commonjs/module.js b/api/commonjs/module.js index c711423124..36dd5a8bfb 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -34,12 +34,19 @@ import path from '../path.js' * resolvers?: ModuleResolver[], * importmap?: ImportMap, * loader?: Loader | object, + * loaders?: object, * package?: Package | PackageOptions * parent?: Module, * state?: State * }} ModuleOptions */ +/** + * @typedef {{ + * extensions?: object + * }} ModuleLoadOptions + */ + export const builtinModules = builtins /** @@ -56,49 +63,21 @@ export function CommonJSModuleScope ( require, module, __filename, - __dirname + __dirname, + process, + global ) { - // eslint-disable-next-line no-unused-vars - const process = require('socket:process') - // eslint-disable-next-line no-unused-vars - const console = require('socket:console') // eslint-disable-next-line no-unused-vars const crypto = require('socket:crypto') // eslint-disable-next-line no-unused-vars const { Buffer } = require('socket:buffer') - // eslint-disable-next-line no-unused-vars - const global = new Proxy(globalThis, { - get (target, key, receiver) { - if (key === 'process') { - return process - } - - if (key === 'console') { - return console - } - - if (key === 'crypto') { - return crypto - } - - if (key === 'Buffer') { - return Buffer - } - - if (key === 'global') { - return global - } - - return Reflect.get(target, key, receiver) - } - }) // eslint-disable-next-line no-unused-expressions void exports, require, module, __filename, __dirname // eslint-disable-next-line no-unused-expressions void process, console, global, crypto, Buffer - return (async function () { + return (function () { 'module code' })() } @@ -171,17 +150,37 @@ export class State { * The module scope for a loaded module. * This is a special object that is seal, frozen, and only exposes an * accessor the 'exports' field. + * @ignore */ export class Scope { + #module = null #exports = Object.create(null) /** * `Scope` class constructor. + * @param {Module} module */ - constructor () { + constructor (module) { + this.#module = module Object.freeze(this) } + get id () { + return this.#module.id + } + + get filename () { + return this.#module.filename + } + + get loaded () { + return this.#module.loaded + } + + get children () { + return this.#module.children + } + get exports () { return this.#exports } @@ -192,11 +191,134 @@ export class Scope { toJSON () { return { + id: this.id, + filename: this.filename, + children: this.children, exports: this.#exports } } } +/** + * An abstract base class for loading a module. + */ +export class ModuleLoader { + /** + * Creates a `ModuleLoader` instance from the `module` currently being loaded. + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {ModuleLoader} + */ + static from (module, options = null) { + const loader = new this(module, options) + return loader + } + + /** + * Creates a new `ModuleLoader` instance from the `module` currently + * being loaded with the `source` string to parse and load with optional + * `ModuleLoadOptions` options. + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + static load (module, options = null) { + return this.from(module, options).load(module, options) + } + + /** + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + load (module, options = null) { + return false + } +} + +/** + * A JavaScript module loader + */ +export class JavaScriptModuleLoader extends ModuleLoader { + /** + * Loads the JavaScript module. + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + load (module, options = null) { + const response = module.loader.load(module.id, options) + // eslint-disable-next-line + const compiled = new Function(`return ${Module.wrap(response.text)}`)() + const __filename = module.id + const __dirname = path.dirname(__filename) + + // eslint-disable-next-line no-useless-call + const result = compiled.call(null, + module.scope.exports, + module.createRequire(options), + module.scope, + __filename, + __dirname, + process, + globalThis + ) + + if (typeof result?.catch === 'function') { + result.catch((error) => { + error.module = module + module.dispatchEvent(new ErrorEvent('error', { error })) + }) + } + + return true + } +} + +/** + * A JSON module loader. + */ +export class JSONModuleLoader extends ModuleLoader { + /** + * Loads the JSON module. + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + load (module, options = null) { + const response = module.loader.load(module.id, options) + module.scope.exports = JSON.parse(response.text) + return true + } +} + +/** + * A WASM module loader + */ +export class WASMModuleLoader extends ModuleLoader { + /** + * Loads the WASM module. + * @param {string} + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + load (module, options = null) { + const response = module.loader.load(module.id, { + ...options, + responseType: 'arraybuffer' + }) + + const instance = new WebAssembly.Instance( + new WebAssembly.Module(response.buffer), + options || undefined + ) + + module.scope.exports = instance.exports + return true + } +} + /** * A container for a loaded CommonJS module. All errors bubble * to the "main" module and global object (if possible). @@ -257,6 +379,28 @@ export class Module extends EventTarget { */ static globalPaths = globalPaths + /** + * Globabl module loaders + * @type {object} + */ + static loaders = Object.assign(Object.create(null), { + '.js' (source, module, options = null) { + return JavaScriptModuleLoader.load(source, module, options) + }, + + '.cjs' (source, module, options = null) { + return JavaScriptModuleLoader.load(source, module, options) + }, + + '.json' (source, module, options = null) { + return JSONModuleLoader.load(source, module, options) + }, + + '.wasm' (source, module, options = null) { + return WASMModuleLoader.load(source, module, options) + } + }) + /** * The main entry module, lazily created. * @type {Module} @@ -366,13 +510,13 @@ export class Module extends EventTarget { #scope = new Scope() #state = new State() #cache = Object.create(null) - #source = null #loader = null #parent = null #package = null #children = [] #resolvers = [] #importmap = new ImportMap() + #loaders = Object.create(null) /** * `Module` class constructor. @@ -410,6 +554,10 @@ export class Module extends EventTarget { : this.#parent?.package ?? new Package(options.name ?? this.#id, options.package) this.addEventListener('error', (event) => { + if (event.error) { + this.#state.error = event.error + } + if (Module.main === this) { // bubble error to globalThis, if possible if (typeof globalThis.dispatchEvent === 'function') { @@ -423,13 +571,12 @@ export class Module extends EventTarget { }) this.#importmap.extend(Module.importmap) + Object.assign(this.#loaders, Module.loaders) if (options.importmap) { this.#importmap.extend(options.importmap) } - this.#resolvers = Array.from(Module.resolvers) - if (Array.isArray(options.resolvers)) { for (const resolver of options.resolvers) { if (typeof resolver === 'function') { @@ -439,6 +586,8 @@ export class Module extends EventTarget { } if (this.#parent) { + Object.assign(this.#loaders, this.#parent.loaders) + if (Array.isArray(options?.resolvers)) { for (const resolver of options.resolvers) { if (typeof resolver === 'function') { @@ -450,6 +599,10 @@ export class Module extends EventTarget { this.#importmap.extend(this.#parent.importmap) } + if (options.loaders && typeof options.loaders === 'object') { + Object.assign(this.#loaders, options.loaders) + } + Module.cache.set(this.id, this) } @@ -508,7 +661,8 @@ export class Module extends EventTarget { * @type {ModuleResolver[]} */ get resolvers () { - return this.#resolvers + const resolvers = Array.from(Module.resolvers).concat(this.#resolvers) + return Array.from(new Set(resolvers)) } /** @@ -575,6 +729,22 @@ export class Module extends EventTarget { return this.#loader } + /** + * The filename of the module. + * @type {string} + */ + get filename () { + return this.#id + } + + /** + * Known source loaders for this module keyed by file extension. + * @type {object} + */ + get loaders () { + return this.#loaders + } + /** * Factory for creating a `require()` function based on a module context. * @param {CreateRequireOptions=} [options] @@ -604,42 +774,47 @@ export class Module extends EventTarget { } /** - * @param {object=} [options] + * Requires a module at for a given `input` which can be a relative file, + * named module, or an absolute URL within the context of this odule. + * @param {string|URL} input + * @param {RequireOptions=} [options] + * @throws ModuleNotFoundError + * @throws ReferenceError + * @throws SyntaxError + * @throws TypeError + * @return {any} + */ + require (url, options = null) { + const require = this.createRequire(options) + return require(url, options) + } + + /** + * Loads the module + * @param {ModuleLoadOptions=} [options] * @return {boolean} */ load (options = null) { - this.#source = Module.wrap(this.loader.load(this.id).text) - // eslint-disable-next-line - const define = new Function(`return ${this.#source}`)() - const __filename = this.id - const __dirname = path.dirname(__filename) + const extension = path.extname(this.id) + + if (typeof this.#loaders[extension] !== 'function') { + return false + } try { this.#state.loading = true - // eslint-disable-next-line no-useless-call - const promise = define.call(null, - this.scope.exports, - this.createRequire(options), - this.scope, - __filename, - __dirname, - process, - globalThis - ) - - promise.catch((error) => { - error.module = this - this.dispatchEvent(new ErrorEvent('error', { error })) - }) - if (this.parent) { - this.parent.children.push(this) - } + if (this.#loaders[extension](this, options)) { + if (this.#parent) { + this.#parent.children.push(this) + } - this.#state.loaded = true + this.#state.loaded = true + } } catch (error) { error.module = this this.#state.error = error + this.dispatchEvent(new ErrorEvent('error', { error })) throw error } finally { this.#state.loading = false From 902775f7c5f805fced925eb4cdd3c423237658e9 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:40:52 -0400 Subject: [PATCH 0440/1178] refactor(src/ipc/bridge.cc): allow buffer outputs in 'stdout/stderr' --- src/ipc/bridge.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index fd8fe87054..e21b2b87ab 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2556,10 +2556,16 @@ static void initRouterTable (Router *router) { * @param value */ router->map("stdout", [=](auto message, auto router, auto reply) { - #if defined(__APPLE__) - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", message.value.c_str()); - #endif - IO::write(message.value, false); + if (message.value.size() > 0) { + #if defined(__APPLE__) + os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", message.value.c_str()); + #endif + IO::write(message.value, false); + } else if (message.buffer.size > 0) { + IO::write(String(message.buffer.bytes, message.buffer.size), false); + } + + reply(Result { message.seq, message }); }); /** @@ -2571,12 +2577,16 @@ static void initRouterTable (Router *router) { if (message.value.size() > 0) { debug("%s", message.value.c_str()); } - } else { + } else if (message.value.size() > 0) { #if defined(__APPLE__) os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", message.value.c_str()); #endif IO::write(message.value, true); + } else if (message.buffer.size > 0) { + IO::write(String(message.buffer.bytes, message.buffer.size), true); } + + reply(Result { message.seq, message }); }); /** From 16211c75122f797e83bf9913ab33d714afcf9d5c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:41:12 -0400 Subject: [PATCH 0441/1178] fix(src/core/preload.cc): fix global 'process' object --- src/core/preload.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/preload.cc b/src/core/preload.cc index 78148a82a9..aa4dbaebda 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -289,7 +289,6 @@ namespace SSC { " const { Module } = await import(source); \n" " const process = await import('socket:process'); \n" " const path = await import('socket:path'); \n" - " \n" " const require = Module.createRequire(globalThis.location.href); \n" " const __filename = Module.main.filename; \n" " const __dirname = path.dirname(__filename); \n" @@ -315,7 +314,7 @@ namespace SSC { " process: { \n" " configurable: true, \n" " enumerable: false, \n" - " get: () => process, \n" + " get: () => process.default, \n" " }, \n" " __dirname: { \n" " configurable: true, \n" From 3fec0eb3ad7e2a7008f2294a7bb352d817d341de Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 00:41:31 -0400 Subject: [PATCH 0442/1178] refactor(src/cli/templates.hh): include 'npm:' and 'node:' prefix in CSP --- src/cli/templates.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 1e14f5744e..d21668b7ad 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -198,8 +198,8 @@ constexpr auto gHelloWorld = R"HTML( Date: Thu, 28 Mar 2024 00:42:20 -0400 Subject: [PATCH 0445/1178] test(commonjs/builtins.js): fix builtins test --- test/src/commonjs/builtins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/commonjs/builtins.js b/test/src/commonjs/builtins.js index 28773c9370..3dc3405748 100644 --- a/test/src/commonjs/builtins.js +++ b/test/src/commonjs/builtins.js @@ -32,7 +32,7 @@ test('builtins - dns', (t) => { }) test('builtins - events', (t) => { - t.equal(typeof events, 'object', 'events is function') + t.equal(typeof events, 'function', 'events is function') t.equal(typeof events?.EventEmitter, 'function', 'events.EventEmitter is function') }) From 295d032b29c8bca2b3a7eb834be6b77afab52271 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 01:34:23 -0400 Subject: [PATCH 0446/1178] feat(api/internal/error.js): initial error hooks before 'CallSite' implementation --- api/internal/error.js | 143 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 api/internal/error.js diff --git a/api/internal/error.js b/api/internal/error.js new file mode 100644 index 0000000000..e970335ad3 --- /dev/null +++ b/api/internal/error.js @@ -0,0 +1,143 @@ +import { CallSite } from './callsite.js' + +function applyPlatforErrorHook (PlatformError, target, message, ...args) { + const error = PlatformError.call(target, message, ...args) + const stack = error.stack.split('\n').slice(2) // slice off the `Error()` + `applyPlatforErrorHook()` call frames + const [, callsite] = stack[0].split('@') + + let stackValue = stack.join('\n') + + Object.defineProperties(error, { + column: { + configurable: true, + enumerable: false, + writable: true + }, + + line: { + configurable: true, + enumerable: false, + writable: true + }, + + message: { + configurable: true, + enumerable: false, + writable: true, + value: message + }, + + name: { + configurable: true, + enumerable: false, + writable: true, + value: target.constructor.name + }, + + sourceURL: { + configurable: true, + enumerable: false, + writable: true + }, + + stack: { + configurable: true, + enumerable: false, + set: (stack) => { + stackValue = stack + Object.defineProperty(error, 'stack', { + configurable: true, + enumerable: false, + writable: true, + value: stackValue + }) + }, + get: () => { + if (Error.stackTraceLimit === 0) { + stackValue = `${error.name}: ${error.message}` + } else { + if (typeof target.constructor.prepareStackTrace === 'function') { + const callsites = [] + stackValue = target.constructor.prepareStackTrace(error, callsites) + } + } + + Object.defineProperty(error, 'stack', { + configurable: true, + enumerable: false, + writable: true, + value: stackValue + }) + + return stackValue + } + } + }) + + if (URL.canParse(callsite)) { + const url = new URL(callsite) + const parts = url.pathname.split(':') + const line = parseInt(parts[1]) + const column = parseInt(parts[2]) + + error.sourceURL = new URL(parts[0] + url.search, url.origin).href + + if (Number.isFinite(line)) { + error.line = line + } + + if (Number.isFinite(column)) { + error.column = column + } + } else { + delete error.sourceURL + delete error.column + delete error.line + } + + Object.setPrototypeOf(error, target) + + return error +} + +function makeError (PlatformError) { + const Error = function Error (message, ...args) { + if (!(this instanceof Error)) { + return new Error(message, ...args) + } + + return applyPlatforErrorHook(PlatformError, this, message, ...args) + } + + Error.prototype = PlatformError.prototype + + /** + * @ignore + */ + Error.stackTraceLimit = 32 + + /** + * @ignore + */ + // eslint-disable-next-line + Error.captureStackTrace = function (err, ErrorConstructor) { + // TODO + } + + // Install + globalThis[PlatformError.name] = Error + + return Error +} + +export const Error = makeError(globalThis.Error) +export const URIError = makeError(globalThis.URIError) +export const EvalError = makeError(globalThis.EvalError) +export const TypeError = makeError(globalThis.TypeError) +export const RangeError = makeError(globalThis.RangeError) +export const SyntaxError = makeError(globalThis.SyntaxError) +export const ReferenceError = makeError(globalThis.ReferenceError) + +export default { + Error +} From 9a0c53a6414abfc7dcb3003202cbac8bbd4f32ab Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 01:34:44 -0400 Subject: [PATCH 0447/1178] refactor(api/internal/callsite.js): initial 'CallSite' structure --- api/internal/callsite.js | 104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 api/internal/callsite.js diff --git a/api/internal/callsite.js b/api/internal/callsite.js new file mode 100644 index 0000000000..9a7cbc7c34 --- /dev/null +++ b/api/internal/callsite.js @@ -0,0 +1,104 @@ +import { createHook } from '../async/hooks.js' + +let isAsyncContext = false +const asyncContexts = new Set([ + 'Promise', + 'Timeout', + 'Interval', + 'Immediate', + 'Microtask' +]) + +const hook = createHook({ + before (asyncId, type) { + if (asyncContexts.has(type)) { + isAsyncContext = true + } + }, + + after (asyncId, type) { + if (asyncContexts.has(type)) { + isAsyncContext = false + } + } +}) + +hook.enable() + +export class CallSite { + static PromiseElementIndexSymbol = Symbol.for('socket.runtime.CallSite.PromiseElementIndex') + static PromiseAllSymbol = Symbol.for('socket.runtime.CallSite.PromiseAll') + static PromiseAnySymbol = Symbol.for('socket.runtime.CallSite.PromiseAny') + + #error = null + + constructor (error) { + this.#error = error + } + + get error () { + return this.#error + } + + getThis () { + } + + getTypeName () { + } + + getFunction () { + } + + getFunctionName () { + const stack = this.#error.split('\n') + const parts = stack.split('@') + if (!parts[0]) { + return '' + } + + return parts[0] + } + + getFileName () { + if (URL.canParse(this.#error.sourceURL)) { + const url = new URL(this.#error.sourceURL) + return url.href + } + + return 'unknown location' + } + + getLineNumber () { + return this.#error.line + } + + getColumnNumber () { + return this.#error.column + } + + getEvalOrigin () { + } + + isTopLevel () { + } + + isEval () { + } + + isNative () { + return false + } + + isConstructor () { + } + + isAsync () { + return isAsyncContext + } + + isPromiseAll () { + return this.#error + } +} + +export default CallSite From c0f5e5f645d5946945b4f7eb4c0dc075f1494ad5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 01:35:08 -0400 Subject: [PATCH 0448/1178] fix(api/internal/promise.js): fix 'Promise.all' and 'Promise.any' --- api/internal/promise.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/internal/promise.js b/api/internal/promise.js index 1ea1c41d39..e07aef76fe 100644 --- a/api/internal/promise.js +++ b/api/internal/promise.js @@ -25,9 +25,9 @@ globalThis.Promise = class Promise extends NativePromise { } globalThis.Promise.all = function (iterable) { - return NativePromiseAll(...Array.from(iterable).map((promise, index) => { + return NativePromiseAll.call(NativePromise, Array.from(iterable).map((promise, index) => { return promise.catch((err) => { - throw Object.defineProperties(err, { + return Promise.reject(Object.defineProperties(err, { [Symbol.for('socket.runtime.CallSite.PromiseElementIndex')]: { configurable: false, enumerable: false, @@ -41,15 +41,15 @@ globalThis.Promise.all = function (iterable) { writable: false, value: true } - }) + })) }) })) } globalThis.Promise.any = function (iterable) { - return NativePromiseAny(...Array.from(iterable).map((promise, index) => { + return NativePromiseAny.call(NativePromise, Array.from(iterable).map((promise, index) => { return promise.catch((err) => { - throw Object.defineProperties(err, { + return Promise.reject(Object.defineProperties(err, { [Symbol.for('socket.runtime.CallSite.PromiseElementIndex')]: { configurable: false, enumerable: false, @@ -63,7 +63,7 @@ globalThis.Promise.any = function (iterable) { writable: false, value: true } - }) + })) }) })) } From f281e616c045cd61c2a9ce5b46093401e70ee93a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 28 Mar 2024 01:35:44 -0400 Subject: [PATCH 0449/1178] chore(api): generate types + docs --- api/README.md | 12 +- api/index.d.ts | 3392 ++++++++++++++++++++++++++++-------------------- 2 files changed, 1995 insertions(+), 1409 deletions(-) diff --git a/api/README.md b/api/README.md index 1cca6de1eb..159e2132c8 100644 --- a/api/README.md +++ b/api/README.md @@ -1998,17 +1998,17 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L17) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L19) This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. -## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L25) +## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L27) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L163) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L171) Adds callback to the 'nextTick' queue. @@ -2016,7 +2016,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L194) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L202) Computed high resolution time as a `BigInt`. @@ -2028,7 +2028,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L220) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L228) @@ -2036,7 +2036,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L232) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L240) Returns an object describing the memory usage of the Node.js process measured in bytes. diff --git a/api/index.d.ts b/api/index.d.ts index bcd55b8068..0fa18e07a0 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -901,8 +901,10 @@ declare module "socket:diagnostics" { declare module "socket:internal/symbols" { export const dispose: any; + export const serialize: any; namespace _default { export { dispose }; + export { serialize }; } export default _default; } @@ -1921,6 +1923,254 @@ declare module "socket:signal" { import { signal as constants } from "socket:os/constants"; } +declare module "socket:stream/web" { + export const ReadableStream: { + new (underlyingSource: UnderlyingByteSource, strategy?: { + highWaterMark?: number; + }): ReadableStream; + new (underlyingSource: UnderlyingDefaultSource, strategy?: QueuingStrategy): ReadableStream; + new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; + prototype: ReadableStream; + } | typeof UnsupportedStreamInterface; + export const ReadableStreamDefaultReader: { + new (stream: ReadableStream): ReadableStreamDefaultReader; + prototype: ReadableStreamDefaultReader; + } | typeof UnsupportedStreamInterface; + export const ReadableStreamBYOBReader: { + new (stream: ReadableStream): ReadableStreamBYOBReader; + prototype: ReadableStreamBYOBReader; + } | typeof UnsupportedStreamInterface; + export const ReadableStreamBYOBRequest: typeof UnsupportedStreamInterface; + export const ReadableByteStreamController: typeof UnsupportedStreamInterface; + export const ReadableStreamDefaultController: typeof UnsupportedStreamInterface; + export const TransformStream: { + new (transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy): TransformStream; + prototype: TransformStream; + } | typeof UnsupportedStreamInterface; + export const TransformStreamDefaultController: typeof UnsupportedStreamInterface; + export const WritableStream: { + new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): WritableStream; + prototype: WritableStream; + } | typeof UnsupportedStreamInterface; + export const WritableStreamDefaultWriter: { + new (stream: WritableStream): WritableStreamDefaultWriter; + prototype: WritableStreamDefaultWriter; + } | typeof UnsupportedStreamInterface; + export const WritableStreamDefaultController: typeof UnsupportedStreamInterface; + export const ByteLengthQueuingStrategy: { + new (init: QueuingStrategyInit): ByteLengthQueuingStrategy; + prototype: ByteLengthQueuingStrategy; + } | typeof UnsupportedStreamInterface; + export const CountQueuingStrategy: { + new (init: QueuingStrategyInit): CountQueuingStrategy; + prototype: CountQueuingStrategy; + } | typeof UnsupportedStreamInterface; + export const TextEncoderStream: typeof UnsupportedStreamInterface; + export const TextDecoderStream: { + new (label?: string, options?: TextDecoderOptions): TextDecoderStream; + prototype: TextDecoderStream; + } | typeof UnsupportedStreamInterface; + export const CompressionStream: { + new (format: CompressionFormat): CompressionStream; + prototype: CompressionStream; + } | typeof UnsupportedStreamInterface; + export const DecompressionStream: { + new (format: CompressionFormat): DecompressionStream; + prototype: DecompressionStream; + } | typeof UnsupportedStreamInterface; + export default exports; + class UnsupportedStreamInterface { + } + import * as exports from "socket:stream/web"; + +} + +declare module "socket:stream" { + export function pipelinePromise(...streams: any[]): Promise; + export function pipeline(stream: any, ...streams: any[]): any; + export function isStream(stream: any): boolean; + export function isStreamx(stream: any): boolean; + export function getStreamError(stream: any): any; + export function isReadStreamx(stream: any): any; + export { web }; + export default exports; + export class FixedFIFO { + constructor(hwm: any); + buffer: any[]; + mask: number; + top: number; + btm: number; + next: any; + clear(): void; + push(data: any): boolean; + shift(): any; + peek(): any; + isEmpty(): boolean; + } + export class FIFO { + constructor(hwm: any); + hwm: any; + head: exports.FixedFIFO; + tail: exports.FixedFIFO; + length: number; + clear(): void; + push(val: any): void; + shift(): any; + peek(): any; + isEmpty(): boolean; + } + export class WritableState { + constructor(stream: any, { highWaterMark, map, mapWritable, byteLength, byteLengthWritable }?: { + highWaterMark?: number; + map?: any; + mapWritable: any; + byteLength: any; + byteLengthWritable: any; + }); + stream: any; + queue: exports.FIFO; + highWaterMark: number; + buffered: number; + error: any; + pipeline: any; + drains: any; + byteLength: any; + map: any; + afterWrite: any; + afterUpdateNextTick: any; + get ended(): boolean; + push(data: any): boolean; + shift(): any; + end(data: any): void; + autoBatch(data: any, cb: any): any; + update(): void; + updateNonPrimary(): void; + continueUpdate(): boolean; + updateCallback(): void; + updateNextTick(): void; + } + export class ReadableState { + constructor(stream: any, { highWaterMark, map, mapReadable, byteLength, byteLengthReadable }?: { + highWaterMark?: number; + map?: any; + mapReadable: any; + byteLength: any; + byteLengthReadable: any; + }); + stream: any; + queue: exports.FIFO; + highWaterMark: number; + buffered: number; + readAhead: boolean; + error: any; + pipeline: exports.Pipeline; + byteLength: any; + map: any; + pipeTo: any; + afterRead: any; + afterUpdateNextTick: any; + get ended(): boolean; + pipe(pipeTo: any, cb: any): void; + push(data: any): boolean; + shift(): any; + unshift(data: any): void; + read(): any; + drain(): void; + update(): void; + updateNonPrimary(): void; + continueUpdate(): boolean; + updateCallback(): void; + updateNextTick(): void; + } + export class TransformState { + constructor(stream: any); + data: any; + afterTransform: any; + afterFinal: any; + } + export class Pipeline { + constructor(src: any, dst: any, cb: any); + from: any; + to: any; + afterPipe: any; + error: any; + pipeToFinished: boolean; + finished(): void; + done(stream: any, err: any): void; + } + export class Stream extends EventEmitter { + constructor(opts: any); + _duplexState: number; + _readableState: any; + _writableState: any; + _open(cb: any): void; + _destroy(cb: any): void; + _predestroy(): void; + get readable(): boolean; + get writable(): boolean; + get destroyed(): boolean; + get destroying(): boolean; + destroy(err: any): void; + } + export class Readable extends exports.Stream { + static _fromAsyncIterator(ite: any, opts: any): exports.Readable; + static from(data: any, opts: any): any; + static isBackpressured(rs: any): boolean; + static isPaused(rs: any): boolean; + _readableState: exports.ReadableState; + _read(cb: any): void; + pipe(dest: any, cb: any): any; + read(): any; + push(data: any): boolean; + unshift(data: any): void; + resume(): this; + pause(): this; + } + export class Writable extends exports.Stream { + static isBackpressured(ws: any): boolean; + static drained(ws: any): Promise; + _writableState: exports.WritableState; + _writev(batch: any, cb: any): void; + _write(data: any, cb: any): void; + _final(cb: any): void; + write(data: any): boolean; + end(data: any): this; + } + export class Duplex extends exports.Readable { + _writableState: exports.WritableState; + _writev(batch: any, cb: any): void; + _write(data: any, cb: any): void; + _final(cb: any): void; + write(data: any): boolean; + end(data: any): this; + } + export class Transform extends exports.Duplex { + _transformState: exports.TransformState; + _transform(data: any, cb: any): void; + _flush(cb: any): void; + } + export class PassThrough extends exports.Transform { + } + import web from "socket:stream/web"; + import * as exports from "socket:stream"; + import { EventEmitter } from "socket:events"; + +} + +declare module "socket:tty" { + export function WriteStream(fd: any): Writable; + export function ReadStream(fd: any): Readable; + export function isatty(fd: any): boolean; + namespace _default { + export { WriteStream }; + export { ReadStream }; + export { isatty }; + } + export default _default; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; +} + declare module "socket:process" { /** * Adds callback to the 'nextTick' queue. @@ -2512,240 +2762,6 @@ declare module "socket:path" { export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG }; } -declare module "socket:stream/web" { - export const ReadableStream: { - new (underlyingSource: UnderlyingByteSource, strategy?: { - highWaterMark?: number; - }): ReadableStream; - new (underlyingSource: UnderlyingDefaultSource, strategy?: QueuingStrategy): ReadableStream; - new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; - prototype: ReadableStream; - } | typeof UnsupportedStreamInterface; - export const ReadableStreamDefaultReader: { - new (stream: ReadableStream): ReadableStreamDefaultReader; - prototype: ReadableStreamDefaultReader; - } | typeof UnsupportedStreamInterface; - export const ReadableStreamBYOBReader: { - new (stream: ReadableStream): ReadableStreamBYOBReader; - prototype: ReadableStreamBYOBReader; - } | typeof UnsupportedStreamInterface; - export const ReadableStreamBYOBRequest: typeof UnsupportedStreamInterface; - export const ReadableByteStreamController: typeof UnsupportedStreamInterface; - export const ReadableStreamDefaultController: typeof UnsupportedStreamInterface; - export const TransformStream: { - new (transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy): TransformStream; - prototype: TransformStream; - } | typeof UnsupportedStreamInterface; - export const TransformStreamDefaultController: typeof UnsupportedStreamInterface; - export const WritableStream: { - new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): WritableStream; - prototype: WritableStream; - } | typeof UnsupportedStreamInterface; - export const WritableStreamDefaultWriter: { - new (stream: WritableStream): WritableStreamDefaultWriter; - prototype: WritableStreamDefaultWriter; - } | typeof UnsupportedStreamInterface; - export const WritableStreamDefaultController: typeof UnsupportedStreamInterface; - export const ByteLengthQueuingStrategy: { - new (init: QueuingStrategyInit): ByteLengthQueuingStrategy; - prototype: ByteLengthQueuingStrategy; - } | typeof UnsupportedStreamInterface; - export const CountQueuingStrategy: { - new (init: QueuingStrategyInit): CountQueuingStrategy; - prototype: CountQueuingStrategy; - } | typeof UnsupportedStreamInterface; - export const TextEncoderStream: typeof UnsupportedStreamInterface; - export const TextDecoderStream: { - new (label?: string, options?: TextDecoderOptions): TextDecoderStream; - prototype: TextDecoderStream; - } | typeof UnsupportedStreamInterface; - export const CompressionStream: { - new (format: CompressionFormat): CompressionStream; - prototype: CompressionStream; - } | typeof UnsupportedStreamInterface; - export const DecompressionStream: { - new (format: CompressionFormat): DecompressionStream; - prototype: DecompressionStream; - } | typeof UnsupportedStreamInterface; - export default exports; - class UnsupportedStreamInterface { - } - import * as exports from "socket:stream/web"; - -} - -declare module "socket:stream" { - export function pipelinePromise(...streams: any[]): Promise; - export function pipeline(stream: any, ...streams: any[]): any; - export function isStream(stream: any): boolean; - export function isStreamx(stream: any): boolean; - export function getStreamError(stream: any): any; - export function isReadStreamx(stream: any): any; - export { web }; - export default exports; - export class FixedFIFO { - constructor(hwm: any); - buffer: any[]; - mask: number; - top: number; - btm: number; - next: any; - clear(): void; - push(data: any): boolean; - shift(): any; - peek(): any; - isEmpty(): boolean; - } - export class FIFO { - constructor(hwm: any); - hwm: any; - head: exports.FixedFIFO; - tail: exports.FixedFIFO; - length: number; - clear(): void; - push(val: any): void; - shift(): any; - peek(): any; - isEmpty(): boolean; - } - export class WritableState { - constructor(stream: any, { highWaterMark, map, mapWritable, byteLength, byteLengthWritable }?: { - highWaterMark?: number; - map?: any; - mapWritable: any; - byteLength: any; - byteLengthWritable: any; - }); - stream: any; - queue: exports.FIFO; - highWaterMark: number; - buffered: number; - error: any; - pipeline: any; - drains: any; - byteLength: any; - map: any; - afterWrite: any; - afterUpdateNextTick: any; - get ended(): boolean; - push(data: any): boolean; - shift(): any; - end(data: any): void; - autoBatch(data: any, cb: any): any; - update(): void; - updateNonPrimary(): void; - continueUpdate(): boolean; - updateCallback(): void; - updateNextTick(): void; - } - export class ReadableState { - constructor(stream: any, { highWaterMark, map, mapReadable, byteLength, byteLengthReadable }?: { - highWaterMark?: number; - map?: any; - mapReadable: any; - byteLength: any; - byteLengthReadable: any; - }); - stream: any; - queue: exports.FIFO; - highWaterMark: number; - buffered: number; - readAhead: boolean; - error: any; - pipeline: exports.Pipeline; - byteLength: any; - map: any; - pipeTo: any; - afterRead: any; - afterUpdateNextTick: any; - get ended(): boolean; - pipe(pipeTo: any, cb: any): void; - push(data: any): boolean; - shift(): any; - unshift(data: any): void; - read(): any; - drain(): void; - update(): void; - updateNonPrimary(): void; - continueUpdate(): boolean; - updateCallback(): void; - updateNextTick(): void; - } - export class TransformState { - constructor(stream: any); - data: any; - afterTransform: any; - afterFinal: any; - } - export class Pipeline { - constructor(src: any, dst: any, cb: any); - from: any; - to: any; - afterPipe: any; - error: any; - pipeToFinished: boolean; - finished(): void; - done(stream: any, err: any): void; - } - export class Stream extends EventEmitter { - constructor(opts: any); - _duplexState: number; - _readableState: any; - _writableState: any; - _open(cb: any): void; - _destroy(cb: any): void; - _predestroy(): void; - get readable(): boolean; - get writable(): boolean; - get destroyed(): boolean; - get destroying(): boolean; - destroy(err: any): void; - } - export class Readable extends exports.Stream { - static _fromAsyncIterator(ite: any, opts: any): exports.Readable; - static from(data: any, opts: any): any; - static isBackpressured(rs: any): boolean; - static isPaused(rs: any): boolean; - _readableState: exports.ReadableState; - _read(cb: any): void; - pipe(dest: any, cb: any): any; - read(): any; - push(data: any): boolean; - unshift(data: any): void; - resume(): this; - pause(): this; - } - export class Writable extends exports.Stream { - static isBackpressured(ws: any): boolean; - static drained(ws: any): Promise; - _writableState: exports.WritableState; - _writev(batch: any, cb: any): void; - _write(data: any, cb: any): void; - _final(cb: any): void; - write(data: any): boolean; - end(data: any): this; - } - export class Duplex extends exports.Readable { - _writableState: exports.WritableState; - _writev(batch: any, cb: any): void; - _write(data: any, cb: any): void; - _final(cb: any): void; - write(data: any): boolean; - end(data: any): this; - } - export class Transform extends exports.Duplex { - _transformState: exports.TransformState; - _transform(data: any, cb: any): void; - _flush(cb: any): void; - } - export class PassThrough extends exports.Transform { - } - import web from "socket:stream/web"; - import * as exports from "socket:stream"; - import { EventEmitter } from "socket:events"; - -} - declare module "socket:fs/stream" { export const DEFAULT_STREAM_HIGH_WATER_MARK: number; /** @@ -11247,732 +11263,1565 @@ declare module "socket:index" { export { network, Cache, sha256, Encryption, Packet, NAT }; } -declare module "socket:stream-relay/proxy" { - export default PeerWorkerProxy; +declare module "socket:commonjs/cache" { /** - * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. - * @param {{ createSocket: function('udp4', null, object?): object }} options - */ - export class PeerWorkerProxy { - constructor(options: any, port: any, fn: any); - init(): Promise; - reconnect(): Promise; - disconnect(): Promise; - getInfo(): Promise; - getState(): Promise; - open(...args: any[]): Promise; - seal(...args: any[]): Promise; - sealUnsigned(...args: any[]): Promise; - openUnsigned(...args: any[]): Promise; - addEncryptionKey(...args: any[]): Promise; - send(...args: any[]): Promise; - sendUnpublished(...args: any[]): Promise; - cacheInsert(...args: any[]): Promise; - mcast(...args: any[]): Promise; - requestReflection(...args: any[]): Promise; - join(...args: any[]): Promise; - publish(...args: any[]): Promise; - sync(...args: any[]): Promise; - close(...args: any[]): Promise; - query(...args: any[]): Promise; - compileCachePredicate(src: any): Promise; - callWorkerThread(prop: any, data: any): Deferred; - callMainThread(prop: any, args: any): void; - resolveMainThread(seq: any, data: any): void; - #private; - } - import { Deferred } from "socket:async"; -} - -declare module "socket:stream-relay/api" { - export default api; - /** - * Initializes and returns the network bus. - * - * @async - * @function - * @param {object} options - Configuration options for the network bus. - * @param {object} events - A nodejs compatibe implementation of the events module. - * @param {object} dgram - A nodejs compatible implementation of the dgram module. - * @returns {Promise} - A promise that resolves to the initialized network bus. - */ - export function api(options: object, events: object, dgram: object): Promise; -} - -declare module "socket:network" { - export default network; - export function network(options: any): Promise; - import { Cache } from "socket:stream-relay/index"; - import { sha256 } from "socket:stream-relay/index"; - import { Encryption } from "socket:stream-relay/index"; - import { Packet } from "socket:stream-relay/index"; - import { NAT } from "socket:stream-relay/index"; - export { Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:string_decoder" { - export function StringDecoder(encoding: any): void; - export class StringDecoder { - constructor(encoding: any); - encoding: any; - text: typeof utf16Text | typeof base64Text; - end: typeof utf16End | typeof base64End | typeof simpleEnd; - fillLast: typeof utf8FillLast; - write: typeof simpleWrite; - lastNeed: number; - lastTotal: number; - lastChar: Uint8Array; - } - export default StringDecoder; - function utf16Text(buf: any, i: any): any; - class utf16Text { - constructor(buf: any, i: any); - lastNeed: number; - lastTotal: number; - } - function base64Text(buf: any, i: any): any; - class base64Text { - constructor(buf: any, i: any); - lastNeed: number; - lastTotal: number; - } - function utf16End(buf: any): any; - function base64End(buf: any): any; - function simpleEnd(buf: any): any; - function utf8FillLast(buf: any): any; - function simpleWrite(buf: any): any; -} - -declare module "socket:test/context" { - export default function _default(GLOBAL_TEST_RUNNER: any): void; -} - -declare module "socket:test/dom-helpers" { - /** - * Converts querySelector string to an HTMLElement or validates an existing HTMLElement. - * - * @export - * @param {string|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @returns {Element} The HTMLElement, Element, or Window that corresponds to the selector. - * @throws {Error} Throws an error if the `selector` is not a string that resolves to an HTMLElement or not an instance of HTMLElement, Element, or Window. - * - */ - export function toElement(selector: string | Element): Element; - /** - * Waits for an element to appear in the DOM and resolves the promise when it does. - * - * @export - * @param {Object} args - Configuration arguments. - * @param {string} [args.selector] - The CSS selector to look for. - * @param {boolean} [args.visible=true] - Whether the element should be visible. - * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. - * @param {() => HTMLElement | Element | null | undefined} [lambda] - An optional function that returns the element. Used if the `selector` is not provided. - * @returns {Promise} - A promise that resolves to the found element. - * - * @throws {Error} - Throws an error if neither `lambda` nor `selector` is provided. - * @throws {Error} - Throws an error if the element is not found within the timeout. - * - * @example - * ```js - * waitFor({ selector: '#my-element', visible: true, timeout: 5000 }) - * .then(el => console.log('Element found:', el)) - * .catch(err => console.log('Element not found:', err)); - * ``` - */ - export function waitFor(args: { - selector?: string; - visible?: boolean; - timeout?: number; - }, lambda?: () => HTMLElement | Element | null | undefined): Promise; - /** - * Waits for an element's text content to match a given string or regular expression. - * - * @export - * @param {Object} args - Configuration arguments. - * @param {Element} args.element - The root element from which to begin searching. - * @param {string} [args.text] - The text to search for within elements. - * @param {RegExp} [args.regex] - A regular expression to match against element text content. - * @param {boolean} [args.multipleTags=false] - Whether to look for text across multiple sibling elements. - * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. - * @returns {Promise} - A promise that resolves to the found element or null. - * - * @example - * ```js - * waitForText({ element: document.body, text: 'Hello', timeout: 5000 }) - * .then(el => console.log('Element found:', el)) - * .catch(err => console.log('Element not found:', err)); - * ``` - */ - export function waitForText(args: { - element: Element; - text?: string; - regex?: RegExp; - multipleTags?: boolean; - timeout?: number; - }): Promise; - /** - * @export - * @param {Object} args - Arguments - * @param {string | Event} args.event - The event to dispatch. - * @param {HTMLElement | Element | window} [args.element=window] - The element to dispatch the event on. - * @returns {void} - * - * @throws {Error} Throws an error if the `event` is not a string that can be converted to a CustomEvent or not an instance of Event. - */ - export function event(args: { - event: string | Event; - element?: HTMLElement | Element | (Window & typeof globalThis); - }): void; - /** - * @export - * Copy pasted from https://raw.githubusercontent.com/testing-library/jest-dom/master/src/to-be-visible.js - * @param {Element | HTMLElement} element - * @param {Element | HTMLElement} [previousElement] - * @returns {boolean} - */ - export function isElementVisible(element: Element | HTMLElement, previousElement?: Element | HTMLElement): boolean; -} - -declare module "socket:test/index" { - /** - * @returns {number} - The default timeout for tests in milliseconds. - */ - export function getDefaultTestRunnerTimeout(): number; - /** - * @param {string} name - * @param {TestFn} [fn] - * @returns {void} - */ - export function only(name: string, fn?: TestFn): void; - /** - * @param {string} _name - * @param {TestFn} [_fn] - * @returns {void} - */ - export function skip(_name: string, _fn?: TestFn): void; - /** - * @param {boolean} strict - * @returns {void} - */ - export function setStrict(strict: boolean): void; - /** - * @typedef {{ - * (name: string, fn?: TestFn): void - * only(name: string, fn?: TestFn): void - * skip(name: string, fn?: TestFn): void - * }} testWithProperties - * @ignore - */ - /** - * @type {testWithProperties} - * @param {string} name - * @param {TestFn} [fn] - * @returns {void} - */ - export function test(name: string, fn?: TestFn): void; - export namespace test { - export { only }; - export { skip }; - } - /** - * @typedef {(t: Test) => (void | Promise)} TestFn + * @typedef {{ + * types?: object, + * loader?: import('./loader.js').Loader + * }} CacheOptions */ + export const CACHE_CHANNEL_MESSAGE_ID: "id"; + export const CACHE_CHANNEL_MESSAGE_REPLICATE: "replicate"; /** - * @class + * A container for a shared cache that lives for the life time of + * application execution. Updates to this storage are replicated to other + * instances in the application context, including windows and workers. */ - export class Test { + export class Cache { + static data: Map; + static types: Map; /** - * @constructor + * `Cache` class constructor. * @param {string} name - * @param {TestFn} fn - * @param {TestRunner} runner + * @param {CacheOptions=} [options] */ - constructor(name: string, fn: TestFn, runner: TestRunner); + constructor(name: string, options?: CacheOptions | undefined); /** + * The unique ID for this cache. * @type {string} - * @ignore */ - name: string; + get id(): string; /** - * @type {null|number} - * @ignore + * The loader associated with this cache. + * @type {import('./loader.js').Loader} */ - _planned: null | number; + get loader(): import("socket:commonjs/loader").Loader; /** - * @type {null|number} - * @ignore + * The cache name + * @type {string} */ - _actual: null | number; + get name(): string; /** - * @type {TestFn} - * @ignore + * The underlying cache data map. + * @type {Map} */ - fn: TestFn; + get data(): Map; /** - * @type {TestRunner} - * @ignore + * The broadcast channel associated with this cach. + * @type {BroadcastChannel} */ - runner: TestRunner; + get channel(): BroadcastChannel; /** - * @type{{ pass: number, fail: number }} - * @ignore + * The size of the cache. + * @type {number} */ - _result: { - pass: number; - fail: number; - }; + get size(): number; /** - * @type {boolean} - * @ignore + * Get a value at `key`. + * @param {string} key + * @return {object|undefined} */ - done: boolean; + get(key: string): object | undefined; /** - * @type {boolean} - * @ignore + * Set `value` at `key`. + * @param {string} key + * @param {object} value + * @return {Cache} */ - strict: boolean; + set(key: string, value: object): Cache; /** - * @param {string} msg - * @returns {void} + * Returns `true` if `key` is in cache, otherwise `false`. + * @param {string} + * @return {boolean} */ - comment(msg: string): void; + has(key: any): boolean; /** - * Plan the number of assertions. - * - * @param {number} n - * @returns {void} + * Delete a value at `key`. + * This does not replicate to shared caches. + * @param {string} key + * @return {object|undefined} */ - plan(n: number): void; + delete(key: string): object | undefined; /** - * @template T - * @param {T} actual - * @param {T} expected - * @param {string} [msg] - * @returns {void} + * Returns an iterator for all cache keys. + * @return {object} */ - deepEqual(actual: T, expected: T, msg?: string): void; + keys(): object; /** - * @template T - * @param {T} actual - * @param {T} expected - * @param {string} [msg] - * @returns {void} + * Returns an iterator for all cache values. + * @return {object} */ - notDeepEqual(actual: T_1, expected: T_1, msg?: string): void; + values(): object; /** - * @template T - * @param {T} actual - * @param {T} expected - * @param {string} [msg] - * @returns {void} + * Returns an iterator for all cache entries. + * @return {object} */ - equal(actual: T_2, expected: T_2, msg?: string): void; + entries(): object; /** - * @param {unknown} actual - * @param {unknown} expected - * @param {string} [msg] - * @returns {void} + * Clears all entries in the cache. + * This does not replicate to shared caches. + * @return {undefined} */ - notEqual(actual: unknown, expected: unknown, msg?: string): void; + clear(): undefined; /** - * @param {string} [msg] - * @returns {void} + * Enumerates entries in map calling `callback(value, key + * @param {function(object, string, Cache): any} callback */ - fail(msg?: string): void; + forEach(callback: (arg0: object, arg1: string, arg2: Cache) => any): void; /** - * @param {unknown} actual - * @param {string} [msg] - * @returns {void} + * Broadcasts a replication to other shared caches. */ - ok(actual: unknown, msg?: string): void; + replicate(): this; /** - * @param {string} [msg] - * @returns {void} + * Destroys the cache. This function stops the broadcast channel and removes + * and listeners */ - pass(msg?: string): void; + destroy(): void; /** - * @param {Error | null | undefined} err - * @param {string} [msg] - * @returns {void} + * @ignore */ - ifError(err: Error | null | undefined, msg?: string): void; + [Symbol.iterator](): IterableIterator<[any, any]>; + #private; + } + export default Cache; + export type CacheOptions = { + types?: object; + loader?: import("socket:commonjs/loader").Loader; + }; +} + +declare module "socket:commonjs/loader" { + /** + * @typedef {{ + * extensions?: string[] | Set + * origin?: URL | string, + * statuses?: Cache + * cache?: { response?: Cache, status?: Cache } + * }} LoaderOptions + */ + /** + * @typedef {{ + * loader?: Loader, + * origin?: URL | string + * }} RequestOptions + */ + /** + * @typedef {{ + * headers?: Headers | object | array[], + * status?: number + * }} RequestStatusOptions + */ + /** + * @typedef {{ + * headers?: Headers | object + * }} RequestLoadOptions + */ + /** + * @typedef {{ + * request?: Request, + * headers?: Headers, + * status?: number, + * buffer?: ArrayBuffer, + * text?: string + * }} ResponseOptions + */ + /** + * A container for the status of a CommonJS resource. A `RequestStatus` object + * represents meta data for a `Request` that comes from a preflight + * HTTP HEAD request. + */ + export class RequestStatus { /** - * @param {Function} fn - * @param {RegExp | any} [expected] - * @param {string} [message] - * @returns {void} + * Creates a `RequestStatus` from JSON input. + * @param {object} json + * @return {RequestStatus} */ - throws(fn: Function, expected?: RegExp | any, message?: string): void; + static from(json: object): RequestStatus; /** - * Sleep for ms with an optional msg - * - * @param {number} ms - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.sleep(100) - * ``` + * `RequestStatus` class constructor. + * @param {Request} request + * @param {RequestStatusOptions} [options] */ - sleep(ms: number, msg?: string): Promise; + constructor(request: Request, options?: RequestStatusOptions); + set request(request: Request); /** - * Request animation frame with an optional msg. Falls back to a 0ms setTimeout when - * tests are run headlessly. - * - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.requestAnimationFrame() - * ``` + * The `Request` object associated with this `RequestStatus` object. + * @type {Request} */ - requestAnimationFrame(msg?: string): Promise; + get request(): Request; /** - * Dispatch the `click`` method on an element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.click('.class button', 'Click a button') - * ``` + * The unique ID of this `RequestStatus`, which is the absolute URL as a string. + * @type {string} */ - click(selector: string | HTMLElement | Element, msg?: string): Promise; + get id(): string; /** - * Dispatch the click window.MouseEvent on an element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.eventClick('.class button', 'Click a button with an event') - * ``` + * The origin for this `RequestStatus` object. + * @type {string} */ - eventClick(selector: string | HTMLElement | Element, msg?: string): Promise; + get origin(): string; /** - * Dispatch an event on the target. - * - * @param {string | Event} event - The event name or Event instance to dispatch. - * @param {string|HTMLElement|Element} target - A CSS selector string, or an instance of HTMLElement, or Element to dispatch the event on. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.dispatchEvent('my-event', '#my-div', 'Fire the my-event event') - * ``` + * A HTTP status code for this `RequestStatus` object. + * @type {number|undefined} */ - dispatchEvent(event: string | Event, target: string | HTMLElement | Element, msg?: string): Promise; + get status(): number; /** - * Call the focus method on element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.focus('#my-div') - * ``` + * An alias for `status`. + * @type {number|undefined} */ - focus(selector: string | HTMLElement | Element, msg?: string): Promise; + get value(): number; /** - * Call the blur method on element specified by selector. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.blur('#my-div') - * ``` + * @ignore */ - blur(selector: string | HTMLElement | Element, msg?: string): Promise; + get valueOf(): number; /** - * Consecutively set the str value of the element specified by selector to simulate typing. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {string} str - The string to type into the :focus element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.typeValue('#my-div', 'Hello World', 'Type "Hello World" into #my-div') - * ``` + * The HTTP headers for this `RequestStatus` object. + * @type {Headers} */ - type(selector: string | HTMLElement | Element, str: string, msg?: string): Promise; + get headers(): Headers; /** - * appendChild an element el to a parent selector element. - * - * @param {string|HTMLElement|Element} parentSelector - A CSS selector string, or an instance of HTMLElement, or Element to appendChild on. - * @param {HTMLElement|Element} el - A element to append to the parent element. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * const myElement = createElement('div') - * await t.appendChild('#parent-selector', myElement, 'Append myElement into #parent-selector') - * ``` + * The resource location for this `RequestStatus` object. This value is + * determined from the 'Content-Location' header, if available, otherwise + * it is derived from the request URL pathname (including the query string). + * @type {string} */ - appendChild(parentSelector: string | HTMLElement | Element, el: HTMLElement | Element, msg?: string): Promise; + get location(): string; /** - * Remove an element from the DOM. - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to remove from the DOM. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.removeElement('#dom-selector', 'Remove #dom-selector') - * ``` + * `true` if the response status is considered OK, otherwise `false`. + * @type {boolean} */ - removeElement(selector: string | HTMLElement | Element, msg?: string): Promise; + get ok(): boolean; /** - * Test if an element is visible - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.elementVisible('#dom-selector','Element is visible') - * ``` + * Loads the internal state for this `RequestStatus` object. + * @param {RequestLoadOptions|boolean} [options] + * @return {RequestStatus} */ - elementVisible(selector: string | HTMLElement | Element, msg?: string): Promise; + load(options?: RequestLoadOptions | boolean): RequestStatus; /** - * Test if an element is invisible - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.elementInvisible('#dom-selector','Element is invisible') - * ``` + * Converts this `RequestStatus` to JSON. + * @ignore + * @return {{ + * id: string, + * origin: string | null, + * status: number, + * headers: Array + * request: object | null | undefined + * }} */ - elementInvisible(selector: string | HTMLElement | Element, msg?: string): Promise; - /** - * Test if an element is invisible - * - * @param {string|(() => HTMLElement|Element|null|undefined)} querySelectorOrFn - A query string or a function that returns an element. - * @param {Object} [opts] - * @param {boolean} [opts.visible] - The element needs to be visible. - * @param {number} [opts.timeout] - The maximum amount of time to wait. - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.waitFor('#dom-selector', { visible: true },'#dom-selector is on the page and visible') - * ``` + toJSON(includeRequest?: boolean): { + id: string; + origin: string | null; + status: number; + headers: Array; + request: object | null | undefined; + }; + #private; + } + /** + * A container for a synchronous CommonJS request to local resource or + * over the network. + */ + export class Request { + /** + * Creates a `Request` instance from JSON input + * @param {object} json + * @param {RequestOptions=} [options] + * @return {Request} */ - waitFor(querySelectorOrFn: string | (() => HTMLElement | Element | null | undefined), opts?: { - visible?: boolean; - timeout?: number; - }, msg?: string): Promise; + static from(json: object, options?: RequestOptions | undefined): Request; /** - * @typedef {Object} WaitForTextOpts - * @property {string} [text] - The text to wait for - * @property {number} [timeout] - * @property {Boolean} [multipleTags] - * @property {RegExp} [regex] The regex to wait for + * `Request` class constructor. + * @param {URL|string} url + * @param {URL|string=} [origin] + * @param {RequestOptions=} [options] */ + constructor(url: URL | string, origin?: (URL | string) | undefined, options?: RequestOptions | undefined); /** - * Test if an element is invisible - * - * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. - * @param {WaitForTextOpts | string | RegExp} [opts] - * @param {string} [msg] - * @returns {Promise} - * - * @example - * ```js - * await t.waitForText('#dom-selector', 'Text to wait for') - * ``` - * - * @example - * ```js - * await t.waitForText('#dom-selector', /hello/i) - * ``` - * - * @example - * ```js - * await t.waitForText('#dom-selector', { - * text: 'Text to wait for', - * multipleTags: true - * }) - * ``` + * The unique ID of this `Request`, which is the absolute URL as a string. + * @type {string} */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { - /** - * - The text to wait for - */ - text?: string; - timeout?: number; - multipleTags?: boolean; - /** - * The regex to wait for - */ - regex?: RegExp; - }, msg?: string): Promise; + get id(): string; /** - * Run a querySelector as an assert and also get the results - * - * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. - * @param {string} [msg] - * @returns {HTMLElement | Element} - * - * @example - * ```js - * const element = await t.querySelector('#dom-selector') - * ``` + * The absolute `URL` of this `Request` object. + * @type {URL} */ - querySelector(selector: string, msg?: string): HTMLElement | Element; + get url(): URL; /** - * Run a querySelectorAll as an assert and also get the results - * - * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. - * @param {string} [msg] - @returns {Array} - * - * @example - * ```js - * const elements = await t.querySelectorAll('#dom-selector', '') - * ``` + * The origin for this `Request`. + * @type {string} */ - querySelectorAll(selector: string, msg?: string): Array; + get origin(): string; /** - * Retrieves the computed styles for a given element. - * - * @param {string|Element} selector - The CSS selector or the Element object for which to get the computed styles. - * @param {string} [msg] - An optional message to display when the operation is successful. Default message will be generated based on the type of selector. - * @returns {CSSStyleDeclaration} - The computed styles of the element. - * @throws {Error} - Throws an error if the element has no `ownerDocument` or if `ownerDocument.defaultView` is not available. - * - * @example - * ```js - * // Using CSS selector - * const style = getComputedStyle('.my-element', 'Custom success message'); - * ``` - * - * @example - * ```js - * // Using Element object - * const el = document.querySelector('.my-element'); - * const style = getComputedStyle(el); - * ``` + * The `Loader` for this `Request` object. + * @type {Loader?} */ - getComputedStyle(selector: string | Element, msg?: string): CSSStyleDeclaration; + get loader(): Loader; /** - * @param {boolean} pass - * @param {unknown} actual - * @param {unknown} expected - * @param {string} description - * @param {string} operator - * @returns {void} - * @ignore + * The `RequestStatus` for this `Request` + * @type {RequestStatus} */ - _assert(pass: boolean, actual: unknown, expected: unknown, description: string, operator: string): void; + get status(): RequestStatus; /** - * @returns {Promise<{ - * pass: number, - * fail: number - * }>} + * Loads the CommonJS source file, optionally checking the `Loader` cache + * first, unless ignored when `options.cache` is `false`. + * @param {RequestLoadOptions=} [options] + * @return {Response} */ - run(): Promise<{ - pass: number; - fail: number; - }>; + load(options?: RequestLoadOptions | undefined): Response; + /** + * Converts this `Request` to JSON. + * @ignore + * @return {{ + * url: string, + * status: object | undefined + * }} + */ + toJSON(includeStatus?: boolean): { + url: string; + status: object | undefined; + }; + #private; } /** - * @class + * A container for a synchronous CommonJS request response for a local resource + * or over the network. */ - export class TestRunner { + export class Response { /** - * @constructor - * @param {(lines: string) => void} [report] + * Creates a `Response` from JSON input + * @param {obejct} json + * @param {ResponseOptions=} [options] + * @return {Response} */ - constructor(report?: (lines: string) => void); + static from(json: obejct, options?: ResponseOptions | undefined): Response; /** - * @type {(lines: string) => void} - * @ignore + * `Response` class constructor. + * @param {Request|ResponseOptions} request + * @param {ResponseOptions=} [options] */ - report: (lines: string) => void; + constructor(request: Request | ResponseOptions, options?: ResponseOptions | undefined); /** - * @type {Test[]} - * @ignore + * The unique ID of this `Response`, which is the absolute + * URL of the request as a string. + * @type {string} */ - tests: Test[]; + get id(): string; /** - * @type {Test[]} - * @ignore + * The `Request` object associated with this `Response` object. + * @type {Request} */ - onlyTests: Test[]; + get request(): Request; /** - * @type {boolean} - * @ignore + * The response headers from the associated request. + * @type {Headers} */ - scheduled: boolean; + get headers(): Headers; + /** + * The `Loader` associated with this `Response` object. + * @type {Loader?} + */ + get loader(): Loader; /** + * The `Response` status code from the associated `Request` object. * @type {number} - * @ignore */ - _id: number; + get status(): number; /** - * @type {boolean} - * @ignore + * The `Response` string from the associated `Request` + * @type {string} */ - completed: boolean; + get text(): string; /** - * @type {boolean} - * @ignore + * The `Response` array buffer from the associated `Request` + * @type {ArrayBuffer?} */ - rethrowExceptions: boolean; + get buffer(): ArrayBuffer; /** + * `true` if the response is considered OK, otherwise `false`. * @type {boolean} - * @ignore */ - strict: boolean; + get ok(): boolean; /** - * @type {function | void} + * Converts this `Response` to JSON. * @ignore + * @return {{ + * id: string, + * text: string, + * status: number, + * buffer: number[] | null, + * headers: Array + * }} */ - _onFinishCallback: Function | void; + toJSON(): { + id: string; + text: string; + status: number; + buffer: number[] | null; + headers: Array; + }; + #private; + } + /** + * A container for loading CommonJS module sources + */ + export class Loader { /** - * @returns {string} + * A request class used by `Loader` objects. + * @type {typeof Request} */ - nextId(): string; + static Request: typeof Request; /** - * @type {number} + * A response class used by `Loader` objects. + * @type {typeof Request} */ - get length(): number; + static Response: typeof Request; /** - * @param {string} name - * @param {TestFn} fn - * @param {boolean} only - * @returns {void} + * Resolves a given module URL to an absolute URL with an optional `origin`. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} */ - add(name: string, fn: TestFn, only: boolean): void; + static resolve(url: URL | string, origin?: URL | string): string; /** - * @returns {Promise} + * Default extensions for a loader. + * @type {Set} */ - run(): Promise; + static defaultExtensions: Set; /** - * @param {(result: { total: number, success: number, fail: number }) => void} callback - * @returns {void} - */ + * `Loader` class constructor. + * @param {string|URL|LoaderOptions} origin + * @param {LoaderOptions=} [options] + */ + constructor(origin: string | URL | LoaderOptions, options?: LoaderOptions | undefined); + /** + * The internal caches for this `Loader` object. + * @type {{ response: Cache, status: Cache }} + */ + get cache(): { + response: Cache; + status: Cache; + }; + /** + * A set of supported `Loader` extensions. + * @type {Set} + */ + get extensions(): Set; + set origin(origin: string); + /** + * The origin of this `Loader` object. + * @type {string} + */ + get origin(): string; + /** + * Loads a CommonJS module source file at `url` with an optional `origin`, which + * defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {Response} + */ + load(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): Response; + /** + * Queries the status of a CommonJS module source file at `url` with an + * optional `origin`, which defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {RequestStatus} + */ + status(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): RequestStatus; + /** + * Resolves a given module URL to an absolute URL based on the loader origin. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} + */ + resolve(url: URL | string, origin?: URL | string): string; + #private; + } + export default Loader; + export type LoaderOptions = { + extensions?: string[] | Set; + origin?: URL | string; + statuses?: Cache; + cache?: { + response?: Cache; + status?: Cache; + }; + }; + export type RequestOptions = { + loader?: Loader; + origin?: URL | string; + }; + export type RequestStatusOptions = { + headers?: Headers | object | any[][]; + status?: number; + }; + export type RequestLoadOptions = { + headers?: Headers | object; + }; + export type ResponseOptions = { + request?: Request; + headers?: Headers; + status?: number; + buffer?: ArrayBuffer; + text?: string; + }; + import { Headers } from "socket:ipc"; + import { Cache } from "socket:commonjs/cache"; +} + +declare module "socket:commonjs/package" { + /** + * @typedef {{ + * manifest?: string, + * index?: string, + * description?: string, + * version?: string, + * license?: string, + * exports?: object, + * type?: 'commonjs' | 'module', + * info?: object, + * origin?: string, + * dependencies?: Dependencies | object | Map + * }} PackageOptions + */ + /** + * @typedef {import('./loader.js').RequestOptions & { + * type?: 'commonjs' | 'module' + * prefix?: string + * }} PackageLoadOptions + */ + /** + * {import('./loader.js').RequestOptions & { + * load?: boolean, + * type?: 'commonjs' | 'module', + * browser?: boolean, + * children?: string[] + * extensions?: string[] | Set + * }} PackageResolveOptions + */ + /** + * @typedef {{ + * organization: string | null, + * name: string, + * version: string | null, + * pathname: string, + * url: URL, + * isRelative: boolean, + * hasManifest: boolean + * }} ParsedPackageName + /** + * The default package index file such as 'index.js' + * @type {string} + */ + export const DEFAULT_PACKAGE_INDEX: string; + /** + * The default package manifest file name such as 'package.json' + * @type {string} + */ + export const DEFAULT_PACKAGE_MANIFEST_FILE_NAME: string; + /** + * The default package path prefix such as 'node_modules/' + * @type {string} + */ + export const DEFAULT_PACKAGE_PREFIX: string; + /** + * The default package version, when one is not provided + * @type {string} + */ + export const DEFAULT_PACKAGE_VERSION: string; + /** + * The default license for a package' + * @type {string} + */ + export const DEFAULT_LICENSE: string; + /** + * A container for a package name that includes a package organization identifier, + * its fully qualified name, or for relative package names, its pathname + */ + export class Name { + /** + * Parses a package name input resolving the actual module name, including an + * organization name given. If a path includes a manifest file + * ('package.json'), then the directory containing that file is considered a + * valid package and it will be included in the returned value. If a relative + * path is given, then the path is returned if it is a valid pathname. This + * function returns `null` for bad input. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {ParsedPackageName?} + */ + static parse(input: string | URL, options?: { + origin?: string | URL; + manifest?: string; + }): ParsedPackageName | null; + /** + * Returns `true` if the given `input` can be parsed by `Name.parse` or given + * as input to the `Name` class constructor. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {boolean} + */ + static canParse(input: string | URL, options?: { + origin?: string | URL; + manifest?: string; + }): boolean; + /** + * Creates a new `Name` from input. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {Name} + */ + static from(input: string | URL, options?: { + origin?: string | URL; + manifest?: string; + } | undefined): Name; + /** + * `Name` class constructor. + * @param {string|URL|NameOptions|Name} name + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @throws TypeError + */ + constructor(name: string | URL | NameOptions | Name, options?: { + origin?: string | URL; + manifest?: string; + } | undefined); + /** + * The id of this package name. + * @type {string} + */ + get id(): string; + /** + * The actual package name. + * @type {string} + */ + get name(): string; + /** + * The origin of the package, if available. + * @type {string?} + */ + get origin(): string; + /** + * The package version if available. + * @type {string} + */ + get version(): string; + /** + * The actual package pathname. + * @type {string} + */ + get pathname(): string; + /** + * The organization name. This value may be `null`. + * @type {string?} + */ + get organization(): string; + /** + * `true` if the package name was relative, otherwise `false`. + * @type {boolean} + */ + get isRelative(): boolean; + /** + * Converts this package name to a string. + * @ignore + * @return {string} + */ + toString(): string; + /** + * Converts this `Name` instance to JSON. + * @ignore + * @return {object} + */ + toJSON(): object; + #private; + } + /** + * A container for package dependencies that map a package name to a `Package` instance. + */ + export class Dependencies { + constructor(parent: any, options?: any); + get map(): Map; + get origin(): any; + add(name: any, info?: any): void; + get(name: any): any; + entries(): IterableIterator<[any, any]>; + keys(): IterableIterator; + values(): IterableIterator; + load(options?: any): void; + [Symbol.iterator](): IterableIterator<[any, any]>; + #private; + } + /** + * A container for CommonJS module metadata, often in a `package.json` file. + */ + export class Package { + /** + * A high level class for a package name. + * @type {typeof Name} + */ + static Name: typeof Name; + /** + * A high level container for package dependencies. + * @type {typeof Dependencies} + */ + static Dependencies: typeof Dependencies; + /** + * `Package` class constructor. + * @param {string|URL|NameOptions|Name} name + * @param {PackageOptions=} [options] + */ + constructor(name: string | URL | NameOptions | Name, options?: PackageOptions | undefined); + /** + * The unique ID of this `Package`, which is the absolute + * URL of the directory that contains its manifest file. + * @type {string} + */ + get id(): string; + /** + * The absolute URL to the package manifest file + * @type {string} + */ + get url(): string; + /** + * A loader for this package, if available. This value may be `null`. + * @type {Loader} + */ + get loader(): Loader; + /** + * The name of the package. + * @type {string} + */ + get name(): string; + /** + * The description of the package. + * @type {string} + */ + get description(): string; + /** + * The organization of the package. This value may be `null`. + * @type {string?} + */ + get organization(): string; + /** + * The license of the package. + * @type {string} + */ + get license(): string; + /** + * The version of the package. + * @type {string} + */ + get version(): string; + /** + * The origin for this package. + * @type {string} + */ + get origin(): string; + /** + * The exports mappings for the package + * @type {object} + */ + get exports(): any; + /** + * The package type. + * @type {'commonjs'|'module'} + */ + get type(): "module" | "commonjs"; + /** + * The raw package metadata object. + * @type {object?} + */ + get info(): any; + /** + * @type {Dependencies} + */ + get dependencies(): Dependencies; + /** + * An alias for `entry` + * @type {string?} + */ + get main(): string; + /** + * The entry to the package + * @type {string?} + */ + get entry(): string; + /** + * Load the package information at an optional `origin` with + * optional request `options`. + * @param {PackageLoadOptions=} [options] + * @throws SyntaxError + * @return {boolean} + */ + load(origin?: any, options?: PackageLoadOptions | undefined): boolean; + /** + * Resolve a file's `pathname` within the package. + * @param {string|URL} pathname + * @param {PackageResolveOptions=} [options] + * @return {string} + */ + resolve(pathname: string | URL, options?: PackageResolveOptions): string; + #private; + } + export default Package; + export type PackageOptions = { + manifest?: string; + index?: string; + description?: string; + version?: string; + license?: string; + exports?: object; + type?: 'commonjs' | 'module'; + info?: object; + origin?: string; + dependencies?: Dependencies | object | Map; + }; + export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { + type?: 'commonjs' | 'module'; + prefix?: string; + }; + /** + * /** + * The default package index file such as 'index.js' + */ + export type ParsedPackageName = { + organization: string | null; + name: string; + version: string | null; + pathname: string; + url: URL; + isRelative: boolean; + hasManifest: boolean; + }; + import { Loader } from "socket:commonjs/loader"; +} + +declare module "socket:stream-relay/proxy" { + export default PeerWorkerProxy; + /** + * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ + export class PeerWorkerProxy { + constructor(options: any, port: any, fn: any); + init(): Promise; + reconnect(): Promise; + disconnect(): Promise; + getInfo(): Promise; + getState(): Promise; + open(...args: any[]): Promise; + seal(...args: any[]): Promise; + sealUnsigned(...args: any[]): Promise; + openUnsigned(...args: any[]): Promise; + addEncryptionKey(...args: any[]): Promise; + send(...args: any[]): Promise; + sendUnpublished(...args: any[]): Promise; + cacheInsert(...args: any[]): Promise; + mcast(...args: any[]): Promise; + requestReflection(...args: any[]): Promise; + join(...args: any[]): Promise; + publish(...args: any[]): Promise; + sync(...args: any[]): Promise; + close(...args: any[]): Promise; + query(...args: any[]): Promise; + compileCachePredicate(src: any): Promise; + callWorkerThread(prop: any, data: any): Deferred; + callMainThread(prop: any, args: any): void; + resolveMainThread(seq: any, data: any): void; + #private; + } + import { Deferred } from "socket:async"; +} + +declare module "socket:stream-relay/api" { + export default api; + /** + * Initializes and returns the network bus. + * + * @async + * @function + * @param {object} options - Configuration options for the network bus. + * @param {object} events - A nodejs compatibe implementation of the events module. + * @param {object} dgram - A nodejs compatible implementation of the dgram module. + * @returns {Promise} - A promise that resolves to the initialized network bus. + */ + export function api(options: object, events: object, dgram: object): Promise; +} + +declare module "socket:network" { + export default network; + export function network(options: any): Promise; + import { Cache } from "socket:stream-relay/index"; + import { sha256 } from "socket:stream-relay/index"; + import { Encryption } from "socket:stream-relay/index"; + import { Packet } from "socket:stream-relay/index"; + import { NAT } from "socket:stream-relay/index"; + export { Cache, sha256, Encryption, Packet, NAT }; +} + +declare module "socket:string_decoder" { + export function StringDecoder(encoding: any): void; + export class StringDecoder { + constructor(encoding: any); + encoding: any; + text: typeof utf16Text | typeof base64Text; + end: typeof utf16End | typeof base64End | typeof simpleEnd; + fillLast: typeof utf8FillLast; + write: typeof simpleWrite; + lastNeed: number; + lastTotal: number; + lastChar: Uint8Array; + } + export default StringDecoder; + function utf16Text(buf: any, i: any): any; + class utf16Text { + constructor(buf: any, i: any); + lastNeed: number; + lastTotal: number; + } + function base64Text(buf: any, i: any): any; + class base64Text { + constructor(buf: any, i: any); + lastNeed: number; + lastTotal: number; + } + function utf16End(buf: any): any; + function base64End(buf: any): any; + function simpleEnd(buf: any): any; + function utf8FillLast(buf: any): any; + function simpleWrite(buf: any): any; +} + +declare module "socket:test/context" { + export default function _default(GLOBAL_TEST_RUNNER: any): void; +} + +declare module "socket:test/dom-helpers" { + /** + * Converts querySelector string to an HTMLElement or validates an existing HTMLElement. + * + * @export + * @param {string|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @returns {Element} The HTMLElement, Element, or Window that corresponds to the selector. + * @throws {Error} Throws an error if the `selector` is not a string that resolves to an HTMLElement or not an instance of HTMLElement, Element, or Window. + * + */ + export function toElement(selector: string | Element): Element; + /** + * Waits for an element to appear in the DOM and resolves the promise when it does. + * + * @export + * @param {Object} args - Configuration arguments. + * @param {string} [args.selector] - The CSS selector to look for. + * @param {boolean} [args.visible=true] - Whether the element should be visible. + * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. + * @param {() => HTMLElement | Element | null | undefined} [lambda] - An optional function that returns the element. Used if the `selector` is not provided. + * @returns {Promise} - A promise that resolves to the found element. + * + * @throws {Error} - Throws an error if neither `lambda` nor `selector` is provided. + * @throws {Error} - Throws an error if the element is not found within the timeout. + * + * @example + * ```js + * waitFor({ selector: '#my-element', visible: true, timeout: 5000 }) + * .then(el => console.log('Element found:', el)) + * .catch(err => console.log('Element not found:', err)); + * ``` + */ + export function waitFor(args: { + selector?: string; + visible?: boolean; + timeout?: number; + }, lambda?: () => HTMLElement | Element | null | undefined): Promise; + /** + * Waits for an element's text content to match a given string or regular expression. + * + * @export + * @param {Object} args - Configuration arguments. + * @param {Element} args.element - The root element from which to begin searching. + * @param {string} [args.text] - The text to search for within elements. + * @param {RegExp} [args.regex] - A regular expression to match against element text content. + * @param {boolean} [args.multipleTags=false] - Whether to look for text across multiple sibling elements. + * @param {number} [args.timeout=defaultTimeout] - Time in milliseconds to wait before rejecting the promise. + * @returns {Promise} - A promise that resolves to the found element or null. + * + * @example + * ```js + * waitForText({ element: document.body, text: 'Hello', timeout: 5000 }) + * .then(el => console.log('Element found:', el)) + * .catch(err => console.log('Element not found:', err)); + * ``` + */ + export function waitForText(args: { + element: Element; + text?: string; + regex?: RegExp; + multipleTags?: boolean; + timeout?: number; + }): Promise; + /** + * @export + * @param {Object} args - Arguments + * @param {string | Event} args.event - The event to dispatch. + * @param {HTMLElement | Element | window} [args.element=window] - The element to dispatch the event on. + * @returns {void} + * + * @throws {Error} Throws an error if the `event` is not a string that can be converted to a CustomEvent or not an instance of Event. + */ + export function event(args: { + event: string | Event; + element?: HTMLElement | Element | (Window & typeof globalThis); + }): void; + /** + * @export + * Copy pasted from https://raw.githubusercontent.com/testing-library/jest-dom/master/src/to-be-visible.js + * @param {Element | HTMLElement} element + * @param {Element | HTMLElement} [previousElement] + * @returns {boolean} + */ + export function isElementVisible(element: Element | HTMLElement, previousElement?: Element | HTMLElement): boolean; +} + +declare module "socket:test/index" { + /** + * @returns {number} - The default timeout for tests in milliseconds. + */ + export function getDefaultTestRunnerTimeout(): number; + /** + * @param {string} name + * @param {TestFn} [fn] + * @returns {void} + */ + export function only(name: string, fn?: TestFn): void; + /** + * @param {string} _name + * @param {TestFn} [_fn] + * @returns {void} + */ + export function skip(_name: string, _fn?: TestFn): void; + /** + * @param {boolean} strict + * @returns {void} + */ + export function setStrict(strict: boolean): void; + /** + * @typedef {{ + * (name: string, fn?: TestFn): void + * only(name: string, fn?: TestFn): void + * skip(name: string, fn?: TestFn): void + * }} testWithProperties + * @ignore + */ + /** + * @type {testWithProperties} + * @param {string} name + * @param {TestFn} [fn] + * @returns {void} + */ + export function test(name: string, fn?: TestFn): void; + export namespace test { + export { only }; + export { skip }; + } + /** + * @typedef {(t: Test) => (void | Promise)} TestFn + */ + /** + * @class + */ + export class Test { + /** + * @constructor + * @param {string} name + * @param {TestFn} fn + * @param {TestRunner} runner + */ + constructor(name: string, fn: TestFn, runner: TestRunner); + /** + * @type {string} + * @ignore + */ + name: string; + /** + * @type {null|number} + * @ignore + */ + _planned: null | number; + /** + * @type {null|number} + * @ignore + */ + _actual: null | number; + /** + * @type {TestFn} + * @ignore + */ + fn: TestFn; + /** + * @type {TestRunner} + * @ignore + */ + runner: TestRunner; + /** + * @type{{ pass: number, fail: number }} + * @ignore + */ + _result: { + pass: number; + fail: number; + }; + /** + * @type {boolean} + * @ignore + */ + done: boolean; + /** + * @type {boolean} + * @ignore + */ + strict: boolean; + /** + * @param {string} msg + * @returns {void} + */ + comment(msg: string): void; + /** + * Plan the number of assertions. + * + * @param {number} n + * @returns {void} + */ + plan(n: number): void; + /** + * @template T + * @param {T} actual + * @param {T} expected + * @param {string} [msg] + * @returns {void} + */ + deepEqual(actual: T, expected: T, msg?: string): void; + /** + * @template T + * @param {T} actual + * @param {T} expected + * @param {string} [msg] + * @returns {void} + */ + notDeepEqual(actual: T_1, expected: T_1, msg?: string): void; + /** + * @template T + * @param {T} actual + * @param {T} expected + * @param {string} [msg] + * @returns {void} + */ + equal(actual: T_2, expected: T_2, msg?: string): void; + /** + * @param {unknown} actual + * @param {unknown} expected + * @param {string} [msg] + * @returns {void} + */ + notEqual(actual: unknown, expected: unknown, msg?: string): void; + /** + * @param {string} [msg] + * @returns {void} + */ + fail(msg?: string): void; + /** + * @param {unknown} actual + * @param {string} [msg] + * @returns {void} + */ + ok(actual: unknown, msg?: string): void; + /** + * @param {string} [msg] + * @returns {void} + */ + pass(msg?: string): void; + /** + * @param {Error | null | undefined} err + * @param {string} [msg] + * @returns {void} + */ + ifError(err: Error | null | undefined, msg?: string): void; + /** + * @param {Function} fn + * @param {RegExp | any} [expected] + * @param {string} [message] + * @returns {void} + */ + throws(fn: Function, expected?: RegExp | any, message?: string): void; + /** + * Sleep for ms with an optional msg + * + * @param {number} ms + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.sleep(100) + * ``` + */ + sleep(ms: number, msg?: string): Promise; + /** + * Request animation frame with an optional msg. Falls back to a 0ms setTimeout when + * tests are run headlessly. + * + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.requestAnimationFrame() + * ``` + */ + requestAnimationFrame(msg?: string): Promise; + /** + * Dispatch the `click`` method on an element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.click('.class button', 'Click a button') + * ``` + */ + click(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Dispatch the click window.MouseEvent on an element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.eventClick('.class button', 'Click a button with an event') + * ``` + */ + eventClick(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Dispatch an event on the target. + * + * @param {string | Event} event - The event name or Event instance to dispatch. + * @param {string|HTMLElement|Element} target - A CSS selector string, or an instance of HTMLElement, or Element to dispatch the event on. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.dispatchEvent('my-event', '#my-div', 'Fire the my-event event') + * ``` + */ + dispatchEvent(event: string | Event, target: string | HTMLElement | Element, msg?: string): Promise; + /** + * Call the focus method on element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.focus('#my-div') + * ``` + */ + focus(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Call the blur method on element specified by selector. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.blur('#my-div') + * ``` + */ + blur(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Consecutively set the str value of the element specified by selector to simulate typing. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {string} str - The string to type into the :focus element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.typeValue('#my-div', 'Hello World', 'Type "Hello World" into #my-div') + * ``` + */ + type(selector: string | HTMLElement | Element, str: string, msg?: string): Promise; + /** + * appendChild an element el to a parent selector element. + * + * @param {string|HTMLElement|Element} parentSelector - A CSS selector string, or an instance of HTMLElement, or Element to appendChild on. + * @param {HTMLElement|Element} el - A element to append to the parent element. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * const myElement = createElement('div') + * await t.appendChild('#parent-selector', myElement, 'Append myElement into #parent-selector') + * ``` + */ + appendChild(parentSelector: string | HTMLElement | Element, el: HTMLElement | Element, msg?: string): Promise; + /** + * Remove an element from the DOM. + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to remove from the DOM. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.removeElement('#dom-selector', 'Remove #dom-selector') + * ``` + */ + removeElement(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Test if an element is visible + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.elementVisible('#dom-selector','Element is visible') + * ``` + */ + elementVisible(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Test if an element is invisible + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element to test visibility on. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.elementInvisible('#dom-selector','Element is invisible') + * ``` + */ + elementInvisible(selector: string | HTMLElement | Element, msg?: string): Promise; + /** + * Test if an element is invisible + * + * @param {string|(() => HTMLElement|Element|null|undefined)} querySelectorOrFn - A query string or a function that returns an element. + * @param {Object} [opts] + * @param {boolean} [opts.visible] - The element needs to be visible. + * @param {number} [opts.timeout] - The maximum amount of time to wait. + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.waitFor('#dom-selector', { visible: true },'#dom-selector is on the page and visible') + * ``` + */ + waitFor(querySelectorOrFn: string | (() => HTMLElement | Element | null | undefined), opts?: { + visible?: boolean; + timeout?: number; + }, msg?: string): Promise; + /** + * @typedef {Object} WaitForTextOpts + * @property {string} [text] - The text to wait for + * @property {number} [timeout] + * @property {Boolean} [multipleTags] + * @property {RegExp} [regex] The regex to wait for + */ + /** + * Test if an element is invisible + * + * @param {string|HTMLElement|Element} selector - A CSS selector string, or an instance of HTMLElement, or Element. + * @param {WaitForTextOpts | string | RegExp} [opts] + * @param {string} [msg] + * @returns {Promise} + * + * @example + * ```js + * await t.waitForText('#dom-selector', 'Text to wait for') + * ``` + * + * @example + * ```js + * await t.waitForText('#dom-selector', /hello/i) + * ``` + * + * @example + * ```js + * await t.waitForText('#dom-selector', { + * text: 'Text to wait for', + * multipleTags: true + * }) + * ``` + */ + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + /** + * - The text to wait for + */ + text?: string; + timeout?: number; + multipleTags?: boolean; + /** + * The regex to wait for + */ + regex?: RegExp; + }, msg?: string): Promise; + /** + * Run a querySelector as an assert and also get the results + * + * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. + * @param {string} [msg] + * @returns {HTMLElement | Element} + * + * @example + * ```js + * const element = await t.querySelector('#dom-selector') + * ``` + */ + querySelector(selector: string, msg?: string): HTMLElement | Element; + /** + * Run a querySelectorAll as an assert and also get the results + * + * @param {string} selector - A CSS selector string, or an instance of HTMLElement, or Element to select. + * @param {string} [msg] + @returns {Array} + * + * @example + * ```js + * const elements = await t.querySelectorAll('#dom-selector', '') + * ``` + */ + querySelectorAll(selector: string, msg?: string): Array; + /** + * Retrieves the computed styles for a given element. + * + * @param {string|Element} selector - The CSS selector or the Element object for which to get the computed styles. + * @param {string} [msg] - An optional message to display when the operation is successful. Default message will be generated based on the type of selector. + * @returns {CSSStyleDeclaration} - The computed styles of the element. + * @throws {Error} - Throws an error if the element has no `ownerDocument` or if `ownerDocument.defaultView` is not available. + * + * @example + * ```js + * // Using CSS selector + * const style = getComputedStyle('.my-element', 'Custom success message'); + * ``` + * + * @example + * ```js + * // Using Element object + * const el = document.querySelector('.my-element'); + * const style = getComputedStyle(el); + * ``` + */ + getComputedStyle(selector: string | Element, msg?: string): CSSStyleDeclaration; + /** + * @param {boolean} pass + * @param {unknown} actual + * @param {unknown} expected + * @param {string} description + * @param {string} operator + * @returns {void} + * @ignore + */ + _assert(pass: boolean, actual: unknown, expected: unknown, description: string, operator: string): void; + /** + * @returns {Promise<{ + * pass: number, + * fail: number + * }>} + */ + run(): Promise<{ + pass: number; + fail: number; + }>; + } + /** + * @class + */ + export class TestRunner { + /** + * @constructor + * @param {(lines: string) => void} [report] + */ + constructor(report?: (lines: string) => void); + /** + * @type {(lines: string) => void} + * @ignore + */ + report: (lines: string) => void; + /** + * @type {Test[]} + * @ignore + */ + tests: Test[]; + /** + * @type {Test[]} + * @ignore + */ + onlyTests: Test[]; + /** + * @type {boolean} + * @ignore + */ + scheduled: boolean; + /** + * @type {number} + * @ignore + */ + _id: number; + /** + * @type {boolean} + * @ignore + */ + completed: boolean; + /** + * @type {boolean} + * @ignore + */ + rethrowExceptions: boolean; + /** + * @type {boolean} + * @ignore + */ + strict: boolean; + /** + * @type {function | void} + * @ignore + */ + _onFinishCallback: Function | void; + /** + * @returns {string} + */ + nextId(): string; + /** + * @type {number} + */ + get length(): number; + /** + * @param {string} name + * @param {TestFn} fn + * @param {boolean} only + * @returns {void} + */ + add(name: string, fn: TestFn, only: boolean): void; + /** + * @returns {Promise} + */ + run(): Promise; + /** + * @param {(result: { total: number, success: number, fail: number }) => void} callback + * @returns {void} + */ onFinish(callback: (result: { total: number; success: number; @@ -12057,535 +12906,171 @@ declare module "socket:timers/scheduler" { export function postTask(callback: any, options?: any): Promise; namespace _default { export { postTask }; - export { setImmediate as yield }; - export { wait }; - } - export default _default; - import { setImmediate } from "socket:timers/promises"; -} - -declare module "socket:timers/index" { - export function setTimeout(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; - export function clearTimeout(timeout: any): void; - export function setInterval(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; - export function clearInterval(interval: any): void; - export function setImmediate(callback: any, ...args: any[]): import("socket:timers/timer").Timer; - export function clearImmediate(immediate: any): void; - /** - * Pause async execution for `timeout` milliseconds. - * @param {number} timeout - * @return {Promise} - */ - export function sleep(timeout: number): Promise; - export namespace sleep { - /** - * Pause sync execution for `timeout` milliseconds. - * @param {number} timeout - */ - function sync(timeout: number): void; - } - export { platform }; - namespace _default { - export { platform }; - export { promises }; - export { scheduler }; - export { setTimeout }; - export { clearTimeout }; - export { setInterval }; - export { clearInterval }; - export { setImmediate }; - export { clearImmediate }; - } - export default _default; - import platform from "socket:timers/platform"; - import promises from "socket:timers/promises"; - import scheduler from "socket:timers/scheduler"; -} - -declare module "socket:timers" { - export * from "socket:timers/index"; - export default exports; - import * as exports from "socket:timers/index"; -} - -declare module "socket:commonjs/builtins" { - /** - * Predicate to determine if a given module name is a builtin module. - * @param {string} name - * @param {{ builtins?: object }} - * @return {boolean} - */ - export function isBuiltin(name: string, options?: any): boolean; - /** - * Gets a builtin module by name. - * @param {string} name - * @param {{ builtins?: object }} - * @return {any} - */ - export function getBuiltin(name: string, options?: any): any; - /** - * A mapping of builtin modules - * @type {object} - */ - export const builtins: object; - /** - * Known runtime specific builtin modules. - * @type {string[]} - */ - export const runtimeModules: string[]; - export default builtins; -} - -declare module "socket:commonjs/loader" { - /** - * @typedef {{ - * extensions?: string[] | Set - * origin?: URL | string, - * statuses?: Map - * cache?: Map - * }} LoaderOptions - */ - /** - * @typedef {{ - * loader?: Loader, - * origin?: URL | string - * }} RequestOptions - */ - /** - * @typedef {{ - * headers?: Headers | object - * }} RequestLoadOptions - */ - /** - * @typedef {{ - * request?: Request, - * headers?: Headers, - * status?: number, - * text?: string - * }} ResponseOptions - */ - /** - * A container for the status of a CommonJS resource. A `RequestStatus` object - * represents meta data for a `Request` that comes from a preflight - * HTTP HEAD request. - */ - export class RequestStatus { - /** - * `RequestStatus` class constructor. - * @param {Request} request - */ - constructor(request: Request); - /** - * The unique ID of this `RequestStatus`, which is the absolute URL as a string. - * @type {string} - */ - get id(): string; - /** - * The origin for this `RequestStatus` object. - * @type {string} - */ - get origin(): string; - /** - * A HTTP status code for this `RequestStatus` object. - * @type {number|undefined} - */ - get status(): number; - /** - * An alias for `status`. - * @type {number|undefined} - */ - get value(): number; - /** - * @ignore - */ - get valueOf(): number; - /** - * The HTTP headers for this `RequestStatus` object. - * @type {Headers} - */ - get headers(): Headers; - /** - * The resource location for this `RequestStatus` object. This value is - * determined from the 'Content-Location' header, if available, otherwise - * it is derived from the request URL pathname (including the query string). - * @type {string} - */ - get location(): string; - /** - * `true` if the response status is considered OK, otherwise `false`. - * @type {boolean} - */ - get ok(): boolean; - /** - * Loads the internal state for this `RequestStatus` object. - * @param {RequestLoadOptions|boolean} [options] - * @return {RequestStatus} - */ - load(options?: RequestLoadOptions | boolean): RequestStatus; - #private; - } - /** - * A container for a synchronous CommonJS request to local resource or - * over the network. - */ - export class Request { - /** - * `Request` class constructor. - * @param {URL|string} url - * @param {URL|string=} [origin] - * @param {RequestOptions=} [options] - */ - constructor(url: URL | string, origin?: (URL | string) | undefined, options?: RequestOptions | undefined); - /** - * The unique ID of this `Request`, which is the absolute URL as a string. - * @type {string} - */ - get id(): string; - /** - * The absolute `URL` of this `Request` object. - * @type {URL} - */ - get url(): URL; - /** - * The origin for this `Request`. - * @type {string} - */ - get origin(): string; - /** - * The `Loader` for this `Request` object. - * @type {Loader?} - */ - get loader(): Loader; - /** - * The `RequestStatus` for this `Request` - * @type {RequestStatus} - */ - get status(): RequestStatus; - /** - * Loads the CommonJS source file, optionally checking the `Loader` cache - * first, unless ignored when `options.cache` is `false`. - * @param {RequestLoadOptions=} [options] - * @return {Response} - */ - load(options?: RequestLoadOptions | undefined): Response; - #private; + export { setImmediate as yield }; + export { wait }; } + export default _default; + import { setImmediate } from "socket:timers/promises"; +} + +declare module "socket:timers/index" { + export function setTimeout(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; + export function clearTimeout(timeout: any): void; + export function setInterval(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; + export function clearInterval(interval: any): void; + export function setImmediate(callback: any, ...args: any[]): import("socket:timers/timer").Timer; + export function clearImmediate(immediate: any): void; /** - * A container for a synchronous CommonJS request response for a local resource - * or over the network. + * Pause async execution for `timeout` milliseconds. + * @param {number} timeout + * @return {Promise} */ - export class Response { - /** - * `Response` class constructor. - * @param {Request|ResponseOptions} request - * @param {ResponseOptions=} [options] - */ - constructor(request: Request | ResponseOptions, options?: ResponseOptions | undefined); - /** - * The unique ID of this `Response`, which is the absolute - * URL of the request as a string. - * @type {string} - */ - get id(): string; - /** - * The `Request` object associated with this `Response` object. - * @type {Request} - */ - get request(): Request; - /** - * The `Loader` associated with this `Response` object. - * @type {Loader?} - */ - get loader(): Loader; - /** - * The `Response` status code from the associated `Request` object. - * @type {number} - */ - get status(): number; - /** - * The `Response` string from the associated `Request` - * @type {string} - */ - get text(): string; + export function sleep(timeout: number): Promise; + export namespace sleep { /** - * `true` if the response is considered OK, otherwise `false`. - * @type {boolean} + * Pause sync execution for `timeout` milliseconds. + * @param {number} timeout */ - get ok(): boolean; - #private; + function sync(timeout: number): void; + } + export { platform }; + namespace _default { + export { platform }; + export { promises }; + export { scheduler }; + export { setTimeout }; + export { clearTimeout }; + export { setInterval }; + export { clearInterval }; + export { setImmediate }; + export { clearImmediate }; } + export default _default; + import platform from "socket:timers/platform"; + import promises from "socket:timers/promises"; + import scheduler from "socket:timers/scheduler"; +} + +declare module "socket:timers" { + export * from "socket:timers/index"; + export default exports; + import * as exports from "socket:timers/index"; +} + +declare module "socket:commonjs/builtins" { /** - * A container for loading CommonJS module sources + * Defines a builtin module by name making a shallow copy of the + * module exports. + * @param {string} + * @param {object} exports */ - export class Loader { - /** - * A request class used by `Loader` objects. - * @type {typeof Request} - */ - static Request: typeof Request; - /** - * A response class used by `Loader` objects. - * @type {typeof Request} - */ - static Response: typeof Request; - /** - * Resolves a given module URL to an absolute URL with an optional `origin`. - * @param {URL|string} url - * @param {URL|string} [origin] - * @return {string} - */ - static resolve(url: URL | string, origin?: URL | string): string; - /** - * Default extensions for a loader. - * @type {Set} - */ - static defaultExtensions: Set; - /** - * `Loader` class constructor. - * @param {string|URL|LoaderOptions} origin - * @param {LoaderOptions=} [options] - */ - constructor(origin: string | URL | LoaderOptions, options?: LoaderOptions | undefined); - /** - * The internal cache for this `Loader` object. - * @type {Map} - */ - get cache(): Map; - /** - * The internal statuses for this `Loader` object. - * @type {Map} - */ - get statuses(): Map; - /** - * A set of supported `Loader` extensions. - * @type {Set} - */ - get extensions(): Set; - set origin(origin: string); - /** - * The origin of this `Loader` object. - * @type {string} - */ - get origin(): string; - /** - * Loads a CommonJS module source file at `url` with an optional `origin`, which - * defaults to the application origin. - * @param {URL|string} url - * @param {URL|string|object} [origin] - * @param {RequestOptions=} [options] - * @return {Response} - */ - load(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): Response; - /** - * Queries the status of a CommonJS module source file at `url` with an - * optional `origin`, which defaults to the application origin. - * @param {URL|string} url - * @param {URL|string|object} [origin] - * @param {RequestOptions=} [options] - * @return {RequestStatus} - */ - status(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): RequestStatus; - /** - * Resolves a given module URL to an absolute URL based on the loader origin. - * @param {URL|string} url - * @param {URL|string} [origin] - * @return {string} - */ - resolve(url: URL | string, origin?: URL | string): string; - #private; - } - export default Loader; - export type LoaderOptions = { - extensions?: string[] | Set; - origin?: URL | string; - statuses?: Map; - cache?: Map; - }; - export type RequestOptions = { - loader?: Loader; - origin?: URL | string; - }; - export type RequestLoadOptions = { - headers?: Headers | object; - }; - export type ResponseOptions = { - request?: Request; - headers?: Headers; - status?: number; - text?: string; - }; - import { Headers } from "socket:ipc"; + export function define(name: any, exports: object): void; + /** + * Predicate to determine if a given module name is a builtin module. + * @param {string} name + * @param {{ builtins?: object }} + * @return {boolean} + */ + export function isBuiltin(name: string, options?: any): boolean; + /** + * Gets a builtin module by name. + * @param {string} name + * @param {{ builtins?: object }} + * @return {any} + */ + export function getBuiltin(name: string, options?: any): any; + /** + * A mapping of builtin modules + * @type {object} + */ + export const builtins: object; + /** + * Known runtime specific builtin modules. + * @type {string[]} + */ + export const runtimeModules: string[]; + export default builtins; } -declare module "socket:commonjs/package" { +declare module "socket:commonjs/require" { /** - * @typedef {{ - * prefix?: string, - * manifest?: string, - * index?: string, - * description?: string, - * version?: string, - * license?: string, - * exports?: object, - * type?: 'commonjs' | 'module', - * info?: object - * }} PackageOptions + * Factory for creating a `require()` function based on a module context. + * @param {CreateRequireOptions} options + * @return {RequireFunction} */ + export function createRequire(options: CreateRequireOptions): RequireFunction; /** - * @typedef {import('./loader.js').RequestOptions & { - * type?: 'commonjs' | 'module' - * }} PackageLoadOptions + * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver */ /** - * {import('./loader.js').RequestOptions & { - * load?: boolean, - * type?: 'commonjs' | 'module', - * browser?: boolean, - * children?: string[] - * extensions?: string[] | Set - * }} PackageResolveOptions + * @typedef {{ + * module: import('./module.js').Module, + * prefix?: string, + * request?: import('./loader.js').RequestOptions, + * builtins?: object + * }} CreateRequireOptions */ /** - * The default package index file such as 'index.js' - * @type {string} + * @typedef {function(string): any} RequireFunction */ - export const DEFAULT_PACKAGE_INDEX: string; /** - * The default package manifest file name such as 'package.json' - * @type {string} + * @typedef {import('./package.js').PackageOptions} PackageOptions */ - export const DEFAULT_PACKAGE_MANIFEST_FILE_NAME: string; /** - * The default package path prefix such as 'node_modules/' - * @type {string} + * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions */ - export const DEFAULT_PACKAGE_PREFIX: string; /** - * The default package version, when one is not provided - * @type {string} + * @typedef {PackageResolveOptions & PackageOptions} ResolveOptions + */ + /** + * @typedef {ResolveOptions & { + * resolvers?: RequireResolver[], + * importmap?: import('./module.js').ImportMap, + * }} RequireOptions */ - export const DEFAULT_PACKAGE_VERSION: string; /** - * The default license for a package' - * @type {string} + * An array of global require paths, relative to the origin. + * @type {string[]} */ - export const DEFAULT_LICENSE: string; + export const globalPaths: string[]; /** - * A container for CommonJS module metadata, often in a `package.json` file. + * An object attached to a `require()` function that contains metadata + * about the current module context. */ - export class Package { + export class Meta { /** - * @param {string} input - * @param {PackageOptions&PackageLoadOptions} [options] - * @return {Package?} - */ - static find(input: string, options?: PackageOptions & PackageLoadOptions): Package | null; - /** - * `Package` class constructor. - * @param {string} name - * @param {PackageOptions} [options] + * `Meta` class constructor. + * @param {import('./module.js').Module} module */ - constructor(name: string, options?: PackageOptions); + constructor(module: import('./module.js').Module); /** - * The unique ID of this `Package`, which is the absolute - * URL of the directory that contains its manifest file. + * The referrer (parent) of this module. * @type {string} */ - get id(): string; + get referrer(): string; /** - * The absolute URL to the package manifest file + * The referrer (parent) of this module. * @type {string} */ get url(): string; - /** - * The package module path prefix. - * @type {string} - */ - get prefix(): string; - /** - * A loader for this package, if available. This value may be `null`. - * @type {Loader} - */ - get loader(): Loader; - /** - * The name of the package. - * @type {string} - */ - get name(): string; - /** - * The description of the package. - * @type {string} - */ - get description(): string; - /** - * The license of the package. - * @type {string} - */ - get license(): string; - /** - * The version of the package. - * @type {string} - */ - get version(): string; - /** - * The origin for this package. - * @type {string} - */ - get origin(): string; - /** - * The exports mappings for the package - * @type {object} - */ - get exports(): any; - /** - * The package type. - * @type {'commonjs'|'module'} - */ - get type(): "module" | "commonjs"; - /** - * The raw package metadata object. - * @type {object?} - */ - get info(): any; - /** - * The entry to the package - * @type {string?} - */ - get entry(): string; - /** - * Load the package information at an optional `origin` with - * optional request `options`. - * @param {string|PackageLoadOptions=} [origin] - * @param {PackageLoadOptions=} [options] - * @throws SyntaxError - * @return {boolean} - */ - load(origin?: (string | PackageLoadOptions) | undefined, options?: PackageLoadOptions | undefined): boolean; - /** - * Resolve a file's `pathname` within the package. - * @param {string|URL} pathname - * @param {PackageResolveOptions=} [options] - * @return {string} - */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } - export default Package; - export type PackageOptions = { + export default createRequire; + export type RequireResolver = (arg0: string, arg1: import("socket:commonjs/module").Module, arg2: (arg0: string) => any) => any; + export type CreateRequireOptions = { + module: import("socket:commonjs/module").Module; prefix?: string; - manifest?: string; - index?: string; - description?: string; - version?: string; - license?: string; - exports?: object; - type?: 'commonjs' | 'module'; - info?: object; + request?: import("socket:commonjs/loader").RequestOptions; + builtins?: object; }; - export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + export type RequireFunction = (arg0: string) => any; + export type PackageOptions = import("socket:commonjs/package").PackageOptions; + export type PackageResolveOptions = import("socket:commonjs/package").PackageResolveOptions; + export type ResolveOptions = PackageResolveOptions & PackageOptions; + export type RequireOptions = ResolveOptions & { + resolvers?: RequireResolver[]; + importmap?: import("socket:commonjs/module").ImportMap; }; - import { Loader } from "socket:commonjs/loader"; } declare module "socket:commonjs/module" { @@ -12598,7 +13083,7 @@ declare module "socket:commonjs/module" { * @param {string} __filename * @param {string} __dirname */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string): Promise; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: any): void; export function createRequire(url: any, options?: any): any; /** * @typedef {import('./require.js').RequireResolver[]} ModuleResolver @@ -12621,11 +13106,17 @@ declare module "socket:commonjs/module" { * resolvers?: ModuleResolver[], * importmap?: ImportMap, * loader?: Loader | object, + * loaders?: object, * package?: Package | PackageOptions * parent?: Module, * state?: State * }} ModuleOptions */ + /** + * @typedef {{ + * extensions?: object + * }} ModuleLoadOptions + */ export const builtinModules: any; /** * CommonJS module scope source wrapper. @@ -12669,15 +13160,70 @@ declare module "socket:commonjs/module" { * The module scope for a loaded module. * This is a special object that is seal, frozen, and only exposes an * accessor the 'exports' field. + * @ignore */ export class Scope { + /** + * `Scope` class constructor. + * @param {Module} module + */ + constructor(module: Module); + get id(): any; + get filename(): any; + get loaded(): any; + get children(): any; set exports(exports: any); get exports(): any; toJSON(): { + id: any; + filename: any; + children: any; exports: any; }; #private; } + /** + * An abstract base class for loading a module. + */ + export class ModuleLoader { + /** + * Creates a `ModuleLoader` instance from the `module` currently being loaded. + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {ModuleLoader} + */ + static from(module: Module, options?: ModuleLoadOptions | undefined): ModuleLoader; + /** + * Creates a new `ModuleLoader` instance from the `module` currently + * being loaded with the `source` string to parse and load with optional + * `ModuleLoadOptions` options. + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + static load(module: Module, options?: ModuleLoadOptions | undefined): boolean; + /** + * @param {Module} module + * @param {ModuleLoadOptions=} [options] + * @return {boolean} + */ + load(module: Module, options?: ModuleLoadOptions | undefined): boolean; + } + /** + * A JavaScript module loader + */ + export class JavaScriptModuleLoader extends ModuleLoader { + } + /** + * A JSON module loader. + */ + export class JSONModuleLoader extends ModuleLoader { + } + /** + * A WASM module loader + */ + export class WASMModuleLoader extends ModuleLoader { + } /** * A container for a loaded CommonJS module. All errors bubble * to the "main" module and global object (if possible). @@ -12729,6 +13275,11 @@ declare module "socket:commonjs/module" { * @type {string[]} */ static globalPaths: string[]; + /** + * Globabl module loaders + * @type {object} + */ + static loaders: object; /** * The main entry module, lazily created. * @type {Module} @@ -12834,6 +13385,16 @@ declare module "socket:commonjs/module" { * @type {Loader} */ get loader(): Loader; + /** + * The filename of the module. + * @type {string} + */ + get filename(): string; + /** + * Known source loaders for this module keyed by file extension. + * @type {object} + */ + get loaders(): any; /** * Factory for creating a `require()` function based on a module context. * @param {CreateRequireOptions=} [options] @@ -12848,10 +13409,23 @@ declare module "socket:commonjs/module" { */ createModule(url: string | URL | Module, options?: ModuleOptions | undefined): any; /** - * @param {object=} [options] + * Requires a module at for a given `input` which can be a relative file, + * named module, or an absolute URL within the context of this odule. + * @param {string|URL} input + * @param {RequireOptions=} [options] + * @throws ModuleNotFoundError + * @throws ReferenceError + * @throws SyntaxError + * @throws TypeError + * @return {any} + */ + require(url: any, options?: RequireOptions): any; + /** + * Loads the module + * @param {ModuleLoadOptions=} [options] * @return {boolean} */ - load(options?: object | undefined): boolean; + load(options?: ModuleLoadOptions | undefined): boolean; resolve(input: any): string; /** * @ignore @@ -12872,82 +13446,27 @@ declare module "socket:commonjs/module" { resolvers?: import("socket:commonjs/require").RequireResolver[][]; importmap?: ImportMap; loader?: Loader | object; + loaders?: object; package?: Package | PackageOptions; parent?: Module; state?: State; }; + export type ModuleLoadOptions = { + extensions?: object; + }; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; import builtins from "socket:commonjs/builtins"; } -declare module "socket:commonjs/require" { - /** - * Factory for creating a `require()` function based on a module context. - * @param {CreateRequireOptions} options - * @return {RequireFunction} - */ - export function createRequire(options: CreateRequireOptions): RequireFunction; - /** - * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver - */ - /** - * @typedef {{ - * module: import('./module.js').Module, - * prefix?: string, - * request?: import('./loader.js').RequestOptions, - * builtins?: object - * }} CreateRequireOptions - */ - /** - * @typedef {function(string): any} RequireFunction - */ - /** - * @typedef {import('./package.js').PackageOptions} PackageOptions - */ - /** - * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions - */ - /** - * @typedef {PackageResolveOptions & PackageOptions} ResolveOptions - */ - /** - * @typedef {ResolveOptions & { - * resolvers?: RequireResolver[], - * importmap?: import('./module.js').ImportMap, - * }} RequireOptions - */ - /** - * An array of global require paths, relative to the origin. - * @type {string[]} - */ - export const globalPaths: string[]; - export default createRequire; - export type RequireResolver = (arg0: string, arg1: import("socket:commonjs/module").Module, arg2: (arg0: string) => any) => any; - export type CreateRequireOptions = { - module: import("socket:commonjs/module").Module; - prefix?: string; - request?: import("socket:commonjs/loader").RequestOptions; - builtins?: object; - }; - export type RequireFunction = (arg0: string) => any; - export type PackageOptions = import("socket:commonjs/package").PackageOptions; - export type PackageResolveOptions = import("socket:commonjs/package").PackageResolveOptions; - export type ResolveOptions = PackageResolveOptions & PackageOptions; - export type RequireOptions = ResolveOptions & { - resolvers?: RequireResolver[]; - importmap?: import("socket:commonjs/module").ImportMap; - }; -} - declare module "socket:module" { export const builtinModules: any; export default Module; + import { createRequire } from "socket:commonjs/module"; import { Module } from "socket:commonjs/module"; import builtins from "socket:commonjs/builtins"; import { isBuiltin } from "socket:commonjs/builtins"; - import { createRequire } from "socket:commonjs/require"; - export { Module, builtins, isBuiltin, createRequire }; + export { createRequire, Module, builtins, isBuiltin }; } declare module "socket:node-esm-loader" { @@ -13447,6 +13966,59 @@ declare module "socket:child_process/worker" { export {}; } +declare module "socket:internal/callsite" { + export class CallSite { + static PromiseElementIndexSymbol: symbol; + static PromiseAllSymbol: symbol; + static PromiseAnySymbol: symbol; + constructor(error: any); + get error(): any; + getThis(): void; + getTypeName(): void; + getFunction(): void; + getFunctionName(): any; + getFileName(): string; + getLineNumber(): any; + getColumnNumber(): any; + getEvalOrigin(): void; + isTopLevel(): void; + isEval(): void; + isNative(): boolean; + isConstructor(): void; + isAsync(): boolean; + isPromiseAll(): any; + #private; + } + export default CallSite; +} + +declare module "socket:internal/error" { + export function Error(message: any, ...args: any[]): any; + export namespace Error { + let stackTraceLimit: number; + /** + * @ignore + */ + function captureStackTrace(err: any, ErrorConstructor: any): void; + } + export function Error(message: any, ...args: any[]): any; + export namespace Error { } + export function Error(message: any, ...args: any[]): any; + export namespace Error { } + export function Error(message: any, ...args: any[]): any; + export namespace Error { } + export function Error(message: any, ...args: any[]): any; + export namespace Error { } + export function Error(message: any, ...args: any[]): any; + export namespace Error { } + export function Error(message: any, ...args: any[]): any; + export namespace Error { } + namespace _default { + export { Error }; + } + export default _default; +} + declare module "socket:internal/geolocation" { /** * Get the current position of the device. @@ -13487,6 +14059,32 @@ declare module "socket:internal/geolocation" { export default _default; } +declare module "socket:internal/post-message" { + const _default: any; + export default _default; +} + +declare module "socket:internal/promise" { + export const NativePromise: PromiseConstructor; + export namespace NativePromisePrototype { + export let then: (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise; + let _catch: (onrejected?: (reason: any) => TResult | PromiseLike) => Promise; + export { _catch as catch }; + let _finally: (onfinally?: () => void) => Promise; + export { _finally as finally }; + } + export const NativePromiseAll: any; + export const NativePromiseAny: any; + export default Promise; + var Promise: PromiseConstructor; + interface Promise { + then(onfulfilled?: (value: T) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike): Promise; + catch(onrejected?: (reason: any) => TResult | PromiseLike): Promise; + finally(onfinally?: () => void): Promise; + readonly [Symbol.toStringTag]: string; + } +} + declare module "socket:internal/streams" { export class ReadableStream extends globalThis.ReadableStream { constructor(options: any); @@ -13701,18 +14299,6 @@ declare module "socket:internal/pickers" { import { FileSystemHandle } from "socket:fs/web"; } -declare module "socket:internal/promise" { - export namespace NativePromisePrototype { - export let then: (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise; - let _catch: (onrejected?: (reason: any) => TResult | PromiseLike) => Promise; - export { _catch as catch }; - let _finally: (onfinally?: () => void) => Promise; - export { _finally as finally }; - } - const Promise: PromiseConstructor; - export default Promise; -} - declare module "socket:internal/primitives" { export function init(): { natives: {}; From d6f345288aba2c6cccdfef225dd0bfb6feea92af Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:06:59 -0400 Subject: [PATCH 0450/1178] fix(api/child_process.js): fix missing 'reject()' --- api/child_process.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/child_process.js b/api/child_process.js index d4f18b91ac..853cdc1f7a 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -555,6 +555,7 @@ export function exec (command, options, callback) { hasError = true stdout.splice(0, stdout.length) stderr.splice(0, stderr.length) + reject(err) }) child.once('close', () => { From 77d3ff2e100bbfdbae3b5a4a43d980c10b50b68d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:07:19 -0400 Subject: [PATCH 0451/1178] refactor(api/commonjs): clean up and improve exports --- api/commonjs/builtins.js | 14 ++++++++----- api/commonjs/cache.js | 1 - api/commonjs/module.js | 43 +++++++++++++++++++++++++++++++--------- api/commonjs/package.js | 9 +++------ api/commonjs/require.js | 29 ++++++++++++++++++++++++--- 5 files changed, 72 insertions(+), 24 deletions(-) diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index 5ffd5a960c..0c7020bcb3 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -62,8 +62,12 @@ export const builtins = {} * @param {string} * @param {object} exports */ -export function define (name, exports) { - builtins[name] = { ...exports } +export function define (name, exports, copy = true) { + if (copy) { + builtins[name] = { ...exports } + } else { + builtins[name] = exports + } } // eslint-disable-next-line @@ -83,8 +87,8 @@ define('async_hooks', { }) define('application', application) -define('assert', assert) -define('buffer', buffer) +define('assert', assert, false) +define('buffer', buffer, false) define('console', console) define('constants', constants) // eslint-disable-next-line @@ -109,7 +113,7 @@ define('network', network) define('os', os) define('path', path) define('perf_hooks', { performance: globalThis.performance }) -define('process', process) +define('process', process, false) define('querystring', querystring) define('stream', stream) define('stream/web', stream.web) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index 3e108d16aa..5857e3f265 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -67,7 +67,6 @@ export class Cache { if (!this.has(...entry)) { const [key, value] = entry if (value?.__type__) { - console.log(key) this.#data.set(key, this.#types.get(value.__type__).from(value, { loader: this.#loader })) diff --git a/api/commonjs/module.js b/api/commonjs/module.js index 36dd5a8bfb..9a5333cc0c 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -57,6 +57,8 @@ export const builtinModules = builtins * @param {Module} module * @param {string} __filename * @param {string} __dirname + * @param {typeof process} process + * @param {object} global */ export function CommonJSModuleScope ( exports, @@ -248,8 +250,7 @@ export class JavaScriptModuleLoader extends ModuleLoader { */ load (module, options = null) { const response = module.loader.load(module.id, options) - // eslint-disable-next-line - const compiled = new Function(`return ${Module.wrap(response.text)}`)() + const compiled = Module.compile(response.text, { url: response.id }) const __filename = module.id const __dirname = path.dirname(__filename) @@ -384,20 +385,20 @@ export class Module extends EventTarget { * @type {object} */ static loaders = Object.assign(Object.create(null), { - '.js' (source, module, options = null) { - return JavaScriptModuleLoader.load(source, module, options) + '.js' (module, options = null) { + return JavaScriptModuleLoader.load(module, options) }, - '.cjs' (source, module, options = null) { - return JavaScriptModuleLoader.load(source, module, options) + '.cjs' (module, options = null) { + return JavaScriptModuleLoader.load(module, options) }, - '.json' (source, module, options = null) { - return JSONModuleLoader.load(source, module, options) + '.json' (module, options = null) { + return JSONModuleLoader.load(module, options) }, '.wasm' (source, module, options = null) { - return WASMModuleLoader.load(source, module, options) + return WASMModuleLoader.load(module, options) } }) @@ -462,6 +463,30 @@ export class Module extends EventTarget { return [head, body, tail].join('\n') } + /** + * Compiles given JavaScript module source. + * @param {string} source + * @param {{ url?: URL | string }=} [options] + * @return {function( + * object, + * function(string): any, + * Module, + * string, + * string, + * typeof process, + * object + * ): any} + */ + static compile (source, options = null) { + // eslint-disable-next-line + const compiled = new Function( + `return ${Module.wrap(source)}\n` + + `// # sourceURL=${options?.url ?? ''}` + )() + + return compiled + } + /** * Creates a `Module` from source URL and optionally a parent module. * @param {string|URL|Module} url diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 2658372834..590c4c4495 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -903,7 +903,7 @@ export class Package { } } - const extensions = extname !== '' + const extensions = extname !== '' && this.loader.extensions.has(extname) ? new Set([extname]) : new Set(Array .from(options?.extensions ?? []) @@ -970,11 +970,8 @@ export class Package { } } - if (!extname) { - let response = null - - response = this.loader.load(pathname + extension, origin, options) - + if (!extname || !this.loader.extensions.has(extname)) { + let response = this.loader.load(pathname + extension, origin, options) if (response.ok) { return interpolateBrowserResolution(response.id) } diff --git a/api/commonjs/require.js b/api/commonjs/require.js index b2bfa13f2b..c9af887097 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -107,6 +107,29 @@ export function createRequire (options) { main }) + /** + * Gets an ESM default export, if requested and applicable. + * @ignore + * @param {object} exports + */ + function getDefaultExports (exports) { + if (options?.default !== true) { + return exports + } + + if (exports && typeof exports === 'object') { + if ( + Object.keys(exports).length === 2 && + exports.__esModule === true && + 'default' in exports + ) { + return exports.default + } + } + + return exports + } + /** * @param {string} input * @param {ResolveOptions & RequireOptions=} [options @@ -156,7 +179,7 @@ export function createRequire (options) { } if (cache[input]) { - return cache[input].exports + return getDefaultExports(cache[input].exports) } const resolved = resolve(input, { @@ -165,7 +188,7 @@ export function createRequire (options) { }) if (cache[resolved]) { - return cache[resolved].exports + return getDefaultExports(cache[resolved].exports) } const child = module.createModule(resolved, { @@ -182,7 +205,7 @@ export function createRequire (options) { cache[input] = child if (child.load(options)) { - return child.exports + return getDefaultExports(child.exports) } throw new ModuleNotFoundError( From a82ebac9ea8bcecff770cb14e38ee32ab74dcd02 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:08:03 -0400 Subject: [PATCH 0452/1178] refactor(api/console.js): improve output chunking on macos/ios --- api/console.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/api/console.js b/api/console.js index 32f99d50e3..733ab4e979 100644 --- a/api/console.js +++ b/api/console.js @@ -170,14 +170,26 @@ export class Console { if (/ios|darwin/i.test(os.platform())) { const parts = value.split('\n') + const pending = [] for (const part of parts) { - try { - value = encodeURIComponent(part) - const uri = `ipc://${destination}?value=${value}&${extra}&resolve=false` - this.postMessage?.(uri) - } catch (err) { - this.console?.warn?.(`Failed to write to ${destination}: ${err.message}`) - return + if (part.length > 256) { + for (let i = 0; i < part.length; i += 256) { + pending.push(part.slice(i, i + 256)) + } + } else { + pending.push(part) + } + + while (pending.length) { + const output = pending.shift() + try { + const value = encodeURIComponent(output) + const uri = `ipc://${destination}?value=${value}&${extra}&resolve=false` + this.postMessage?.(uri) + } catch (err) { + this.console?.warn?.(`Failed to write to ${destination}: ${err.message}`) + return + } } } return From 805474d0bd519846b189647f3e34a9543cfa02c1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:08:26 -0400 Subject: [PATCH 0453/1178] refactor(api/fs): handle URL inputs for local origin --- api/fs/handle.js | 30 ++++++++++++++++ api/fs/index.js | 85 +++++++++++++++++++++++++++++++++++++++------- api/fs/promises.js | 55 ++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 13 deletions(-) diff --git a/api/fs/handle.js b/api/fs/handle.js index 12ab80a3ca..6099b70770 100644 --- a/api/fs/handle.js +++ b/api/fs/handle.js @@ -35,6 +35,25 @@ const dc = diagnostics.channels.group('fs', [ 'handle.close' ]) +function normalizePath (path) { + if (path instanceof URL) { + if (path.origin === globalThis.location.origin) { + return normalizePath(path.href) + } + + return null + } + + if (URL.canParse(path)) { + const url = new URL(path) + if (url.origin === globalThis.location.origin) { + path = `./${url.pathname.slice(1)}` + } + } + + return path +} + export const kOpening = Symbol.for('fs.FileHandle.opening') export const kClosing = Symbol.for('fs.FileHandle.closing') export const kClosed = Symbol.for('fs.FileHandle.closed') @@ -96,6 +115,7 @@ export class FileHandle extends EventEmitter { mode = FileHandle.DEFAULT_ACCESS_MODE } + path = normalizePath(path) const result = await ipc.request('fs.access', { mode, path }, options) if (result.err) { @@ -124,6 +144,7 @@ export class FileHandle extends EventEmitter { mode = FileHandle.DEFAULT_OPEN_MODE } + path = normalizePath(path) const handle = new this({ path, flags, mode }) if (typeof handle.path !== 'string') { @@ -161,6 +182,10 @@ export class FileHandle extends EventEmitter { this.path = options?.path || null this.mode = options?.mode || FileHandle.DEFAULT_OPEN_MODE + if (this.path) { + this.path = normalizePath(this.path) + } + // this id will be used to identify the file handle that is a // reference stored in the native side this.id = String(options?.id || rand64()) @@ -846,6 +871,7 @@ export class DirectoryHandle extends EventEmitter { * @return {Promise} */ static async open (path, options) { + path = normalizePath(path) const handle = new this({ path }) if (typeof handle.path !== 'string') { @@ -884,6 +910,10 @@ export class DirectoryHandle extends EventEmitter { this.id = String(options?.id || rand64()) this.path = options?.path || null + if (this.path) { + this.path = normalizePath(this.path) + } + // @TODO(jwerle): implement usage of this internally this.bufferSize = Math.min( DirectoryHandle.MAX_BUFFER_SIZE, diff --git a/api/fs/index.js b/api/fs/index.js index d4fed69cea..e9dcc2048f 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -50,12 +50,33 @@ function defaultCallback (err) { if (err) throw err } +function normalizePath (path) { + if (path instanceof URL) { + if (path.origin === globalThis.location.origin) { + return normalizePath(path.href) + } + + return null + } + + if (URL.canParse(path)) { + const url = new URL(path) + if (url.origin === globalThis.location.origin) { + path = `./${url.pathname.slice(1)}` + } + } + + return path +} + async function visit (path, options = null, callback) { if (typeof options === 'function') { callback = options options = {} } + path = normalizePath(path) + const { flags, flag, mode } = options || {} let handle = null @@ -94,6 +115,8 @@ export function access (path, mode, callback) { throw new TypeError('callback must be a function.') } + path = normalizePath(path) + FileHandle .access(path, mode) .then((mode) => callback(null, mode)) @@ -108,6 +131,7 @@ export function access (path, mode, callback) { * @param {string?} [mode = F_OK(0)] */ export function accessSync (path, mode) { + path = normalizePath(path) const result = ipc.sendSync('fs.access', { path, mode }) if (result.err) { @@ -128,6 +152,7 @@ export function exists (path, callback) { throw new TypeError('callback must be a function.') } + path = normalizePath(path) access(path, (err) => { // eslint-disable-next-line callback(err !== null) @@ -140,6 +165,7 @@ export function exists (path, callback) { * @param {function(Boolean)?} [callback] */ export function existsSync (path) { + path = normalizePath(path) try { accessSync(path) return true @@ -171,6 +197,7 @@ export function chmod (path, mode, callback) { throw new TypeError('callback must be a function.') } + path = normalizePath(path) ipc.request('fs.chmod', { mode, path }).then((result) => { result?.err ? callback(result.err) : callback(null) }) @@ -192,6 +219,7 @@ export function chmodSync (path, mode) { throw new RangeError(`The value of "mode" is out of range. It must be an integer. Received ${mode}`) } + path = normalizePath(path) const result = ipc.sendSync('fs.chmod', { mode, path }) if (result.err) { @@ -207,6 +235,7 @@ export function chmodSync (path, mode) { * @param {function} callback */ export function chown (path, uid, gid, callback) { + path = normalizePath(path) if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -235,6 +264,7 @@ export function chown (path, uid, gid, callback) { * @param {number} gid */ export function chownSync (path, uid, gid) { + path = normalizePath(path) if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -285,6 +315,9 @@ export function close (fd, callback) { * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFile (src, dest, flags, callback) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') } @@ -314,6 +347,9 @@ export function copyFile (src, dest, flags, callback) { * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ export function copyFileSync (src, dest, flags) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') } @@ -345,6 +381,8 @@ export function createReadStream (path, options) { path = options?.path || null } + path = normalizePath(path) + let handle = null const stream = new ReadStream({ autoClose: typeof options?.fd !== 'number', @@ -388,6 +426,8 @@ export function createWriteStream (path, options) { path = options?.path || null } + path = normalizePath(path) + let handle = null const stream = new WriteStream({ autoClose: typeof options?.fd !== 'number', @@ -506,6 +546,8 @@ export function ftruncate (fd, offset, callback) { * @param {function} callback */ export function lchown (path, uid, gid, callback) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -534,6 +576,9 @@ export function lchown (path, uid, gid, callback) { * @param {function} */ export function link (src, dest, callback) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') } @@ -555,6 +600,8 @@ export function link (src, dest, callback) { * @ignore */ export function mkdir (path, options, callback) { + path = normalizePath(path) + if ((typeof options === 'undefined') || (typeof options === 'function')) { throw new TypeError('options must be an object.') } @@ -584,6 +631,8 @@ export function mkdir (path, options, callback) { * @ignore */ export function mkdirSync (path, options) { + path = normalizePath(path) + if ((typeof options === 'undefined') || (typeof options === 'function')) { throw new TypeError('options must be an object.') } @@ -650,6 +699,8 @@ export function open (path, flags = 'r', mode = 0o666, options = null, callback) throw new TypeError('callback must be a function.') } + path = normalizePath(path) + FileHandle .open(path, flags, mode, options) .then((handle) => { @@ -678,6 +729,8 @@ export function opendir (path, options = {}, callback) { throw new TypeError('callback must be a function.') } + path = normalizePath(path) + DirectoryHandle .open(path, options) .then((handle) => callback(null, new Dir(handle, options))) @@ -777,6 +830,7 @@ export function readdir (path, options = {}, callback) { throw new TypeError('callback must be a function.') } + path = normalizePath(path) options = { entries: DirectoryHandle.MAX_ENTRIES, withFileTypes: false, @@ -824,10 +878,8 @@ export function readFile (path, options = {}, callback) { options = { encoding: options } } - options = { - flags: 'r', - ...options - } + path = normalizePath(path) + options = { flags: 'r', ...options } if (typeof callback !== 'function') { throw new TypeError('callback must be a function.') @@ -864,10 +916,8 @@ export function readFileSync (path, options = {}) { options = { encoding: options } } - options = { - flags: 'r', - ...options - } + path = normalizePath(path) + options = { flags: 'r', ...options } let result = null @@ -926,6 +976,7 @@ export function readlink (path, callback) { throw new TypeError('callback must be a function.') } + path = normalizePath(path) ipc.request('fs.readlink', { path }).then((result) => { result?.err ? callback(result.err) : callback(null, result.data.path) }).catch(callback) @@ -957,6 +1008,9 @@ export function realpath (path, callback) { * @param {function} callback */ export function rename (src, dest, callback) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -980,6 +1034,8 @@ export function rename (src, dest, callback) { * @param {function} callback */ export function rmdir (path, callback) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -1001,6 +1057,7 @@ export function rmdir (path, callback) { * @param {string?} [options.flag ? 'r'] */ export function statSync (path, options) { + path = normalizePath(path) const result = ipc.sendSync('fs.stat', { path }) if (result.err) { @@ -1093,6 +1150,8 @@ export function lstat (path, options, callback) { */ export function symlink (src, dest, type = null, callback) { let flags = 0 + src = normalizePath(src) + dest = normalizePath(dest) if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') @@ -1133,6 +1192,8 @@ export function symlink (src, dest, type = null, callback) { * @param {function} callback */ export function unlink (path, callback) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -1167,11 +1228,8 @@ export function writeFile (path, data, options, callback) { options = { encoding: options } } - options = { - mode: 0o666, - flag: 'w', - ...options - } + path = normalizePath(path) + options = { mode: 0o666, flag: 'w', ...options } if (typeof callback !== 'function') { throw new TypeError('callback must be a function.') @@ -1207,6 +1265,7 @@ export function watch (path, options, callback = null) { callback = options } + path = normalizePath(path) const watcher = new Watcher(path, options) watcher.on('change', callback) return watcher diff --git a/api/fs/promises.js b/api/fs/promises.js index 513ab95821..41f01d8c5b 100644 --- a/api/fs/promises.js +++ b/api/fs/promises.js @@ -48,6 +48,25 @@ export { WriteStream } +function normalizePath (path) { + if (path instanceof URL) { + if (path.origin === globalThis.location.origin) { + return normalizePath(path.href) + } + + return null + } + + if (URL.canParse(path)) { + const url = new URL(path) + if (url.origin === globalThis.location.origin) { + path = `./${url.pathname.slice(1)}` + } + } + + return path +} + /** * @typedef {import('../buffer.js').Buffer} Buffer * @typedef {import('.stats.js').Stats} Stats @@ -61,6 +80,7 @@ async function visit (path, options, callback) { } const { flags, flag, mode } = options || {} + path = normalizePath(path) // just visit `FileHandle`, without closing if given if (path instanceof FileHandle) { @@ -83,6 +103,7 @@ async function visit (path, options, callback) { * @param {object?} [options] */ export async function access (path, mode, options) { + path = normalizePath(path) return await FileHandle.access(path, mode, options) } @@ -93,6 +114,8 @@ export async function access (path, mode, options) { * @returns {Promise} */ export async function chmod (path, mode) { + path = normalizePath(path) + if (typeof mode !== 'number') { throw new TypeError(`The argument 'mode' must be a 32-bit unsigned integer or an octal string. Received ${mode}`) } @@ -116,6 +139,8 @@ export async function chmod (path, mode) { * @return {Promise} */ export async function chown (path, uid, gid) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -143,6 +168,9 @@ export async function chown (path, uid, gid) { * @return {Promise} */ export async function copyFile (src, dest, flags) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') } @@ -170,6 +198,8 @@ export async function copyFile (src, dest, flags) { * @return {Promise} */ export async function lchown (path, uid, gid) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'src\' must be a string') } @@ -196,6 +226,9 @@ export async function lchown (path, uid, gid) { * @return {Promise} */ export async function link (src, dest) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') } @@ -224,6 +257,8 @@ export async function mkdir (path, options = {}) { const mode = options.mode ?? 0o777 const recursive = Boolean(options.recursive) + path = normalizePath(path) + if (typeof mode !== 'number') { throw new TypeError('mode must be a number.') } @@ -249,6 +284,7 @@ export async function mkdir (path, options = {}) { * @return {Promise} */ export async function open (path, flags = 'r', mode = 0o666) { + path = normalizePath(path) return await FileHandle.open(path, flags, mode) } @@ -261,6 +297,7 @@ export async function open (path, flags = 'r', mode = 0o666) { * @return {Promise} */ export async function opendir (path, options) { + path = normalizePath(path) const handle = await DirectoryHandle.open(path, options) return new Dir(handle, options) } @@ -273,6 +310,7 @@ export async function opendir (path, options) { * @param {boolean?} [options.withFileTypes = false] */ export async function readdir (path, options) { + path = normalizePath(path) options = { entries: DirectoryHandle.MAX_ENTRIES, withFileTypes: false, @@ -314,6 +352,7 @@ export async function readFile (path, options) { options = { encoding: options } } + path = normalizePath(path) options = { flags: 'r', ...options } return await visit(path, options, async (handle) => { @@ -327,6 +366,8 @@ export async function readFile (path, options) { * @return {Promise} */ export async function readlink (path) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -346,6 +387,8 @@ export async function readlink (path) { * @return {Promise} */ export async function realpath (path) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -366,6 +409,9 @@ export async function realpath (path) { * @return {Promise} */ export async function rename (src, dest) { + src = normalizePath(src) + dest = normalizePath(dest) + if (typeof src !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -387,6 +433,8 @@ export async function rename (src, dest) { * @return {Promise} */ export async function rmdir (path) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -407,6 +455,7 @@ export async function rmdir (path) { * @return {Promise} */ export async function stat (path, options) { + path = normalizePath(path) return await visit(path, {}, async (handle) => { return await handle.stat(options) }) @@ -421,6 +470,7 @@ export async function stat (path, options) { * @return {Promise} */ export async function lstat (path, options) { + path = normalizePath(path) return await visit(path, {}, async (handle) => { return await handle.lstat(options) }) @@ -434,6 +484,7 @@ export async function lstat (path, options) { */ export async function symlink (src, dest, type = null) { let flags = 0 + src = normalizePath(dest) if (typeof src !== 'string') { throw new TypeError('The argument \'src\' must be a string') @@ -468,6 +519,8 @@ export async function symlink (src, dest, type = null) { * @return {Promise} */ export async function unlink (path) { + path = normalizePath(path) + if (typeof path !== 'string') { throw new TypeError('The argument \'path\' must be a string') } @@ -495,6 +548,7 @@ export async function writeFile (path, data, options) { options = { encoding: options } } + path = normalizePath(path) options = { flag: 'w', mode: 0o666, ...options } return await visit(path, options, async (handle) => { @@ -511,6 +565,7 @@ export async function writeFile (path, data, options) { * @return {Watcher} */ export function watch (path, options) { + path = normalizePath(path) return new Watcher(path, options) } From 24f7e8b53fbe62e0bb65b4a0e9f57fc902cc0edc Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:08:41 -0400 Subject: [PATCH 0454/1178] refactor(api/location.js): handle blob (worker) origins --- api/location.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/location.js b/api/location.js index 8081eee68f..cb1a5fd3bd 100644 --- a/api/location.js +++ b/api/location.js @@ -20,9 +20,9 @@ export const hostname = ( export const host = hostname export const search = href.split('?')[1] ?? '' export const hash = href.split('#')[1] ?? '' -export const pathname = href.slice( - href.indexOf(hostname) + hostname.length -) +export const pathname = globalLocation.protocol === 'blob:' + ? '/' + : href.slice(href.indexOf(hostname) + hostname.length) export const origin = `${protocol}//${(host + pathname).replace(/\/\//g, '/')}` From f270bd5aead39e5ccc0d1df80e14144e9adf311d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:08:50 -0400 Subject: [PATCH 0455/1178] chore(api/process.js): remove dead import --- api/process.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/process.js b/api/process.js index 510253e720..25793266fc 100644 --- a/api/process.js +++ b/api/process.js @@ -8,7 +8,6 @@ */ import { primordials, send } from './ipc.js' import { EventEmitter } from './events.js' -import { Buffer } from './buffer.js' import signal from './signal.js' import tty from './tty.js' import os from './os.js' From 910131a672b75bcaeff0b19a0995d15af84f7598 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:09:23 -0400 Subject: [PATCH 0456/1178] refactor(api/util.js): improve output and anon functions --- api/util.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/util.js b/api/util.js index f0a2f6a7d3..bd803ad684 100644 --- a/api/util.js +++ b/api/util.js @@ -511,7 +511,9 @@ export function inspect (value, options) { try { const hidden = Object.getOwnPropertyNames(value) for (const key of hidden) { - keys.add(key) + if (value instanceof Error && !/stack|message|name/.test(key)) { + keys.add(key) + } } } catch (err) {} } @@ -695,11 +697,14 @@ export function inspect (value, options) { output.push(`(${context}:${lineno})`) } else if (context) { output.push(`${context}`) + } else if (!symbol) { + output.push('') } if (output.length) { output.unshift(' at') } + return output.filter(Boolean).join(' ') } From c4e4924578a584638a08e35c99e1205c3f59e029 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:09:41 -0400 Subject: [PATCH 0457/1178] refactor(api/path/path.js): handle URL input for 'join()' --- api/path/path.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/path/path.js b/api/path/path.js index b08eebcdb7..ab9250d84f 100644 --- a/api/path/path.js +++ b/api/path/path.js @@ -170,10 +170,12 @@ export function join (options, ...components) { const { sep } = options const queries = [] const resolved = [] - let protocol = null - const isAbsolute = components[0].trim().startsWith(sep) + let protocol = null + let hostname = null + let origin = null + while (components.length) { let component = String(components.shift() || '') const url = parseURL(component, { strict: true }) || component @@ -181,6 +183,7 @@ export function join (options, ...components) { if (url.protocol) { if (!protocol) { protocol = url.protocol + origin = url.origin } component = url.pathname @@ -208,6 +211,10 @@ export function join (options, ...components) { const joined = resolved.join(sep) + if (origin) { + return new URL(joined, origin).href + } + return isAbsolute ? sep + joined : joined From a177bcc0b687f342d69a2c6c3ba397e1e610ea20 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:10:04 -0400 Subject: [PATCH 0458/1178] refactor(api/internal/callsite.js): finish internal 'CallSite' API --- api/internal/callsite.js | 839 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 827 insertions(+), 12 deletions(-) diff --git a/api/internal/callsite.js b/api/internal/callsite.js index 9a7cbc7c34..1979e6bf0e 100644 --- a/api/internal/callsite.js +++ b/api/internal/callsite.js @@ -1,4 +1,8 @@ +import InternalSymbols from './symbols.js' import { createHook } from '../async/hooks.js' +import { murmur3 } from '../crypto.js' +import { Buffer } from '../buffer.js' +import path from '../path.js' let isAsyncContext = false const asyncContexts = new Set([ @@ -25,80 +29,891 @@ const hook = createHook({ hook.enable() +/** + * @typedef {{ + * sourceURL: string | null, + * symbol: string, + * column: number | undefined, + * line: number | undefined, + * native: boolean + * }} ParsedStackFrame + */ + +/** + * A container for location data related to a `StackFrame` + */ +export class StackFrameLocation { + /** + * Creates a `StackFrameLocation` from JSON input. + * @param {object=} json + * @return {StackFrameLocation} + */ + static from (json) { + const location = new this() + + if (Number.isFinite(json?.lineNumber)) { + location.lineNumber = json.lineNumber + } + + if (Number.isFinite(json?.columnNumber)) { + location.columnNumber = json.columnNumber + } + + if (json?.sourceURL && URL.canParse(json.sourceURL)) { + location.sourceURL = new URL(json.sourceURL).href + } + + if (json?.isNative === true) { + location.isNative = true + } + + return location + } + + /** + * The line number of the location of the stack frame, if available. + * @type {number | undefined} + */ + lineNumber + + /** + * The column number of the location of the stack frame, if available. + * @type {number | undefined} + */ + columnNumber + + /** + * The source URL of the location of the stack frame, if available. This value + * may be `null`. + * @type {string?} + */ + sourceURL = null + + /** + * `true` if the stack frame location is in native location, otherwise + * this value `false` (default). + * @type + */ + isNative = false + + /** + * Converts this `StackFrameLocation` to a JSON object. + * @ignore + * @return {{ + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }} + */ + toJSON () { + return { + lineNumber: this.lineNumber, + columnNumber: this.columnNumber, + sourceURL: this.sourceURL, + isNative: this.isNative + } + } + + /** + * Serializes this `StackFrameLocation`, suitable for `postMessage()` transfers. + * @ignore + * @return {{ + * __type__: 'StackFrameLocation', + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }} + */ + [InternalSymbols.serialize] () { + return { __type__: 'StackFrameLocation', ...this.toJSON() } + } +} + +/** + * A stack frame container related to a `CallSite`. + */ +export class StackFrame { + /** + * Parses a raw stack frame string into structured data. + * @param {string} rawStackFrame + * @return {ParsedStackFrame} + */ + static parse (rawStackFrame) { + const parsed = { + sourceURL: null, + symbol: '', + column: undefined, + line: undefined + } + + const parts = rawStackFrame.split('@') + const symbol = parts.shift() + const location = parts.shift() + + if (symbol) { + parsed.symbol = symbol + } + + if (location === '[native code]') { + parsed.native = true + } else if (location && URL.canParse(location)) { + const url = new URL(location) + const [pathname, lineno, columnno] = url.pathname.split(':') + const line = parseInt(lineno) + const column = parseInt(columnno) + + if (Number.isFinite(line)) { + parsed.line = line + } + + if (Number.isFinite(column)) { + parsed.column = column + } + + parsed.sourceURL = new URL(pathname + url.search, url.origin).href + } + + return parsed + } + + /** + * Creates a new `StackFrame` from an `Error` and raw stack frame + * source `rawStackFrame`. + * @param {Error} error + * @param {string} rawStackFrame + * @return {StackFrame} + */ + static from (error, rawStackFrame) { + const parsed = this.parse(rawStackFrame) + return new this(error, parsed, rawStackFrame) + } + + /** + * The stack frame location data. + * @type {StackFrameLocation} + */ + location = new StackFrameLocation() + + /** + * The `Error` associated with this `StackFrame` instance. + * @type {Error?} + */ + error = null + + /** + * The name of the function where the stack frame is located. + * @type {string?} + */ + symbol = null + + /** + * The raw stack frame source string. + * @type {string?} + */ + source = null + + /** + * `StackFrame` class constructor. + * @param {Error} error + * @param {ParsedStackFrame=} [frame] + * @param {string=} [source] + */ + constructor (error, frame = null, source = null) { + if (error instanceof Error) { + this.error = error + } + + if (typeof source === 'string') { + this.source = source + } + + if (Number.isFinite(frame?.line)) { + this.location.lineNumber = frame.line + } + + if (Number.isFinite(frame?.column)) { + this.location.columnNumber = frame.column + } + + if (typeof frame?.sourceURL === 'string' && URL.canParse(frame.sourceURL)) { + this.location.sourceURL = frame.sourceURL + } + + if (typeof frame?.symbol === 'string') { + this.symbol = frame.symbol + } + + if (frame?.native === true) { + this.location.isNative = true + } + } + + /** + * Converts this `StackFrameLocation` to a JSON object. + * @ignore + * @return {{ + * location: { + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * }} + */ + toJSON () { + return { + location: this.location.toJSON(), + isNative: this.isNative, + symbol: this.symbol, + source: this.source, + error: this.error === null + ? null + : { + message: this.error.message ?? '', + name: this.error.name ?? '', + stack: String(this.error[CallSite.StackSourceSymbol] ?? this.error.stack ?? '') + } + } + } + + /** + * Serializes this `StackFrame`, suitable for `postMessage()` transfers. + * @ignore + * @return {{ + * __type__: 'StackFrame', + * location: { + * __type__: 'StackFrameLocation', + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * }} + */ + [InternalSymbols.serialize] () { + return { + __type__: 'StackFrame', + ...this.toJSON(), + location: this.location[InternalSymbols.serialize]() + } + } +} + +// private symbol for `CallSiteList` previous reference +const $previous = Symbol('previos') + +/** + * A v8 compatible interface and container for call site information. + */ export class CallSite { + /** + * An internal symbol used to refer to the index of a promise in + * `Promise.all` or `Promise.any` function call site. + * @ignore + * @type {symbol} + */ static PromiseElementIndexSymbol = Symbol.for('socket.runtime.CallSite.PromiseElementIndex') + + /** + * An internal symbol used to indicate that a call site is in a `Promise.all` + * function call. + * @ignore + * @type {symbol} + */ static PromiseAllSymbol = Symbol.for('socket.runtime.CallSite.PromiseAll') + + /** + * An internal symbol used to indicate that a call site is in a `Promise.any` + * function call. + * @ignore + * @type {symbol} + */ static PromiseAnySymbol = Symbol.for('socket.runtime.CallSite.PromiseAny') + /** + * An internal source symbol used to store the original `Error` stack source. + * @ignore + * @type {symbol} + */ + static StackSourceSymbol = Symbol.for('socket.runtime.CallSite.StackSource') + #error = null + #frame = null + #previous = null - constructor (error) { + /** + * `CallSite` class constructor + * @param {Error} error + * @param {string} rawStackFrame + * @param {CallSite=} previous + */ + constructor (error, rawStackFrame, previous = null) { this.#error = error + this.#frame = StackFrame.from(error, rawStackFrame) + if (previous !== null && previous instanceof CallSite) { + this.#previous = previous + } } + /** + * Private accessor to "friend class" `CallSiteList`. + * @ignore + */ + get [$previous] () { return this.#previous } + set [$previous] (previous) { + if (previous === null || previous instanceof CallSite) { + this.#previous = previous + } + } + + /** + * The `Error` associated with the call site. + * @type {Error} + */ get error () { return this.#error } + /** + * The previous `CallSite` instance, if available. + * @type {CallSite?} + */ + get previous () { + return this.#previous + } + + /** + * A reference to the `StackFrame` data. + * @type {StackFrame} + */ + get frame () { + return this.#frame + } + + /** + * This function _ALWAYS__ returns `globalThis` as `this` cannot be determined. + * @return {object} + */ getThis () { + // not supported + return globalThis } + /** + * This function _ALWAYS__ returns `null` as the type name of `this` + * cannot be determined. + * @return {null} + */ getTypeName () { + // not supported + return null } + /** + * This function _ALWAYS__ returns `undefined` as the current function + * reference cannot be determined. + * @return {undefined} + */ getFunction () { + // not supported + return undefined } + /** + * Returns the name of the function in at the call site, if available. + * @return {string|undefined} + */ getFunctionName () { - const stack = this.#error.split('\n') - const parts = stack.split('@') - if (!parts[0]) { - return '' + const symbol = this.#frame.symbol + + if (symbol === 'global code' || symbol === 'module code' || symbol === 'eval code') { + return undefined } - return parts[0] + return symbol + } + + /** + * An alias to `getFunctionName() + * @return {string} + */ + getMethodName () { + return this.getFunctionName() } + /** + * Get the filename of the call site location, if available, otherwise this + * function returns 'unknown location'. + * @return {string} + */ getFileName () { - if (URL.canParse(this.#error.sourceURL)) { - const url = new URL(this.#error.sourceURL) - return url.href + if (this.#frame.location.sourceURL) { + const root = new URL('../../', import.meta.url || globalThis.location.href).pathname + + let filename = new URL(this.#frame.location.sourceURL).pathname.replace(root, '') + + if (/\/socket\//.test(filename)) { + filename = filename.replace('socket/', 'socket:').replace(/.js$/, '') + return filename + } + + return path.basename(new URL(this.#frame.location.sourceURL).pathname) } return 'unknown location' } + /** + * Returns the location source URL defaulting to the global location. + * @return {string} + */ + getScriptNameOrSourceURL () { + const url = new URL(this.#frame.location.sourceURL ?? globalThis.location.href) + let filename = url.pathname.replace(url.pathname, '') + + if (/\/socket\//.test(filename)) { + filename = filename.replace('socket/', 'socket:').replace(/.js$/, '') + return filename + } + + return url.href + } + + /** + * Returns a hash value of the source URL return by `getScriptNameOrSourceURL()` + * @return {string} + */ + getScriptHash () { + return Buffer.from(String(murmur3(this.getScriptNameOrSourceURL()))).toString('hex') + } + + /** + * Returns the line number of the call site location. + * This value may be `undefined`. + * @return {number|undefined} + */ getLineNumber () { - return this.#error.line + return this.#frame.lineNumber } + /** + * @ignore + * @return {number} + */ + getPosition () { + return 0 + } + + /** + * Attempts to get an "enclosing" line number, potentially the previous + * line number of the call site + * @param {number|undefined} + */ + getEnclosingLineNumber () { + if (this.#previous) { + const previousSourceURL = this.#previous.getScriptNameOrSourceURL() + if (previousSourceURL && previousSourceURL === this.getScriptNameOrSourceURL()) { + return this.#previous.getLineNumber() + } + } + } + + /** + * Returns the column number of the call site location. + * This value may be `undefined`. + * @return {number|undefined} + */ getColumnNumber () { - return this.#error.column + return this.#frame.columnNumber } + /** + * Attempts to get an "enclosing" column number, potentially the previous + * line number of the call site + * @param {number|undefined} + */ + getEnclosingColumnNumber () { + if (this.#previous) { + const previousSourceURL = this.#previous.getScriptNameOrSourceURL() + if (previousSourceURL && previousSourceURL === this.getScriptNameOrSourceURL()) { + return this.#previous.getColumnNumber() + } + } + } + + /** + * Gets the origin of where `eval()` was called if this call site function + * originated from a call to `eval()`. This function may return `undefined`. + * @return {string|undefined} + */ getEvalOrigin () { + let current = this + + while (current) { + if (current.frame.symbol === 'eval' && current.frame.location.isNative) { + const previous = current.previous + if (previous) { + return previous.location.sourceURL + } + } + + current = this.previous + } } + /** + * This function _ALWAYS__ returns `false` as `this` cannot be determined so + * "top level" detection is not possible. + * @return {boolean} + */ isTopLevel () { + return false } + /** + * Returns `true` if this call site originated from a call to `eval()`. + * @return {boolean} + */ isEval () { + let current = this + + while (current) { + if (current.frame.symbol === 'eval' || current.frame.symbol === 'eval code') { + return true + } + + current = this.previous + } + + return false } + /** + * Returns `true` if the call site is in a native location, otherwise `false`. + * @return {boolean} + */ isNative () { - return false + return this.#frame.location.isNative } + /** + * This function _ALWAYS_ returns `false` as constructor detection + * is not possible. + * @return {boolean} + */ isConstructor () { + // not supported + return false } + /** + * Returns `true` if the call site is in async context, otherwise `false`. + * @return {boolean} + */ isAsync () { return isAsyncContext } + /** + * Returns `true` if the call site is in a `Promise.all()` function call, + * otherwise `false. + * @return {boolean} + */ isPromiseAll () { + return this.#error[CallSite.PromiseAllSymbol] === true + } + + /** + * Gets the index of the promise element that was followed in a + * `Promise.all()` or `Promise.any()` function call. If not available, then + * this function returns `null`. + * @return {number|null} + */ + getPromiseIndex () { + return this.#error[CallSite.PromiseElementIndexSymbol] ?? null + } + + /** + * Converts this call site to a string. + * @return {string} + */ + toString () { + const { symbol, location } = this.#frame + const output = [symbol] + + if (location.sourceURL) { + const pathname = new URL(location.sourceURL).pathname + const root = new URL('../../', import.meta.url || globalThis.location.href).pathname + + let filename = pathname.replace(root, '') + + if (/\/?socket\//.test(filename)) { + filename = filename.replace('socket/', 'socket:').replace(/.js$/, '') + } + + if (location.lineNumber && location.columnNumber) { + output.push(`(${filename}:${location.lineNumber}:${location.columnNumber})`) + } else if (location.lineNumber) { + output.push(`(${filename}:${location.lineNumber})`) + } else { + output.push(`${filename}`) + } + } + + return output.filter(Boolean).join(' ') + } + + /** + * Converts this `CallSite` to a JSON object. + * @ignore + * @return {{ + * frame: { + * location: { + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * } + * }} + */ + toJSON () { + return { + frame: this.#frame.toJSON() + } + } + + /** + * Serializes this `CallSite`, suitable for `postMessage()` transfers. + * @ignore + * @return {{ + * __type__: 'CallSite', + * frame: { + * __type__: 'StackFrame', + * location: { + * __type__: 'StackFrameLocation', + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * } + * }} + */ + [InternalSymbols.serialize] () { + return { + __type__: 'CallSite', + frame: this.#frame[InternalSymbols.serialize]() + } + } +} + +/** + * An array based list container for `CallSite` instances. + */ +export class CallSiteList extends Array { + /** + * Creates a `CallSiteList` instance from `Error` input. + * @param {Error} error + * @param {string} source + * @return {CallSiteList} + */ + static from (error, source) { + const callsites = new this( + error, + source.split('\n').slice(0, Error.stackTraceLimit) + ) + + for (const source of callsites.sources.reverse()) { + callsites.unshift(new CallSite(error, source, callsites[0])) + } + + return callsites + } + + #error = null + #sources = [] + + /** + * `CallSiteList` class constructor. + * @param {Error} error + * @param {string[]=} [sources] + */ + constructor (error, sources = null) { + super() + this.#error = error + + if (Array.isArray(sources)) { + this.#sources = Array.from(sources) // copy + } else if (typeof error.stack === 'string') { + this.#sources = error.stack.split('\n') + } else if (typeof error[CallSiteList.StackSourceSymbol] === 'string') { + this.#sources = error[CallSiteList.StackSourceSymbol].split('\n') + } + } + + /** + * A reference to the `Error` for this `CallSiteList` instance. + * @type {Error} + */ + get error () { return this.#error } + + /** + * An array of stack frame source strings. + * @type {string[]} + */ + get sources () { + return this.#sources + } + + /** + * The original stack string derived from the sources. + * @type {string} + */ + get stack () { + return this.#sources.join('\n') + } + + /** + * Adds `CallSite` instances to the top of the list, linking previous + * instances to the next one. + * @param {...CallSite} callsites + * @return {number} + */ + unshift (...callsites) { + for (const callsite of callsites) { + if (callsite instanceof CallSite) { + callsite[$previous] = this[0] + super.unshift(callsite) + } + } + + return this.length + } + + /** + * A no-op function as `CallSite` instances cannot be added to the end + * of the list. + * @return {number} + */ + push () { + // no-op + return this.length + } + + /** + * Pops a `CallSite` off the end of the list. + * @return {CallSite|undefined} + */ + pop () { + const value = super.pop() + + if (this.length >= 1) { + this[this.length - 1][$previous] = null + } + + return value + } + + /** + * Converts the `CallSiteList` to a string combining the error name, message, + * and callsite stack information into a friendly string. + * @return {string} + */ + toString () { + const stack = this.map((callsite) => ` at ${callsite.toString()}`) + if (this.#error.name && this.#error.message) { + return [`${this.#error.name}: ${this.#error.message}`].concat(stack).join('\n') + } else if (this.#error.name) { + return [this.#error.name].concat(stack).join('\n') + } else { + return stack.join('\n') + } + } + + /** + * Converts this `CallSiteList` to a JSON object. + * @return {{ + * frame: { + * location: { + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * } + * }[]} + */ + toJSON () { + return Array.from(this.map((callsite) => callsite.toJSON())) + } + + /** + * Serializes this `CallSiteList`, suitable for `postMessage()` transfers. + * @ignore + * @return {Array<{ + * __type__: 'CallSite', + * frame: { + * __type__: 'StackFrame', + * location: { + * __type__: 'StackFrameLocation', + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * } + * }>} + */ + [InternalSymbols.serialize] () { + return this.toJSON() + } + + /** + * @ignore + */ + [Symbol.for('socket.util.inspect.custom')] () { + return this.toString() + } +} + +/** + * Creates an ordered and link array of `CallSite` instances from a + * given `Error`. + * @param {Error} error + * @param {string} source + * @return {CallSite[]} + */ +export function createCallSites (error, source) { + return CallSiteList.from(error, source) } export default CallSite From 98d8ec143306de3c52152b7b4ef92d1b65fdcfca Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:10:35 -0400 Subject: [PATCH 0459/1178] refactor(api/internal/error.js): finish 'prepareStackTrace' and 'captureStackTrace' impl --- api/internal/error.js | 144 +++++++++++++++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 30 deletions(-) diff --git a/api/internal/error.js b/api/internal/error.js index e970335ad3..997ee64fcd 100644 --- a/api/internal/error.js +++ b/api/internal/error.js @@ -1,13 +1,35 @@ -import { CallSite } from './callsite.js' - -function applyPlatforErrorHook (PlatformError, target, message, ...args) { - const error = PlatformError.call(target, message, ...args) +import { CallSite, createCallSites } from './callsite.js' + +/** + * The default `Error` class stack trace limit. + * @type {number} + */ +export const DEFAULT_ERROR_STACK_TRACE_LIMIT = 10 + +const DefaultPlatformError = globalThis.Error + +/** + * @ignore + * @param {typeof globalThis.Error} PlatformError + * @param {Error} target + * @param {...any} args + * @return {Error} + */ +function applyPlatforErrorHook (PlatformError, Constructor, target, ...args) { + const error = PlatformError.call(target, ...args) const stack = error.stack.split('\n').slice(2) // slice off the `Error()` + `applyPlatforErrorHook()` call frames const [, callsite] = stack[0].split('@') let stackValue = stack.join('\n') + let sourceStack = stackValue Object.defineProperties(error, { + [CallSite.StackSourceSymbol]: { + configurable: false, + enumerable: false, + get: () => sourceStack + }, + column: { configurable: true, enumerable: false, @@ -24,7 +46,7 @@ function applyPlatforErrorHook (PlatformError, target, message, ...args) { configurable: true, enumerable: false, writable: true, - value: message + value: error.message }, name: { @@ -45,20 +67,25 @@ function applyPlatforErrorHook (PlatformError, target, message, ...args) { enumerable: false, set: (stack) => { stackValue = stack + if (stackValue && typeof stackValue === 'string') { + sourceStack = stackValue + } + }, + get: () => { Object.defineProperty(error, 'stack', { configurable: true, enumerable: false, writable: true, value: stackValue }) - }, - get: () => { + if (Error.stackTraceLimit === 0) { stackValue = `${error.name}: ${error.message}` } else { - if (typeof target.constructor.prepareStackTrace === 'function') { - const callsites = [] - stackValue = target.constructor.prepareStackTrace(error, callsites) + const prepareStackTrace = Constructor.prepareStackTrace || globalThis.Error.prepareStackTrace + if (typeof prepareStackTrace === 'function') { + const callsites = createCallSites(error, stackValue) + stackValue = prepareStackTrace(error, callsites) } } @@ -100,29 +127,60 @@ function applyPlatforErrorHook (PlatformError, target, message, ...args) { return error } -function makeError (PlatformError) { - const Error = function Error (message, ...args) { +/** + * Creates and install a new runtime `Error` class + * @param {typeof globalThis.Error} PlatformError + * @param {boolean=} [isBaseError] + * @return {typeof globalThis.Error} + */ +function installRuntimeError (PlatformError, isBaseError = false) { + if (!PlatformError) { + PlatformError = DefaultPlatformError + } + + function Error (...args) { if (!(this instanceof Error)) { - return new Error(message, ...args) + return new Error(...args) } - return applyPlatforErrorHook(PlatformError, this, message, ...args) + return applyPlatforErrorHook(PlatformError, Error, this, ...args) } + // directly inherit platform `Error` prototype Error.prototype = PlatformError.prototype /** * @ignore */ - Error.stackTraceLimit = 32 + Error.stackTraceLimit = DEFAULT_ERROR_STACK_TRACE_LIMIT + + Object.defineProperty(Error, 'captureStackTrace', { + configurable: true, + enumerable: false, + // eslint-disable-next-line + value (target) { + if (!target || typeof target !== 'object') { + throw new TypeError( + `Invalid target given to ${PlatformError.name}.captureStackTrace. ` + + `Expecting 'target' to be an object. Received: ${target}` + ) + } - /** - * @ignore - */ - // eslint-disable-next-line - Error.captureStackTrace = function (err, ErrorConstructor) { - // TODO - } + // prepareStackTrace is already called there + if (target instanceof Error) { + target.stack = new PlatformError().stack.split('\n').slice(2).join('\n') + } else { + const stack = new PlatformError().stack.split('\n').slice(2).join('\n') + const prepareStackTrace = Error.prepareStackTrace || globalThis.Error.prepareStackTrace + if (typeof prepareStackTrace === 'function') { + const callsites = createCallSites(target, stack) + target.stack = prepareStackTrace(target, callsites) + } else { + target.stack = stack + } + } + } + }) // Install globalThis[PlatformError.name] = Error @@ -130,14 +188,40 @@ function makeError (PlatformError) { return Error } -export const Error = makeError(globalThis.Error) -export const URIError = makeError(globalThis.URIError) -export const EvalError = makeError(globalThis.EvalError) -export const TypeError = makeError(globalThis.TypeError) -export const RangeError = makeError(globalThis.RangeError) -export const SyntaxError = makeError(globalThis.SyntaxError) -export const ReferenceError = makeError(globalThis.ReferenceError) +export const Error = installRuntimeError(globalThis.Error, true) +export const URIError = installRuntimeError(globalThis.URIError) +export const EvalError = installRuntimeError(globalThis.EvalError) +export const TypeError = installRuntimeError(globalThis.TypeError) +export const RangeError = installRuntimeError(globalThis.RangeError) +export const MediaError = installRuntimeError(globalThis.MediaError) +export const SyntaxError = installRuntimeError(globalThis.SyntaxError) +export const ReferenceError = installRuntimeError(globalThis.ReferenceError) +export const AggregateError = installRuntimeError(globalThis.AggregateError) + +// web +export const RTCError = installRuntimeError(globalThis.RTCError) +export const OverconstrainedError = installRuntimeError(globalThis.OverconstrainedError) +export const GeolocationPositionError = installRuntimeError(globalThis.GeolocationPositionError) + +// not-standard +export const ApplePayError = installRuntimeError(globalThis.ApplePayError) export default { - Error + Error, + URIError, + EvalError, + TypeError, + RangeError, + MediaError, + SyntaxError, + ReferenceError, + AggregateError, + + // web + RTCError, + OverconstrainedError, + GeolocationPositionError, + + // non-standard + ApplePayError } From 83b9e6e6e587b5feafb364c4971ded1f8a283a42 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:10:50 -0400 Subject: [PATCH 0460/1178] fix(api/internal/promise.js): verify element is 'Promise' --- api/internal/promise.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/internal/promise.js b/api/internal/promise.js index e07aef76fe..69c982a033 100644 --- a/api/internal/promise.js +++ b/api/internal/promise.js @@ -26,6 +26,10 @@ globalThis.Promise = class Promise extends NativePromise { globalThis.Promise.all = function (iterable) { return NativePromiseAll.call(NativePromise, Array.from(iterable).map((promise, index) => { + if (!promise || typeof promise.catch !== 'function') { + return promise + } + return promise.catch((err) => { return Promise.reject(Object.defineProperties(err, { [Symbol.for('socket.runtime.CallSite.PromiseElementIndex')]: { @@ -48,6 +52,10 @@ globalThis.Promise.all = function (iterable) { globalThis.Promise.any = function (iterable) { return NativePromiseAny.call(NativePromise, Array.from(iterable).map((promise, index) => { + if (!promise || typeof promise.catch !== 'function') { + return promise + } + return promise.catch((err) => { return Promise.reject(Object.defineProperties(err, { [Symbol.for('socket.runtime.CallSite.PromiseElementIndex')]: { From 8f527c5b9c42130dcd52b0e17098f65b5039b60f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:11:25 -0400 Subject: [PATCH 0461/1178] refactor(api/internal/streams.js): initial stub for byte (byob) read stream controller --- api/internal/streams.js | 70 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/api/internal/streams.js b/api/internal/streams.js index af24e6d66b..7b2d46e044 100644 --- a/api/internal/streams.js +++ b/api/internal/streams.js @@ -1,3 +1,9 @@ +import { Deferred } from '../async/deferred.js' + +const PlatformReadableStreamBYOBReader = globalThis.ReadableStreamBYOBReader +const PlatformReadableStreamBYOBRequest = globalThis.ReadableStreamBYOBRequest +const PlatformReadableByteStreamController = globalThis.ReadableByteStreamController + export class ReadableStream extends globalThis.ReadableStream { constructor (options) { if (options?.type === 'bytes') { @@ -9,6 +15,70 @@ export class ReadableStream extends globalThis.ReadableStream { super(options) } + + getReader (options) { + if (options?.mode === 'byob') { + return new ReadableStreamBYOBReader(this) + } + + return super.getReader(options) + } +} + +class ReadableStreamBYOBReaderCancellation { + reason = undefined + state = false +} + +class ReadableStreamBYOBReadResult { + value = undefined + done = false + + constructor (value = undefined, done = false) { + this.value = value + this.done = done + } +} + +export const ReadableStreamBYOBReader = PlatformReadableStreamBYOBReader || class ReadableStreamBYOBReader { + #closed = new Deferred() + #cancellation = new ReadableStreamBYOBReaderCancellation() + + get closed () { + return this.#closed.promise + } + + cancel (reason = undefined) { + this.#cancellation.state = true + if (typeof reason === 'string') { + this.#cancellation.reason = reason + } + this.#closed.resolve(reason) + return this.#closed.promise + } + + read (view) { + if (this.#cancellation.state === true) { + return new ReadableStreamBYOBReadResult(undefined, true) + } + } +} + +export const ReadableStreamBYOBRequest = PlatformReadableStreamBYOBRequest || class ReadableStreamBYOBRequest { + #view = null + + get view () { + return this.#view + } + + respond (bytesWritten) { + } + + respondWithNewView (view) { + } +} + +export const ReadableByteStreamController = PlatformReadableByteStreamController || class ReadableByteStreamController { } export default { From 29ddb80ddfa0507de11ab0fce90e7275885f4e47 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:11:40 -0400 Subject: [PATCH 0462/1178] refactor(src/ipc/bridge.cc): handle 'HEAD' requests on macos/ios --- src/ipc/bridge.cc | 93 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index e21b2b87ab..ade800a0da 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -3991,14 +3991,16 @@ static void registerSchemeHandler (Router *router) { return; } - const auto data = [NSData - dataWithBytes: res.buffer.bytes - length: res.buffer.size - ]; - - if (res.buffer.size && data.length > 0) { - [task didReceiveData: data]; - } + if (fetchRequest.method != "HEAD" && fetchRequest.method != "OPTIONS") { + const auto data = [NSData + dataWithBytes: res.buffer.bytes + length: res.buffer.size + ]; + + if (res.buffer.size && data.length > 0) { + [task didReceiveData: data]; + } + } [task didFinish]; [self finalizeTask: task]; @@ -4112,7 +4114,7 @@ static void registerSchemeHandler (Router *router) { "" ); - data = [@(redirectSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; + headers[@"content-length"] = @(redirectSource.size()); auto response = [[NSHTTPURLResponse alloc] initWithURL: [NSURL URLWithString: @(redirectURL.c_str())] @@ -4122,7 +4124,12 @@ static void registerSchemeHandler (Router *router) { ]; [task didReceiveResponse: response]; - [task didReceiveData: data]; + + if (String(request.HTTPMethod.UTF8String) != "HEAD") { + data = [@(redirectSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; + [task didReceiveData: data]; + } + [task didFinish]; #if !__has_feature(objc_arc) @@ -4150,6 +4157,35 @@ static void registerSchemeHandler (Router *router) { } } + if (String(request.HTTPMethod.UTF8String) == "HEAD") { + NSNumber* size = nullptr; + NSError* error = nullptr; + [url startAccessingSecurityScopedResource]; + [url getResourceValue: &size + forKey: NSURLFileSizeKey + error: &error + ]; + [url stopAccessingSecurityScopedResource]; + + if (size != nullptr) { + headers[@"content-length"] = size.stringValue; + } + + auto response = [[NSHTTPURLResponse alloc] + initWithURL: request.URL + statusCode: 200 + HTTPVersion: @"HTTP/1.1" + headerFields: headers + ]; + + [task didReceiveResponse: response]; + [task didFinish]; + + #if !__has_feature(objc_arc) + [response release]; + #endif + } + [url startAccessingSecurityScopedResource]; auto data = [NSData dataWithContentsOfURL: url]; headers[@"content-length"] = [@(data.length) stringValue]; @@ -4338,13 +4374,15 @@ static void registerSchemeHandler (Router *router) { return; } - const auto data = [NSData - dataWithBytes: res.buffer.bytes - length: res.buffer.size - ]; + if (fetchRequest.method != "HEAD" && fetchRequest.method != "OPTIONS") { + const auto data = [NSData + dataWithBytes: res.buffer.bytes + length: res.buffer.size + ]; - if (res.buffer.size && data.length > 0) { - [task didReceiveData: data]; + if (res.buffer.size && data.length > 0) { + [task didReceiveData: data]; + } } [task didFinish]; @@ -4406,7 +4444,7 @@ static void registerSchemeHandler (Router *router) { "" ); - data = [@(redirectSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; + headers[@"content-length"] = @(redirectSource.size()); auto response = [[NSHTTPURLResponse alloc] initWithURL: [NSURL URLWithString: @(redirectURL.c_str())] @@ -4416,7 +4454,12 @@ static void registerSchemeHandler (Router *router) { ]; [task didReceiveResponse: response]; - [task didReceiveData: data]; + + if (String(request.HTTPMethod.UTF8String) != "HEAD") { + data = [@(redirectSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; + [task didReceiveData: data]; + } + [task didFinish]; #if !__has_feature(objc_arc) @@ -4441,7 +4484,19 @@ static void registerSchemeHandler (Router *router) { ]; #endif - if (String(request.HTTPMethod.UTF8String) == "GET") { + if (String(request.HTTPMethod.UTF8String) == "HEAD") { + NSNumber* size = nullptr; + NSError* error = nullptr; + [components.URL + getResourceValue: &size + forKey: NSURLFileSizeKey + error: &error + ]; + + if (size != nullptr) { + headers[@"content-length"] = size.stringValue; + } + } else if (String(request.HTTPMethod.UTF8String) == "GET") { data = [NSData dataWithContentsOfURL: components.URL]; } From 4b8d16292d20712484fddb331f08d8e778830831 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:12:04 -0400 Subject: [PATCH 0463/1178] refactor(src/core/service_worker_container.cc): case insensitive header check --- src/core/service_worker_container.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 9887788a2f..f21340de22 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -639,11 +639,17 @@ namespace SSC { const auto key = trim(parts[0]); const auto value = trim(parts[1]); - if (key == "Runtime-ServiceWorker-Fetch-Mode" && value == "ignore") { + if ( + (key == "Runtime-ServiceWorker-Fetch-Mode" && value == "ignore") || + (key == "runtime-serviceworker-fetch-mode" && value == "ignore") + ) { return false; } - if (key == "runtime-worker-type" && value == "serviceworker") { + if ( + (key == "Runtime-Worker-Type" && value == "serviceworker") || + (key == "runtime-worker-type" && value == "serviceworker") + ) { return false; } } @@ -662,6 +668,10 @@ namespace SSC { } } + if (scope.size() == 0) { + return false; + } + scope = normalizeScope(scope); if (scope.size() > 0 && this->registrations.contains(scope)) { From 090a1463feddbf77e826162b7061fa0eae10b66d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 01:13:22 -0400 Subject: [PATCH 0464/1178] chore(api): generate types + docs --- api/README.md | 168 +++++++-------- api/index.d.ts | 574 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 623 insertions(+), 119 deletions(-) diff --git a/api/README.md b/api/README.md index 159e2132c8..55c20ed740 100644 --- a/api/README.md +++ b/api/README.md @@ -811,7 +811,7 @@ External docs: https://nodejs.org/api/events.html import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L87) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L108) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -823,7 +823,7 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L110) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L133) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` @@ -834,7 +834,7 @@ Synchronously check access a file for a given mode calling `callback` | path | string \| Buffer \| URL | | false | | | mode | string? | F_OK(0) | true | | -## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L126) +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L150) Checks if a path exists @@ -843,7 +843,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L142) +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L167) Checks if a path exists @@ -852,7 +852,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L161) +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L187) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -866,7 +866,7 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L186) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L213) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Synchronously changes the permissions of a file. @@ -877,7 +877,7 @@ Synchronously changes the permissions of a file. | path | string \| Buffer \| URL | | false | | | mode | number | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L209) +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L237) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -888,7 +888,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L237) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L266) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -898,7 +898,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | uid | number | | false | | | gid | number | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L263) +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L293) External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -908,7 +908,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L287) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L317) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -920,7 +920,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L316) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L349) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -931,7 +931,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L342) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L378) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -945,7 +945,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L385) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L423) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -959,7 +959,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L431) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L471) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the for the file descriptor. See @@ -973,7 +973,7 @@ Invokes the callback with the for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L458) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L498) Request that all data for the open file descriptor is flushed to the storage device. @@ -983,7 +983,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L480) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L520) Truncates the file up to `offset` bytes. @@ -993,7 +993,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L508) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L548) Chages ownership of link at `path` with `uid` and `gid. @@ -1004,7 +1004,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L536) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L578) Creates a link to `dest` from `src`. @@ -1014,7 +1014,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L618) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L667) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1027,7 +1027,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L671) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L722) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1040,7 +1040,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L697) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L750) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1054,7 +1054,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L732) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L785) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1068,7 +1068,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L766) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L819) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1081,7 +1081,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L817) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L871) @@ -1094,7 +1094,7 @@ Asynchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L862) +## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L914) @@ -1106,7 +1106,7 @@ Asynchronously read all entries in a directory. | options.flag ? r | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L920) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L970) Reads link at `path` @@ -1115,7 +1115,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L939) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L990) Computes real path for `path` @@ -1124,7 +1124,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L959) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1010) Renames file or directory at `src` to `dest`. @@ -1134,7 +1134,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L982) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1036) Removes directory at `path`. @@ -1143,7 +1143,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1003) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1059) Synchronously get the stats of a file @@ -1154,7 +1154,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1022) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1079) Get the stats of a file @@ -1167,7 +1167,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1060) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1117) Get the stats of a symbolic link @@ -1180,7 +1180,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1094) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1151) Creates a symlink of `src` at `dest`. @@ -1189,7 +1189,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1135) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1194) Unlinks (removes) file at `path`. @@ -1198,7 +1198,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1160) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1221) @@ -1213,7 +1213,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1205) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1263) Watch for changes at `path` calling `callback` @@ -1256,7 +1256,7 @@ Watch for changes at `path` calling `callback` import fs from 'socket:fs/promises' ``` -## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L85) +## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L105) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode Asynchronously check access a file. @@ -1267,7 +1267,7 @@ Asynchronously check access a file. | mode | string? | | true | | | options | object? | | true | | -## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L95) +## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L116) External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode @@ -1281,7 +1281,7 @@ External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode | :--- | :--- | :--- | | Not specified | Promise | | -## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L118) +## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L141) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -1295,7 +1295,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L145) +## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L170) Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -1309,7 +1309,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | :--- | :--- | :--- | | Not specified | Promise | | -## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L172) +## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L200) Chages ownership of link at `path` with `uid` and `gid. @@ -1323,7 +1323,7 @@ Chages ownership of link at `path` with `uid` and `gid. | :--- | :--- | :--- | | Not specified | Promise | | -## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L198) +## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L228) Creates a link to `dest` from `dest`. @@ -1336,7 +1336,7 @@ Creates a link to `dest` from `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L223) +## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L256) Asynchronously creates a directory. @@ -1352,7 +1352,7 @@ Asynchronously creates a directory. | :--- | :--- | :--- | | Not specified | Promise | Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. | -## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L251) +## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L286) External docs: https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode Asynchronously open a file. @@ -1368,7 +1368,7 @@ Asynchronously open a file. | :--- | :--- | :--- | | Not specified | Promise | | -## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L263) +## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L299) External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options @@ -1384,7 +1384,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options | :--- | :--- | :--- | | Not specified | Promise | | -## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L275) +## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L312) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options @@ -1396,7 +1396,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | options.encoding | string? | utf8 | true | | | options.withFileTypes | boolean? | false | true | | -## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L312) +## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L350) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options @@ -1413,7 +1413,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | :--- | :--- | :--- | | Not specified | Promise | | -## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L329) +## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L368) Reads link at `path` @@ -1425,7 +1425,7 @@ Reads link at `path` | :--- | :--- | :--- | | Not specified | Promise | | -## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L348) +## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L389) Computes real path for `path` @@ -1437,7 +1437,7 @@ Computes real path for `path` | :--- | :--- | :--- | | Not specified | Promise | | -## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L368) +## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L411) Renames file or directory at `src` to `dest`. @@ -1450,7 +1450,7 @@ Renames file or directory at `src` to `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L389) +## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L435) Removes directory at `path`. @@ -1462,7 +1462,7 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L409) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L457) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options Get the stats of a file @@ -1477,7 +1477,7 @@ Get the stats of a file | :--- | :--- | :--- | | Not specified | Promise | | -## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L423) +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L472) External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options Get the stats of a symbolic link. @@ -1492,7 +1492,7 @@ Get the stats of a symbolic link. | :--- | :--- | :--- | | Not specified | Promise | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L435) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L485) Creates a symlink of `src` at `dest`. @@ -1505,7 +1505,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L470) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L521) Unlinks (removes) file at `path`. @@ -1517,7 +1517,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L493) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L546) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1536,7 +1536,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L513) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L567) Watch for changes at `path` calling `callback` @@ -1831,7 +1831,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L222) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L229) Computes directory name of path. @@ -1844,7 +1844,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L264) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L271) Computes base name of path. @@ -1857,7 +1857,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L278) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L285) Computes extension name of path. @@ -1870,7 +1870,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L289) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L296) Computes normalized path @@ -1883,7 +1883,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L339) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L346) Formats `Path` object into a string. @@ -1896,7 +1896,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L355) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L362) Parses input `path` into a `Path` instance. @@ -1908,11 +1908,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L383) +## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L390) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L389) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L396) Creates a `Path` instance from `input` and optional `cwd`. @@ -1921,7 +1921,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L412) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L419) `Path` class constructor. @@ -1930,47 +1930,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L485) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L492) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L492) +### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L499) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L526) +### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L533) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L534) +### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L541) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L553) +### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L560) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L574) +### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L581) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L609) +### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L616) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L621) +### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L628) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L629) +### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L636) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L649) +### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L656) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L656) +### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L663) @@ -1978,7 +1978,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L664) +### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L671) Converts this `Path` instance to a string. @@ -1998,17 +1998,17 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L19) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L18) This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. -## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L27) +## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L26) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L171) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L170) Adds callback to the 'nextTick' queue. @@ -2016,7 +2016,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L202) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L201) Computed high resolution time as a `BigInt`. @@ -2028,7 +2028,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L228) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L227) @@ -2036,7 +2036,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L240) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L239) Returns an object describing the memory usage of the Node.js process measured in bytes. diff --git a/api/index.d.ts b/api/index.d.ts index 0fa18e07a0..62c3caab7d 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -12964,7 +12964,7 @@ declare module "socket:commonjs/builtins" { * @param {string} * @param {object} exports */ - export function define(name: any, exports: object): void; + export function define(name: any, exports: object, copy?: boolean): void; /** * Predicate to determine if a given module name is a builtin module. * @param {string} name @@ -13082,8 +13082,10 @@ declare module "socket:commonjs/module" { * @param {Module} module * @param {string} __filename * @param {string} __dirname + * @param {typeof process} process + * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: any): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; export function createRequire(url: any, options?: any): any; /** * @typedef {import('./require.js').RequireResolver[]} ModuleResolver @@ -13290,6 +13292,23 @@ declare module "socket:commonjs/module" { * @param {string} source */ static wrap(source: string): string; + /** + * Compiles given JavaScript module source. + * @param {string} source + * @param {{ url?: URL | string }=} [options] + * @return {function( + * object, + * function(string): any, + * Module, + * string, + * string, + * typeof process, + * object + * ): any} + */ + static compile(source: string, options?: { + url?: URL | string; + } | undefined): (arg0: object, arg1: (arg0: string) => any, arg2: Module, arg3: string, arg4: string, arg5: typeof process, arg6: object) => any; /** * Creates a `Module` from source URL and optionally a parent module. * @param {string|URL|Module} url @@ -13454,6 +13473,7 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; import builtins from "socket:commonjs/builtins"; @@ -13967,54 +13987,501 @@ declare module "socket:child_process/worker" { } declare module "socket:internal/callsite" { + /** + * Creates an ordered and link array of `CallSite` instances from a + * given `Error`. + * @param {Error} error + * @param {string} source + * @return {CallSite[]} + */ + export function createCallSites(error: Error, source: string): CallSite[]; + /** + * @typedef {{ + * sourceURL: string | null, + * symbol: string, + * column: number | undefined, + * line: number | undefined, + * native: boolean + * }} ParsedStackFrame + */ + /** + * A container for location data related to a `StackFrame` + */ + export class StackFrameLocation { + /** + * Creates a `StackFrameLocation` from JSON input. + * @param {object=} json + * @return {StackFrameLocation} + */ + static from(json?: object | undefined): StackFrameLocation; + /** + * The line number of the location of the stack frame, if available. + * @type {number | undefined} + */ + lineNumber: number | undefined; + /** + * The column number of the location of the stack frame, if available. + * @type {number | undefined} + */ + columnNumber: number | undefined; + /** + * The source URL of the location of the stack frame, if available. This value + * may be `null`. + * @type {string?} + */ + sourceURL: string | null; + /** + * `true` if the stack frame location is in native location, otherwise + * this value `false` (default). + * @type + */ + isNative: any; + /** + * Converts this `StackFrameLocation` to a JSON object. + * @ignore + * @return {{ + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }} + */ + toJSON(): { + lineNumber: number | undefined; + columnNumber: number | undefined; + sourceURL: string | null; + isNative: boolean; + }; + } + /** + * A stack frame container related to a `CallSite`. + */ + export class StackFrame { + /** + * Parses a raw stack frame string into structured data. + * @param {string} rawStackFrame + * @return {ParsedStackFrame} + */ + static parse(rawStackFrame: string): ParsedStackFrame; + /** + * Creates a new `StackFrame` from an `Error` and raw stack frame + * source `rawStackFrame`. + * @param {Error} error + * @param {string} rawStackFrame + * @return {StackFrame} + */ + static from(error: Error, rawStackFrame: string): StackFrame; + /** + * `StackFrame` class constructor. + * @param {Error} error + * @param {ParsedStackFrame=} [frame] + * @param {string=} [source] + */ + constructor(error: Error, frame?: ParsedStackFrame | undefined, source?: string | undefined); + /** + * The stack frame location data. + * @type {StackFrameLocation} + */ + location: StackFrameLocation; + /** + * The `Error` associated with this `StackFrame` instance. + * @type {Error?} + */ + error: Error | null; + /** + * The name of the function where the stack frame is located. + * @type {string?} + */ + symbol: string | null; + /** + * The raw stack frame source string. + * @type {string?} + */ + source: string | null; + /** + * Converts this `StackFrameLocation` to a JSON object. + * @ignore + * @return {{ + * location: { + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * }} + */ + toJSON(): { + location: { + lineNumber: number | undefined; + columnNumber: number | undefined; + sourceURL: string | null; + isNative: boolean; + }; + isNative: boolean; + symbol: string | null; + source: string | null; + error: { + message: string; + name: string; + stack: string; + } | null; + }; + } + /** + * A v8 compatible interface and container for call site information. + */ export class CallSite { + /** + * An internal symbol used to refer to the index of a promise in + * `Promise.all` or `Promise.any` function call site. + * @ignore + * @type {symbol} + */ static PromiseElementIndexSymbol: symbol; + /** + * An internal symbol used to indicate that a call site is in a `Promise.all` + * function call. + * @ignore + * @type {symbol} + */ static PromiseAllSymbol: symbol; + /** + * An internal symbol used to indicate that a call site is in a `Promise.any` + * function call. + * @ignore + * @type {symbol} + */ static PromiseAnySymbol: symbol; - constructor(error: any); - get error(): any; - getThis(): void; - getTypeName(): void; - getFunction(): void; - getFunctionName(): any; + /** + * An internal source symbol used to store the original `Error` stack source. + * @ignore + * @type {symbol} + */ + static StackSourceSymbol: symbol; + /** + * `CallSite` class constructor + * @param {Error} error + * @param {string} rawStackFrame + * @param {CallSite=} previous + */ + constructor(error: Error, rawStackFrame: string, previous?: CallSite | undefined); + /** + * The `Error` associated with the call site. + * @type {Error} + */ + get error(): Error; + /** + * The previous `CallSite` instance, if available. + * @type {CallSite?} + */ + get previous(): CallSite; + /** + * A reference to the `StackFrame` data. + * @type {StackFrame} + */ + get frame(): StackFrame; + /** + * This function _ALWAYS__ returns `globalThis` as `this` cannot be determined. + * @return {object} + */ + getThis(): object; + /** + * This function _ALWAYS__ returns `null` as the type name of `this` + * cannot be determined. + * @return {null} + */ + getTypeName(): null; + /** + * This function _ALWAYS__ returns `undefined` as the current function + * reference cannot be determined. + * @return {undefined} + */ + getFunction(): undefined; + /** + * Returns the name of the function in at the call site, if available. + * @return {string|undefined} + */ + getFunctionName(): string | undefined; + /** + * An alias to `getFunctionName() + * @return {string} + */ + getMethodName(): string; + /** + * Get the filename of the call site location, if available, otherwise this + * function returns 'unknown location'. + * @return {string} + */ getFileName(): string; - getLineNumber(): any; - getColumnNumber(): any; - getEvalOrigin(): void; - isTopLevel(): void; - isEval(): void; + /** + * Returns the location source URL defaulting to the global location. + * @return {string} + */ + getScriptNameOrSourceURL(): string; + /** + * Returns a hash value of the source URL return by `getScriptNameOrSourceURL()` + * @return {string} + */ + getScriptHash(): string; + /** + * Returns the line number of the call site location. + * This value may be `undefined`. + * @return {number|undefined} + */ + getLineNumber(): number | undefined; + /** + * @ignore + * @return {number} + */ + getPosition(): number; + /** + * Attempts to get an "enclosing" line number, potentially the previous + * line number of the call site + * @param {number|undefined} + */ + getEnclosingLineNumber(): any; + /** + * Returns the column number of the call site location. + * This value may be `undefined`. + * @return {number|undefined} + */ + getColumnNumber(): number | undefined; + /** + * Attempts to get an "enclosing" column number, potentially the previous + * line number of the call site + * @param {number|undefined} + */ + getEnclosingColumnNumber(): any; + /** + * Gets the origin of where `eval()` was called if this call site function + * originated from a call to `eval()`. This function may return `undefined`. + * @return {string|undefined} + */ + getEvalOrigin(): string | undefined; + /** + * This function _ALWAYS__ returns `false` as `this` cannot be determined so + * "top level" detection is not possible. + * @return {boolean} + */ + isTopLevel(): boolean; + /** + * Returns `true` if this call site originated from a call to `eval()`. + * @return {boolean} + */ + isEval(): boolean; + /** + * Returns `true` if the call site is in a native location, otherwise `false`. + * @return {boolean} + */ isNative(): boolean; - isConstructor(): void; + /** + * This function _ALWAYS_ returns `false` as constructor detection + * is not possible. + * @return {boolean} + */ + isConstructor(): boolean; + /** + * Returns `true` if the call site is in async context, otherwise `false`. + * @return {boolean} + */ isAsync(): boolean; - isPromiseAll(): any; + /** + * Returns `true` if the call site is in a `Promise.all()` function call, + * otherwise `false. + * @return {boolean} + */ + isPromiseAll(): boolean; + /** + * Gets the index of the promise element that was followed in a + * `Promise.all()` or `Promise.any()` function call. If not available, then + * this function returns `null`. + * @return {number|null} + */ + getPromiseIndex(): number | null; + /** + * Converts this call site to a string. + * @return {string} + */ + toString(): string; + /** + * Converts this `CallSite` to a JSON object. + * @ignore + * @return {{ + * frame: { + * location: { + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * } + * }} + */ + toJSON(): { + frame: { + location: { + lineNumber: number | undefined; + columnNumber: number | undefined; + sourceURL: string | null; + isNative: boolean; + }; + isNative: boolean; + symbol: string | null; + source: string | null; + error: { + message: string; + name: string; + stack: string; + } | null; + }; + }; + set [$previous](previous: any); + /** + * Private accessor to "friend class" `CallSiteList`. + * @ignore + */ + get [$previous](): any; + #private; + } + /** + * An array based list container for `CallSite` instances. + */ + export class CallSiteList extends Array { + /** + * Creates a `CallSiteList` instance from `Error` input. + * @param {Error} error + * @param {string} source + * @return {CallSiteList} + */ + static from(error: Error, source: string): CallSiteList; + /** + * `CallSiteList` class constructor. + * @param {Error} error + * @param {string[]=} [sources] + */ + constructor(error: Error, sources?: string[] | undefined); + /** + * A reference to the `Error` for this `CallSiteList` instance. + * @type {Error} + */ + get error(): Error; + /** + * An array of stack frame source strings. + * @type {string[]} + */ + get sources(): string[]; + /** + * The original stack string derived from the sources. + * @type {string} + */ + get stack(): string; + /** + * Adds `CallSite` instances to the top of the list, linking previous + * instances to the next one. + * @param {...CallSite} callsites + * @return {number} + */ + unshift(...callsites: CallSite[]): number; + /** + * A no-op function as `CallSite` instances cannot be added to the end + * of the list. + * @return {number} + */ + push(): number; + /** + * Pops a `CallSite` off the end of the list. + * @return {CallSite|undefined} + */ + pop(): CallSite | undefined; + /** + * Converts this `CallSiteList` to a JSON object. + * @return {{ + * frame: { + * location: { + * lineNumber: number | undefined, + * columnNumber: number | undefined, + * sourceURL: string | null, + * isNative: boolean + * }, + * isNative: boolean, + * symbol: string | null, + * source: string | null, + * error: { message: string, name: string, stack: string } | null + * } + * }[]} + */ + toJSON(): { + frame: { + location: { + lineNumber: number | undefined; + columnNumber: number | undefined; + sourceURL: string | null; + isNative: boolean; + }; + isNative: boolean; + symbol: string | null; + source: string | null; + error: { + message: string; + name: string; + stack: string; + } | null; + }; + }[]; #private; } export default CallSite; + export type ParsedStackFrame = { + sourceURL: string | null; + symbol: string; + column: number | undefined; + line: number | undefined; + native: boolean; + }; + const $previous: unique symbol; } declare module "socket:internal/error" { - export function Error(message: any, ...args: any[]): any; - export namespace Error { - let stackTraceLimit: number; - /** - * @ignore - */ - function captureStackTrace(err: any, ErrorConstructor: any): void; - } - export function Error(message: any, ...args: any[]): any; - export namespace Error { } - export function Error(message: any, ...args: any[]): any; - export namespace Error { } - export function Error(message: any, ...args: any[]): any; - export namespace Error { } - export function Error(message: any, ...args: any[]): any; - export namespace Error { } - export function Error(message: any, ...args: any[]): any; - export namespace Error { } - export function Error(message: any, ...args: any[]): any; - export namespace Error { } + /** + * The default `Error` class stack trace limit. + * @type {number} + */ + export const DEFAULT_ERROR_STACK_TRACE_LIMIT: number; + export const Error: ErrorConstructor; + export const URIError: ErrorConstructor; + export const EvalError: ErrorConstructor; + export const TypeError: ErrorConstructor; + export const RangeError: ErrorConstructor; + export const MediaError: ErrorConstructor; + export const SyntaxError: ErrorConstructor; + export const ReferenceError: ErrorConstructor; + export const AggregateError: ErrorConstructor; + export const RTCError: ErrorConstructor; + export const OverconstrainedError: ErrorConstructor; + export const GeolocationPositionError: ErrorConstructor; + export const ApplePayError: ErrorConstructor; namespace _default { export { Error }; + export { URIError }; + export { EvalError }; + export { TypeError }; + export { RangeError }; + export { MediaError }; + export { SyntaxError }; + export { ReferenceError }; + export { AggregateError }; + export { RTCError }; + export { OverconstrainedError }; + export { GeolocationPositionError }; + export { ApplePayError }; } export default _default; } @@ -14088,11 +14555,48 @@ declare module "socket:internal/promise" { declare module "socket:internal/streams" { export class ReadableStream extends globalThis.ReadableStream { constructor(options: any); + getReader(options: any): any; } + export const ReadableStreamBYOBReader: { + new (stream: globalThis.ReadableStream): ReadableStreamBYOBReader; + prototype: ReadableStreamBYOBReader; + } | { + new (): { + "__#91@#closed": Deferred; + "__#91@#cancellation": ReadableStreamBYOBReaderCancellation; + readonly closed: Promise; + cancel(reason?: any): Promise; + read(view: any): ReadableStreamBYOBReadResult; + }; + }; + export const ReadableStreamBYOBRequest: { + new (): ReadableStreamBYOBRequest; + prototype: ReadableStreamBYOBRequest; + } | { + new (): { + "__#92@#view": any; + readonly view: any; + respond(bytesWritten: any): void; + respondWithNewView(view: any): void; + }; + }; + export const ReadableByteStreamController: { + new (): {}; + }; namespace _default { export { ReadableStream }; } export default _default; + import { Deferred } from "socket:async/deferred"; + class ReadableStreamBYOBReaderCancellation { + reason: any; + state: boolean; + } + class ReadableStreamBYOBReadResult { + constructor(value?: any, done?: boolean); + value: any; + done: boolean; + } } declare module "socket:service-worker/registration" { From 8e3edfc387f08b5bb2fd6569038f2f20bfd5bb74 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 14:19:47 -0400 Subject: [PATCH 0465/1178] fix(api/commonjs/cache.js): fix shared cache --- api/commonjs/cache.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index 5857e3f265..29b4ba3c54 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -16,15 +16,15 @@ export const CACHE_CHANNEL_MESSAGE_REPLICATE = 'replicate' * instances in the application context, including windows and workers. */ export class Cache { - static data = new Map() static types = new Map() + static shared = Object.create(null) #onmessage = null #channel = null #loader = null #name = '' - #types = Cache.types - #data = Cache.data + #types = null + #data = null #id = null /** @@ -37,8 +37,14 @@ export class Cache { throw new TypeError(`Expecting 'name' to be a string. Received: ${name}`) } + if (!Cache.shared[name]) { + Cache.shared[name] = new Map() + } + this.#id = Math.random().toString(16).slice(2) this.#name = name + this.#data = Cache.shared[name] + this.#types = new Map(Cache.types.entries()) this.#loader = options?.loader ?? null this.#channel = new BroadcastChannel(`socket.runtime.commonjs.cache.${name}`) From e6616e0b8095a06364f54326223fdf0e9231e6a8 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 14:20:37 -0400 Subject: [PATCH 0466/1178] refactor(api/commonjs/require.js): allow opt-out cache --- api/commonjs/require.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/api/commonjs/require.js b/api/commonjs/require.js index c9af887097..173e1e0c69 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -37,6 +37,7 @@ import location from '../location.js' * @typedef {ResolveOptions & { * resolvers?: RequireResolver[], * importmap?: import('./module.js').ImportMap, + * cache?: boolean * }} RequireOptions */ @@ -201,8 +202,13 @@ export function createRequire (options) { }) }) - cache[resolved] = child - cache[input] = child + if (options?.cache === false) { + delete cache[resolved] + delete cache[input] + } else { + cache[resolved] = child + cache[input] = child + } if (child.load(options)) { return getDefaultExports(child.exports) From 8e2b8e9b6dcb88cd43498b3609f3f9a505134036 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 14:21:00 -0400 Subject: [PATCH 0467/1178] fix(api/commonjs/package.js): fix empty origin --- api/commonjs/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 590c4c4495..5e18a272ee 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -1012,7 +1012,7 @@ export class Package { } const url = new URL(id) - const prefix = new URL('.', origin).href + const prefix = new URL('.', origin || location.origin).href const pathname = `./${url.href.replace(prefix, '')}` if (info.browser && typeof info.browser === 'object') { From c1a1e7fd95787decb3fd85b701b88e2f3f38df72 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 29 Mar 2024 14:21:21 -0400 Subject: [PATCH 0468/1178] chore(api): generate types + docs --- api/index.d.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 62c3caab7d..00b43b70ec 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -11278,8 +11278,8 @@ declare module "socket:commonjs/cache" { * instances in the application context, including windows and workers. */ export class Cache { - static data: Map; static types: Map; + static shared: any; /** * `Cache` class constructor. * @param {string} name @@ -11380,7 +11380,7 @@ declare module "socket:commonjs/cache" { /** * @ignore */ - [Symbol.iterator](): IterableIterator<[any, any]>; + [Symbol.iterator](): any; #private; } export default Cache; @@ -13026,6 +13026,7 @@ declare module "socket:commonjs/require" { * @typedef {ResolveOptions & { * resolvers?: RequireResolver[], * importmap?: import('./module.js').ImportMap, + * cache?: boolean * }} RequireOptions */ /** @@ -13070,6 +13071,7 @@ declare module "socket:commonjs/require" { export type RequireOptions = ResolveOptions & { resolvers?: RequireResolver[]; importmap?: import("socket:commonjs/module").ImportMap; + cache?: boolean; }; } From 1f87e3c8559a67362572b708724d13b29fc5eb59 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 11:28:02 -0400 Subject: [PATCH 0469/1178] fix(api/commonjs/package.js): handle package loading without 'package.json' --- api/commonjs/package.js | 49 +++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 5e18a272ee..42db90f0df 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -232,9 +232,9 @@ export class Name { return { organization: null, - name, + name: name ?? pathname, version, - pathname, + pathname: name && pathname ? pathname : null, url, isRelative, hasManifest @@ -341,26 +341,37 @@ export class Name { */ get name () { return this.#name } + /** + * An alias for 'name'. + * @type {string} + */ + get value () { return this.#name } + /** * The origin of the package, if available. + * This value may be `null`. * @type {string?} */ get origin () { return this.#origin } /** * The package version if available. - * @type {string} + * This value may be `null`. + * @type {string?} */ get version () { return this.#version } /** - * The actual package pathname. + * The actual package pathname, if given in name string. + * This value is always a string defaulting to '.' if no path + * was given in name string. * @type {string} */ - get pathname () { return this.#pathname } + get pathname () { return this.#pathname || '.' } /** - * The organization name. This value may be `null`. + * The organization name. + * This value may be `null`. * @type {string?} */ get organization () { return this.#organization } @@ -698,7 +709,9 @@ export class Package { entry = `.${entry}` } - return new URL(entry, this.id).href + if (URL.canParse(entry, this.id)) { + return new URL(entry, this.id).href + } } return null @@ -731,6 +744,28 @@ export class Package { const response = this.loader.load(pathname, origin, options) if (!response.text) { + const entry = path.basename(this.entry ?? './index.js') + const pathname = `${prefix}${this.name}/${entry}` + const response = this.loader.load(pathname, origin, options) + + if (response.text) { + this.#id = response.id + this.#info = { + name: this.name.value, + main: entry, + module: entry + } + + if (/(import|export|export default|from)\s/.test(response.text)) { + this.#info.type = 'module' + } else { + this.#info.type = 'commonjs' + } + + this.#type = this.#info.type + return true + } + return false } From aa85066c5d8f340939e82e313563a5591a3cbffe Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 11:31:39 -0400 Subject: [PATCH 0470/1178] refactor(api/commonjs/require.js): handle 'options.origins' in 'require.resolve' --- api/commonjs/require.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 173e1e0c69..9ec6ece766 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -30,7 +30,11 @@ import location from '../location.js' */ /** - * @typedef {PackageResolveOptions & PackageOptions} ResolveOptions + * @typedef { + * PackageResolveOptions & + * PackageOptions & + * { origins?: string[] | URL[] } + * } ResolveOptions */ /** @@ -256,7 +260,11 @@ export function createRequire (options) { }) } - const origins = resolve.paths(input) + const origins = new Set([] + .concat(options?.origins) + .concat(resolve.paths(input)) + .filter(Boolean) + ) for (const origin of origins) { // relative require From 0416beb57b5d2ec2b5f0285b44c030296a714aaa Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 11:32:23 -0400 Subject: [PATCH 0471/1178] refactor(api/npm/module.js): use new 'Package' from 'socket:commonjs' --- api/npm/module.js | 248 +++++++++------------------------------------- 1 file changed, 46 insertions(+), 202 deletions(-) diff --git a/api/npm/module.js b/api/npm/module.js index 6969cc372e..ac00dcad6e 100644 --- a/api/npm/module.js +++ b/api/npm/module.js @@ -1,220 +1,64 @@ -import { Module, createRequire } from '../module.js' -import { MIMEType } from '../mime.js' +import { DEFAULT_PACKAGE_PREFIX, Package } from '../commonjs/package.js' import location from '../location.js' -import path from '../path.js' -export async function resolve (specifier, origin = null) { +/** + * @typedef {{ + * package: Package + * origin: string, + * type: 'commonjs' | 'module',, + * url: string + * }} ModuleResolution + */ + +/** + * Resolves an NPM module for a given `specifier` and an optional `origin`. + * @param {string|URL} specifier + * @param {string|URL=} [origin] + * @param {{ prefix?: string }} [options] + * @return {ModuleResolution|null} + */ +export async function resolve (specifier, origin = null, options = null) { + if (origin && typeof origin === 'object' && !(origin instanceof URL)) { + options = origin + origin = options.origin ?? null + } + if (!origin) { origin = location.origin } - const parts = specifier.split('/') - const prefix = './node_modules' - const pathname = parts.slice(1).join('/').trim() - const moduleName = parts[0] - let packageJSON = null - let resolved = null - - const require = createRequire(origin, { - headers: { - 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' - } - }) - - try { - packageJSON = require(`${prefix}/${moduleName}/package.json`) - } catch { - try { - require(`${prefix}/${moduleName}/index.js`) - packageJSON = { - type: 'commonjs', - main: `${prefix}/${moduleName}/index.js` - } - } catch (err) { - if ( - (err.name === 'SyntaxError' && err.message.includes('import call')) || - (err.name === 'SyntaxError' && /unexpected keyword '(export|import|default|from)'/i.test(err.message)) || - (err.message.includes('import.meta')) - ) { - return { - origin, - type: 'module', - path: `${prefix}/${moduleName}/index.js` - } - } else { - throw err - } - } + if (!origin.endsWith('/')) { + origin += '/' } - // top level import - if (pathname.length === 0) { - // legacy - if (packageJSON.module) { - resolved = { - type: 'module', - path: path.join(`${prefix}/${moduleName}`, packageJSON.module) - } - } - - // simple - if (typeof packageJSON.exports === 'string') { - resolved = { - type: packageJSON.type, - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports) - } - } - - // exports object - if (packageJSON.exports && typeof packageJSON.exports === 'object') { - if (typeof packageJSON.exports.import === 'string') { - resolved = { - type: 'module', - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports.import) - } - } else if (typeof packageJSON.exports.require === 'string') { - resolved = { - type: 'commonjs', - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports.require) - } - } else if (typeof packageJSON.exports['.'] === 'string') { - resolved = { - type: packageJSON.type, - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports['.']) - } - } else if (typeof packageJSON.exports['.']?.import === 'string') { - resolved = { - type: 'module', - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports['.'].import) - } - } else if (typeof packageJSON.exports['.']?.require === 'string') { - resolved = { - type: 'commonjs', - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports['.'].require) - } - } - } else if (typeof packageJSON.main === 'string') { - resolved = { - type: 'commonjs', - path: path.join(`${prefix}/${moduleName}`, packageJSON.main) - } - } else { - resolved = { - type: packageJSON.type ?? 'commonjs', - path: path.join(`${prefix}/${moduleName}`, 'index.js') - } - } + // just use `specifier` to derive name and origin if it was a URL + if (specifier instanceof URL) { + origin = specifier.origin + specifier = specifier.pathname.slice(1) + specifier.search } - if (!resolved && pathname.length) { - // no exports, just join paths - if (!packageJSON.exports || typeof packageJSON.exports === 'string') { - resolved = { - type: packageJSON.type ?? 'commonjs', - path: path.join(`${prefix}/${moduleName}`, pathname) - } - } else if (packageJSON.exports && typeof packageJSON.exports === 'object') { - const extensions = [ - '', - '.js', - '.mjs', - '.cjs', - '.json' - ] + const prefix = options?.prefix ?? DEFAULT_PACKAGE_PREFIX + const name = Package.Name.from(specifier, { origin }) + const pkg = new Package(name.value, { + loader: { origin } + }) - do { - const name = `./${pathname}` + extensions.shift() - if (typeof packageJSON.exports[name] === 'string') { - resolved = { - type: packageJSON.type ?? 'commonjs', - path: path.join(`${prefix}/${moduleName}`, packageJSON.exports[name]) - } - break - } else if (packageJSON.exports[name] && typeof packageJSON.exports[name] === 'object') { - const exports = packageJSON.exports[name] - if (packageJSON.type === 'module' && exports.import) { - resolved = { - type: 'module', - path: path.join(`${prefix}/${moduleName}`, exports.import) - } - break - } else if (exports.require) { - resolved = { - type: 'commonjs', - path: path.join(`${prefix}/${moduleName}`, exports.require) - } + const pathname = name.pathname.replace(name.value, '.') || '.' - break - } - } - } while (extensions.length) + try { + return { + package: pkg, + origin: pkg.origin, + type: pkg.type, + url: pkg.resolve(pathname, { prefix }) + } + } catch (err) { + if (err?.code === 'MODULE_NOT_FOUND') { + return null } - } - - if (!resolved) { - const filename = path.join(`${prefix}/${moduleName}`, pathname) - try { - const module = Module.from(filename, Module.main) - const loaded = module.load({ - evaluate: false, - headers: { - 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' - } - }) - - if (loaded) { - resolved = { - type: 'commonjs', - path: new URL(module.id).pathname - } - } - } catch {} - } - - if (!resolved) { - return null - } - - if (resolved.path.endsWith('/')) { - resolved.path += 'index.js' - } - - if (!path.extname(resolved.path)) { - const extensions = [ - '.js', - '.mjs', - '.cjs', - '.json', - '' // maybe an extension was omitted for the actual file, try that _last_ - ] - - do { - resolved.path = resolved.path + extensions.shift() - - try { - const response = await fetch(new URL(resolved.path, origin)) - - if (response.ok) { - const { essence } = new MIMEType(response.headers.get('content-type')) - const contentLength = parseInt(response.headers.get('content-length')) - if (contentLength > 0) { - if (essence === 'text/javascript' || essence === 'application/javascript') { - break - } - } - } - } catch { - // ignore - } - if (extensions.length === 0) { - return null - } - } while (extensions.length) + throw err } - - resolved.origin = origin - return resolved } export default { From d7390fb4e4b4d6ae3197bea5bf64e8e7d6259bd2 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 11:32:46 -0400 Subject: [PATCH 0472/1178] refactor(api/npm/service-worker.js): simplify package resolution --- api/npm/service-worker.js | 56 ++++++++++++++------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index d286738766..9b60af776b 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -1,6 +1,5 @@ -import { createRequire } from '../module.js' +import { resolve } from './module.js' import process from '../process.js' -import module from './module.js' import http from '../http.js' import util from '../util.js' @@ -43,15 +42,13 @@ export async function onRequest (request, env, ctx) { const origin = origins.shift() if (origin.startsWith('npm:')) { - const potentialSpeciifier = new URL(origin).pathname + const potentialSpecifier = new URL(origin).pathname for (const potentialOrigin of origins) { - try { - const resolution = await module.resolve(potentialSpeciifier, potentialOrigin) - if (resolution) { - potentialOrigins.push(new URL(resolution.path, resolution.origin).href) - break - } - } catch { } + const resolution = await resolve(potentialSpecifier, potentialOrigin) + if (resolution) { + potentialOrigins.push(resolution.url) + break + } } } else { potentialOrigins.push(origin) @@ -59,14 +56,7 @@ export async function onRequest (request, env, ctx) { while (potentialOrigins.length && resolved === null) { const importOrigin = new URL('./', potentialOrigins.shift()).href - try { - resolved = await module.resolve(specifier, importOrigin) - } catch (err) { - globalThis.reportError(err) - return new Response(util.inspect(err), { - status: 500 - }) - } + resolved = await resolve(specifier, importOrigin) } } @@ -78,20 +68,18 @@ export async function onRequest (request, env, ctx) { return } - const source = new URL(resolved.path, resolved.origin) - if (resolved.type === 'module') { - const response = await fetch(source.href) + const response = await fetch(resolved.url) const text = await response.text() const proxy = text.includes('export default') ? ` - import module from '${source.href}' - export * from '${source.href}' + import module from '${resolved.url}' + export * from '${resolved.url}' export default module ` : ` - import * as module from '${source.href}' - export * from '${source.href}' + import * as module from '${resolved.url}' + export * from '${resolved.url}' export default module ` @@ -103,19 +91,11 @@ export async function onRequest (request, env, ctx) { } if (resolved.type === 'commonjs') { - const pathspec = (source.pathname + source.search).replace(/^\/node_modules\//, '') - console.log({ origin: source.origin, pathspec }) const proxy = ` - import { createRequire } from 'socket:module' - const require = createRequire('${source.origin + '/'}', { - headers: { - 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' - } - }) - const exports = require('${pathspec}') - console.log('${pathspec}', exports) + import { Module } from 'socket:module' + const exports = Module.main.require('${resolved.url}') export default exports?.default ?? exports ?? null - ` + ` return new Response(proxy, { headers: { @@ -147,5 +127,9 @@ export default async function (request, env, ctx) { if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { console.debug(err) } + + return new Response(util.inspect(err), { + status: 500 + }) } } From f620fbf5ec4f64590c28aeec896d2d30de4ce3cd Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 11:32:57 -0400 Subject: [PATCH 0473/1178] chore(api/path/path.js): clean up lint --- api/path/path.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/path/path.js b/api/path/path.js index ab9250d84f..01ad70a57c 100644 --- a/api/path/path.js +++ b/api/path/path.js @@ -173,7 +173,6 @@ export function join (options, ...components) { const isAbsolute = components[0].trim().startsWith(sep) let protocol = null - let hostname = null let origin = null while (components.length) { From 7118197f7f1359f8c1a0b80d431d19cac8d4f2b1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 11:33:19 -0400 Subject: [PATCH 0474/1178] chore(api): generate types + docs --- api/README.md | 42 +++++++++++++++++----------------- api/index.d.ts | 61 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/api/README.md b/api/README.md index 55c20ed740..1243f5dd41 100644 --- a/api/README.md +++ b/api/README.md @@ -1831,7 +1831,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L229) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L228) Computes directory name of path. @@ -1844,7 +1844,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L271) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L270) Computes base name of path. @@ -1857,7 +1857,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L285) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L284) Computes extension name of path. @@ -1870,7 +1870,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L296) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L295) Computes normalized path @@ -1883,7 +1883,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L346) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L345) Formats `Path` object into a string. @@ -1896,7 +1896,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L362) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L361) Parses input `path` into a `Path` instance. @@ -1908,11 +1908,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L390) +## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L389) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L396) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L395) Creates a `Path` instance from `input` and optional `cwd`. @@ -1921,7 +1921,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L419) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L418) `Path` class constructor. @@ -1930,47 +1930,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L492) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L491) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L499) +### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L498) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L533) +### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L532) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L541) +### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L540) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L560) +### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L559) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L581) +### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L580) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L616) +### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L615) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L628) +### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L627) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L636) +### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L635) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L656) +### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L655) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L663) +### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L662) @@ -1978,7 +1978,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L671) +### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L670) Converts this `Path` instance to a string. diff --git a/api/index.d.ts b/api/index.d.ts index 00b43b70ec..b0d48d059f 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -11896,23 +11896,33 @@ declare module "socket:commonjs/package" { * @type {string} */ get name(): string; + /** + * An alias for 'name'. + * @type {string} + */ + get value(): string; /** * The origin of the package, if available. + * This value may be `null`. * @type {string?} */ get origin(): string; /** * The package version if available. - * @type {string} + * This value may be `null`. + * @type {string?} */ get version(): string; /** - * The actual package pathname. + * The actual package pathname, if given in name string. + * This value is always a string defaulting to '.' if no path + * was given in name string. * @type {string} */ get pathname(): string; /** - * The organization name. This value may be `null`. + * The organization name. + * This value may be `null`. * @type {string?} */ get organization(): string; @@ -13020,7 +13030,11 @@ declare module "socket:commonjs/require" { * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions */ /** - * @typedef {PackageResolveOptions & PackageOptions} ResolveOptions + * @typedef { + * PackageResolveOptions & + * PackageOptions & + * { origins?: string[] | URL[] } + * } ResolveOptions */ /** * @typedef {ResolveOptions & { @@ -13067,7 +13081,6 @@ declare module "socket:commonjs/require" { export type RequireFunction = (arg0: string) => any; export type PackageOptions = import("socket:commonjs/package").PackageOptions; export type PackageResolveOptions = import("socket:commonjs/package").PackageResolveOptions; - export type ResolveOptions = PackageResolveOptions & PackageOptions; export type RequireOptions = ResolveOptions & { resolvers?: RequireResolver[]; importmap?: import("socket:commonjs/module").ImportMap; @@ -14878,19 +14891,39 @@ declare module "socket:internal/worker" { } declare module "socket:npm/module" { - export function resolve(specifier: any, origin?: any): Promise<{ - origin: any; - type: string; - path: string; - } | { - type: any; - path: string; - origin?: undefined; - }>; + /** + * @typedef {{ + * package: Package + * origin: string, + * type: 'commonjs' | 'module',, + * url: string + * }} ModuleResolution + */ + /** + * Resolves an NPM module for a given `specifier` and an optional `origin`. + * @param {string|URL} specifier + * @param {string|URL=} [origin] + * @param {{ prefix?: string }} [options] + * @return {ModuleResolution|null} + */ + export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { + prefix?: string; + }): ModuleResolution | null; + /** + * , + * url: string + * }} ModuleResolution + */ + export type resolve = { + package: Package; + origin: string; + type: 'commonjs' | 'module'; + }; namespace _default { export { resolve }; } export default _default; + import { Package } from "socket:commonjs/package"; } declare module "socket:npm/service-worker" { From 4d8a17ca05438b934a083416a3404af3e3d7a889 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:13:33 -0400 Subject: [PATCH 0475/1178] fix(api/commonjs/builtins.js): handle special default exports --- api/commonjs/builtins.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index 0c7020bcb3..8e4c584704 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -17,12 +17,12 @@ import * as assert from '../assert.js' import * as buffer from '../buffer.js' // eslint-disable-next-line import * as child_process from '../child_process.js' -import * as console from '../console.js' +import console from '../console.js' import * as constants from '../constants.js' import * as crypto from '../crypto.js' import * as dgram from '../dgram.js' import * as dns from '../dns.js' -import * as events from '../events.js' +import events from '../events.js' import * as extension from '../extension.js' import * as fs from '../fs.js' import * as gc from '../gc.js' @@ -63,8 +63,12 @@ export const builtins = {} * @param {object} exports */ export function define (name, exports, copy = true) { - if (copy) { - builtins[name] = { ...exports } + if (exports && typeof exports === 'object') { + if (copy) { + builtins[name] = { ...exports } + } else { + builtins[name] = exports + } } else { builtins[name] = exports } @@ -89,7 +93,7 @@ define('async_hooks', { define('application', application) define('assert', assert, false) define('buffer', buffer, false) -define('console', console) +define('console', console, false) define('constants', constants) // eslint-disable-next-line define('child_process', child_process) From 2c3f3ede31e7e3d98c0d313690559f55ddc72921 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:13:46 -0400 Subject: [PATCH 0476/1178] chore(api/commonjs/cache.js): clean up --- api/commonjs/cache.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index 29b4ba3c54..e9d109f452 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -271,7 +271,6 @@ export class Cache { return { args: [this.#data, this.#channel, this.#onmessage], handle (data, channel, onmessage) { - console.log('gc') data.clear() channel.removeEventListener('message', onmessage) } From 6c9ddbf72561851b57ae95cef0428f235d15deb3 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:14:01 -0400 Subject: [PATCH 0477/1178] refactor(api/commonjs/loader.js): support default headers --- api/commonjs/loader.js | 57 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 69619b81a1..102bc17278 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -17,7 +17,8 @@ const textDecoder = new TextDecoder() * extensions?: string[] | Set * origin?: URL | string, * statuses?: Cache - * cache?: { response?: Cache, status?: Cache } + * cache?: { response?: Cache, status?: Cache }, + * headers?: Headers | Map | object | string[][] * }} LoaderOptions */ @@ -198,13 +199,20 @@ export class RequestStatus { request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') } + if (this.#request?.loader) { + const entries = this.#request.loader.headers.entries() + for (const entry of entries) { + request.setRequestHeader(...entry) + } + } + if (options?.headers && typeof options?.headers === 'object') { const entries = typeof options.headers.entries === 'function' ? options.headers.entries() : Object.entries(options.headers) for (const entry of entries) { - request.setRequestHeader(entry[0], entry[1]) + request.setRequestHeader(...entry) } } @@ -403,13 +411,20 @@ export class Request { request.responseType = options.responseType } + if (this.#loader) { + const entries = this.#loader.headers.entries() + for (const entry of entries) { + request.setRequestHeader(...entry) + } + } + if (options?.headers && typeof options?.headers === 'object') { const entries = typeof options.headers.entries === 'function' ? options.headers.entries() : Object.entries(options.headers) for (const entry of entries) { - request.setRequestHeader(entry[0], entry[1]) + request.setRequestHeader(...entry) } } @@ -697,6 +712,7 @@ export class Loader { } #origin = null + #headers = new Headers() #extensions = Loader.defaultExtensions /** @@ -712,6 +728,22 @@ export class Loader { this.#origin = Loader.resolve('.', origin) + if (options?.headers && typeof options.headers === 'object') { + if (Array.isArray(options.headers)) { + for (const entry of options.headers) { + this.#headers.set(...entry) + } + } else if (typeof options.headers.entries === 'function') { + for (const entry of options.headers.entries()) { + this.#headers.set(...entry) + } + } else { + for (const key in options.headers) { + this.#headers.set(key, options.headers[key]) + } + } + } + this.#cache.response = options?.cache?.response instanceof Cache ? options.cache.response : new Cache('loader.response', { loader: this, types: { Response } }) @@ -738,6 +770,14 @@ export class Loader { return this.#cache } + /** + * Headers used in too loader requests. + * @type {Headers} + */ + get headers () { + return this.#headers + } + /** * A set of supported `Loader` extensions. * @type {Set} @@ -766,7 +806,7 @@ export class Loader { load (url, origin, options) { if (origin && typeof origin === 'object' && !(origin instanceof URL)) { options = origin - origin = this.origin + origin = options.origin ?? this.origin } if (!origin) { @@ -792,7 +832,7 @@ export class Loader { status (url, origin, options = null) { if (origin && typeof origin === 'object' && !(origin instanceof URL)) { options = origin - origin = this.origin + origin = options.origin ?? this.origin } if (!origin) { @@ -824,6 +864,13 @@ export class Loader { resolve (url, origin) { return Loader.resolve(url, origin || this.origin) } + + /** + * @ignore + */ + [Symbol.for('socket.util.inspect.custom')] () { + return `Loader ('${this.origin}') { }` + } } export default Loader From 31395330c1a0ff39c549b1fb71419e8c046ae40b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:14:29 -0400 Subject: [PATCH 0478/1178] refactor(api/commonjs/module.js): handle subpath imports and browser mappings --- api/commonjs/module.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/api/commonjs/module.js b/api/commonjs/module.js index 9a5333cc0c..ea1778cd32 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -570,9 +570,7 @@ export class Module extends EventTarget { this.#state = options.state } - this.#loader = options?.loader instanceof Loader - ? options.loader - : new Loader(this.#id, options?.loader) + this.#loader = new Loader(this.#id, options?.loader) this.#package = options.package instanceof Package ? options.package @@ -610,6 +608,20 @@ export class Module extends EventTarget { } } + // includes `.browser` field mapping + for (const key in this.package.imports) { + const value = this.package.imports[key] + if (value) { + this.#resolvers.push((specifier, ctx, next) => { + if (specifier === key) { + return value.default ?? value.browser ?? next(specifier) + } + + return next(specifier) + }) + } + } + if (this.#parent) { Object.assign(this.#loaders, this.#parent.loaders) @@ -822,6 +834,10 @@ export class Module extends EventTarget { load (options = null) { const extension = path.extname(this.id) + if (this.#state.loaded) { + return true + } + if (typeof this.#loaders[extension] !== 'function') { return false } @@ -860,6 +876,12 @@ export class Module extends EventTarget { } } +/** + * Creates a `require` function from a given module URL. + * @param {string|URL} url + * @param {ModuleOptions=} [options] + * @return {RequireFunction} + */ export function createRequire (url, options = null) { return Module.createRequire(url, options) } From 0e63e4f2109283737a0b63a9de975fa435a9566f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:15:52 -0400 Subject: [PATCH 0479/1178] fix(api/commonjs/package.js): fail on node addon, handle package imports and browering mappings --- api/commonjs/package.js | 147 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 42db90f0df..d23c4e45af 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -13,6 +13,19 @@ import path from '../path.js' */ const isWorkerScope = globalThis.self === globalThis && !globalThis.window +/** + * @ignore + * @param {string} source + * @return {boolean} + */ +function detectESM (source) { + if (/(import\s|export[{|\s]|export\sdefault|(from\s['|"]))\s/.test(source)) { + return true + } + + return false +} + /** * @typedef {{ * manifest?: string, @@ -485,6 +498,18 @@ export class Package { */ static Dependencies = Dependencies + /** + * Creates and loads a package + * @param {string|URL|NameOptions|Name} name + * @param {PackageOptions & PackageLoadOptions=} [options] + * @return {Package} + */ + static load (name, options = null) { + const pkg = new this(name, options) + pkg.load(options) + return pkg + } + #id = null #name = null #type = 'commonjs' @@ -496,6 +521,8 @@ export class Package { #info = null #loader = null + #imports = {} + #exports = { '.': { require: './index.js', @@ -517,9 +544,7 @@ export class Package { } // the module loader - this.#loader = options.loader instanceof Loader - ? options.loader - : new Loader(options.loader) + this.#loader = new Loader(options.loader) this.#id = options.id ?? null this.#name = Name.from(name, { @@ -529,6 +554,9 @@ export class Package { // early meta data this.#info = options.info ?? null + this.#type = options.type && /(commonjs|module)/.test(options.type) + ? options.type + : this.#type this.#exports = options.exports ?? this.#exports this.#license = options.license ?? DEFAULT_LICENSE this.#version = options.version ?? this.#name.version ?? DEFAULT_PACKAGE_VERSION @@ -580,6 +608,16 @@ export class Package { return new URL(this.#id).href } + /** + * A reference to the package subpath imports and browser mappings. + * These values are typically used with its corresponding `Module` + * instance require resolvers. + * @type {object} + */ + get imports () { + return this.#imports + } + /** * A loader for this package, if available. This value may be `null`. * @type {Loader} @@ -588,6 +626,14 @@ export class Package { return this.#loader } + /** + * `true` if the package was actually "loaded", otherwise `false`. + * @type {boolean} + */ + get loaded () { + return this.#info !== null + } + /** * The name of the package. * @type {string} @@ -730,8 +776,10 @@ export class Package { origin = options.origin } - if (options?.force !== true && this.#info) { - return true + if (!this.origin || origin === this.origin) { + if (options?.force !== true && this.#info) { + return true + } } if (!origin) { @@ -756,7 +804,7 @@ export class Package { module: entry } - if (/(import|export|export default|from)\s/.test(response.text)) { + if (detectESM(response.text)) { this.#info.type = 'module' } else { this.#info.type = 'commonjs' @@ -786,6 +834,8 @@ export class Package { this.#version = info.version this.#description = info.description + this.#loader.origin = origin + if (info.dependencies && typeof info.dependencies === 'object') { for (const name in info.dependencies) { const version = info.dependencies[name] @@ -799,7 +849,9 @@ export class Package { if (info.main) { this.#exports['.'].require = info.main - this.#exports['.'].import = info.main + if (info.type === 'module') { + this.#exports['.'].import = info.main + } } if (info.module) { @@ -871,6 +923,67 @@ export class Package { } } + if ( + this.#info.imports && + !Array.isArray(this.#info.imports) && + typeof this.#info.imports === 'object' + ) { + for (const key in this.#info.imports) { + const value = this.#info.imports[key] + if (typeof value === 'string') { + this.#imports[key] = { default: value } + } else if (value && typeof value === 'object') { + this.#imports[key] = {} + if (value.default) { + this.#imports[key].default = value.default + } + + if (value.browser) { + this.#imports[key].browser = value.browser + } + } + } + } + + if ( + this.#info.browser && + !Array.isArray(this.#info.browser) && + typeof this.#info.browser === 'object' + ) { + for (const key in this.#info.browser) { + const value = this.#info.browser[key] + + if (typeof value === 'string') { + if (key.startsWith('.')) { + if (this.#exports[key]) { + this.#exports[key].browser = value + } + } else { + this.#imports[key] ??= { } + this.#imports[key].browser = value + } + } else if (value && typeof value === 'object') { + this.#imports[key] ??= {} + if (value.default) { + this.#imports[key].default = value.default + } + + if (value.browser) { + this.#imports[key].browser = value.browser + } + } + } + } + + if (this.#type === 'module') { + if (this.#info.type !== 'module' && this.entry) { + const source = this.loader.load(this.entry, origin, options).text + if (!detectESM(source)) { + this.#type = 'commonjs' + } + } + } + return true } @@ -890,6 +1003,13 @@ export class Package { const extname = path.extname(pathname) const type = options?.type ?? this.type + if (info?.addon === true) { + throw new ModuleNotFoundError( + `Cannot find module '${pathname}' (requested module is a Node.js addon)`, + options?.children?.map?.((mod) => mod.id) + ) + } + let origin = this.id // an absolute URL was given, just try to resolve it @@ -1063,6 +1183,19 @@ export class Package { return id } } + + /** + * @ignore + */ + [Symbol.for('socket.util.inspect.custom')] () { + if (this.name && this.version) { + return `Package '(${this.name}@${this.version}') { }` + } else if (this.name) { + return `Package ('${this.name}') { }` + } else { + return 'Package { }' + } + } } export default Package From 08ff8b7c60ea202a8c02ff49093375f54667c5cd Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:16:34 -0400 Subject: [PATCH 0480/1178] fix(api/commonjs/require.js): fix missing headers, fix package construction and resolutions --- api/commonjs/require.js | 44 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 9ec6ece766..e0335ea1c1 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -1,7 +1,6 @@ import { DEFAULT_PACKAGE_PREFIX, Package } from './package.js' import { getBuiltin, isBuiltin } from './builtins.js' import { ModuleNotFoundError } from '../errors.js' -import { Loader } from './loader.js' import location from '../location.js' /** @@ -93,7 +92,7 @@ export class Meta { * @return {RequireFunction} */ export function createRequire (options) { - const { builtins, module } = options + const { builtins, headers, module, prefix } = options const { cache, loaders, main } = module // non-standard 'require.meta' object @@ -183,10 +182,6 @@ export function createRequire (options) { return getBuiltin(input, { builtins: options?.builtins ?? builtins }) } - if (cache[input]) { - return getDefaultExports(cache[input].exports) - } - const resolved = resolve(input, { type: module.package.type, ...options @@ -196,22 +191,31 @@ export function createRequire (options) { return getDefaultExports(cache[resolved].exports) } - const child = module.createModule(resolved, { - ...options, - package: input.startsWith('.') || input.startsWith('/') - ? module.package - : new Package(resolved, { - loader: new Loader(module.loader), + let child = null + + if (input.startsWith('.') || input.startsWith('/')) { + child = module.createModule(resolved, { + ...options, + loader: { headers, origin: module.package.loader.origin }, + package: module.package + }) + } else { + const origin = new URL('..', module.package.loader.origin) + child = module.createModule(resolved, { + loader: { headers, origin }, + ...options, + package: Package.load(input, { + loader: { headers, origin }, + prefix, ...options }) - }) + }) + } if (options?.cache === false) { delete cache[resolved] - delete cache[input] } else { cache[resolved] = child - cache[input] = child } if (child.load(options)) { @@ -273,12 +277,12 @@ export function createRequire (options) { } else { // named module const moduleName = Package.Name.from(input) const pathname = moduleName.pathname.replace(moduleName.name, '.') - const manifest = new Package(moduleName.name, { - loader: new Loader(origin) + const pkg = new Package(moduleName.name, { + loader: { headers, origin } }) try { - return manifest.resolve(pathname, { + return pkg.resolve(pathname, { type: module.package.type, ...options }) @@ -330,9 +334,9 @@ export function createRequire (options) { const results = Array .from(origins) - .map((origin) => origin.endsWith(options.prefix) + .map((origin) => origin.endsWith(prefix) ? new URL(origin) - : new URL(options.prefix, origin) + : new URL(prefix, origin) ) .map((url) => url.href) From 8d0d39bcfbe5567f41cf2fde18968f292a3f6a58 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:16:52 -0400 Subject: [PATCH 0481/1178] refactor(api/console.js): make 'Console' constructor available on default exports --- api/console.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/console.js b/api/console.js index 733ab4e979..7dee690660 100644 --- a/api/console.js +++ b/api/console.js @@ -430,7 +430,10 @@ export function patchGlobalConsole (globalConsole, options = {}) { return globalConsole } -export default new Console({ +export default Object.assign(new Console({ postMessage, console: patchGlobalConsole(globalConsole) +}), { + Console, + globalConsole }) From aaa72d68c20ec25b1f5329622591c965f51ccae4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:17:08 -0400 Subject: [PATCH 0482/1178] fix(api/internal/async/hooks.js): fix typo --- api/internal/async/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/async/hooks.js b/api/internal/async/hooks.js index 440d8d4c74..632a9f69dd 100644 --- a/api/internal/async/hooks.js +++ b/api/internal/async/hooks.js @@ -268,7 +268,7 @@ export function getTopLevelAsyncResourceName () { const { type, frameType } = globalThis.__args.client return ( frameType.replace('none', '').split('-').filter(Boolean).map(toProperCase).join('') + - toProperCase(type.repl) + toProperCase(type) ) } From c5c99cd06d1b149a1f1249ae911c9dad0c0589a8 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:17:33 -0400 Subject: [PATCH 0483/1178] refactor(api/internal/worker.js): move init into runtime worker preload --- api/internal/worker.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/internal/worker.js b/api/internal/worker.js index b11b0c5f82..ef6d1d367b 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -1,6 +1,4 @@ /* global reportError, EventTarget, CustomEvent, MessageEvent */ -import './init.js' - import { rand64 } from '../crypto.js' import globals from './globals.js' import hooks from '../hooks.js' From 01f288746c2ec9eb3c7b752751ff3947bfd1fa18 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:17:56 -0400 Subject: [PATCH 0484/1178] refactor(api/internal/init.js): use async module in worker preload, make 'load' event sync --- api/internal/init.js | 56 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 7fbb26ada6..500523828d 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -212,21 +212,35 @@ if ((globalThis.window) === globalThis) { } class RuntimeWorker extends GlobalWorker { - #onglobaldata = null - #id = null - + /** + * Internal worker pool + * @ignore + */ static pool = globalThis.top?.Worker?.pool ?? new Map() + /** + * Handles `Symbol.species` + * @ignore + */ static get [Symbol.species] () { return GlobalWorker } + #id = null + #objectURL = null + #onglobaldata = null + /** * `RuntimeWorker` class worker. * @ignore + * @param {string|URL} filename + * @param {object=} [options] */ constructor (filename, options, ...args) { - const url = encodeURIComponent(new URL(filename, globalThis.location.href || '/').toString()) + options = { ...options } + + const workerType = options[Symbol.for('socket.runtime.internal.worker.type')] ?? 'worker' + const url = encodeURIComponent(new URL(filename, location.href || '/').toString()) const id = String(rand64()) const preload = ` @@ -246,9 +260,16 @@ class RuntimeWorker extends GlobalWorker { value: '${id}' }) + Object.defineProperty(globalThis, 'RUNTIME_WORKER_TYPE', { + configurable: false, + enumerable: false, + value: '${workerType}' + }) + Object.defineProperty(globalThis, 'RUNTIME_WORKER_LOCATION', { configurable: false, enumerable: false, + writable: true, value: decodeURIComponent('${url}') }) @@ -264,12 +285,18 @@ class RuntimeWorker extends GlobalWorker { RUNTIME_WORKER_MESSAGE_EVENT_BACKLOG.push(event) } - import '${globalThis.location.protocol}//${globalThis.location.hostname}/socket/internal/worker.js?source=${url}' - import hooks from 'socket:hooks' + try { + await import('${globalThis.location.protocol}//${globalThis.location.hostname}/socket/internal/init.js') + const hooks = await import('${globalThis.location.protocol}//${globalThis.location.hostname}/socket/hooks.js') - hooks.onReady(() => { - globalThis.removeEventListener('message', onInitialWorkerMessages) - }) + hooks.onReady(() => { + globalThis.removeEventListener('message', onInitialWorkerMessages) + }) + + await import('${globalThis.location.protocol}//${globalThis.location.hostname}/socket/internal/worker.js?source=${url}') + } catch (err) { + globalThis.reportError(err) + } `.trim() const objectURL = URL.createObjectURL( @@ -287,6 +314,7 @@ class RuntimeWorker extends GlobalWorker { RuntimeWorker.pool.set(id, new WeakRef(this)) this.#id = id + this.#objectURL = objectURL this.#onglobaldata = (event) => { const data = new Uint8Array(event.detail.data).buffer @@ -383,7 +411,7 @@ class RuntimeWorker extends GlobalWorker { } }, { transfer }) } catch (err) { - console.warn('RuntimeWorker:', err) + globalThis.reportError(err) } }) } @@ -398,6 +426,10 @@ class RuntimeWorker extends GlobalWorker { return this.#id } + get objectURL () { + return this.#objectURL + } + terminate () { globalThis.removeEventListener('data', this.#onglobaldata) return super.terminate() @@ -491,10 +523,10 @@ import { config } from '../application.js' import globals from './globals.js' import '../console.js' -ipc.send('platform.event', { +ipc.sendSync('platform.event', { value: 'load', 'location.href': globalThis.location.href -}).catch(reportError) +}) class ConcurrentQueue extends EventTarget { concurrency = Infinity From 50c2837b1e90f669ef920ad9055d6e6a4f929497 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:19:06 -0400 Subject: [PATCH 0485/1178] refactor(api/ipc.js): improve worker metadara --- api/ipc.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/ipc.js b/api/ipc.js index 2342d17837..981b773fa9 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1044,6 +1044,10 @@ class IPCSearchParams extends URLSearchParams { this.set('runtime-worker-id', globalThis.RUNTIME_WORKER_ID) } + if (globalThis.RUNTIME_WORKER_LOCATION) { + this.set('runtime-worker-location', globalThis.RUNTIME_WORKER_LOCATION) + } + const runtimeFrameSource = globalThis.document ? globalThis.document.querySelector('meta[name=runtime-frame-source]')?.content : '' @@ -1052,7 +1056,11 @@ class IPCSearchParams extends URLSearchParams { this.set('runtime-frame-type', 'nested') } else if (!globalThis.window && globalThis.self === globalThis) { this.set('runtime-frame-type', 'worker') - if (globalThis.clients && globalThis.FetchEvent) { + if ( + globalThis.isServiceWorkerScope || + (globalThis.clients && globalThis.FetchEvent) || + globalThis.RUNTIME_WORKER_TYPE === 'serviceWorker' + ) { this.set('runtime-worker-type', 'serviceworker') } else { this.set('runtime-worker-type', 'worker') From d3e81324958e18124ecc3b16332061252db938e3 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:19:21 -0400 Subject: [PATCH 0486/1178] refactor(api/npm/module.js): resolve 'url' in try/catch --- api/npm/module.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/api/npm/module.js b/api/npm/module.js index ac00dcad6e..8f1b71cd93 100644 --- a/api/npm/module.js +++ b/api/npm/module.js @@ -5,7 +5,7 @@ import location from '../location.js' * @typedef {{ * package: Package * origin: string, - * type: 'commonjs' | 'module',, + * type: 'commonjs' | 'module', * url: string * }} ModuleResolution */ @@ -14,7 +14,7 @@ import location from '../location.js' * Resolves an NPM module for a given `specifier` and an optional `origin`. * @param {string|URL} specifier * @param {string|URL=} [origin] - * @param {{ prefix?: string }} [options] + * @param {{ prefix?: string, type?: 'commonjs' | 'module' }} [options] * @return {ModuleResolution|null} */ export async function resolve (specifier, origin = null, options = null) { @@ -39,6 +39,7 @@ export async function resolve (specifier, origin = null, options = null) { const prefix = options?.prefix ?? DEFAULT_PACKAGE_PREFIX const name = Package.Name.from(specifier, { origin }) + const type = options?.type ?? 'module' // prefer 'module' in this context const pkg = new Package(name.value, { loader: { origin } }) @@ -46,11 +47,14 @@ export async function resolve (specifier, origin = null, options = null) { const pathname = name.pathname.replace(name.value, '.') || '.' try { + // will call `pkg.load()` internally + // can throw `ModuleNotFoundError` + const url = pkg.resolve(pathname, { prefix, type }) return { package: pkg, origin: pkg.origin, type: pkg.type, - url: pkg.resolve(pathname, { prefix }) + url } } catch (err) { if (err?.code === 'MODULE_NOT_FOUND') { From fb9e39d7714b9b2646bdd21b040023dc8645d27b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:19:41 -0400 Subject: [PATCH 0487/1178] refactor(api/npm/service-worker.js): include 'referer' in initial origins --- api/npm/service-worker.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index 9b60af776b..f269c95f77 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -10,12 +10,24 @@ export async function onRequest (request, env, ctx) { const url = new URL(request.url) const origin = url.origin.replace('npm://', 'socket://') + const referer = request.headers.get('referer') const specifier = url.pathname.replace('/socket/npm/', '') const importOrigins = url.searchParams.getAll('origin').concat(url.searchParams.getAll('origin[]')) let resolved = null let origins = [] + if (referer && !referer.startsWith('blob:')) { + if (URL.canParse(referer, origin)) { + const refererURL = new URL(referer, origin) + if (refererURL.href.endsWith('/')) { + importOrigins.push(refererURL.href) + } else { + importOrigins.push(new URL('./', refererURL).href) + } + } + } + for (const value of importOrigins) { if (value.startsWith('npm:')) { origins.push(value) @@ -92,8 +104,13 @@ export async function onRequest (request, env, ctx) { if (resolved.type === 'commonjs') { const proxy = ` - import { Module } from 'socket:module' - const exports = Module.main.require('${resolved.url}') + import { createRequire } from 'socket:module' + const require = createRequire('${resolved.origin}', { + headers: { + 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' + } + }) + const exports = require('${resolved.url}') export default exports?.default ?? exports ?? null ` From 1a6f8c472c745896844620fc83dc6e183b4e43e1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:20:01 -0400 Subject: [PATCH 0488/1178] refactor(api/service-worker/init.js): set internal worker type symbol --- api/service-worker/init.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/service-worker/init.js b/api/service-worker/init.js index 4696f7a59f..6aba96bbc2 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -45,7 +45,8 @@ export async function onRegister (event) { } const worker = new Worker('./worker.js', { - name: `ServiceWorker (${info.pathname})` + name: `ServiceWorker (${info.pathname})`, + [Symbol.for('socket.runtime.internal.worker.type')]: 'serviceWorker' }) workers.set(info.hash, worker) From 885c7ab8c3728f88d02f96d6da3ac531bf62e4b0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:20:27 -0400 Subject: [PATCH 0489/1178] refactor(api/service-worker): handle 'registering' state --- api/service-worker/registration.js | 3 ++- api/service-worker/state.js | 3 ++- api/service-worker/worker.js | 10 ++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/api/service-worker/registration.js b/api/service-worker/registration.js index 6f09eb1be7..a4a066fa8a 100644 --- a/api/service-worker/registration.js +++ b/api/service-worker/registration.js @@ -10,7 +10,8 @@ export class ServiceWorkerRegistration { constructor (info, serviceWorker) { this.#info = info - if (serviceWorker.state === 'installing' || serviceWorker.state === 'parsed') { + // many states here just end up being the 'installing' state to the front end + if (/installing|parsed|registered|registering/.test(serviceWorker.state)) { this.#installing = serviceWorker } else if (serviceWorker.state === 'installed') { this.#waiting = serviceWorker diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 7d81181d45..1f09540649 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -29,7 +29,8 @@ const descriptors = { id: this.id, scope: this.serviceWorker.scope, state: this.serviceWorker.state, - scriptURL: this.serviceWorker.scriptURL + scriptURL: this.serviceWorker.scriptURL, + workerURL: globalThis.location.href }) } } diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index f0e127185a..115dd88486 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -103,7 +103,14 @@ async function onMessage (event) { }) try { + globalThis.RUNTIME_WORKER_LOCATION = scriptURL + state.serviceWorker.state = 'registering' + await state.notify('serviceWorker') + + // import module, which could be ESM, CommonJS, + // or a simple ServiceWorker const result = await import(scriptURL) + if (typeof module.exports === 'function') { module.exports = { default: module.exports @@ -111,6 +118,9 @@ async function onMessage (event) { } else { Object.assign(module.exports, result) } + + state.serviceWorker.state = 'registered' + await state.notify('serviceWorker') } catch (err) { console.error(err) globalThis.reportError(err) From a45f3a9f67b89b5fb55335e51fb8f6d73bb47c96 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 1 Apr 2024 22:21:23 -0400 Subject: [PATCH 0490/1178] refactor(src/core/preload.cc): allow 'referrer-policy' to be set --- src/core/preload.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/preload.cc b/src/core/preload.cc index aa4dbaebda..3c35d1fbdd 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -22,6 +22,10 @@ namespace SSC { String preload = ""; if (preloadOptions.wrap) { + if (opts.referrerPolicy.size() > 0) { + preload += "\n"; + preload += "\n"; + } preload += "\n"; preload += " - + + + + +
+

You are seeing this window because you are likely debugging a Service Worker

+

+    
+ +
+

Socket Runtime ()

+
diff --git a/api/service-worker/init.js b/api/service-worker/init.js index 6aba96bbc2..7255dd4f4a 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -50,14 +50,32 @@ export async function onRegister (event) { }) workers.set(info.hash, worker) - worker.addEventListener('message', onHandShakeMessage) + worker.addEventListener('message', onMessage) - async function onHandShakeMessage (event) { + async function onMessage (event) { if (event.data.__service_worker_ready === true) { worker.postMessage({ register: info }) } else if (event.data.__service_worker_registered?.id === info.id) { worker.postMessage({ install: info }) - worker.removeEventListener('message', onHandShakeMessage) + } else if (Array.isArray(event.data.__service_worker_debug)) { + const log = document.querySelector('#log') + if (log) { + for (const entry of event.data.__service_worker_debug) { + const lines = entry.split('\n') + const span = document.createElement('span') + for (const line of lines) { + if (!line) continue + const item = document.createElement('code') + item.innerHTML = line + .replace(/\s/g, ' ') + .replace(/(Error|TypeError|SyntaxError|ReferenceError|RangeError)/g, '$1') + span.appendChild(item) + } + log.appendChild(span) + } + + log.scrollTop = log.scrollHeight + } } } } diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 1f09540649..89e9df9c28 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -1,5 +1,6 @@ /* global reportError */ import application from '../application.js' +import debug from './debug.js' import ipc from '../ipc.js' export const channel = new BroadcastChannel('socket.runtime.serviceWorker.state') @@ -25,6 +26,13 @@ const descriptors = { channel.postMessage({ [type]: this[type] }) if (this.id && type === 'serviceWorker') { + debug( + '[%s]: ServiceWorker (%s) updated state to "%s"', + this.serviceWorker.scriptURL, + this.id, + this.serviceWorker.state + ) + await ipc.request('serviceWorker.updateState', { id: this.id, scope: this.serviceWorker.scope, diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 3400b7b7e7..992fbdfb65 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -5,8 +5,10 @@ import { STATUS_CODES } from '../http.js' import { Environment } from './env.js' import { Deferred } from '../async.js' import { Buffer } from '../buffer.js' +import globals from '../internal/globals.js' import process from '../process.js' import clients from './clients.js' +import debug from './debug.js' import hooks from '../hooks.js' import state from './state.js' import path from '../path.js' @@ -25,6 +27,9 @@ const events = new Set() hooks.onReady(onReady) globalThis.addEventListener('message', onMessage) +globals.register('ServiceWorker.state', state) +globals.register('ServiceWorker.events', events) + function onReady () { globalThis.postMessage(SERVICE_WORKER_READY_TOKEN) } @@ -40,10 +45,11 @@ async function onMessage (event) { state.id = id state.serviceWorker.scope = scope state.serviceWorker.scriptURL = scriptURL + globals.register('ServiceWorker.module', module) Module.main.addEventListener('error', (event) => { if (event.error) { - globalThis.reportError(event.error) + debug(event.error) } }) @@ -122,7 +128,7 @@ async function onMessage (event) { state.serviceWorker.state = 'registered' await state.notify('serviceWorker') } catch (err) { - globalThis.reportError(err) + debug(err) state.serviceWorker.state = 'error' await state.notify('serviceWorker') return @@ -169,7 +175,7 @@ async function onMessage (event) { try { await state.activate(event.context.env, event.ontext) } catch (err) { - state.reportError(err) + debug(err) } }) } @@ -179,7 +185,7 @@ async function onMessage (event) { try { await state.install(event.context.env, event.context) } catch (err) { - state.reportError(err) + debug(err) } }) } @@ -210,7 +216,7 @@ async function onMessage (event) { event.context ) } catch (err) { - state.reportError(err) + debug(err) response = new Response(util.inspect(err), { statusText: err.message || STATUS_CODES[500], status: 500 From c980e5e5939fd7bb6a1c8354d1fd0652c72f228a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 5 Apr 2024 15:58:04 -0400 Subject: [PATCH 0518/1178] test(mime): fix test --- test/src/mime.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/mime.js b/test/src/mime.js index 12302e3ce2..a68be15d0c 100644 --- a/test/src/mime.js +++ b/test/src/mime.js @@ -99,7 +99,7 @@ test('mime.lookup', async (t) => { const results = await mime.lookup(ext) const mimes = results.map((result) => result.mime) const i = intersection(mimes, expect) - t.ok(i.length > 0, `mime.lookup returns resuls for ${ext}`) + t.ok(i.length > 0, `mime.lookup returns results for ${ext}`) for (const result of results) { expect.includes(result) } @@ -113,6 +113,9 @@ test('verify internal database content type prefix', async (t) => { for (const entry of database.entries()) { allContentTypesStartWithDatabaseName = entry[1].startsWith(database.name + '/') + if (!allContentTypesStartWithDatabaseName) { + break + } } t.ok( From 0030a6ea547a134180025e4d9f83c1fa2af6c501 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 5 Apr 2024 15:58:26 -0400 Subject: [PATCH 0519/1178] fix(src/core/service_worker_container.cc): improve HTML detection in service worker --- src/core/service_worker_container.cc | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 3504cb9520..692cdce068 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -375,13 +375,15 @@ namespace SSC { String contentType = ""; + // find content type for (const auto& entry : headers) { auto pair = split(trim(entry), ':'); auto key = trim(pair[0]); auto value = trim(pair[1]); - if (key == "content-type") { + if (key == "content-type" || key == "Content-Type") { contentType = value; + break; } } @@ -390,17 +392,26 @@ namespace SSC { // XXX(@jwerle): we handle this in the android runtime #if !SSC_PLATFORM_ANDROID const auto extname = Path(request.pathname).extension().string(); + auto html = (message.buffer.bytes != nullptr && message.buffer.size > 0) + ? String(response.buffer.bytes, response.buffer.size) + : String(""); + if ( - statusCode < 400 && - (message.buffer.bytes != nullptr && message.buffer.size > 0) && - (extname.ends_with("html") || contentType == "text/html") + html.size() > 0 && + ( + (extname.ends_with("html") || contentType == "text/html") || + (html.find("\n") + request.client.preload ); - auto html = String(response.buffer.bytes, response.buffer.size); auto begin = String(""); auto end = String(""); auto x = html.find(begin); From 928d2c34b76dc05b52cf169cc5af49cd72f59fed Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 5 Apr 2024 15:58:46 -0400 Subject: [PATCH 0520/1178] fix(src/ipc/bridge.cc): fix 'serviceWorker.getRegistration' scope filter --- src/ipc/bridge.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index ff3cf84c0d..265c2e18a4 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2666,7 +2666,7 @@ static void initRouterTable (Router *router) { for (const auto& entry : router->core->serviceWorker.registrations) { const auto& registration = entry.second; - if (registration.options.scope.starts_with(scope)) { + if (scope.starts_with(registration.options.scope)) { auto json = JSON::Object { JSON::Object::Entries { {"registration", registration.json()}, From e0ea30e9417c81b35dacf0f23e955a1a3b709fe8 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 5 Apr 2024 15:59:02 -0400 Subject: [PATCH 0521/1178] refactor(src/desktop/main.cc): improve service worker window sizing --- src/desktop/main.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index a74e8d6e72..5731571abc 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1571,9 +1571,14 @@ MAIN { userConfig["permissions_allow_service_worker"] != "false" ) { auto serviceWorkerUserConfig = userConfig; + auto screen = defaultWindow->getScreenSize(); serviceWorkerUserConfig["webview_watch_reload"] = "false"; auto serviceWorkerWindow = windowManager.createWindow({ .canExit = false, + .width = defaultWindow->getSizeInPixels("80%", screen.width), + .height = defaultWindow->getSizeInPixels("80%", screen.height), + .minWidth = defaultWindow->getSizeInPixels("40%", screen.width), + .minHeight = defaultWindow->getSizeInPixels("30%", screen.height), .index = SSC_SERVICE_WORKER_CONTAINER_WINDOW_INDEX, .headless = Env::get("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG").size() == 0, .userConfig = serviceWorkerUserConfig, From 590feecc0223ee3a1cc3567f4ad6003b5d87ec01 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 5 Apr 2024 16:04:30 -0400 Subject: [PATCH 0522/1178] chore(package.json): add 'web-stream-polyfill' --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 80251960c8..fc7a7ea4c9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "standard": "^17.1.0", "typescript": "5.3.2", "urlpattern-polyfill": "^9.0.0", + "web-streams-polyfill": "^4.0.0", "whatwg-fetch": "^3.6.17", "whatwg-url": "^13.0.0" }, @@ -35,12 +36,12 @@ "/api/url/urlpattern/urlpattern.js", "/api/url/url/url.js", "/api/fetch/fetch.js", + "/api/internal/streams/web.js", "/npm/packages/@socketsupply/socket-node/index.cjs" ] }, "workspaces": [ "npm/packages/@socketsupply/socket-node" ], - "version": "0.0.0", - "dependencies": {} + "version": "0.0.0" } From e886f65212b64386b8eb6d28033499c8e4a5b6bb Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 5 Apr 2024 16:04:59 -0400 Subject: [PATCH 0523/1178] chore(api): generate types + docs --- api/README.md | 52 ++-- api/index.d.ts | 816 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 675 insertions(+), 193 deletions(-) diff --git a/api/README.md b/api/README.md index 99de104483..bed844a467 100644 --- a/api/README.md +++ b/api/README.md @@ -1770,7 +1770,7 @@ Returns the home directory of the current user. import { Path } from 'socket:path' ``` -## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L49) +## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L44) External docs: https://nodejs.org/api/path.html#path_path_resolve_paths The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -1783,7 +1783,7 @@ The path.resolve() method resolves a sequence of paths or path segments into an | :--- | :--- | :--- | | Not specified | string | | -## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L87) +## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L82) Computes current working directory for a path @@ -1796,7 +1796,7 @@ Computes current working directory for a path | :--- | :--- | :--- | | Not specified | string | | -## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L111) +## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L106) Computed location origin. Defaults to `socket:///` if not available. @@ -1804,7 +1804,7 @@ Computed location origin. Defaults to `socket:///` if not available. | :--- | :--- | :--- | | Not specified | string | | -## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L122) +## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L117) Computes the relative path from `from` to `to`. @@ -1818,7 +1818,7 @@ Computes the relative path from `from` to `to`. | :--- | :--- | :--- | | Not specified | string | | -## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L169) +## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L164) Joins path components. This function may not return an absolute path. @@ -1831,7 +1831,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L228) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L221) Computes directory name of path. @@ -1844,7 +1844,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L270) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L263) Computes base name of path. @@ -1857,7 +1857,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L284) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L277) Computes extension name of path. @@ -1870,7 +1870,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L295) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L288) Computes normalized path @@ -1883,7 +1883,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L345) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L338) Formats `Path` object into a string. @@ -1896,7 +1896,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L361) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L354) Parses input `path` into a `Path` instance. @@ -1908,11 +1908,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L389) +## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L382) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L395) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L388) Creates a `Path` instance from `input` and optional `cwd`. @@ -1921,7 +1921,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L418) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L411) `Path` class constructor. @@ -1930,47 +1930,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L491) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L484) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L498) +### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L491) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L532) +### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L525) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L540) +### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L533) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L559) +### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L552) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L580) +### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L573) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L615) +### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L608) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L627) +### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L620) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L635) +### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L628) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L655) +### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L648) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L662) +### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L655) @@ -1978,7 +1978,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L670) +### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L663) Converts this `Path` instance to a string. diff --git a/api/index.d.ts b/api/index.d.ts index 6aca5620d1..b56930c1e5 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -241,7 +241,7 @@ declare module "socket:events" { declare module "socket:async/context" { /** - * @module AsyncContext + * @module Async.AsyncContext * * Async Context for JavaScript based on the TC39 proposal. * @@ -431,17 +431,16 @@ declare module "socket:async/context" { * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at * the time of the snapshot. - * @template T * @return {FrozenRevert} */ - static snapshot(): FrozenRevert; + static snapshot(): FrozenRevert; /** * Restores the storage `Mapping` state to state at the time the * "revert" (`FrozenRevert` or `Revert`) was created. * @template T * @param {Revert|FrozenRevert} revert */ - static restore(revert: FrozenRevert | Revert): void; + static restore(revert: FrozenRevert | Revert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -566,9 +565,32 @@ declare module "socket:async/context" { } declare module "socket:async/wrap" { - export function isTagged(fn: any): boolean; - export function tag(fn: any): any; - export function wrap(fn: any): any; + /** + * Returns `true` if a given function `fn` has the "async" wrapped tag, + * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this + * function will return `false`. + * @ignore + * @param {function} fn + * @param {boolean} + */ + export function isTagged(fn: Function): boolean; + /** + * Tags a function `fn` as being "async wrapped" so subsequent calls to + * `wrap(fn)` do not wrap an already wrapped function. + * @ignore + * @param {function} fn + * @return {function} + */ + export function tag(fn: Function): Function; + /** + * Wraps a function `fn` that captures a snapshot of the current async + * context. This function is idempotent and will not wrap a function more + * than once. + * @ignore + * @param {function} fn + * @return {function} + */ + export function wrap(fn: Function): Function; export const symbol: unique symbol; export default wrap; } @@ -911,14 +933,14 @@ declare module "socket:internal/symbols" { declare module "socket:gc" { /** - * Track `object` ref to call `Symbol.for('gc.finalize')` method when + * Track `object` ref to call `Symbol.for('socket.gc.finalize')` method when * environment garbage collects object. * @param {object} object * @return {boolean} */ export function ref(object: object, ...args: any[]): boolean; /** - * Stop tracking `object` ref to call `Symbol.for('gc.finalize')` method when + * Stop tracking `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when * environment garbage collects object. * @param {object} object * @return {boolean} @@ -1923,48 +1945,152 @@ declare module "socket:signal" { import { signal as constants } from "socket:os/constants"; } +declare module "socket:internal/streams/web" { + export class ByteLengthQueuingStrategy { + constructor(e: any); + _byteLengthQueuingStrategyHighWaterMark: any; + get highWaterMark(): any; + get size(): (e: any) => any; + } + export class CountQueuingStrategy { + constructor(e: any); + _countQueuingStrategyHighWaterMark: any; + get highWaterMark(): any; + get size(): () => number; + } + export class ReadableByteStreamController { + get byobRequest(): any; + get desiredSize(): number; + close(): void; + enqueue(e: any): void; + error(e?: any): void; + _pendingPullIntos: v; + [T](e: any): any; + [C](e: any): any; + [P](): void; + } + export class ReadableStream { + static from(e: any): any; + constructor(e?: {}, t?: {}); + get locked(): boolean; + cancel(e?: any): any; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + pipeThrough(e: any, t?: {}): any; + pipeTo(e: any, t?: {}): any; + tee(): any; + values(e?: any): any; + } + export class ReadableStreamBYOBReader { + constructor(e: any); + _readIntoRequests: v; + get closed(): any; + cancel(e?: any): any; + read(e: any, t?: {}): any; + releaseLock(): void; + } + export class ReadableStreamBYOBRequest { + get view(): any; + respond(e: any): void; + respondWithNewView(e: any): void; + } + export class ReadableStreamDefaultController { + get desiredSize(): number; + close(): void; + enqueue(e?: any): void; + error(e?: any): void; + [T](e: any): any; + [C](e: any): void; + [P](): void; + } + export class ReadableStreamDefaultReader { + constructor(e: any); + _readRequests: v; + get closed(): any; + cancel(e?: any): any; + read(): any; + releaseLock(): void; + } + export class TransformStream { + constructor(e?: {}, t?: {}, r?: {}); + get readable(): any; + get writable(): any; + } + export class TransformStreamDefaultController { + get desiredSize(): number; + enqueue(e?: any): void; + error(e?: any): void; + terminate(): void; + } + export class WritableStream { + constructor(e?: {}, t?: {}); + get locked(): boolean; + abort(e?: any): any; + close(): any; + getWriter(): WritableStreamDefaultWriter; + } + export class WritableStreamDefaultController { + get abortReason(): any; + get signal(): any; + error(e?: any): void; + [w](e: any): any; + [R](): void; + } + export class WritableStreamDefaultWriter { + constructor(e: any); + _ownerWritableStream: any; + get closed(): any; + get desiredSize(): number; + get ready(): any; + abort(e?: any): any; + close(): any; + releaseLock(): void; + write(e?: any): any; + } + class v { + _cursor: number; + _size: number; + _front: { + _elements: any[]; + _next: any; + }; + _back: { + _elements: any[]; + _next: any; + }; + get length(): number; + push(e: any): void; + shift(): any; + forEach(e: any): void; + peek(): any; + } + const T: unique symbol; + const C: unique symbol; + const P: unique symbol; + const w: unique symbol; + const R: unique symbol; + export {}; +} + +declare module "socket:internal/streams" { + const _default: any; + export default _default; + import { ReadableStream } from "socket:internal/streams/web"; + import { ReadableStreamBYOBReader } from "socket:internal/streams/web"; + import { ReadableByteStreamController } from "socket:internal/streams/web"; + import { ReadableStreamBYOBRequest } from "socket:internal/streams/web"; + import { ReadableStreamDefaultController } from "socket:internal/streams/web"; + import { ReadableStreamDefaultReader } from "socket:internal/streams/web"; + import { WritableStream } from "socket:internal/streams/web"; + import { WritableStreamDefaultController } from "socket:internal/streams/web"; + import { WritableStreamDefaultWriter } from "socket:internal/streams/web"; + import { TransformStream } from "socket:internal/streams/web"; + import { TransformStreamDefaultController } from "socket:internal/streams/web"; + import { ByteLengthQueuingStrategy } from "socket:internal/streams/web"; + import { CountQueuingStrategy } from "socket:internal/streams/web"; + export { ReadableStream, ReadableStreamBYOBReader, ReadableByteStreamController, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy }; +} + declare module "socket:stream/web" { - export const ReadableStream: { - new (underlyingSource: UnderlyingByteSource, strategy?: { - highWaterMark?: number; - }): ReadableStream; - new (underlyingSource: UnderlyingDefaultSource, strategy?: QueuingStrategy): ReadableStream; - new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; - prototype: ReadableStream; - } | typeof UnsupportedStreamInterface; - export const ReadableStreamDefaultReader: { - new (stream: ReadableStream): ReadableStreamDefaultReader; - prototype: ReadableStreamDefaultReader; - } | typeof UnsupportedStreamInterface; - export const ReadableStreamBYOBReader: { - new (stream: ReadableStream): ReadableStreamBYOBReader; - prototype: ReadableStreamBYOBReader; - } | typeof UnsupportedStreamInterface; - export const ReadableStreamBYOBRequest: typeof UnsupportedStreamInterface; - export const ReadableByteStreamController: typeof UnsupportedStreamInterface; - export const ReadableStreamDefaultController: typeof UnsupportedStreamInterface; - export const TransformStream: { - new (transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy): TransformStream; - prototype: TransformStream; - } | typeof UnsupportedStreamInterface; - export const TransformStreamDefaultController: typeof UnsupportedStreamInterface; - export const WritableStream: { - new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): WritableStream; - prototype: WritableStream; - } | typeof UnsupportedStreamInterface; - export const WritableStreamDefaultWriter: { - new (stream: WritableStream): WritableStreamDefaultWriter; - prototype: WritableStreamDefaultWriter; - } | typeof UnsupportedStreamInterface; - export const WritableStreamDefaultController: typeof UnsupportedStreamInterface; - export const ByteLengthQueuingStrategy: { - new (init: QueuingStrategyInit): ByteLengthQueuingStrategy; - prototype: ByteLengthQueuingStrategy; - } | typeof UnsupportedStreamInterface; - export const CountQueuingStrategy: { - new (init: QueuingStrategyInit): CountQueuingStrategy; - prototype: CountQueuingStrategy; - } | typeof UnsupportedStreamInterface; export const TextEncoderStream: typeof UnsupportedStreamInterface; export const TextDecoderStream: { new (label?: string, options?: TextDecoderOptions): TextDecoderStream; @@ -1979,10 +2105,24 @@ declare module "socket:stream/web" { prototype: DecompressionStream; } | typeof UnsupportedStreamInterface; export default exports; + import { ReadableStream } from "socket:internal/streams"; + import { ReadableStreamBYOBReader } from "socket:internal/streams"; + import { ReadableByteStreamController } from "socket:internal/streams"; + import { ReadableStreamBYOBRequest } from "socket:internal/streams"; + import { ReadableStreamDefaultController } from "socket:internal/streams"; + import { ReadableStreamDefaultReader } from "socket:internal/streams"; + import { WritableStream } from "socket:internal/streams"; + import { WritableStreamDefaultController } from "socket:internal/streams"; + import { WritableStreamDefaultWriter } from "socket:internal/streams"; + import { TransformStream } from "socket:internal/streams"; + import { TransformStreamDefaultController } from "socket:internal/streams"; + import { ByteLengthQueuingStrategy } from "socket:internal/streams"; + import { CountQueuingStrategy } from "socket:internal/streams"; class UnsupportedStreamInterface { } import * as exports from "socket:stream/web"; + export { ReadableStream, ReadableStreamBYOBReader, ReadableByteStreamController, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy }; } declare module "socket:stream" { @@ -1993,7 +2133,6 @@ declare module "socket:stream" { export function getStreamError(stream: any): any; export function isReadStreamx(stream: any): any; export { web }; - export default exports; export class FixedFIFO { constructor(hwm: any); buffer: any[]; @@ -2010,8 +2149,8 @@ declare module "socket:stream" { export class FIFO { constructor(hwm: any); hwm: any; - head: exports.FixedFIFO; - tail: exports.FixedFIFO; + head: FixedFIFO; + tail: FixedFIFO; length: number; clear(): void; push(val: any): void; @@ -2028,7 +2167,7 @@ declare module "socket:stream" { byteLengthWritable: any; }); stream: any; - queue: exports.FIFO; + queue: FIFO; highWaterMark: number; buffered: number; error: any; @@ -2058,12 +2197,12 @@ declare module "socket:stream" { byteLengthReadable: any; }); stream: any; - queue: exports.FIFO; + queue: FIFO; highWaterMark: number; buffered: number; readAhead: boolean; error: any; - pipeline: exports.Pipeline; + pipeline: Pipeline; byteLength: any; map: any; pipeTo: any; @@ -2112,12 +2251,12 @@ declare module "socket:stream" { get destroying(): boolean; destroy(err: any): void; } - export class Readable extends exports.Stream { - static _fromAsyncIterator(ite: any, opts: any): exports.Readable; + export class Readable extends Stream { + static _fromAsyncIterator(ite: any, opts: any): Readable; static from(data: any, opts: any): any; static isBackpressured(rs: any): boolean; static isPaused(rs: any): boolean; - _readableState: exports.ReadableState; + _readableState: ReadableState; _read(cb: any): void; pipe(dest: any, cb: any): any; read(): any; @@ -2126,35 +2265,45 @@ declare module "socket:stream" { resume(): this; pause(): this; } - export class Writable extends exports.Stream { + export class Writable extends Stream { static isBackpressured(ws: any): boolean; static drained(ws: any): Promise; - _writableState: exports.WritableState; + _writableState: WritableState; _writev(batch: any, cb: any): void; _write(data: any, cb: any): void; _final(cb: any): void; write(data: any): boolean; end(data: any): this; } - export class Duplex extends exports.Readable { - _writableState: exports.WritableState; + export class Duplex extends Readable { + _writableState: WritableState; _writev(batch: any, cb: any): void; _write(data: any, cb: any): void; _final(cb: any): void; write(data: any): boolean; end(data: any): this; } - export class Transform extends exports.Duplex { - _transformState: exports.TransformState; + export class Transform extends Duplex { + _transformState: TransformState; _transform(data: any, cb: any): void; _flush(cb: any): void; } - export class PassThrough extends exports.Transform { - } + export class PassThrough extends Transform { + } + const _default: typeof Stream & { + web: typeof web; + Readable: typeof Readable; + Writable: typeof Writable; + Duplex: typeof Duplex; + Transform: typeof Transform; + PassThrough: typeof PassThrough; + pipeline: typeof pipeline & { + [x: symbol]: typeof pipelinePromise; + }; + }; + export default _default; import web from "socket:stream/web"; - import * as exports from "socket:stream"; import { EventEmitter } from "socket:events"; - } declare module "socket:tty" { @@ -2208,36 +2357,6 @@ declare module "socket:process" { const process: any; } -declare module "socket:location" { - export function toString(): string; - export const globalLocation: Location | { - origin: string; - host: string; - hostname: string; - pathname: string; - href: string; - }; - export const href: string; - export const protocol: "socket:"; - export const hostname: string; - export const host: string; - export const search: string; - export const hash: string; - export const pathname: string; - export const origin: string; - namespace _default { - export { origin }; - export { href }; - export { protocol }; - export { hostname }; - export { host }; - export { search }; - export { pathname }; - export { toString }; - } - export default _default; -} - declare module "socket:url/urlpattern/urlpattern" { export { me as URLPattern }; var me: { @@ -2289,7 +2408,22 @@ declare module "socket:querystring" { } declare module "socket:url/index" { - export function parse(input: any, options?: any): any; + export function parse(input: any, options?: any): { + hash: any; + host: any; + hostname: any; + origin: any; + auth: string; + password: any; + pathname: any; + path: any; + port: any; + protocol: any; + search: any; + searchParams: any; + username: any; + [Symbol.toStringTag]: string; + }; export function resolve(from: any, to: any): any; export function format(input: any): any; const URLPattern_base: { @@ -2318,7 +2452,9 @@ declare module "socket:url/index" { } export const protocols: Set; export default URL; - export const URL: any; + export class URL { + private constructor(); + } export const URLSearchParams: any; export const parseURL: any; } @@ -2329,6 +2465,36 @@ declare module "socket:url" { import URL from "socket:url/index"; } +declare module "socket:location" { + export function toString(): string; + export const globalLocation: Location | { + origin: string; + host: string; + hostname: string; + pathname: string; + href: string; + }; + export const href: string; + export const protocol: "socket:"; + export const hostname: string; + export const host: string; + export const search: string; + export const hash: string; + export const pathname: string; + export const origin: string; + namespace _default { + export { origin }; + export { href }; + export { protocol }; + export { hostname }; + export { host }; + export { search }; + export { pathname }; + export { toString }; + } + export default _default; +} + declare module "socket:path/path" { /** * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -5268,6 +5434,314 @@ declare module "socket:errors" { } +declare module "socket:util/types" { + /** + * Returns `true` if input is a plan `Object` instance. + * @param {any} input + * @return {boolean} + */ + export function isPlainObject(input: any): boolean; + /** + * Returns `true` if input is an `AsyncFunction` + * @param {any} input + * @return {boolean} + */ + export function isAsyncFunction(input: any): boolean; + /** + * Returns `true` if input is an `Function` + * @param {any} input + * @return {boolean} + */ + export function isFunction(input: any): boolean; + /** + * Returns `true` if input is an `AsyncFunction` object. + * @param {any} input + * @return {boolean} + */ + export function isAsyncFunctionObject(input: any): boolean; + /** + * Returns `true` if input is an `Function` object. + * @param {any} input + * @return {boolean} + */ + export function isFunctionObject(input: any): boolean; + /** + * Always returns `false`. + * @param {any} input + * @return {boolean} + */ + export function isExternal(input: any): boolean; + /** + * Returns `true` if input is a `Date` instance. + * @param {any} input + * @return {boolean} + */ + export function isDate(input: any): boolean; + /** + * Returns `true` if input is an `arguments` object. + * @param {any} input + * @return {boolean} + */ + export function isArgumentsObject(input: any): boolean; + /** + * Returns `true` if input is a `BigInt` object. + * @param {any} input + * @return {boolean} + */ + export function isBigIntObject(input: any): boolean; + /** + * Returns `true` if input is a `Boolean` object. + * @param {any} input + * @return {boolean} + */ + export function isBooleanObject(input: any): boolean; + /** + * Returns `true` if input is a `Number` object. + * @param {any} input + * @return {boolean} + */ + export function isNumberObject(input: any): boolean; + /** + * Returns `true` if input is a `String` object. + * @param {any} input + * @return {boolean} + */ + export function isStringObject(input: any): boolean; + /** + * Returns `true` if input is a `Symbol` object. + * @param {any} input + * @return {boolean} + */ + export function isSymbolObject(input: any): boolean; + /** + * Returns `true` if input is native `Error` instance. + * @param {any} input + * @return {boolean} + */ + export function isNativeError(input: any): boolean; + /** + * Returns `true` if input is a `RegExp` instance. + * @param {any} input + * @return {boolean} + */ + export function isRegExp(input: any): boolean; + /** + * Returns `true` if input is a `GeneratorFunction`. + * @param {any} input + * @return {boolean} + */ + export function isGeneratorFunction(input: any): boolean; + /** + * Returns `true` if input is an `AsyncGeneratorFunction`. + * @param {any} input + * @return {boolean} + */ + export function isAsyncGeneratorFunction(input: any): boolean; + /** + * Returns `true` if input is an instance of a `Generator`. + * @param {any} input + * @return {boolean} + */ + export function isGeneratorObject(input: any): boolean; + /** + * Returns `true` if input is a `Promise` instance. + * @param {any} input + * @return {boolean} + */ + export function isPromise(input: any): boolean; + /** + * Returns `true` if input is a `Map` instance. + * @param {any} input + * @return {boolean} + */ + export function isMap(input: any): boolean; + /** + * Returns `true` if input is a `Set` instance. + * @param {any} input + * @return {boolean} + */ + export function isSet(input: any): boolean; + /** + * Returns `true` if input is an instance of an `Iterator`. + * @param {any} input + * @return {boolean} + */ + export function isIterator(input: any): boolean; + /** + * Returns `true` if input is an instance of an `AsyncIterator`. + * @param {any} input + * @return {boolean} + */ + export function isAsyncIterator(input: any): boolean; + /** + * Returns `true` if input is an instance of a `MapIterator`. + * @param {any} input + * @return {boolean} + */ + export function isMapIterator(input: any): boolean; + /** + * Returns `true` if input is an instance of a `SetIterator`. + * @param {any} input + * @return {boolean} + */ + export function isSetIterator(input: any): boolean; + /** + * Returns `true` if input is a `WeakMap` instance. + * @param {any} input + * @return {boolean} + */ + export function isWeakMap(input: any): boolean; + /** + * Returns `true` if input is a `WeakSet` instance. + * @param {any} input + * @return {boolean} + */ + export function isWeakSet(input: any): boolean; + /** + * Returns `true` if input is an `ArrayBuffer` instance. + * @param {any} input + * @return {boolean} + */ + export function isArrayBuffer(input: any): boolean; + /** + * Returns `true` if input is an `DataView` instance. + * @param {any} input + * @return {boolean} + */ + export function isDataView(input: any): boolean; + /** + * Returns `true` if input is a `SharedArrayBuffer`. + * This will always return `false` if a `SharedArrayBuffer` + * type is not available. + * @param {any} input + * @return {boolean} + */ + export function isSharedArrayBuffer(input: any): boolean; + /** + * Not supported. This function will return `false` always. + * @param {any} input + * @return {boolean} + */ + export function isProxy(input: any): boolean; + /** + * Returns `true` if input looks like a module namespace object. + * @param {any} input + * @return {boolean} + */ + export function isModuleNamespaceObject(input: any): boolean; + /** + * Returns `true` if input is an `ArrayBuffer` of `SharedArrayBuffer`. + * @param {any} input + * @return {boolean} + */ + export function isAnyArrayBuffer(input: any): boolean; + /** + * Returns `true` if input is a "boxed" primitive. + * @param {any} input + * @return {boolean} + */ + export function isBoxedPrimitive(input: any): boolean; + /** + * Returns `true` if input is an `ArrayBuffer` view. + * @param {any} input + * @return {boolean} + */ + export function isArrayBufferView(input: any): boolean; + /** + * Returns `true` if input is a `TypedArray` instance. + * @param {any} input + * @return {boolean} + */ + export function isTypedArray(input: any): boolean; + /** + * Returns `true` if input is an `Uint8Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint8Array(input: any): boolean; + /** + * Returns `true` if input is an `Uint8ClampedArray` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint8ClampedArray(input: any): boolean; + /** + * Returns `true` if input is an `Uint16Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint16Array(input: any): boolean; + /** + * Returns `true` if input is an `Uint32Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint32Array(input: any): boolean; + /** + * Returns `true` if input is an Int8Array`` instance. + * @param {any} input + * @return {boolean} + */ + export function isInt8Array(input: any): boolean; + /** + * Returns `true` if input is an `Int16Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isInt16Array(input: any): boolean; + /** + * Returns `true` if input is an `Int32Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isInt32Array(input: any): boolean; + /** + * Returns `true` if input is an `Float32Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isFloat32Array(input: any): boolean; + /** + * Returns `true` if input is an `Float64Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isFloat64Array(input: any): boolean; + /** + * Returns `true` if input is an `BigInt64Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isBigInt64Array(input: any): boolean; + /** + * Returns `true` if input is an `BigUint64Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isBigUint64Array(input: any): boolean; + /** + * @ignore + * @param {any} input + * @return {boolean} + */ + export function isKeyObject(input: any): boolean; + /** + * Returns `true` if input is a `CryptoKey` instance. + * @param {any} input + * @return {boolean} + */ + export function isCryptoKey(input: any): boolean; + /** + * Returns `true` if input is an `Array`. + * @param {any} input + * @return {boolean} + */ + export const isArray: any; + export default exports; + import * as exports from "socket:util/types"; + +} + declare module "socket:mime/index" { /** * Look up a MIME type in various MIME databases. @@ -5275,6 +5749,12 @@ declare module "socket:mime/index" { * @return {Promise} */ export function lookup(query: string): Promise; + /** + * Look up a MIME type in various MIME databases synchronously. + * @param {string} query + * @return {DatabaseQueryResult[]} + */ + export function lookupSync(query: string): DatabaseQueryResult[]; /** * A container for a database lookup query. */ @@ -5337,12 +5817,28 @@ declare module "socket:mime/index" { * @return {Promise} */ load(): Promise; + /** + * Loads database MIME entries synchronously into internal map. + */ + loadSync(): void; /** * Lookup MIME type by name or content type * @param {string} query - * @return {Promise} + * @return {Promise} + */ + lookup(query: string): Promise; + /** + * Lookup MIME type by name or content type synchronously. + * @param {string} query + * @return {Promise} + */ + lookupSync(query: string): Promise; + /** + * Queries database map and returns an array of results + * @param {string} query + * @return {DatabaseQueryResult[]} */ - lookup(query: string): Promise; + query(query: string): DatabaseQueryResult[]; } /** * A database of MIME types for 'application/' content types @@ -5419,6 +5915,7 @@ declare module "socket:mime/index" { export { Database }; export { databases }; export { lookup }; + export { lookupSync }; export { MIMEParams }; export { MIMEType }; export { application }; @@ -5447,7 +5944,7 @@ declare module "socket:util" { export function hasOwnProperty(object: any, property: any): any; export function isDate(object: any): boolean; export function isTypedArray(object: any): boolean; - export function isArrayLike(object: any): boolean; + export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; @@ -5479,8 +5976,8 @@ declare module "socket:util" { export function promisify(original: any): any; export function inspect(value: any, options: any): any; export namespace inspect { - let custom: symbol; let ignore: symbol; + let custom: symbol; } export function format(format: any, ...args: any[]): string; export function parseJSON(string: any): any; @@ -5490,6 +5987,7 @@ declare module "socket:util" { export function compareBuffers(a: any, b: any): any; export function inherits(Constructor: any, Super: any): void; export function deprecate(...args: any[]): void; + export { types }; export const TextDecoder: { new (label?: string, options?: TextDecoderOptions): TextDecoder; prototype: TextDecoder; @@ -5499,11 +5997,13 @@ declare module "socket:util" { prototype: TextEncoder; }; export const isArray: any; + export const inspectSymbols: symbol[]; export class IllegalConstructor { } export const MIMEType: typeof mime.MIMEType; export const MIMEParams: typeof mime.MIMEParams; export default exports; + import types from "socket:util/types"; import mime from "socket:mime"; import * as exports from "socket:util"; @@ -8660,6 +9160,11 @@ declare module "socket:service-worker/env" { }; } +declare module "socket:service-worker/debug" { + export function debug(...args: any[]): void; + export default debug; +} + declare module "socket:service-worker/state" { export const channel: BroadcastChannel; export const state: any; @@ -8778,6 +9283,7 @@ declare module "socket:service-worker/context" { } declare module "socket:service-worker/events" { + export const textEncoder: TextEncoderStream; export const FETCH_EVENT_TIMEOUT: number; /** * The `ExtendableEvent` interface extends the lifetime of the "install" and @@ -8943,16 +9449,19 @@ declare module "socket:http/adapters" { */ export class ServiceWorkerServerAdapter extends ServerAdapter { /** + * Handles the 'install' service worker event. * @ignore - * @param {import('../service-worker/events.js').ExtendableEvent} + * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: any): Promise; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise; /** + * Handles the 'activate' service worker event. * @ignore - * @param {import('../service-worker/events.js').ExtendableEvent} + * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: any): Promise; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise; /** + * Handles the 'fetch' service worker event. * @ignore * @param {import('../service-worker/events.js').FetchEvent} */ @@ -9217,6 +9726,10 @@ declare module "socket:http" { * @return {OutgoingMessage} */ setTimeout(timeout: number, callback?: Function | undefined): OutgoingMessage; + /** + * @ignore + */ + _implicitHeader(): void; #private; } /** @@ -9233,12 +9746,6 @@ declare module "socket:http" { * @param {object} options */ constructor(options: object); - /** - * This property will be `true` if a complete HTTP message has been received - * and successfully parsed. - * @type {boolean} - */ - get complete(): boolean; set url(url: string); /** * The URL for this incoming message. This value is not absolute with @@ -9247,6 +9754,12 @@ declare module "socket:http" { * @type {string} */ get url(): string; + /** + * This property will be `true` if a complete HTTP message has been received + * and successfully parsed. + * @type {boolean} + */ + get complete(): boolean; /** * An object of the incoming message headers. * @type {object} @@ -9567,6 +10080,9 @@ declare module "socket:http" { * @ignore */ reuseSocket(): void; + /** + * @ignore + */ destroy(): void; } /** @@ -9657,6 +10173,17 @@ declare module "socket:http" { * @type {number} */ get maxConnections(): number; + /** + * Gets the HTTP server address and port that it this server is + * listening (emulated) on in the runtime with respect to the + * adapter internal being used by the server. + * @return {{ family: string, address: string, port: number}} + */ + address(): { + family: string; + address: string; + port: number; + }; /** * Closes the server. * @param {function=} [close] @@ -11963,7 +12490,7 @@ declare module "socket:commonjs/package" { get map(): Map; get origin(): any; add(name: any, info?: any): void; - get(name: any): any; + get(name: any, options?: any): any; entries(): IterableIterator<[any, any]>; keys(): IterableIterator; values(): IterableIterator; @@ -13214,9 +13741,9 @@ declare module "socket:commonjs/module" { * accessor the 'exports' field. * @ignore */ - export class Scope { + export class ModuleScope { /** - * `Scope` class constructor. + * `ModuleScope` class constructor. * @param {Module} module */ constructor(module: Module); @@ -13273,6 +13800,7 @@ declare module "socket:commonjs/module" { } /** * A WASM module loader + */ export class WASMModuleLoader extends ModuleLoader { } @@ -13436,9 +13964,9 @@ declare module "socket:commonjs/module" { get exports(): any; /** * The scope of the module given to parsed modules. - * @type {Scope} + * @type {ModuleScope} */ - get scope(): Scope; + get scope(): ModuleScope; /** * The origin of the loaded module. * @type {string} @@ -14505,6 +15033,7 @@ declare module "socket:internal/error" { * @type {number} */ export const DEFAULT_ERROR_STACK_TRACE_LIMIT: number; + export const DefaultPlatformError: ErrorConstructor; export const Error: ErrorConstructor; export const URIError: ErrorConstructor; export const EvalError: ErrorConstructor; @@ -14602,53 +15131,6 @@ declare module "socket:internal/promise" { } } -declare module "socket:internal/streams" { - export class ReadableStream extends globalThis.ReadableStream { - constructor(options: any); - getReader(options: any): any; - } - export const ReadableStreamBYOBReader: { - new (stream: globalThis.ReadableStream): ReadableStreamBYOBReader; - prototype: ReadableStreamBYOBReader; - } | { - new (): { - "__#91@#closed": Deferred; - "__#91@#cancellation": ReadableStreamBYOBReaderCancellation; - readonly closed: Promise; - cancel(reason?: any): Promise; - read(view: any): ReadableStreamBYOBReadResult; - }; - }; - export const ReadableStreamBYOBRequest: { - new (): ReadableStreamBYOBRequest; - prototype: ReadableStreamBYOBRequest; - } | { - new (): { - "__#92@#view": any; - readonly view: any; - respond(bytesWritten: any): void; - respondWithNewView(view: any): void; - }; - }; - export const ReadableByteStreamController: { - new (): {}; - }; - namespace _default { - export { ReadableStream }; - } - export default _default; - import { Deferred } from "socket:async/deferred"; - class ReadableStreamBYOBReaderCancellation { - reason: any; - state: boolean; - } - class ReadableStreamBYOBReadResult { - constructor(value?: any, done?: boolean); - value: any; - done: boolean; - } -} - declare module "socket:service-worker/registration" { export class ServiceWorkerRegistration { constructor(info: any, serviceWorker: any); From ed48092b76a690a073eb4653692536bf64a24a56 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 02:27:23 -0400 Subject: [PATCH 0524/1178] refactor(api/commonjs): improve resolvers --- api/commonjs/builtins.js | 2 +- api/commonjs/module.js | 2 +- api/commonjs/require.js | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index e5d4b0acf6..97dfa6f16b 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -46,7 +46,7 @@ import * as timers from '../timers.js' import * as tty from '../tty.js' import * as url from '../url.js' import util from '../util.js' -import * as vm from '../vm.js' +import vm from '../vm.js' import * as window from '../window.js' // eslint-disable-next-line import * as worker_threads from '../worker_threads.js' diff --git a/api/commonjs/module.js b/api/commonjs/module.js index ec7ce624ed..ae20521c3d 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -10,7 +10,7 @@ import process from '../process.js' import path from '../path.js' /** - * @typedef {import('./require.js').RequireResolver[]} ModuleResolver + * @typedef {function(string, Module, function(string): any): any} ModuleResolver */ /** diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 94c785e3c2..0846ac4123 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -1,6 +1,7 @@ import { DEFAULT_PACKAGE_PREFIX, Package } from './package.js' import { getBuiltin, isBuiltin } from './builtins.js' import { ModuleNotFoundError } from '../errors.js' +import { isFunction } from '../util/types.js' import location from '../location.js' /** @@ -12,7 +13,8 @@ import location from '../location.js' * module: import('./module.js').Module, * prefix?: string, * request?: import('./loader.js').RequestOptions, - * builtins?: object + * builtins?: object, + * resolvers?: RequireFunction[] * }} CreateRequireOptions */ @@ -92,7 +94,7 @@ export class Meta { * @return {RequireFunction} */ export function createRequire (options) { - const { builtins, headers, module, prefix } = options + const { builtins, headers, resolvers, module, prefix } = options const { cache, loaders, main } = module // non-standard 'require.meta' object @@ -104,8 +106,10 @@ export function createRequire (options) { return Object.assign(require, { extensions: loaders, + resolvers: module.resolvers.concat(resolvers).filter(isFunction), resolve, loaders, + module, cache, meta, main @@ -141,8 +145,10 @@ export function createRequire (options) { */ function applyResolvers (input, options = null) { const resolvers = Array - .from(module.resolvers) + .from([]) .concat(options?.resolvers) + .concat(require.resolvers) + .concat(module.resolvers) .filter(Boolean) return next(input) @@ -166,6 +172,10 @@ export function createRequire (options) { * @return {any} */ function require (input, options = null) { + if (input instanceof URL) { + input = input.href + } + const resolvedInput = applyResolvers(input, options) if (resolvedInput && typeof resolvedInput !== 'string') { @@ -253,7 +263,7 @@ export function createRequire (options) { */ function resolve (input, options = null) { if (input instanceof URL) { - input = String(input) + input = input.href } const resolvedInput = applyResolvers(input, options) From 175caaab6f492e3d39e996e28009d2bc04519965 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 02:27:45 -0400 Subject: [PATCH 0525/1178] refactor(api/http): use async resource and context --- api/http.js | 33 +++++++++++++++++++++++++++++++++ api/http/adapters.js | 25 ++++++++++++++++++++----- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/api/http.js b/api/http.js index 63cb7580c2..5a8a882b56 100644 --- a/api/http.js +++ b/api/http.js @@ -1,4 +1,6 @@ import { Writable, Readable, Duplex } from './stream.js' +import { AsyncResource } from './async/resource.js' +import { AsyncContext } from './async/context.js' import { EventEmitter } from './events.js' import { toProperCase } from './util.js' import { Buffer } from './buffer.js' @@ -510,6 +512,7 @@ export class IncomingMessage extends Readable { #statusMessage = null #statusCode = 0 #complete = false + #context = new AsyncContext.Variable() #headers = {} #timeout = null #method = 'GET' @@ -596,6 +599,20 @@ export class IncomingMessage extends Readable { } } + /** + * @type {Server} + */ + get server () { + return this.#server + } + + /** + * @type {AsyncContext.Variable} + */ + get context () { + return this.#context + } + /** * This property will be `true` if a complete HTTP message has been received * and successfully parsed. @@ -1003,6 +1020,13 @@ export class ServerResponse extends OutgoingMessage { this.#server = options?.server ?? null } + /** + * @type {Server} + */ + get server () { + return this.#server + } + /** * A reference to the original HTTP request object. * @type {IncomingMessage} @@ -1498,6 +1522,7 @@ export class Server extends EventEmitter { #listening = false #adapter = null #closed = false + #resource = new AsyncResource('HTTPServer') #port = 0 #host = null @@ -1509,6 +1534,14 @@ export class Server extends EventEmitter { keepAliveTimeout = 0 headersTimeout = 60000 + /** + * @ignore + * @type {AsyncResource} + */ + get resource () { + return this.#resource + } + /** * The adapter interface for this `Server` instance. * @ignore diff --git a/api/http/adapters.js b/api/http/adapters.js index 3afbd68e30..d738a57001 100644 --- a/api/http/adapters.js +++ b/api/http/adapters.js @@ -1,4 +1,4 @@ -import { Deferred } from '../async.js' +import { Deferred, AsyncContext } from '../async.js' import { Buffer } from '../buffer.js' import process from '../process.js' import assert from '../assert.js' @@ -19,6 +19,7 @@ import assert from '../assert.js' */ export class ServerAdapter extends EventTarget { #server = null + #context = new AsyncContext.Variable() #httpInterface = null /** @@ -52,6 +53,14 @@ export class ServerAdapter extends EventTarget { return this.#httpInterface } + /** + * A readonly reference to the `AsyncContext.Variable` associated with this + * `ServerAdapter` instance. + */ + get context () { + return this.#context + } + /** * Called when the adapter should destroy itself. * @abstract @@ -156,6 +165,8 @@ export class ServiceWorkerServerAdapter extends ServerAdapter { const deferred = new Deferred() + event.respondWith(deferred.promise) + const incomingMessage = new this.httpInterface.IncomingMessage({ complete: !/post|put/i.test(event.request.method), headers: event.request.headers, @@ -175,10 +186,14 @@ export class ServiceWorkerServerAdapter extends ServerAdapter { serverResponse ) - this.server.emit('connection', connection) - event.respondWith(deferred.promise) - - this.server.emit('request', incomingMessage, serverResponse) + this.server.resource.runInAsyncScope(() => { + this.context.run({ connection, incomingMessage, serverResponse, event }, () => { + incomingMessage.context.run({ event }, () => { + this.server.emit('connection', connection) + this.server.emit('request', incomingMessage, serverResponse) + }) + }) + }) if (/post|put/i.test(event.request.method)) { const { highWaterMark } = incomingMessage._readableState From 71ddcdcc2344b70e0a773377961f98edf12fca92 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 02:28:01 -0400 Subject: [PATCH 0526/1178] refactor(api/mime): add 'params' getter --- api/mime/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/mime/index.js b/api/mime/index.js index e61373190d..89cf76d811 100644 --- a/api/mime/index.js +++ b/api/mime/index.js @@ -349,6 +349,10 @@ export class MIMEType { return `${this.type}/${this.subtype}` } + get params () { + return this.#params + } + toString () { const params = this.params.toString() From 1bf9aec0bf7888714f33ff3b75c475ae048cc8d0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 02:28:25 -0400 Subject: [PATCH 0527/1178] chore(api/path): fix docs --- api/path/posix.js | 4 ++-- api/path/win32.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/path/posix.js b/api/path/posix.js index 45a1122a11..671c43d7fa 100644 --- a/api/path/posix.js +++ b/api/path/posix.js @@ -85,10 +85,10 @@ export function dirname (path) { /** * Computes base name of path. * @param {PathComponent} path - * @param {string} suffix + * @param {string=} [suffix] * @return {string} */ -export function basename (path, suffix) { +export function basename (path, suffix = null) { return Path.basename({ sep }, path).replace(suffix || '', '') } diff --git a/api/path/win32.js b/api/path/win32.js index c41b5c3eee..c7be443b0c 100644 --- a/api/path/win32.js +++ b/api/path/win32.js @@ -84,10 +84,10 @@ export function dirname (path) { /** * Computes base name of path. * @param {PathComponent} path - * @param {string} suffix + * @param {string=} [suffix] * @return {string} */ -export function basename (path, suffix) { +export function basename (path, suffix = null) { return Path.basename({ sep }, path).replace(suffix || '', '') } From 352e3e321c92026f554de86c7baa38d1166fd748 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 02:28:51 -0400 Subject: [PATCH 0528/1178] chore(api): generate types --- api/index.d.ts | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index b56930c1e5..f7eda23437 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -2712,10 +2712,10 @@ declare module "socket:path/win32" { /** * Computes base name of path. * @param {PathComponent} path - * @param {string} suffix + * @param {string=} [suffix] * @return {string} */ - export function basename(path: PathComponent, suffix: string): string; + export function basename(path: PathComponent, suffix?: string | undefined): string; /** * Computes extension name of path. * @param {PathComponent} path @@ -2806,10 +2806,10 @@ declare module "socket:path/posix" { /** * Computes base name of path. * @param {PathComponent} path - * @param {string} suffix + * @param {string=} [suffix] * @return {string} */ - export function basename(path: PathComponent, suffix: string): string; + export function basename(path: PathComponent, suffix?: string | undefined): string; /** * Computes extension name of path. * @param {PathComponent} path @@ -5907,6 +5907,7 @@ declare module "socket:mime/index" { set subtype(value: any); get subtype(): any; get essence(): string; + get params(): any; toString(): string; toJSON(): string; #private; @@ -9436,6 +9437,11 @@ declare module "socket:http/adapters" { * @type {HTTPModuleInterface} */ get httpInterface(): HTTPModuleInterface; + /** + * A readonly reference to the `AsyncContext.Variable` associated with this + * `ServerAdapter` instance. + */ + get context(): import("socket:async/context").Variable; /** * Called when the adapter should destroy itself. * @abstract @@ -9754,6 +9760,14 @@ declare module "socket:http" { * @type {string} */ get url(): string; + /** + * @type {Server} + */ + get server(): exports.Server; + /** + * @type {AsyncContext.Variable} + */ + get context(): typeof import("socket:async/context").Variable; /** * This property will be `true` if a complete HTTP message has been received * and successfully parsed. @@ -9948,6 +9962,10 @@ declare module "socket:http" { * @param {object} options */ constructor(options: object); + /** + * @type {Server} + */ + get server(): exports.Server; /** * A reference to the original HTTP request object. * @type {IncomingMessage} @@ -10125,6 +10143,11 @@ declare module "socket:http" { maxRequestsPerSocket: number; keepAliveTimeout: number; headersTimeout: number; + /** + * @ignore + * @type {AsyncResource} + */ + get resource(): AsyncResource; /** * The adapter interface for this `Server` instance. * @ignore @@ -10217,6 +10240,7 @@ declare module "socket:http" { import { Readable } from "socket:stream"; import { EventEmitter } from "socket:events"; import { Duplex } from "socket:stream"; + import { AsyncResource } from "socket:async/resource"; import * as exports from "socket:http"; } @@ -13573,7 +13597,8 @@ declare module "socket:commonjs/require" { * module: import('./module.js').Module, * prefix?: string, * request?: import('./loader.js').RequestOptions, - * builtins?: object + * builtins?: object, + * resolvers?: RequireFunction[] * }} CreateRequireOptions */ /** @@ -13633,6 +13658,7 @@ declare module "socket:commonjs/require" { prefix?: string; request?: import("socket:commonjs/loader").RequestOptions; builtins?: object; + resolvers?: RequireFunction[]; }; export type RequireFunction = (arg0: string) => any; export type PackageOptions = import("socket:commonjs/package").PackageOptions; @@ -13665,7 +13691,7 @@ declare module "socket:commonjs/module" { */ export function createRequire(url: string | URL, options?: ModuleOptions | undefined): RequireFunction; /** - * @typedef {import('./require.js').RequireResolver[]} ModuleResolver + * @typedef {function(string, Module, function(string): any): any} ModuleResolver */ /** * @typedef {import('./require.js').RequireFunction} RequireFunction @@ -14031,7 +14057,7 @@ declare module "socket:commonjs/module" { #private; } export default Module; - export type ModuleResolver = import("socket:commonjs/require").RequireResolver[]; + export type ModuleResolver = (arg0: string, arg1: Module, arg2: (arg0: string) => any) => any; export type RequireFunction = import("socket:commonjs/require").RequireFunction; export type PackageOptions = import("socket:commonjs/package").PackageOptions; export type CreateRequireOptions = { @@ -14040,7 +14066,7 @@ declare module "socket:commonjs/module" { builtins?: object; }; export type ModuleOptions = { - resolvers?: import("socket:commonjs/require").RequireResolver[][]; + resolvers?: ModuleResolver[]; importmap?: ImportMap; loader?: Loader | object; loaders?: object; From e83dd19893ecd78974d578cfe711dc7a3b954d38 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 03:29:38 -0400 Subject: [PATCH 0529/1178] fix(api/internal/init.js): 'isSocketRuntime' --- api/internal/init.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/internal/init.js b/api/internal/init.js index 48acf7a997..7c87a3ceea 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -260,6 +260,12 @@ class RuntimeWorker extends GlobalWorker { value: true }) + Object.defineProperty(globalThis, 'isSocketRuntime', { + configurable: false, + enumerable: false, + value: true + }) + Object.defineProperty(globalThis, 'RUNTIME_WORKER_ID', { configurable: false, enumerable: false, From c3fe2c8cf67b55b449232b39b9b7d08b1ad2efd3 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 6 Apr 2024 10:45:59 +0200 Subject: [PATCH 0530/1178] fix(core): fix execSync Plain text values are able to escape from the JSON string and break it. If we're going to move a string value we should escape it for now. Later we should just return two different array buffers for {stdout,stderr} --- api/child_process.js | 8 ++++---- src/core/child_process.cc | 16 ++++++++-------- src/ipc/bridge.cc | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/child_process.js b/api/child_process.js index 9b5433d456..50f20c8572 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -659,7 +659,7 @@ export function execSync (command, options) { code: typeof code === 'string' ? code : null, signal: errorSignal || null, status: Number.isFinite(code) ? code : null, - output: [null, stdout.join('\n'), stderr.join('\n')] + output: [null, stdout, stderr] }) // @ts-ignore @@ -673,9 +673,9 @@ export function execSync (command, options) { throw error } - const output = options?.encoding === 'utf8' - ? stdout.join('\n') - : Buffer.from(stdout.join('\n')) + const output = stdout && options?.encoding === 'utf8' + ? stdout + : Buffer.from(stdout) return output } diff --git a/src/core/child_process.cc b/src/core/child_process.cc index 4d869b206c..ea3d991755 100644 --- a/src/core/child_process.cc +++ b/src/core/child_process.cc @@ -73,8 +73,8 @@ namespace SSC { const auto command = args.size() > 0 ? args.at(0) : String(""); const auto argv = join(args.size() > 1 ? Vector{ args.begin() + 1, args.end() } : Vector{}, " "); - auto stdoutBuffer = new JSON::Array{}; - auto stderrBuffer = new JSON::Array{}; + StringStream* stdoutBuffer = new StringStream; + StringStream* stderrBuffer = new StringStream; const auto onStdout = [=](const String& output) mutable { if (!options.allowStdout || output.size() == 0) { @@ -82,7 +82,7 @@ namespace SSC { } if (stdoutBuffer != nullptr) { - stdoutBuffer->push(output); + *stdoutBuffer << String(output); } }; @@ -92,7 +92,7 @@ namespace SSC { } if (stderrBuffer != nullptr) { - stderrBuffer->push(output); + *stderrBuffer << String(output); } }; @@ -112,8 +112,8 @@ namespace SSC { {"data", JSON::Object::Entries { {"id", std::to_string(id)}, {"pid", std::to_string(pid)}, - {"stdout", *stdoutBuffer}, - {"stderr", *stderrBuffer}, + {"stdout", encodeURIComponent(stdoutBuffer->str())}, + {"stderr", encodeURIComponent(stderrBuffer->str())}, {"code", code} }} }; @@ -156,8 +156,8 @@ namespace SSC { {"err", JSON::Object::Entries { {"id", std::to_string(id)}, {"pid", std::to_string(pid)}, - {"stdout", *stdoutBuffer}, - {"stderr", *stderrBuffer}, + {"stdout", encodeURIComponent(stdoutBuffer->str())}, + {"stderr", encodeURIComponent(stderrBuffer->str())}, {"code", "ETIMEDOUT"} }} }; diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 265c2e18a4..9c34a58fe0 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -404,7 +404,7 @@ static void initRouterTable (Router *router) { if (args.size() == 0 || args.at(0).size() == 0) { auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, + {"source", "child_process.exec"}, {"err", JSON::Object::Entries { {"message", "Spawn requires at least one argument with a length greater than zero"}, }} From 2a9b66df2ca82c6520694eb9141d19ae75ef0ea6 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 6 Apr 2024 11:10:05 +0200 Subject: [PATCH 0531/1178] fix(internal): don't try to write to read-only properties --- api/internal/post-message.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/internal/post-message.js b/api/internal/post-message.js index 6fd7cd0f8f..6fb6e4b152 100644 --- a/api/internal/post-message.js +++ b/api/internal/post-message.js @@ -39,7 +39,10 @@ function map (object, callback) { } else if (object && typeof object === 'object') { object = callback(object) for (const key in object) { - object[key] = map(object[key], callback) + const descriptor = Object.getOwnPropertyDescriptor(object, key) + if (descriptor && descriptor.writable) { + object[key] = map(object[key], callback) + } } } From 7a032a25ed06ac0db5fca6b911e55bb25d352c38 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sat, 6 Apr 2024 10:49:16 -0400 Subject: [PATCH 0532/1178] fix(src/ios/main.mm): fix missing 'npm:' and 'node:' prefix --- src/ios/main.mm | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ios/main.mm b/src/ios/main.mm index fa56e9c6d4..6912fa5515 100644 --- a/src/ios/main.mm +++ b/src/ios/main.mm @@ -345,6 +345,36 @@ - (BOOL) application: (UIApplication*) application forURLScheme: @"socket" ]; + [config + setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"node" + ]; + + [config + setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @"npm" + ]; + + for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (core->protocolHandlers.registerHandler(scheme)) { + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @(replace(trim(scheme), ":", "").c_str())]; + } + } + + for (const auto& entry : opts.userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + const auto data = entry.second; + if (core->protocolHandlers.registerHandler(scheme, { data })) { + [config setURLSchemeHandler: bridge->router.schemeHandler + forURLScheme: @(scheme.c_str())]; + } + } + } + self.content = config.userContentController; [self.content From 6bd3cdaad9dcdd886acde83600f2b2610b7d4815 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Tue, 9 Apr 2024 23:13:37 +0200 Subject: [PATCH 0533/1178] fix(fs): flags should be optional on copyFile, improve config copy --- api/CONFIG.md | 56 +++++++++++++++++++----------------- api/fs/index.js | 6 ++-- api/fs/promises.js | 2 +- api/index.d.ts | 6 ++-- bin/docs-generator/config.js | 18 ++++++------ 5 files changed, 46 insertions(+), 42 deletions(-) diff --git a/api/CONFIG.md b/api/CONFIG.md index a34975a8f2..ce3fb7c3bf 100644 --- a/api/CONFIG.md +++ b/api/CONFIG.md @@ -1,14 +1,16 @@ -# Configuration basics +# Configuration +## Overview -The configuration file is a simple INI `socket.ini` file in the root of the project. -The file is read on startup and the values are used to configure the project. -Sometimes it's useful to overide the values in `socket.ini` or keep some of the values local (e.g. `[ios] simulator_device`) -or secret (e.g. `[ios] codesign_identity`, `[ios] provisioning_profile`, etc.) -This can be done by creating a file called `.sscrc` in the root of the project. -It is possible to override both Command Line Interface (CLI) and Configuration File (INI) options. + +The configuration file is an INI file (`socket.ini`) in the root of every Socket runtime project. +The file is read at compile time. Sometimes it's useful to overide its values or keep some of the +values locally, only on your computer (e.g. `[ios] simulator_device`) or secrets +(e.g. `[ios] codesign_identity`, `[ios] provisioning_profile`, etc.); this can be done by +creating a file called `.sscrc` in the root of the project. It is possible to override both +Command Line Interface (CLI) and Configuration File (INI) options. Example: @@ -45,7 +47,7 @@ simulator_device = "iPhone 15" Use the full path instead. -# `build` +### `build` Key | Default Value | Description :--- | :--- | :--- @@ -58,19 +60,19 @@ name | | The name of the program and executable to be output. Can't contain sp output | "build" | The binary output path. It's recommended to add this path to .gitignore. script | | The build script. It runs before the `[build] copy` phase. -# `build.script` +### `build.script` Key | Default Value | Description :--- | :--- | :--- forward_arguments | false | If true, it will pass build arguments to the build script. WARNING: this could be deprecated in the future. -# `build.watch` +### `build.watch` Key | Default Value | Description :--- | :--- | :--- sources[] | | Configure your project to watch for sources that could change when running `ssc`. Could be a string or an array of strings -# `webview` +### `webview` Key | Default Value | Description :--- | :--- | :--- @@ -79,14 +81,14 @@ default_index | "" | Set default 'index.html' path to open for implicit routes watch | false | Tell the webview to watch for changes in its resources headers[] | "" | Custom headers injected on all webview routes -# `webview.watch` +### `webview.watch` Key | Default Value | Description :--- | :--- | :--- reload | true | Configure webview to reload when a file changes service_worker_reload_timeout | 500 | Timeout in milliseconds to wait for service worker to reload before reloading webview -# `webview.navigator.mounts` +### `webview.navigator.mounts` Key | Default Value | Description :--- | :--- | :--- @@ -94,7 +96,7 @@ $HOST_HOME/directory-in-home-folder/ | | $HOST_CONTAINER/directory-app-container/ | | $HOST_PROCESS_WORKING_DIRECTORY/directory-in-app-process-working-directory/ | | -# `permissions` +### `permissions` Key | Default Value | Description :--- | :--- | :--- @@ -111,13 +113,13 @@ allow_data_access | true | Allow/Disallow data access in application allow_airplay | true | Allow/Disallow AirPlay access in application (macOS/iOS) only allow_hotkeys | true | Allow/Disallow HotKey binding registration (desktop only) -# `debug` +### `debug` Key | Default Value | Description :--- | :--- | :--- flags | | Advanced Compiler Settings for debug purposes (ie C++ compiler -g, etc). -# `meta` +### `meta` Key | Default Value | Description :--- | :--- | :--- @@ -132,7 +134,7 @@ title | | The title of the app used in metadata files. This is NOT a window ti type | "" | Builds an extension when set to "extension". version | | A string that indicates the version of the application. It should be a semver triple like 1.2.3. Defaults to 1.0.0. -# `android` +### `android` Key | Default Value | Description :--- | :--- | :--- @@ -148,7 +150,7 @@ sources | | icon | | The icon to use for identifying your app on Android. icon_sizes | | The various sizes and scales of the icons to create, required minimum are listed by default. -# `ios` +### `ios` Key | Default Value | Description :--- | :--- | :--- @@ -160,7 +162,7 @@ nonexempt_encryption | false | Indicate to Apple if you are using encryption th icon | | The icon to use for identifying your app on iOS. icon_sizes | | The various sizes and scales of the icons to create, required minimum are listed by default. -# `linux` +### `linux` Key | Default Value | Description :--- | :--- | :--- @@ -169,7 +171,7 @@ cmd | | The command to execute to spawn the "back-end" process. icon | | The icon to use for identifying your app in Linux desktop environments. icon_sizes | | The various sizes and scales of the icons to create, required minimum are listed by default. -# `mac` +### `mac` Key | Default Value | Description :--- | :--- | :--- @@ -182,14 +184,14 @@ trafficLightPosition | | If titleBarStyle is "hiddenInset", this will determin icon | | The icon to use for identifying your app on MacOS. icon_sizes | | The various sizes and scales of the icons to create, required minimum are listed by default. -# `native` +### `native` Key | Default Value | Description :--- | :--- | :--- files | | Files that should be added to the compile step. headers | | Extra Headers -# `win` +### `win` Key | Default Value | Description :--- | :--- | :--- @@ -199,7 +201,7 @@ pfx | | A relative path to the pfx file used for signing. icon | | The signing information needed by the appx api. The icon to use for identifying your app on Windows. icon_sizes | | The various sizes and scales of the icons to create, required minimum are listed by default. -# `window` +### `window` Key | Default Value | Description :--- | :--- | :--- @@ -219,25 +221,25 @@ minimizable | true | Determines if the window is minimizable. closable | true | Determines if the window is closable. utility | false | Determines the window is utility window. -# `window.alert` +### `window.alert` Key | Default Value | Description :--- | :--- | :--- title | | The title that appears in the 'alert', 'prompt', and 'confirm' dialogs. If this value is not present, then the application title is used instead. Currently only supported on iOS/macOS. defalut value = "" -# `application` +### `application` Key | Default Value | Description :--- | :--- | :--- agent | false | If agent is set to true, the app will not display in the tab/window switcher or dock/task-bar etc. Useful if you are building a tray-only app. -# `tray` +### `tray` Key | Default Value | Description :--- | :--- | :--- icon | | The icon to be displayed in the operating system tray. On Windows, you may need to use ICO format. defalut value = "" -# `headless` +### `headless` Key | Default Value | Description :--- | :--- | :--- diff --git a/api/fs/index.js b/api/fs/index.js index 5b19bda5b4..987d3a1375 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -314,7 +314,7 @@ export function close (fd, callback) { * @param {function(Error=)=} [callback] - The function to call after completion. * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ -export function copyFile (src, dest, flags, callback) { +export function copyFile (src, dest, flags = 0, callback) { src = normalizePath(src) dest = normalizePath(dest) @@ -326,7 +326,7 @@ export function copyFile (src, dest, flags, callback) { throw new TypeError('The argument \'dest\' must be a string') } - if (!Number.isInteger(flags)) { + if (flags && !Number.isInteger(flags)) { throw new TypeError('The argument \'flags\' must be an integer') } @@ -346,7 +346,7 @@ export function copyFile (src, dest, flags, callback) { * @param {number} flags - Modifiers for copy operation. * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ -export function copyFileSync (src, dest, flags) { +export function copyFileSync (src, dest, flags = 0) { src = normalizePath(src) dest = normalizePath(dest) diff --git a/api/fs/promises.js b/api/fs/promises.js index 41f01d8c5b..bd581d036f 100644 --- a/api/fs/promises.js +++ b/api/fs/promises.js @@ -167,7 +167,7 @@ export async function chown (path, uid, gid) { * @param {number} flags - Modifiers for copy operation. * @return {Promise} */ -export async function copyFile (src, dest, flags) { +export async function copyFile (src, dest, flags = 0) { src = normalizePath(src) dest = normalizePath(dest) diff --git a/api/index.d.ts b/api/index.d.ts index f7eda23437..d98a709fe4 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -4006,7 +4006,7 @@ declare module "socket:fs/promises" { * @param {number} flags - Modifiers for copy operation. * @return {Promise} */ - export function copyFile(src: string, dest: string, flags: number): Promise; + export function copyFile(src: string, dest: string, flags?: number): Promise; /** * Chages ownership of link at `path` with `uid` and `gid. * @param {string} path @@ -4247,7 +4247,7 @@ declare module "socket:fs/index" { * @param {function(Error=)=} [callback] - The function to call after completion. * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ - export function copyFile(src: string, dest: string, flags: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; + export function copyFile(src: string, dest: string, flags?: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; /** * Synchronously copies `src` to `dest` calling `callback` upon success or error. * @param {string} src - The source file path. @@ -4255,7 +4255,7 @@ declare module "socket:fs/index" { * @param {number} flags - Modifiers for copy operation. * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ - export function copyFileSync(src: string, dest: string, flags: number): void; + export function copyFileSync(src: string, dest: string, flags?: number): void; /** * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} * @param {string | Buffer | URL} path diff --git a/bin/docs-generator/config.js b/bin/docs-generator/config.js index 24af8184d0..1d2aa64366 100644 --- a/bin/docs-generator/config.js +++ b/bin/docs-generator/config.js @@ -37,14 +37,16 @@ function parseIni (iniText) { function createConfigMd (sections) { let md = '\n' md += '\n\n' - md += '# Configuration basics\n' + md += '# Configuration\n', + md += '## Overview\n' md += ` -The configuration file is a simple INI \`socket.ini\` file in the root of the project. -The file is read on startup and the values are used to configure the project. -Sometimes it's useful to overide the values in \`socket.ini\` or keep some of the values local (e.g. \`[ios] simulator_device\`) -or secret (e.g. \`[ios] codesign_identity\`, \`[ios] provisioning_profile\`, etc.) -This can be done by creating a file called \`.sscrc\` in the root of the project. -It is possible to override both Command Line Interface (CLI) and Configuration File (INI) options. + +The configuration file is an INI file (\`socket.ini\`) in the root of every Socket runtime project. +The file is read at compile time. Sometimes it's useful to overide its values or keep some of the +values locally, only on your computer (e.g. \`[ios] simulator_device\`) or secrets +(e.g. \`[ios] codesign_identity\`, \`[ios] provisioning_profile\`, etc.); this can be done by +creating a file called \`.sscrc\` in the root of the project. It is possible to override both +Command Line Interface (CLI) and Configuration File (INI) options. Example: @@ -83,7 +85,7 @@ simulator_device = "iPhone 15" ` md += '\n' Object.entries(sections).forEach(([sectionName, settings]) => { - md += `# \`${sectionName}\`\n` + md += `### \`${sectionName}\`\n` md += '\n' md += 'Key | Default Value | Description\n' md += ':--- | :--- | :---\n' From 939ea7a0f8d723cf742e367cb1005a88ef085cc0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:32:27 -0400 Subject: [PATCH 0534/1178] refactor(api/commonjs): improve cache persistence --- api/commonjs/cache.js | 669 ++++++++++++++++++++++++++++++++++++++++- api/commonjs/loader.js | 56 ++-- 2 files changed, 695 insertions(+), 30 deletions(-) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index e9d109f452..0181e49fb3 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -1,5 +1,20 @@ +/* global ErrorEvent */ +import { Deferred } from '../async/deferred.js' +import serialize from '../internal/serialize.js' +import database from '../internal/database.js' import gc from '../gc.js' +/** + * @ignore + * @param {string?} + * @return {object|null} + */ +function parseJSON (source) { + try { + return JSON.parse(source) + } catch { return null } +} + /** * @typedef {{ * types?: object, @@ -10,16 +25,549 @@ import gc from '../gc.js' export const CACHE_CHANNEL_MESSAGE_ID = 'id' export const CACHE_CHANNEL_MESSAGE_REPLICATE = 'replicate' +/** + * @typedef {{ + * name: string + * }} StorageOptions + */ + +/** + * An storage context object with persistence and durability + * for service worker storages. + */ +export class Storage extends EventTarget { + /** + * Maximum entries that will be restored from storage into the context object. + * @type {number} + */ + static MAX_CONTEXT_ENTRIES = 16 * 1024 + + /** + * A mapping of known `Storage` instances. + * @type {Map} + */ + static instances = new Map() + + /** + * Opens an storage for a particular name. + * @param {StorageOptions} options + * @return {Promise} + */ + static async open (options) { + if (Storage.instances.has(options?.name)) { + const storage = Storage.instances.get(options.name) + await storage.ready + return storage + } + + const storage = new this(options) + Storage.instances.set(storage.name, storage) + await storage.open() + return storage + } + + #database = null + #context = {} + #proxy = null + #ready = null + #name = null + + /** + * `Storage` class constructor + * @ignore + * @param {StorageOptions} options + */ + constructor (options) { + super() + + this.#name = options.name + this.#proxy = new Proxy(this.#context, { + get: (_, property) => { + return this.#context[property] + }, + + set: (_, property, value) => { + this.#context[property] = value + if (this.database && this.database.opened) { + this.forwardRequest(this.database.put(property, value)) + } + return true + }, + + deleteProperty: (_, property) => { + if (this.database && this.database.opened) { + this.forwardRequest(this.database.delete(property)) + } + + return Reflect.deleteProperty(this.#context, property) + }, + + getOwnPropertyDescriptor: (_, property) => { + if (property in this.#context) { + return { + configurable: true, + enumerable: true, + writable: true, + value: this.#context[property] + } + } + }, + + has: (_, property) => { + return Reflect.has(this.#context, property) + }, + + ownKeys: (_) => { + return Reflect.ownKeys(this.#context) + } + }) + } + + /** + * A reference to the currently opened storage database. + * @type {import('../internal/database.js').Database} + */ + get database () { + return this.#database + } + + /** + * `true` if the storage is opened, otherwise `false`. + * @type {boolean} + */ + get opened () { + return this.#database !== null + } + + /** + * A proxied object for reading and writing storage state. + * Values written to this object must be cloneable with respect to the + * structured clone algorithm. + * @see {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm} + * @type {Proxy} + */ + get context () { + return this.#proxy + } + + /** + * The current storage name. This value is also used as the + * internal database name. + * @type {string} + */ + get name () { + return `socket.runtime.commonjs.cache.storage(${this.#name})` + } + + /** + * A promise that resolves when the storage is opened. + * @type {Promise?} + */ + get ready () { + return this.#ready?.promise + } + + /** + * @ignore + * @param {Promise} promise + */ + async forwardRequest (promise) { + try { + return await promise + } catch (error) { + this.dispatchEvent(new ErrorEvent('error', { error })) + } + } + + /** + * Resets the current storage to an empty state. + */ + async reset () { + await this.close() + await database.drop(this.name) + this.#database = null + await this.open() + } + + /** + * Synchronizes database entries into the storage context. + */ + async sync () { + const entries = await this.#database.get(undefined, { + count: Storage.MAX_CONTEXT_ENTRIES + }) + + const promises = [] + const snapshot = Object.keys(this.#context) + const delta = [] + + for (const [key, value] of entries) { + if (!snapshot.includes(key)) { + delta.push(key) + } + + this.#context[key] = value + } + + for (const key of delta) { + const value = this.#context[key] + promises.push(this.forwardRequest(this.database.put(key, value))) + } + + await Promise.all(promises) + } + + /** + * Opens the storage. + * @ignore + */ + async open () { + if (!this.#database) { + this.#ready = new Deferred() + this.#database = await database.open(this.name) + await this.sync() + this.#ready.resolve() + } + + return this.#ready.promise + } + + /** + * Closes the storage database, purging existing state. + * @ignore + */ + async close () { + await this.#database.close() + for (const key in this.#context) { + Reflect.deleteProperty(this.#context, key) + } + } +} + +/** + * Computes a commonjs session storage cache key. + * @ignore + * @param {Cache=} [cache] + * @param {string=} [key] + * @return {string} + */ +export function sessionStorageCacheKey (cache = null, key = null) { + if (cache && key) { + return `commonjs:cache:${cache.name}:${key}` + } else if (cache) { + return `commonjs:cache:${cache.name}` + } else { + return 'commonjs:cache' + } +} + +/** + * Restores values in a session storage into the cache. + * @ignore + * @param {Cache} cache + */ +export function restoreFromSessionStorage (cache) { + if ( + globalThis.sessionStorage && + typeof globalThis.sessionStorage === 'object' + ) { + const prefix = `${sessionStorageCacheKey(cache)}:` + for (const cacheKey in globalThis.sessionStorage) { + if (cacheKey.startsWith(prefix)) { + const value = parseJSON(globalThis.sessionStorage[cacheKey]) + const key = cacheKey.replace(prefix, '') + if (value && !cache.has(key)) { + if (value?.__type__) { + cache.data.set(key, cache.types.get(value.__type__).from(value, { + loader: cache.loader + })) + } else { + cache.data.set(key, value) + } + } + } + } + } +} + +/** + * Computes a commonjs session storage cache key. + * @ignore + * @param {Cache} cache + * @param {string} key + */ +export function updateSessionStorage (cache, key) { + if ( + globalThis.sessionStorage && + typeof globalThis.sessionStorage === 'object' + ) { + const cacheKey = sessionStorageCacheKey(cache, key) + if (cache.has(key)) { + const value = cache.get(key) + try { + globalThis.sessionStorage[cacheKey] = ( + JSON.stringify(serialize(value)) + ) + } catch {} + } else { + delete globalThis.sessionStorage[cacheKey] + } + } +} + +/** + * A container for `Snapshot` data storage. + */ +export class SnapshotData { + /** + * `SnapshotData` class constructor. + * @param {object=} [data] + */ + constructor (data = null) { + // make the `prototype` a `null` value + Object.setPrototypeOf(this, null) + + if (data && typeof data === 'object') { + Object.assign(this, data) + } + + this[Symbol.toStringTag] = 'Snapshot.Data' + + this.toJSON = () => { + return { ...this } + } + } +} + +/** + * A container for storing a snapshot of the cache data. + */ +export class Snapshot { + /** + * @type {typeof SnapshotData} + */ + static Data = SnapshotData + + // @ts-ignore + #data = new Snapshot.Data() + + /** + * `Snapshot` class constructor. + */ + constructor () { + for (const key in Cache.shared) { + // @ts-ignore + this.#data[key] = new Snapshot.Data(Object.fromEntries(Cache.shared[key].entries())) + } + } + + /** + * A reference to the snapshot data. + * @type {Snapshot.Data} + */ + get data () { + return this.#data + } + + /** + * @ignore + */ + [Symbol.for('socket.runtime.serialize')] () { + return { ...serialize(this.#data) } + } + + /** + * @ignore + * @return {object} + */ + toJSON () { + return { ...serialize(this.#data) } + } +} + +/** + * An interface for managing and performing operations on a collection + * of `Cache` objects. + */ +export class CacheCollection { + /** + * `CacheCollection` class constructor. + * @ignore + * @param {Cache[]|Record} collection + */ + constructor (collection) { + if (collection && typeof collection === 'object') { + if (Array.isArray(collection)) { + for (const value of collection) { + if (value instanceof Cache) { + this.add(value) + } + } + } else { + for (const key in collection) { + const value = collection[key] + this.add(key, value) + } + } + } + } + + /** + * Adds a `Cache` instance to the collection. + * @param {string|Cache} name + * @param {Cache=} [cache] + * @param {boolean} + */ + add (name, cache = null) { + if (name instanceof Cache) { + return this.add(name.name, name) + } + + if (typeof name === 'string' && cache instanceof Cache) { + if (name in Object.getPrototypeOf(this)) { + return false + } + + Object.defineProperty(this, name, { + configurable: false, + enumerable: true, + writable: false, + value: cache + }) + + return true + } + + return false + } + + /** + * Calls a method on each `Cache` object in the collection. + * @param {string} method + * @param {...any} args + * @return {Promise>} + */ + async call (method, ...args) { + const results = {} + + for (const key in this) { + const value = this[key] + if (value instanceof Cache) { + if (typeof value[method] === 'function') { + results[key] = await value[method](...args) + } + } + } + + return results + } + + async restore () { + return await this.call('restore') + } + + async reset () { + return await this.call('reset') + } + + async snapshot () { + return await this.call('snapshot') + } + + async get (key) { + return await this.call('get', key) + } + + async delete (key) { + return await this.call('delete', key) + } + + async keys (key) { + return await this.call('keys', key) + } + + async values (key) { + return await this.call('values', key) + } + + async clear (key) { + return await this.call('clear', key) + } +} + /** * A container for a shared cache that lives for the life time of * application execution. Updates to this storage are replicated to other * instances in the application context, including windows and workers. */ export class Cache { + /** + * A globally shared type mapping for the cache to use when + * derserializing a value. + * @type {Map} + */ static types = new Map() + + /** + * A globally shared cache store keyed by cache name. This is useful so + * when multiple instances of a `Cache` are created, they can share the + * same data store, reducing duplications. + * @type {Record} + */ static shared = Object.create(null) + /** + * A mapping of opened `Storage` instances. + * @type {Map} + */ + static storages = Storage.instances + + /** + * The `Cache.Snapshot` class. + * @type {typeof Snapshot} + */ + static Snapshot = Snapshot + + /** + * The `Cache.Storage` class + * @type {typeof Storage} + */ + static Storage = Storage + + /** + * Creates a snapshot of the current cache which can be serialized and + * stored in persistent storage. + * @return {Snapshot} + */ + static snapshot () { + return new Snapshot() + } + + /** + * Restore caches from persistent storage. + * @param {string[]} names + * @return {Promise} + */ + static async restore (names) { + const promises = [] + + for (const name of names) { + promises.push(Storage.open({ name }).then((storage) => { + this.storages.set(name, storage) + + Cache.shared[name] ||= new Map() + for (const key in storage.context) { + const value = storage.context[key] + Cache.shared[name].set(key, value) + } + })) + } + + await Promise.all(promises) + } + #onmessage = null + #storage = null #channel = null #loader = null #name = '' @@ -46,8 +594,13 @@ export class Cache { this.#data = Cache.shared[name] this.#types = new Map(Cache.types.entries()) this.#loader = options?.loader ?? null + this.#storage = Cache.storages.get(name) ?? new Storage({ name }) this.#channel = new BroadcastChannel(`socket.runtime.commonjs.cache.${name}`) + if (!Cache.storages.has(name)) { + Cache.storages.set(name, this.#storage) + } + if (options?.types && typeof options.types === 'object') { for (const key in options.types) { const value = options.types[key] @@ -85,6 +638,7 @@ export class Cache { } this.#channel.addEventListener('message', this.#onmessage) + this.#storage.open() gc.ref(this) } @@ -105,6 +659,14 @@ export class Cache { return this.#loader } + /** + * A reference to the persisted storage. + * @type {Storage} + */ + get storage () { + return this.#storage + } + /** * The cache name * @type {string} @@ -137,13 +699,104 @@ export class Cache { return this.#data.size } + /** + * @type {Map} + */ + get types () { + return this.#types + } + + /** + * Resets the cache map, persisted storage, and session storage. + */ + async reset () { + const keys = this.keys() + this.#data.clear() + + for (const key of keys) { + // will call `delete` + updateSessionStorage(this, key) + } + + await this.#storage.reset() + } + + /** + * Restores cache data from session storage. + */ + async restore () { + restoreFromSessionStorage(this) + + if (!this.#storage.opened) { + await this.#storage.open() + } + + for (const key in this.#storage.context) { + const value = this.#storage.context[key] + + if (value && !this.#data.has(key)) { + if (value?.__type__) { + this.#data.set(key, this.#types.get(value.__type__).from(value, { + loader: this.loader + })) + } else { + this.#data.set(key, value) + } + } + } + } + + /** + * Creates a snapshot of the current cache which can be serialized and + * stored in persistent storage. + * @return {Snapshot.Data} + */ + snapshot () { + const snapshot = new Snapshot() + return snapshot.data[this.name] + } + /** * Get a value at `key`. * @param {string} key * @return {object|undefined} */ get (key) { - return this.#data.get(key) + const types = Array.from(this.types.values()) + let value = null + + if (this.#data.has(key)) { + value = this.#data.get(key) + } else if (key in this.#storage.context) { + value = this.#storage.context[key] + } + + if (!value) { + return + } + + // late init from type + if (value?.__type__ && this.#types.has(value.__type__)) { + value = this.#types.get(value.__type__).from(value, { + loader: this.#loader + }) + } else if (types.length === 1) { + // if there is only 1 type in this cache types mapping, it most likely is the + // general type used for this cache, so try to use it + const [Type] = types + if (typeof Type === 'function' && !(value instanceof Type)) { + if (typeof Type.from === 'function') { + value = Type.from(value, { + loader: this.#loader + }) + } + } + } + + // reset the value + this.#data.set(key, value) + + return value } /** @@ -154,6 +807,8 @@ export class Cache { */ set (key, value) { this.#data.set(key, value) + this.#storage.context[key] = serialize(value) + updateSessionStorage(this, key) return this } @@ -170,10 +825,16 @@ export class Cache { * Delete a value at `key`. * This does not replicate to shared caches. * @param {string} key - * @return {object|undefined} + * @return {boolean} */ delete (key) { - return this.#data.delete(key) + delete this.#storage.context[key] + if (this.#data.delete(key)) { + updateSessionStorage(this, key) + return true + } + + return false } /** @@ -270,7 +931,7 @@ export class Cache { [gc.finalizer] () { return { args: [this.#data, this.#channel, this.#onmessage], - handle (data, channel, onmessage) { + async handle (data, channel, onmessage) { data.clear() channel.removeEventListener('message', onmessage) } diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index ca929ea253..45c2a4f337 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -2,9 +2,9 @@ /** * @module CommonJS.Loader */ +import { CacheCollection, Cache } from './cache.js' import InternalSymbols from '../internal/symbols.js' import { Headers } from '../ipc.js' -import { Cache } from './cache.js' import location from '../location.js' import path from '../path.js' @@ -63,8 +63,10 @@ export class RequestStatus { * @param {object} json * @return {RequestStatus} */ - static from (json) { - return new this(null, json) + static from (json, options) { + const status = new this(null, json) + status.request = Request.from({ url: json.id }, { ...options, status }) + return status } #status = undefined @@ -99,7 +101,7 @@ export class RequestStatus { set request (request) { this.#request = request - if (request.loader) { + if (!this.#status && request.loader?.cache?.status) { this.#status = request.loader.cache.status.get(request.id)?.value } } @@ -223,6 +225,10 @@ export class RequestStatus { const contentLocation = this.#headers.get('content-location') + if (this.#request) { + this.#request.loader.cache.status.set(this.id, this) + } + // verify 'Content-Location' header if given in response // @ts-ignore if (this.#request && contentLocation && URL.canParse(contentLocation, this.origin)) { @@ -495,8 +501,8 @@ export class Response { */ static from (json, options) { return new this({ - request: Request.from({ url: json.id }, options), - ...json + ...json, + request: Request.from({ url: json.id }, options) }, options) } @@ -605,7 +611,7 @@ export class Response { * @type {boolean} */ get ok () { - return this.status >= 200 && this.status < 400 + return this.id && this.status >= 200 && this.status < 400 } /** @@ -699,17 +705,7 @@ export class Loader { '.wasm' ]) - #cache = { - /** - * @type {Cache?} - */ - response: null, - - /** - * @type {Cache?} - */ - status: null - } + #cache = new CacheCollection() #origin = null #headers = new Headers() @@ -744,14 +740,6 @@ export class Loader { } } - this.#cache.response = options?.cache?.response instanceof Cache - ? options.cache.response - : new Cache('loader.response', { loader: this, types: { Response } }) - - this.#cache.status = options?.cache?.status instanceof Cache - ? options.cache.status - : new Cache('loader.status', { loader: this, types: { RequestStatus } }) - if (options?.extensions && typeof options.extensions === 'object') { if (Array.isArray(options.extensions) || options instanceof Set) { for (const value of options.extensions) { @@ -760,6 +748,22 @@ export class Loader { } } } + + this.#cache.add( + 'status', + options?.cache?.status instanceof Cache + ? options.cache.status + : new Cache('loader.status', { loader: this, types: { RequestStatus } }) + ) + + this.#cache.add( + 'response', + options?.cache?.response instanceof Cache + ? options.cache.response + : new Cache('loader.response', { loader: this, types: { Response } }) + ) + + this.#cache.restore() } /** From 31d7efca27efafc1caa8cde922948d67fd26f392 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:32:50 -0400 Subject: [PATCH 0535/1178] fix(api/fs): fix 'mkdirSync' options/defaults --- api/fs/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/api/fs/index.js b/api/fs/index.js index 987d3a1375..c48a4fa8d8 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -602,8 +602,9 @@ export function link (src, dest, callback) { export function mkdir (path, options, callback) { path = normalizePath(path) - if ((typeof options === 'undefined') || (typeof options === 'function')) { - throw new TypeError('options must be an object.') + if (typeof options === 'function') { + callback = options + options = null } if (typeof callback !== 'function') { @@ -629,16 +630,14 @@ export function mkdir (path, options, callback) { /** * @ignore + * @param {string|URL} path + * @param {object=} [options] */ -export function mkdirSync (path, options) { +export function mkdirSync (path, options = null) { path = normalizePath(path) - if ((typeof options === 'undefined') || (typeof options === 'function')) { - throw new TypeError('options must be an object.') - } - - const mode = options.mode || 0o777 - const recursive = Boolean(options.recursive) // default to false + const mode = options?.mode || 0o777 + const recursive = Boolean(options?.recursive) // default to false if (typeof mode !== 'number') { throw new TypeError('mode must be a number.') From 297b9167768bd2dd602327171ee5d5e722694009 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:35:36 -0400 Subject: [PATCH 0536/1178] chore(api/fetch): upgrade 'whatwg-fetch' --- api/fetch/fetch.js | 746 ++++++++++++++++++++++++++------------------- 1 file changed, 430 insertions(+), 316 deletions(-) diff --git a/api/fetch/fetch.js b/api/fetch/fetch.js index 37353080ca..f1832e69d1 100644 --- a/api/fetch/fetch.js +++ b/api/fetch/fetch.js @@ -1,527 +1,641 @@ // node_modules/whatwg-fetch/fetch.js -var g = typeof globalThis !== "undefined" && globalThis || typeof self !== "undefined" && self || // eslint-disable-next-line no-undef -typeof global !== "undefined" && global || {}; +/* eslint-disable no-prototype-builtins */ +var g = + (typeof globalThis !== 'undefined' && globalThis) || + (typeof self !== 'undefined' && self) || + // eslint-disable-next-line no-undef + (typeof global !== 'undefined' && global) || + {} + var support = { - searchParams: "URLSearchParams" in g, - iterable: "Symbol" in g && "iterator" in Symbol, - blob: "FileReader" in g && "Blob" in g && function() { - try { - new Blob(); - return true; - } catch (e) { - return false; - } - }(), - formData: "FormData" in g, - arrayBuffer: "ArrayBuffer" in g -}; + searchParams: 'URLSearchParams' in g, + iterable: 'Symbol' in g && 'iterator' in Symbol, + blob: + 'FileReader' in g && + 'Blob' in g && + (function() { + try { + new Blob() + return true + } catch (e) { + return false + } + })(), + formData: 'FormData' in g, + arrayBuffer: 'ArrayBuffer' in g +} + function isDataView(obj) { - return obj && DataView.prototype.isPrototypeOf(obj); + return obj && DataView.prototype.isPrototypeOf(obj) } + if (support.arrayBuffer) { - viewClasses = [ - "[object Int8Array]", - "[object Uint8Array]", - "[object Uint8ClampedArray]", - "[object Int16Array]", - "[object Uint16Array]", - "[object Int32Array]", - "[object Uint32Array]", - "[object Float32Array]", - "[object Float64Array]" - ]; - isArrayBufferView = ArrayBuffer.isView || function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; - }; -} -var viewClasses; -var isArrayBufferView; + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ] + + var isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 + } +} + function normalizeName(name) { - if (typeof name !== "string") { - name = String(name); + if (typeof name !== 'string') { + name = String(name) } - if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === "") { - throw new TypeError('Invalid character in header field name: "' + name + '"'); + if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') { + throw new TypeError('Invalid character in header field name: "' + name + '"') } - return name.toLowerCase(); + return name.toLowerCase() } + function normalizeValue(value) { - if (typeof value !== "string") { - value = String(value); + if (typeof value !== 'string') { + value = String(value) } - return value; + return value } + +// Build a destructive iterator for the value list function iteratorFor(items) { var iterator = { next: function() { - var value = items.shift(); - return { done: value === void 0, value }; + var value = items.shift() + return {done: value === undefined, value: value} } - }; + } + if (support.iterable) { iterator[Symbol.iterator] = function() { - return iterator; - }; + return iterator + } } - return iterator; + + return iterator } -function Headers(headers) { - this.map = {}; + +export function Headers(headers) { + this.map = {} + if (headers instanceof Headers) { headers.forEach(function(value, name) { - this.append(name, value); - }, this); + this.append(name, value) + }, this) } else if (Array.isArray(headers)) { headers.forEach(function(header) { if (header.length != 2) { - throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length); + throw new TypeError('Headers constructor: expected name/value pair to be length 2, found' + header.length) } - this.append(header[0], header[1]); - }, this); + this.append(header[0], header[1]) + }, this) } else if (headers) { Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]); - }, this); + this.append(name, headers[name]) + }, this) } } + Headers.prototype.append = function(name, value) { - name = normalizeName(name); - value = normalizeValue(value); - var oldValue = this.map[name]; - this.map[name] = oldValue ? oldValue + ", " + value : value; -}; -Headers.prototype["delete"] = function(name) { - delete this.map[normalizeName(name)]; -}; + name = normalizeName(name) + value = normalizeValue(value) + var oldValue = this.map[name] + this.map[name] = oldValue ? oldValue + ', ' + value : value +} + +Headers.prototype['delete'] = function(name) { + delete this.map[normalizeName(name)] +} + Headers.prototype.get = function(name) { - name = normalizeName(name); - return this.has(name) ? this.map[name] : null; -}; + name = normalizeName(name) + return this.has(name) ? this.map[name] : null +} + Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)); -}; + return this.map.hasOwnProperty(normalizeName(name)) +} + Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = normalizeValue(value); -}; + this.map[normalizeName(name)] = normalizeValue(value) +} + Headers.prototype.forEach = function(callback, thisArg) { for (var name in this.map) { if (this.map.hasOwnProperty(name)) { - callback.call(thisArg, this.map[name], name, this); + callback.call(thisArg, this.map[name], name, this) } } -}; +} + Headers.prototype.keys = function() { - var items = []; + var items = [] this.forEach(function(value, name) { - items.push(name); - }); - return iteratorFor(items); -}; + items.push(name) + }) + return iteratorFor(items) +} + Headers.prototype.values = function() { - var items = []; + var items = [] this.forEach(function(value) { - items.push(value); - }); - return iteratorFor(items); -}; + items.push(value) + }) + return iteratorFor(items) +} + Headers.prototype.entries = function() { - var items = []; + var items = [] this.forEach(function(value, name) { - items.push([name, value]); - }); - return iteratorFor(items); -}; + items.push([name, value]) + }) + return iteratorFor(items) +} + if (support.iterable) { - Headers.prototype[Symbol.iterator] = Headers.prototype.entries; + Headers.prototype[Symbol.iterator] = Headers.prototype.entries } + function consumed(body) { - if (body._noBody) - return; + if (body._noBody) return if (body.bodyUsed) { - return Promise.reject(new TypeError("Already read")); + return Promise.reject(new TypeError('Already read')) } - body.bodyUsed = true; + body.bodyUsed = true } + function fileReaderReady(reader) { return new Promise(function(resolve, reject) { reader.onload = function() { - resolve(reader.result); - }; + resolve(reader.result) + } reader.onerror = function() { - reject(reader.error); - }; - }); + reject(reader.error) + } + }) } + function readBlobAsArrayBuffer(blob) { - var reader = new FileReader(); - var promise = fileReaderReady(reader); - reader.readAsArrayBuffer(blob); - return promise; + var reader = new FileReader() + var promise = fileReaderReady(reader) + reader.readAsArrayBuffer(blob) + return promise } + function readBlobAsText(blob) { - var reader = new FileReader(); - var promise = fileReaderReady(reader); - var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type); - var encoding = match ? match[1] : "utf-8"; - reader.readAsText(blob, encoding); - return promise; + var reader = new FileReader() + var promise = fileReaderReady(reader) + var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type) + var encoding = match ? match[1] : 'utf-8' + reader.readAsText(blob, encoding) + return promise } + function readArrayBufferAsText(buf) { - var view = new Uint8Array(buf); - var chars = new Array(view.length); + var view = new Uint8Array(buf) + var chars = new Array(view.length) + for (var i = 0; i < view.length; i++) { - chars[i] = String.fromCharCode(view[i]); + chars[i] = String.fromCharCode(view[i]) } - return chars.join(""); + return chars.join('') } + function bufferClone(buf) { if (buf.slice) { - return buf.slice(0); + return buf.slice(0) } else { - var view = new Uint8Array(buf.byteLength); - view.set(new Uint8Array(buf)); - return view.buffer; + var view = new Uint8Array(buf.byteLength) + view.set(new Uint8Array(buf)) + return view.buffer } } + function Body() { - this.bodyUsed = false; + this.bodyUsed = false + this._initBody = function(body) { - this.bodyUsed = this.bodyUsed; - this._bodyInit = body; + /* + fetch-mock wraps the Response object in an ES6 Proxy to + provide useful test harness features such as flush. However, on + ES5 browsers without fetch or Proxy support pollyfills must be used; + the proxy-pollyfill is unable to proxy an attribute unless it exists + on the object before the Proxy is created. This change ensures + Response.bodyUsed exists on the instance, while maintaining the + semantic of setting Request.bodyUsed in the constructor before + _initBody is called. + */ + // eslint-disable-next-line no-self-assign + this.bodyUsed = this.bodyUsed + this._bodyInit = body if (!body) { this._noBody = true; - this._bodyText = ""; - } else if (typeof body === "string") { - this._bodyText = body; + this._bodyText = '' + } else if (typeof body === 'string') { + this._bodyText = body } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body; + this._bodyBlob = body } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body; + this._bodyFormData = body } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString(); + this._bodyText = body.toString() } else if (support.arrayBuffer && support.blob && isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer); - this._bodyInit = new Blob([this._bodyArrayBuffer]); + this._bodyArrayBuffer = bufferClone(body.buffer) + // IE 10-11 can't handle a DataView body. + this._bodyInit = new Blob([this._bodyArrayBuffer]) } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body); + this._bodyArrayBuffer = bufferClone(body) } else { - this._bodyText = body = Object.prototype.toString.call(body); + this._bodyText = body = Object.prototype.toString.call(body) } - if (!this.headers.get("content-type")) { - if (typeof body === "string") { - this.headers.set("content-type", "text/plain;charset=UTF-8"); + + if (!this.headers.get('content-type')) { + if (typeof body === 'string') { + this.headers.set('content-type', 'text/plain;charset=UTF-8') } else if (this._bodyBlob && this._bodyBlob.type) { - this.headers.set("content-type", this._bodyBlob.type); + this.headers.set('content-type', this._bodyBlob.type) } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this.headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8"); + this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') } } - }; + } + if (support.blob) { this.blob = function() { - var rejected = consumed(this); + var rejected = consumed(this) if (rejected) { - return rejected; + return rejected } + if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob); + return Promise.resolve(this._bodyBlob) } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])); + return Promise.resolve(new Blob([this._bodyArrayBuffer])) } else if (this._bodyFormData) { - throw new Error("could not read FormData body as blob"); + throw new Error('could not read FormData body as blob') } else { - return Promise.resolve(new Blob([this._bodyText])); + return Promise.resolve(new Blob([this._bodyText])) } - }; + } } + this.arrayBuffer = function() { if (this._bodyArrayBuffer) { - var isConsumed = consumed(this); + var isConsumed = consumed(this) if (isConsumed) { - return isConsumed; + return isConsumed } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) { return Promise.resolve( this._bodyArrayBuffer.buffer.slice( this._bodyArrayBuffer.byteOffset, this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength ) - ); + ) } else { - return Promise.resolve(this._bodyArrayBuffer); + return Promise.resolve(this._bodyArrayBuffer) } } else if (support.blob) { - return this.blob().then(readBlobAsArrayBuffer); + return this.blob().then(readBlobAsArrayBuffer) } else { - throw new Error("could not read as ArrayBuffer"); + throw new Error('could not read as ArrayBuffer') } - }; + } + this.text = function() { - var rejected = consumed(this); + var rejected = consumed(this) if (rejected) { - return rejected; + return rejected } + if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob); + return readBlobAsText(this._bodyBlob) } else if (this._bodyArrayBuffer) { - return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)); + return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) } else if (this._bodyFormData) { - throw new Error("could not read FormData body as text"); + throw new Error('could not read FormData body as text') } else { - return Promise.resolve(this._bodyText); + return Promise.resolve(this._bodyText) } - }; + } + if (support.formData) { this.formData = function() { - return this.text().then(decode); - }; + return this.text().then(decode) + } } + this.json = function() { - return this.text().then(JSON.parse); - }; - return this; + return this.text().then(JSON.parse) + } + + return this } -var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"]; + +// HTTP methods whose capitalization should be normalized +var methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'] + function normalizeMethod(method) { - var upcased = method.toUpperCase(); - return methods.indexOf(upcased) > -1 ? upcased : method; + var upcased = method.toUpperCase() + return methods.indexOf(upcased) > -1 ? upcased : method } -function Request(input, options) { + +export function Request(input, options) { if (!(this instanceof Request)) { - throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.'); + throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.') } - options = options || {}; - var body = options.body; + + options = options || {} + var body = options.body + if (input instanceof Request) { if (input.bodyUsed) { - throw new TypeError("Already read"); + throw new TypeError('Already read') } - this.url = input.url; - this.credentials = input.credentials; + this.url = input.url + this.credentials = input.credentials if (!options.headers) { - this.headers = new Headers(input.headers); + this.headers = new Headers(input.headers) } - this.method = input.method; - this.mode = input.mode; - this.signal = input.signal; + this.method = input.method + this.mode = input.mode + this.signal = input.signal if (!body && input._bodyInit != null) { - body = input._bodyInit; - input.bodyUsed = true; + body = input._bodyInit + input.bodyUsed = true } } else { - this.url = String(input); + this.url = String(input) } - this.credentials = options.credentials || this.credentials || "same-origin"; + + this.credentials = options.credentials || this.credentials || 'same-origin' if (options.headers || !this.headers) { - this.headers = new Headers(options.headers); + this.headers = new Headers(options.headers) } - this.method = normalizeMethod(options.method || this.method || "GET"); - this.mode = options.mode || this.mode || null; - this.signal = options.signal || this.signal || function() { - if ("AbortController" in g) { + this.method = normalizeMethod(options.method || this.method || 'GET') + this.mode = options.mode || this.mode || null + this.signal = options.signal || this.signal || (function () { + if ('AbortController' in g) { var ctrl = new AbortController(); return ctrl.signal; } - }(); - this.referrer = null; - if ((this.method === "GET" || this.method === "HEAD") && body) { - throw new TypeError("Body not allowed for GET or HEAD requests"); + }()); + this.referrer = null + + if ((this.method === 'GET' || this.method === 'HEAD') && body) { + throw new TypeError('Body not allowed for GET or HEAD requests') } - this._initBody(body); - if (this.method === "GET" || this.method === "HEAD") { - if (options.cache === "no-store" || options.cache === "no-cache") { - var reParamSearch = /([?&])_=[^&]*/; + this._initBody(body) + + if (this.method === 'GET' || this.method === 'HEAD') { + if (options.cache === 'no-store' || options.cache === 'no-cache') { + // Search for a '_' parameter in the query string + var reParamSearch = /([?&])_=[^&]*/ if (reParamSearch.test(this.url)) { - this.url = this.url.replace(reParamSearch, "$1_=" + (/* @__PURE__ */ new Date()).getTime()); + // If it already exists then set the value with the current time + this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime()) } else { - var reQueryString = /\?/; - this.url += (reQueryString.test(this.url) ? "&" : "?") + "_=" + (/* @__PURE__ */ new Date()).getTime(); + // Otherwise add a new '_' parameter to the end with the current time + var reQueryString = /\?/ + this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime() } } } } + Request.prototype.clone = function() { - return new Request(this, { body: this._bodyInit }); -}; + return new Request(this, {body: this._bodyInit}) +} + function decode(body) { - var form = new FormData(); - body.trim().split("&").forEach(function(bytes) { - if (bytes) { - var split = bytes.split("="); - var name = split.shift().replace(/\+/g, " "); - var value = split.join("=").replace(/\+/g, " "); - form.append(decodeURIComponent(name), decodeURIComponent(value)); - } - }); - return form; + var form = new FormData() + body + .trim() + .split('&') + .forEach(function(bytes) { + if (bytes) { + var split = bytes.split('=') + var name = split.shift().replace(/\+/g, ' ') + var value = split.join('=').replace(/\+/g, ' ') + form.append(decodeURIComponent(name), decodeURIComponent(value)) + } + }) + return form } + function parseHeaders(rawHeaders) { - var headers = new Headers(); - var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " "); - preProcessedHeaders.split("\r").map(function(header) { - return header.indexOf("\n") === 0 ? header.substr(1, header.length) : header; - }).forEach(function(line) { - var parts = line.split(":"); - var key = parts.shift().trim(); - if (key) { - var value = parts.join(":").trim(); - try { - headers.append(key, value); - } catch (error) { - console.warn("Response " + error.message); + var headers = new Headers() + // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space + // https://tools.ietf.org/html/rfc7230#section-3.2 + var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ') + // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill + // https://github.com/github/fetch/issues/748 + // https://github.com/zloirock/core-js/issues/751 + preProcessedHeaders + .split('\r') + .map(function(header) { + return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header + }) + .forEach(function(line) { + var parts = line.split(':') + var key = parts.shift().trim() + if (key) { + var value = parts.join(':').trim() + try { + headers.append(key, value) + } catch (error) { + console.warn('Response ' + error.message) + } } - } - }); - return headers; + }) + return headers } -Body.call(Request.prototype); -function Response(bodyInit, options) { + +Body.call(Request.prototype) + +export function Response(bodyInit, options) { if (!(this instanceof Response)) { - throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.'); + throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.') } if (!options) { - options = {}; + options = {} } - this.type = "default"; - this.status = options.status === void 0 ? 200 : options.status; + + this.type = 'default' + this.status = options.status === undefined ? 200 : options.status if (this.status < 200 || this.status > 599) { - throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599]."); + throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].") } - this.ok = this.status >= 200 && this.status < 300; - this.statusText = options.statusText === void 0 ? "" : "" + options.statusText; - this.headers = new Headers(options.headers); - this.url = options.url || ""; - this._initBody(bodyInit); + this.ok = this.status >= 200 && this.status < 300 + this.statusText = options.statusText === undefined ? '' : '' + options.statusText + this.headers = new Headers(options.headers) + this.url = options.url || '' + this._initBody(bodyInit) } -Body.call(Response.prototype); + +Body.call(Response.prototype) + Response.prototype.clone = function() { return new Response(this._bodyInit, { status: this.status, statusText: this.statusText, headers: new Headers(this.headers), url: this.url - }); -}; + }) +} + Response.error = function() { - var response = new Response(null, { status: 200, statusText: "" }); - response.status = 0; - response.type = "error"; - return response; -}; -var redirectStatuses = [301, 302, 303, 307, 308]; + var response = new Response(null, {status: 200, statusText: ''}) + response.ok = false + response.status = 0 + response.type = 'error' + return response +} + +var redirectStatuses = [301, 302, 303, 307, 308] + Response.redirect = function(url, status) { if (redirectStatuses.indexOf(status) === -1) { - throw new RangeError("Invalid status code"); + throw new RangeError('Invalid status code') } - return new Response(null, { status, headers: { location: url } }); -}; -var DOMException = g.DOMException; + + return new Response(null, {status: status, headers: {location: url}}) +} + +export var DOMException = g.DOMException try { - new DOMException(); + new DOMException() } catch (err) { DOMException = function(message, name) { - this.message = message; - this.name = name; - var error = Error(message); - this.stack = error.stack; - }; - DOMException.prototype = Object.create(Error.prototype); - DOMException.prototype.constructor = DOMException; -} -function fetch(input, init) { + this.message = message + this.name = name + var error = Error(message) + this.stack = error.stack + } + DOMException.prototype = Object.create(Error.prototype) + DOMException.prototype.constructor = DOMException +} + +export function fetch(input, init) { return new Promise(function(resolve, reject) { - var request = new Request(input, init); + var request = new Request(input, init) + if (request.signal && request.signal.aborted) { - return reject(new DOMException("Aborted", "AbortError")); + return reject(new DOMException('Aborted', 'AbortError')) } - var xhr = new XMLHttpRequest(); + + var xhr = new XMLHttpRequest() + function abortXhr() { - xhr.abort(); + xhr.abort() } + xhr.onload = function() { var options = { - status: xhr.status, statusText: xhr.statusText, - headers: parseHeaders(xhr.getAllResponseHeaders() || "") - }; - options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL"); - var body = "response" in xhr ? xhr.response : xhr.responseText; + headers: parseHeaders(xhr.getAllResponseHeaders() || '') + } + // This check if specifically for when a user fetches a file locally from the file system + // Only if the status is out of a normal range + if (request.url.indexOf('file://') === 0 && (xhr.status < 200 || xhr.status > 599)) { + options.status = 200; + } else { + options.status = xhr.status; + } + options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') + var body = 'response' in xhr ? xhr.response : xhr.responseText setTimeout(function() { - resolve(new Response(body, options)); - }, 0); - }; + resolve(new Response(body, options)) + }, 0) + } + xhr.onerror = function() { setTimeout(function() { - reject(new TypeError("Network request failed")); - }, 0); - }; + reject(new TypeError('Network request failed')) + }, 0) + } + xhr.ontimeout = function() { setTimeout(function() { - reject(new TypeError("Network request failed")); - }, 0); - }; + reject(new TypeError('Network request timed out')) + }, 0) + } + xhr.onabort = function() { setTimeout(function() { - reject(new DOMException("Aborted", "AbortError")); - }, 0); - }; + reject(new DOMException('Aborted', 'AbortError')) + }, 0) + } + function fixUrl(url) { try { - return url === "" && g.location.href ? g.location.href : url; + return url === '' && g.location.href ? g.location.href : url } catch (e) { - return url; + return url } } - xhr.open(request.method, fixUrl(request.url), true); - if (request.credentials === "include") { - xhr.withCredentials = true; - } else if (request.credentials === "omit") { - xhr.withCredentials = false; + + xhr.open(request.method, fixUrl(request.url), true) + + if (request.credentials === 'include') { + xhr.withCredentials = true + } else if (request.credentials === 'omit') { + xhr.withCredentials = false } - if ("responseType" in xhr) { + + if ('responseType' in xhr) { if (support.blob) { - xhr.responseType = "blob"; - } else if (support.arrayBuffer) { - xhr.responseType = "arraybuffer"; + xhr.responseType = 'blob' + } else if ( + support.arrayBuffer + ) { + xhr.responseType = 'arraybuffer' } } - if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) { + + if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers || (g.Headers && init.headers instanceof g.Headers))) { var names = []; Object.getOwnPropertyNames(init.headers).forEach(function(name) { - names.push(normalizeName(name)); - xhr.setRequestHeader(name, normalizeValue(init.headers[name])); - }); + names.push(normalizeName(name)) + xhr.setRequestHeader(name, normalizeValue(init.headers[name])) + }) request.headers.forEach(function(value, name) { if (names.indexOf(name) === -1) { - xhr.setRequestHeader(name, value); + xhr.setRequestHeader(name, value) } - }); + }) } else { request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value); - }); + xhr.setRequestHeader(name, value) + }) } + if (request.signal) { - request.signal.addEventListener("abort", abortXhr); + request.signal.addEventListener('abort', abortXhr) + xhr.onreadystatechange = function() { + // DONE (success or failure) if (xhr.readyState === 4) { - request.signal.removeEventListener("abort", abortXhr); + request.signal.removeEventListener('abort', abortXhr) } - }; + } } - xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit); - }); -} -fetch.polyfill = true; -if (!g.fetch) { - g.fetch = fetch; - g.Headers = Headers; - g.Request = Request; - g.Response = Response; -} -export { - DOMException, + + xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) + }) +} + +export default { + fetch, Headers, Request, - Response, - fetch -}; + Response +} From 4dcd04d79a6fbd3639903b6d65109faae34a255e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:35:56 -0400 Subject: [PATCH 0537/1178] chore(api/url): upgrade 'whatwg-url' and 'urlpattern' --- api/url/url/url.js | 105 +++++++++++++++++++------------ api/url/urlpattern/urlpattern.js | 2 +- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/api/url/url/url.js b/api/url/url/url.js index 0a3da16c62..dffe00b3ca 100644 --- a/api/url/url/url.js +++ b/api/url/url/url.js @@ -732,7 +732,7 @@ var require_punycode = __commonJS({ * @memberOf punycode * @type String */ - "version": "2.1.0", + "version": "2.3.1", /** * An object of methods to convert from JavaScript's internal character * representation (UCS-2) to Unicode code points, and back. @@ -758,17 +758,17 @@ var require_regexes = __commonJS({ "node_modules/tr46/lib/regexes.js"(exports, module) { "use strict"; var combiningMarks = /[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3C\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CF3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D81-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA82C\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11000}-\u{11002}\u{11038}-\u{11046}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11082}\u{110B0}-\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{11134}\u{11145}\u{11146}\u{11173}\u{11180}-\u{11182}\u{111B3}-\u{111C0}\u{111C9}-\u{111CC}\u{111CE}\u{111CF}\u{1122C}-\u{11237}\u{1123E}\u{11241}\u{112DF}-\u{112EA}\u{11300}-\u{11303}\u{1133B}\u{1133C}\u{1133E}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11357}\u{11362}\u{11363}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11435}-\u{11446}\u{1145E}\u{114B0}-\u{114C3}\u{115AF}-\u{115B5}\u{115B8}-\u{115C0}\u{115DC}\u{115DD}\u{11630}-\u{11640}\u{116AB}-\u{116B7}\u{1171D}-\u{1172B}\u{1182C}-\u{1183A}\u{11930}-\u{11935}\u{11937}\u{11938}\u{1193B}-\u{1193E}\u{11940}\u{11942}\u{11943}\u{119D1}-\u{119D7}\u{119DA}-\u{119E0}\u{119E4}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A39}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A5B}\u{11A8A}-\u{11A99}\u{11C2F}-\u{11C36}\u{11C38}-\u{11C3F}\u{11C92}-\u{11CA7}\u{11CA9}-\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D8A}-\u{11D8E}\u{11D90}\u{11D91}\u{11D93}-\u{11D97}\u{11EF3}-\u{11EF6}\u{11F00}\u{11F01}\u{11F03}\u{11F34}-\u{11F3A}\u{11F3E}-\u{11F42}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F51}-\u{16F87}\u{16F8F}-\u{16F92}\u{16FE4}\u{16FF0}\u{16FF1}\u{1BC9D}\u{1BC9E}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D165}-\u{1D169}\u{1D16D}-\u{1D172}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{E0100}-\u{E01EF}]/u; - var combiningClassVirama = /[\u094D\u09CD\u0A4D\u0ACD\u0B4D\u0BCD\u0C4D\u0CCD\u0D3B\u0D3C\u0D4D\u0DCA\u0E3A\u0EBA\u0F84\u1039\u103A\u1714\u1734\u17D2\u1A60\u1B44\u1BAA\u1BAB\u1BF2\u1BF3\u2D7F\uA806\uA8C4\uA953\uA9C0\uAAF6\uABED\u{10A3F}\u{11046}\u{1107F}\u{110B9}\u{11133}\u{11134}\u{111C0}\u{11235}\u{112EA}\u{1134D}\u{11442}\u{114C2}\u{115BF}\u{1163F}\u{116B6}\u{1172B}\u{11839}\u{119E0}\u{11A34}\u{11A47}\u{11A99}\u{11C3F}\u{11D44}\u{11D45}\u{11D97}]/u; - var validZWNJ = /[\u0620\u0626\u0628\u062A-\u062E\u0633-\u063F\u0641-\u0647\u0649\u064A\u066E\u066F\u0678-\u0687\u069A-\u06BF\u06C1\u06C2\u06CC\u06CE\u06D0\u06D1\u06FA-\u06FC\u06FF\u0712-\u0714\u071A-\u071D\u071F-\u0727\u0729\u072B\u072D\u072E\u074E-\u0758\u075C-\u076A\u076D-\u0770\u0772\u0775-\u0777\u077A-\u077F\u07CA-\u07EA\u0841-\u0845\u0848\u084A-\u0853\u0855\u0860\u0862-\u0865\u0868\u08A0-\u08A9\u08AF\u08B0\u08B3\u08B4\u08B6-\u08B8\u08BA-\u08BD\u1807\u1820-\u1878\u1887-\u18A8\u18AA\uA840-\uA872\u{10AC0}-\u{10AC4}\u{10ACD}\u{10AD3}-\u{10ADC}\u{10ADE}-\u{10AE0}\u{10AEB}-\u{10AEE}\u{10B80}\u{10B82}\u{10B86}-\u{10B88}\u{10B8A}\u{10B8B}\u{10B8D}\u{10B90}\u{10BAD}\u{10BAE}\u{10D00}-\u{10D21}\u{10D23}\u{10F30}-\u{10F32}\u{10F34}-\u{10F44}\u{10F51}-\u{10F53}\u{1E900}-\u{1E943}][\xAD\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u061C\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u200B\u200E\u200F\u202A-\u202E\u2060-\u2064\u206A-\u206F\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFEFF\uFFF9-\uFFFB\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10F46}-\u{10F50}\u{11001}\u{11038}-\u{11046}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{13430}-\u{13438}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{1BC9D}\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1D167}-\u{1D169}\u{1D173}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E130}-\u{1E136}\u{1E2EC}-\u{1E2EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]*\u200C[\xAD\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u061C\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u200B\u200E\u200F\u202A-\u202E\u2060-\u2064\u206A-\u206F\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFEFF\uFFF9-\uFFFB\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10F46}-\u{10F50}\u{11001}\u{11038}-\u{11046}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{13430}-\u{13438}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{1BC9D}\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1D167}-\u{1D169}\u{1D173}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E130}-\u{1E136}\u{1E2EC}-\u{1E2EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]*[\u0620\u0622-\u063F\u0641-\u064A\u066E\u066F\u0671-\u0673\u0675-\u06D3\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u077F\u07CA-\u07EA\u0840-\u0855\u0860\u0862-\u0865\u0867-\u086A\u08A0-\u08AC\u08AE-\u08B4\u08B6-\u08BD\u1807\u1820-\u1878\u1887-\u18A8\u18AA\uA840-\uA871\u{10AC0}-\u{10AC5}\u{10AC7}\u{10AC9}\u{10ACA}\u{10ACE}-\u{10AD6}\u{10AD8}-\u{10AE1}\u{10AE4}\u{10AEB}-\u{10AEF}\u{10B80}-\u{10B91}\u{10BA9}-\u{10BAE}\u{10D01}-\u{10D23}\u{10F30}-\u{10F44}\u{10F51}-\u{10F54}\u{1E900}-\u{1E943}]/u; + var combiningClassVirama = /[\u094D\u09CD\u0A4D\u0ACD\u0B4D\u0BCD\u0C4D\u0CCD\u0D3B\u0D3C\u0D4D\u0DCA\u0E3A\u0EBA\u0F84\u1039\u103A\u1714\u1715\u1734\u17D2\u1A60\u1B44\u1BAA\u1BAB\u1BF2\u1BF3\u2D7F\uA806\uA82C\uA8C4\uA953\uA9C0\uAAF6\uABED\u{10A3F}\u{11046}\u{11070}\u{1107F}\u{110B9}\u{11133}\u{11134}\u{111C0}\u{11235}\u{112EA}\u{1134D}\u{11442}\u{114C2}\u{115BF}\u{1163F}\u{116B6}\u{1172B}\u{11839}\u{1193D}\u{1193E}\u{119E0}\u{11A34}\u{11A47}\u{11A99}\u{11C3F}\u{11D44}\u{11D45}\u{11D97}\u{11F41}\u{11F42}]/u; + var validZWNJ = /[\u0620\u0626\u0628\u062A-\u062E\u0633-\u063F\u0641-\u0647\u0649\u064A\u066E\u066F\u0678-\u0687\u069A-\u06BF\u06C1\u06C2\u06CC\u06CE\u06D0\u06D1\u06FA-\u06FC\u06FF\u0712-\u0714\u071A-\u071D\u071F-\u0727\u0729\u072B\u072D\u072E\u074E-\u0758\u075C-\u076A\u076D-\u0770\u0772\u0775-\u0777\u077A-\u077F\u07CA-\u07EA\u0841-\u0845\u0848\u084A-\u0853\u0855\u0860\u0862-\u0865\u0868\u0886\u0889-\u088D\u08A0-\u08A9\u08AF\u08B0\u08B3-\u08B8\u08BA-\u08C8\u1807\u1820-\u1878\u1887-\u18A8\u18AA\uA840-\uA872\u{10AC0}-\u{10AC4}\u{10ACD}\u{10AD3}-\u{10ADC}\u{10ADE}-\u{10AE0}\u{10AEB}-\u{10AEE}\u{10B80}\u{10B82}\u{10B86}-\u{10B88}\u{10B8A}\u{10B8B}\u{10B8D}\u{10B90}\u{10BAD}\u{10BAE}\u{10D00}-\u{10D21}\u{10D23}\u{10F30}-\u{10F32}\u{10F34}-\u{10F44}\u{10F51}-\u{10F53}\u{10F70}-\u{10F73}\u{10F76}-\u{10F81}\u{10FB0}\u{10FB2}\u{10FB3}\u{10FB8}\u{10FBB}\u{10FBC}\u{10FBE}\u{10FBF}\u{10FC1}\u{10FC4}\u{10FCA}\u{10FCB}\u{1E900}-\u{1E943}][\xAD\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u061C\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u200B\u200E\u200F\u202A-\u202E\u2060-\u2064\u206A-\u206F\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFEFF\uFFF9-\uFFFB\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13430}-\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]*\u200C[\xAD\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u061C\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u200B\u200E\u200F\u202A-\u202E\u2060-\u2064\u206A-\u206F\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFEFF\uFFF9-\uFFFB\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13430}-\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]*[\u0620\u0622-\u063F\u0641-\u064A\u066E\u066F\u0671-\u0673\u0675-\u06D3\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u077F\u07CA-\u07EA\u0840-\u0858\u0860\u0862-\u0865\u0867-\u086A\u0870-\u0882\u0886\u0889-\u088E\u08A0-\u08AC\u08AE-\u08C8\u1807\u1820-\u1878\u1887-\u18A8\u18AA\uA840-\uA871\u{10AC0}-\u{10AC5}\u{10AC7}\u{10AC9}\u{10ACA}\u{10ACE}-\u{10AD6}\u{10AD8}-\u{10AE1}\u{10AE4}\u{10AEB}-\u{10AEF}\u{10B80}-\u{10B91}\u{10BA9}-\u{10BAE}\u{10D01}-\u{10D23}\u{10F30}-\u{10F44}\u{10F51}-\u{10F54}\u{10F70}-\u{10F81}\u{10FB0}\u{10FB2}-\u{10FB6}\u{10FB8}-\u{10FBF}\u{10FC1}-\u{10FC4}\u{10FC9}\u{10FCA}\u{1E900}-\u{1E943}]/u; var bidiDomain = /[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05EA\u05EF-\u05F4\u0600-\u0605\u0608\u060B\u060D\u061B-\u064A\u0660-\u0669\u066B-\u066F\u0671-\u06D5\u06DD\u06E5\u06E6\u06EE\u06EF\u06FA-\u070D\u070F\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u083E\u0840-\u0858\u085E\u0860-\u086A\u0870-\u088E\u0890\u0891\u08A0-\u08C9\u08E2\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBC2\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFC\uFE70-\uFE74\uFE76-\uFEFC\u{10800}-\u{10805}\u{10808}\u{1080A}-\u{10835}\u{10837}\u{10838}\u{1083C}\u{1083F}-\u{10855}\u{10857}-\u{1089E}\u{108A7}-\u{108AF}\u{108E0}-\u{108F2}\u{108F4}\u{108F5}\u{108FB}-\u{1091B}\u{10920}-\u{10939}\u{1093F}\u{10980}-\u{109B7}\u{109BC}-\u{109CF}\u{109D2}-\u{10A00}\u{10A10}-\u{10A13}\u{10A15}-\u{10A17}\u{10A19}-\u{10A35}\u{10A40}-\u{10A48}\u{10A50}-\u{10A58}\u{10A60}-\u{10A9F}\u{10AC0}-\u{10AE4}\u{10AEB}-\u{10AF6}\u{10B00}-\u{10B35}\u{10B40}-\u{10B55}\u{10B58}-\u{10B72}\u{10B78}-\u{10B91}\u{10B99}-\u{10B9C}\u{10BA9}-\u{10BAF}\u{10C00}-\u{10C48}\u{10C80}-\u{10CB2}\u{10CC0}-\u{10CF2}\u{10CFA}-\u{10D23}\u{10D30}-\u{10D39}\u{10E60}-\u{10E7E}\u{10E80}-\u{10EA9}\u{10EAD}\u{10EB0}\u{10EB1}\u{10F00}-\u{10F27}\u{10F30}-\u{10F45}\u{10F51}-\u{10F59}\u{10F70}-\u{10F81}\u{10F86}-\u{10F89}\u{10FB0}-\u{10FCB}\u{10FE0}-\u{10FF6}\u{1E800}-\u{1E8C4}\u{1E8C7}-\u{1E8CF}\u{1E900}-\u{1E943}\u{1E94B}\u{1E950}-\u{1E959}\u{1E95E}\u{1E95F}\u{1EC71}-\u{1ECB4}\u{1ED01}-\u{1ED3D}\u{1EE00}-\u{1EE03}\u{1EE05}-\u{1EE1F}\u{1EE21}\u{1EE22}\u{1EE24}\u{1EE27}\u{1EE29}-\u{1EE32}\u{1EE34}-\u{1EE37}\u{1EE39}\u{1EE3B}\u{1EE42}\u{1EE47}\u{1EE49}\u{1EE4B}\u{1EE4D}-\u{1EE4F}\u{1EE51}\u{1EE52}\u{1EE54}\u{1EE57}\u{1EE59}\u{1EE5B}\u{1EE5D}\u{1EE5F}\u{1EE61}\u{1EE62}\u{1EE64}\u{1EE67}-\u{1EE6A}\u{1EE6C}-\u{1EE72}\u{1EE74}-\u{1EE77}\u{1EE79}-\u{1EE7C}\u{1EE7E}\u{1EE80}-\u{1EE89}\u{1EE8B}-\u{1EE9B}\u{1EEA1}-\u{1EEA3}\u{1EEA5}-\u{1EEA9}\u{1EEAB}-\u{1EEBB}]/u; - var bidiS1LTR = /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02BB-\u02C1\u02D0\u02D1\u02E0-\u02E4\u02EE\u0370-\u0373\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0482\u048A-\u052F\u0531-\u0556\u0559-\u0589\u0903-\u0939\u093B\u093D-\u0940\u0949-\u094C\u094E-\u0950\u0958-\u0961\u0964-\u0980\u0982\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C0\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09FA\u09FC\u09FD\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A40\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A76\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC0\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0\u0AE1\u0AE6-\u0AF0\u0AF9\u0B02\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B3E\u0B40\u0B47\u0B48\u0B4B\u0B4C\u0B57\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE\u0BBF\u0BC1\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0BE6-\u0BF2\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C41-\u0C44\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C77\u0C7F\u0C80\u0C82-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA\u0CCB\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1-\u0CF3\u0D02-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D4F\u0D54-\u0D61\u0D66-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E4F-\u0E5B\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00-\u0F17\u0F1A-\u0F34\u0F36\u0F38\u0F3E-\u0F47\u0F49-\u0F6C\u0F7F\u0F85\u0F88-\u0F8C\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE-\u0FDA\u1000-\u102C\u1031\u1038\u103B\u103C\u103F-\u1057\u105A-\u105D\u1061-\u1070\u1075-\u1081\u1083\u1084\u1087-\u108C\u108E-\u109C\u109E-\u10C5\u10C7\u10CD\u10D0-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1360-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u167F\u1681-\u169A\u16A0-\u16F8\u1700-\u1711\u1715\u171F-\u1731\u1734-\u1736\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17B6\u17BE-\u17C5\u17C7\u17C8\u17D4-\u17DA\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1923-\u1926\u1929-\u192B\u1930\u1931\u1933-\u1938\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A19\u1A1A\u1A1E-\u1A55\u1A57\u1A61\u1A63\u1A64\u1A6D-\u1A72\u1A80-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD\u1B04-\u1B33\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B4C\u1B50-\u1B6A\u1B74-\u1B7E\u1B82-\u1BA1\u1BA6\u1BA7\u1BAA\u1BAE-\u1BE5\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2\u1BF3\u1BFC-\u1C2B\u1C34\u1C35\u1C3B-\u1C49\u1C4D-\u1C88\u1C90-\u1CBA\u1CBD-\u1CC7\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5-\u1CF7\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200E\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u214F\u2160-\u2188\u2336-\u237A\u2395\u249C-\u24E9\u26AC\u2800-\u28FF\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D70\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u302E\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3190-\u31BF\u31F0-\u321C\u3220-\u324F\u3260-\u327B\u327F-\u32B0\u32C0-\u32CB\u32D0-\u3376\u337B-\u33DD\u33E0-\u33FE\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA60C\uA610-\uA62B\uA640-\uA66E\uA680-\uA69D\uA6A0-\uA6EF\uA6F2-\uA6F7\uA722-\uA787\uA789-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA824\uA827\uA830-\uA837\uA840-\uA873\uA880-\uA8C3\uA8CE-\uA8D9\uA8F2-\uA8FE\uA900-\uA925\uA92E-\uA946\uA952\uA953\uA95F-\uA97C\uA983-\uA9B2\uA9B4\uA9B5\uA9BA\uA9BB\uA9BE-\uA9CD\uA9CF-\uA9D9\uA9DE-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA2F\uAA30\uAA33\uAA34\uAA40-\uAA42\uAA44-\uAA4B\uAA4D\uAA50-\uAA59\uAA5C-\uAA7B\uAA7D-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAAEB\uAAEE-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB69\uAB70-\uABE4\uABE6\uABE7\uABE9-\uABEC\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uD800-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10100}\u{10102}\u{10107}-\u{10133}\u{10137}-\u{1013F}\u{1018D}\u{1018E}\u{101D0}-\u{101FC}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{10300}-\u{10323}\u{1032D}-\u{1034A}\u{10350}-\u{10375}\u{10380}-\u{1039D}\u{1039F}-\u{103C3}\u{103C8}-\u{103D5}\u{10400}-\u{1049D}\u{104A0}-\u{104A9}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{1056F}-\u{1057A}\u{1057C}-\u{1058A}\u{1058C}-\u{10592}\u{10594}\u{10595}\u{10597}-\u{105A1}\u{105A3}-\u{105B1}\u{105B3}-\u{105B9}\u{105BB}\u{105BC}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10780}-\u{10785}\u{10787}-\u{107B0}\u{107B2}-\u{107BA}\u{11000}\u{11002}-\u{11037}\u{11047}-\u{1104D}\u{11066}-\u{1106F}\u{11071}\u{11072}\u{11075}\u{11082}-\u{110B2}\u{110B7}\u{110B8}\u{110BB}-\u{110C1}\u{110CD}\u{110D0}-\u{110E8}\u{110F0}-\u{110F9}\u{11103}-\u{11126}\u{1112C}\u{11136}-\u{11147}\u{11150}-\u{11172}\u{11174}-\u{11176}\u{11182}-\u{111B5}\u{111BF}-\u{111C8}\u{111CD}\u{111CE}\u{111D0}-\u{111DF}\u{111E1}-\u{111F4}\u{11200}-\u{11211}\u{11213}-\u{1122E}\u{11232}\u{11233}\u{11235}\u{11238}-\u{1123D}\u{1123F}\u{11240}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A9}\u{112B0}-\u{112DE}\u{112E0}-\u{112E2}\u{112F0}-\u{112F9}\u{11302}\u{11303}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133D}-\u{1133F}\u{11341}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11350}\u{11357}\u{1135D}-\u{11363}\u{11400}-\u{11437}\u{11440}\u{11441}\u{11445}\u{11447}-\u{1145B}\u{1145D}\u{1145F}-\u{11461}\u{11480}-\u{114B2}\u{114B9}\u{114BB}-\u{114BE}\u{114C1}\u{114C4}-\u{114C7}\u{114D0}-\u{114D9}\u{11580}-\u{115B1}\u{115B8}-\u{115BB}\u{115BE}\u{115C1}-\u{115DB}\u{11600}-\u{11632}\u{1163B}\u{1163C}\u{1163E}\u{11641}-\u{11644}\u{11650}-\u{11659}\u{11680}-\u{116AA}\u{116AC}\u{116AE}\u{116AF}\u{116B6}\u{116B8}\u{116B9}\u{116C0}-\u{116C9}\u{11700}-\u{1171A}\u{11720}\u{11721}\u{11726}\u{11730}-\u{11746}\u{11800}-\u{1182E}\u{11838}\u{1183B}\u{118A0}-\u{118F2}\u{118FF}-\u{11906}\u{11909}\u{1190C}-\u{11913}\u{11915}\u{11916}\u{11918}-\u{11935}\u{11937}\u{11938}\u{1193D}\u{1193F}-\u{11942}\u{11944}-\u{11946}\u{11950}-\u{11959}\u{119A0}-\u{119A7}\u{119AA}-\u{119D3}\u{119DC}-\u{119DF}\u{119E1}-\u{119E4}\u{11A00}\u{11A07}\u{11A08}\u{11A0B}-\u{11A32}\u{11A39}\u{11A3A}\u{11A3F}-\u{11A46}\u{11A50}\u{11A57}\u{11A58}\u{11A5C}-\u{11A89}\u{11A97}\u{11A9A}-\u{11AA2}\u{11AB0}-\u{11AF8}\u{11B00}-\u{11B09}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C2F}\u{11C3E}-\u{11C45}\u{11C50}-\u{11C6C}\u{11C70}-\u{11C8F}\u{11CA9}\u{11CB1}\u{11CB4}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D30}\u{11D46}\u{11D50}-\u{11D59}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D8E}\u{11D93}\u{11D94}\u{11D96}\u{11D98}\u{11DA0}-\u{11DA9}\u{11EE0}-\u{11EF2}\u{11EF5}-\u{11EF8}\u{11F02}-\u{11F10}\u{11F12}-\u{11F35}\u{11F3E}\u{11F3F}\u{11F41}\u{11F43}-\u{11F59}\u{11FB0}\u{11FC0}-\u{11FD4}\u{11FFF}-\u{12399}\u{12400}-\u{1246E}\u{12470}-\u{12474}\u{12480}-\u{12543}\u{12F90}-\u{12FF2}\u{13000}-\u{1343F}\u{13441}-\u{13446}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16A60}-\u{16A69}\u{16A6E}-\u{16ABE}\u{16AC0}-\u{16AC9}\u{16AD0}-\u{16AED}\u{16AF5}\u{16B00}-\u{16B2F}\u{16B37}-\u{16B45}\u{16B50}-\u{16B59}\u{16B5B}-\u{16B61}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E9A}\u{16F00}-\u{16F4A}\u{16F50}-\u{16F87}\u{16F93}-\u{16F9F}\u{16FE0}\u{16FE1}\u{16FE3}\u{16FF0}\u{16FF1}\u{17000}-\u{187F7}\u{18800}-\u{18CD5}\u{18D00}-\u{18D08}\u{1AFF0}-\u{1AFF3}\u{1AFF5}-\u{1AFFB}\u{1AFFD}\u{1AFFE}\u{1B000}-\u{1B122}\u{1B132}\u{1B150}-\u{1B152}\u{1B155}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1BC9C}\u{1BC9F}\u{1CF50}-\u{1CFC3}\u{1D000}-\u{1D0F5}\u{1D100}-\u{1D126}\u{1D129}-\u{1D166}\u{1D16A}-\u{1D172}\u{1D183}\u{1D184}\u{1D18C}-\u{1D1A9}\u{1D1AE}-\u{1D1E8}\u{1D2C0}-\u{1D2D3}\u{1D2E0}-\u{1D2F3}\u{1D360}-\u{1D378}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D6DA}\u{1D6DC}-\u{1D714}\u{1D716}-\u{1D74E}\u{1D750}-\u{1D788}\u{1D78A}-\u{1D7C2}\u{1D7C4}-\u{1D7CB}\u{1D800}-\u{1D9FF}\u{1DA37}-\u{1DA3A}\u{1DA6D}-\u{1DA74}\u{1DA76}-\u{1DA83}\u{1DA85}-\u{1DA8B}\u{1DF00}-\u{1DF1E}\u{1DF25}-\u{1DF2A}\u{1E030}-\u{1E06D}\u{1E100}-\u{1E12C}\u{1E137}-\u{1E13D}\u{1E140}-\u{1E149}\u{1E14E}\u{1E14F}\u{1E290}-\u{1E2AD}\u{1E2C0}-\u{1E2EB}\u{1E2F0}-\u{1E2F9}\u{1E4D0}-\u{1E4EB}\u{1E4F0}-\u{1E4F9}\u{1E7E0}-\u{1E7E6}\u{1E7E8}-\u{1E7EB}\u{1E7ED}\u{1E7EE}\u{1E7F0}-\u{1E7FE}\u{1F110}-\u{1F12E}\u{1F130}-\u{1F169}\u{1F170}-\u{1F1AC}\u{1F1E6}-\u{1F202}\u{1F210}-\u{1F23B}\u{1F240}-\u{1F248}\u{1F250}\u{1F251}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B739}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2F800}-\u{2FA1D}\u{30000}-\u{3134A}\u{31350}-\u{323AF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u; + var bidiS1LTR = /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02BB-\u02C1\u02D0\u02D1\u02E0-\u02E4\u02EE\u0370-\u0373\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0482\u048A-\u052F\u0531-\u0556\u0559-\u0589\u0903-\u0939\u093B\u093D-\u0940\u0949-\u094C\u094E-\u0950\u0958-\u0961\u0964-\u0980\u0982\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C0\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09FA\u09FC\u09FD\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A40\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A76\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC0\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0\u0AE1\u0AE6-\u0AF0\u0AF9\u0B02\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B3E\u0B40\u0B47\u0B48\u0B4B\u0B4C\u0B57\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE\u0BBF\u0BC1\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0BE6-\u0BF2\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C41-\u0C44\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C77\u0C7F\u0C80\u0C82-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA\u0CCB\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1-\u0CF3\u0D02-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D4F\u0D54-\u0D61\u0D66-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E4F-\u0E5B\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00-\u0F17\u0F1A-\u0F34\u0F36\u0F38\u0F3E-\u0F47\u0F49-\u0F6C\u0F7F\u0F85\u0F88-\u0F8C\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE-\u0FDA\u1000-\u102C\u1031\u1038\u103B\u103C\u103F-\u1057\u105A-\u105D\u1061-\u1070\u1075-\u1081\u1083\u1084\u1087-\u108C\u108E-\u109C\u109E-\u10C5\u10C7\u10CD\u10D0-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1360-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u167F\u1681-\u169A\u16A0-\u16F8\u1700-\u1711\u1715\u171F-\u1731\u1734-\u1736\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17B6\u17BE-\u17C5\u17C7\u17C8\u17D4-\u17DA\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1923-\u1926\u1929-\u192B\u1930\u1931\u1933-\u1938\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A19\u1A1A\u1A1E-\u1A55\u1A57\u1A61\u1A63\u1A64\u1A6D-\u1A72\u1A80-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD\u1B04-\u1B33\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B4C\u1B50-\u1B6A\u1B74-\u1B7E\u1B82-\u1BA1\u1BA6\u1BA7\u1BAA\u1BAE-\u1BE5\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2\u1BF3\u1BFC-\u1C2B\u1C34\u1C35\u1C3B-\u1C49\u1C4D-\u1C88\u1C90-\u1CBA\u1CBD-\u1CC7\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5-\u1CF7\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200E\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u214F\u2160-\u2188\u2336-\u237A\u2395\u249C-\u24E9\u26AC\u2800-\u28FF\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D70\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u302E\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3190-\u31BF\u31F0-\u321C\u3220-\u324F\u3260-\u327B\u327F-\u32B0\u32C0-\u32CB\u32D0-\u3376\u337B-\u33DD\u33E0-\u33FE\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA60C\uA610-\uA62B\uA640-\uA66E\uA680-\uA69D\uA6A0-\uA6EF\uA6F2-\uA6F7\uA722-\uA787\uA789-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA824\uA827\uA830-\uA837\uA840-\uA873\uA880-\uA8C3\uA8CE-\uA8D9\uA8F2-\uA8FE\uA900-\uA925\uA92E-\uA946\uA952\uA953\uA95F-\uA97C\uA983-\uA9B2\uA9B4\uA9B5\uA9BA\uA9BB\uA9BE-\uA9CD\uA9CF-\uA9D9\uA9DE-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA2F\uAA30\uAA33\uAA34\uAA40-\uAA42\uAA44-\uAA4B\uAA4D\uAA50-\uAA59\uAA5C-\uAA7B\uAA7D-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAAEB\uAAEE-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB69\uAB70-\uABE4\uABE6\uABE7\uABE9-\uABEC\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uD800-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10100}\u{10102}\u{10107}-\u{10133}\u{10137}-\u{1013F}\u{1018D}\u{1018E}\u{101D0}-\u{101FC}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{10300}-\u{10323}\u{1032D}-\u{1034A}\u{10350}-\u{10375}\u{10380}-\u{1039D}\u{1039F}-\u{103C3}\u{103C8}-\u{103D5}\u{10400}-\u{1049D}\u{104A0}-\u{104A9}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{1056F}-\u{1057A}\u{1057C}-\u{1058A}\u{1058C}-\u{10592}\u{10594}\u{10595}\u{10597}-\u{105A1}\u{105A3}-\u{105B1}\u{105B3}-\u{105B9}\u{105BB}\u{105BC}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10780}-\u{10785}\u{10787}-\u{107B0}\u{107B2}-\u{107BA}\u{11000}\u{11002}-\u{11037}\u{11047}-\u{1104D}\u{11066}-\u{1106F}\u{11071}\u{11072}\u{11075}\u{11082}-\u{110B2}\u{110B7}\u{110B8}\u{110BB}-\u{110C1}\u{110CD}\u{110D0}-\u{110E8}\u{110F0}-\u{110F9}\u{11103}-\u{11126}\u{1112C}\u{11136}-\u{11147}\u{11150}-\u{11172}\u{11174}-\u{11176}\u{11182}-\u{111B5}\u{111BF}-\u{111C8}\u{111CD}\u{111CE}\u{111D0}-\u{111DF}\u{111E1}-\u{111F4}\u{11200}-\u{11211}\u{11213}-\u{1122E}\u{11232}\u{11233}\u{11235}\u{11238}-\u{1123D}\u{1123F}\u{11240}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A9}\u{112B0}-\u{112DE}\u{112E0}-\u{112E2}\u{112F0}-\u{112F9}\u{11302}\u{11303}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133D}-\u{1133F}\u{11341}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11350}\u{11357}\u{1135D}-\u{11363}\u{11400}-\u{11437}\u{11440}\u{11441}\u{11445}\u{11447}-\u{1145B}\u{1145D}\u{1145F}-\u{11461}\u{11480}-\u{114B2}\u{114B9}\u{114BB}-\u{114BE}\u{114C1}\u{114C4}-\u{114C7}\u{114D0}-\u{114D9}\u{11580}-\u{115B1}\u{115B8}-\u{115BB}\u{115BE}\u{115C1}-\u{115DB}\u{11600}-\u{11632}\u{1163B}\u{1163C}\u{1163E}\u{11641}-\u{11644}\u{11650}-\u{11659}\u{11680}-\u{116AA}\u{116AC}\u{116AE}\u{116AF}\u{116B6}\u{116B8}\u{116B9}\u{116C0}-\u{116C9}\u{11700}-\u{1171A}\u{11720}\u{11721}\u{11726}\u{11730}-\u{11746}\u{11800}-\u{1182E}\u{11838}\u{1183B}\u{118A0}-\u{118F2}\u{118FF}-\u{11906}\u{11909}\u{1190C}-\u{11913}\u{11915}\u{11916}\u{11918}-\u{11935}\u{11937}\u{11938}\u{1193D}\u{1193F}-\u{11942}\u{11944}-\u{11946}\u{11950}-\u{11959}\u{119A0}-\u{119A7}\u{119AA}-\u{119D3}\u{119DC}-\u{119DF}\u{119E1}-\u{119E4}\u{11A00}\u{11A07}\u{11A08}\u{11A0B}-\u{11A32}\u{11A39}\u{11A3A}\u{11A3F}-\u{11A46}\u{11A50}\u{11A57}\u{11A58}\u{11A5C}-\u{11A89}\u{11A97}\u{11A9A}-\u{11AA2}\u{11AB0}-\u{11AF8}\u{11B00}-\u{11B09}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C2F}\u{11C3E}-\u{11C45}\u{11C50}-\u{11C6C}\u{11C70}-\u{11C8F}\u{11CA9}\u{11CB1}\u{11CB4}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D30}\u{11D46}\u{11D50}-\u{11D59}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D8E}\u{11D93}\u{11D94}\u{11D96}\u{11D98}\u{11DA0}-\u{11DA9}\u{11EE0}-\u{11EF2}\u{11EF5}-\u{11EF8}\u{11F02}-\u{11F10}\u{11F12}-\u{11F35}\u{11F3E}\u{11F3F}\u{11F41}\u{11F43}-\u{11F59}\u{11FB0}\u{11FC0}-\u{11FD4}\u{11FFF}-\u{12399}\u{12400}-\u{1246E}\u{12470}-\u{12474}\u{12480}-\u{12543}\u{12F90}-\u{12FF2}\u{13000}-\u{1343F}\u{13441}-\u{13446}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16A60}-\u{16A69}\u{16A6E}-\u{16ABE}\u{16AC0}-\u{16AC9}\u{16AD0}-\u{16AED}\u{16AF5}\u{16B00}-\u{16B2F}\u{16B37}-\u{16B45}\u{16B50}-\u{16B59}\u{16B5B}-\u{16B61}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E9A}\u{16F00}-\u{16F4A}\u{16F50}-\u{16F87}\u{16F93}-\u{16F9F}\u{16FE0}\u{16FE1}\u{16FE3}\u{16FF0}\u{16FF1}\u{17000}-\u{187F7}\u{18800}-\u{18CD5}\u{18D00}-\u{18D08}\u{1AFF0}-\u{1AFF3}\u{1AFF5}-\u{1AFFB}\u{1AFFD}\u{1AFFE}\u{1B000}-\u{1B122}\u{1B132}\u{1B150}-\u{1B152}\u{1B155}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1BC9C}\u{1BC9F}\u{1CF50}-\u{1CFC3}\u{1D000}-\u{1D0F5}\u{1D100}-\u{1D126}\u{1D129}-\u{1D166}\u{1D16A}-\u{1D172}\u{1D183}\u{1D184}\u{1D18C}-\u{1D1A9}\u{1D1AE}-\u{1D1E8}\u{1D2C0}-\u{1D2D3}\u{1D2E0}-\u{1D2F3}\u{1D360}-\u{1D378}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D6DA}\u{1D6DC}-\u{1D714}\u{1D716}-\u{1D74E}\u{1D750}-\u{1D788}\u{1D78A}-\u{1D7C2}\u{1D7C4}-\u{1D7CB}\u{1D800}-\u{1D9FF}\u{1DA37}-\u{1DA3A}\u{1DA6D}-\u{1DA74}\u{1DA76}-\u{1DA83}\u{1DA85}-\u{1DA8B}\u{1DF00}-\u{1DF1E}\u{1DF25}-\u{1DF2A}\u{1E030}-\u{1E06D}\u{1E100}-\u{1E12C}\u{1E137}-\u{1E13D}\u{1E140}-\u{1E149}\u{1E14E}\u{1E14F}\u{1E290}-\u{1E2AD}\u{1E2C0}-\u{1E2EB}\u{1E2F0}-\u{1E2F9}\u{1E4D0}-\u{1E4EB}\u{1E4F0}-\u{1E4F9}\u{1E7E0}-\u{1E7E6}\u{1E7E8}-\u{1E7EB}\u{1E7ED}\u{1E7EE}\u{1E7F0}-\u{1E7FE}\u{1F110}-\u{1F12E}\u{1F130}-\u{1F169}\u{1F170}-\u{1F1AC}\u{1F1E6}-\u{1F202}\u{1F210}-\u{1F23B}\u{1F240}-\u{1F248}\u{1F250}\u{1F251}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B739}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2EBF0}-\u{2EE5D}\u{2F800}-\u{2FA1D}\u{30000}-\u{3134A}\u{31350}-\u{323AF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u; var bidiS1RTL = /[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05EA\u05EF-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u070D\u070F\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u083E\u0840-\u0858\u085E\u0860-\u086A\u0870-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBC2\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFC\uFE70-\uFE74\uFE76-\uFEFC\u{10800}-\u{10805}\u{10808}\u{1080A}-\u{10835}\u{10837}\u{10838}\u{1083C}\u{1083F}-\u{10855}\u{10857}-\u{1089E}\u{108A7}-\u{108AF}\u{108E0}-\u{108F2}\u{108F4}\u{108F5}\u{108FB}-\u{1091B}\u{10920}-\u{10939}\u{1093F}\u{10980}-\u{109B7}\u{109BC}-\u{109CF}\u{109D2}-\u{10A00}\u{10A10}-\u{10A13}\u{10A15}-\u{10A17}\u{10A19}-\u{10A35}\u{10A40}-\u{10A48}\u{10A50}-\u{10A58}\u{10A60}-\u{10A9F}\u{10AC0}-\u{10AE4}\u{10AEB}-\u{10AF6}\u{10B00}-\u{10B35}\u{10B40}-\u{10B55}\u{10B58}-\u{10B72}\u{10B78}-\u{10B91}\u{10B99}-\u{10B9C}\u{10BA9}-\u{10BAF}\u{10C00}-\u{10C48}\u{10C80}-\u{10CB2}\u{10CC0}-\u{10CF2}\u{10CFA}-\u{10D23}\u{10E80}-\u{10EA9}\u{10EAD}\u{10EB0}\u{10EB1}\u{10F00}-\u{10F27}\u{10F30}-\u{10F45}\u{10F51}-\u{10F59}\u{10F70}-\u{10F81}\u{10F86}-\u{10F89}\u{10FB0}-\u{10FCB}\u{10FE0}-\u{10FF6}\u{1E800}-\u{1E8C4}\u{1E8C7}-\u{1E8CF}\u{1E900}-\u{1E943}\u{1E94B}\u{1E950}-\u{1E959}\u{1E95E}\u{1E95F}\u{1EC71}-\u{1ECB4}\u{1ED01}-\u{1ED3D}\u{1EE00}-\u{1EE03}\u{1EE05}-\u{1EE1F}\u{1EE21}\u{1EE22}\u{1EE24}\u{1EE27}\u{1EE29}-\u{1EE32}\u{1EE34}-\u{1EE37}\u{1EE39}\u{1EE3B}\u{1EE42}\u{1EE47}\u{1EE49}\u{1EE4B}\u{1EE4D}-\u{1EE4F}\u{1EE51}\u{1EE52}\u{1EE54}\u{1EE57}\u{1EE59}\u{1EE5B}\u{1EE5D}\u{1EE5F}\u{1EE61}\u{1EE62}\u{1EE64}\u{1EE67}-\u{1EE6A}\u{1EE6C}-\u{1EE72}\u{1EE74}-\u{1EE77}\u{1EE79}-\u{1EE7C}\u{1EE7E}\u{1EE80}-\u{1EE89}\u{1EE8B}-\u{1EE9B}\u{1EEA1}-\u{1EEA3}\u{1EEA5}-\u{1EEA9}\u{1EEAB}-\u{1EEBB}]/u; - var bidiS2 = /^[\0-\x08\x0E-\x1B!-@\[-`\{-\x84\x86-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02B9\u02BA\u02C2-\u02CF\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u036F\u0374\u0375\u037E\u0384\u0385\u0387\u03F6\u0483-\u0489\u058A\u058D-\u058F\u0591-\u05C7\u05D0-\u05EA\u05EF-\u05F4\u0600-\u070D\u070F-\u074A\u074D-\u07B1\u07C0-\u07FA\u07FD-\u082D\u0830-\u083E\u0840-\u085B\u085E\u0860-\u086A\u0870-\u088E\u0890\u0891\u0898-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09F2\u09F3\u09FB\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AF1\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0BF3-\u0BFA\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C78-\u0C7E\u0C81\u0CBC\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39-\u0F3D\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1390-\u1399\u1400\u169B\u169C\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DB\u17DD\u17F0-\u17F9\u1800-\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1940\u1944\u1945\u19DE-\u19FF\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u200B-\u200D\u200F-\u2027\u202F-\u205E\u2060-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20C0\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u2150-\u215F\u2189-\u218B\u2190-\u2335\u237B-\u2394\u2396-\u2426\u2440-\u244A\u2460-\u249B\u24EA-\u26AB\u26AD-\u27FF\u2900-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2CEF-\u2CF1\u2CF9-\u2CFF\u2D7F\u2DE0-\u2E5D\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u3004\u3008-\u3020\u302A-\u302D\u3030\u3036\u3037\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u31C0-\u31E3\u321D\u321E\u3250-\u325F\u327C-\u327E\u32B1-\u32BF\u32CC-\u32CF\u3377-\u337A\u33DE\u33DF\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA66F-\uA67F\uA69E\uA69F\uA6F0\uA6F1\uA700-\uA721\uA788\uA802\uA806\uA80B\uA825\uA826\uA828-\uA82C\uA838\uA839\uA874-\uA877\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uAB6A\uAB6B\uABE5\uABE8\uABED\uFB1D-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBC2\uFBD3-\uFD8F\uFD92-\uFDC7\uFDCF\uFDF0-\uFE19\uFE20-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFE70-\uFE74\uFE76-\uFEFC\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD\u{10101}\u{10140}-\u{1018C}\u{10190}-\u{1019C}\u{101A0}\u{101FD}\u{102E0}-\u{102FB}\u{10376}-\u{1037A}\u{10800}-\u{10805}\u{10808}\u{1080A}-\u{10835}\u{10837}\u{10838}\u{1083C}\u{1083F}-\u{10855}\u{10857}-\u{1089E}\u{108A7}-\u{108AF}\u{108E0}-\u{108F2}\u{108F4}\u{108F5}\u{108FB}-\u{1091B}\u{1091F}-\u{10939}\u{1093F}\u{10980}-\u{109B7}\u{109BC}-\u{109CF}\u{109D2}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A13}\u{10A15}-\u{10A17}\u{10A19}-\u{10A35}\u{10A38}-\u{10A3A}\u{10A3F}-\u{10A48}\u{10A50}-\u{10A58}\u{10A60}-\u{10A9F}\u{10AC0}-\u{10AE6}\u{10AEB}-\u{10AF6}\u{10B00}-\u{10B35}\u{10B39}-\u{10B55}\u{10B58}-\u{10B72}\u{10B78}-\u{10B91}\u{10B99}-\u{10B9C}\u{10BA9}-\u{10BAF}\u{10C00}-\u{10C48}\u{10C80}-\u{10CB2}\u{10CC0}-\u{10CF2}\u{10CFA}-\u{10D27}\u{10D30}-\u{10D39}\u{10E60}-\u{10E7E}\u{10E80}-\u{10EA9}\u{10EAB}-\u{10EAD}\u{10EB0}\u{10EB1}\u{10EFD}-\u{10F27}\u{10F30}-\u{10F59}\u{10F70}-\u{10F89}\u{10FB0}-\u{10FCB}\u{10FE0}-\u{10FF6}\u{11001}\u{11038}-\u{11046}\u{11052}-\u{11065}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{11660}-\u{1166C}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A06}\u{11A09}\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{11FD5}-\u{11FF1}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE2}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D1E9}\u{1D1EA}\u{1D200}-\u{1D245}\u{1D300}-\u{1D356}\u{1D6DB}\u{1D715}\u{1D74F}\u{1D789}\u{1D7C3}\u{1D7CE}-\u{1D7FF}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E2FF}\u{1E4EC}-\u{1E4EF}\u{1E800}-\u{1E8C4}\u{1E8C7}-\u{1E8D6}\u{1E900}-\u{1E94B}\u{1E950}-\u{1E959}\u{1E95E}\u{1E95F}\u{1EC71}-\u{1ECB4}\u{1ED01}-\u{1ED3D}\u{1EE00}-\u{1EE03}\u{1EE05}-\u{1EE1F}\u{1EE21}\u{1EE22}\u{1EE24}\u{1EE27}\u{1EE29}-\u{1EE32}\u{1EE34}-\u{1EE37}\u{1EE39}\u{1EE3B}\u{1EE42}\u{1EE47}\u{1EE49}\u{1EE4B}\u{1EE4D}-\u{1EE4F}\u{1EE51}\u{1EE52}\u{1EE54}\u{1EE57}\u{1EE59}\u{1EE5B}\u{1EE5D}\u{1EE5F}\u{1EE61}\u{1EE62}\u{1EE64}\u{1EE67}-\u{1EE6A}\u{1EE6C}-\u{1EE72}\u{1EE74}-\u{1EE77}\u{1EE79}-\u{1EE7C}\u{1EE7E}\u{1EE80}-\u{1EE89}\u{1EE8B}-\u{1EE9B}\u{1EEA1}-\u{1EEA3}\u{1EEA5}-\u{1EEA9}\u{1EEAB}-\u{1EEBB}\u{1EEF0}\u{1EEF1}\u{1F000}-\u{1F02B}\u{1F030}-\u{1F093}\u{1F0A0}-\u{1F0AE}\u{1F0B1}-\u{1F0BF}\u{1F0C1}-\u{1F0CF}\u{1F0D1}-\u{1F0F5}\u{1F100}-\u{1F10F}\u{1F12F}\u{1F16A}-\u{1F16F}\u{1F1AD}\u{1F260}-\u{1F265}\u{1F300}-\u{1F6D7}\u{1F6DC}-\u{1F6EC}\u{1F6F0}-\u{1F6FC}\u{1F700}-\u{1F776}\u{1F77B}-\u{1F7D9}\u{1F7E0}-\u{1F7EB}\u{1F7F0}\u{1F800}-\u{1F80B}\u{1F810}-\u{1F847}\u{1F850}-\u{1F859}\u{1F860}-\u{1F887}\u{1F890}-\u{1F8AD}\u{1F8B0}\u{1F8B1}\u{1F900}-\u{1FA53}\u{1FA60}-\u{1FA6D}\u{1FA70}-\u{1FA7C}\u{1FA80}-\u{1FA88}\u{1FA90}-\u{1FABD}\u{1FABF}-\u{1FAC5}\u{1FACE}-\u{1FADB}\u{1FAE0}-\u{1FAE8}\u{1FAF0}-\u{1FAF8}\u{1FB00}-\u{1FB92}\u{1FB94}-\u{1FBCA}\u{1FBF0}-\u{1FBF9}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]*$/u; + var bidiS2 = /^[\0-\x08\x0E-\x1B!-@\[-`\{-\x84\x86-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02B9\u02BA\u02C2-\u02CF\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u036F\u0374\u0375\u037E\u0384\u0385\u0387\u03F6\u0483-\u0489\u058A\u058D-\u058F\u0591-\u05C7\u05D0-\u05EA\u05EF-\u05F4\u0600-\u070D\u070F-\u074A\u074D-\u07B1\u07C0-\u07FA\u07FD-\u082D\u0830-\u083E\u0840-\u085B\u085E\u0860-\u086A\u0870-\u088E\u0890\u0891\u0898-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09F2\u09F3\u09FB\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AF1\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0BF3-\u0BFA\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C78-\u0C7E\u0C81\u0CBC\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39-\u0F3D\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1390-\u1399\u1400\u169B\u169C\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DB\u17DD\u17F0-\u17F9\u1800-\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1940\u1944\u1945\u19DE-\u19FF\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u200B-\u200D\u200F-\u2027\u202F-\u205E\u2060-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20C0\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u2150-\u215F\u2189-\u218B\u2190-\u2335\u237B-\u2394\u2396-\u2426\u2440-\u244A\u2460-\u249B\u24EA-\u26AB\u26AD-\u27FF\u2900-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2CEF-\u2CF1\u2CF9-\u2CFF\u2D7F\u2DE0-\u2E5D\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFF\u3001-\u3004\u3008-\u3020\u302A-\u302D\u3030\u3036\u3037\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u31C0-\u31E3\u31EF\u321D\u321E\u3250-\u325F\u327C-\u327E\u32B1-\u32BF\u32CC-\u32CF\u3377-\u337A\u33DE\u33DF\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA66F-\uA67F\uA69E\uA69F\uA6F0\uA6F1\uA700-\uA721\uA788\uA802\uA806\uA80B\uA825\uA826\uA828-\uA82C\uA838\uA839\uA874-\uA877\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uAB6A\uAB6B\uABE5\uABE8\uABED\uFB1D-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBC2\uFBD3-\uFD8F\uFD92-\uFDC7\uFDCF\uFDF0-\uFE19\uFE20-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFE70-\uFE74\uFE76-\uFEFC\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD\u{10101}\u{10140}-\u{1018C}\u{10190}-\u{1019C}\u{101A0}\u{101FD}\u{102E0}-\u{102FB}\u{10376}-\u{1037A}\u{10800}-\u{10805}\u{10808}\u{1080A}-\u{10835}\u{10837}\u{10838}\u{1083C}\u{1083F}-\u{10855}\u{10857}-\u{1089E}\u{108A7}-\u{108AF}\u{108E0}-\u{108F2}\u{108F4}\u{108F5}\u{108FB}-\u{1091B}\u{1091F}-\u{10939}\u{1093F}\u{10980}-\u{109B7}\u{109BC}-\u{109CF}\u{109D2}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A13}\u{10A15}-\u{10A17}\u{10A19}-\u{10A35}\u{10A38}-\u{10A3A}\u{10A3F}-\u{10A48}\u{10A50}-\u{10A58}\u{10A60}-\u{10A9F}\u{10AC0}-\u{10AE6}\u{10AEB}-\u{10AF6}\u{10B00}-\u{10B35}\u{10B39}-\u{10B55}\u{10B58}-\u{10B72}\u{10B78}-\u{10B91}\u{10B99}-\u{10B9C}\u{10BA9}-\u{10BAF}\u{10C00}-\u{10C48}\u{10C80}-\u{10CB2}\u{10CC0}-\u{10CF2}\u{10CFA}-\u{10D27}\u{10D30}-\u{10D39}\u{10E60}-\u{10E7E}\u{10E80}-\u{10EA9}\u{10EAB}-\u{10EAD}\u{10EB0}\u{10EB1}\u{10EFD}-\u{10F27}\u{10F30}-\u{10F59}\u{10F70}-\u{10F89}\u{10FB0}-\u{10FCB}\u{10FE0}-\u{10FF6}\u{11001}\u{11038}-\u{11046}\u{11052}-\u{11065}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{11660}-\u{1166C}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A06}\u{11A09}\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{11FD5}-\u{11FF1}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE2}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D1E9}\u{1D1EA}\u{1D200}-\u{1D245}\u{1D300}-\u{1D356}\u{1D6DB}\u{1D715}\u{1D74F}\u{1D789}\u{1D7C3}\u{1D7CE}-\u{1D7FF}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E2FF}\u{1E4EC}-\u{1E4EF}\u{1E800}-\u{1E8C4}\u{1E8C7}-\u{1E8D6}\u{1E900}-\u{1E94B}\u{1E950}-\u{1E959}\u{1E95E}\u{1E95F}\u{1EC71}-\u{1ECB4}\u{1ED01}-\u{1ED3D}\u{1EE00}-\u{1EE03}\u{1EE05}-\u{1EE1F}\u{1EE21}\u{1EE22}\u{1EE24}\u{1EE27}\u{1EE29}-\u{1EE32}\u{1EE34}-\u{1EE37}\u{1EE39}\u{1EE3B}\u{1EE42}\u{1EE47}\u{1EE49}\u{1EE4B}\u{1EE4D}-\u{1EE4F}\u{1EE51}\u{1EE52}\u{1EE54}\u{1EE57}\u{1EE59}\u{1EE5B}\u{1EE5D}\u{1EE5F}\u{1EE61}\u{1EE62}\u{1EE64}\u{1EE67}-\u{1EE6A}\u{1EE6C}-\u{1EE72}\u{1EE74}-\u{1EE77}\u{1EE79}-\u{1EE7C}\u{1EE7E}\u{1EE80}-\u{1EE89}\u{1EE8B}-\u{1EE9B}\u{1EEA1}-\u{1EEA3}\u{1EEA5}-\u{1EEA9}\u{1EEAB}-\u{1EEBB}\u{1EEF0}\u{1EEF1}\u{1F000}-\u{1F02B}\u{1F030}-\u{1F093}\u{1F0A0}-\u{1F0AE}\u{1F0B1}-\u{1F0BF}\u{1F0C1}-\u{1F0CF}\u{1F0D1}-\u{1F0F5}\u{1F100}-\u{1F10F}\u{1F12F}\u{1F16A}-\u{1F16F}\u{1F1AD}\u{1F260}-\u{1F265}\u{1F300}-\u{1F6D7}\u{1F6DC}-\u{1F6EC}\u{1F6F0}-\u{1F6FC}\u{1F700}-\u{1F776}\u{1F77B}-\u{1F7D9}\u{1F7E0}-\u{1F7EB}\u{1F7F0}\u{1F800}-\u{1F80B}\u{1F810}-\u{1F847}\u{1F850}-\u{1F859}\u{1F860}-\u{1F887}\u{1F890}-\u{1F8AD}\u{1F8B0}\u{1F8B1}\u{1F900}-\u{1FA53}\u{1FA60}-\u{1FA6D}\u{1FA70}-\u{1FA7C}\u{1FA80}-\u{1FA88}\u{1FA90}-\u{1FABD}\u{1FABF}-\u{1FAC5}\u{1FACE}-\u{1FADB}\u{1FAE0}-\u{1FAE8}\u{1FAF0}-\u{1FAF8}\u{1FB00}-\u{1FB92}\u{1FB94}-\u{1FBCA}\u{1FBF0}-\u{1FBF9}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]*$/u; var bidiS3 = /[0-9\xB2\xB3\xB9\u05BE\u05C0\u05C3\u05C6\u05D0-\u05EA\u05EF-\u05F4\u0600-\u0605\u0608\u060B\u060D\u061B-\u064A\u0660-\u0669\u066B-\u066F\u0671-\u06D5\u06DD\u06E5\u06E6\u06EE-\u070D\u070F\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u083E\u0840-\u0858\u085E\u0860-\u086A\u0870-\u088E\u0890\u0891\u08A0-\u08C9\u08E2\u200F\u2070\u2074-\u2079\u2080-\u2089\u2488-\u249B\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBC2\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFC\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\u{102E1}-\u{102FB}\u{10800}-\u{10805}\u{10808}\u{1080A}-\u{10835}\u{10837}\u{10838}\u{1083C}\u{1083F}-\u{10855}\u{10857}-\u{1089E}\u{108A7}-\u{108AF}\u{108E0}-\u{108F2}\u{108F4}\u{108F5}\u{108FB}-\u{1091B}\u{10920}-\u{10939}\u{1093F}\u{10980}-\u{109B7}\u{109BC}-\u{109CF}\u{109D2}-\u{10A00}\u{10A10}-\u{10A13}\u{10A15}-\u{10A17}\u{10A19}-\u{10A35}\u{10A40}-\u{10A48}\u{10A50}-\u{10A58}\u{10A60}-\u{10A9F}\u{10AC0}-\u{10AE4}\u{10AEB}-\u{10AF6}\u{10B00}-\u{10B35}\u{10B40}-\u{10B55}\u{10B58}-\u{10B72}\u{10B78}-\u{10B91}\u{10B99}-\u{10B9C}\u{10BA9}-\u{10BAF}\u{10C00}-\u{10C48}\u{10C80}-\u{10CB2}\u{10CC0}-\u{10CF2}\u{10CFA}-\u{10D23}\u{10D30}-\u{10D39}\u{10E60}-\u{10E7E}\u{10E80}-\u{10EA9}\u{10EAD}\u{10EB0}\u{10EB1}\u{10F00}-\u{10F27}\u{10F30}-\u{10F45}\u{10F51}-\u{10F59}\u{10F70}-\u{10F81}\u{10F86}-\u{10F89}\u{10FB0}-\u{10FCB}\u{10FE0}-\u{10FF6}\u{1D7CE}-\u{1D7FF}\u{1E800}-\u{1E8C4}\u{1E8C7}-\u{1E8CF}\u{1E900}-\u{1E943}\u{1E94B}\u{1E950}-\u{1E959}\u{1E95E}\u{1E95F}\u{1EC71}-\u{1ECB4}\u{1ED01}-\u{1ED3D}\u{1EE00}-\u{1EE03}\u{1EE05}-\u{1EE1F}\u{1EE21}\u{1EE22}\u{1EE24}\u{1EE27}\u{1EE29}-\u{1EE32}\u{1EE34}-\u{1EE37}\u{1EE39}\u{1EE3B}\u{1EE42}\u{1EE47}\u{1EE49}\u{1EE4B}\u{1EE4D}-\u{1EE4F}\u{1EE51}\u{1EE52}\u{1EE54}\u{1EE57}\u{1EE59}\u{1EE5B}\u{1EE5D}\u{1EE5F}\u{1EE61}\u{1EE62}\u{1EE64}\u{1EE67}-\u{1EE6A}\u{1EE6C}-\u{1EE72}\u{1EE74}-\u{1EE77}\u{1EE79}-\u{1EE7C}\u{1EE7E}\u{1EE80}-\u{1EE89}\u{1EE8B}-\u{1EE9B}\u{1EEA1}-\u{1EEA3}\u{1EEA5}-\u{1EEA9}\u{1EEAB}-\u{1EEBB}\u{1F100}-\u{1F10A}\u{1FBF0}-\u{1FBF9}][\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A06}\u{11A09}\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{E0100}-\u{E01EF}]*$/u; var bidiS4EN = /[0-9\xB2\xB3\xB9\u06F0-\u06F9\u2070\u2074-\u2079\u2080-\u2089\u2488-\u249B\uFF10-\uFF19\u{102E1}-\u{102FB}\u{1D7CE}-\u{1D7FF}\u{1F100}-\u{1F10A}\u{1FBF0}-\u{1FBF9}]/u; var bidiS4AN = /[\u0600-\u0605\u0660-\u0669\u066B\u066C\u06DD\u0890\u0891\u08E2\u{10D30}-\u{10D39}\u{10E60}-\u{10E7E}]/u; - var bidiS5 = /^[\0-\x08\x0E-\x1B!-\x84\x86-\u0377\u037A-\u037F\u0384-\u038A\u038C\u038E-\u03A1\u03A3-\u052F\u0531-\u0556\u0559-\u058A\u058D-\u058F\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0606\u0607\u0609\u060A\u060C\u060E-\u061A\u064B-\u065F\u066A\u0670\u06D6-\u06DC\u06DE-\u06E4\u06E7-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07F6-\u07F9\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09FE\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A76\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AF1\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B77\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BFA\u0C00-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3C-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C5D\u0C60-\u0C63\u0C66-\u0C6F\u0C77-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1-\u0CF3\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4F\u0D54-\u0D63\u0D66-\u0D7F\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4\u0E01-\u0E3A\u0E3F-\u0E5B\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECE\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00-\u0F47\u0F49-\u0F6C\u0F71-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FDA\u1000-\u10C5\u10C7\u10CD\u10D0-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u137C\u1380-\u1399\u13A0-\u13F5\u13F8-\u13FD\u1400-\u167F\u1681-\u169C\u16A0-\u16F8\u1700-\u1715\u171F-\u1736\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u1819\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1940\u1944-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u19DE-\u1A1B\u1A1E-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD\u1AB0-\u1ACE\u1B00-\u1B4C\u1B50-\u1B7E\u1B80-\u1BF3\u1BFC-\u1C37\u1C3B-\u1C49\u1C4D-\u1C88\u1C90-\u1CBA\u1CBD-\u1CC7\u1CD0-\u1CFA\u1D00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u200B-\u200E\u2010-\u2027\u202F-\u205E\u2060-\u2064\u206A-\u2071\u2074-\u208E\u2090-\u209C\u20A0-\u20C0\u20D0-\u20F0\u2100-\u218B\u2190-\u2426\u2440-\u244A\u2460-\u2B73\u2B76-\u2B95\u2B97-\u2CF3\u2CF9-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D70\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2E5D\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u303F\u3041-\u3096\u3099-\u30FF\u3105-\u312F\u3131-\u318E\u3190-\u31E3\u31F0-\u321E\u3220-\uA48C\uA490-\uA4C6\uA4D0-\uA62B\uA640-\uA6F7\uA700-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA82C\uA830-\uA839\uA840-\uA877\uA880-\uA8C5\uA8CE-\uA8D9\uA8E0-\uA953\uA95F-\uA97C\uA980-\uA9CD\uA9CF-\uA9D9\uA9DE-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA5C-\uAAC2\uAADB-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB6B\uAB70-\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uD800-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1E\uFB29\uFD3E-\uFD4F\uFDCF\uFDFD-\uFE19\uFE20-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10100}-\u{10102}\u{10107}-\u{10133}\u{10137}-\u{1018E}\u{10190}-\u{1019C}\u{101A0}\u{101D0}-\u{101FD}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{102E0}-\u{102FB}\u{10300}-\u{10323}\u{1032D}-\u{1034A}\u{10350}-\u{1037A}\u{10380}-\u{1039D}\u{1039F}-\u{103C3}\u{103C8}-\u{103D5}\u{10400}-\u{1049D}\u{104A0}-\u{104A9}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{1056F}-\u{1057A}\u{1057C}-\u{1058A}\u{1058C}-\u{10592}\u{10594}\u{10595}\u{10597}-\u{105A1}\u{105A3}-\u{105B1}\u{105B3}-\u{105B9}\u{105BB}\u{105BC}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10780}-\u{10785}\u{10787}-\u{107B0}\u{107B2}-\u{107BA}\u{1091F}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10B39}-\u{10B3F}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11000}-\u{1104D}\u{11052}-\u{11075}\u{1107F}-\u{110C2}\u{110CD}\u{110D0}-\u{110E8}\u{110F0}-\u{110F9}\u{11100}-\u{11134}\u{11136}-\u{11147}\u{11150}-\u{11176}\u{11180}-\u{111DF}\u{111E1}-\u{111F4}\u{11200}-\u{11211}\u{11213}-\u{11241}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A9}\u{112B0}-\u{112EA}\u{112F0}-\u{112F9}\u{11300}-\u{11303}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133B}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11350}\u{11357}\u{1135D}-\u{11363}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11400}-\u{1145B}\u{1145D}-\u{11461}\u{11480}-\u{114C7}\u{114D0}-\u{114D9}\u{11580}-\u{115B5}\u{115B8}-\u{115DD}\u{11600}-\u{11644}\u{11650}-\u{11659}\u{11660}-\u{1166C}\u{11680}-\u{116B9}\u{116C0}-\u{116C9}\u{11700}-\u{1171A}\u{1171D}-\u{1172B}\u{11730}-\u{11746}\u{11800}-\u{1183B}\u{118A0}-\u{118F2}\u{118FF}-\u{11906}\u{11909}\u{1190C}-\u{11913}\u{11915}\u{11916}\u{11918}-\u{11935}\u{11937}\u{11938}\u{1193B}-\u{11946}\u{11950}-\u{11959}\u{119A0}-\u{119A7}\u{119AA}-\u{119D7}\u{119DA}-\u{119E4}\u{11A00}-\u{11A47}\u{11A50}-\u{11AA2}\u{11AB0}-\u{11AF8}\u{11B00}-\u{11B09}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C36}\u{11C38}-\u{11C45}\u{11C50}-\u{11C6C}\u{11C70}-\u{11C8F}\u{11C92}-\u{11CA7}\u{11CA9}-\u{11CB6}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D47}\u{11D50}-\u{11D59}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D8E}\u{11D90}\u{11D91}\u{11D93}-\u{11D98}\u{11DA0}-\u{11DA9}\u{11EE0}-\u{11EF8}\u{11F00}-\u{11F10}\u{11F12}-\u{11F3A}\u{11F3E}-\u{11F59}\u{11FB0}\u{11FC0}-\u{11FF1}\u{11FFF}-\u{12399}\u{12400}-\u{1246E}\u{12470}-\u{12474}\u{12480}-\u{12543}\u{12F90}-\u{12FF2}\u{13000}-\u{13455}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16A60}-\u{16A69}\u{16A6E}-\u{16ABE}\u{16AC0}-\u{16AC9}\u{16AD0}-\u{16AED}\u{16AF0}-\u{16AF5}\u{16B00}-\u{16B45}\u{16B50}-\u{16B59}\u{16B5B}-\u{16B61}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E9A}\u{16F00}-\u{16F4A}\u{16F4F}-\u{16F87}\u{16F8F}-\u{16F9F}\u{16FE0}-\u{16FE4}\u{16FF0}\u{16FF1}\u{17000}-\u{187F7}\u{18800}-\u{18CD5}\u{18D00}-\u{18D08}\u{1AFF0}-\u{1AFF3}\u{1AFF5}-\u{1AFFB}\u{1AFFD}\u{1AFFE}\u{1B000}-\u{1B122}\u{1B132}\u{1B150}-\u{1B152}\u{1B155}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1BC9C}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1CF50}-\u{1CFC3}\u{1D000}-\u{1D0F5}\u{1D100}-\u{1D126}\u{1D129}-\u{1D1EA}\u{1D200}-\u{1D245}\u{1D2C0}-\u{1D2D3}\u{1D2E0}-\u{1D2F3}\u{1D300}-\u{1D356}\u{1D360}-\u{1D378}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D7CB}\u{1D7CE}-\u{1DA8B}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1DF00}-\u{1DF1E}\u{1DF25}-\u{1DF2A}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E030}-\u{1E06D}\u{1E08F}\u{1E100}-\u{1E12C}\u{1E130}-\u{1E13D}\u{1E140}-\u{1E149}\u{1E14E}\u{1E14F}\u{1E290}-\u{1E2AE}\u{1E2C0}-\u{1E2F9}\u{1E2FF}\u{1E4D0}-\u{1E4F9}\u{1E7E0}-\u{1E7E6}\u{1E7E8}-\u{1E7EB}\u{1E7ED}\u{1E7EE}\u{1E7F0}-\u{1E7FE}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{1EEF0}\u{1EEF1}\u{1F000}-\u{1F02B}\u{1F030}-\u{1F093}\u{1F0A0}-\u{1F0AE}\u{1F0B1}-\u{1F0BF}\u{1F0C1}-\u{1F0CF}\u{1F0D1}-\u{1F0F5}\u{1F100}-\u{1F1AD}\u{1F1E6}-\u{1F202}\u{1F210}-\u{1F23B}\u{1F240}-\u{1F248}\u{1F250}\u{1F251}\u{1F260}-\u{1F265}\u{1F300}-\u{1F6D7}\u{1F6DC}-\u{1F6EC}\u{1F6F0}-\u{1F6FC}\u{1F700}-\u{1F776}\u{1F77B}-\u{1F7D9}\u{1F7E0}-\u{1F7EB}\u{1F7F0}\u{1F800}-\u{1F80B}\u{1F810}-\u{1F847}\u{1F850}-\u{1F859}\u{1F860}-\u{1F887}\u{1F890}-\u{1F8AD}\u{1F8B0}\u{1F8B1}\u{1F900}-\u{1FA53}\u{1FA60}-\u{1FA6D}\u{1FA70}-\u{1FA7C}\u{1FA80}-\u{1FA88}\u{1FA90}-\u{1FABD}\u{1FABF}-\u{1FAC5}\u{1FACE}-\u{1FADB}\u{1FAE0}-\u{1FAE8}\u{1FAF0}-\u{1FAF8}\u{1FB00}-\u{1FB92}\u{1FB94}-\u{1FBCA}\u{1FBF0}-\u{1FBF9}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B739}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2F800}-\u{2FA1D}\u{30000}-\u{3134A}\u{31350}-\u{323AF}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]*$/u; - var bidiS6 = /[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02BB-\u02C1\u02D0\u02D1\u02E0-\u02E4\u02EE\u0370-\u0373\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0482\u048A-\u052F\u0531-\u0556\u0559-\u0589\u06F0-\u06F9\u0903-\u0939\u093B\u093D-\u0940\u0949-\u094C\u094E-\u0950\u0958-\u0961\u0964-\u0980\u0982\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C0\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09FA\u09FC\u09FD\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A40\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A76\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC0\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0\u0AE1\u0AE6-\u0AF0\u0AF9\u0B02\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B3E\u0B40\u0B47\u0B48\u0B4B\u0B4C\u0B57\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE\u0BBF\u0BC1\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0BE6-\u0BF2\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C41-\u0C44\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C77\u0C7F\u0C80\u0C82-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA\u0CCB\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1-\u0CF3\u0D02-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D4F\u0D54-\u0D61\u0D66-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E4F-\u0E5B\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00-\u0F17\u0F1A-\u0F34\u0F36\u0F38\u0F3E-\u0F47\u0F49-\u0F6C\u0F7F\u0F85\u0F88-\u0F8C\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE-\u0FDA\u1000-\u102C\u1031\u1038\u103B\u103C\u103F-\u1057\u105A-\u105D\u1061-\u1070\u1075-\u1081\u1083\u1084\u1087-\u108C\u108E-\u109C\u109E-\u10C5\u10C7\u10CD\u10D0-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1360-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u167F\u1681-\u169A\u16A0-\u16F8\u1700-\u1711\u1715\u171F-\u1731\u1734-\u1736\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17B6\u17BE-\u17C5\u17C7\u17C8\u17D4-\u17DA\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1923-\u1926\u1929-\u192B\u1930\u1931\u1933-\u1938\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A19\u1A1A\u1A1E-\u1A55\u1A57\u1A61\u1A63\u1A64\u1A6D-\u1A72\u1A80-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD\u1B04-\u1B33\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B4C\u1B50-\u1B6A\u1B74-\u1B7E\u1B82-\u1BA1\u1BA6\u1BA7\u1BAA\u1BAE-\u1BE5\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2\u1BF3\u1BFC-\u1C2B\u1C34\u1C35\u1C3B-\u1C49\u1C4D-\u1C88\u1C90-\u1CBA\u1CBD-\u1CC7\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5-\u1CF7\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200E\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u214F\u2160-\u2188\u2336-\u237A\u2395\u2488-\u24E9\u26AC\u2800-\u28FF\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D70\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u302E\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3190-\u31BF\u31F0-\u321C\u3220-\u324F\u3260-\u327B\u327F-\u32B0\u32C0-\u32CB\u32D0-\u3376\u337B-\u33DD\u33E0-\u33FE\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA60C\uA610-\uA62B\uA640-\uA66E\uA680-\uA69D\uA6A0-\uA6EF\uA6F2-\uA6F7\uA722-\uA787\uA789-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA824\uA827\uA830-\uA837\uA840-\uA873\uA880-\uA8C3\uA8CE-\uA8D9\uA8F2-\uA8FE\uA900-\uA925\uA92E-\uA946\uA952\uA953\uA95F-\uA97C\uA983-\uA9B2\uA9B4\uA9B5\uA9BA\uA9BB\uA9BE-\uA9CD\uA9CF-\uA9D9\uA9DE-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA2F\uAA30\uAA33\uAA34\uAA40-\uAA42\uAA44-\uAA4B\uAA4D\uAA50-\uAA59\uAA5C-\uAA7B\uAA7D-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAAEB\uAAEE-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB69\uAB70-\uABE4\uABE6\uABE7\uABE9-\uABEC\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uD800-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10100}\u{10102}\u{10107}-\u{10133}\u{10137}-\u{1013F}\u{1018D}\u{1018E}\u{101D0}-\u{101FC}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{102E1}-\u{102FB}\u{10300}-\u{10323}\u{1032D}-\u{1034A}\u{10350}-\u{10375}\u{10380}-\u{1039D}\u{1039F}-\u{103C3}\u{103C8}-\u{103D5}\u{10400}-\u{1049D}\u{104A0}-\u{104A9}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{1056F}-\u{1057A}\u{1057C}-\u{1058A}\u{1058C}-\u{10592}\u{10594}\u{10595}\u{10597}-\u{105A1}\u{105A3}-\u{105B1}\u{105B3}-\u{105B9}\u{105BB}\u{105BC}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10780}-\u{10785}\u{10787}-\u{107B0}\u{107B2}-\u{107BA}\u{11000}\u{11002}-\u{11037}\u{11047}-\u{1104D}\u{11066}-\u{1106F}\u{11071}\u{11072}\u{11075}\u{11082}-\u{110B2}\u{110B7}\u{110B8}\u{110BB}-\u{110C1}\u{110CD}\u{110D0}-\u{110E8}\u{110F0}-\u{110F9}\u{11103}-\u{11126}\u{1112C}\u{11136}-\u{11147}\u{11150}-\u{11172}\u{11174}-\u{11176}\u{11182}-\u{111B5}\u{111BF}-\u{111C8}\u{111CD}\u{111CE}\u{111D0}-\u{111DF}\u{111E1}-\u{111F4}\u{11200}-\u{11211}\u{11213}-\u{1122E}\u{11232}\u{11233}\u{11235}\u{11238}-\u{1123D}\u{1123F}\u{11240}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A9}\u{112B0}-\u{112DE}\u{112E0}-\u{112E2}\u{112F0}-\u{112F9}\u{11302}\u{11303}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133D}-\u{1133F}\u{11341}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11350}\u{11357}\u{1135D}-\u{11363}\u{11400}-\u{11437}\u{11440}\u{11441}\u{11445}\u{11447}-\u{1145B}\u{1145D}\u{1145F}-\u{11461}\u{11480}-\u{114B2}\u{114B9}\u{114BB}-\u{114BE}\u{114C1}\u{114C4}-\u{114C7}\u{114D0}-\u{114D9}\u{11580}-\u{115B1}\u{115B8}-\u{115BB}\u{115BE}\u{115C1}-\u{115DB}\u{11600}-\u{11632}\u{1163B}\u{1163C}\u{1163E}\u{11641}-\u{11644}\u{11650}-\u{11659}\u{11680}-\u{116AA}\u{116AC}\u{116AE}\u{116AF}\u{116B6}\u{116B8}\u{116B9}\u{116C0}-\u{116C9}\u{11700}-\u{1171A}\u{11720}\u{11721}\u{11726}\u{11730}-\u{11746}\u{11800}-\u{1182E}\u{11838}\u{1183B}\u{118A0}-\u{118F2}\u{118FF}-\u{11906}\u{11909}\u{1190C}-\u{11913}\u{11915}\u{11916}\u{11918}-\u{11935}\u{11937}\u{11938}\u{1193D}\u{1193F}-\u{11942}\u{11944}-\u{11946}\u{11950}-\u{11959}\u{119A0}-\u{119A7}\u{119AA}-\u{119D3}\u{119DC}-\u{119DF}\u{119E1}-\u{119E4}\u{11A00}\u{11A07}\u{11A08}\u{11A0B}-\u{11A32}\u{11A39}\u{11A3A}\u{11A3F}-\u{11A46}\u{11A50}\u{11A57}\u{11A58}\u{11A5C}-\u{11A89}\u{11A97}\u{11A9A}-\u{11AA2}\u{11AB0}-\u{11AF8}\u{11B00}-\u{11B09}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C2F}\u{11C3E}-\u{11C45}\u{11C50}-\u{11C6C}\u{11C70}-\u{11C8F}\u{11CA9}\u{11CB1}\u{11CB4}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D30}\u{11D46}\u{11D50}-\u{11D59}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D8E}\u{11D93}\u{11D94}\u{11D96}\u{11D98}\u{11DA0}-\u{11DA9}\u{11EE0}-\u{11EF2}\u{11EF5}-\u{11EF8}\u{11F02}-\u{11F10}\u{11F12}-\u{11F35}\u{11F3E}\u{11F3F}\u{11F41}\u{11F43}-\u{11F59}\u{11FB0}\u{11FC0}-\u{11FD4}\u{11FFF}-\u{12399}\u{12400}-\u{1246E}\u{12470}-\u{12474}\u{12480}-\u{12543}\u{12F90}-\u{12FF2}\u{13000}-\u{1343F}\u{13441}-\u{13446}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16A60}-\u{16A69}\u{16A6E}-\u{16ABE}\u{16AC0}-\u{16AC9}\u{16AD0}-\u{16AED}\u{16AF5}\u{16B00}-\u{16B2F}\u{16B37}-\u{16B45}\u{16B50}-\u{16B59}\u{16B5B}-\u{16B61}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E9A}\u{16F00}-\u{16F4A}\u{16F50}-\u{16F87}\u{16F93}-\u{16F9F}\u{16FE0}\u{16FE1}\u{16FE3}\u{16FF0}\u{16FF1}\u{17000}-\u{187F7}\u{18800}-\u{18CD5}\u{18D00}-\u{18D08}\u{1AFF0}-\u{1AFF3}\u{1AFF5}-\u{1AFFB}\u{1AFFD}\u{1AFFE}\u{1B000}-\u{1B122}\u{1B132}\u{1B150}-\u{1B152}\u{1B155}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1BC9C}\u{1BC9F}\u{1CF50}-\u{1CFC3}\u{1D000}-\u{1D0F5}\u{1D100}-\u{1D126}\u{1D129}-\u{1D166}\u{1D16A}-\u{1D172}\u{1D183}\u{1D184}\u{1D18C}-\u{1D1A9}\u{1D1AE}-\u{1D1E8}\u{1D2C0}-\u{1D2D3}\u{1D2E0}-\u{1D2F3}\u{1D360}-\u{1D378}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D6DA}\u{1D6DC}-\u{1D714}\u{1D716}-\u{1D74E}\u{1D750}-\u{1D788}\u{1D78A}-\u{1D7C2}\u{1D7C4}-\u{1D7CB}\u{1D7CE}-\u{1D9FF}\u{1DA37}-\u{1DA3A}\u{1DA6D}-\u{1DA74}\u{1DA76}-\u{1DA83}\u{1DA85}-\u{1DA8B}\u{1DF00}-\u{1DF1E}\u{1DF25}-\u{1DF2A}\u{1E030}-\u{1E06D}\u{1E100}-\u{1E12C}\u{1E137}-\u{1E13D}\u{1E140}-\u{1E149}\u{1E14E}\u{1E14F}\u{1E290}-\u{1E2AD}\u{1E2C0}-\u{1E2EB}\u{1E2F0}-\u{1E2F9}\u{1E4D0}-\u{1E4EB}\u{1E4F0}-\u{1E4F9}\u{1E7E0}-\u{1E7E6}\u{1E7E8}-\u{1E7EB}\u{1E7ED}\u{1E7EE}\u{1E7F0}-\u{1E7FE}\u{1F100}-\u{1F10A}\u{1F110}-\u{1F12E}\u{1F130}-\u{1F169}\u{1F170}-\u{1F1AC}\u{1F1E6}-\u{1F202}\u{1F210}-\u{1F23B}\u{1F240}-\u{1F248}\u{1F250}\u{1F251}\u{1FBF0}-\u{1FBF9}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B739}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2F800}-\u{2FA1D}\u{30000}-\u{3134A}\u{31350}-\u{323AF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}][\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A06}\u{11A09}\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{E0100}-\u{E01EF}]*$/u; + var bidiS5 = /^[\0-\x08\x0E-\x1B!-\x84\x86-\u0377\u037A-\u037F\u0384-\u038A\u038C\u038E-\u03A1\u03A3-\u052F\u0531-\u0556\u0559-\u058A\u058D-\u058F\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0606\u0607\u0609\u060A\u060C\u060E-\u061A\u064B-\u065F\u066A\u0670\u06D6-\u06DC\u06DE-\u06E4\u06E7-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07F6-\u07F9\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09FE\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A76\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AF1\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B77\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BFA\u0C00-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3C-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C5D\u0C60-\u0C63\u0C66-\u0C6F\u0C77-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1-\u0CF3\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4F\u0D54-\u0D63\u0D66-\u0D7F\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4\u0E01-\u0E3A\u0E3F-\u0E5B\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECE\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00-\u0F47\u0F49-\u0F6C\u0F71-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FDA\u1000-\u10C5\u10C7\u10CD\u10D0-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u137C\u1380-\u1399\u13A0-\u13F5\u13F8-\u13FD\u1400-\u167F\u1681-\u169C\u16A0-\u16F8\u1700-\u1715\u171F-\u1736\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u1819\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1940\u1944-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u19DE-\u1A1B\u1A1E-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD\u1AB0-\u1ACE\u1B00-\u1B4C\u1B50-\u1B7E\u1B80-\u1BF3\u1BFC-\u1C37\u1C3B-\u1C49\u1C4D-\u1C88\u1C90-\u1CBA\u1CBD-\u1CC7\u1CD0-\u1CFA\u1D00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u200B-\u200E\u2010-\u2027\u202F-\u205E\u2060-\u2064\u206A-\u2071\u2074-\u208E\u2090-\u209C\u20A0-\u20C0\u20D0-\u20F0\u2100-\u218B\u2190-\u2426\u2440-\u244A\u2460-\u2B73\u2B76-\u2B95\u2B97-\u2CF3\u2CF9-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D70\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2E5D\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFF\u3001-\u303F\u3041-\u3096\u3099-\u30FF\u3105-\u312F\u3131-\u318E\u3190-\u31E3\u31EF-\u321E\u3220-\uA48C\uA490-\uA4C6\uA4D0-\uA62B\uA640-\uA6F7\uA700-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA82C\uA830-\uA839\uA840-\uA877\uA880-\uA8C5\uA8CE-\uA8D9\uA8E0-\uA953\uA95F-\uA97C\uA980-\uA9CD\uA9CF-\uA9D9\uA9DE-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA5C-\uAAC2\uAADB-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB6B\uAB70-\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uD800-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1E\uFB29\uFD3E-\uFD4F\uFDCF\uFDFD-\uFE19\uFE20-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10100}-\u{10102}\u{10107}-\u{10133}\u{10137}-\u{1018E}\u{10190}-\u{1019C}\u{101A0}\u{101D0}-\u{101FD}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{102E0}-\u{102FB}\u{10300}-\u{10323}\u{1032D}-\u{1034A}\u{10350}-\u{1037A}\u{10380}-\u{1039D}\u{1039F}-\u{103C3}\u{103C8}-\u{103D5}\u{10400}-\u{1049D}\u{104A0}-\u{104A9}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{1056F}-\u{1057A}\u{1057C}-\u{1058A}\u{1058C}-\u{10592}\u{10594}\u{10595}\u{10597}-\u{105A1}\u{105A3}-\u{105B1}\u{105B3}-\u{105B9}\u{105BB}\u{105BC}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10780}-\u{10785}\u{10787}-\u{107B0}\u{107B2}-\u{107BA}\u{1091F}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10B39}-\u{10B3F}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11000}-\u{1104D}\u{11052}-\u{11075}\u{1107F}-\u{110C2}\u{110CD}\u{110D0}-\u{110E8}\u{110F0}-\u{110F9}\u{11100}-\u{11134}\u{11136}-\u{11147}\u{11150}-\u{11176}\u{11180}-\u{111DF}\u{111E1}-\u{111F4}\u{11200}-\u{11211}\u{11213}-\u{11241}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A9}\u{112B0}-\u{112EA}\u{112F0}-\u{112F9}\u{11300}-\u{11303}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133B}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11350}\u{11357}\u{1135D}-\u{11363}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11400}-\u{1145B}\u{1145D}-\u{11461}\u{11480}-\u{114C7}\u{114D0}-\u{114D9}\u{11580}-\u{115B5}\u{115B8}-\u{115DD}\u{11600}-\u{11644}\u{11650}-\u{11659}\u{11660}-\u{1166C}\u{11680}-\u{116B9}\u{116C0}-\u{116C9}\u{11700}-\u{1171A}\u{1171D}-\u{1172B}\u{11730}-\u{11746}\u{11800}-\u{1183B}\u{118A0}-\u{118F2}\u{118FF}-\u{11906}\u{11909}\u{1190C}-\u{11913}\u{11915}\u{11916}\u{11918}-\u{11935}\u{11937}\u{11938}\u{1193B}-\u{11946}\u{11950}-\u{11959}\u{119A0}-\u{119A7}\u{119AA}-\u{119D7}\u{119DA}-\u{119E4}\u{11A00}-\u{11A47}\u{11A50}-\u{11AA2}\u{11AB0}-\u{11AF8}\u{11B00}-\u{11B09}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C36}\u{11C38}-\u{11C45}\u{11C50}-\u{11C6C}\u{11C70}-\u{11C8F}\u{11C92}-\u{11CA7}\u{11CA9}-\u{11CB6}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D47}\u{11D50}-\u{11D59}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D8E}\u{11D90}\u{11D91}\u{11D93}-\u{11D98}\u{11DA0}-\u{11DA9}\u{11EE0}-\u{11EF8}\u{11F00}-\u{11F10}\u{11F12}-\u{11F3A}\u{11F3E}-\u{11F59}\u{11FB0}\u{11FC0}-\u{11FF1}\u{11FFF}-\u{12399}\u{12400}-\u{1246E}\u{12470}-\u{12474}\u{12480}-\u{12543}\u{12F90}-\u{12FF2}\u{13000}-\u{13455}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16A60}-\u{16A69}\u{16A6E}-\u{16ABE}\u{16AC0}-\u{16AC9}\u{16AD0}-\u{16AED}\u{16AF0}-\u{16AF5}\u{16B00}-\u{16B45}\u{16B50}-\u{16B59}\u{16B5B}-\u{16B61}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E9A}\u{16F00}-\u{16F4A}\u{16F4F}-\u{16F87}\u{16F8F}-\u{16F9F}\u{16FE0}-\u{16FE4}\u{16FF0}\u{16FF1}\u{17000}-\u{187F7}\u{18800}-\u{18CD5}\u{18D00}-\u{18D08}\u{1AFF0}-\u{1AFF3}\u{1AFF5}-\u{1AFFB}\u{1AFFD}\u{1AFFE}\u{1B000}-\u{1B122}\u{1B132}\u{1B150}-\u{1B152}\u{1B155}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1BC9C}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1CF50}-\u{1CFC3}\u{1D000}-\u{1D0F5}\u{1D100}-\u{1D126}\u{1D129}-\u{1D1EA}\u{1D200}-\u{1D245}\u{1D2C0}-\u{1D2D3}\u{1D2E0}-\u{1D2F3}\u{1D300}-\u{1D356}\u{1D360}-\u{1D378}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D7CB}\u{1D7CE}-\u{1DA8B}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1DF00}-\u{1DF1E}\u{1DF25}-\u{1DF2A}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E030}-\u{1E06D}\u{1E08F}\u{1E100}-\u{1E12C}\u{1E130}-\u{1E13D}\u{1E140}-\u{1E149}\u{1E14E}\u{1E14F}\u{1E290}-\u{1E2AE}\u{1E2C0}-\u{1E2F9}\u{1E2FF}\u{1E4D0}-\u{1E4F9}\u{1E7E0}-\u{1E7E6}\u{1E7E8}-\u{1E7EB}\u{1E7ED}\u{1E7EE}\u{1E7F0}-\u{1E7FE}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{1EEF0}\u{1EEF1}\u{1F000}-\u{1F02B}\u{1F030}-\u{1F093}\u{1F0A0}-\u{1F0AE}\u{1F0B1}-\u{1F0BF}\u{1F0C1}-\u{1F0CF}\u{1F0D1}-\u{1F0F5}\u{1F100}-\u{1F1AD}\u{1F1E6}-\u{1F202}\u{1F210}-\u{1F23B}\u{1F240}-\u{1F248}\u{1F250}\u{1F251}\u{1F260}-\u{1F265}\u{1F300}-\u{1F6D7}\u{1F6DC}-\u{1F6EC}\u{1F6F0}-\u{1F6FC}\u{1F700}-\u{1F776}\u{1F77B}-\u{1F7D9}\u{1F7E0}-\u{1F7EB}\u{1F7F0}\u{1F800}-\u{1F80B}\u{1F810}-\u{1F847}\u{1F850}-\u{1F859}\u{1F860}-\u{1F887}\u{1F890}-\u{1F8AD}\u{1F8B0}\u{1F8B1}\u{1F900}-\u{1FA53}\u{1FA60}-\u{1FA6D}\u{1FA70}-\u{1FA7C}\u{1FA80}-\u{1FA88}\u{1FA90}-\u{1FABD}\u{1FABF}-\u{1FAC5}\u{1FACE}-\u{1FADB}\u{1FAE0}-\u{1FAE8}\u{1FAF0}-\u{1FAF8}\u{1FB00}-\u{1FB92}\u{1FB94}-\u{1FBCA}\u{1FBF0}-\u{1FBF9}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B739}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2EBF0}-\u{2EE5D}\u{2F800}-\u{2FA1D}\u{30000}-\u{3134A}\u{31350}-\u{323AF}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]*$/u; + var bidiS6 = /[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02BB-\u02C1\u02D0\u02D1\u02E0-\u02E4\u02EE\u0370-\u0373\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0482\u048A-\u052F\u0531-\u0556\u0559-\u0589\u06F0-\u06F9\u0903-\u0939\u093B\u093D-\u0940\u0949-\u094C\u094E-\u0950\u0958-\u0961\u0964-\u0980\u0982\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C0\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09FA\u09FC\u09FD\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A40\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A76\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC0\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0\u0AE1\u0AE6-\u0AF0\u0AF9\u0B02\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B3E\u0B40\u0B47\u0B48\u0B4B\u0B4C\u0B57\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE\u0BBF\u0BC1\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0BE6-\u0BF2\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C41-\u0C44\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C77\u0C7F\u0C80\u0C82-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA\u0CCB\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1-\u0CF3\u0D02-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D4F\u0D54-\u0D61\u0D66-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E4F-\u0E5B\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00-\u0F17\u0F1A-\u0F34\u0F36\u0F38\u0F3E-\u0F47\u0F49-\u0F6C\u0F7F\u0F85\u0F88-\u0F8C\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE-\u0FDA\u1000-\u102C\u1031\u1038\u103B\u103C\u103F-\u1057\u105A-\u105D\u1061-\u1070\u1075-\u1081\u1083\u1084\u1087-\u108C\u108E-\u109C\u109E-\u10C5\u10C7\u10CD\u10D0-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1360-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u167F\u1681-\u169A\u16A0-\u16F8\u1700-\u1711\u1715\u171F-\u1731\u1734-\u1736\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17B6\u17BE-\u17C5\u17C7\u17C8\u17D4-\u17DA\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1923-\u1926\u1929-\u192B\u1930\u1931\u1933-\u1938\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A19\u1A1A\u1A1E-\u1A55\u1A57\u1A61\u1A63\u1A64\u1A6D-\u1A72\u1A80-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD\u1B04-\u1B33\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B4C\u1B50-\u1B6A\u1B74-\u1B7E\u1B82-\u1BA1\u1BA6\u1BA7\u1BAA\u1BAE-\u1BE5\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2\u1BF3\u1BFC-\u1C2B\u1C34\u1C35\u1C3B-\u1C49\u1C4D-\u1C88\u1C90-\u1CBA\u1CBD-\u1CC7\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5-\u1CF7\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200E\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u214F\u2160-\u2188\u2336-\u237A\u2395\u2488-\u24E9\u26AC\u2800-\u28FF\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D70\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u302E\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3190-\u31BF\u31F0-\u321C\u3220-\u324F\u3260-\u327B\u327F-\u32B0\u32C0-\u32CB\u32D0-\u3376\u337B-\u33DD\u33E0-\u33FE\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA60C\uA610-\uA62B\uA640-\uA66E\uA680-\uA69D\uA6A0-\uA6EF\uA6F2-\uA6F7\uA722-\uA787\uA789-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA824\uA827\uA830-\uA837\uA840-\uA873\uA880-\uA8C3\uA8CE-\uA8D9\uA8F2-\uA8FE\uA900-\uA925\uA92E-\uA946\uA952\uA953\uA95F-\uA97C\uA983-\uA9B2\uA9B4\uA9B5\uA9BA\uA9BB\uA9BE-\uA9CD\uA9CF-\uA9D9\uA9DE-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA2F\uAA30\uAA33\uAA34\uAA40-\uAA42\uAA44-\uAA4B\uAA4D\uAA50-\uAA59\uAA5C-\uAA7B\uAA7D-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAAEB\uAAEE-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB69\uAB70-\uABE4\uABE6\uABE7\uABE9-\uABEC\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uD800-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10100}\u{10102}\u{10107}-\u{10133}\u{10137}-\u{1013F}\u{1018D}\u{1018E}\u{101D0}-\u{101FC}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{102E1}-\u{102FB}\u{10300}-\u{10323}\u{1032D}-\u{1034A}\u{10350}-\u{10375}\u{10380}-\u{1039D}\u{1039F}-\u{103C3}\u{103C8}-\u{103D5}\u{10400}-\u{1049D}\u{104A0}-\u{104A9}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{1056F}-\u{1057A}\u{1057C}-\u{1058A}\u{1058C}-\u{10592}\u{10594}\u{10595}\u{10597}-\u{105A1}\u{105A3}-\u{105B1}\u{105B3}-\u{105B9}\u{105BB}\u{105BC}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10780}-\u{10785}\u{10787}-\u{107B0}\u{107B2}-\u{107BA}\u{11000}\u{11002}-\u{11037}\u{11047}-\u{1104D}\u{11066}-\u{1106F}\u{11071}\u{11072}\u{11075}\u{11082}-\u{110B2}\u{110B7}\u{110B8}\u{110BB}-\u{110C1}\u{110CD}\u{110D0}-\u{110E8}\u{110F0}-\u{110F9}\u{11103}-\u{11126}\u{1112C}\u{11136}-\u{11147}\u{11150}-\u{11172}\u{11174}-\u{11176}\u{11182}-\u{111B5}\u{111BF}-\u{111C8}\u{111CD}\u{111CE}\u{111D0}-\u{111DF}\u{111E1}-\u{111F4}\u{11200}-\u{11211}\u{11213}-\u{1122E}\u{11232}\u{11233}\u{11235}\u{11238}-\u{1123D}\u{1123F}\u{11240}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A9}\u{112B0}-\u{112DE}\u{112E0}-\u{112E2}\u{112F0}-\u{112F9}\u{11302}\u{11303}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133D}-\u{1133F}\u{11341}-\u{11344}\u{11347}\u{11348}\u{1134B}-\u{1134D}\u{11350}\u{11357}\u{1135D}-\u{11363}\u{11400}-\u{11437}\u{11440}\u{11441}\u{11445}\u{11447}-\u{1145B}\u{1145D}\u{1145F}-\u{11461}\u{11480}-\u{114B2}\u{114B9}\u{114BB}-\u{114BE}\u{114C1}\u{114C4}-\u{114C7}\u{114D0}-\u{114D9}\u{11580}-\u{115B1}\u{115B8}-\u{115BB}\u{115BE}\u{115C1}-\u{115DB}\u{11600}-\u{11632}\u{1163B}\u{1163C}\u{1163E}\u{11641}-\u{11644}\u{11650}-\u{11659}\u{11680}-\u{116AA}\u{116AC}\u{116AE}\u{116AF}\u{116B6}\u{116B8}\u{116B9}\u{116C0}-\u{116C9}\u{11700}-\u{1171A}\u{11720}\u{11721}\u{11726}\u{11730}-\u{11746}\u{11800}-\u{1182E}\u{11838}\u{1183B}\u{118A0}-\u{118F2}\u{118FF}-\u{11906}\u{11909}\u{1190C}-\u{11913}\u{11915}\u{11916}\u{11918}-\u{11935}\u{11937}\u{11938}\u{1193D}\u{1193F}-\u{11942}\u{11944}-\u{11946}\u{11950}-\u{11959}\u{119A0}-\u{119A7}\u{119AA}-\u{119D3}\u{119DC}-\u{119DF}\u{119E1}-\u{119E4}\u{11A00}\u{11A07}\u{11A08}\u{11A0B}-\u{11A32}\u{11A39}\u{11A3A}\u{11A3F}-\u{11A46}\u{11A50}\u{11A57}\u{11A58}\u{11A5C}-\u{11A89}\u{11A97}\u{11A9A}-\u{11AA2}\u{11AB0}-\u{11AF8}\u{11B00}-\u{11B09}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C2F}\u{11C3E}-\u{11C45}\u{11C50}-\u{11C6C}\u{11C70}-\u{11C8F}\u{11CA9}\u{11CB1}\u{11CB4}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D30}\u{11D46}\u{11D50}-\u{11D59}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D8E}\u{11D93}\u{11D94}\u{11D96}\u{11D98}\u{11DA0}-\u{11DA9}\u{11EE0}-\u{11EF2}\u{11EF5}-\u{11EF8}\u{11F02}-\u{11F10}\u{11F12}-\u{11F35}\u{11F3E}\u{11F3F}\u{11F41}\u{11F43}-\u{11F59}\u{11FB0}\u{11FC0}-\u{11FD4}\u{11FFF}-\u{12399}\u{12400}-\u{1246E}\u{12470}-\u{12474}\u{12480}-\u{12543}\u{12F90}-\u{12FF2}\u{13000}-\u{1343F}\u{13441}-\u{13446}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16A60}-\u{16A69}\u{16A6E}-\u{16ABE}\u{16AC0}-\u{16AC9}\u{16AD0}-\u{16AED}\u{16AF5}\u{16B00}-\u{16B2F}\u{16B37}-\u{16B45}\u{16B50}-\u{16B59}\u{16B5B}-\u{16B61}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E9A}\u{16F00}-\u{16F4A}\u{16F50}-\u{16F87}\u{16F93}-\u{16F9F}\u{16FE0}\u{16FE1}\u{16FE3}\u{16FF0}\u{16FF1}\u{17000}-\u{187F7}\u{18800}-\u{18CD5}\u{18D00}-\u{18D08}\u{1AFF0}-\u{1AFF3}\u{1AFF5}-\u{1AFFB}\u{1AFFD}\u{1AFFE}\u{1B000}-\u{1B122}\u{1B132}\u{1B150}-\u{1B152}\u{1B155}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1BC9C}\u{1BC9F}\u{1CF50}-\u{1CFC3}\u{1D000}-\u{1D0F5}\u{1D100}-\u{1D126}\u{1D129}-\u{1D166}\u{1D16A}-\u{1D172}\u{1D183}\u{1D184}\u{1D18C}-\u{1D1A9}\u{1D1AE}-\u{1D1E8}\u{1D2C0}-\u{1D2D3}\u{1D2E0}-\u{1D2F3}\u{1D360}-\u{1D378}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D6DA}\u{1D6DC}-\u{1D714}\u{1D716}-\u{1D74E}\u{1D750}-\u{1D788}\u{1D78A}-\u{1D7C2}\u{1D7C4}-\u{1D7CB}\u{1D7CE}-\u{1D9FF}\u{1DA37}-\u{1DA3A}\u{1DA6D}-\u{1DA74}\u{1DA76}-\u{1DA83}\u{1DA85}-\u{1DA8B}\u{1DF00}-\u{1DF1E}\u{1DF25}-\u{1DF2A}\u{1E030}-\u{1E06D}\u{1E100}-\u{1E12C}\u{1E137}-\u{1E13D}\u{1E140}-\u{1E149}\u{1E14E}\u{1E14F}\u{1E290}-\u{1E2AD}\u{1E2C0}-\u{1E2EB}\u{1E2F0}-\u{1E2F9}\u{1E4D0}-\u{1E4EB}\u{1E4F0}-\u{1E4F9}\u{1E7E0}-\u{1E7E6}\u{1E7E8}-\u{1E7EB}\u{1E7ED}\u{1E7EE}\u{1E7F0}-\u{1E7FE}\u{1F100}-\u{1F10A}\u{1F110}-\u{1F12E}\u{1F130}-\u{1F169}\u{1F170}-\u{1F1AC}\u{1F1E6}-\u{1F202}\u{1F210}-\u{1F23B}\u{1F240}-\u{1F248}\u{1F250}\u{1F251}\u{1FBF0}-\u{1FBF9}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B739}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2EBF0}-\u{2EE5D}\u{2F800}-\u{2FA1D}\u{30000}-\u{3134A}\u{31350}-\u{323AF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}][\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}\u{11301}\u{1133B}\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}\u{114C0}\u{114C2}\u{114C3}\u{115B2}-\u{115B5}\u{115BC}\u{115BD}\u{115BF}\u{115C0}\u{115DC}\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}\u{1183A}\u{1193B}\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}\u{119DB}\u{119E0}\u{11A01}-\u{11A06}\u{11A09}\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}\u{11CB3}\u{11CB5}\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}\u{11D91}\u{11D95}\u{11D97}\u{11EF3}\u{11EF4}\u{11F00}\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}\u{1BC9E}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{E0100}-\u{E01EF}]*$/u; module.exports = { combiningMarks, combiningClassVirama, @@ -789,7 +789,7 @@ var require_regexes = __commonJS({ // node_modules/tr46/lib/mappingTable.json var require_mappingTable = __commonJS({ "node_modules/tr46/lib/mappingTable.json"(exports, module) { - module.exports = [[[0, 44], 4], [[45, 46], 2], [47, 4], [[48, 57], 2], [[58, 64], 4], [65, 1, "a"], [66, 1, "b"], [67, 1, "c"], [68, 1, "d"], [69, 1, "e"], [70, 1, "f"], [71, 1, "g"], [72, 1, "h"], [73, 1, "i"], [74, 1, "j"], [75, 1, "k"], [76, 1, "l"], [77, 1, "m"], [78, 1, "n"], [79, 1, "o"], [80, 1, "p"], [81, 1, "q"], [82, 1, "r"], [83, 1, "s"], [84, 1, "t"], [85, 1, "u"], [86, 1, "v"], [87, 1, "w"], [88, 1, "x"], [89, 1, "y"], [90, 1, "z"], [[91, 96], 4], [[97, 122], 2], [[123, 127], 4], [[128, 159], 3], [160, 5, " "], [[161, 167], 2], [168, 5, " \u0308"], [169, 2], [170, 1, "a"], [[171, 172], 2], [173, 7], [174, 2], [175, 5, " \u0304"], [[176, 177], 2], [178, 1, "2"], [179, 1, "3"], [180, 5, " \u0301"], [181, 1, "\u03BC"], [182, 2], [183, 2], [184, 5, " \u0327"], [185, 1, "1"], [186, 1, "o"], [187, 2], [188, 1, "1\u20444"], [189, 1, "1\u20442"], [190, 1, "3\u20444"], [191, 2], [192, 1, "\xE0"], [193, 1, "\xE1"], [194, 1, "\xE2"], [195, 1, "\xE3"], [196, 1, "\xE4"], [197, 1, "\xE5"], [198, 1, "\xE6"], [199, 1, "\xE7"], [200, 1, "\xE8"], [201, 1, "\xE9"], [202, 1, "\xEA"], [203, 1, "\xEB"], [204, 1, "\xEC"], [205, 1, "\xED"], [206, 1, "\xEE"], [207, 1, "\xEF"], [208, 1, "\xF0"], [209, 1, "\xF1"], [210, 1, "\xF2"], [211, 1, "\xF3"], [212, 1, "\xF4"], [213, 1, "\xF5"], [214, 1, "\xF6"], [215, 2], [216, 1, "\xF8"], [217, 1, "\xF9"], [218, 1, "\xFA"], [219, 1, "\xFB"], [220, 1, "\xFC"], [221, 1, "\xFD"], [222, 1, "\xFE"], [223, 6, "ss"], [[224, 246], 2], [247, 2], [[248, 255], 2], [256, 1, "\u0101"], [257, 2], [258, 1, "\u0103"], [259, 2], [260, 1, "\u0105"], [261, 2], [262, 1, "\u0107"], [263, 2], [264, 1, "\u0109"], [265, 2], [266, 1, "\u010B"], [267, 2], [268, 1, "\u010D"], [269, 2], [270, 1, "\u010F"], [271, 2], [272, 1, "\u0111"], [273, 2], [274, 1, "\u0113"], [275, 2], [276, 1, "\u0115"], [277, 2], [278, 1, "\u0117"], [279, 2], [280, 1, "\u0119"], [281, 2], [282, 1, "\u011B"], [283, 2], [284, 1, "\u011D"], [285, 2], [286, 1, "\u011F"], [287, 2], [288, 1, "\u0121"], [289, 2], [290, 1, "\u0123"], [291, 2], [292, 1, "\u0125"], [293, 2], [294, 1, "\u0127"], [295, 2], [296, 1, "\u0129"], [297, 2], [298, 1, "\u012B"], [299, 2], [300, 1, "\u012D"], [301, 2], [302, 1, "\u012F"], [303, 2], [304, 1, "i\u0307"], [305, 2], [[306, 307], 1, "ij"], [308, 1, "\u0135"], [309, 2], [310, 1, "\u0137"], [[311, 312], 2], [313, 1, "\u013A"], [314, 2], [315, 1, "\u013C"], [316, 2], [317, 1, "\u013E"], [318, 2], [[319, 320], 1, "l\xB7"], [321, 1, "\u0142"], [322, 2], [323, 1, "\u0144"], [324, 2], [325, 1, "\u0146"], [326, 2], [327, 1, "\u0148"], [328, 2], [329, 1, "\u02BCn"], [330, 1, "\u014B"], [331, 2], [332, 1, "\u014D"], [333, 2], [334, 1, "\u014F"], [335, 2], [336, 1, "\u0151"], [337, 2], [338, 1, "\u0153"], [339, 2], [340, 1, "\u0155"], [341, 2], [342, 1, "\u0157"], [343, 2], [344, 1, "\u0159"], [345, 2], [346, 1, "\u015B"], [347, 2], [348, 1, "\u015D"], [349, 2], [350, 1, "\u015F"], [351, 2], [352, 1, "\u0161"], [353, 2], [354, 1, "\u0163"], [355, 2], [356, 1, "\u0165"], [357, 2], [358, 1, "\u0167"], [359, 2], [360, 1, "\u0169"], [361, 2], [362, 1, "\u016B"], [363, 2], [364, 1, "\u016D"], [365, 2], [366, 1, "\u016F"], [367, 2], [368, 1, "\u0171"], [369, 2], [370, 1, "\u0173"], [371, 2], [372, 1, "\u0175"], [373, 2], [374, 1, "\u0177"], [375, 2], [376, 1, "\xFF"], [377, 1, "\u017A"], [378, 2], [379, 1, "\u017C"], [380, 2], [381, 1, "\u017E"], [382, 2], [383, 1, "s"], [384, 2], [385, 1, "\u0253"], [386, 1, "\u0183"], [387, 2], [388, 1, "\u0185"], [389, 2], [390, 1, "\u0254"], [391, 1, "\u0188"], [392, 2], [393, 1, "\u0256"], [394, 1, "\u0257"], [395, 1, "\u018C"], [[396, 397], 2], [398, 1, "\u01DD"], [399, 1, "\u0259"], [400, 1, "\u025B"], [401, 1, "\u0192"], [402, 2], [403, 1, "\u0260"], [404, 1, "\u0263"], [405, 2], [406, 1, "\u0269"], [407, 1, "\u0268"], [408, 1, "\u0199"], [[409, 411], 2], [412, 1, "\u026F"], [413, 1, "\u0272"], [414, 2], [415, 1, "\u0275"], [416, 1, "\u01A1"], [417, 2], [418, 1, "\u01A3"], [419, 2], [420, 1, "\u01A5"], [421, 2], [422, 1, "\u0280"], [423, 1, "\u01A8"], [424, 2], [425, 1, "\u0283"], [[426, 427], 2], [428, 1, "\u01AD"], [429, 2], [430, 1, "\u0288"], [431, 1, "\u01B0"], [432, 2], [433, 1, "\u028A"], [434, 1, "\u028B"], [435, 1, "\u01B4"], [436, 2], [437, 1, "\u01B6"], [438, 2], [439, 1, "\u0292"], [440, 1, "\u01B9"], [[441, 443], 2], [444, 1, "\u01BD"], [[445, 451], 2], [[452, 454], 1, "d\u017E"], [[455, 457], 1, "lj"], [[458, 460], 1, "nj"], [461, 1, "\u01CE"], [462, 2], [463, 1, "\u01D0"], [464, 2], [465, 1, "\u01D2"], [466, 2], [467, 1, "\u01D4"], [468, 2], [469, 1, "\u01D6"], [470, 2], [471, 1, "\u01D8"], [472, 2], [473, 1, "\u01DA"], [474, 2], [475, 1, "\u01DC"], [[476, 477], 2], [478, 1, "\u01DF"], [479, 2], [480, 1, "\u01E1"], [481, 2], [482, 1, "\u01E3"], [483, 2], [484, 1, "\u01E5"], [485, 2], [486, 1, "\u01E7"], [487, 2], [488, 1, "\u01E9"], [489, 2], [490, 1, "\u01EB"], [491, 2], [492, 1, "\u01ED"], [493, 2], [494, 1, "\u01EF"], [[495, 496], 2], [[497, 499], 1, "dz"], [500, 1, "\u01F5"], [501, 2], [502, 1, "\u0195"], [503, 1, "\u01BF"], [504, 1, "\u01F9"], [505, 2], [506, 1, "\u01FB"], [507, 2], [508, 1, "\u01FD"], [509, 2], [510, 1, "\u01FF"], [511, 2], [512, 1, "\u0201"], [513, 2], [514, 1, "\u0203"], [515, 2], [516, 1, "\u0205"], [517, 2], [518, 1, "\u0207"], [519, 2], [520, 1, "\u0209"], [521, 2], [522, 1, "\u020B"], [523, 2], [524, 1, "\u020D"], [525, 2], [526, 1, "\u020F"], [527, 2], [528, 1, "\u0211"], [529, 2], [530, 1, "\u0213"], [531, 2], [532, 1, "\u0215"], [533, 2], [534, 1, "\u0217"], [535, 2], [536, 1, "\u0219"], [537, 2], [538, 1, "\u021B"], [539, 2], [540, 1, "\u021D"], [541, 2], [542, 1, "\u021F"], [543, 2], [544, 1, "\u019E"], [545, 2], [546, 1, "\u0223"], [547, 2], [548, 1, "\u0225"], [549, 2], [550, 1, "\u0227"], [551, 2], [552, 1, "\u0229"], [553, 2], [554, 1, "\u022B"], [555, 2], [556, 1, "\u022D"], [557, 2], [558, 1, "\u022F"], [559, 2], [560, 1, "\u0231"], [561, 2], [562, 1, "\u0233"], [563, 2], [[564, 566], 2], [[567, 569], 2], [570, 1, "\u2C65"], [571, 1, "\u023C"], [572, 2], [573, 1, "\u019A"], [574, 1, "\u2C66"], [[575, 576], 2], [577, 1, "\u0242"], [578, 2], [579, 1, "\u0180"], [580, 1, "\u0289"], [581, 1, "\u028C"], [582, 1, "\u0247"], [583, 2], [584, 1, "\u0249"], [585, 2], [586, 1, "\u024B"], [587, 2], [588, 1, "\u024D"], [589, 2], [590, 1, "\u024F"], [591, 2], [[592, 680], 2], [[681, 685], 2], [[686, 687], 2], [688, 1, "h"], [689, 1, "\u0266"], [690, 1, "j"], [691, 1, "r"], [692, 1, "\u0279"], [693, 1, "\u027B"], [694, 1, "\u0281"], [695, 1, "w"], [696, 1, "y"], [[697, 705], 2], [[706, 709], 2], [[710, 721], 2], [[722, 727], 2], [728, 5, " \u0306"], [729, 5, " \u0307"], [730, 5, " \u030A"], [731, 5, " \u0328"], [732, 5, " \u0303"], [733, 5, " \u030B"], [734, 2], [735, 2], [736, 1, "\u0263"], [737, 1, "l"], [738, 1, "s"], [739, 1, "x"], [740, 1, "\u0295"], [[741, 745], 2], [[746, 747], 2], [748, 2], [749, 2], [750, 2], [[751, 767], 2], [[768, 831], 2], [832, 1, "\u0300"], [833, 1, "\u0301"], [834, 2], [835, 1, "\u0313"], [836, 1, "\u0308\u0301"], [837, 1, "\u03B9"], [[838, 846], 2], [847, 7], [[848, 855], 2], [[856, 860], 2], [[861, 863], 2], [[864, 865], 2], [866, 2], [[867, 879], 2], [880, 1, "\u0371"], [881, 2], [882, 1, "\u0373"], [883, 2], [884, 1, "\u02B9"], [885, 2], [886, 1, "\u0377"], [887, 2], [[888, 889], 3], [890, 5, " \u03B9"], [[891, 893], 2], [894, 5, ";"], [895, 1, "\u03F3"], [[896, 899], 3], [900, 5, " \u0301"], [901, 5, " \u0308\u0301"], [902, 1, "\u03AC"], [903, 1, "\xB7"], [904, 1, "\u03AD"], [905, 1, "\u03AE"], [906, 1, "\u03AF"], [907, 3], [908, 1, "\u03CC"], [909, 3], [910, 1, "\u03CD"], [911, 1, "\u03CE"], [912, 2], [913, 1, "\u03B1"], [914, 1, "\u03B2"], [915, 1, "\u03B3"], [916, 1, "\u03B4"], [917, 1, "\u03B5"], [918, 1, "\u03B6"], [919, 1, "\u03B7"], [920, 1, "\u03B8"], [921, 1, "\u03B9"], [922, 1, "\u03BA"], [923, 1, "\u03BB"], [924, 1, "\u03BC"], [925, 1, "\u03BD"], [926, 1, "\u03BE"], [927, 1, "\u03BF"], [928, 1, "\u03C0"], [929, 1, "\u03C1"], [930, 3], [931, 1, "\u03C3"], [932, 1, "\u03C4"], [933, 1, "\u03C5"], [934, 1, "\u03C6"], [935, 1, "\u03C7"], [936, 1, "\u03C8"], [937, 1, "\u03C9"], [938, 1, "\u03CA"], [939, 1, "\u03CB"], [[940, 961], 2], [962, 6, "\u03C3"], [[963, 974], 2], [975, 1, "\u03D7"], [976, 1, "\u03B2"], [977, 1, "\u03B8"], [978, 1, "\u03C5"], [979, 1, "\u03CD"], [980, 1, "\u03CB"], [981, 1, "\u03C6"], [982, 1, "\u03C0"], [983, 2], [984, 1, "\u03D9"], [985, 2], [986, 1, "\u03DB"], [987, 2], [988, 1, "\u03DD"], [989, 2], [990, 1, "\u03DF"], [991, 2], [992, 1, "\u03E1"], [993, 2], [994, 1, "\u03E3"], [995, 2], [996, 1, "\u03E5"], [997, 2], [998, 1, "\u03E7"], [999, 2], [1e3, 1, "\u03E9"], [1001, 2], [1002, 1, "\u03EB"], [1003, 2], [1004, 1, "\u03ED"], [1005, 2], [1006, 1, "\u03EF"], [1007, 2], [1008, 1, "\u03BA"], [1009, 1, "\u03C1"], [1010, 1, "\u03C3"], [1011, 2], [1012, 1, "\u03B8"], [1013, 1, "\u03B5"], [1014, 2], [1015, 1, "\u03F8"], [1016, 2], [1017, 1, "\u03C3"], [1018, 1, "\u03FB"], [1019, 2], [1020, 2], [1021, 1, "\u037B"], [1022, 1, "\u037C"], [1023, 1, "\u037D"], [1024, 1, "\u0450"], [1025, 1, "\u0451"], [1026, 1, "\u0452"], [1027, 1, "\u0453"], [1028, 1, "\u0454"], [1029, 1, "\u0455"], [1030, 1, "\u0456"], [1031, 1, "\u0457"], [1032, 1, "\u0458"], [1033, 1, "\u0459"], [1034, 1, "\u045A"], [1035, 1, "\u045B"], [1036, 1, "\u045C"], [1037, 1, "\u045D"], [1038, 1, "\u045E"], [1039, 1, "\u045F"], [1040, 1, "\u0430"], [1041, 1, "\u0431"], [1042, 1, "\u0432"], [1043, 1, "\u0433"], [1044, 1, "\u0434"], [1045, 1, "\u0435"], [1046, 1, "\u0436"], [1047, 1, "\u0437"], [1048, 1, "\u0438"], [1049, 1, "\u0439"], [1050, 1, "\u043A"], [1051, 1, "\u043B"], [1052, 1, "\u043C"], [1053, 1, "\u043D"], [1054, 1, "\u043E"], [1055, 1, "\u043F"], [1056, 1, "\u0440"], [1057, 1, "\u0441"], [1058, 1, "\u0442"], [1059, 1, "\u0443"], [1060, 1, "\u0444"], [1061, 1, "\u0445"], [1062, 1, "\u0446"], [1063, 1, "\u0447"], [1064, 1, "\u0448"], [1065, 1, "\u0449"], [1066, 1, "\u044A"], [1067, 1, "\u044B"], [1068, 1, "\u044C"], [1069, 1, "\u044D"], [1070, 1, "\u044E"], [1071, 1, "\u044F"], [[1072, 1103], 2], [1104, 2], [[1105, 1116], 2], [1117, 2], [[1118, 1119], 2], [1120, 1, "\u0461"], [1121, 2], [1122, 1, "\u0463"], [1123, 2], [1124, 1, "\u0465"], [1125, 2], [1126, 1, "\u0467"], [1127, 2], [1128, 1, "\u0469"], [1129, 2], [1130, 1, "\u046B"], [1131, 2], [1132, 1, "\u046D"], [1133, 2], [1134, 1, "\u046F"], [1135, 2], [1136, 1, "\u0471"], [1137, 2], [1138, 1, "\u0473"], [1139, 2], [1140, 1, "\u0475"], [1141, 2], [1142, 1, "\u0477"], [1143, 2], [1144, 1, "\u0479"], [1145, 2], [1146, 1, "\u047B"], [1147, 2], [1148, 1, "\u047D"], [1149, 2], [1150, 1, "\u047F"], [1151, 2], [1152, 1, "\u0481"], [1153, 2], [1154, 2], [[1155, 1158], 2], [1159, 2], [[1160, 1161], 2], [1162, 1, "\u048B"], [1163, 2], [1164, 1, "\u048D"], [1165, 2], [1166, 1, "\u048F"], [1167, 2], [1168, 1, "\u0491"], [1169, 2], [1170, 1, "\u0493"], [1171, 2], [1172, 1, "\u0495"], [1173, 2], [1174, 1, "\u0497"], [1175, 2], [1176, 1, "\u0499"], [1177, 2], [1178, 1, "\u049B"], [1179, 2], [1180, 1, "\u049D"], [1181, 2], [1182, 1, "\u049F"], [1183, 2], [1184, 1, "\u04A1"], [1185, 2], [1186, 1, "\u04A3"], [1187, 2], [1188, 1, "\u04A5"], [1189, 2], [1190, 1, "\u04A7"], [1191, 2], [1192, 1, "\u04A9"], [1193, 2], [1194, 1, "\u04AB"], [1195, 2], [1196, 1, "\u04AD"], [1197, 2], [1198, 1, "\u04AF"], [1199, 2], [1200, 1, "\u04B1"], [1201, 2], [1202, 1, "\u04B3"], [1203, 2], [1204, 1, "\u04B5"], [1205, 2], [1206, 1, "\u04B7"], [1207, 2], [1208, 1, "\u04B9"], [1209, 2], [1210, 1, "\u04BB"], [1211, 2], [1212, 1, "\u04BD"], [1213, 2], [1214, 1, "\u04BF"], [1215, 2], [1216, 3], [1217, 1, "\u04C2"], [1218, 2], [1219, 1, "\u04C4"], [1220, 2], [1221, 1, "\u04C6"], [1222, 2], [1223, 1, "\u04C8"], [1224, 2], [1225, 1, "\u04CA"], [1226, 2], [1227, 1, "\u04CC"], [1228, 2], [1229, 1, "\u04CE"], [1230, 2], [1231, 2], [1232, 1, "\u04D1"], [1233, 2], [1234, 1, "\u04D3"], [1235, 2], [1236, 1, "\u04D5"], [1237, 2], [1238, 1, "\u04D7"], [1239, 2], [1240, 1, "\u04D9"], [1241, 2], [1242, 1, "\u04DB"], [1243, 2], [1244, 1, "\u04DD"], [1245, 2], [1246, 1, "\u04DF"], [1247, 2], [1248, 1, "\u04E1"], [1249, 2], [1250, 1, "\u04E3"], [1251, 2], [1252, 1, "\u04E5"], [1253, 2], [1254, 1, "\u04E7"], [1255, 2], [1256, 1, "\u04E9"], [1257, 2], [1258, 1, "\u04EB"], [1259, 2], [1260, 1, "\u04ED"], [1261, 2], [1262, 1, "\u04EF"], [1263, 2], [1264, 1, "\u04F1"], [1265, 2], [1266, 1, "\u04F3"], [1267, 2], [1268, 1, "\u04F5"], [1269, 2], [1270, 1, "\u04F7"], [1271, 2], [1272, 1, "\u04F9"], [1273, 2], [1274, 1, "\u04FB"], [1275, 2], [1276, 1, "\u04FD"], [1277, 2], [1278, 1, "\u04FF"], [1279, 2], [1280, 1, "\u0501"], [1281, 2], [1282, 1, "\u0503"], [1283, 2], [1284, 1, "\u0505"], [1285, 2], [1286, 1, "\u0507"], [1287, 2], [1288, 1, "\u0509"], [1289, 2], [1290, 1, "\u050B"], [1291, 2], [1292, 1, "\u050D"], [1293, 2], [1294, 1, "\u050F"], [1295, 2], [1296, 1, "\u0511"], [1297, 2], [1298, 1, "\u0513"], [1299, 2], [1300, 1, "\u0515"], [1301, 2], [1302, 1, "\u0517"], [1303, 2], [1304, 1, "\u0519"], [1305, 2], [1306, 1, "\u051B"], [1307, 2], [1308, 1, "\u051D"], [1309, 2], [1310, 1, "\u051F"], [1311, 2], [1312, 1, "\u0521"], [1313, 2], [1314, 1, "\u0523"], [1315, 2], [1316, 1, "\u0525"], [1317, 2], [1318, 1, "\u0527"], [1319, 2], [1320, 1, "\u0529"], [1321, 2], [1322, 1, "\u052B"], [1323, 2], [1324, 1, "\u052D"], [1325, 2], [1326, 1, "\u052F"], [1327, 2], [1328, 3], [1329, 1, "\u0561"], [1330, 1, "\u0562"], [1331, 1, "\u0563"], [1332, 1, "\u0564"], [1333, 1, "\u0565"], [1334, 1, "\u0566"], [1335, 1, "\u0567"], [1336, 1, "\u0568"], [1337, 1, "\u0569"], [1338, 1, "\u056A"], [1339, 1, "\u056B"], [1340, 1, "\u056C"], [1341, 1, "\u056D"], [1342, 1, "\u056E"], [1343, 1, "\u056F"], [1344, 1, "\u0570"], [1345, 1, "\u0571"], [1346, 1, "\u0572"], [1347, 1, "\u0573"], [1348, 1, "\u0574"], [1349, 1, "\u0575"], [1350, 1, "\u0576"], [1351, 1, "\u0577"], [1352, 1, "\u0578"], [1353, 1, "\u0579"], [1354, 1, "\u057A"], [1355, 1, "\u057B"], [1356, 1, "\u057C"], [1357, 1, "\u057D"], [1358, 1, "\u057E"], [1359, 1, "\u057F"], [1360, 1, "\u0580"], [1361, 1, "\u0581"], [1362, 1, "\u0582"], [1363, 1, "\u0583"], [1364, 1, "\u0584"], [1365, 1, "\u0585"], [1366, 1, "\u0586"], [[1367, 1368], 3], [1369, 2], [[1370, 1375], 2], [1376, 2], [[1377, 1414], 2], [1415, 1, "\u0565\u0582"], [1416, 2], [1417, 2], [1418, 2], [[1419, 1420], 3], [[1421, 1422], 2], [1423, 2], [1424, 3], [[1425, 1441], 2], [1442, 2], [[1443, 1455], 2], [[1456, 1465], 2], [1466, 2], [[1467, 1469], 2], [1470, 2], [1471, 2], [1472, 2], [[1473, 1474], 2], [1475, 2], [1476, 2], [1477, 2], [1478, 2], [1479, 2], [[1480, 1487], 3], [[1488, 1514], 2], [[1515, 1518], 3], [1519, 2], [[1520, 1524], 2], [[1525, 1535], 3], [[1536, 1539], 3], [1540, 3], [1541, 3], [[1542, 1546], 2], [1547, 2], [1548, 2], [[1549, 1551], 2], [[1552, 1557], 2], [[1558, 1562], 2], [1563, 2], [1564, 3], [1565, 2], [1566, 2], [1567, 2], [1568, 2], [[1569, 1594], 2], [[1595, 1599], 2], [1600, 2], [[1601, 1618], 2], [[1619, 1621], 2], [[1622, 1624], 2], [[1625, 1630], 2], [1631, 2], [[1632, 1641], 2], [[1642, 1645], 2], [[1646, 1647], 2], [[1648, 1652], 2], [1653, 1, "\u0627\u0674"], [1654, 1, "\u0648\u0674"], [1655, 1, "\u06C7\u0674"], [1656, 1, "\u064A\u0674"], [[1657, 1719], 2], [[1720, 1721], 2], [[1722, 1726], 2], [1727, 2], [[1728, 1742], 2], [1743, 2], [[1744, 1747], 2], [1748, 2], [[1749, 1756], 2], [1757, 3], [1758, 2], [[1759, 1768], 2], [1769, 2], [[1770, 1773], 2], [[1774, 1775], 2], [[1776, 1785], 2], [[1786, 1790], 2], [1791, 2], [[1792, 1805], 2], [1806, 3], [1807, 3], [[1808, 1836], 2], [[1837, 1839], 2], [[1840, 1866], 2], [[1867, 1868], 3], [[1869, 1871], 2], [[1872, 1901], 2], [[1902, 1919], 2], [[1920, 1968], 2], [1969, 2], [[1970, 1983], 3], [[1984, 2037], 2], [[2038, 2042], 2], [[2043, 2044], 3], [2045, 2], [[2046, 2047], 2], [[2048, 2093], 2], [[2094, 2095], 3], [[2096, 2110], 2], [2111, 3], [[2112, 2139], 2], [[2140, 2141], 3], [2142, 2], [2143, 3], [[2144, 2154], 2], [[2155, 2159], 3], [[2160, 2183], 2], [2184, 2], [[2185, 2190], 2], [2191, 3], [[2192, 2193], 3], [[2194, 2199], 3], [[2200, 2207], 2], [2208, 2], [2209, 2], [[2210, 2220], 2], [[2221, 2226], 2], [[2227, 2228], 2], [2229, 2], [[2230, 2237], 2], [[2238, 2247], 2], [[2248, 2258], 2], [2259, 2], [[2260, 2273], 2], [2274, 3], [2275, 2], [[2276, 2302], 2], [2303, 2], [2304, 2], [[2305, 2307], 2], [2308, 2], [[2309, 2361], 2], [[2362, 2363], 2], [[2364, 2381], 2], [2382, 2], [2383, 2], [[2384, 2388], 2], [2389, 2], [[2390, 2391], 2], [2392, 1, "\u0915\u093C"], [2393, 1, "\u0916\u093C"], [2394, 1, "\u0917\u093C"], [2395, 1, "\u091C\u093C"], [2396, 1, "\u0921\u093C"], [2397, 1, "\u0922\u093C"], [2398, 1, "\u092B\u093C"], [2399, 1, "\u092F\u093C"], [[2400, 2403], 2], [[2404, 2405], 2], [[2406, 2415], 2], [2416, 2], [[2417, 2418], 2], [[2419, 2423], 2], [2424, 2], [[2425, 2426], 2], [[2427, 2428], 2], [2429, 2], [[2430, 2431], 2], [2432, 2], [[2433, 2435], 2], [2436, 3], [[2437, 2444], 2], [[2445, 2446], 3], [[2447, 2448], 2], [[2449, 2450], 3], [[2451, 2472], 2], [2473, 3], [[2474, 2480], 2], [2481, 3], [2482, 2], [[2483, 2485], 3], [[2486, 2489], 2], [[2490, 2491], 3], [2492, 2], [2493, 2], [[2494, 2500], 2], [[2501, 2502], 3], [[2503, 2504], 2], [[2505, 2506], 3], [[2507, 2509], 2], [2510, 2], [[2511, 2518], 3], [2519, 2], [[2520, 2523], 3], [2524, 1, "\u09A1\u09BC"], [2525, 1, "\u09A2\u09BC"], [2526, 3], [2527, 1, "\u09AF\u09BC"], [[2528, 2531], 2], [[2532, 2533], 3], [[2534, 2545], 2], [[2546, 2554], 2], [2555, 2], [2556, 2], [2557, 2], [2558, 2], [[2559, 2560], 3], [2561, 2], [2562, 2], [2563, 2], [2564, 3], [[2565, 2570], 2], [[2571, 2574], 3], [[2575, 2576], 2], [[2577, 2578], 3], [[2579, 2600], 2], [2601, 3], [[2602, 2608], 2], [2609, 3], [2610, 2], [2611, 1, "\u0A32\u0A3C"], [2612, 3], [2613, 2], [2614, 1, "\u0A38\u0A3C"], [2615, 3], [[2616, 2617], 2], [[2618, 2619], 3], [2620, 2], [2621, 3], [[2622, 2626], 2], [[2627, 2630], 3], [[2631, 2632], 2], [[2633, 2634], 3], [[2635, 2637], 2], [[2638, 2640], 3], [2641, 2], [[2642, 2648], 3], [2649, 1, "\u0A16\u0A3C"], [2650, 1, "\u0A17\u0A3C"], [2651, 1, "\u0A1C\u0A3C"], [2652, 2], [2653, 3], [2654, 1, "\u0A2B\u0A3C"], [[2655, 2661], 3], [[2662, 2676], 2], [2677, 2], [2678, 2], [[2679, 2688], 3], [[2689, 2691], 2], [2692, 3], [[2693, 2699], 2], [2700, 2], [2701, 2], [2702, 3], [[2703, 2705], 2], [2706, 3], [[2707, 2728], 2], [2729, 3], [[2730, 2736], 2], [2737, 3], [[2738, 2739], 2], [2740, 3], [[2741, 2745], 2], [[2746, 2747], 3], [[2748, 2757], 2], [2758, 3], [[2759, 2761], 2], [2762, 3], [[2763, 2765], 2], [[2766, 2767], 3], [2768, 2], [[2769, 2783], 3], [2784, 2], [[2785, 2787], 2], [[2788, 2789], 3], [[2790, 2799], 2], [2800, 2], [2801, 2], [[2802, 2808], 3], [2809, 2], [[2810, 2815], 2], [2816, 3], [[2817, 2819], 2], [2820, 3], [[2821, 2828], 2], [[2829, 2830], 3], [[2831, 2832], 2], [[2833, 2834], 3], [[2835, 2856], 2], [2857, 3], [[2858, 2864], 2], [2865, 3], [[2866, 2867], 2], [2868, 3], [2869, 2], [[2870, 2873], 2], [[2874, 2875], 3], [[2876, 2883], 2], [2884, 2], [[2885, 2886], 3], [[2887, 2888], 2], [[2889, 2890], 3], [[2891, 2893], 2], [[2894, 2900], 3], [2901, 2], [[2902, 2903], 2], [[2904, 2907], 3], [2908, 1, "\u0B21\u0B3C"], [2909, 1, "\u0B22\u0B3C"], [2910, 3], [[2911, 2913], 2], [[2914, 2915], 2], [[2916, 2917], 3], [[2918, 2927], 2], [2928, 2], [2929, 2], [[2930, 2935], 2], [[2936, 2945], 3], [[2946, 2947], 2], [2948, 3], [[2949, 2954], 2], [[2955, 2957], 3], [[2958, 2960], 2], [2961, 3], [[2962, 2965], 2], [[2966, 2968], 3], [[2969, 2970], 2], [2971, 3], [2972, 2], [2973, 3], [[2974, 2975], 2], [[2976, 2978], 3], [[2979, 2980], 2], [[2981, 2983], 3], [[2984, 2986], 2], [[2987, 2989], 3], [[2990, 2997], 2], [2998, 2], [[2999, 3001], 2], [[3002, 3005], 3], [[3006, 3010], 2], [[3011, 3013], 3], [[3014, 3016], 2], [3017, 3], [[3018, 3021], 2], [[3022, 3023], 3], [3024, 2], [[3025, 3030], 3], [3031, 2], [[3032, 3045], 3], [3046, 2], [[3047, 3055], 2], [[3056, 3058], 2], [[3059, 3066], 2], [[3067, 3071], 3], [3072, 2], [[3073, 3075], 2], [3076, 2], [[3077, 3084], 2], [3085, 3], [[3086, 3088], 2], [3089, 3], [[3090, 3112], 2], [3113, 3], [[3114, 3123], 2], [3124, 2], [[3125, 3129], 2], [[3130, 3131], 3], [3132, 2], [3133, 2], [[3134, 3140], 2], [3141, 3], [[3142, 3144], 2], [3145, 3], [[3146, 3149], 2], [[3150, 3156], 3], [[3157, 3158], 2], [3159, 3], [[3160, 3161], 2], [3162, 2], [[3163, 3164], 3], [3165, 2], [[3166, 3167], 3], [[3168, 3169], 2], [[3170, 3171], 2], [[3172, 3173], 3], [[3174, 3183], 2], [[3184, 3190], 3], [3191, 2], [[3192, 3199], 2], [3200, 2], [3201, 2], [[3202, 3203], 2], [3204, 2], [[3205, 3212], 2], [3213, 3], [[3214, 3216], 2], [3217, 3], [[3218, 3240], 2], [3241, 3], [[3242, 3251], 2], [3252, 3], [[3253, 3257], 2], [[3258, 3259], 3], [[3260, 3261], 2], [[3262, 3268], 2], [3269, 3], [[3270, 3272], 2], [3273, 3], [[3274, 3277], 2], [[3278, 3284], 3], [[3285, 3286], 2], [[3287, 3292], 3], [3293, 2], [3294, 2], [3295, 3], [[3296, 3297], 2], [[3298, 3299], 2], [[3300, 3301], 3], [[3302, 3311], 2], [3312, 3], [[3313, 3314], 2], [3315, 2], [[3316, 3327], 3], [3328, 2], [3329, 2], [[3330, 3331], 2], [3332, 2], [[3333, 3340], 2], [3341, 3], [[3342, 3344], 2], [3345, 3], [[3346, 3368], 2], [3369, 2], [[3370, 3385], 2], [3386, 2], [[3387, 3388], 2], [3389, 2], [[3390, 3395], 2], [3396, 2], [3397, 3], [[3398, 3400], 2], [3401, 3], [[3402, 3405], 2], [3406, 2], [3407, 2], [[3408, 3411], 3], [[3412, 3414], 2], [3415, 2], [[3416, 3422], 2], [3423, 2], [[3424, 3425], 2], [[3426, 3427], 2], [[3428, 3429], 3], [[3430, 3439], 2], [[3440, 3445], 2], [[3446, 3448], 2], [3449, 2], [[3450, 3455], 2], [3456, 3], [3457, 2], [[3458, 3459], 2], [3460, 3], [[3461, 3478], 2], [[3479, 3481], 3], [[3482, 3505], 2], [3506, 3], [[3507, 3515], 2], [3516, 3], [3517, 2], [[3518, 3519], 3], [[3520, 3526], 2], [[3527, 3529], 3], [3530, 2], [[3531, 3534], 3], [[3535, 3540], 2], [3541, 3], [3542, 2], [3543, 3], [[3544, 3551], 2], [[3552, 3557], 3], [[3558, 3567], 2], [[3568, 3569], 3], [[3570, 3571], 2], [3572, 2], [[3573, 3584], 3], [[3585, 3634], 2], [3635, 1, "\u0E4D\u0E32"], [[3636, 3642], 2], [[3643, 3646], 3], [3647, 2], [[3648, 3662], 2], [3663, 2], [[3664, 3673], 2], [[3674, 3675], 2], [[3676, 3712], 3], [[3713, 3714], 2], [3715, 3], [3716, 2], [3717, 3], [3718, 2], [[3719, 3720], 2], [3721, 2], [3722, 2], [3723, 3], [3724, 2], [3725, 2], [[3726, 3731], 2], [[3732, 3735], 2], [3736, 2], [[3737, 3743], 2], [3744, 2], [[3745, 3747], 2], [3748, 3], [3749, 2], [3750, 3], [3751, 2], [[3752, 3753], 2], [[3754, 3755], 2], [3756, 2], [[3757, 3762], 2], [3763, 1, "\u0ECD\u0EB2"], [[3764, 3769], 2], [3770, 2], [[3771, 3773], 2], [[3774, 3775], 3], [[3776, 3780], 2], [3781, 3], [3782, 2], [3783, 3], [[3784, 3789], 2], [3790, 2], [3791, 3], [[3792, 3801], 2], [[3802, 3803], 3], [3804, 1, "\u0EAB\u0E99"], [3805, 1, "\u0EAB\u0EA1"], [[3806, 3807], 2], [[3808, 3839], 3], [3840, 2], [[3841, 3850], 2], [3851, 2], [3852, 1, "\u0F0B"], [[3853, 3863], 2], [[3864, 3865], 2], [[3866, 3871], 2], [[3872, 3881], 2], [[3882, 3892], 2], [3893, 2], [3894, 2], [3895, 2], [3896, 2], [3897, 2], [[3898, 3901], 2], [[3902, 3906], 2], [3907, 1, "\u0F42\u0FB7"], [[3908, 3911], 2], [3912, 3], [[3913, 3916], 2], [3917, 1, "\u0F4C\u0FB7"], [[3918, 3921], 2], [3922, 1, "\u0F51\u0FB7"], [[3923, 3926], 2], [3927, 1, "\u0F56\u0FB7"], [[3928, 3931], 2], [3932, 1, "\u0F5B\u0FB7"], [[3933, 3944], 2], [3945, 1, "\u0F40\u0FB5"], [3946, 2], [[3947, 3948], 2], [[3949, 3952], 3], [[3953, 3954], 2], [3955, 1, "\u0F71\u0F72"], [3956, 2], [3957, 1, "\u0F71\u0F74"], [3958, 1, "\u0FB2\u0F80"], [3959, 1, "\u0FB2\u0F71\u0F80"], [3960, 1, "\u0FB3\u0F80"], [3961, 1, "\u0FB3\u0F71\u0F80"], [[3962, 3968], 2], [3969, 1, "\u0F71\u0F80"], [[3970, 3972], 2], [3973, 2], [[3974, 3979], 2], [[3980, 3983], 2], [[3984, 3986], 2], [3987, 1, "\u0F92\u0FB7"], [[3988, 3989], 2], [3990, 2], [3991, 2], [3992, 3], [[3993, 3996], 2], [3997, 1, "\u0F9C\u0FB7"], [[3998, 4001], 2], [4002, 1, "\u0FA1\u0FB7"], [[4003, 4006], 2], [4007, 1, "\u0FA6\u0FB7"], [[4008, 4011], 2], [4012, 1, "\u0FAB\u0FB7"], [4013, 2], [[4014, 4016], 2], [[4017, 4023], 2], [4024, 2], [4025, 1, "\u0F90\u0FB5"], [[4026, 4028], 2], [4029, 3], [[4030, 4037], 2], [4038, 2], [[4039, 4044], 2], [4045, 3], [4046, 2], [4047, 2], [[4048, 4049], 2], [[4050, 4052], 2], [[4053, 4056], 2], [[4057, 4058], 2], [[4059, 4095], 3], [[4096, 4129], 2], [4130, 2], [[4131, 4135], 2], [4136, 2], [[4137, 4138], 2], [4139, 2], [[4140, 4146], 2], [[4147, 4149], 2], [[4150, 4153], 2], [[4154, 4159], 2], [[4160, 4169], 2], [[4170, 4175], 2], [[4176, 4185], 2], [[4186, 4249], 2], [[4250, 4253], 2], [[4254, 4255], 2], [[4256, 4293], 3], [4294, 3], [4295, 1, "\u2D27"], [[4296, 4300], 3], [4301, 1, "\u2D2D"], [[4302, 4303], 3], [[4304, 4342], 2], [[4343, 4344], 2], [[4345, 4346], 2], [4347, 2], [4348, 1, "\u10DC"], [[4349, 4351], 2], [[4352, 4441], 2], [[4442, 4446], 2], [[4447, 4448], 3], [[4449, 4514], 2], [[4515, 4519], 2], [[4520, 4601], 2], [[4602, 4607], 2], [[4608, 4614], 2], [4615, 2], [[4616, 4678], 2], [4679, 2], [4680, 2], [4681, 3], [[4682, 4685], 2], [[4686, 4687], 3], [[4688, 4694], 2], [4695, 3], [4696, 2], [4697, 3], [[4698, 4701], 2], [[4702, 4703], 3], [[4704, 4742], 2], [4743, 2], [4744, 2], [4745, 3], [[4746, 4749], 2], [[4750, 4751], 3], [[4752, 4782], 2], [4783, 2], [4784, 2], [4785, 3], [[4786, 4789], 2], [[4790, 4791], 3], [[4792, 4798], 2], [4799, 3], [4800, 2], [4801, 3], [[4802, 4805], 2], [[4806, 4807], 3], [[4808, 4814], 2], [4815, 2], [[4816, 4822], 2], [4823, 3], [[4824, 4846], 2], [4847, 2], [[4848, 4878], 2], [4879, 2], [4880, 2], [4881, 3], [[4882, 4885], 2], [[4886, 4887], 3], [[4888, 4894], 2], [4895, 2], [[4896, 4934], 2], [4935, 2], [[4936, 4954], 2], [[4955, 4956], 3], [[4957, 4958], 2], [4959, 2], [4960, 2], [[4961, 4988], 2], [[4989, 4991], 3], [[4992, 5007], 2], [[5008, 5017], 2], [[5018, 5023], 3], [[5024, 5108], 2], [5109, 2], [[5110, 5111], 3], [5112, 1, "\u13F0"], [5113, 1, "\u13F1"], [5114, 1, "\u13F2"], [5115, 1, "\u13F3"], [5116, 1, "\u13F4"], [5117, 1, "\u13F5"], [[5118, 5119], 3], [5120, 2], [[5121, 5740], 2], [[5741, 5742], 2], [[5743, 5750], 2], [[5751, 5759], 2], [5760, 3], [[5761, 5786], 2], [[5787, 5788], 2], [[5789, 5791], 3], [[5792, 5866], 2], [[5867, 5872], 2], [[5873, 5880], 2], [[5881, 5887], 3], [[5888, 5900], 2], [5901, 2], [[5902, 5908], 2], [5909, 2], [[5910, 5918], 3], [5919, 2], [[5920, 5940], 2], [[5941, 5942], 2], [[5943, 5951], 3], [[5952, 5971], 2], [[5972, 5983], 3], [[5984, 5996], 2], [5997, 3], [[5998, 6e3], 2], [6001, 3], [[6002, 6003], 2], [[6004, 6015], 3], [[6016, 6067], 2], [[6068, 6069], 3], [[6070, 6099], 2], [[6100, 6102], 2], [6103, 2], [[6104, 6107], 2], [6108, 2], [6109, 2], [[6110, 6111], 3], [[6112, 6121], 2], [[6122, 6127], 3], [[6128, 6137], 2], [[6138, 6143], 3], [[6144, 6149], 2], [6150, 3], [[6151, 6154], 2], [[6155, 6157], 7], [6158, 3], [6159, 7], [[6160, 6169], 2], [[6170, 6175], 3], [[6176, 6263], 2], [6264, 2], [[6265, 6271], 3], [[6272, 6313], 2], [6314, 2], [[6315, 6319], 3], [[6320, 6389], 2], [[6390, 6399], 3], [[6400, 6428], 2], [[6429, 6430], 2], [6431, 3], [[6432, 6443], 2], [[6444, 6447], 3], [[6448, 6459], 2], [[6460, 6463], 3], [6464, 2], [[6465, 6467], 3], [[6468, 6469], 2], [[6470, 6509], 2], [[6510, 6511], 3], [[6512, 6516], 2], [[6517, 6527], 3], [[6528, 6569], 2], [[6570, 6571], 2], [[6572, 6575], 3], [[6576, 6601], 2], [[6602, 6607], 3], [[6608, 6617], 2], [6618, 2], [[6619, 6621], 3], [[6622, 6623], 2], [[6624, 6655], 2], [[6656, 6683], 2], [[6684, 6685], 3], [[6686, 6687], 2], [[6688, 6750], 2], [6751, 3], [[6752, 6780], 2], [[6781, 6782], 3], [[6783, 6793], 2], [[6794, 6799], 3], [[6800, 6809], 2], [[6810, 6815], 3], [[6816, 6822], 2], [6823, 2], [[6824, 6829], 2], [[6830, 6831], 3], [[6832, 6845], 2], [6846, 2], [[6847, 6848], 2], [[6849, 6862], 2], [[6863, 6911], 3], [[6912, 6987], 2], [6988, 2], [[6989, 6991], 3], [[6992, 7001], 2], [[7002, 7018], 2], [[7019, 7027], 2], [[7028, 7036], 2], [[7037, 7038], 2], [7039, 3], [[7040, 7082], 2], [[7083, 7085], 2], [[7086, 7097], 2], [[7098, 7103], 2], [[7104, 7155], 2], [[7156, 7163], 3], [[7164, 7167], 2], [[7168, 7223], 2], [[7224, 7226], 3], [[7227, 7231], 2], [[7232, 7241], 2], [[7242, 7244], 3], [[7245, 7293], 2], [[7294, 7295], 2], [7296, 1, "\u0432"], [7297, 1, "\u0434"], [7298, 1, "\u043E"], [7299, 1, "\u0441"], [[7300, 7301], 1, "\u0442"], [7302, 1, "\u044A"], [7303, 1, "\u0463"], [7304, 1, "\uA64B"], [[7305, 7311], 3], [7312, 1, "\u10D0"], [7313, 1, "\u10D1"], [7314, 1, "\u10D2"], [7315, 1, "\u10D3"], [7316, 1, "\u10D4"], [7317, 1, "\u10D5"], [7318, 1, "\u10D6"], [7319, 1, "\u10D7"], [7320, 1, "\u10D8"], [7321, 1, "\u10D9"], [7322, 1, "\u10DA"], [7323, 1, "\u10DB"], [7324, 1, "\u10DC"], [7325, 1, "\u10DD"], [7326, 1, "\u10DE"], [7327, 1, "\u10DF"], [7328, 1, "\u10E0"], [7329, 1, "\u10E1"], [7330, 1, "\u10E2"], [7331, 1, "\u10E3"], [7332, 1, "\u10E4"], [7333, 1, "\u10E5"], [7334, 1, "\u10E6"], [7335, 1, "\u10E7"], [7336, 1, "\u10E8"], [7337, 1, "\u10E9"], [7338, 1, "\u10EA"], [7339, 1, "\u10EB"], [7340, 1, "\u10EC"], [7341, 1, "\u10ED"], [7342, 1, "\u10EE"], [7343, 1, "\u10EF"], [7344, 1, "\u10F0"], [7345, 1, "\u10F1"], [7346, 1, "\u10F2"], [7347, 1, "\u10F3"], [7348, 1, "\u10F4"], [7349, 1, "\u10F5"], [7350, 1, "\u10F6"], [7351, 1, "\u10F7"], [7352, 1, "\u10F8"], [7353, 1, "\u10F9"], [7354, 1, "\u10FA"], [[7355, 7356], 3], [7357, 1, "\u10FD"], [7358, 1, "\u10FE"], [7359, 1, "\u10FF"], [[7360, 7367], 2], [[7368, 7375], 3], [[7376, 7378], 2], [7379, 2], [[7380, 7410], 2], [[7411, 7414], 2], [7415, 2], [[7416, 7417], 2], [7418, 2], [[7419, 7423], 3], [[7424, 7467], 2], [7468, 1, "a"], [7469, 1, "\xE6"], [7470, 1, "b"], [7471, 2], [7472, 1, "d"], [7473, 1, "e"], [7474, 1, "\u01DD"], [7475, 1, "g"], [7476, 1, "h"], [7477, 1, "i"], [7478, 1, "j"], [7479, 1, "k"], [7480, 1, "l"], [7481, 1, "m"], [7482, 1, "n"], [7483, 2], [7484, 1, "o"], [7485, 1, "\u0223"], [7486, 1, "p"], [7487, 1, "r"], [7488, 1, "t"], [7489, 1, "u"], [7490, 1, "w"], [7491, 1, "a"], [7492, 1, "\u0250"], [7493, 1, "\u0251"], [7494, 1, "\u1D02"], [7495, 1, "b"], [7496, 1, "d"], [7497, 1, "e"], [7498, 1, "\u0259"], [7499, 1, "\u025B"], [7500, 1, "\u025C"], [7501, 1, "g"], [7502, 2], [7503, 1, "k"], [7504, 1, "m"], [7505, 1, "\u014B"], [7506, 1, "o"], [7507, 1, "\u0254"], [7508, 1, "\u1D16"], [7509, 1, "\u1D17"], [7510, 1, "p"], [7511, 1, "t"], [7512, 1, "u"], [7513, 1, "\u1D1D"], [7514, 1, "\u026F"], [7515, 1, "v"], [7516, 1, "\u1D25"], [7517, 1, "\u03B2"], [7518, 1, "\u03B3"], [7519, 1, "\u03B4"], [7520, 1, "\u03C6"], [7521, 1, "\u03C7"], [7522, 1, "i"], [7523, 1, "r"], [7524, 1, "u"], [7525, 1, "v"], [7526, 1, "\u03B2"], [7527, 1, "\u03B3"], [7528, 1, "\u03C1"], [7529, 1, "\u03C6"], [7530, 1, "\u03C7"], [7531, 2], [[7532, 7543], 2], [7544, 1, "\u043D"], [[7545, 7578], 2], [7579, 1, "\u0252"], [7580, 1, "c"], [7581, 1, "\u0255"], [7582, 1, "\xF0"], [7583, 1, "\u025C"], [7584, 1, "f"], [7585, 1, "\u025F"], [7586, 1, "\u0261"], [7587, 1, "\u0265"], [7588, 1, "\u0268"], [7589, 1, "\u0269"], [7590, 1, "\u026A"], [7591, 1, "\u1D7B"], [7592, 1, "\u029D"], [7593, 1, "\u026D"], [7594, 1, "\u1D85"], [7595, 1, "\u029F"], [7596, 1, "\u0271"], [7597, 1, "\u0270"], [7598, 1, "\u0272"], [7599, 1, "\u0273"], [7600, 1, "\u0274"], [7601, 1, "\u0275"], [7602, 1, "\u0278"], [7603, 1, "\u0282"], [7604, 1, "\u0283"], [7605, 1, "\u01AB"], [7606, 1, "\u0289"], [7607, 1, "\u028A"], [7608, 1, "\u1D1C"], [7609, 1, "\u028B"], [7610, 1, "\u028C"], [7611, 1, "z"], [7612, 1, "\u0290"], [7613, 1, "\u0291"], [7614, 1, "\u0292"], [7615, 1, "\u03B8"], [[7616, 7619], 2], [[7620, 7626], 2], [[7627, 7654], 2], [[7655, 7669], 2], [[7670, 7673], 2], [7674, 2], [7675, 2], [7676, 2], [7677, 2], [[7678, 7679], 2], [7680, 1, "\u1E01"], [7681, 2], [7682, 1, "\u1E03"], [7683, 2], [7684, 1, "\u1E05"], [7685, 2], [7686, 1, "\u1E07"], [7687, 2], [7688, 1, "\u1E09"], [7689, 2], [7690, 1, "\u1E0B"], [7691, 2], [7692, 1, "\u1E0D"], [7693, 2], [7694, 1, "\u1E0F"], [7695, 2], [7696, 1, "\u1E11"], [7697, 2], [7698, 1, "\u1E13"], [7699, 2], [7700, 1, "\u1E15"], [7701, 2], [7702, 1, "\u1E17"], [7703, 2], [7704, 1, "\u1E19"], [7705, 2], [7706, 1, "\u1E1B"], [7707, 2], [7708, 1, "\u1E1D"], [7709, 2], [7710, 1, "\u1E1F"], [7711, 2], [7712, 1, "\u1E21"], [7713, 2], [7714, 1, "\u1E23"], [7715, 2], [7716, 1, "\u1E25"], [7717, 2], [7718, 1, "\u1E27"], [7719, 2], [7720, 1, "\u1E29"], [7721, 2], [7722, 1, "\u1E2B"], [7723, 2], [7724, 1, "\u1E2D"], [7725, 2], [7726, 1, "\u1E2F"], [7727, 2], [7728, 1, "\u1E31"], [7729, 2], [7730, 1, "\u1E33"], [7731, 2], [7732, 1, "\u1E35"], [7733, 2], [7734, 1, "\u1E37"], [7735, 2], [7736, 1, "\u1E39"], [7737, 2], [7738, 1, "\u1E3B"], [7739, 2], [7740, 1, "\u1E3D"], [7741, 2], [7742, 1, "\u1E3F"], [7743, 2], [7744, 1, "\u1E41"], [7745, 2], [7746, 1, "\u1E43"], [7747, 2], [7748, 1, "\u1E45"], [7749, 2], [7750, 1, "\u1E47"], [7751, 2], [7752, 1, "\u1E49"], [7753, 2], [7754, 1, "\u1E4B"], [7755, 2], [7756, 1, "\u1E4D"], [7757, 2], [7758, 1, "\u1E4F"], [7759, 2], [7760, 1, "\u1E51"], [7761, 2], [7762, 1, "\u1E53"], [7763, 2], [7764, 1, "\u1E55"], [7765, 2], [7766, 1, "\u1E57"], [7767, 2], [7768, 1, "\u1E59"], [7769, 2], [7770, 1, "\u1E5B"], [7771, 2], [7772, 1, "\u1E5D"], [7773, 2], [7774, 1, "\u1E5F"], [7775, 2], [7776, 1, "\u1E61"], [7777, 2], [7778, 1, "\u1E63"], [7779, 2], [7780, 1, "\u1E65"], [7781, 2], [7782, 1, "\u1E67"], [7783, 2], [7784, 1, "\u1E69"], [7785, 2], [7786, 1, "\u1E6B"], [7787, 2], [7788, 1, "\u1E6D"], [7789, 2], [7790, 1, "\u1E6F"], [7791, 2], [7792, 1, "\u1E71"], [7793, 2], [7794, 1, "\u1E73"], [7795, 2], [7796, 1, "\u1E75"], [7797, 2], [7798, 1, "\u1E77"], [7799, 2], [7800, 1, "\u1E79"], [7801, 2], [7802, 1, "\u1E7B"], [7803, 2], [7804, 1, "\u1E7D"], [7805, 2], [7806, 1, "\u1E7F"], [7807, 2], [7808, 1, "\u1E81"], [7809, 2], [7810, 1, "\u1E83"], [7811, 2], [7812, 1, "\u1E85"], [7813, 2], [7814, 1, "\u1E87"], [7815, 2], [7816, 1, "\u1E89"], [7817, 2], [7818, 1, "\u1E8B"], [7819, 2], [7820, 1, "\u1E8D"], [7821, 2], [7822, 1, "\u1E8F"], [7823, 2], [7824, 1, "\u1E91"], [7825, 2], [7826, 1, "\u1E93"], [7827, 2], [7828, 1, "\u1E95"], [[7829, 7833], 2], [7834, 1, "a\u02BE"], [7835, 1, "\u1E61"], [[7836, 7837], 2], [7838, 1, "ss"], [7839, 2], [7840, 1, "\u1EA1"], [7841, 2], [7842, 1, "\u1EA3"], [7843, 2], [7844, 1, "\u1EA5"], [7845, 2], [7846, 1, "\u1EA7"], [7847, 2], [7848, 1, "\u1EA9"], [7849, 2], [7850, 1, "\u1EAB"], [7851, 2], [7852, 1, "\u1EAD"], [7853, 2], [7854, 1, "\u1EAF"], [7855, 2], [7856, 1, "\u1EB1"], [7857, 2], [7858, 1, "\u1EB3"], [7859, 2], [7860, 1, "\u1EB5"], [7861, 2], [7862, 1, "\u1EB7"], [7863, 2], [7864, 1, "\u1EB9"], [7865, 2], [7866, 1, "\u1EBB"], [7867, 2], [7868, 1, "\u1EBD"], [7869, 2], [7870, 1, "\u1EBF"], [7871, 2], [7872, 1, "\u1EC1"], [7873, 2], [7874, 1, "\u1EC3"], [7875, 2], [7876, 1, "\u1EC5"], [7877, 2], [7878, 1, "\u1EC7"], [7879, 2], [7880, 1, "\u1EC9"], [7881, 2], [7882, 1, "\u1ECB"], [7883, 2], [7884, 1, "\u1ECD"], [7885, 2], [7886, 1, "\u1ECF"], [7887, 2], [7888, 1, "\u1ED1"], [7889, 2], [7890, 1, "\u1ED3"], [7891, 2], [7892, 1, "\u1ED5"], [7893, 2], [7894, 1, "\u1ED7"], [7895, 2], [7896, 1, "\u1ED9"], [7897, 2], [7898, 1, "\u1EDB"], [7899, 2], [7900, 1, "\u1EDD"], [7901, 2], [7902, 1, "\u1EDF"], [7903, 2], [7904, 1, "\u1EE1"], [7905, 2], [7906, 1, "\u1EE3"], [7907, 2], [7908, 1, "\u1EE5"], [7909, 2], [7910, 1, "\u1EE7"], [7911, 2], [7912, 1, "\u1EE9"], [7913, 2], [7914, 1, "\u1EEB"], [7915, 2], [7916, 1, "\u1EED"], [7917, 2], [7918, 1, "\u1EEF"], [7919, 2], [7920, 1, "\u1EF1"], [7921, 2], [7922, 1, "\u1EF3"], [7923, 2], [7924, 1, "\u1EF5"], [7925, 2], [7926, 1, "\u1EF7"], [7927, 2], [7928, 1, "\u1EF9"], [7929, 2], [7930, 1, "\u1EFB"], [7931, 2], [7932, 1, "\u1EFD"], [7933, 2], [7934, 1, "\u1EFF"], [7935, 2], [[7936, 7943], 2], [7944, 1, "\u1F00"], [7945, 1, "\u1F01"], [7946, 1, "\u1F02"], [7947, 1, "\u1F03"], [7948, 1, "\u1F04"], [7949, 1, "\u1F05"], [7950, 1, "\u1F06"], [7951, 1, "\u1F07"], [[7952, 7957], 2], [[7958, 7959], 3], [7960, 1, "\u1F10"], [7961, 1, "\u1F11"], [7962, 1, "\u1F12"], [7963, 1, "\u1F13"], [7964, 1, "\u1F14"], [7965, 1, "\u1F15"], [[7966, 7967], 3], [[7968, 7975], 2], [7976, 1, "\u1F20"], [7977, 1, "\u1F21"], [7978, 1, "\u1F22"], [7979, 1, "\u1F23"], [7980, 1, "\u1F24"], [7981, 1, "\u1F25"], [7982, 1, "\u1F26"], [7983, 1, "\u1F27"], [[7984, 7991], 2], [7992, 1, "\u1F30"], [7993, 1, "\u1F31"], [7994, 1, "\u1F32"], [7995, 1, "\u1F33"], [7996, 1, "\u1F34"], [7997, 1, "\u1F35"], [7998, 1, "\u1F36"], [7999, 1, "\u1F37"], [[8e3, 8005], 2], [[8006, 8007], 3], [8008, 1, "\u1F40"], [8009, 1, "\u1F41"], [8010, 1, "\u1F42"], [8011, 1, "\u1F43"], [8012, 1, "\u1F44"], [8013, 1, "\u1F45"], [[8014, 8015], 3], [[8016, 8023], 2], [8024, 3], [8025, 1, "\u1F51"], [8026, 3], [8027, 1, "\u1F53"], [8028, 3], [8029, 1, "\u1F55"], [8030, 3], [8031, 1, "\u1F57"], [[8032, 8039], 2], [8040, 1, "\u1F60"], [8041, 1, "\u1F61"], [8042, 1, "\u1F62"], [8043, 1, "\u1F63"], [8044, 1, "\u1F64"], [8045, 1, "\u1F65"], [8046, 1, "\u1F66"], [8047, 1, "\u1F67"], [8048, 2], [8049, 1, "\u03AC"], [8050, 2], [8051, 1, "\u03AD"], [8052, 2], [8053, 1, "\u03AE"], [8054, 2], [8055, 1, "\u03AF"], [8056, 2], [8057, 1, "\u03CC"], [8058, 2], [8059, 1, "\u03CD"], [8060, 2], [8061, 1, "\u03CE"], [[8062, 8063], 3], [8064, 1, "\u1F00\u03B9"], [8065, 1, "\u1F01\u03B9"], [8066, 1, "\u1F02\u03B9"], [8067, 1, "\u1F03\u03B9"], [8068, 1, "\u1F04\u03B9"], [8069, 1, "\u1F05\u03B9"], [8070, 1, "\u1F06\u03B9"], [8071, 1, "\u1F07\u03B9"], [8072, 1, "\u1F00\u03B9"], [8073, 1, "\u1F01\u03B9"], [8074, 1, "\u1F02\u03B9"], [8075, 1, "\u1F03\u03B9"], [8076, 1, "\u1F04\u03B9"], [8077, 1, "\u1F05\u03B9"], [8078, 1, "\u1F06\u03B9"], [8079, 1, "\u1F07\u03B9"], [8080, 1, "\u1F20\u03B9"], [8081, 1, "\u1F21\u03B9"], [8082, 1, "\u1F22\u03B9"], [8083, 1, "\u1F23\u03B9"], [8084, 1, "\u1F24\u03B9"], [8085, 1, "\u1F25\u03B9"], [8086, 1, "\u1F26\u03B9"], [8087, 1, "\u1F27\u03B9"], [8088, 1, "\u1F20\u03B9"], [8089, 1, "\u1F21\u03B9"], [8090, 1, "\u1F22\u03B9"], [8091, 1, "\u1F23\u03B9"], [8092, 1, "\u1F24\u03B9"], [8093, 1, "\u1F25\u03B9"], [8094, 1, "\u1F26\u03B9"], [8095, 1, "\u1F27\u03B9"], [8096, 1, "\u1F60\u03B9"], [8097, 1, "\u1F61\u03B9"], [8098, 1, "\u1F62\u03B9"], [8099, 1, "\u1F63\u03B9"], [8100, 1, "\u1F64\u03B9"], [8101, 1, "\u1F65\u03B9"], [8102, 1, "\u1F66\u03B9"], [8103, 1, "\u1F67\u03B9"], [8104, 1, "\u1F60\u03B9"], [8105, 1, "\u1F61\u03B9"], [8106, 1, "\u1F62\u03B9"], [8107, 1, "\u1F63\u03B9"], [8108, 1, "\u1F64\u03B9"], [8109, 1, "\u1F65\u03B9"], [8110, 1, "\u1F66\u03B9"], [8111, 1, "\u1F67\u03B9"], [[8112, 8113], 2], [8114, 1, "\u1F70\u03B9"], [8115, 1, "\u03B1\u03B9"], [8116, 1, "\u03AC\u03B9"], [8117, 3], [8118, 2], [8119, 1, "\u1FB6\u03B9"], [8120, 1, "\u1FB0"], [8121, 1, "\u1FB1"], [8122, 1, "\u1F70"], [8123, 1, "\u03AC"], [8124, 1, "\u03B1\u03B9"], [8125, 5, " \u0313"], [8126, 1, "\u03B9"], [8127, 5, " \u0313"], [8128, 5, " \u0342"], [8129, 5, " \u0308\u0342"], [8130, 1, "\u1F74\u03B9"], [8131, 1, "\u03B7\u03B9"], [8132, 1, "\u03AE\u03B9"], [8133, 3], [8134, 2], [8135, 1, "\u1FC6\u03B9"], [8136, 1, "\u1F72"], [8137, 1, "\u03AD"], [8138, 1, "\u1F74"], [8139, 1, "\u03AE"], [8140, 1, "\u03B7\u03B9"], [8141, 5, " \u0313\u0300"], [8142, 5, " \u0313\u0301"], [8143, 5, " \u0313\u0342"], [[8144, 8146], 2], [8147, 1, "\u0390"], [[8148, 8149], 3], [[8150, 8151], 2], [8152, 1, "\u1FD0"], [8153, 1, "\u1FD1"], [8154, 1, "\u1F76"], [8155, 1, "\u03AF"], [8156, 3], [8157, 5, " \u0314\u0300"], [8158, 5, " \u0314\u0301"], [8159, 5, " \u0314\u0342"], [[8160, 8162], 2], [8163, 1, "\u03B0"], [[8164, 8167], 2], [8168, 1, "\u1FE0"], [8169, 1, "\u1FE1"], [8170, 1, "\u1F7A"], [8171, 1, "\u03CD"], [8172, 1, "\u1FE5"], [8173, 5, " \u0308\u0300"], [8174, 5, " \u0308\u0301"], [8175, 5, "`"], [[8176, 8177], 3], [8178, 1, "\u1F7C\u03B9"], [8179, 1, "\u03C9\u03B9"], [8180, 1, "\u03CE\u03B9"], [8181, 3], [8182, 2], [8183, 1, "\u1FF6\u03B9"], [8184, 1, "\u1F78"], [8185, 1, "\u03CC"], [8186, 1, "\u1F7C"], [8187, 1, "\u03CE"], [8188, 1, "\u03C9\u03B9"], [8189, 5, " \u0301"], [8190, 5, " \u0314"], [8191, 3], [[8192, 8202], 5, " "], [8203, 7], [[8204, 8205], 6, ""], [[8206, 8207], 3], [8208, 2], [8209, 1, "\u2010"], [[8210, 8214], 2], [8215, 5, " \u0333"], [[8216, 8227], 2], [[8228, 8230], 3], [8231, 2], [[8232, 8238], 3], [8239, 5, " "], [[8240, 8242], 2], [8243, 1, "\u2032\u2032"], [8244, 1, "\u2032\u2032\u2032"], [8245, 2], [8246, 1, "\u2035\u2035"], [8247, 1, "\u2035\u2035\u2035"], [[8248, 8251], 2], [8252, 5, "!!"], [8253, 2], [8254, 5, " \u0305"], [[8255, 8262], 2], [8263, 5, "??"], [8264, 5, "?!"], [8265, 5, "!?"], [[8266, 8269], 2], [[8270, 8274], 2], [[8275, 8276], 2], [[8277, 8278], 2], [8279, 1, "\u2032\u2032\u2032\u2032"], [[8280, 8286], 2], [8287, 5, " "], [8288, 7], [[8289, 8291], 3], [8292, 7], [8293, 3], [[8294, 8297], 3], [[8298, 8303], 3], [8304, 1, "0"], [8305, 1, "i"], [[8306, 8307], 3], [8308, 1, "4"], [8309, 1, "5"], [8310, 1, "6"], [8311, 1, "7"], [8312, 1, "8"], [8313, 1, "9"], [8314, 5, "+"], [8315, 1, "\u2212"], [8316, 5, "="], [8317, 5, "("], [8318, 5, ")"], [8319, 1, "n"], [8320, 1, "0"], [8321, 1, "1"], [8322, 1, "2"], [8323, 1, "3"], [8324, 1, "4"], [8325, 1, "5"], [8326, 1, "6"], [8327, 1, "7"], [8328, 1, "8"], [8329, 1, "9"], [8330, 5, "+"], [8331, 1, "\u2212"], [8332, 5, "="], [8333, 5, "("], [8334, 5, ")"], [8335, 3], [8336, 1, "a"], [8337, 1, "e"], [8338, 1, "o"], [8339, 1, "x"], [8340, 1, "\u0259"], [8341, 1, "h"], [8342, 1, "k"], [8343, 1, "l"], [8344, 1, "m"], [8345, 1, "n"], [8346, 1, "p"], [8347, 1, "s"], [8348, 1, "t"], [[8349, 8351], 3], [[8352, 8359], 2], [8360, 1, "rs"], [[8361, 8362], 2], [8363, 2], [8364, 2], [[8365, 8367], 2], [[8368, 8369], 2], [[8370, 8373], 2], [[8374, 8376], 2], [8377, 2], [8378, 2], [[8379, 8381], 2], [8382, 2], [8383, 2], [8384, 2], [[8385, 8399], 3], [[8400, 8417], 2], [[8418, 8419], 2], [[8420, 8426], 2], [8427, 2], [[8428, 8431], 2], [8432, 2], [[8433, 8447], 3], [8448, 5, "a/c"], [8449, 5, "a/s"], [8450, 1, "c"], [8451, 1, "\xB0c"], [8452, 2], [8453, 5, "c/o"], [8454, 5, "c/u"], [8455, 1, "\u025B"], [8456, 2], [8457, 1, "\xB0f"], [8458, 1, "g"], [[8459, 8462], 1, "h"], [8463, 1, "\u0127"], [[8464, 8465], 1, "i"], [[8466, 8467], 1, "l"], [8468, 2], [8469, 1, "n"], [8470, 1, "no"], [[8471, 8472], 2], [8473, 1, "p"], [8474, 1, "q"], [[8475, 8477], 1, "r"], [[8478, 8479], 2], [8480, 1, "sm"], [8481, 1, "tel"], [8482, 1, "tm"], [8483, 2], [8484, 1, "z"], [8485, 2], [8486, 1, "\u03C9"], [8487, 2], [8488, 1, "z"], [8489, 2], [8490, 1, "k"], [8491, 1, "\xE5"], [8492, 1, "b"], [8493, 1, "c"], [8494, 2], [[8495, 8496], 1, "e"], [8497, 1, "f"], [8498, 3], [8499, 1, "m"], [8500, 1, "o"], [8501, 1, "\u05D0"], [8502, 1, "\u05D1"], [8503, 1, "\u05D2"], [8504, 1, "\u05D3"], [8505, 1, "i"], [8506, 2], [8507, 1, "fax"], [8508, 1, "\u03C0"], [[8509, 8510], 1, "\u03B3"], [8511, 1, "\u03C0"], [8512, 1, "\u2211"], [[8513, 8516], 2], [[8517, 8518], 1, "d"], [8519, 1, "e"], [8520, 1, "i"], [8521, 1, "j"], [[8522, 8523], 2], [8524, 2], [8525, 2], [8526, 2], [8527, 2], [8528, 1, "1\u20447"], [8529, 1, "1\u20449"], [8530, 1, "1\u204410"], [8531, 1, "1\u20443"], [8532, 1, "2\u20443"], [8533, 1, "1\u20445"], [8534, 1, "2\u20445"], [8535, 1, "3\u20445"], [8536, 1, "4\u20445"], [8537, 1, "1\u20446"], [8538, 1, "5\u20446"], [8539, 1, "1\u20448"], [8540, 1, "3\u20448"], [8541, 1, "5\u20448"], [8542, 1, "7\u20448"], [8543, 1, "1\u2044"], [8544, 1, "i"], [8545, 1, "ii"], [8546, 1, "iii"], [8547, 1, "iv"], [8548, 1, "v"], [8549, 1, "vi"], [8550, 1, "vii"], [8551, 1, "viii"], [8552, 1, "ix"], [8553, 1, "x"], [8554, 1, "xi"], [8555, 1, "xii"], [8556, 1, "l"], [8557, 1, "c"], [8558, 1, "d"], [8559, 1, "m"], [8560, 1, "i"], [8561, 1, "ii"], [8562, 1, "iii"], [8563, 1, "iv"], [8564, 1, "v"], [8565, 1, "vi"], [8566, 1, "vii"], [8567, 1, "viii"], [8568, 1, "ix"], [8569, 1, "x"], [8570, 1, "xi"], [8571, 1, "xii"], [8572, 1, "l"], [8573, 1, "c"], [8574, 1, "d"], [8575, 1, "m"], [[8576, 8578], 2], [8579, 3], [8580, 2], [[8581, 8584], 2], [8585, 1, "0\u20443"], [[8586, 8587], 2], [[8588, 8591], 3], [[8592, 8682], 2], [[8683, 8691], 2], [[8692, 8703], 2], [[8704, 8747], 2], [8748, 1, "\u222B\u222B"], [8749, 1, "\u222B\u222B\u222B"], [8750, 2], [8751, 1, "\u222E\u222E"], [8752, 1, "\u222E\u222E\u222E"], [[8753, 8799], 2], [8800, 4], [[8801, 8813], 2], [[8814, 8815], 4], [[8816, 8945], 2], [[8946, 8959], 2], [8960, 2], [8961, 2], [[8962, 9e3], 2], [9001, 1, "\u3008"], [9002, 1, "\u3009"], [[9003, 9082], 2], [9083, 2], [9084, 2], [[9085, 9114], 2], [[9115, 9166], 2], [[9167, 9168], 2], [[9169, 9179], 2], [[9180, 9191], 2], [9192, 2], [[9193, 9203], 2], [[9204, 9210], 2], [[9211, 9214], 2], [9215, 2], [[9216, 9252], 2], [[9253, 9254], 2], [[9255, 9279], 3], [[9280, 9290], 2], [[9291, 9311], 3], [9312, 1, "1"], [9313, 1, "2"], [9314, 1, "3"], [9315, 1, "4"], [9316, 1, "5"], [9317, 1, "6"], [9318, 1, "7"], [9319, 1, "8"], [9320, 1, "9"], [9321, 1, "10"], [9322, 1, "11"], [9323, 1, "12"], [9324, 1, "13"], [9325, 1, "14"], [9326, 1, "15"], [9327, 1, "16"], [9328, 1, "17"], [9329, 1, "18"], [9330, 1, "19"], [9331, 1, "20"], [9332, 5, "(1)"], [9333, 5, "(2)"], [9334, 5, "(3)"], [9335, 5, "(4)"], [9336, 5, "(5)"], [9337, 5, "(6)"], [9338, 5, "(7)"], [9339, 5, "(8)"], [9340, 5, "(9)"], [9341, 5, "(10)"], [9342, 5, "(11)"], [9343, 5, "(12)"], [9344, 5, "(13)"], [9345, 5, "(14)"], [9346, 5, "(15)"], [9347, 5, "(16)"], [9348, 5, "(17)"], [9349, 5, "(18)"], [9350, 5, "(19)"], [9351, 5, "(20)"], [[9352, 9371], 3], [9372, 5, "(a)"], [9373, 5, "(b)"], [9374, 5, "(c)"], [9375, 5, "(d)"], [9376, 5, "(e)"], [9377, 5, "(f)"], [9378, 5, "(g)"], [9379, 5, "(h)"], [9380, 5, "(i)"], [9381, 5, "(j)"], [9382, 5, "(k)"], [9383, 5, "(l)"], [9384, 5, "(m)"], [9385, 5, "(n)"], [9386, 5, "(o)"], [9387, 5, "(p)"], [9388, 5, "(q)"], [9389, 5, "(r)"], [9390, 5, "(s)"], [9391, 5, "(t)"], [9392, 5, "(u)"], [9393, 5, "(v)"], [9394, 5, "(w)"], [9395, 5, "(x)"], [9396, 5, "(y)"], [9397, 5, "(z)"], [9398, 1, "a"], [9399, 1, "b"], [9400, 1, "c"], [9401, 1, "d"], [9402, 1, "e"], [9403, 1, "f"], [9404, 1, "g"], [9405, 1, "h"], [9406, 1, "i"], [9407, 1, "j"], [9408, 1, "k"], [9409, 1, "l"], [9410, 1, "m"], [9411, 1, "n"], [9412, 1, "o"], [9413, 1, "p"], [9414, 1, "q"], [9415, 1, "r"], [9416, 1, "s"], [9417, 1, "t"], [9418, 1, "u"], [9419, 1, "v"], [9420, 1, "w"], [9421, 1, "x"], [9422, 1, "y"], [9423, 1, "z"], [9424, 1, "a"], [9425, 1, "b"], [9426, 1, "c"], [9427, 1, "d"], [9428, 1, "e"], [9429, 1, "f"], [9430, 1, "g"], [9431, 1, "h"], [9432, 1, "i"], [9433, 1, "j"], [9434, 1, "k"], [9435, 1, "l"], [9436, 1, "m"], [9437, 1, "n"], [9438, 1, "o"], [9439, 1, "p"], [9440, 1, "q"], [9441, 1, "r"], [9442, 1, "s"], [9443, 1, "t"], [9444, 1, "u"], [9445, 1, "v"], [9446, 1, "w"], [9447, 1, "x"], [9448, 1, "y"], [9449, 1, "z"], [9450, 1, "0"], [[9451, 9470], 2], [9471, 2], [[9472, 9621], 2], [[9622, 9631], 2], [[9632, 9711], 2], [[9712, 9719], 2], [[9720, 9727], 2], [[9728, 9747], 2], [[9748, 9749], 2], [[9750, 9751], 2], [9752, 2], [9753, 2], [[9754, 9839], 2], [[9840, 9841], 2], [[9842, 9853], 2], [[9854, 9855], 2], [[9856, 9865], 2], [[9866, 9873], 2], [[9874, 9884], 2], [9885, 2], [[9886, 9887], 2], [[9888, 9889], 2], [[9890, 9905], 2], [9906, 2], [[9907, 9916], 2], [[9917, 9919], 2], [[9920, 9923], 2], [[9924, 9933], 2], [9934, 2], [[9935, 9953], 2], [9954, 2], [9955, 2], [[9956, 9959], 2], [[9960, 9983], 2], [9984, 2], [[9985, 9988], 2], [9989, 2], [[9990, 9993], 2], [[9994, 9995], 2], [[9996, 10023], 2], [10024, 2], [[10025, 10059], 2], [10060, 2], [10061, 2], [10062, 2], [[10063, 10066], 2], [[10067, 10069], 2], [10070, 2], [10071, 2], [[10072, 10078], 2], [[10079, 10080], 2], [[10081, 10087], 2], [[10088, 10101], 2], [[10102, 10132], 2], [[10133, 10135], 2], [[10136, 10159], 2], [10160, 2], [[10161, 10174], 2], [10175, 2], [[10176, 10182], 2], [[10183, 10186], 2], [10187, 2], [10188, 2], [10189, 2], [[10190, 10191], 2], [[10192, 10219], 2], [[10220, 10223], 2], [[10224, 10239], 2], [[10240, 10495], 2], [[10496, 10763], 2], [10764, 1, "\u222B\u222B\u222B\u222B"], [[10765, 10867], 2], [10868, 5, "::="], [10869, 5, "=="], [10870, 5, "==="], [[10871, 10971], 2], [10972, 1, "\u2ADD\u0338"], [[10973, 11007], 2], [[11008, 11021], 2], [[11022, 11027], 2], [[11028, 11034], 2], [[11035, 11039], 2], [[11040, 11043], 2], [[11044, 11084], 2], [[11085, 11087], 2], [[11088, 11092], 2], [[11093, 11097], 2], [[11098, 11123], 2], [[11124, 11125], 3], [[11126, 11157], 2], [11158, 3], [11159, 2], [[11160, 11193], 2], [[11194, 11196], 2], [[11197, 11208], 2], [11209, 2], [[11210, 11217], 2], [11218, 2], [[11219, 11243], 2], [[11244, 11247], 2], [[11248, 11262], 2], [11263, 2], [11264, 1, "\u2C30"], [11265, 1, "\u2C31"], [11266, 1, "\u2C32"], [11267, 1, "\u2C33"], [11268, 1, "\u2C34"], [11269, 1, "\u2C35"], [11270, 1, "\u2C36"], [11271, 1, "\u2C37"], [11272, 1, "\u2C38"], [11273, 1, "\u2C39"], [11274, 1, "\u2C3A"], [11275, 1, "\u2C3B"], [11276, 1, "\u2C3C"], [11277, 1, "\u2C3D"], [11278, 1, "\u2C3E"], [11279, 1, "\u2C3F"], [11280, 1, "\u2C40"], [11281, 1, "\u2C41"], [11282, 1, "\u2C42"], [11283, 1, "\u2C43"], [11284, 1, "\u2C44"], [11285, 1, "\u2C45"], [11286, 1, "\u2C46"], [11287, 1, "\u2C47"], [11288, 1, "\u2C48"], [11289, 1, "\u2C49"], [11290, 1, "\u2C4A"], [11291, 1, "\u2C4B"], [11292, 1, "\u2C4C"], [11293, 1, "\u2C4D"], [11294, 1, "\u2C4E"], [11295, 1, "\u2C4F"], [11296, 1, "\u2C50"], [11297, 1, "\u2C51"], [11298, 1, "\u2C52"], [11299, 1, "\u2C53"], [11300, 1, "\u2C54"], [11301, 1, "\u2C55"], [11302, 1, "\u2C56"], [11303, 1, "\u2C57"], [11304, 1, "\u2C58"], [11305, 1, "\u2C59"], [11306, 1, "\u2C5A"], [11307, 1, "\u2C5B"], [11308, 1, "\u2C5C"], [11309, 1, "\u2C5D"], [11310, 1, "\u2C5E"], [11311, 1, "\u2C5F"], [[11312, 11358], 2], [11359, 2], [11360, 1, "\u2C61"], [11361, 2], [11362, 1, "\u026B"], [11363, 1, "\u1D7D"], [11364, 1, "\u027D"], [[11365, 11366], 2], [11367, 1, "\u2C68"], [11368, 2], [11369, 1, "\u2C6A"], [11370, 2], [11371, 1, "\u2C6C"], [11372, 2], [11373, 1, "\u0251"], [11374, 1, "\u0271"], [11375, 1, "\u0250"], [11376, 1, "\u0252"], [11377, 2], [11378, 1, "\u2C73"], [11379, 2], [11380, 2], [11381, 1, "\u2C76"], [[11382, 11383], 2], [[11384, 11387], 2], [11388, 1, "j"], [11389, 1, "v"], [11390, 1, "\u023F"], [11391, 1, "\u0240"], [11392, 1, "\u2C81"], [11393, 2], [11394, 1, "\u2C83"], [11395, 2], [11396, 1, "\u2C85"], [11397, 2], [11398, 1, "\u2C87"], [11399, 2], [11400, 1, "\u2C89"], [11401, 2], [11402, 1, "\u2C8B"], [11403, 2], [11404, 1, "\u2C8D"], [11405, 2], [11406, 1, "\u2C8F"], [11407, 2], [11408, 1, "\u2C91"], [11409, 2], [11410, 1, "\u2C93"], [11411, 2], [11412, 1, "\u2C95"], [11413, 2], [11414, 1, "\u2C97"], [11415, 2], [11416, 1, "\u2C99"], [11417, 2], [11418, 1, "\u2C9B"], [11419, 2], [11420, 1, "\u2C9D"], [11421, 2], [11422, 1, "\u2C9F"], [11423, 2], [11424, 1, "\u2CA1"], [11425, 2], [11426, 1, "\u2CA3"], [11427, 2], [11428, 1, "\u2CA5"], [11429, 2], [11430, 1, "\u2CA7"], [11431, 2], [11432, 1, "\u2CA9"], [11433, 2], [11434, 1, "\u2CAB"], [11435, 2], [11436, 1, "\u2CAD"], [11437, 2], [11438, 1, "\u2CAF"], [11439, 2], [11440, 1, "\u2CB1"], [11441, 2], [11442, 1, "\u2CB3"], [11443, 2], [11444, 1, "\u2CB5"], [11445, 2], [11446, 1, "\u2CB7"], [11447, 2], [11448, 1, "\u2CB9"], [11449, 2], [11450, 1, "\u2CBB"], [11451, 2], [11452, 1, "\u2CBD"], [11453, 2], [11454, 1, "\u2CBF"], [11455, 2], [11456, 1, "\u2CC1"], [11457, 2], [11458, 1, "\u2CC3"], [11459, 2], [11460, 1, "\u2CC5"], [11461, 2], [11462, 1, "\u2CC7"], [11463, 2], [11464, 1, "\u2CC9"], [11465, 2], [11466, 1, "\u2CCB"], [11467, 2], [11468, 1, "\u2CCD"], [11469, 2], [11470, 1, "\u2CCF"], [11471, 2], [11472, 1, "\u2CD1"], [11473, 2], [11474, 1, "\u2CD3"], [11475, 2], [11476, 1, "\u2CD5"], [11477, 2], [11478, 1, "\u2CD7"], [11479, 2], [11480, 1, "\u2CD9"], [11481, 2], [11482, 1, "\u2CDB"], [11483, 2], [11484, 1, "\u2CDD"], [11485, 2], [11486, 1, "\u2CDF"], [11487, 2], [11488, 1, "\u2CE1"], [11489, 2], [11490, 1, "\u2CE3"], [[11491, 11492], 2], [[11493, 11498], 2], [11499, 1, "\u2CEC"], [11500, 2], [11501, 1, "\u2CEE"], [[11502, 11505], 2], [11506, 1, "\u2CF3"], [11507, 2], [[11508, 11512], 3], [[11513, 11519], 2], [[11520, 11557], 2], [11558, 3], [11559, 2], [[11560, 11564], 3], [11565, 2], [[11566, 11567], 3], [[11568, 11621], 2], [[11622, 11623], 2], [[11624, 11630], 3], [11631, 1, "\u2D61"], [11632, 2], [[11633, 11646], 3], [11647, 2], [[11648, 11670], 2], [[11671, 11679], 3], [[11680, 11686], 2], [11687, 3], [[11688, 11694], 2], [11695, 3], [[11696, 11702], 2], [11703, 3], [[11704, 11710], 2], [11711, 3], [[11712, 11718], 2], [11719, 3], [[11720, 11726], 2], [11727, 3], [[11728, 11734], 2], [11735, 3], [[11736, 11742], 2], [11743, 3], [[11744, 11775], 2], [[11776, 11799], 2], [[11800, 11803], 2], [[11804, 11805], 2], [[11806, 11822], 2], [11823, 2], [11824, 2], [11825, 2], [[11826, 11835], 2], [[11836, 11842], 2], [[11843, 11844], 2], [[11845, 11849], 2], [[11850, 11854], 2], [11855, 2], [[11856, 11858], 2], [[11859, 11869], 2], [[11870, 11903], 3], [[11904, 11929], 2], [11930, 3], [[11931, 11934], 2], [11935, 1, "\u6BCD"], [[11936, 12018], 2], [12019, 1, "\u9F9F"], [[12020, 12031], 3], [12032, 1, "\u4E00"], [12033, 1, "\u4E28"], [12034, 1, "\u4E36"], [12035, 1, "\u4E3F"], [12036, 1, "\u4E59"], [12037, 1, "\u4E85"], [12038, 1, "\u4E8C"], [12039, 1, "\u4EA0"], [12040, 1, "\u4EBA"], [12041, 1, "\u513F"], [12042, 1, "\u5165"], [12043, 1, "\u516B"], [12044, 1, "\u5182"], [12045, 1, "\u5196"], [12046, 1, "\u51AB"], [12047, 1, "\u51E0"], [12048, 1, "\u51F5"], [12049, 1, "\u5200"], [12050, 1, "\u529B"], [12051, 1, "\u52F9"], [12052, 1, "\u5315"], [12053, 1, "\u531A"], [12054, 1, "\u5338"], [12055, 1, "\u5341"], [12056, 1, "\u535C"], [12057, 1, "\u5369"], [12058, 1, "\u5382"], [12059, 1, "\u53B6"], [12060, 1, "\u53C8"], [12061, 1, "\u53E3"], [12062, 1, "\u56D7"], [12063, 1, "\u571F"], [12064, 1, "\u58EB"], [12065, 1, "\u5902"], [12066, 1, "\u590A"], [12067, 1, "\u5915"], [12068, 1, "\u5927"], [12069, 1, "\u5973"], [12070, 1, "\u5B50"], [12071, 1, "\u5B80"], [12072, 1, "\u5BF8"], [12073, 1, "\u5C0F"], [12074, 1, "\u5C22"], [12075, 1, "\u5C38"], [12076, 1, "\u5C6E"], [12077, 1, "\u5C71"], [12078, 1, "\u5DDB"], [12079, 1, "\u5DE5"], [12080, 1, "\u5DF1"], [12081, 1, "\u5DFE"], [12082, 1, "\u5E72"], [12083, 1, "\u5E7A"], [12084, 1, "\u5E7F"], [12085, 1, "\u5EF4"], [12086, 1, "\u5EFE"], [12087, 1, "\u5F0B"], [12088, 1, "\u5F13"], [12089, 1, "\u5F50"], [12090, 1, "\u5F61"], [12091, 1, "\u5F73"], [12092, 1, "\u5FC3"], [12093, 1, "\u6208"], [12094, 1, "\u6236"], [12095, 1, "\u624B"], [12096, 1, "\u652F"], [12097, 1, "\u6534"], [12098, 1, "\u6587"], [12099, 1, "\u6597"], [12100, 1, "\u65A4"], [12101, 1, "\u65B9"], [12102, 1, "\u65E0"], [12103, 1, "\u65E5"], [12104, 1, "\u66F0"], [12105, 1, "\u6708"], [12106, 1, "\u6728"], [12107, 1, "\u6B20"], [12108, 1, "\u6B62"], [12109, 1, "\u6B79"], [12110, 1, "\u6BB3"], [12111, 1, "\u6BCB"], [12112, 1, "\u6BD4"], [12113, 1, "\u6BDB"], [12114, 1, "\u6C0F"], [12115, 1, "\u6C14"], [12116, 1, "\u6C34"], [12117, 1, "\u706B"], [12118, 1, "\u722A"], [12119, 1, "\u7236"], [12120, 1, "\u723B"], [12121, 1, "\u723F"], [12122, 1, "\u7247"], [12123, 1, "\u7259"], [12124, 1, "\u725B"], [12125, 1, "\u72AC"], [12126, 1, "\u7384"], [12127, 1, "\u7389"], [12128, 1, "\u74DC"], [12129, 1, "\u74E6"], [12130, 1, "\u7518"], [12131, 1, "\u751F"], [12132, 1, "\u7528"], [12133, 1, "\u7530"], [12134, 1, "\u758B"], [12135, 1, "\u7592"], [12136, 1, "\u7676"], [12137, 1, "\u767D"], [12138, 1, "\u76AE"], [12139, 1, "\u76BF"], [12140, 1, "\u76EE"], [12141, 1, "\u77DB"], [12142, 1, "\u77E2"], [12143, 1, "\u77F3"], [12144, 1, "\u793A"], [12145, 1, "\u79B8"], [12146, 1, "\u79BE"], [12147, 1, "\u7A74"], [12148, 1, "\u7ACB"], [12149, 1, "\u7AF9"], [12150, 1, "\u7C73"], [12151, 1, "\u7CF8"], [12152, 1, "\u7F36"], [12153, 1, "\u7F51"], [12154, 1, "\u7F8A"], [12155, 1, "\u7FBD"], [12156, 1, "\u8001"], [12157, 1, "\u800C"], [12158, 1, "\u8012"], [12159, 1, "\u8033"], [12160, 1, "\u807F"], [12161, 1, "\u8089"], [12162, 1, "\u81E3"], [12163, 1, "\u81EA"], [12164, 1, "\u81F3"], [12165, 1, "\u81FC"], [12166, 1, "\u820C"], [12167, 1, "\u821B"], [12168, 1, "\u821F"], [12169, 1, "\u826E"], [12170, 1, "\u8272"], [12171, 1, "\u8278"], [12172, 1, "\u864D"], [12173, 1, "\u866B"], [12174, 1, "\u8840"], [12175, 1, "\u884C"], [12176, 1, "\u8863"], [12177, 1, "\u897E"], [12178, 1, "\u898B"], [12179, 1, "\u89D2"], [12180, 1, "\u8A00"], [12181, 1, "\u8C37"], [12182, 1, "\u8C46"], [12183, 1, "\u8C55"], [12184, 1, "\u8C78"], [12185, 1, "\u8C9D"], [12186, 1, "\u8D64"], [12187, 1, "\u8D70"], [12188, 1, "\u8DB3"], [12189, 1, "\u8EAB"], [12190, 1, "\u8ECA"], [12191, 1, "\u8F9B"], [12192, 1, "\u8FB0"], [12193, 1, "\u8FB5"], [12194, 1, "\u9091"], [12195, 1, "\u9149"], [12196, 1, "\u91C6"], [12197, 1, "\u91CC"], [12198, 1, "\u91D1"], [12199, 1, "\u9577"], [12200, 1, "\u9580"], [12201, 1, "\u961C"], [12202, 1, "\u96B6"], [12203, 1, "\u96B9"], [12204, 1, "\u96E8"], [12205, 1, "\u9751"], [12206, 1, "\u975E"], [12207, 1, "\u9762"], [12208, 1, "\u9769"], [12209, 1, "\u97CB"], [12210, 1, "\u97ED"], [12211, 1, "\u97F3"], [12212, 1, "\u9801"], [12213, 1, "\u98A8"], [12214, 1, "\u98DB"], [12215, 1, "\u98DF"], [12216, 1, "\u9996"], [12217, 1, "\u9999"], [12218, 1, "\u99AC"], [12219, 1, "\u9AA8"], [12220, 1, "\u9AD8"], [12221, 1, "\u9ADF"], [12222, 1, "\u9B25"], [12223, 1, "\u9B2F"], [12224, 1, "\u9B32"], [12225, 1, "\u9B3C"], [12226, 1, "\u9B5A"], [12227, 1, "\u9CE5"], [12228, 1, "\u9E75"], [12229, 1, "\u9E7F"], [12230, 1, "\u9EA5"], [12231, 1, "\u9EBB"], [12232, 1, "\u9EC3"], [12233, 1, "\u9ECD"], [12234, 1, "\u9ED1"], [12235, 1, "\u9EF9"], [12236, 1, "\u9EFD"], [12237, 1, "\u9F0E"], [12238, 1, "\u9F13"], [12239, 1, "\u9F20"], [12240, 1, "\u9F3B"], [12241, 1, "\u9F4A"], [12242, 1, "\u9F52"], [12243, 1, "\u9F8D"], [12244, 1, "\u9F9C"], [12245, 1, "\u9FA0"], [[12246, 12271], 3], [[12272, 12283], 3], [[12284, 12287], 3], [12288, 5, " "], [12289, 2], [12290, 1, "."], [[12291, 12292], 2], [[12293, 12295], 2], [[12296, 12329], 2], [[12330, 12333], 2], [[12334, 12341], 2], [12342, 1, "\u3012"], [12343, 2], [12344, 1, "\u5341"], [12345, 1, "\u5344"], [12346, 1, "\u5345"], [12347, 2], [12348, 2], [12349, 2], [12350, 2], [12351, 2], [12352, 3], [[12353, 12436], 2], [[12437, 12438], 2], [[12439, 12440], 3], [[12441, 12442], 2], [12443, 5, " \u3099"], [12444, 5, " \u309A"], [[12445, 12446], 2], [12447, 1, "\u3088\u308A"], [12448, 2], [[12449, 12542], 2], [12543, 1, "\u30B3\u30C8"], [[12544, 12548], 3], [[12549, 12588], 2], [12589, 2], [12590, 2], [12591, 2], [12592, 3], [12593, 1, "\u1100"], [12594, 1, "\u1101"], [12595, 1, "\u11AA"], [12596, 1, "\u1102"], [12597, 1, "\u11AC"], [12598, 1, "\u11AD"], [12599, 1, "\u1103"], [12600, 1, "\u1104"], [12601, 1, "\u1105"], [12602, 1, "\u11B0"], [12603, 1, "\u11B1"], [12604, 1, "\u11B2"], [12605, 1, "\u11B3"], [12606, 1, "\u11B4"], [12607, 1, "\u11B5"], [12608, 1, "\u111A"], [12609, 1, "\u1106"], [12610, 1, "\u1107"], [12611, 1, "\u1108"], [12612, 1, "\u1121"], [12613, 1, "\u1109"], [12614, 1, "\u110A"], [12615, 1, "\u110B"], [12616, 1, "\u110C"], [12617, 1, "\u110D"], [12618, 1, "\u110E"], [12619, 1, "\u110F"], [12620, 1, "\u1110"], [12621, 1, "\u1111"], [12622, 1, "\u1112"], [12623, 1, "\u1161"], [12624, 1, "\u1162"], [12625, 1, "\u1163"], [12626, 1, "\u1164"], [12627, 1, "\u1165"], [12628, 1, "\u1166"], [12629, 1, "\u1167"], [12630, 1, "\u1168"], [12631, 1, "\u1169"], [12632, 1, "\u116A"], [12633, 1, "\u116B"], [12634, 1, "\u116C"], [12635, 1, "\u116D"], [12636, 1, "\u116E"], [12637, 1, "\u116F"], [12638, 1, "\u1170"], [12639, 1, "\u1171"], [12640, 1, "\u1172"], [12641, 1, "\u1173"], [12642, 1, "\u1174"], [12643, 1, "\u1175"], [12644, 3], [12645, 1, "\u1114"], [12646, 1, "\u1115"], [12647, 1, "\u11C7"], [12648, 1, "\u11C8"], [12649, 1, "\u11CC"], [12650, 1, "\u11CE"], [12651, 1, "\u11D3"], [12652, 1, "\u11D7"], [12653, 1, "\u11D9"], [12654, 1, "\u111C"], [12655, 1, "\u11DD"], [12656, 1, "\u11DF"], [12657, 1, "\u111D"], [12658, 1, "\u111E"], [12659, 1, "\u1120"], [12660, 1, "\u1122"], [12661, 1, "\u1123"], [12662, 1, "\u1127"], [12663, 1, "\u1129"], [12664, 1, "\u112B"], [12665, 1, "\u112C"], [12666, 1, "\u112D"], [12667, 1, "\u112E"], [12668, 1, "\u112F"], [12669, 1, "\u1132"], [12670, 1, "\u1136"], [12671, 1, "\u1140"], [12672, 1, "\u1147"], [12673, 1, "\u114C"], [12674, 1, "\u11F1"], [12675, 1, "\u11F2"], [12676, 1, "\u1157"], [12677, 1, "\u1158"], [12678, 1, "\u1159"], [12679, 1, "\u1184"], [12680, 1, "\u1185"], [12681, 1, "\u1188"], [12682, 1, "\u1191"], [12683, 1, "\u1192"], [12684, 1, "\u1194"], [12685, 1, "\u119E"], [12686, 1, "\u11A1"], [12687, 3], [[12688, 12689], 2], [12690, 1, "\u4E00"], [12691, 1, "\u4E8C"], [12692, 1, "\u4E09"], [12693, 1, "\u56DB"], [12694, 1, "\u4E0A"], [12695, 1, "\u4E2D"], [12696, 1, "\u4E0B"], [12697, 1, "\u7532"], [12698, 1, "\u4E59"], [12699, 1, "\u4E19"], [12700, 1, "\u4E01"], [12701, 1, "\u5929"], [12702, 1, "\u5730"], [12703, 1, "\u4EBA"], [[12704, 12727], 2], [[12728, 12730], 2], [[12731, 12735], 2], [[12736, 12751], 2], [[12752, 12771], 2], [[12772, 12783], 3], [[12784, 12799], 2], [12800, 5, "(\u1100)"], [12801, 5, "(\u1102)"], [12802, 5, "(\u1103)"], [12803, 5, "(\u1105)"], [12804, 5, "(\u1106)"], [12805, 5, "(\u1107)"], [12806, 5, "(\u1109)"], [12807, 5, "(\u110B)"], [12808, 5, "(\u110C)"], [12809, 5, "(\u110E)"], [12810, 5, "(\u110F)"], [12811, 5, "(\u1110)"], [12812, 5, "(\u1111)"], [12813, 5, "(\u1112)"], [12814, 5, "(\uAC00)"], [12815, 5, "(\uB098)"], [12816, 5, "(\uB2E4)"], [12817, 5, "(\uB77C)"], [12818, 5, "(\uB9C8)"], [12819, 5, "(\uBC14)"], [12820, 5, "(\uC0AC)"], [12821, 5, "(\uC544)"], [12822, 5, "(\uC790)"], [12823, 5, "(\uCC28)"], [12824, 5, "(\uCE74)"], [12825, 5, "(\uD0C0)"], [12826, 5, "(\uD30C)"], [12827, 5, "(\uD558)"], [12828, 5, "(\uC8FC)"], [12829, 5, "(\uC624\uC804)"], [12830, 5, "(\uC624\uD6C4)"], [12831, 3], [12832, 5, "(\u4E00)"], [12833, 5, "(\u4E8C)"], [12834, 5, "(\u4E09)"], [12835, 5, "(\u56DB)"], [12836, 5, "(\u4E94)"], [12837, 5, "(\u516D)"], [12838, 5, "(\u4E03)"], [12839, 5, "(\u516B)"], [12840, 5, "(\u4E5D)"], [12841, 5, "(\u5341)"], [12842, 5, "(\u6708)"], [12843, 5, "(\u706B)"], [12844, 5, "(\u6C34)"], [12845, 5, "(\u6728)"], [12846, 5, "(\u91D1)"], [12847, 5, "(\u571F)"], [12848, 5, "(\u65E5)"], [12849, 5, "(\u682A)"], [12850, 5, "(\u6709)"], [12851, 5, "(\u793E)"], [12852, 5, "(\u540D)"], [12853, 5, "(\u7279)"], [12854, 5, "(\u8CA1)"], [12855, 5, "(\u795D)"], [12856, 5, "(\u52B4)"], [12857, 5, "(\u4EE3)"], [12858, 5, "(\u547C)"], [12859, 5, "(\u5B66)"], [12860, 5, "(\u76E3)"], [12861, 5, "(\u4F01)"], [12862, 5, "(\u8CC7)"], [12863, 5, "(\u5354)"], [12864, 5, "(\u796D)"], [12865, 5, "(\u4F11)"], [12866, 5, "(\u81EA)"], [12867, 5, "(\u81F3)"], [12868, 1, "\u554F"], [12869, 1, "\u5E7C"], [12870, 1, "\u6587"], [12871, 1, "\u7B8F"], [[12872, 12879], 2], [12880, 1, "pte"], [12881, 1, "21"], [12882, 1, "22"], [12883, 1, "23"], [12884, 1, "24"], [12885, 1, "25"], [12886, 1, "26"], [12887, 1, "27"], [12888, 1, "28"], [12889, 1, "29"], [12890, 1, "30"], [12891, 1, "31"], [12892, 1, "32"], [12893, 1, "33"], [12894, 1, "34"], [12895, 1, "35"], [12896, 1, "\u1100"], [12897, 1, "\u1102"], [12898, 1, "\u1103"], [12899, 1, "\u1105"], [12900, 1, "\u1106"], [12901, 1, "\u1107"], [12902, 1, "\u1109"], [12903, 1, "\u110B"], [12904, 1, "\u110C"], [12905, 1, "\u110E"], [12906, 1, "\u110F"], [12907, 1, "\u1110"], [12908, 1, "\u1111"], [12909, 1, "\u1112"], [12910, 1, "\uAC00"], [12911, 1, "\uB098"], [12912, 1, "\uB2E4"], [12913, 1, "\uB77C"], [12914, 1, "\uB9C8"], [12915, 1, "\uBC14"], [12916, 1, "\uC0AC"], [12917, 1, "\uC544"], [12918, 1, "\uC790"], [12919, 1, "\uCC28"], [12920, 1, "\uCE74"], [12921, 1, "\uD0C0"], [12922, 1, "\uD30C"], [12923, 1, "\uD558"], [12924, 1, "\uCC38\uACE0"], [12925, 1, "\uC8FC\uC758"], [12926, 1, "\uC6B0"], [12927, 2], [12928, 1, "\u4E00"], [12929, 1, "\u4E8C"], [12930, 1, "\u4E09"], [12931, 1, "\u56DB"], [12932, 1, "\u4E94"], [12933, 1, "\u516D"], [12934, 1, "\u4E03"], [12935, 1, "\u516B"], [12936, 1, "\u4E5D"], [12937, 1, "\u5341"], [12938, 1, "\u6708"], [12939, 1, "\u706B"], [12940, 1, "\u6C34"], [12941, 1, "\u6728"], [12942, 1, "\u91D1"], [12943, 1, "\u571F"], [12944, 1, "\u65E5"], [12945, 1, "\u682A"], [12946, 1, "\u6709"], [12947, 1, "\u793E"], [12948, 1, "\u540D"], [12949, 1, "\u7279"], [12950, 1, "\u8CA1"], [12951, 1, "\u795D"], [12952, 1, "\u52B4"], [12953, 1, "\u79D8"], [12954, 1, "\u7537"], [12955, 1, "\u5973"], [12956, 1, "\u9069"], [12957, 1, "\u512A"], [12958, 1, "\u5370"], [12959, 1, "\u6CE8"], [12960, 1, "\u9805"], [12961, 1, "\u4F11"], [12962, 1, "\u5199"], [12963, 1, "\u6B63"], [12964, 1, "\u4E0A"], [12965, 1, "\u4E2D"], [12966, 1, "\u4E0B"], [12967, 1, "\u5DE6"], [12968, 1, "\u53F3"], [12969, 1, "\u533B"], [12970, 1, "\u5B97"], [12971, 1, "\u5B66"], [12972, 1, "\u76E3"], [12973, 1, "\u4F01"], [12974, 1, "\u8CC7"], [12975, 1, "\u5354"], [12976, 1, "\u591C"], [12977, 1, "36"], [12978, 1, "37"], [12979, 1, "38"], [12980, 1, "39"], [12981, 1, "40"], [12982, 1, "41"], [12983, 1, "42"], [12984, 1, "43"], [12985, 1, "44"], [12986, 1, "45"], [12987, 1, "46"], [12988, 1, "47"], [12989, 1, "48"], [12990, 1, "49"], [12991, 1, "50"], [12992, 1, "1\u6708"], [12993, 1, "2\u6708"], [12994, 1, "3\u6708"], [12995, 1, "4\u6708"], [12996, 1, "5\u6708"], [12997, 1, "6\u6708"], [12998, 1, "7\u6708"], [12999, 1, "8\u6708"], [13e3, 1, "9\u6708"], [13001, 1, "10\u6708"], [13002, 1, "11\u6708"], [13003, 1, "12\u6708"], [13004, 1, "hg"], [13005, 1, "erg"], [13006, 1, "ev"], [13007, 1, "ltd"], [13008, 1, "\u30A2"], [13009, 1, "\u30A4"], [13010, 1, "\u30A6"], [13011, 1, "\u30A8"], [13012, 1, "\u30AA"], [13013, 1, "\u30AB"], [13014, 1, "\u30AD"], [13015, 1, "\u30AF"], [13016, 1, "\u30B1"], [13017, 1, "\u30B3"], [13018, 1, "\u30B5"], [13019, 1, "\u30B7"], [13020, 1, "\u30B9"], [13021, 1, "\u30BB"], [13022, 1, "\u30BD"], [13023, 1, "\u30BF"], [13024, 1, "\u30C1"], [13025, 1, "\u30C4"], [13026, 1, "\u30C6"], [13027, 1, "\u30C8"], [13028, 1, "\u30CA"], [13029, 1, "\u30CB"], [13030, 1, "\u30CC"], [13031, 1, "\u30CD"], [13032, 1, "\u30CE"], [13033, 1, "\u30CF"], [13034, 1, "\u30D2"], [13035, 1, "\u30D5"], [13036, 1, "\u30D8"], [13037, 1, "\u30DB"], [13038, 1, "\u30DE"], [13039, 1, "\u30DF"], [13040, 1, "\u30E0"], [13041, 1, "\u30E1"], [13042, 1, "\u30E2"], [13043, 1, "\u30E4"], [13044, 1, "\u30E6"], [13045, 1, "\u30E8"], [13046, 1, "\u30E9"], [13047, 1, "\u30EA"], [13048, 1, "\u30EB"], [13049, 1, "\u30EC"], [13050, 1, "\u30ED"], [13051, 1, "\u30EF"], [13052, 1, "\u30F0"], [13053, 1, "\u30F1"], [13054, 1, "\u30F2"], [13055, 1, "\u4EE4\u548C"], [13056, 1, "\u30A2\u30D1\u30FC\u30C8"], [13057, 1, "\u30A2\u30EB\u30D5\u30A1"], [13058, 1, "\u30A2\u30F3\u30DA\u30A2"], [13059, 1, "\u30A2\u30FC\u30EB"], [13060, 1, "\u30A4\u30CB\u30F3\u30B0"], [13061, 1, "\u30A4\u30F3\u30C1"], [13062, 1, "\u30A6\u30A9\u30F3"], [13063, 1, "\u30A8\u30B9\u30AF\u30FC\u30C9"], [13064, 1, "\u30A8\u30FC\u30AB\u30FC"], [13065, 1, "\u30AA\u30F3\u30B9"], [13066, 1, "\u30AA\u30FC\u30E0"], [13067, 1, "\u30AB\u30A4\u30EA"], [13068, 1, "\u30AB\u30E9\u30C3\u30C8"], [13069, 1, "\u30AB\u30ED\u30EA\u30FC"], [13070, 1, "\u30AC\u30ED\u30F3"], [13071, 1, "\u30AC\u30F3\u30DE"], [13072, 1, "\u30AE\u30AC"], [13073, 1, "\u30AE\u30CB\u30FC"], [13074, 1, "\u30AD\u30E5\u30EA\u30FC"], [13075, 1, "\u30AE\u30EB\u30C0\u30FC"], [13076, 1, "\u30AD\u30ED"], [13077, 1, "\u30AD\u30ED\u30B0\u30E9\u30E0"], [13078, 1, "\u30AD\u30ED\u30E1\u30FC\u30C8\u30EB"], [13079, 1, "\u30AD\u30ED\u30EF\u30C3\u30C8"], [13080, 1, "\u30B0\u30E9\u30E0"], [13081, 1, "\u30B0\u30E9\u30E0\u30C8\u30F3"], [13082, 1, "\u30AF\u30EB\u30BC\u30A4\u30ED"], [13083, 1, "\u30AF\u30ED\u30FC\u30CD"], [13084, 1, "\u30B1\u30FC\u30B9"], [13085, 1, "\u30B3\u30EB\u30CA"], [13086, 1, "\u30B3\u30FC\u30DD"], [13087, 1, "\u30B5\u30A4\u30AF\u30EB"], [13088, 1, "\u30B5\u30F3\u30C1\u30FC\u30E0"], [13089, 1, "\u30B7\u30EA\u30F3\u30B0"], [13090, 1, "\u30BB\u30F3\u30C1"], [13091, 1, "\u30BB\u30F3\u30C8"], [13092, 1, "\u30C0\u30FC\u30B9"], [13093, 1, "\u30C7\u30B7"], [13094, 1, "\u30C9\u30EB"], [13095, 1, "\u30C8\u30F3"], [13096, 1, "\u30CA\u30CE"], [13097, 1, "\u30CE\u30C3\u30C8"], [13098, 1, "\u30CF\u30A4\u30C4"], [13099, 1, "\u30D1\u30FC\u30BB\u30F3\u30C8"], [13100, 1, "\u30D1\u30FC\u30C4"], [13101, 1, "\u30D0\u30FC\u30EC\u30EB"], [13102, 1, "\u30D4\u30A2\u30B9\u30C8\u30EB"], [13103, 1, "\u30D4\u30AF\u30EB"], [13104, 1, "\u30D4\u30B3"], [13105, 1, "\u30D3\u30EB"], [13106, 1, "\u30D5\u30A1\u30E9\u30C3\u30C9"], [13107, 1, "\u30D5\u30A3\u30FC\u30C8"], [13108, 1, "\u30D6\u30C3\u30B7\u30A7\u30EB"], [13109, 1, "\u30D5\u30E9\u30F3"], [13110, 1, "\u30D8\u30AF\u30BF\u30FC\u30EB"], [13111, 1, "\u30DA\u30BD"], [13112, 1, "\u30DA\u30CB\u30D2"], [13113, 1, "\u30D8\u30EB\u30C4"], [13114, 1, "\u30DA\u30F3\u30B9"], [13115, 1, "\u30DA\u30FC\u30B8"], [13116, 1, "\u30D9\u30FC\u30BF"], [13117, 1, "\u30DD\u30A4\u30F3\u30C8"], [13118, 1, "\u30DC\u30EB\u30C8"], [13119, 1, "\u30DB\u30F3"], [13120, 1, "\u30DD\u30F3\u30C9"], [13121, 1, "\u30DB\u30FC\u30EB"], [13122, 1, "\u30DB\u30FC\u30F3"], [13123, 1, "\u30DE\u30A4\u30AF\u30ED"], [13124, 1, "\u30DE\u30A4\u30EB"], [13125, 1, "\u30DE\u30C3\u30CF"], [13126, 1, "\u30DE\u30EB\u30AF"], [13127, 1, "\u30DE\u30F3\u30B7\u30E7\u30F3"], [13128, 1, "\u30DF\u30AF\u30ED\u30F3"], [13129, 1, "\u30DF\u30EA"], [13130, 1, "\u30DF\u30EA\u30D0\u30FC\u30EB"], [13131, 1, "\u30E1\u30AC"], [13132, 1, "\u30E1\u30AC\u30C8\u30F3"], [13133, 1, "\u30E1\u30FC\u30C8\u30EB"], [13134, 1, "\u30E4\u30FC\u30C9"], [13135, 1, "\u30E4\u30FC\u30EB"], [13136, 1, "\u30E6\u30A2\u30F3"], [13137, 1, "\u30EA\u30C3\u30C8\u30EB"], [13138, 1, "\u30EA\u30E9"], [13139, 1, "\u30EB\u30D4\u30FC"], [13140, 1, "\u30EB\u30FC\u30D6\u30EB"], [13141, 1, "\u30EC\u30E0"], [13142, 1, "\u30EC\u30F3\u30C8\u30B2\u30F3"], [13143, 1, "\u30EF\u30C3\u30C8"], [13144, 1, "0\u70B9"], [13145, 1, "1\u70B9"], [13146, 1, "2\u70B9"], [13147, 1, "3\u70B9"], [13148, 1, "4\u70B9"], [13149, 1, "5\u70B9"], [13150, 1, "6\u70B9"], [13151, 1, "7\u70B9"], [13152, 1, "8\u70B9"], [13153, 1, "9\u70B9"], [13154, 1, "10\u70B9"], [13155, 1, "11\u70B9"], [13156, 1, "12\u70B9"], [13157, 1, "13\u70B9"], [13158, 1, "14\u70B9"], [13159, 1, "15\u70B9"], [13160, 1, "16\u70B9"], [13161, 1, "17\u70B9"], [13162, 1, "18\u70B9"], [13163, 1, "19\u70B9"], [13164, 1, "20\u70B9"], [13165, 1, "21\u70B9"], [13166, 1, "22\u70B9"], [13167, 1, "23\u70B9"], [13168, 1, "24\u70B9"], [13169, 1, "hpa"], [13170, 1, "da"], [13171, 1, "au"], [13172, 1, "bar"], [13173, 1, "ov"], [13174, 1, "pc"], [13175, 1, "dm"], [13176, 1, "dm2"], [13177, 1, "dm3"], [13178, 1, "iu"], [13179, 1, "\u5E73\u6210"], [13180, 1, "\u662D\u548C"], [13181, 1, "\u5927\u6B63"], [13182, 1, "\u660E\u6CBB"], [13183, 1, "\u682A\u5F0F\u4F1A\u793E"], [13184, 1, "pa"], [13185, 1, "na"], [13186, 1, "\u03BCa"], [13187, 1, "ma"], [13188, 1, "ka"], [13189, 1, "kb"], [13190, 1, "mb"], [13191, 1, "gb"], [13192, 1, "cal"], [13193, 1, "kcal"], [13194, 1, "pf"], [13195, 1, "nf"], [13196, 1, "\u03BCf"], [13197, 1, "\u03BCg"], [13198, 1, "mg"], [13199, 1, "kg"], [13200, 1, "hz"], [13201, 1, "khz"], [13202, 1, "mhz"], [13203, 1, "ghz"], [13204, 1, "thz"], [13205, 1, "\u03BCl"], [13206, 1, "ml"], [13207, 1, "dl"], [13208, 1, "kl"], [13209, 1, "fm"], [13210, 1, "nm"], [13211, 1, "\u03BCm"], [13212, 1, "mm"], [13213, 1, "cm"], [13214, 1, "km"], [13215, 1, "mm2"], [13216, 1, "cm2"], [13217, 1, "m2"], [13218, 1, "km2"], [13219, 1, "mm3"], [13220, 1, "cm3"], [13221, 1, "m3"], [13222, 1, "km3"], [13223, 1, "m\u2215s"], [13224, 1, "m\u2215s2"], [13225, 1, "pa"], [13226, 1, "kpa"], [13227, 1, "mpa"], [13228, 1, "gpa"], [13229, 1, "rad"], [13230, 1, "rad\u2215s"], [13231, 1, "rad\u2215s2"], [13232, 1, "ps"], [13233, 1, "ns"], [13234, 1, "\u03BCs"], [13235, 1, "ms"], [13236, 1, "pv"], [13237, 1, "nv"], [13238, 1, "\u03BCv"], [13239, 1, "mv"], [13240, 1, "kv"], [13241, 1, "mv"], [13242, 1, "pw"], [13243, 1, "nw"], [13244, 1, "\u03BCw"], [13245, 1, "mw"], [13246, 1, "kw"], [13247, 1, "mw"], [13248, 1, "k\u03C9"], [13249, 1, "m\u03C9"], [13250, 3], [13251, 1, "bq"], [13252, 1, "cc"], [13253, 1, "cd"], [13254, 1, "c\u2215kg"], [13255, 3], [13256, 1, "db"], [13257, 1, "gy"], [13258, 1, "ha"], [13259, 1, "hp"], [13260, 1, "in"], [13261, 1, "kk"], [13262, 1, "km"], [13263, 1, "kt"], [13264, 1, "lm"], [13265, 1, "ln"], [13266, 1, "log"], [13267, 1, "lx"], [13268, 1, "mb"], [13269, 1, "mil"], [13270, 1, "mol"], [13271, 1, "ph"], [13272, 3], [13273, 1, "ppm"], [13274, 1, "pr"], [13275, 1, "sr"], [13276, 1, "sv"], [13277, 1, "wb"], [13278, 1, "v\u2215m"], [13279, 1, "a\u2215m"], [13280, 1, "1\u65E5"], [13281, 1, "2\u65E5"], [13282, 1, "3\u65E5"], [13283, 1, "4\u65E5"], [13284, 1, "5\u65E5"], [13285, 1, "6\u65E5"], [13286, 1, "7\u65E5"], [13287, 1, "8\u65E5"], [13288, 1, "9\u65E5"], [13289, 1, "10\u65E5"], [13290, 1, "11\u65E5"], [13291, 1, "12\u65E5"], [13292, 1, "13\u65E5"], [13293, 1, "14\u65E5"], [13294, 1, "15\u65E5"], [13295, 1, "16\u65E5"], [13296, 1, "17\u65E5"], [13297, 1, "18\u65E5"], [13298, 1, "19\u65E5"], [13299, 1, "20\u65E5"], [13300, 1, "21\u65E5"], [13301, 1, "22\u65E5"], [13302, 1, "23\u65E5"], [13303, 1, "24\u65E5"], [13304, 1, "25\u65E5"], [13305, 1, "26\u65E5"], [13306, 1, "27\u65E5"], [13307, 1, "28\u65E5"], [13308, 1, "29\u65E5"], [13309, 1, "30\u65E5"], [13310, 1, "31\u65E5"], [13311, 1, "gal"], [[13312, 19893], 2], [[19894, 19903], 2], [[19904, 19967], 2], [[19968, 40869], 2], [[40870, 40891], 2], [[40892, 40899], 2], [[40900, 40907], 2], [40908, 2], [[40909, 40917], 2], [[40918, 40938], 2], [[40939, 40943], 2], [[40944, 40956], 2], [[40957, 40959], 2], [[40960, 42124], 2], [[42125, 42127], 3], [[42128, 42145], 2], [[42146, 42147], 2], [[42148, 42163], 2], [42164, 2], [[42165, 42176], 2], [42177, 2], [[42178, 42180], 2], [42181, 2], [42182, 2], [[42183, 42191], 3], [[42192, 42237], 2], [[42238, 42239], 2], [[42240, 42508], 2], [[42509, 42511], 2], [[42512, 42539], 2], [[42540, 42559], 3], [42560, 1, "\uA641"], [42561, 2], [42562, 1, "\uA643"], [42563, 2], [42564, 1, "\uA645"], [42565, 2], [42566, 1, "\uA647"], [42567, 2], [42568, 1, "\uA649"], [42569, 2], [42570, 1, "\uA64B"], [42571, 2], [42572, 1, "\uA64D"], [42573, 2], [42574, 1, "\uA64F"], [42575, 2], [42576, 1, "\uA651"], [42577, 2], [42578, 1, "\uA653"], [42579, 2], [42580, 1, "\uA655"], [42581, 2], [42582, 1, "\uA657"], [42583, 2], [42584, 1, "\uA659"], [42585, 2], [42586, 1, "\uA65B"], [42587, 2], [42588, 1, "\uA65D"], [42589, 2], [42590, 1, "\uA65F"], [42591, 2], [42592, 1, "\uA661"], [42593, 2], [42594, 1, "\uA663"], [42595, 2], [42596, 1, "\uA665"], [42597, 2], [42598, 1, "\uA667"], [42599, 2], [42600, 1, "\uA669"], [42601, 2], [42602, 1, "\uA66B"], [42603, 2], [42604, 1, "\uA66D"], [[42605, 42607], 2], [[42608, 42611], 2], [[42612, 42619], 2], [[42620, 42621], 2], [42622, 2], [42623, 2], [42624, 1, "\uA681"], [42625, 2], [42626, 1, "\uA683"], [42627, 2], [42628, 1, "\uA685"], [42629, 2], [42630, 1, "\uA687"], [42631, 2], [42632, 1, "\uA689"], [42633, 2], [42634, 1, "\uA68B"], [42635, 2], [42636, 1, "\uA68D"], [42637, 2], [42638, 1, "\uA68F"], [42639, 2], [42640, 1, "\uA691"], [42641, 2], [42642, 1, "\uA693"], [42643, 2], [42644, 1, "\uA695"], [42645, 2], [42646, 1, "\uA697"], [42647, 2], [42648, 1, "\uA699"], [42649, 2], [42650, 1, "\uA69B"], [42651, 2], [42652, 1, "\u044A"], [42653, 1, "\u044C"], [42654, 2], [42655, 2], [[42656, 42725], 2], [[42726, 42735], 2], [[42736, 42737], 2], [[42738, 42743], 2], [[42744, 42751], 3], [[42752, 42774], 2], [[42775, 42778], 2], [[42779, 42783], 2], [[42784, 42785], 2], [42786, 1, "\uA723"], [42787, 2], [42788, 1, "\uA725"], [42789, 2], [42790, 1, "\uA727"], [42791, 2], [42792, 1, "\uA729"], [42793, 2], [42794, 1, "\uA72B"], [42795, 2], [42796, 1, "\uA72D"], [42797, 2], [42798, 1, "\uA72F"], [[42799, 42801], 2], [42802, 1, "\uA733"], [42803, 2], [42804, 1, "\uA735"], [42805, 2], [42806, 1, "\uA737"], [42807, 2], [42808, 1, "\uA739"], [42809, 2], [42810, 1, "\uA73B"], [42811, 2], [42812, 1, "\uA73D"], [42813, 2], [42814, 1, "\uA73F"], [42815, 2], [42816, 1, "\uA741"], [42817, 2], [42818, 1, "\uA743"], [42819, 2], [42820, 1, "\uA745"], [42821, 2], [42822, 1, "\uA747"], [42823, 2], [42824, 1, "\uA749"], [42825, 2], [42826, 1, "\uA74B"], [42827, 2], [42828, 1, "\uA74D"], [42829, 2], [42830, 1, "\uA74F"], [42831, 2], [42832, 1, "\uA751"], [42833, 2], [42834, 1, "\uA753"], [42835, 2], [42836, 1, "\uA755"], [42837, 2], [42838, 1, "\uA757"], [42839, 2], [42840, 1, "\uA759"], [42841, 2], [42842, 1, "\uA75B"], [42843, 2], [42844, 1, "\uA75D"], [42845, 2], [42846, 1, "\uA75F"], [42847, 2], [42848, 1, "\uA761"], [42849, 2], [42850, 1, "\uA763"], [42851, 2], [42852, 1, "\uA765"], [42853, 2], [42854, 1, "\uA767"], [42855, 2], [42856, 1, "\uA769"], [42857, 2], [42858, 1, "\uA76B"], [42859, 2], [42860, 1, "\uA76D"], [42861, 2], [42862, 1, "\uA76F"], [42863, 2], [42864, 1, "\uA76F"], [[42865, 42872], 2], [42873, 1, "\uA77A"], [42874, 2], [42875, 1, "\uA77C"], [42876, 2], [42877, 1, "\u1D79"], [42878, 1, "\uA77F"], [42879, 2], [42880, 1, "\uA781"], [42881, 2], [42882, 1, "\uA783"], [42883, 2], [42884, 1, "\uA785"], [42885, 2], [42886, 1, "\uA787"], [[42887, 42888], 2], [[42889, 42890], 2], [42891, 1, "\uA78C"], [42892, 2], [42893, 1, "\u0265"], [42894, 2], [42895, 2], [42896, 1, "\uA791"], [42897, 2], [42898, 1, "\uA793"], [42899, 2], [[42900, 42901], 2], [42902, 1, "\uA797"], [42903, 2], [42904, 1, "\uA799"], [42905, 2], [42906, 1, "\uA79B"], [42907, 2], [42908, 1, "\uA79D"], [42909, 2], [42910, 1, "\uA79F"], [42911, 2], [42912, 1, "\uA7A1"], [42913, 2], [42914, 1, "\uA7A3"], [42915, 2], [42916, 1, "\uA7A5"], [42917, 2], [42918, 1, "\uA7A7"], [42919, 2], [42920, 1, "\uA7A9"], [42921, 2], [42922, 1, "\u0266"], [42923, 1, "\u025C"], [42924, 1, "\u0261"], [42925, 1, "\u026C"], [42926, 1, "\u026A"], [42927, 2], [42928, 1, "\u029E"], [42929, 1, "\u0287"], [42930, 1, "\u029D"], [42931, 1, "\uAB53"], [42932, 1, "\uA7B5"], [42933, 2], [42934, 1, "\uA7B7"], [42935, 2], [42936, 1, "\uA7B9"], [42937, 2], [42938, 1, "\uA7BB"], [42939, 2], [42940, 1, "\uA7BD"], [42941, 2], [42942, 1, "\uA7BF"], [42943, 2], [42944, 1, "\uA7C1"], [42945, 2], [42946, 1, "\uA7C3"], [42947, 2], [42948, 1, "\uA794"], [42949, 1, "\u0282"], [42950, 1, "\u1D8E"], [42951, 1, "\uA7C8"], [42952, 2], [42953, 1, "\uA7CA"], [42954, 2], [[42955, 42959], 3], [42960, 1, "\uA7D1"], [42961, 2], [42962, 3], [42963, 2], [42964, 3], [42965, 2], [42966, 1, "\uA7D7"], [42967, 2], [42968, 1, "\uA7D9"], [42969, 2], [[42970, 42993], 3], [42994, 1, "c"], [42995, 1, "f"], [42996, 1, "q"], [42997, 1, "\uA7F6"], [42998, 2], [42999, 2], [43e3, 1, "\u0127"], [43001, 1, "\u0153"], [43002, 2], [[43003, 43007], 2], [[43008, 43047], 2], [[43048, 43051], 2], [43052, 2], [[43053, 43055], 3], [[43056, 43065], 2], [[43066, 43071], 3], [[43072, 43123], 2], [[43124, 43127], 2], [[43128, 43135], 3], [[43136, 43204], 2], [43205, 2], [[43206, 43213], 3], [[43214, 43215], 2], [[43216, 43225], 2], [[43226, 43231], 3], [[43232, 43255], 2], [[43256, 43258], 2], [43259, 2], [43260, 2], [43261, 2], [[43262, 43263], 2], [[43264, 43309], 2], [[43310, 43311], 2], [[43312, 43347], 2], [[43348, 43358], 3], [43359, 2], [[43360, 43388], 2], [[43389, 43391], 3], [[43392, 43456], 2], [[43457, 43469], 2], [43470, 3], [[43471, 43481], 2], [[43482, 43485], 3], [[43486, 43487], 2], [[43488, 43518], 2], [43519, 3], [[43520, 43574], 2], [[43575, 43583], 3], [[43584, 43597], 2], [[43598, 43599], 3], [[43600, 43609], 2], [[43610, 43611], 3], [[43612, 43615], 2], [[43616, 43638], 2], [[43639, 43641], 2], [[43642, 43643], 2], [[43644, 43647], 2], [[43648, 43714], 2], [[43715, 43738], 3], [[43739, 43741], 2], [[43742, 43743], 2], [[43744, 43759], 2], [[43760, 43761], 2], [[43762, 43766], 2], [[43767, 43776], 3], [[43777, 43782], 2], [[43783, 43784], 3], [[43785, 43790], 2], [[43791, 43792], 3], [[43793, 43798], 2], [[43799, 43807], 3], [[43808, 43814], 2], [43815, 3], [[43816, 43822], 2], [43823, 3], [[43824, 43866], 2], [43867, 2], [43868, 1, "\uA727"], [43869, 1, "\uAB37"], [43870, 1, "\u026B"], [43871, 1, "\uAB52"], [[43872, 43875], 2], [[43876, 43877], 2], [[43878, 43879], 2], [43880, 2], [43881, 1, "\u028D"], [[43882, 43883], 2], [[43884, 43887], 3], [43888, 1, "\u13A0"], [43889, 1, "\u13A1"], [43890, 1, "\u13A2"], [43891, 1, "\u13A3"], [43892, 1, "\u13A4"], [43893, 1, "\u13A5"], [43894, 1, "\u13A6"], [43895, 1, "\u13A7"], [43896, 1, "\u13A8"], [43897, 1, "\u13A9"], [43898, 1, "\u13AA"], [43899, 1, "\u13AB"], [43900, 1, "\u13AC"], [43901, 1, "\u13AD"], [43902, 1, "\u13AE"], [43903, 1, "\u13AF"], [43904, 1, "\u13B0"], [43905, 1, "\u13B1"], [43906, 1, "\u13B2"], [43907, 1, "\u13B3"], [43908, 1, "\u13B4"], [43909, 1, "\u13B5"], [43910, 1, "\u13B6"], [43911, 1, "\u13B7"], [43912, 1, "\u13B8"], [43913, 1, "\u13B9"], [43914, 1, "\u13BA"], [43915, 1, "\u13BB"], [43916, 1, "\u13BC"], [43917, 1, "\u13BD"], [43918, 1, "\u13BE"], [43919, 1, "\u13BF"], [43920, 1, "\u13C0"], [43921, 1, "\u13C1"], [43922, 1, "\u13C2"], [43923, 1, "\u13C3"], [43924, 1, "\u13C4"], [43925, 1, "\u13C5"], [43926, 1, "\u13C6"], [43927, 1, "\u13C7"], [43928, 1, "\u13C8"], [43929, 1, "\u13C9"], [43930, 1, "\u13CA"], [43931, 1, "\u13CB"], [43932, 1, "\u13CC"], [43933, 1, "\u13CD"], [43934, 1, "\u13CE"], [43935, 1, "\u13CF"], [43936, 1, "\u13D0"], [43937, 1, "\u13D1"], [43938, 1, "\u13D2"], [43939, 1, "\u13D3"], [43940, 1, "\u13D4"], [43941, 1, "\u13D5"], [43942, 1, "\u13D6"], [43943, 1, "\u13D7"], [43944, 1, "\u13D8"], [43945, 1, "\u13D9"], [43946, 1, "\u13DA"], [43947, 1, "\u13DB"], [43948, 1, "\u13DC"], [43949, 1, "\u13DD"], [43950, 1, "\u13DE"], [43951, 1, "\u13DF"], [43952, 1, "\u13E0"], [43953, 1, "\u13E1"], [43954, 1, "\u13E2"], [43955, 1, "\u13E3"], [43956, 1, "\u13E4"], [43957, 1, "\u13E5"], [43958, 1, "\u13E6"], [43959, 1, "\u13E7"], [43960, 1, "\u13E8"], [43961, 1, "\u13E9"], [43962, 1, "\u13EA"], [43963, 1, "\u13EB"], [43964, 1, "\u13EC"], [43965, 1, "\u13ED"], [43966, 1, "\u13EE"], [43967, 1, "\u13EF"], [[43968, 44010], 2], [44011, 2], [[44012, 44013], 2], [[44014, 44015], 3], [[44016, 44025], 2], [[44026, 44031], 3], [[44032, 55203], 2], [[55204, 55215], 3], [[55216, 55238], 2], [[55239, 55242], 3], [[55243, 55291], 2], [[55292, 55295], 3], [[55296, 57343], 3], [[57344, 63743], 3], [63744, 1, "\u8C48"], [63745, 1, "\u66F4"], [63746, 1, "\u8ECA"], [63747, 1, "\u8CC8"], [63748, 1, "\u6ED1"], [63749, 1, "\u4E32"], [63750, 1, "\u53E5"], [[63751, 63752], 1, "\u9F9C"], [63753, 1, "\u5951"], [63754, 1, "\u91D1"], [63755, 1, "\u5587"], [63756, 1, "\u5948"], [63757, 1, "\u61F6"], [63758, 1, "\u7669"], [63759, 1, "\u7F85"], [63760, 1, "\u863F"], [63761, 1, "\u87BA"], [63762, 1, "\u88F8"], [63763, 1, "\u908F"], [63764, 1, "\u6A02"], [63765, 1, "\u6D1B"], [63766, 1, "\u70D9"], [63767, 1, "\u73DE"], [63768, 1, "\u843D"], [63769, 1, "\u916A"], [63770, 1, "\u99F1"], [63771, 1, "\u4E82"], [63772, 1, "\u5375"], [63773, 1, "\u6B04"], [63774, 1, "\u721B"], [63775, 1, "\u862D"], [63776, 1, "\u9E1E"], [63777, 1, "\u5D50"], [63778, 1, "\u6FEB"], [63779, 1, "\u85CD"], [63780, 1, "\u8964"], [63781, 1, "\u62C9"], [63782, 1, "\u81D8"], [63783, 1, "\u881F"], [63784, 1, "\u5ECA"], [63785, 1, "\u6717"], [63786, 1, "\u6D6A"], [63787, 1, "\u72FC"], [63788, 1, "\u90CE"], [63789, 1, "\u4F86"], [63790, 1, "\u51B7"], [63791, 1, "\u52DE"], [63792, 1, "\u64C4"], [63793, 1, "\u6AD3"], [63794, 1, "\u7210"], [63795, 1, "\u76E7"], [63796, 1, "\u8001"], [63797, 1, "\u8606"], [63798, 1, "\u865C"], [63799, 1, "\u8DEF"], [63800, 1, "\u9732"], [63801, 1, "\u9B6F"], [63802, 1, "\u9DFA"], [63803, 1, "\u788C"], [63804, 1, "\u797F"], [63805, 1, "\u7DA0"], [63806, 1, "\u83C9"], [63807, 1, "\u9304"], [63808, 1, "\u9E7F"], [63809, 1, "\u8AD6"], [63810, 1, "\u58DF"], [63811, 1, "\u5F04"], [63812, 1, "\u7C60"], [63813, 1, "\u807E"], [63814, 1, "\u7262"], [63815, 1, "\u78CA"], [63816, 1, "\u8CC2"], [63817, 1, "\u96F7"], [63818, 1, "\u58D8"], [63819, 1, "\u5C62"], [63820, 1, "\u6A13"], [63821, 1, "\u6DDA"], [63822, 1, "\u6F0F"], [63823, 1, "\u7D2F"], [63824, 1, "\u7E37"], [63825, 1, "\u964B"], [63826, 1, "\u52D2"], [63827, 1, "\u808B"], [63828, 1, "\u51DC"], [63829, 1, "\u51CC"], [63830, 1, "\u7A1C"], [63831, 1, "\u7DBE"], [63832, 1, "\u83F1"], [63833, 1, "\u9675"], [63834, 1, "\u8B80"], [63835, 1, "\u62CF"], [63836, 1, "\u6A02"], [63837, 1, "\u8AFE"], [63838, 1, "\u4E39"], [63839, 1, "\u5BE7"], [63840, 1, "\u6012"], [63841, 1, "\u7387"], [63842, 1, "\u7570"], [63843, 1, "\u5317"], [63844, 1, "\u78FB"], [63845, 1, "\u4FBF"], [63846, 1, "\u5FA9"], [63847, 1, "\u4E0D"], [63848, 1, "\u6CCC"], [63849, 1, "\u6578"], [63850, 1, "\u7D22"], [63851, 1, "\u53C3"], [63852, 1, "\u585E"], [63853, 1, "\u7701"], [63854, 1, "\u8449"], [63855, 1, "\u8AAA"], [63856, 1, "\u6BBA"], [63857, 1, "\u8FB0"], [63858, 1, "\u6C88"], [63859, 1, "\u62FE"], [63860, 1, "\u82E5"], [63861, 1, "\u63A0"], [63862, 1, "\u7565"], [63863, 1, "\u4EAE"], [63864, 1, "\u5169"], [63865, 1, "\u51C9"], [63866, 1, "\u6881"], [63867, 1, "\u7CE7"], [63868, 1, "\u826F"], [63869, 1, "\u8AD2"], [63870, 1, "\u91CF"], [63871, 1, "\u52F5"], [63872, 1, "\u5442"], [63873, 1, "\u5973"], [63874, 1, "\u5EEC"], [63875, 1, "\u65C5"], [63876, 1, "\u6FFE"], [63877, 1, "\u792A"], [63878, 1, "\u95AD"], [63879, 1, "\u9A6A"], [63880, 1, "\u9E97"], [63881, 1, "\u9ECE"], [63882, 1, "\u529B"], [63883, 1, "\u66C6"], [63884, 1, "\u6B77"], [63885, 1, "\u8F62"], [63886, 1, "\u5E74"], [63887, 1, "\u6190"], [63888, 1, "\u6200"], [63889, 1, "\u649A"], [63890, 1, "\u6F23"], [63891, 1, "\u7149"], [63892, 1, "\u7489"], [63893, 1, "\u79CA"], [63894, 1, "\u7DF4"], [63895, 1, "\u806F"], [63896, 1, "\u8F26"], [63897, 1, "\u84EE"], [63898, 1, "\u9023"], [63899, 1, "\u934A"], [63900, 1, "\u5217"], [63901, 1, "\u52A3"], [63902, 1, "\u54BD"], [63903, 1, "\u70C8"], [63904, 1, "\u88C2"], [63905, 1, "\u8AAA"], [63906, 1, "\u5EC9"], [63907, 1, "\u5FF5"], [63908, 1, "\u637B"], [63909, 1, "\u6BAE"], [63910, 1, "\u7C3E"], [63911, 1, "\u7375"], [63912, 1, "\u4EE4"], [63913, 1, "\u56F9"], [63914, 1, "\u5BE7"], [63915, 1, "\u5DBA"], [63916, 1, "\u601C"], [63917, 1, "\u73B2"], [63918, 1, "\u7469"], [63919, 1, "\u7F9A"], [63920, 1, "\u8046"], [63921, 1, "\u9234"], [63922, 1, "\u96F6"], [63923, 1, "\u9748"], [63924, 1, "\u9818"], [63925, 1, "\u4F8B"], [63926, 1, "\u79AE"], [63927, 1, "\u91B4"], [63928, 1, "\u96B8"], [63929, 1, "\u60E1"], [63930, 1, "\u4E86"], [63931, 1, "\u50DA"], [63932, 1, "\u5BEE"], [63933, 1, "\u5C3F"], [63934, 1, "\u6599"], [63935, 1, "\u6A02"], [63936, 1, "\u71CE"], [63937, 1, "\u7642"], [63938, 1, "\u84FC"], [63939, 1, "\u907C"], [63940, 1, "\u9F8D"], [63941, 1, "\u6688"], [63942, 1, "\u962E"], [63943, 1, "\u5289"], [63944, 1, "\u677B"], [63945, 1, "\u67F3"], [63946, 1, "\u6D41"], [63947, 1, "\u6E9C"], [63948, 1, "\u7409"], [63949, 1, "\u7559"], [63950, 1, "\u786B"], [63951, 1, "\u7D10"], [63952, 1, "\u985E"], [63953, 1, "\u516D"], [63954, 1, "\u622E"], [63955, 1, "\u9678"], [63956, 1, "\u502B"], [63957, 1, "\u5D19"], [63958, 1, "\u6DEA"], [63959, 1, "\u8F2A"], [63960, 1, "\u5F8B"], [63961, 1, "\u6144"], [63962, 1, "\u6817"], [63963, 1, "\u7387"], [63964, 1, "\u9686"], [63965, 1, "\u5229"], [63966, 1, "\u540F"], [63967, 1, "\u5C65"], [63968, 1, "\u6613"], [63969, 1, "\u674E"], [63970, 1, "\u68A8"], [63971, 1, "\u6CE5"], [63972, 1, "\u7406"], [63973, 1, "\u75E2"], [63974, 1, "\u7F79"], [63975, 1, "\u88CF"], [63976, 1, "\u88E1"], [63977, 1, "\u91CC"], [63978, 1, "\u96E2"], [63979, 1, "\u533F"], [63980, 1, "\u6EBA"], [63981, 1, "\u541D"], [63982, 1, "\u71D0"], [63983, 1, "\u7498"], [63984, 1, "\u85FA"], [63985, 1, "\u96A3"], [63986, 1, "\u9C57"], [63987, 1, "\u9E9F"], [63988, 1, "\u6797"], [63989, 1, "\u6DCB"], [63990, 1, "\u81E8"], [63991, 1, "\u7ACB"], [63992, 1, "\u7B20"], [63993, 1, "\u7C92"], [63994, 1, "\u72C0"], [63995, 1, "\u7099"], [63996, 1, "\u8B58"], [63997, 1, "\u4EC0"], [63998, 1, "\u8336"], [63999, 1, "\u523A"], [64e3, 1, "\u5207"], [64001, 1, "\u5EA6"], [64002, 1, "\u62D3"], [64003, 1, "\u7CD6"], [64004, 1, "\u5B85"], [64005, 1, "\u6D1E"], [64006, 1, "\u66B4"], [64007, 1, "\u8F3B"], [64008, 1, "\u884C"], [64009, 1, "\u964D"], [64010, 1, "\u898B"], [64011, 1, "\u5ED3"], [64012, 1, "\u5140"], [64013, 1, "\u55C0"], [[64014, 64015], 2], [64016, 1, "\u585A"], [64017, 2], [64018, 1, "\u6674"], [[64019, 64020], 2], [64021, 1, "\u51DE"], [64022, 1, "\u732A"], [64023, 1, "\u76CA"], [64024, 1, "\u793C"], [64025, 1, "\u795E"], [64026, 1, "\u7965"], [64027, 1, "\u798F"], [64028, 1, "\u9756"], [64029, 1, "\u7CBE"], [64030, 1, "\u7FBD"], [64031, 2], [64032, 1, "\u8612"], [64033, 2], [64034, 1, "\u8AF8"], [[64035, 64036], 2], [64037, 1, "\u9038"], [64038, 1, "\u90FD"], [[64039, 64041], 2], [64042, 1, "\u98EF"], [64043, 1, "\u98FC"], [64044, 1, "\u9928"], [64045, 1, "\u9DB4"], [64046, 1, "\u90DE"], [64047, 1, "\u96B7"], [64048, 1, "\u4FAE"], [64049, 1, "\u50E7"], [64050, 1, "\u514D"], [64051, 1, "\u52C9"], [64052, 1, "\u52E4"], [64053, 1, "\u5351"], [64054, 1, "\u559D"], [64055, 1, "\u5606"], [64056, 1, "\u5668"], [64057, 1, "\u5840"], [64058, 1, "\u58A8"], [64059, 1, "\u5C64"], [64060, 1, "\u5C6E"], [64061, 1, "\u6094"], [64062, 1, "\u6168"], [64063, 1, "\u618E"], [64064, 1, "\u61F2"], [64065, 1, "\u654F"], [64066, 1, "\u65E2"], [64067, 1, "\u6691"], [64068, 1, "\u6885"], [64069, 1, "\u6D77"], [64070, 1, "\u6E1A"], [64071, 1, "\u6F22"], [64072, 1, "\u716E"], [64073, 1, "\u722B"], [64074, 1, "\u7422"], [64075, 1, "\u7891"], [64076, 1, "\u793E"], [64077, 1, "\u7949"], [64078, 1, "\u7948"], [64079, 1, "\u7950"], [64080, 1, "\u7956"], [64081, 1, "\u795D"], [64082, 1, "\u798D"], [64083, 1, "\u798E"], [64084, 1, "\u7A40"], [64085, 1, "\u7A81"], [64086, 1, "\u7BC0"], [64087, 1, "\u7DF4"], [64088, 1, "\u7E09"], [64089, 1, "\u7E41"], [64090, 1, "\u7F72"], [64091, 1, "\u8005"], [64092, 1, "\u81ED"], [[64093, 64094], 1, "\u8279"], [64095, 1, "\u8457"], [64096, 1, "\u8910"], [64097, 1, "\u8996"], [64098, 1, "\u8B01"], [64099, 1, "\u8B39"], [64100, 1, "\u8CD3"], [64101, 1, "\u8D08"], [64102, 1, "\u8FB6"], [64103, 1, "\u9038"], [64104, 1, "\u96E3"], [64105, 1, "\u97FF"], [64106, 1, "\u983B"], [64107, 1, "\u6075"], [64108, 1, "\u{242EE}"], [64109, 1, "\u8218"], [[64110, 64111], 3], [64112, 1, "\u4E26"], [64113, 1, "\u51B5"], [64114, 1, "\u5168"], [64115, 1, "\u4F80"], [64116, 1, "\u5145"], [64117, 1, "\u5180"], [64118, 1, "\u52C7"], [64119, 1, "\u52FA"], [64120, 1, "\u559D"], [64121, 1, "\u5555"], [64122, 1, "\u5599"], [64123, 1, "\u55E2"], [64124, 1, "\u585A"], [64125, 1, "\u58B3"], [64126, 1, "\u5944"], [64127, 1, "\u5954"], [64128, 1, "\u5A62"], [64129, 1, "\u5B28"], [64130, 1, "\u5ED2"], [64131, 1, "\u5ED9"], [64132, 1, "\u5F69"], [64133, 1, "\u5FAD"], [64134, 1, "\u60D8"], [64135, 1, "\u614E"], [64136, 1, "\u6108"], [64137, 1, "\u618E"], [64138, 1, "\u6160"], [64139, 1, "\u61F2"], [64140, 1, "\u6234"], [64141, 1, "\u63C4"], [64142, 1, "\u641C"], [64143, 1, "\u6452"], [64144, 1, "\u6556"], [64145, 1, "\u6674"], [64146, 1, "\u6717"], [64147, 1, "\u671B"], [64148, 1, "\u6756"], [64149, 1, "\u6B79"], [64150, 1, "\u6BBA"], [64151, 1, "\u6D41"], [64152, 1, "\u6EDB"], [64153, 1, "\u6ECB"], [64154, 1, "\u6F22"], [64155, 1, "\u701E"], [64156, 1, "\u716E"], [64157, 1, "\u77A7"], [64158, 1, "\u7235"], [64159, 1, "\u72AF"], [64160, 1, "\u732A"], [64161, 1, "\u7471"], [64162, 1, "\u7506"], [64163, 1, "\u753B"], [64164, 1, "\u761D"], [64165, 1, "\u761F"], [64166, 1, "\u76CA"], [64167, 1, "\u76DB"], [64168, 1, "\u76F4"], [64169, 1, "\u774A"], [64170, 1, "\u7740"], [64171, 1, "\u78CC"], [64172, 1, "\u7AB1"], [64173, 1, "\u7BC0"], [64174, 1, "\u7C7B"], [64175, 1, "\u7D5B"], [64176, 1, "\u7DF4"], [64177, 1, "\u7F3E"], [64178, 1, "\u8005"], [64179, 1, "\u8352"], [64180, 1, "\u83EF"], [64181, 1, "\u8779"], [64182, 1, "\u8941"], [64183, 1, "\u8986"], [64184, 1, "\u8996"], [64185, 1, "\u8ABF"], [64186, 1, "\u8AF8"], [64187, 1, "\u8ACB"], [64188, 1, "\u8B01"], [64189, 1, "\u8AFE"], [64190, 1, "\u8AED"], [64191, 1, "\u8B39"], [64192, 1, "\u8B8A"], [64193, 1, "\u8D08"], [64194, 1, "\u8F38"], [64195, 1, "\u9072"], [64196, 1, "\u9199"], [64197, 1, "\u9276"], [64198, 1, "\u967C"], [64199, 1, "\u96E3"], [64200, 1, "\u9756"], [64201, 1, "\u97DB"], [64202, 1, "\u97FF"], [64203, 1, "\u980B"], [64204, 1, "\u983B"], [64205, 1, "\u9B12"], [64206, 1, "\u9F9C"], [64207, 1, "\u{2284A}"], [64208, 1, "\u{22844}"], [64209, 1, "\u{233D5}"], [64210, 1, "\u3B9D"], [64211, 1, "\u4018"], [64212, 1, "\u4039"], [64213, 1, "\u{25249}"], [64214, 1, "\u{25CD0}"], [64215, 1, "\u{27ED3}"], [64216, 1, "\u9F43"], [64217, 1, "\u9F8E"], [[64218, 64255], 3], [64256, 1, "ff"], [64257, 1, "fi"], [64258, 1, "fl"], [64259, 1, "ffi"], [64260, 1, "ffl"], [[64261, 64262], 1, "st"], [[64263, 64274], 3], [64275, 1, "\u0574\u0576"], [64276, 1, "\u0574\u0565"], [64277, 1, "\u0574\u056B"], [64278, 1, "\u057E\u0576"], [64279, 1, "\u0574\u056D"], [[64280, 64284], 3], [64285, 1, "\u05D9\u05B4"], [64286, 2], [64287, 1, "\u05F2\u05B7"], [64288, 1, "\u05E2"], [64289, 1, "\u05D0"], [64290, 1, "\u05D3"], [64291, 1, "\u05D4"], [64292, 1, "\u05DB"], [64293, 1, "\u05DC"], [64294, 1, "\u05DD"], [64295, 1, "\u05E8"], [64296, 1, "\u05EA"], [64297, 5, "+"], [64298, 1, "\u05E9\u05C1"], [64299, 1, "\u05E9\u05C2"], [64300, 1, "\u05E9\u05BC\u05C1"], [64301, 1, "\u05E9\u05BC\u05C2"], [64302, 1, "\u05D0\u05B7"], [64303, 1, "\u05D0\u05B8"], [64304, 1, "\u05D0\u05BC"], [64305, 1, "\u05D1\u05BC"], [64306, 1, "\u05D2\u05BC"], [64307, 1, "\u05D3\u05BC"], [64308, 1, "\u05D4\u05BC"], [64309, 1, "\u05D5\u05BC"], [64310, 1, "\u05D6\u05BC"], [64311, 3], [64312, 1, "\u05D8\u05BC"], [64313, 1, "\u05D9\u05BC"], [64314, 1, "\u05DA\u05BC"], [64315, 1, "\u05DB\u05BC"], [64316, 1, "\u05DC\u05BC"], [64317, 3], [64318, 1, "\u05DE\u05BC"], [64319, 3], [64320, 1, "\u05E0\u05BC"], [64321, 1, "\u05E1\u05BC"], [64322, 3], [64323, 1, "\u05E3\u05BC"], [64324, 1, "\u05E4\u05BC"], [64325, 3], [64326, 1, "\u05E6\u05BC"], [64327, 1, "\u05E7\u05BC"], [64328, 1, "\u05E8\u05BC"], [64329, 1, "\u05E9\u05BC"], [64330, 1, "\u05EA\u05BC"], [64331, 1, "\u05D5\u05B9"], [64332, 1, "\u05D1\u05BF"], [64333, 1, "\u05DB\u05BF"], [64334, 1, "\u05E4\u05BF"], [64335, 1, "\u05D0\u05DC"], [[64336, 64337], 1, "\u0671"], [[64338, 64341], 1, "\u067B"], [[64342, 64345], 1, "\u067E"], [[64346, 64349], 1, "\u0680"], [[64350, 64353], 1, "\u067A"], [[64354, 64357], 1, "\u067F"], [[64358, 64361], 1, "\u0679"], [[64362, 64365], 1, "\u06A4"], [[64366, 64369], 1, "\u06A6"], [[64370, 64373], 1, "\u0684"], [[64374, 64377], 1, "\u0683"], [[64378, 64381], 1, "\u0686"], [[64382, 64385], 1, "\u0687"], [[64386, 64387], 1, "\u068D"], [[64388, 64389], 1, "\u068C"], [[64390, 64391], 1, "\u068E"], [[64392, 64393], 1, "\u0688"], [[64394, 64395], 1, "\u0698"], [[64396, 64397], 1, "\u0691"], [[64398, 64401], 1, "\u06A9"], [[64402, 64405], 1, "\u06AF"], [[64406, 64409], 1, "\u06B3"], [[64410, 64413], 1, "\u06B1"], [[64414, 64415], 1, "\u06BA"], [[64416, 64419], 1, "\u06BB"], [[64420, 64421], 1, "\u06C0"], [[64422, 64425], 1, "\u06C1"], [[64426, 64429], 1, "\u06BE"], [[64430, 64431], 1, "\u06D2"], [[64432, 64433], 1, "\u06D3"], [[64434, 64449], 2], [64450, 2], [[64451, 64466], 3], [[64467, 64470], 1, "\u06AD"], [[64471, 64472], 1, "\u06C7"], [[64473, 64474], 1, "\u06C6"], [[64475, 64476], 1, "\u06C8"], [64477, 1, "\u06C7\u0674"], [[64478, 64479], 1, "\u06CB"], [[64480, 64481], 1, "\u06C5"], [[64482, 64483], 1, "\u06C9"], [[64484, 64487], 1, "\u06D0"], [[64488, 64489], 1, "\u0649"], [[64490, 64491], 1, "\u0626\u0627"], [[64492, 64493], 1, "\u0626\u06D5"], [[64494, 64495], 1, "\u0626\u0648"], [[64496, 64497], 1, "\u0626\u06C7"], [[64498, 64499], 1, "\u0626\u06C6"], [[64500, 64501], 1, "\u0626\u06C8"], [[64502, 64504], 1, "\u0626\u06D0"], [[64505, 64507], 1, "\u0626\u0649"], [[64508, 64511], 1, "\u06CC"], [64512, 1, "\u0626\u062C"], [64513, 1, "\u0626\u062D"], [64514, 1, "\u0626\u0645"], [64515, 1, "\u0626\u0649"], [64516, 1, "\u0626\u064A"], [64517, 1, "\u0628\u062C"], [64518, 1, "\u0628\u062D"], [64519, 1, "\u0628\u062E"], [64520, 1, "\u0628\u0645"], [64521, 1, "\u0628\u0649"], [64522, 1, "\u0628\u064A"], [64523, 1, "\u062A\u062C"], [64524, 1, "\u062A\u062D"], [64525, 1, "\u062A\u062E"], [64526, 1, "\u062A\u0645"], [64527, 1, "\u062A\u0649"], [64528, 1, "\u062A\u064A"], [64529, 1, "\u062B\u062C"], [64530, 1, "\u062B\u0645"], [64531, 1, "\u062B\u0649"], [64532, 1, "\u062B\u064A"], [64533, 1, "\u062C\u062D"], [64534, 1, "\u062C\u0645"], [64535, 1, "\u062D\u062C"], [64536, 1, "\u062D\u0645"], [64537, 1, "\u062E\u062C"], [64538, 1, "\u062E\u062D"], [64539, 1, "\u062E\u0645"], [64540, 1, "\u0633\u062C"], [64541, 1, "\u0633\u062D"], [64542, 1, "\u0633\u062E"], [64543, 1, "\u0633\u0645"], [64544, 1, "\u0635\u062D"], [64545, 1, "\u0635\u0645"], [64546, 1, "\u0636\u062C"], [64547, 1, "\u0636\u062D"], [64548, 1, "\u0636\u062E"], [64549, 1, "\u0636\u0645"], [64550, 1, "\u0637\u062D"], [64551, 1, "\u0637\u0645"], [64552, 1, "\u0638\u0645"], [64553, 1, "\u0639\u062C"], [64554, 1, "\u0639\u0645"], [64555, 1, "\u063A\u062C"], [64556, 1, "\u063A\u0645"], [64557, 1, "\u0641\u062C"], [64558, 1, "\u0641\u062D"], [64559, 1, "\u0641\u062E"], [64560, 1, "\u0641\u0645"], [64561, 1, "\u0641\u0649"], [64562, 1, "\u0641\u064A"], [64563, 1, "\u0642\u062D"], [64564, 1, "\u0642\u0645"], [64565, 1, "\u0642\u0649"], [64566, 1, "\u0642\u064A"], [64567, 1, "\u0643\u0627"], [64568, 1, "\u0643\u062C"], [64569, 1, "\u0643\u062D"], [64570, 1, "\u0643\u062E"], [64571, 1, "\u0643\u0644"], [64572, 1, "\u0643\u0645"], [64573, 1, "\u0643\u0649"], [64574, 1, "\u0643\u064A"], [64575, 1, "\u0644\u062C"], [64576, 1, "\u0644\u062D"], [64577, 1, "\u0644\u062E"], [64578, 1, "\u0644\u0645"], [64579, 1, "\u0644\u0649"], [64580, 1, "\u0644\u064A"], [64581, 1, "\u0645\u062C"], [64582, 1, "\u0645\u062D"], [64583, 1, "\u0645\u062E"], [64584, 1, "\u0645\u0645"], [64585, 1, "\u0645\u0649"], [64586, 1, "\u0645\u064A"], [64587, 1, "\u0646\u062C"], [64588, 1, "\u0646\u062D"], [64589, 1, "\u0646\u062E"], [64590, 1, "\u0646\u0645"], [64591, 1, "\u0646\u0649"], [64592, 1, "\u0646\u064A"], [64593, 1, "\u0647\u062C"], [64594, 1, "\u0647\u0645"], [64595, 1, "\u0647\u0649"], [64596, 1, "\u0647\u064A"], [64597, 1, "\u064A\u062C"], [64598, 1, "\u064A\u062D"], [64599, 1, "\u064A\u062E"], [64600, 1, "\u064A\u0645"], [64601, 1, "\u064A\u0649"], [64602, 1, "\u064A\u064A"], [64603, 1, "\u0630\u0670"], [64604, 1, "\u0631\u0670"], [64605, 1, "\u0649\u0670"], [64606, 5, " \u064C\u0651"], [64607, 5, " \u064D\u0651"], [64608, 5, " \u064E\u0651"], [64609, 5, " \u064F\u0651"], [64610, 5, " \u0650\u0651"], [64611, 5, " \u0651\u0670"], [64612, 1, "\u0626\u0631"], [64613, 1, "\u0626\u0632"], [64614, 1, "\u0626\u0645"], [64615, 1, "\u0626\u0646"], [64616, 1, "\u0626\u0649"], [64617, 1, "\u0626\u064A"], [64618, 1, "\u0628\u0631"], [64619, 1, "\u0628\u0632"], [64620, 1, "\u0628\u0645"], [64621, 1, "\u0628\u0646"], [64622, 1, "\u0628\u0649"], [64623, 1, "\u0628\u064A"], [64624, 1, "\u062A\u0631"], [64625, 1, "\u062A\u0632"], [64626, 1, "\u062A\u0645"], [64627, 1, "\u062A\u0646"], [64628, 1, "\u062A\u0649"], [64629, 1, "\u062A\u064A"], [64630, 1, "\u062B\u0631"], [64631, 1, "\u062B\u0632"], [64632, 1, "\u062B\u0645"], [64633, 1, "\u062B\u0646"], [64634, 1, "\u062B\u0649"], [64635, 1, "\u062B\u064A"], [64636, 1, "\u0641\u0649"], [64637, 1, "\u0641\u064A"], [64638, 1, "\u0642\u0649"], [64639, 1, "\u0642\u064A"], [64640, 1, "\u0643\u0627"], [64641, 1, "\u0643\u0644"], [64642, 1, "\u0643\u0645"], [64643, 1, "\u0643\u0649"], [64644, 1, "\u0643\u064A"], [64645, 1, "\u0644\u0645"], [64646, 1, "\u0644\u0649"], [64647, 1, "\u0644\u064A"], [64648, 1, "\u0645\u0627"], [64649, 1, "\u0645\u0645"], [64650, 1, "\u0646\u0631"], [64651, 1, "\u0646\u0632"], [64652, 1, "\u0646\u0645"], [64653, 1, "\u0646\u0646"], [64654, 1, "\u0646\u0649"], [64655, 1, "\u0646\u064A"], [64656, 1, "\u0649\u0670"], [64657, 1, "\u064A\u0631"], [64658, 1, "\u064A\u0632"], [64659, 1, "\u064A\u0645"], [64660, 1, "\u064A\u0646"], [64661, 1, "\u064A\u0649"], [64662, 1, "\u064A\u064A"], [64663, 1, "\u0626\u062C"], [64664, 1, "\u0626\u062D"], [64665, 1, "\u0626\u062E"], [64666, 1, "\u0626\u0645"], [64667, 1, "\u0626\u0647"], [64668, 1, "\u0628\u062C"], [64669, 1, "\u0628\u062D"], [64670, 1, "\u0628\u062E"], [64671, 1, "\u0628\u0645"], [64672, 1, "\u0628\u0647"], [64673, 1, "\u062A\u062C"], [64674, 1, "\u062A\u062D"], [64675, 1, "\u062A\u062E"], [64676, 1, "\u062A\u0645"], [64677, 1, "\u062A\u0647"], [64678, 1, "\u062B\u0645"], [64679, 1, "\u062C\u062D"], [64680, 1, "\u062C\u0645"], [64681, 1, "\u062D\u062C"], [64682, 1, "\u062D\u0645"], [64683, 1, "\u062E\u062C"], [64684, 1, "\u062E\u0645"], [64685, 1, "\u0633\u062C"], [64686, 1, "\u0633\u062D"], [64687, 1, "\u0633\u062E"], [64688, 1, "\u0633\u0645"], [64689, 1, "\u0635\u062D"], [64690, 1, "\u0635\u062E"], [64691, 1, "\u0635\u0645"], [64692, 1, "\u0636\u062C"], [64693, 1, "\u0636\u062D"], [64694, 1, "\u0636\u062E"], [64695, 1, "\u0636\u0645"], [64696, 1, "\u0637\u062D"], [64697, 1, "\u0638\u0645"], [64698, 1, "\u0639\u062C"], [64699, 1, "\u0639\u0645"], [64700, 1, "\u063A\u062C"], [64701, 1, "\u063A\u0645"], [64702, 1, "\u0641\u062C"], [64703, 1, "\u0641\u062D"], [64704, 1, "\u0641\u062E"], [64705, 1, "\u0641\u0645"], [64706, 1, "\u0642\u062D"], [64707, 1, "\u0642\u0645"], [64708, 1, "\u0643\u062C"], [64709, 1, "\u0643\u062D"], [64710, 1, "\u0643\u062E"], [64711, 1, "\u0643\u0644"], [64712, 1, "\u0643\u0645"], [64713, 1, "\u0644\u062C"], [64714, 1, "\u0644\u062D"], [64715, 1, "\u0644\u062E"], [64716, 1, "\u0644\u0645"], [64717, 1, "\u0644\u0647"], [64718, 1, "\u0645\u062C"], [64719, 1, "\u0645\u062D"], [64720, 1, "\u0645\u062E"], [64721, 1, "\u0645\u0645"], [64722, 1, "\u0646\u062C"], [64723, 1, "\u0646\u062D"], [64724, 1, "\u0646\u062E"], [64725, 1, "\u0646\u0645"], [64726, 1, "\u0646\u0647"], [64727, 1, "\u0647\u062C"], [64728, 1, "\u0647\u0645"], [64729, 1, "\u0647\u0670"], [64730, 1, "\u064A\u062C"], [64731, 1, "\u064A\u062D"], [64732, 1, "\u064A\u062E"], [64733, 1, "\u064A\u0645"], [64734, 1, "\u064A\u0647"], [64735, 1, "\u0626\u0645"], [64736, 1, "\u0626\u0647"], [64737, 1, "\u0628\u0645"], [64738, 1, "\u0628\u0647"], [64739, 1, "\u062A\u0645"], [64740, 1, "\u062A\u0647"], [64741, 1, "\u062B\u0645"], [64742, 1, "\u062B\u0647"], [64743, 1, "\u0633\u0645"], [64744, 1, "\u0633\u0647"], [64745, 1, "\u0634\u0645"], [64746, 1, "\u0634\u0647"], [64747, 1, "\u0643\u0644"], [64748, 1, "\u0643\u0645"], [64749, 1, "\u0644\u0645"], [64750, 1, "\u0646\u0645"], [64751, 1, "\u0646\u0647"], [64752, 1, "\u064A\u0645"], [64753, 1, "\u064A\u0647"], [64754, 1, "\u0640\u064E\u0651"], [64755, 1, "\u0640\u064F\u0651"], [64756, 1, "\u0640\u0650\u0651"], [64757, 1, "\u0637\u0649"], [64758, 1, "\u0637\u064A"], [64759, 1, "\u0639\u0649"], [64760, 1, "\u0639\u064A"], [64761, 1, "\u063A\u0649"], [64762, 1, "\u063A\u064A"], [64763, 1, "\u0633\u0649"], [64764, 1, "\u0633\u064A"], [64765, 1, "\u0634\u0649"], [64766, 1, "\u0634\u064A"], [64767, 1, "\u062D\u0649"], [64768, 1, "\u062D\u064A"], [64769, 1, "\u062C\u0649"], [64770, 1, "\u062C\u064A"], [64771, 1, "\u062E\u0649"], [64772, 1, "\u062E\u064A"], [64773, 1, "\u0635\u0649"], [64774, 1, "\u0635\u064A"], [64775, 1, "\u0636\u0649"], [64776, 1, "\u0636\u064A"], [64777, 1, "\u0634\u062C"], [64778, 1, "\u0634\u062D"], [64779, 1, "\u0634\u062E"], [64780, 1, "\u0634\u0645"], [64781, 1, "\u0634\u0631"], [64782, 1, "\u0633\u0631"], [64783, 1, "\u0635\u0631"], [64784, 1, "\u0636\u0631"], [64785, 1, "\u0637\u0649"], [64786, 1, "\u0637\u064A"], [64787, 1, "\u0639\u0649"], [64788, 1, "\u0639\u064A"], [64789, 1, "\u063A\u0649"], [64790, 1, "\u063A\u064A"], [64791, 1, "\u0633\u0649"], [64792, 1, "\u0633\u064A"], [64793, 1, "\u0634\u0649"], [64794, 1, "\u0634\u064A"], [64795, 1, "\u062D\u0649"], [64796, 1, "\u062D\u064A"], [64797, 1, "\u062C\u0649"], [64798, 1, "\u062C\u064A"], [64799, 1, "\u062E\u0649"], [64800, 1, "\u062E\u064A"], [64801, 1, "\u0635\u0649"], [64802, 1, "\u0635\u064A"], [64803, 1, "\u0636\u0649"], [64804, 1, "\u0636\u064A"], [64805, 1, "\u0634\u062C"], [64806, 1, "\u0634\u062D"], [64807, 1, "\u0634\u062E"], [64808, 1, "\u0634\u0645"], [64809, 1, "\u0634\u0631"], [64810, 1, "\u0633\u0631"], [64811, 1, "\u0635\u0631"], [64812, 1, "\u0636\u0631"], [64813, 1, "\u0634\u062C"], [64814, 1, "\u0634\u062D"], [64815, 1, "\u0634\u062E"], [64816, 1, "\u0634\u0645"], [64817, 1, "\u0633\u0647"], [64818, 1, "\u0634\u0647"], [64819, 1, "\u0637\u0645"], [64820, 1, "\u0633\u062C"], [64821, 1, "\u0633\u062D"], [64822, 1, "\u0633\u062E"], [64823, 1, "\u0634\u062C"], [64824, 1, "\u0634\u062D"], [64825, 1, "\u0634\u062E"], [64826, 1, "\u0637\u0645"], [64827, 1, "\u0638\u0645"], [[64828, 64829], 1, "\u0627\u064B"], [[64830, 64831], 2], [[64832, 64847], 2], [64848, 1, "\u062A\u062C\u0645"], [[64849, 64850], 1, "\u062A\u062D\u062C"], [64851, 1, "\u062A\u062D\u0645"], [64852, 1, "\u062A\u062E\u0645"], [64853, 1, "\u062A\u0645\u062C"], [64854, 1, "\u062A\u0645\u062D"], [64855, 1, "\u062A\u0645\u062E"], [[64856, 64857], 1, "\u062C\u0645\u062D"], [64858, 1, "\u062D\u0645\u064A"], [64859, 1, "\u062D\u0645\u0649"], [64860, 1, "\u0633\u062D\u062C"], [64861, 1, "\u0633\u062C\u062D"], [64862, 1, "\u0633\u062C\u0649"], [[64863, 64864], 1, "\u0633\u0645\u062D"], [64865, 1, "\u0633\u0645\u062C"], [[64866, 64867], 1, "\u0633\u0645\u0645"], [[64868, 64869], 1, "\u0635\u062D\u062D"], [64870, 1, "\u0635\u0645\u0645"], [[64871, 64872], 1, "\u0634\u062D\u0645"], [64873, 1, "\u0634\u062C\u064A"], [[64874, 64875], 1, "\u0634\u0645\u062E"], [[64876, 64877], 1, "\u0634\u0645\u0645"], [64878, 1, "\u0636\u062D\u0649"], [[64879, 64880], 1, "\u0636\u062E\u0645"], [[64881, 64882], 1, "\u0637\u0645\u062D"], [64883, 1, "\u0637\u0645\u0645"], [64884, 1, "\u0637\u0645\u064A"], [64885, 1, "\u0639\u062C\u0645"], [[64886, 64887], 1, "\u0639\u0645\u0645"], [64888, 1, "\u0639\u0645\u0649"], [64889, 1, "\u063A\u0645\u0645"], [64890, 1, "\u063A\u0645\u064A"], [64891, 1, "\u063A\u0645\u0649"], [[64892, 64893], 1, "\u0641\u062E\u0645"], [64894, 1, "\u0642\u0645\u062D"], [64895, 1, "\u0642\u0645\u0645"], [64896, 1, "\u0644\u062D\u0645"], [64897, 1, "\u0644\u062D\u064A"], [64898, 1, "\u0644\u062D\u0649"], [[64899, 64900], 1, "\u0644\u062C\u062C"], [[64901, 64902], 1, "\u0644\u062E\u0645"], [[64903, 64904], 1, "\u0644\u0645\u062D"], [64905, 1, "\u0645\u062D\u062C"], [64906, 1, "\u0645\u062D\u0645"], [64907, 1, "\u0645\u062D\u064A"], [64908, 1, "\u0645\u062C\u062D"], [64909, 1, "\u0645\u062C\u0645"], [64910, 1, "\u0645\u062E\u062C"], [64911, 1, "\u0645\u062E\u0645"], [[64912, 64913], 3], [64914, 1, "\u0645\u062C\u062E"], [64915, 1, "\u0647\u0645\u062C"], [64916, 1, "\u0647\u0645\u0645"], [64917, 1, "\u0646\u062D\u0645"], [64918, 1, "\u0646\u062D\u0649"], [[64919, 64920], 1, "\u0646\u062C\u0645"], [64921, 1, "\u0646\u062C\u0649"], [64922, 1, "\u0646\u0645\u064A"], [64923, 1, "\u0646\u0645\u0649"], [[64924, 64925], 1, "\u064A\u0645\u0645"], [64926, 1, "\u0628\u062E\u064A"], [64927, 1, "\u062A\u062C\u064A"], [64928, 1, "\u062A\u062C\u0649"], [64929, 1, "\u062A\u062E\u064A"], [64930, 1, "\u062A\u062E\u0649"], [64931, 1, "\u062A\u0645\u064A"], [64932, 1, "\u062A\u0645\u0649"], [64933, 1, "\u062C\u0645\u064A"], [64934, 1, "\u062C\u062D\u0649"], [64935, 1, "\u062C\u0645\u0649"], [64936, 1, "\u0633\u062E\u0649"], [64937, 1, "\u0635\u062D\u064A"], [64938, 1, "\u0634\u062D\u064A"], [64939, 1, "\u0636\u062D\u064A"], [64940, 1, "\u0644\u062C\u064A"], [64941, 1, "\u0644\u0645\u064A"], [64942, 1, "\u064A\u062D\u064A"], [64943, 1, "\u064A\u062C\u064A"], [64944, 1, "\u064A\u0645\u064A"], [64945, 1, "\u0645\u0645\u064A"], [64946, 1, "\u0642\u0645\u064A"], [64947, 1, "\u0646\u062D\u064A"], [64948, 1, "\u0642\u0645\u062D"], [64949, 1, "\u0644\u062D\u0645"], [64950, 1, "\u0639\u0645\u064A"], [64951, 1, "\u0643\u0645\u064A"], [64952, 1, "\u0646\u062C\u062D"], [64953, 1, "\u0645\u062E\u064A"], [64954, 1, "\u0644\u062C\u0645"], [64955, 1, "\u0643\u0645\u0645"], [64956, 1, "\u0644\u062C\u0645"], [64957, 1, "\u0646\u062C\u062D"], [64958, 1, "\u062C\u062D\u064A"], [64959, 1, "\u062D\u062C\u064A"], [64960, 1, "\u0645\u062C\u064A"], [64961, 1, "\u0641\u0645\u064A"], [64962, 1, "\u0628\u062D\u064A"], [64963, 1, "\u0643\u0645\u0645"], [64964, 1, "\u0639\u062C\u0645"], [64965, 1, "\u0635\u0645\u0645"], [64966, 1, "\u0633\u062E\u064A"], [64967, 1, "\u0646\u062C\u064A"], [[64968, 64974], 3], [64975, 2], [[64976, 65007], 3], [65008, 1, "\u0635\u0644\u06D2"], [65009, 1, "\u0642\u0644\u06D2"], [65010, 1, "\u0627\u0644\u0644\u0647"], [65011, 1, "\u0627\u0643\u0628\u0631"], [65012, 1, "\u0645\u062D\u0645\u062F"], [65013, 1, "\u0635\u0644\u0639\u0645"], [65014, 1, "\u0631\u0633\u0648\u0644"], [65015, 1, "\u0639\u0644\u064A\u0647"], [65016, 1, "\u0648\u0633\u0644\u0645"], [65017, 1, "\u0635\u0644\u0649"], [65018, 5, "\u0635\u0644\u0649 \u0627\u0644\u0644\u0647 \u0639\u0644\u064A\u0647 \u0648\u0633\u0644\u0645"], [65019, 5, "\u062C\u0644 \u062C\u0644\u0627\u0644\u0647"], [65020, 1, "\u0631\u06CC\u0627\u0644"], [65021, 2], [[65022, 65023], 2], [[65024, 65039], 7], [65040, 5, ","], [65041, 1, "\u3001"], [65042, 3], [65043, 5, ":"], [65044, 5, ";"], [65045, 5, "!"], [65046, 5, "?"], [65047, 1, "\u3016"], [65048, 1, "\u3017"], [65049, 3], [[65050, 65055], 3], [[65056, 65059], 2], [[65060, 65062], 2], [[65063, 65069], 2], [[65070, 65071], 2], [65072, 3], [65073, 1, "\u2014"], [65074, 1, "\u2013"], [[65075, 65076], 5, "_"], [65077, 5, "("], [65078, 5, ")"], [65079, 5, "{"], [65080, 5, "}"], [65081, 1, "\u3014"], [65082, 1, "\u3015"], [65083, 1, "\u3010"], [65084, 1, "\u3011"], [65085, 1, "\u300A"], [65086, 1, "\u300B"], [65087, 1, "\u3008"], [65088, 1, "\u3009"], [65089, 1, "\u300C"], [65090, 1, "\u300D"], [65091, 1, "\u300E"], [65092, 1, "\u300F"], [[65093, 65094], 2], [65095, 5, "["], [65096, 5, "]"], [[65097, 65100], 5, " \u0305"], [[65101, 65103], 5, "_"], [65104, 5, ","], [65105, 1, "\u3001"], [65106, 3], [65107, 3], [65108, 5, ";"], [65109, 5, ":"], [65110, 5, "?"], [65111, 5, "!"], [65112, 1, "\u2014"], [65113, 5, "("], [65114, 5, ")"], [65115, 5, "{"], [65116, 5, "}"], [65117, 1, "\u3014"], [65118, 1, "\u3015"], [65119, 5, "#"], [65120, 5, "&"], [65121, 5, "*"], [65122, 5, "+"], [65123, 1, "-"], [65124, 5, "<"], [65125, 5, ">"], [65126, 5, "="], [65127, 3], [65128, 5, "\\"], [65129, 5, "$"], [65130, 5, "%"], [65131, 5, "@"], [[65132, 65135], 3], [65136, 5, " \u064B"], [65137, 1, "\u0640\u064B"], [65138, 5, " \u064C"], [65139, 2], [65140, 5, " \u064D"], [65141, 3], [65142, 5, " \u064E"], [65143, 1, "\u0640\u064E"], [65144, 5, " \u064F"], [65145, 1, "\u0640\u064F"], [65146, 5, " \u0650"], [65147, 1, "\u0640\u0650"], [65148, 5, " \u0651"], [65149, 1, "\u0640\u0651"], [65150, 5, " \u0652"], [65151, 1, "\u0640\u0652"], [65152, 1, "\u0621"], [[65153, 65154], 1, "\u0622"], [[65155, 65156], 1, "\u0623"], [[65157, 65158], 1, "\u0624"], [[65159, 65160], 1, "\u0625"], [[65161, 65164], 1, "\u0626"], [[65165, 65166], 1, "\u0627"], [[65167, 65170], 1, "\u0628"], [[65171, 65172], 1, "\u0629"], [[65173, 65176], 1, "\u062A"], [[65177, 65180], 1, "\u062B"], [[65181, 65184], 1, "\u062C"], [[65185, 65188], 1, "\u062D"], [[65189, 65192], 1, "\u062E"], [[65193, 65194], 1, "\u062F"], [[65195, 65196], 1, "\u0630"], [[65197, 65198], 1, "\u0631"], [[65199, 65200], 1, "\u0632"], [[65201, 65204], 1, "\u0633"], [[65205, 65208], 1, "\u0634"], [[65209, 65212], 1, "\u0635"], [[65213, 65216], 1, "\u0636"], [[65217, 65220], 1, "\u0637"], [[65221, 65224], 1, "\u0638"], [[65225, 65228], 1, "\u0639"], [[65229, 65232], 1, "\u063A"], [[65233, 65236], 1, "\u0641"], [[65237, 65240], 1, "\u0642"], [[65241, 65244], 1, "\u0643"], [[65245, 65248], 1, "\u0644"], [[65249, 65252], 1, "\u0645"], [[65253, 65256], 1, "\u0646"], [[65257, 65260], 1, "\u0647"], [[65261, 65262], 1, "\u0648"], [[65263, 65264], 1, "\u0649"], [[65265, 65268], 1, "\u064A"], [[65269, 65270], 1, "\u0644\u0622"], [[65271, 65272], 1, "\u0644\u0623"], [[65273, 65274], 1, "\u0644\u0625"], [[65275, 65276], 1, "\u0644\u0627"], [[65277, 65278], 3], [65279, 7], [65280, 3], [65281, 5, "!"], [65282, 5, '"'], [65283, 5, "#"], [65284, 5, "$"], [65285, 5, "%"], [65286, 5, "&"], [65287, 5, "'"], [65288, 5, "("], [65289, 5, ")"], [65290, 5, "*"], [65291, 5, "+"], [65292, 5, ","], [65293, 1, "-"], [65294, 1, "."], [65295, 5, "/"], [65296, 1, "0"], [65297, 1, "1"], [65298, 1, "2"], [65299, 1, "3"], [65300, 1, "4"], [65301, 1, "5"], [65302, 1, "6"], [65303, 1, "7"], [65304, 1, "8"], [65305, 1, "9"], [65306, 5, ":"], [65307, 5, ";"], [65308, 5, "<"], [65309, 5, "="], [65310, 5, ">"], [65311, 5, "?"], [65312, 5, "@"], [65313, 1, "a"], [65314, 1, "b"], [65315, 1, "c"], [65316, 1, "d"], [65317, 1, "e"], [65318, 1, "f"], [65319, 1, "g"], [65320, 1, "h"], [65321, 1, "i"], [65322, 1, "j"], [65323, 1, "k"], [65324, 1, "l"], [65325, 1, "m"], [65326, 1, "n"], [65327, 1, "o"], [65328, 1, "p"], [65329, 1, "q"], [65330, 1, "r"], [65331, 1, "s"], [65332, 1, "t"], [65333, 1, "u"], [65334, 1, "v"], [65335, 1, "w"], [65336, 1, "x"], [65337, 1, "y"], [65338, 1, "z"], [65339, 5, "["], [65340, 5, "\\"], [65341, 5, "]"], [65342, 5, "^"], [65343, 5, "_"], [65344, 5, "`"], [65345, 1, "a"], [65346, 1, "b"], [65347, 1, "c"], [65348, 1, "d"], [65349, 1, "e"], [65350, 1, "f"], [65351, 1, "g"], [65352, 1, "h"], [65353, 1, "i"], [65354, 1, "j"], [65355, 1, "k"], [65356, 1, "l"], [65357, 1, "m"], [65358, 1, "n"], [65359, 1, "o"], [65360, 1, "p"], [65361, 1, "q"], [65362, 1, "r"], [65363, 1, "s"], [65364, 1, "t"], [65365, 1, "u"], [65366, 1, "v"], [65367, 1, "w"], [65368, 1, "x"], [65369, 1, "y"], [65370, 1, "z"], [65371, 5, "{"], [65372, 5, "|"], [65373, 5, "}"], [65374, 5, "~"], [65375, 1, "\u2985"], [65376, 1, "\u2986"], [65377, 1, "."], [65378, 1, "\u300C"], [65379, 1, "\u300D"], [65380, 1, "\u3001"], [65381, 1, "\u30FB"], [65382, 1, "\u30F2"], [65383, 1, "\u30A1"], [65384, 1, "\u30A3"], [65385, 1, "\u30A5"], [65386, 1, "\u30A7"], [65387, 1, "\u30A9"], [65388, 1, "\u30E3"], [65389, 1, "\u30E5"], [65390, 1, "\u30E7"], [65391, 1, "\u30C3"], [65392, 1, "\u30FC"], [65393, 1, "\u30A2"], [65394, 1, "\u30A4"], [65395, 1, "\u30A6"], [65396, 1, "\u30A8"], [65397, 1, "\u30AA"], [65398, 1, "\u30AB"], [65399, 1, "\u30AD"], [65400, 1, "\u30AF"], [65401, 1, "\u30B1"], [65402, 1, "\u30B3"], [65403, 1, "\u30B5"], [65404, 1, "\u30B7"], [65405, 1, "\u30B9"], [65406, 1, "\u30BB"], [65407, 1, "\u30BD"], [65408, 1, "\u30BF"], [65409, 1, "\u30C1"], [65410, 1, "\u30C4"], [65411, 1, "\u30C6"], [65412, 1, "\u30C8"], [65413, 1, "\u30CA"], [65414, 1, "\u30CB"], [65415, 1, "\u30CC"], [65416, 1, "\u30CD"], [65417, 1, "\u30CE"], [65418, 1, "\u30CF"], [65419, 1, "\u30D2"], [65420, 1, "\u30D5"], [65421, 1, "\u30D8"], [65422, 1, "\u30DB"], [65423, 1, "\u30DE"], [65424, 1, "\u30DF"], [65425, 1, "\u30E0"], [65426, 1, "\u30E1"], [65427, 1, "\u30E2"], [65428, 1, "\u30E4"], [65429, 1, "\u30E6"], [65430, 1, "\u30E8"], [65431, 1, "\u30E9"], [65432, 1, "\u30EA"], [65433, 1, "\u30EB"], [65434, 1, "\u30EC"], [65435, 1, "\u30ED"], [65436, 1, "\u30EF"], [65437, 1, "\u30F3"], [65438, 1, "\u3099"], [65439, 1, "\u309A"], [65440, 3], [65441, 1, "\u1100"], [65442, 1, "\u1101"], [65443, 1, "\u11AA"], [65444, 1, "\u1102"], [65445, 1, "\u11AC"], [65446, 1, "\u11AD"], [65447, 1, "\u1103"], [65448, 1, "\u1104"], [65449, 1, "\u1105"], [65450, 1, "\u11B0"], [65451, 1, "\u11B1"], [65452, 1, "\u11B2"], [65453, 1, "\u11B3"], [65454, 1, "\u11B4"], [65455, 1, "\u11B5"], [65456, 1, "\u111A"], [65457, 1, "\u1106"], [65458, 1, "\u1107"], [65459, 1, "\u1108"], [65460, 1, "\u1121"], [65461, 1, "\u1109"], [65462, 1, "\u110A"], [65463, 1, "\u110B"], [65464, 1, "\u110C"], [65465, 1, "\u110D"], [65466, 1, "\u110E"], [65467, 1, "\u110F"], [65468, 1, "\u1110"], [65469, 1, "\u1111"], [65470, 1, "\u1112"], [[65471, 65473], 3], [65474, 1, "\u1161"], [65475, 1, "\u1162"], [65476, 1, "\u1163"], [65477, 1, "\u1164"], [65478, 1, "\u1165"], [65479, 1, "\u1166"], [[65480, 65481], 3], [65482, 1, "\u1167"], [65483, 1, "\u1168"], [65484, 1, "\u1169"], [65485, 1, "\u116A"], [65486, 1, "\u116B"], [65487, 1, "\u116C"], [[65488, 65489], 3], [65490, 1, "\u116D"], [65491, 1, "\u116E"], [65492, 1, "\u116F"], [65493, 1, "\u1170"], [65494, 1, "\u1171"], [65495, 1, "\u1172"], [[65496, 65497], 3], [65498, 1, "\u1173"], [65499, 1, "\u1174"], [65500, 1, "\u1175"], [[65501, 65503], 3], [65504, 1, "\xA2"], [65505, 1, "\xA3"], [65506, 1, "\xAC"], [65507, 5, " \u0304"], [65508, 1, "\xA6"], [65509, 1, "\xA5"], [65510, 1, "\u20A9"], [65511, 3], [65512, 1, "\u2502"], [65513, 1, "\u2190"], [65514, 1, "\u2191"], [65515, 1, "\u2192"], [65516, 1, "\u2193"], [65517, 1, "\u25A0"], [65518, 1, "\u25CB"], [[65519, 65528], 3], [[65529, 65531], 3], [65532, 3], [65533, 3], [[65534, 65535], 3], [[65536, 65547], 2], [65548, 3], [[65549, 65574], 2], [65575, 3], [[65576, 65594], 2], [65595, 3], [[65596, 65597], 2], [65598, 3], [[65599, 65613], 2], [[65614, 65615], 3], [[65616, 65629], 2], [[65630, 65663], 3], [[65664, 65786], 2], [[65787, 65791], 3], [[65792, 65794], 2], [[65795, 65798], 3], [[65799, 65843], 2], [[65844, 65846], 3], [[65847, 65855], 2], [[65856, 65930], 2], [[65931, 65932], 2], [[65933, 65934], 2], [65935, 3], [[65936, 65947], 2], [65948, 2], [[65949, 65951], 3], [65952, 2], [[65953, 65999], 3], [[66e3, 66044], 2], [66045, 2], [[66046, 66175], 3], [[66176, 66204], 2], [[66205, 66207], 3], [[66208, 66256], 2], [[66257, 66271], 3], [66272, 2], [[66273, 66299], 2], [[66300, 66303], 3], [[66304, 66334], 2], [66335, 2], [[66336, 66339], 2], [[66340, 66348], 3], [[66349, 66351], 2], [[66352, 66368], 2], [66369, 2], [[66370, 66377], 2], [66378, 2], [[66379, 66383], 3], [[66384, 66426], 2], [[66427, 66431], 3], [[66432, 66461], 2], [66462, 3], [66463, 2], [[66464, 66499], 2], [[66500, 66503], 3], [[66504, 66511], 2], [[66512, 66517], 2], [[66518, 66559], 3], [66560, 1, "\u{10428}"], [66561, 1, "\u{10429}"], [66562, 1, "\u{1042A}"], [66563, 1, "\u{1042B}"], [66564, 1, "\u{1042C}"], [66565, 1, "\u{1042D}"], [66566, 1, "\u{1042E}"], [66567, 1, "\u{1042F}"], [66568, 1, "\u{10430}"], [66569, 1, "\u{10431}"], [66570, 1, "\u{10432}"], [66571, 1, "\u{10433}"], [66572, 1, "\u{10434}"], [66573, 1, "\u{10435}"], [66574, 1, "\u{10436}"], [66575, 1, "\u{10437}"], [66576, 1, "\u{10438}"], [66577, 1, "\u{10439}"], [66578, 1, "\u{1043A}"], [66579, 1, "\u{1043B}"], [66580, 1, "\u{1043C}"], [66581, 1, "\u{1043D}"], [66582, 1, "\u{1043E}"], [66583, 1, "\u{1043F}"], [66584, 1, "\u{10440}"], [66585, 1, "\u{10441}"], [66586, 1, "\u{10442}"], [66587, 1, "\u{10443}"], [66588, 1, "\u{10444}"], [66589, 1, "\u{10445}"], [66590, 1, "\u{10446}"], [66591, 1, "\u{10447}"], [66592, 1, "\u{10448}"], [66593, 1, "\u{10449}"], [66594, 1, "\u{1044A}"], [66595, 1, "\u{1044B}"], [66596, 1, "\u{1044C}"], [66597, 1, "\u{1044D}"], [66598, 1, "\u{1044E}"], [66599, 1, "\u{1044F}"], [[66600, 66637], 2], [[66638, 66717], 2], [[66718, 66719], 3], [[66720, 66729], 2], [[66730, 66735], 3], [66736, 1, "\u{104D8}"], [66737, 1, "\u{104D9}"], [66738, 1, "\u{104DA}"], [66739, 1, "\u{104DB}"], [66740, 1, "\u{104DC}"], [66741, 1, "\u{104DD}"], [66742, 1, "\u{104DE}"], [66743, 1, "\u{104DF}"], [66744, 1, "\u{104E0}"], [66745, 1, "\u{104E1}"], [66746, 1, "\u{104E2}"], [66747, 1, "\u{104E3}"], [66748, 1, "\u{104E4}"], [66749, 1, "\u{104E5}"], [66750, 1, "\u{104E6}"], [66751, 1, "\u{104E7}"], [66752, 1, "\u{104E8}"], [66753, 1, "\u{104E9}"], [66754, 1, "\u{104EA}"], [66755, 1, "\u{104EB}"], [66756, 1, "\u{104EC}"], [66757, 1, "\u{104ED}"], [66758, 1, "\u{104EE}"], [66759, 1, "\u{104EF}"], [66760, 1, "\u{104F0}"], [66761, 1, "\u{104F1}"], [66762, 1, "\u{104F2}"], [66763, 1, "\u{104F3}"], [66764, 1, "\u{104F4}"], [66765, 1, "\u{104F5}"], [66766, 1, "\u{104F6}"], [66767, 1, "\u{104F7}"], [66768, 1, "\u{104F8}"], [66769, 1, "\u{104F9}"], [66770, 1, "\u{104FA}"], [66771, 1, "\u{104FB}"], [[66772, 66775], 3], [[66776, 66811], 2], [[66812, 66815], 3], [[66816, 66855], 2], [[66856, 66863], 3], [[66864, 66915], 2], [[66916, 66926], 3], [66927, 2], [66928, 1, "\u{10597}"], [66929, 1, "\u{10598}"], [66930, 1, "\u{10599}"], [66931, 1, "\u{1059A}"], [66932, 1, "\u{1059B}"], [66933, 1, "\u{1059C}"], [66934, 1, "\u{1059D}"], [66935, 1, "\u{1059E}"], [66936, 1, "\u{1059F}"], [66937, 1, "\u{105A0}"], [66938, 1, "\u{105A1}"], [66939, 3], [66940, 1, "\u{105A3}"], [66941, 1, "\u{105A4}"], [66942, 1, "\u{105A5}"], [66943, 1, "\u{105A6}"], [66944, 1, "\u{105A7}"], [66945, 1, "\u{105A8}"], [66946, 1, "\u{105A9}"], [66947, 1, "\u{105AA}"], [66948, 1, "\u{105AB}"], [66949, 1, "\u{105AC}"], [66950, 1, "\u{105AD}"], [66951, 1, "\u{105AE}"], [66952, 1, "\u{105AF}"], [66953, 1, "\u{105B0}"], [66954, 1, "\u{105B1}"], [66955, 3], [66956, 1, "\u{105B3}"], [66957, 1, "\u{105B4}"], [66958, 1, "\u{105B5}"], [66959, 1, "\u{105B6}"], [66960, 1, "\u{105B7}"], [66961, 1, "\u{105B8}"], [66962, 1, "\u{105B9}"], [66963, 3], [66964, 1, "\u{105BB}"], [66965, 1, "\u{105BC}"], [66966, 3], [[66967, 66977], 2], [66978, 3], [[66979, 66993], 2], [66994, 3], [[66995, 67001], 2], [67002, 3], [[67003, 67004], 2], [[67005, 67071], 3], [[67072, 67382], 2], [[67383, 67391], 3], [[67392, 67413], 2], [[67414, 67423], 3], [[67424, 67431], 2], [[67432, 67455], 3], [67456, 2], [67457, 1, "\u02D0"], [67458, 1, "\u02D1"], [67459, 1, "\xE6"], [67460, 1, "\u0299"], [67461, 1, "\u0253"], [67462, 3], [67463, 1, "\u02A3"], [67464, 1, "\uAB66"], [67465, 1, "\u02A5"], [67466, 1, "\u02A4"], [67467, 1, "\u0256"], [67468, 1, "\u0257"], [67469, 1, "\u1D91"], [67470, 1, "\u0258"], [67471, 1, "\u025E"], [67472, 1, "\u02A9"], [67473, 1, "\u0264"], [67474, 1, "\u0262"], [67475, 1, "\u0260"], [67476, 1, "\u029B"], [67477, 1, "\u0127"], [67478, 1, "\u029C"], [67479, 1, "\u0267"], [67480, 1, "\u0284"], [67481, 1, "\u02AA"], [67482, 1, "\u02AB"], [67483, 1, "\u026C"], [67484, 1, "\u{1DF04}"], [67485, 1, "\uA78E"], [67486, 1, "\u026E"], [67487, 1, "\u{1DF05}"], [67488, 1, "\u028E"], [67489, 1, "\u{1DF06}"], [67490, 1, "\xF8"], [67491, 1, "\u0276"], [67492, 1, "\u0277"], [67493, 1, "q"], [67494, 1, "\u027A"], [67495, 1, "\u{1DF08}"], [67496, 1, "\u027D"], [67497, 1, "\u027E"], [67498, 1, "\u0280"], [67499, 1, "\u02A8"], [67500, 1, "\u02A6"], [67501, 1, "\uAB67"], [67502, 1, "\u02A7"], [67503, 1, "\u0288"], [67504, 1, "\u2C71"], [67505, 3], [67506, 1, "\u028F"], [67507, 1, "\u02A1"], [67508, 1, "\u02A2"], [67509, 1, "\u0298"], [67510, 1, "\u01C0"], [67511, 1, "\u01C1"], [67512, 1, "\u01C2"], [67513, 1, "\u{1DF0A}"], [67514, 1, "\u{1DF1E}"], [[67515, 67583], 3], [[67584, 67589], 2], [[67590, 67591], 3], [67592, 2], [67593, 3], [[67594, 67637], 2], [67638, 3], [[67639, 67640], 2], [[67641, 67643], 3], [67644, 2], [[67645, 67646], 3], [67647, 2], [[67648, 67669], 2], [67670, 3], [[67671, 67679], 2], [[67680, 67702], 2], [[67703, 67711], 2], [[67712, 67742], 2], [[67743, 67750], 3], [[67751, 67759], 2], [[67760, 67807], 3], [[67808, 67826], 2], [67827, 3], [[67828, 67829], 2], [[67830, 67834], 3], [[67835, 67839], 2], [[67840, 67861], 2], [[67862, 67865], 2], [[67866, 67867], 2], [[67868, 67870], 3], [67871, 2], [[67872, 67897], 2], [[67898, 67902], 3], [67903, 2], [[67904, 67967], 3], [[67968, 68023], 2], [[68024, 68027], 3], [[68028, 68029], 2], [[68030, 68031], 2], [[68032, 68047], 2], [[68048, 68049], 3], [[68050, 68095], 2], [[68096, 68099], 2], [68100, 3], [[68101, 68102], 2], [[68103, 68107], 3], [[68108, 68115], 2], [68116, 3], [[68117, 68119], 2], [68120, 3], [[68121, 68147], 2], [[68148, 68149], 2], [[68150, 68151], 3], [[68152, 68154], 2], [[68155, 68158], 3], [68159, 2], [[68160, 68167], 2], [68168, 2], [[68169, 68175], 3], [[68176, 68184], 2], [[68185, 68191], 3], [[68192, 68220], 2], [[68221, 68223], 2], [[68224, 68252], 2], [[68253, 68255], 2], [[68256, 68287], 3], [[68288, 68295], 2], [68296, 2], [[68297, 68326], 2], [[68327, 68330], 3], [[68331, 68342], 2], [[68343, 68351], 3], [[68352, 68405], 2], [[68406, 68408], 3], [[68409, 68415], 2], [[68416, 68437], 2], [[68438, 68439], 3], [[68440, 68447], 2], [[68448, 68466], 2], [[68467, 68471], 3], [[68472, 68479], 2], [[68480, 68497], 2], [[68498, 68504], 3], [[68505, 68508], 2], [[68509, 68520], 3], [[68521, 68527], 2], [[68528, 68607], 3], [[68608, 68680], 2], [[68681, 68735], 3], [68736, 1, "\u{10CC0}"], [68737, 1, "\u{10CC1}"], [68738, 1, "\u{10CC2}"], [68739, 1, "\u{10CC3}"], [68740, 1, "\u{10CC4}"], [68741, 1, "\u{10CC5}"], [68742, 1, "\u{10CC6}"], [68743, 1, "\u{10CC7}"], [68744, 1, "\u{10CC8}"], [68745, 1, "\u{10CC9}"], [68746, 1, "\u{10CCA}"], [68747, 1, "\u{10CCB}"], [68748, 1, "\u{10CCC}"], [68749, 1, "\u{10CCD}"], [68750, 1, "\u{10CCE}"], [68751, 1, "\u{10CCF}"], [68752, 1, "\u{10CD0}"], [68753, 1, "\u{10CD1}"], [68754, 1, "\u{10CD2}"], [68755, 1, "\u{10CD3}"], [68756, 1, "\u{10CD4}"], [68757, 1, "\u{10CD5}"], [68758, 1, "\u{10CD6}"], [68759, 1, "\u{10CD7}"], [68760, 1, "\u{10CD8}"], [68761, 1, "\u{10CD9}"], [68762, 1, "\u{10CDA}"], [68763, 1, "\u{10CDB}"], [68764, 1, "\u{10CDC}"], [68765, 1, "\u{10CDD}"], [68766, 1, "\u{10CDE}"], [68767, 1, "\u{10CDF}"], [68768, 1, "\u{10CE0}"], [68769, 1, "\u{10CE1}"], [68770, 1, "\u{10CE2}"], [68771, 1, "\u{10CE3}"], [68772, 1, "\u{10CE4}"], [68773, 1, "\u{10CE5}"], [68774, 1, "\u{10CE6}"], [68775, 1, "\u{10CE7}"], [68776, 1, "\u{10CE8}"], [68777, 1, "\u{10CE9}"], [68778, 1, "\u{10CEA}"], [68779, 1, "\u{10CEB}"], [68780, 1, "\u{10CEC}"], [68781, 1, "\u{10CED}"], [68782, 1, "\u{10CEE}"], [68783, 1, "\u{10CEF}"], [68784, 1, "\u{10CF0}"], [68785, 1, "\u{10CF1}"], [68786, 1, "\u{10CF2}"], [[68787, 68799], 3], [[68800, 68850], 2], [[68851, 68857], 3], [[68858, 68863], 2], [[68864, 68903], 2], [[68904, 68911], 3], [[68912, 68921], 2], [[68922, 69215], 3], [[69216, 69246], 2], [69247, 3], [[69248, 69289], 2], [69290, 3], [[69291, 69292], 2], [69293, 2], [[69294, 69295], 3], [[69296, 69297], 2], [[69298, 69372], 3], [[69373, 69375], 2], [[69376, 69404], 2], [[69405, 69414], 2], [69415, 2], [[69416, 69423], 3], [[69424, 69456], 2], [[69457, 69465], 2], [[69466, 69487], 3], [[69488, 69509], 2], [[69510, 69513], 2], [[69514, 69551], 3], [[69552, 69572], 2], [[69573, 69579], 2], [[69580, 69599], 3], [[69600, 69622], 2], [[69623, 69631], 3], [[69632, 69702], 2], [[69703, 69709], 2], [[69710, 69713], 3], [[69714, 69733], 2], [[69734, 69743], 2], [[69744, 69749], 2], [[69750, 69758], 3], [69759, 2], [[69760, 69818], 2], [[69819, 69820], 2], [69821, 3], [[69822, 69825], 2], [69826, 2], [[69827, 69836], 3], [69837, 3], [[69838, 69839], 3], [[69840, 69864], 2], [[69865, 69871], 3], [[69872, 69881], 2], [[69882, 69887], 3], [[69888, 69940], 2], [69941, 3], [[69942, 69951], 2], [[69952, 69955], 2], [[69956, 69958], 2], [69959, 2], [[69960, 69967], 3], [[69968, 70003], 2], [[70004, 70005], 2], [70006, 2], [[70007, 70015], 3], [[70016, 70084], 2], [[70085, 70088], 2], [[70089, 70092], 2], [70093, 2], [[70094, 70095], 2], [[70096, 70105], 2], [70106, 2], [70107, 2], [70108, 2], [[70109, 70111], 2], [70112, 3], [[70113, 70132], 2], [[70133, 70143], 3], [[70144, 70161], 2], [70162, 3], [[70163, 70199], 2], [[70200, 70205], 2], [70206, 2], [[70207, 70209], 2], [[70210, 70271], 3], [[70272, 70278], 2], [70279, 3], [70280, 2], [70281, 3], [[70282, 70285], 2], [70286, 3], [[70287, 70301], 2], [70302, 3], [[70303, 70312], 2], [70313, 2], [[70314, 70319], 3], [[70320, 70378], 2], [[70379, 70383], 3], [[70384, 70393], 2], [[70394, 70399], 3], [70400, 2], [[70401, 70403], 2], [70404, 3], [[70405, 70412], 2], [[70413, 70414], 3], [[70415, 70416], 2], [[70417, 70418], 3], [[70419, 70440], 2], [70441, 3], [[70442, 70448], 2], [70449, 3], [[70450, 70451], 2], [70452, 3], [[70453, 70457], 2], [70458, 3], [70459, 2], [[70460, 70468], 2], [[70469, 70470], 3], [[70471, 70472], 2], [[70473, 70474], 3], [[70475, 70477], 2], [[70478, 70479], 3], [70480, 2], [[70481, 70486], 3], [70487, 2], [[70488, 70492], 3], [[70493, 70499], 2], [[70500, 70501], 3], [[70502, 70508], 2], [[70509, 70511], 3], [[70512, 70516], 2], [[70517, 70655], 3], [[70656, 70730], 2], [[70731, 70735], 2], [[70736, 70745], 2], [70746, 2], [70747, 2], [70748, 3], [70749, 2], [70750, 2], [70751, 2], [[70752, 70753], 2], [[70754, 70783], 3], [[70784, 70853], 2], [70854, 2], [70855, 2], [[70856, 70863], 3], [[70864, 70873], 2], [[70874, 71039], 3], [[71040, 71093], 2], [[71094, 71095], 3], [[71096, 71104], 2], [[71105, 71113], 2], [[71114, 71127], 2], [[71128, 71133], 2], [[71134, 71167], 3], [[71168, 71232], 2], [[71233, 71235], 2], [71236, 2], [[71237, 71247], 3], [[71248, 71257], 2], [[71258, 71263], 3], [[71264, 71276], 2], [[71277, 71295], 3], [[71296, 71351], 2], [71352, 2], [71353, 2], [[71354, 71359], 3], [[71360, 71369], 2], [[71370, 71423], 3], [[71424, 71449], 2], [71450, 2], [[71451, 71452], 3], [[71453, 71467], 2], [[71468, 71471], 3], [[71472, 71481], 2], [[71482, 71487], 2], [[71488, 71494], 2], [[71495, 71679], 3], [[71680, 71738], 2], [71739, 2], [[71740, 71839], 3], [71840, 1, "\u{118C0}"], [71841, 1, "\u{118C1}"], [71842, 1, "\u{118C2}"], [71843, 1, "\u{118C3}"], [71844, 1, "\u{118C4}"], [71845, 1, "\u{118C5}"], [71846, 1, "\u{118C6}"], [71847, 1, "\u{118C7}"], [71848, 1, "\u{118C8}"], [71849, 1, "\u{118C9}"], [71850, 1, "\u{118CA}"], [71851, 1, "\u{118CB}"], [71852, 1, "\u{118CC}"], [71853, 1, "\u{118CD}"], [71854, 1, "\u{118CE}"], [71855, 1, "\u{118CF}"], [71856, 1, "\u{118D0}"], [71857, 1, "\u{118D1}"], [71858, 1, "\u{118D2}"], [71859, 1, "\u{118D3}"], [71860, 1, "\u{118D4}"], [71861, 1, "\u{118D5}"], [71862, 1, "\u{118D6}"], [71863, 1, "\u{118D7}"], [71864, 1, "\u{118D8}"], [71865, 1, "\u{118D9}"], [71866, 1, "\u{118DA}"], [71867, 1, "\u{118DB}"], [71868, 1, "\u{118DC}"], [71869, 1, "\u{118DD}"], [71870, 1, "\u{118DE}"], [71871, 1, "\u{118DF}"], [[71872, 71913], 2], [[71914, 71922], 2], [[71923, 71934], 3], [71935, 2], [[71936, 71942], 2], [[71943, 71944], 3], [71945, 2], [[71946, 71947], 3], [[71948, 71955], 2], [71956, 3], [[71957, 71958], 2], [71959, 3], [[71960, 71989], 2], [71990, 3], [[71991, 71992], 2], [[71993, 71994], 3], [[71995, 72003], 2], [[72004, 72006], 2], [[72007, 72015], 3], [[72016, 72025], 2], [[72026, 72095], 3], [[72096, 72103], 2], [[72104, 72105], 3], [[72106, 72151], 2], [[72152, 72153], 3], [[72154, 72161], 2], [72162, 2], [[72163, 72164], 2], [[72165, 72191], 3], [[72192, 72254], 2], [[72255, 72262], 2], [72263, 2], [[72264, 72271], 3], [[72272, 72323], 2], [[72324, 72325], 2], [[72326, 72345], 2], [[72346, 72348], 2], [72349, 2], [[72350, 72354], 2], [[72355, 72367], 3], [[72368, 72383], 2], [[72384, 72440], 2], [[72441, 72447], 3], [[72448, 72457], 2], [[72458, 72703], 3], [[72704, 72712], 2], [72713, 3], [[72714, 72758], 2], [72759, 3], [[72760, 72768], 2], [[72769, 72773], 2], [[72774, 72783], 3], [[72784, 72793], 2], [[72794, 72812], 2], [[72813, 72815], 3], [[72816, 72817], 2], [[72818, 72847], 2], [[72848, 72849], 3], [[72850, 72871], 2], [72872, 3], [[72873, 72886], 2], [[72887, 72959], 3], [[72960, 72966], 2], [72967, 3], [[72968, 72969], 2], [72970, 3], [[72971, 73014], 2], [[73015, 73017], 3], [73018, 2], [73019, 3], [[73020, 73021], 2], [73022, 3], [[73023, 73031], 2], [[73032, 73039], 3], [[73040, 73049], 2], [[73050, 73055], 3], [[73056, 73061], 2], [73062, 3], [[73063, 73064], 2], [73065, 3], [[73066, 73102], 2], [73103, 3], [[73104, 73105], 2], [73106, 3], [[73107, 73112], 2], [[73113, 73119], 3], [[73120, 73129], 2], [[73130, 73439], 3], [[73440, 73462], 2], [[73463, 73464], 2], [[73465, 73471], 3], [[73472, 73488], 2], [73489, 3], [[73490, 73530], 2], [[73531, 73533], 3], [[73534, 73538], 2], [[73539, 73551], 2], [[73552, 73561], 2], [[73562, 73647], 3], [73648, 2], [[73649, 73663], 3], [[73664, 73713], 2], [[73714, 73726], 3], [73727, 2], [[73728, 74606], 2], [[74607, 74648], 2], [74649, 2], [[74650, 74751], 3], [[74752, 74850], 2], [[74851, 74862], 2], [74863, 3], [[74864, 74867], 2], [74868, 2], [[74869, 74879], 3], [[74880, 75075], 2], [[75076, 77711], 3], [[77712, 77808], 2], [[77809, 77810], 2], [[77811, 77823], 3], [[77824, 78894], 2], [78895, 2], [[78896, 78904], 3], [[78905, 78911], 3], [[78912, 78933], 2], [[78934, 82943], 3], [[82944, 83526], 2], [[83527, 92159], 3], [[92160, 92728], 2], [[92729, 92735], 3], [[92736, 92766], 2], [92767, 3], [[92768, 92777], 2], [[92778, 92781], 3], [[92782, 92783], 2], [[92784, 92862], 2], [92863, 3], [[92864, 92873], 2], [[92874, 92879], 3], [[92880, 92909], 2], [[92910, 92911], 3], [[92912, 92916], 2], [92917, 2], [[92918, 92927], 3], [[92928, 92982], 2], [[92983, 92991], 2], [[92992, 92995], 2], [[92996, 92997], 2], [[92998, 93007], 3], [[93008, 93017], 2], [93018, 3], [[93019, 93025], 2], [93026, 3], [[93027, 93047], 2], [[93048, 93052], 3], [[93053, 93071], 2], [[93072, 93759], 3], [93760, 1, "\u{16E60}"], [93761, 1, "\u{16E61}"], [93762, 1, "\u{16E62}"], [93763, 1, "\u{16E63}"], [93764, 1, "\u{16E64}"], [93765, 1, "\u{16E65}"], [93766, 1, "\u{16E66}"], [93767, 1, "\u{16E67}"], [93768, 1, "\u{16E68}"], [93769, 1, "\u{16E69}"], [93770, 1, "\u{16E6A}"], [93771, 1, "\u{16E6B}"], [93772, 1, "\u{16E6C}"], [93773, 1, "\u{16E6D}"], [93774, 1, "\u{16E6E}"], [93775, 1, "\u{16E6F}"], [93776, 1, "\u{16E70}"], [93777, 1, "\u{16E71}"], [93778, 1, "\u{16E72}"], [93779, 1, "\u{16E73}"], [93780, 1, "\u{16E74}"], [93781, 1, "\u{16E75}"], [93782, 1, "\u{16E76}"], [93783, 1, "\u{16E77}"], [93784, 1, "\u{16E78}"], [93785, 1, "\u{16E79}"], [93786, 1, "\u{16E7A}"], [93787, 1, "\u{16E7B}"], [93788, 1, "\u{16E7C}"], [93789, 1, "\u{16E7D}"], [93790, 1, "\u{16E7E}"], [93791, 1, "\u{16E7F}"], [[93792, 93823], 2], [[93824, 93850], 2], [[93851, 93951], 3], [[93952, 94020], 2], [[94021, 94026], 2], [[94027, 94030], 3], [94031, 2], [[94032, 94078], 2], [[94079, 94087], 2], [[94088, 94094], 3], [[94095, 94111], 2], [[94112, 94175], 3], [94176, 2], [94177, 2], [94178, 2], [94179, 2], [94180, 2], [[94181, 94191], 3], [[94192, 94193], 2], [[94194, 94207], 3], [[94208, 100332], 2], [[100333, 100337], 2], [[100338, 100343], 2], [[100344, 100351], 3], [[100352, 101106], 2], [[101107, 101589], 2], [[101590, 101631], 3], [[101632, 101640], 2], [[101641, 110575], 3], [[110576, 110579], 2], [110580, 3], [[110581, 110587], 2], [110588, 3], [[110589, 110590], 2], [110591, 3], [[110592, 110593], 2], [[110594, 110878], 2], [[110879, 110882], 2], [[110883, 110897], 3], [110898, 2], [[110899, 110927], 3], [[110928, 110930], 2], [[110931, 110932], 3], [110933, 2], [[110934, 110947], 3], [[110948, 110951], 2], [[110952, 110959], 3], [[110960, 111355], 2], [[111356, 113663], 3], [[113664, 113770], 2], [[113771, 113775], 3], [[113776, 113788], 2], [[113789, 113791], 3], [[113792, 113800], 2], [[113801, 113807], 3], [[113808, 113817], 2], [[113818, 113819], 3], [113820, 2], [[113821, 113822], 2], [113823, 2], [[113824, 113827], 7], [[113828, 118527], 3], [[118528, 118573], 2], [[118574, 118575], 3], [[118576, 118598], 2], [[118599, 118607], 3], [[118608, 118723], 2], [[118724, 118783], 3], [[118784, 119029], 2], [[119030, 119039], 3], [[119040, 119078], 2], [[119079, 119080], 3], [119081, 2], [[119082, 119133], 2], [119134, 1, "\u{1D157}\u{1D165}"], [119135, 1, "\u{1D158}\u{1D165}"], [119136, 1, "\u{1D158}\u{1D165}\u{1D16E}"], [119137, 1, "\u{1D158}\u{1D165}\u{1D16F}"], [119138, 1, "\u{1D158}\u{1D165}\u{1D170}"], [119139, 1, "\u{1D158}\u{1D165}\u{1D171}"], [119140, 1, "\u{1D158}\u{1D165}\u{1D172}"], [[119141, 119154], 2], [[119155, 119162], 3], [[119163, 119226], 2], [119227, 1, "\u{1D1B9}\u{1D165}"], [119228, 1, "\u{1D1BA}\u{1D165}"], [119229, 1, "\u{1D1B9}\u{1D165}\u{1D16E}"], [119230, 1, "\u{1D1BA}\u{1D165}\u{1D16E}"], [119231, 1, "\u{1D1B9}\u{1D165}\u{1D16F}"], [119232, 1, "\u{1D1BA}\u{1D165}\u{1D16F}"], [[119233, 119261], 2], [[119262, 119272], 2], [[119273, 119274], 2], [[119275, 119295], 3], [[119296, 119365], 2], [[119366, 119487], 3], [[119488, 119507], 2], [[119508, 119519], 3], [[119520, 119539], 2], [[119540, 119551], 3], [[119552, 119638], 2], [[119639, 119647], 3], [[119648, 119665], 2], [[119666, 119672], 2], [[119673, 119807], 3], [119808, 1, "a"], [119809, 1, "b"], [119810, 1, "c"], [119811, 1, "d"], [119812, 1, "e"], [119813, 1, "f"], [119814, 1, "g"], [119815, 1, "h"], [119816, 1, "i"], [119817, 1, "j"], [119818, 1, "k"], [119819, 1, "l"], [119820, 1, "m"], [119821, 1, "n"], [119822, 1, "o"], [119823, 1, "p"], [119824, 1, "q"], [119825, 1, "r"], [119826, 1, "s"], [119827, 1, "t"], [119828, 1, "u"], [119829, 1, "v"], [119830, 1, "w"], [119831, 1, "x"], [119832, 1, "y"], [119833, 1, "z"], [119834, 1, "a"], [119835, 1, "b"], [119836, 1, "c"], [119837, 1, "d"], [119838, 1, "e"], [119839, 1, "f"], [119840, 1, "g"], [119841, 1, "h"], [119842, 1, "i"], [119843, 1, "j"], [119844, 1, "k"], [119845, 1, "l"], [119846, 1, "m"], [119847, 1, "n"], [119848, 1, "o"], [119849, 1, "p"], [119850, 1, "q"], [119851, 1, "r"], [119852, 1, "s"], [119853, 1, "t"], [119854, 1, "u"], [119855, 1, "v"], [119856, 1, "w"], [119857, 1, "x"], [119858, 1, "y"], [119859, 1, "z"], [119860, 1, "a"], [119861, 1, "b"], [119862, 1, "c"], [119863, 1, "d"], [119864, 1, "e"], [119865, 1, "f"], [119866, 1, "g"], [119867, 1, "h"], [119868, 1, "i"], [119869, 1, "j"], [119870, 1, "k"], [119871, 1, "l"], [119872, 1, "m"], [119873, 1, "n"], [119874, 1, "o"], [119875, 1, "p"], [119876, 1, "q"], [119877, 1, "r"], [119878, 1, "s"], [119879, 1, "t"], [119880, 1, "u"], [119881, 1, "v"], [119882, 1, "w"], [119883, 1, "x"], [119884, 1, "y"], [119885, 1, "z"], [119886, 1, "a"], [119887, 1, "b"], [119888, 1, "c"], [119889, 1, "d"], [119890, 1, "e"], [119891, 1, "f"], [119892, 1, "g"], [119893, 3], [119894, 1, "i"], [119895, 1, "j"], [119896, 1, "k"], [119897, 1, "l"], [119898, 1, "m"], [119899, 1, "n"], [119900, 1, "o"], [119901, 1, "p"], [119902, 1, "q"], [119903, 1, "r"], [119904, 1, "s"], [119905, 1, "t"], [119906, 1, "u"], [119907, 1, "v"], [119908, 1, "w"], [119909, 1, "x"], [119910, 1, "y"], [119911, 1, "z"], [119912, 1, "a"], [119913, 1, "b"], [119914, 1, "c"], [119915, 1, "d"], [119916, 1, "e"], [119917, 1, "f"], [119918, 1, "g"], [119919, 1, "h"], [119920, 1, "i"], [119921, 1, "j"], [119922, 1, "k"], [119923, 1, "l"], [119924, 1, "m"], [119925, 1, "n"], [119926, 1, "o"], [119927, 1, "p"], [119928, 1, "q"], [119929, 1, "r"], [119930, 1, "s"], [119931, 1, "t"], [119932, 1, "u"], [119933, 1, "v"], [119934, 1, "w"], [119935, 1, "x"], [119936, 1, "y"], [119937, 1, "z"], [119938, 1, "a"], [119939, 1, "b"], [119940, 1, "c"], [119941, 1, "d"], [119942, 1, "e"], [119943, 1, "f"], [119944, 1, "g"], [119945, 1, "h"], [119946, 1, "i"], [119947, 1, "j"], [119948, 1, "k"], [119949, 1, "l"], [119950, 1, "m"], [119951, 1, "n"], [119952, 1, "o"], [119953, 1, "p"], [119954, 1, "q"], [119955, 1, "r"], [119956, 1, "s"], [119957, 1, "t"], [119958, 1, "u"], [119959, 1, "v"], [119960, 1, "w"], [119961, 1, "x"], [119962, 1, "y"], [119963, 1, "z"], [119964, 1, "a"], [119965, 3], [119966, 1, "c"], [119967, 1, "d"], [[119968, 119969], 3], [119970, 1, "g"], [[119971, 119972], 3], [119973, 1, "j"], [119974, 1, "k"], [[119975, 119976], 3], [119977, 1, "n"], [119978, 1, "o"], [119979, 1, "p"], [119980, 1, "q"], [119981, 3], [119982, 1, "s"], [119983, 1, "t"], [119984, 1, "u"], [119985, 1, "v"], [119986, 1, "w"], [119987, 1, "x"], [119988, 1, "y"], [119989, 1, "z"], [119990, 1, "a"], [119991, 1, "b"], [119992, 1, "c"], [119993, 1, "d"], [119994, 3], [119995, 1, "f"], [119996, 3], [119997, 1, "h"], [119998, 1, "i"], [119999, 1, "j"], [12e4, 1, "k"], [120001, 1, "l"], [120002, 1, "m"], [120003, 1, "n"], [120004, 3], [120005, 1, "p"], [120006, 1, "q"], [120007, 1, "r"], [120008, 1, "s"], [120009, 1, "t"], [120010, 1, "u"], [120011, 1, "v"], [120012, 1, "w"], [120013, 1, "x"], [120014, 1, "y"], [120015, 1, "z"], [120016, 1, "a"], [120017, 1, "b"], [120018, 1, "c"], [120019, 1, "d"], [120020, 1, "e"], [120021, 1, "f"], [120022, 1, "g"], [120023, 1, "h"], [120024, 1, "i"], [120025, 1, "j"], [120026, 1, "k"], [120027, 1, "l"], [120028, 1, "m"], [120029, 1, "n"], [120030, 1, "o"], [120031, 1, "p"], [120032, 1, "q"], [120033, 1, "r"], [120034, 1, "s"], [120035, 1, "t"], [120036, 1, "u"], [120037, 1, "v"], [120038, 1, "w"], [120039, 1, "x"], [120040, 1, "y"], [120041, 1, "z"], [120042, 1, "a"], [120043, 1, "b"], [120044, 1, "c"], [120045, 1, "d"], [120046, 1, "e"], [120047, 1, "f"], [120048, 1, "g"], [120049, 1, "h"], [120050, 1, "i"], [120051, 1, "j"], [120052, 1, "k"], [120053, 1, "l"], [120054, 1, "m"], [120055, 1, "n"], [120056, 1, "o"], [120057, 1, "p"], [120058, 1, "q"], [120059, 1, "r"], [120060, 1, "s"], [120061, 1, "t"], [120062, 1, "u"], [120063, 1, "v"], [120064, 1, "w"], [120065, 1, "x"], [120066, 1, "y"], [120067, 1, "z"], [120068, 1, "a"], [120069, 1, "b"], [120070, 3], [120071, 1, "d"], [120072, 1, "e"], [120073, 1, "f"], [120074, 1, "g"], [[120075, 120076], 3], [120077, 1, "j"], [120078, 1, "k"], [120079, 1, "l"], [120080, 1, "m"], [120081, 1, "n"], [120082, 1, "o"], [120083, 1, "p"], [120084, 1, "q"], [120085, 3], [120086, 1, "s"], [120087, 1, "t"], [120088, 1, "u"], [120089, 1, "v"], [120090, 1, "w"], [120091, 1, "x"], [120092, 1, "y"], [120093, 3], [120094, 1, "a"], [120095, 1, "b"], [120096, 1, "c"], [120097, 1, "d"], [120098, 1, "e"], [120099, 1, "f"], [120100, 1, "g"], [120101, 1, "h"], [120102, 1, "i"], [120103, 1, "j"], [120104, 1, "k"], [120105, 1, "l"], [120106, 1, "m"], [120107, 1, "n"], [120108, 1, "o"], [120109, 1, "p"], [120110, 1, "q"], [120111, 1, "r"], [120112, 1, "s"], [120113, 1, "t"], [120114, 1, "u"], [120115, 1, "v"], [120116, 1, "w"], [120117, 1, "x"], [120118, 1, "y"], [120119, 1, "z"], [120120, 1, "a"], [120121, 1, "b"], [120122, 3], [120123, 1, "d"], [120124, 1, "e"], [120125, 1, "f"], [120126, 1, "g"], [120127, 3], [120128, 1, "i"], [120129, 1, "j"], [120130, 1, "k"], [120131, 1, "l"], [120132, 1, "m"], [120133, 3], [120134, 1, "o"], [[120135, 120137], 3], [120138, 1, "s"], [120139, 1, "t"], [120140, 1, "u"], [120141, 1, "v"], [120142, 1, "w"], [120143, 1, "x"], [120144, 1, "y"], [120145, 3], [120146, 1, "a"], [120147, 1, "b"], [120148, 1, "c"], [120149, 1, "d"], [120150, 1, "e"], [120151, 1, "f"], [120152, 1, "g"], [120153, 1, "h"], [120154, 1, "i"], [120155, 1, "j"], [120156, 1, "k"], [120157, 1, "l"], [120158, 1, "m"], [120159, 1, "n"], [120160, 1, "o"], [120161, 1, "p"], [120162, 1, "q"], [120163, 1, "r"], [120164, 1, "s"], [120165, 1, "t"], [120166, 1, "u"], [120167, 1, "v"], [120168, 1, "w"], [120169, 1, "x"], [120170, 1, "y"], [120171, 1, "z"], [120172, 1, "a"], [120173, 1, "b"], [120174, 1, "c"], [120175, 1, "d"], [120176, 1, "e"], [120177, 1, "f"], [120178, 1, "g"], [120179, 1, "h"], [120180, 1, "i"], [120181, 1, "j"], [120182, 1, "k"], [120183, 1, "l"], [120184, 1, "m"], [120185, 1, "n"], [120186, 1, "o"], [120187, 1, "p"], [120188, 1, "q"], [120189, 1, "r"], [120190, 1, "s"], [120191, 1, "t"], [120192, 1, "u"], [120193, 1, "v"], [120194, 1, "w"], [120195, 1, "x"], [120196, 1, "y"], [120197, 1, "z"], [120198, 1, "a"], [120199, 1, "b"], [120200, 1, "c"], [120201, 1, "d"], [120202, 1, "e"], [120203, 1, "f"], [120204, 1, "g"], [120205, 1, "h"], [120206, 1, "i"], [120207, 1, "j"], [120208, 1, "k"], [120209, 1, "l"], [120210, 1, "m"], [120211, 1, "n"], [120212, 1, "o"], [120213, 1, "p"], [120214, 1, "q"], [120215, 1, "r"], [120216, 1, "s"], [120217, 1, "t"], [120218, 1, "u"], [120219, 1, "v"], [120220, 1, "w"], [120221, 1, "x"], [120222, 1, "y"], [120223, 1, "z"], [120224, 1, "a"], [120225, 1, "b"], [120226, 1, "c"], [120227, 1, "d"], [120228, 1, "e"], [120229, 1, "f"], [120230, 1, "g"], [120231, 1, "h"], [120232, 1, "i"], [120233, 1, "j"], [120234, 1, "k"], [120235, 1, "l"], [120236, 1, "m"], [120237, 1, "n"], [120238, 1, "o"], [120239, 1, "p"], [120240, 1, "q"], [120241, 1, "r"], [120242, 1, "s"], [120243, 1, "t"], [120244, 1, "u"], [120245, 1, "v"], [120246, 1, "w"], [120247, 1, "x"], [120248, 1, "y"], [120249, 1, "z"], [120250, 1, "a"], [120251, 1, "b"], [120252, 1, "c"], [120253, 1, "d"], [120254, 1, "e"], [120255, 1, "f"], [120256, 1, "g"], [120257, 1, "h"], [120258, 1, "i"], [120259, 1, "j"], [120260, 1, "k"], [120261, 1, "l"], [120262, 1, "m"], [120263, 1, "n"], [120264, 1, "o"], [120265, 1, "p"], [120266, 1, "q"], [120267, 1, "r"], [120268, 1, "s"], [120269, 1, "t"], [120270, 1, "u"], [120271, 1, "v"], [120272, 1, "w"], [120273, 1, "x"], [120274, 1, "y"], [120275, 1, "z"], [120276, 1, "a"], [120277, 1, "b"], [120278, 1, "c"], [120279, 1, "d"], [120280, 1, "e"], [120281, 1, "f"], [120282, 1, "g"], [120283, 1, "h"], [120284, 1, "i"], [120285, 1, "j"], [120286, 1, "k"], [120287, 1, "l"], [120288, 1, "m"], [120289, 1, "n"], [120290, 1, "o"], [120291, 1, "p"], [120292, 1, "q"], [120293, 1, "r"], [120294, 1, "s"], [120295, 1, "t"], [120296, 1, "u"], [120297, 1, "v"], [120298, 1, "w"], [120299, 1, "x"], [120300, 1, "y"], [120301, 1, "z"], [120302, 1, "a"], [120303, 1, "b"], [120304, 1, "c"], [120305, 1, "d"], [120306, 1, "e"], [120307, 1, "f"], [120308, 1, "g"], [120309, 1, "h"], [120310, 1, "i"], [120311, 1, "j"], [120312, 1, "k"], [120313, 1, "l"], [120314, 1, "m"], [120315, 1, "n"], [120316, 1, "o"], [120317, 1, "p"], [120318, 1, "q"], [120319, 1, "r"], [120320, 1, "s"], [120321, 1, "t"], [120322, 1, "u"], [120323, 1, "v"], [120324, 1, "w"], [120325, 1, "x"], [120326, 1, "y"], [120327, 1, "z"], [120328, 1, "a"], [120329, 1, "b"], [120330, 1, "c"], [120331, 1, "d"], [120332, 1, "e"], [120333, 1, "f"], [120334, 1, "g"], [120335, 1, "h"], [120336, 1, "i"], [120337, 1, "j"], [120338, 1, "k"], [120339, 1, "l"], [120340, 1, "m"], [120341, 1, "n"], [120342, 1, "o"], [120343, 1, "p"], [120344, 1, "q"], [120345, 1, "r"], [120346, 1, "s"], [120347, 1, "t"], [120348, 1, "u"], [120349, 1, "v"], [120350, 1, "w"], [120351, 1, "x"], [120352, 1, "y"], [120353, 1, "z"], [120354, 1, "a"], [120355, 1, "b"], [120356, 1, "c"], [120357, 1, "d"], [120358, 1, "e"], [120359, 1, "f"], [120360, 1, "g"], [120361, 1, "h"], [120362, 1, "i"], [120363, 1, "j"], [120364, 1, "k"], [120365, 1, "l"], [120366, 1, "m"], [120367, 1, "n"], [120368, 1, "o"], [120369, 1, "p"], [120370, 1, "q"], [120371, 1, "r"], [120372, 1, "s"], [120373, 1, "t"], [120374, 1, "u"], [120375, 1, "v"], [120376, 1, "w"], [120377, 1, "x"], [120378, 1, "y"], [120379, 1, "z"], [120380, 1, "a"], [120381, 1, "b"], [120382, 1, "c"], [120383, 1, "d"], [120384, 1, "e"], [120385, 1, "f"], [120386, 1, "g"], [120387, 1, "h"], [120388, 1, "i"], [120389, 1, "j"], [120390, 1, "k"], [120391, 1, "l"], [120392, 1, "m"], [120393, 1, "n"], [120394, 1, "o"], [120395, 1, "p"], [120396, 1, "q"], [120397, 1, "r"], [120398, 1, "s"], [120399, 1, "t"], [120400, 1, "u"], [120401, 1, "v"], [120402, 1, "w"], [120403, 1, "x"], [120404, 1, "y"], [120405, 1, "z"], [120406, 1, "a"], [120407, 1, "b"], [120408, 1, "c"], [120409, 1, "d"], [120410, 1, "e"], [120411, 1, "f"], [120412, 1, "g"], [120413, 1, "h"], [120414, 1, "i"], [120415, 1, "j"], [120416, 1, "k"], [120417, 1, "l"], [120418, 1, "m"], [120419, 1, "n"], [120420, 1, "o"], [120421, 1, "p"], [120422, 1, "q"], [120423, 1, "r"], [120424, 1, "s"], [120425, 1, "t"], [120426, 1, "u"], [120427, 1, "v"], [120428, 1, "w"], [120429, 1, "x"], [120430, 1, "y"], [120431, 1, "z"], [120432, 1, "a"], [120433, 1, "b"], [120434, 1, "c"], [120435, 1, "d"], [120436, 1, "e"], [120437, 1, "f"], [120438, 1, "g"], [120439, 1, "h"], [120440, 1, "i"], [120441, 1, "j"], [120442, 1, "k"], [120443, 1, "l"], [120444, 1, "m"], [120445, 1, "n"], [120446, 1, "o"], [120447, 1, "p"], [120448, 1, "q"], [120449, 1, "r"], [120450, 1, "s"], [120451, 1, "t"], [120452, 1, "u"], [120453, 1, "v"], [120454, 1, "w"], [120455, 1, "x"], [120456, 1, "y"], [120457, 1, "z"], [120458, 1, "a"], [120459, 1, "b"], [120460, 1, "c"], [120461, 1, "d"], [120462, 1, "e"], [120463, 1, "f"], [120464, 1, "g"], [120465, 1, "h"], [120466, 1, "i"], [120467, 1, "j"], [120468, 1, "k"], [120469, 1, "l"], [120470, 1, "m"], [120471, 1, "n"], [120472, 1, "o"], [120473, 1, "p"], [120474, 1, "q"], [120475, 1, "r"], [120476, 1, "s"], [120477, 1, "t"], [120478, 1, "u"], [120479, 1, "v"], [120480, 1, "w"], [120481, 1, "x"], [120482, 1, "y"], [120483, 1, "z"], [120484, 1, "\u0131"], [120485, 1, "\u0237"], [[120486, 120487], 3], [120488, 1, "\u03B1"], [120489, 1, "\u03B2"], [120490, 1, "\u03B3"], [120491, 1, "\u03B4"], [120492, 1, "\u03B5"], [120493, 1, "\u03B6"], [120494, 1, "\u03B7"], [120495, 1, "\u03B8"], [120496, 1, "\u03B9"], [120497, 1, "\u03BA"], [120498, 1, "\u03BB"], [120499, 1, "\u03BC"], [120500, 1, "\u03BD"], [120501, 1, "\u03BE"], [120502, 1, "\u03BF"], [120503, 1, "\u03C0"], [120504, 1, "\u03C1"], [120505, 1, "\u03B8"], [120506, 1, "\u03C3"], [120507, 1, "\u03C4"], [120508, 1, "\u03C5"], [120509, 1, "\u03C6"], [120510, 1, "\u03C7"], [120511, 1, "\u03C8"], [120512, 1, "\u03C9"], [120513, 1, "\u2207"], [120514, 1, "\u03B1"], [120515, 1, "\u03B2"], [120516, 1, "\u03B3"], [120517, 1, "\u03B4"], [120518, 1, "\u03B5"], [120519, 1, "\u03B6"], [120520, 1, "\u03B7"], [120521, 1, "\u03B8"], [120522, 1, "\u03B9"], [120523, 1, "\u03BA"], [120524, 1, "\u03BB"], [120525, 1, "\u03BC"], [120526, 1, "\u03BD"], [120527, 1, "\u03BE"], [120528, 1, "\u03BF"], [120529, 1, "\u03C0"], [120530, 1, "\u03C1"], [[120531, 120532], 1, "\u03C3"], [120533, 1, "\u03C4"], [120534, 1, "\u03C5"], [120535, 1, "\u03C6"], [120536, 1, "\u03C7"], [120537, 1, "\u03C8"], [120538, 1, "\u03C9"], [120539, 1, "\u2202"], [120540, 1, "\u03B5"], [120541, 1, "\u03B8"], [120542, 1, "\u03BA"], [120543, 1, "\u03C6"], [120544, 1, "\u03C1"], [120545, 1, "\u03C0"], [120546, 1, "\u03B1"], [120547, 1, "\u03B2"], [120548, 1, "\u03B3"], [120549, 1, "\u03B4"], [120550, 1, "\u03B5"], [120551, 1, "\u03B6"], [120552, 1, "\u03B7"], [120553, 1, "\u03B8"], [120554, 1, "\u03B9"], [120555, 1, "\u03BA"], [120556, 1, "\u03BB"], [120557, 1, "\u03BC"], [120558, 1, "\u03BD"], [120559, 1, "\u03BE"], [120560, 1, "\u03BF"], [120561, 1, "\u03C0"], [120562, 1, "\u03C1"], [120563, 1, "\u03B8"], [120564, 1, "\u03C3"], [120565, 1, "\u03C4"], [120566, 1, "\u03C5"], [120567, 1, "\u03C6"], [120568, 1, "\u03C7"], [120569, 1, "\u03C8"], [120570, 1, "\u03C9"], [120571, 1, "\u2207"], [120572, 1, "\u03B1"], [120573, 1, "\u03B2"], [120574, 1, "\u03B3"], [120575, 1, "\u03B4"], [120576, 1, "\u03B5"], [120577, 1, "\u03B6"], [120578, 1, "\u03B7"], [120579, 1, "\u03B8"], [120580, 1, "\u03B9"], [120581, 1, "\u03BA"], [120582, 1, "\u03BB"], [120583, 1, "\u03BC"], [120584, 1, "\u03BD"], [120585, 1, "\u03BE"], [120586, 1, "\u03BF"], [120587, 1, "\u03C0"], [120588, 1, "\u03C1"], [[120589, 120590], 1, "\u03C3"], [120591, 1, "\u03C4"], [120592, 1, "\u03C5"], [120593, 1, "\u03C6"], [120594, 1, "\u03C7"], [120595, 1, "\u03C8"], [120596, 1, "\u03C9"], [120597, 1, "\u2202"], [120598, 1, "\u03B5"], [120599, 1, "\u03B8"], [120600, 1, "\u03BA"], [120601, 1, "\u03C6"], [120602, 1, "\u03C1"], [120603, 1, "\u03C0"], [120604, 1, "\u03B1"], [120605, 1, "\u03B2"], [120606, 1, "\u03B3"], [120607, 1, "\u03B4"], [120608, 1, "\u03B5"], [120609, 1, "\u03B6"], [120610, 1, "\u03B7"], [120611, 1, "\u03B8"], [120612, 1, "\u03B9"], [120613, 1, "\u03BA"], [120614, 1, "\u03BB"], [120615, 1, "\u03BC"], [120616, 1, "\u03BD"], [120617, 1, "\u03BE"], [120618, 1, "\u03BF"], [120619, 1, "\u03C0"], [120620, 1, "\u03C1"], [120621, 1, "\u03B8"], [120622, 1, "\u03C3"], [120623, 1, "\u03C4"], [120624, 1, "\u03C5"], [120625, 1, "\u03C6"], [120626, 1, "\u03C7"], [120627, 1, "\u03C8"], [120628, 1, "\u03C9"], [120629, 1, "\u2207"], [120630, 1, "\u03B1"], [120631, 1, "\u03B2"], [120632, 1, "\u03B3"], [120633, 1, "\u03B4"], [120634, 1, "\u03B5"], [120635, 1, "\u03B6"], [120636, 1, "\u03B7"], [120637, 1, "\u03B8"], [120638, 1, "\u03B9"], [120639, 1, "\u03BA"], [120640, 1, "\u03BB"], [120641, 1, "\u03BC"], [120642, 1, "\u03BD"], [120643, 1, "\u03BE"], [120644, 1, "\u03BF"], [120645, 1, "\u03C0"], [120646, 1, "\u03C1"], [[120647, 120648], 1, "\u03C3"], [120649, 1, "\u03C4"], [120650, 1, "\u03C5"], [120651, 1, "\u03C6"], [120652, 1, "\u03C7"], [120653, 1, "\u03C8"], [120654, 1, "\u03C9"], [120655, 1, "\u2202"], [120656, 1, "\u03B5"], [120657, 1, "\u03B8"], [120658, 1, "\u03BA"], [120659, 1, "\u03C6"], [120660, 1, "\u03C1"], [120661, 1, "\u03C0"], [120662, 1, "\u03B1"], [120663, 1, "\u03B2"], [120664, 1, "\u03B3"], [120665, 1, "\u03B4"], [120666, 1, "\u03B5"], [120667, 1, "\u03B6"], [120668, 1, "\u03B7"], [120669, 1, "\u03B8"], [120670, 1, "\u03B9"], [120671, 1, "\u03BA"], [120672, 1, "\u03BB"], [120673, 1, "\u03BC"], [120674, 1, "\u03BD"], [120675, 1, "\u03BE"], [120676, 1, "\u03BF"], [120677, 1, "\u03C0"], [120678, 1, "\u03C1"], [120679, 1, "\u03B8"], [120680, 1, "\u03C3"], [120681, 1, "\u03C4"], [120682, 1, "\u03C5"], [120683, 1, "\u03C6"], [120684, 1, "\u03C7"], [120685, 1, "\u03C8"], [120686, 1, "\u03C9"], [120687, 1, "\u2207"], [120688, 1, "\u03B1"], [120689, 1, "\u03B2"], [120690, 1, "\u03B3"], [120691, 1, "\u03B4"], [120692, 1, "\u03B5"], [120693, 1, "\u03B6"], [120694, 1, "\u03B7"], [120695, 1, "\u03B8"], [120696, 1, "\u03B9"], [120697, 1, "\u03BA"], [120698, 1, "\u03BB"], [120699, 1, "\u03BC"], [120700, 1, "\u03BD"], [120701, 1, "\u03BE"], [120702, 1, "\u03BF"], [120703, 1, "\u03C0"], [120704, 1, "\u03C1"], [[120705, 120706], 1, "\u03C3"], [120707, 1, "\u03C4"], [120708, 1, "\u03C5"], [120709, 1, "\u03C6"], [120710, 1, "\u03C7"], [120711, 1, "\u03C8"], [120712, 1, "\u03C9"], [120713, 1, "\u2202"], [120714, 1, "\u03B5"], [120715, 1, "\u03B8"], [120716, 1, "\u03BA"], [120717, 1, "\u03C6"], [120718, 1, "\u03C1"], [120719, 1, "\u03C0"], [120720, 1, "\u03B1"], [120721, 1, "\u03B2"], [120722, 1, "\u03B3"], [120723, 1, "\u03B4"], [120724, 1, "\u03B5"], [120725, 1, "\u03B6"], [120726, 1, "\u03B7"], [120727, 1, "\u03B8"], [120728, 1, "\u03B9"], [120729, 1, "\u03BA"], [120730, 1, "\u03BB"], [120731, 1, "\u03BC"], [120732, 1, "\u03BD"], [120733, 1, "\u03BE"], [120734, 1, "\u03BF"], [120735, 1, "\u03C0"], [120736, 1, "\u03C1"], [120737, 1, "\u03B8"], [120738, 1, "\u03C3"], [120739, 1, "\u03C4"], [120740, 1, "\u03C5"], [120741, 1, "\u03C6"], [120742, 1, "\u03C7"], [120743, 1, "\u03C8"], [120744, 1, "\u03C9"], [120745, 1, "\u2207"], [120746, 1, "\u03B1"], [120747, 1, "\u03B2"], [120748, 1, "\u03B3"], [120749, 1, "\u03B4"], [120750, 1, "\u03B5"], [120751, 1, "\u03B6"], [120752, 1, "\u03B7"], [120753, 1, "\u03B8"], [120754, 1, "\u03B9"], [120755, 1, "\u03BA"], [120756, 1, "\u03BB"], [120757, 1, "\u03BC"], [120758, 1, "\u03BD"], [120759, 1, "\u03BE"], [120760, 1, "\u03BF"], [120761, 1, "\u03C0"], [120762, 1, "\u03C1"], [[120763, 120764], 1, "\u03C3"], [120765, 1, "\u03C4"], [120766, 1, "\u03C5"], [120767, 1, "\u03C6"], [120768, 1, "\u03C7"], [120769, 1, "\u03C8"], [120770, 1, "\u03C9"], [120771, 1, "\u2202"], [120772, 1, "\u03B5"], [120773, 1, "\u03B8"], [120774, 1, "\u03BA"], [120775, 1, "\u03C6"], [120776, 1, "\u03C1"], [120777, 1, "\u03C0"], [[120778, 120779], 1, "\u03DD"], [[120780, 120781], 3], [120782, 1, "0"], [120783, 1, "1"], [120784, 1, "2"], [120785, 1, "3"], [120786, 1, "4"], [120787, 1, "5"], [120788, 1, "6"], [120789, 1, "7"], [120790, 1, "8"], [120791, 1, "9"], [120792, 1, "0"], [120793, 1, "1"], [120794, 1, "2"], [120795, 1, "3"], [120796, 1, "4"], [120797, 1, "5"], [120798, 1, "6"], [120799, 1, "7"], [120800, 1, "8"], [120801, 1, "9"], [120802, 1, "0"], [120803, 1, "1"], [120804, 1, "2"], [120805, 1, "3"], [120806, 1, "4"], [120807, 1, "5"], [120808, 1, "6"], [120809, 1, "7"], [120810, 1, "8"], [120811, 1, "9"], [120812, 1, "0"], [120813, 1, "1"], [120814, 1, "2"], [120815, 1, "3"], [120816, 1, "4"], [120817, 1, "5"], [120818, 1, "6"], [120819, 1, "7"], [120820, 1, "8"], [120821, 1, "9"], [120822, 1, "0"], [120823, 1, "1"], [120824, 1, "2"], [120825, 1, "3"], [120826, 1, "4"], [120827, 1, "5"], [120828, 1, "6"], [120829, 1, "7"], [120830, 1, "8"], [120831, 1, "9"], [[120832, 121343], 2], [[121344, 121398], 2], [[121399, 121402], 2], [[121403, 121452], 2], [[121453, 121460], 2], [121461, 2], [[121462, 121475], 2], [121476, 2], [[121477, 121483], 2], [[121484, 121498], 3], [[121499, 121503], 2], [121504, 3], [[121505, 121519], 2], [[121520, 122623], 3], [[122624, 122654], 2], [[122655, 122660], 3], [[122661, 122666], 2], [[122667, 122879], 3], [[122880, 122886], 2], [122887, 3], [[122888, 122904], 2], [[122905, 122906], 3], [[122907, 122913], 2], [122914, 3], [[122915, 122916], 2], [122917, 3], [[122918, 122922], 2], [[122923, 122927], 3], [122928, 1, "\u0430"], [122929, 1, "\u0431"], [122930, 1, "\u0432"], [122931, 1, "\u0433"], [122932, 1, "\u0434"], [122933, 1, "\u0435"], [122934, 1, "\u0436"], [122935, 1, "\u0437"], [122936, 1, "\u0438"], [122937, 1, "\u043A"], [122938, 1, "\u043B"], [122939, 1, "\u043C"], [122940, 1, "\u043E"], [122941, 1, "\u043F"], [122942, 1, "\u0440"], [122943, 1, "\u0441"], [122944, 1, "\u0442"], [122945, 1, "\u0443"], [122946, 1, "\u0444"], [122947, 1, "\u0445"], [122948, 1, "\u0446"], [122949, 1, "\u0447"], [122950, 1, "\u0448"], [122951, 1, "\u044B"], [122952, 1, "\u044D"], [122953, 1, "\u044E"], [122954, 1, "\uA689"], [122955, 1, "\u04D9"], [122956, 1, "\u0456"], [122957, 1, "\u0458"], [122958, 1, "\u04E9"], [122959, 1, "\u04AF"], [122960, 1, "\u04CF"], [122961, 1, "\u0430"], [122962, 1, "\u0431"], [122963, 1, "\u0432"], [122964, 1, "\u0433"], [122965, 1, "\u0434"], [122966, 1, "\u0435"], [122967, 1, "\u0436"], [122968, 1, "\u0437"], [122969, 1, "\u0438"], [122970, 1, "\u043A"], [122971, 1, "\u043B"], [122972, 1, "\u043E"], [122973, 1, "\u043F"], [122974, 1, "\u0441"], [122975, 1, "\u0443"], [122976, 1, "\u0444"], [122977, 1, "\u0445"], [122978, 1, "\u0446"], [122979, 1, "\u0447"], [122980, 1, "\u0448"], [122981, 1, "\u044A"], [122982, 1, "\u044B"], [122983, 1, "\u0491"], [122984, 1, "\u0456"], [122985, 1, "\u0455"], [122986, 1, "\u045F"], [122987, 1, "\u04AB"], [122988, 1, "\uA651"], [122989, 1, "\u04B1"], [[122990, 123022], 3], [123023, 2], [[123024, 123135], 3], [[123136, 123180], 2], [[123181, 123183], 3], [[123184, 123197], 2], [[123198, 123199], 3], [[123200, 123209], 2], [[123210, 123213], 3], [123214, 2], [123215, 2], [[123216, 123535], 3], [[123536, 123566], 2], [[123567, 123583], 3], [[123584, 123641], 2], [[123642, 123646], 3], [123647, 2], [[123648, 124111], 3], [[124112, 124153], 2], [[124154, 124895], 3], [[124896, 124902], 2], [124903, 3], [[124904, 124907], 2], [124908, 3], [[124909, 124910], 2], [124911, 3], [[124912, 124926], 2], [124927, 3], [[124928, 125124], 2], [[125125, 125126], 3], [[125127, 125135], 2], [[125136, 125142], 2], [[125143, 125183], 3], [125184, 1, "\u{1E922}"], [125185, 1, "\u{1E923}"], [125186, 1, "\u{1E924}"], [125187, 1, "\u{1E925}"], [125188, 1, "\u{1E926}"], [125189, 1, "\u{1E927}"], [125190, 1, "\u{1E928}"], [125191, 1, "\u{1E929}"], [125192, 1, "\u{1E92A}"], [125193, 1, "\u{1E92B}"], [125194, 1, "\u{1E92C}"], [125195, 1, "\u{1E92D}"], [125196, 1, "\u{1E92E}"], [125197, 1, "\u{1E92F}"], [125198, 1, "\u{1E930}"], [125199, 1, "\u{1E931}"], [125200, 1, "\u{1E932}"], [125201, 1, "\u{1E933}"], [125202, 1, "\u{1E934}"], [125203, 1, "\u{1E935}"], [125204, 1, "\u{1E936}"], [125205, 1, "\u{1E937}"], [125206, 1, "\u{1E938}"], [125207, 1, "\u{1E939}"], [125208, 1, "\u{1E93A}"], [125209, 1, "\u{1E93B}"], [125210, 1, "\u{1E93C}"], [125211, 1, "\u{1E93D}"], [125212, 1, "\u{1E93E}"], [125213, 1, "\u{1E93F}"], [125214, 1, "\u{1E940}"], [125215, 1, "\u{1E941}"], [125216, 1, "\u{1E942}"], [125217, 1, "\u{1E943}"], [[125218, 125258], 2], [125259, 2], [[125260, 125263], 3], [[125264, 125273], 2], [[125274, 125277], 3], [[125278, 125279], 2], [[125280, 126064], 3], [[126065, 126132], 2], [[126133, 126208], 3], [[126209, 126269], 2], [[126270, 126463], 3], [126464, 1, "\u0627"], [126465, 1, "\u0628"], [126466, 1, "\u062C"], [126467, 1, "\u062F"], [126468, 3], [126469, 1, "\u0648"], [126470, 1, "\u0632"], [126471, 1, "\u062D"], [126472, 1, "\u0637"], [126473, 1, "\u064A"], [126474, 1, "\u0643"], [126475, 1, "\u0644"], [126476, 1, "\u0645"], [126477, 1, "\u0646"], [126478, 1, "\u0633"], [126479, 1, "\u0639"], [126480, 1, "\u0641"], [126481, 1, "\u0635"], [126482, 1, "\u0642"], [126483, 1, "\u0631"], [126484, 1, "\u0634"], [126485, 1, "\u062A"], [126486, 1, "\u062B"], [126487, 1, "\u062E"], [126488, 1, "\u0630"], [126489, 1, "\u0636"], [126490, 1, "\u0638"], [126491, 1, "\u063A"], [126492, 1, "\u066E"], [126493, 1, "\u06BA"], [126494, 1, "\u06A1"], [126495, 1, "\u066F"], [126496, 3], [126497, 1, "\u0628"], [126498, 1, "\u062C"], [126499, 3], [126500, 1, "\u0647"], [[126501, 126502], 3], [126503, 1, "\u062D"], [126504, 3], [126505, 1, "\u064A"], [126506, 1, "\u0643"], [126507, 1, "\u0644"], [126508, 1, "\u0645"], [126509, 1, "\u0646"], [126510, 1, "\u0633"], [126511, 1, "\u0639"], [126512, 1, "\u0641"], [126513, 1, "\u0635"], [126514, 1, "\u0642"], [126515, 3], [126516, 1, "\u0634"], [126517, 1, "\u062A"], [126518, 1, "\u062B"], [126519, 1, "\u062E"], [126520, 3], [126521, 1, "\u0636"], [126522, 3], [126523, 1, "\u063A"], [[126524, 126529], 3], [126530, 1, "\u062C"], [[126531, 126534], 3], [126535, 1, "\u062D"], [126536, 3], [126537, 1, "\u064A"], [126538, 3], [126539, 1, "\u0644"], [126540, 3], [126541, 1, "\u0646"], [126542, 1, "\u0633"], [126543, 1, "\u0639"], [126544, 3], [126545, 1, "\u0635"], [126546, 1, "\u0642"], [126547, 3], [126548, 1, "\u0634"], [[126549, 126550], 3], [126551, 1, "\u062E"], [126552, 3], [126553, 1, "\u0636"], [126554, 3], [126555, 1, "\u063A"], [126556, 3], [126557, 1, "\u06BA"], [126558, 3], [126559, 1, "\u066F"], [126560, 3], [126561, 1, "\u0628"], [126562, 1, "\u062C"], [126563, 3], [126564, 1, "\u0647"], [[126565, 126566], 3], [126567, 1, "\u062D"], [126568, 1, "\u0637"], [126569, 1, "\u064A"], [126570, 1, "\u0643"], [126571, 3], [126572, 1, "\u0645"], [126573, 1, "\u0646"], [126574, 1, "\u0633"], [126575, 1, "\u0639"], [126576, 1, "\u0641"], [126577, 1, "\u0635"], [126578, 1, "\u0642"], [126579, 3], [126580, 1, "\u0634"], [126581, 1, "\u062A"], [126582, 1, "\u062B"], [126583, 1, "\u062E"], [126584, 3], [126585, 1, "\u0636"], [126586, 1, "\u0638"], [126587, 1, "\u063A"], [126588, 1, "\u066E"], [126589, 3], [126590, 1, "\u06A1"], [126591, 3], [126592, 1, "\u0627"], [126593, 1, "\u0628"], [126594, 1, "\u062C"], [126595, 1, "\u062F"], [126596, 1, "\u0647"], [126597, 1, "\u0648"], [126598, 1, "\u0632"], [126599, 1, "\u062D"], [126600, 1, "\u0637"], [126601, 1, "\u064A"], [126602, 3], [126603, 1, "\u0644"], [126604, 1, "\u0645"], [126605, 1, "\u0646"], [126606, 1, "\u0633"], [126607, 1, "\u0639"], [126608, 1, "\u0641"], [126609, 1, "\u0635"], [126610, 1, "\u0642"], [126611, 1, "\u0631"], [126612, 1, "\u0634"], [126613, 1, "\u062A"], [126614, 1, "\u062B"], [126615, 1, "\u062E"], [126616, 1, "\u0630"], [126617, 1, "\u0636"], [126618, 1, "\u0638"], [126619, 1, "\u063A"], [[126620, 126624], 3], [126625, 1, "\u0628"], [126626, 1, "\u062C"], [126627, 1, "\u062F"], [126628, 3], [126629, 1, "\u0648"], [126630, 1, "\u0632"], [126631, 1, "\u062D"], [126632, 1, "\u0637"], [126633, 1, "\u064A"], [126634, 3], [126635, 1, "\u0644"], [126636, 1, "\u0645"], [126637, 1, "\u0646"], [126638, 1, "\u0633"], [126639, 1, "\u0639"], [126640, 1, "\u0641"], [126641, 1, "\u0635"], [126642, 1, "\u0642"], [126643, 1, "\u0631"], [126644, 1, "\u0634"], [126645, 1, "\u062A"], [126646, 1, "\u062B"], [126647, 1, "\u062E"], [126648, 1, "\u0630"], [126649, 1, "\u0636"], [126650, 1, "\u0638"], [126651, 1, "\u063A"], [[126652, 126703], 3], [[126704, 126705], 2], [[126706, 126975], 3], [[126976, 127019], 2], [[127020, 127023], 3], [[127024, 127123], 2], [[127124, 127135], 3], [[127136, 127150], 2], [[127151, 127152], 3], [[127153, 127166], 2], [127167, 2], [127168, 3], [[127169, 127183], 2], [127184, 3], [[127185, 127199], 2], [[127200, 127221], 2], [[127222, 127231], 3], [127232, 3], [127233, 5, "0,"], [127234, 5, "1,"], [127235, 5, "2,"], [127236, 5, "3,"], [127237, 5, "4,"], [127238, 5, "5,"], [127239, 5, "6,"], [127240, 5, "7,"], [127241, 5, "8,"], [127242, 5, "9,"], [[127243, 127244], 2], [[127245, 127247], 2], [127248, 5, "(a)"], [127249, 5, "(b)"], [127250, 5, "(c)"], [127251, 5, "(d)"], [127252, 5, "(e)"], [127253, 5, "(f)"], [127254, 5, "(g)"], [127255, 5, "(h)"], [127256, 5, "(i)"], [127257, 5, "(j)"], [127258, 5, "(k)"], [127259, 5, "(l)"], [127260, 5, "(m)"], [127261, 5, "(n)"], [127262, 5, "(o)"], [127263, 5, "(p)"], [127264, 5, "(q)"], [127265, 5, "(r)"], [127266, 5, "(s)"], [127267, 5, "(t)"], [127268, 5, "(u)"], [127269, 5, "(v)"], [127270, 5, "(w)"], [127271, 5, "(x)"], [127272, 5, "(y)"], [127273, 5, "(z)"], [127274, 1, "\u3014s\u3015"], [127275, 1, "c"], [127276, 1, "r"], [127277, 1, "cd"], [127278, 1, "wz"], [127279, 2], [127280, 1, "a"], [127281, 1, "b"], [127282, 1, "c"], [127283, 1, "d"], [127284, 1, "e"], [127285, 1, "f"], [127286, 1, "g"], [127287, 1, "h"], [127288, 1, "i"], [127289, 1, "j"], [127290, 1, "k"], [127291, 1, "l"], [127292, 1, "m"], [127293, 1, "n"], [127294, 1, "o"], [127295, 1, "p"], [127296, 1, "q"], [127297, 1, "r"], [127298, 1, "s"], [127299, 1, "t"], [127300, 1, "u"], [127301, 1, "v"], [127302, 1, "w"], [127303, 1, "x"], [127304, 1, "y"], [127305, 1, "z"], [127306, 1, "hv"], [127307, 1, "mv"], [127308, 1, "sd"], [127309, 1, "ss"], [127310, 1, "ppv"], [127311, 1, "wc"], [[127312, 127318], 2], [127319, 2], [[127320, 127326], 2], [127327, 2], [[127328, 127337], 2], [127338, 1, "mc"], [127339, 1, "md"], [127340, 1, "mr"], [[127341, 127343], 2], [[127344, 127352], 2], [127353, 2], [127354, 2], [[127355, 127356], 2], [[127357, 127358], 2], [127359, 2], [[127360, 127369], 2], [[127370, 127373], 2], [[127374, 127375], 2], [127376, 1, "dj"], [[127377, 127386], 2], [[127387, 127404], 2], [127405, 2], [[127406, 127461], 3], [[127462, 127487], 2], [127488, 1, "\u307B\u304B"], [127489, 1, "\u30B3\u30B3"], [127490, 1, "\u30B5"], [[127491, 127503], 3], [127504, 1, "\u624B"], [127505, 1, "\u5B57"], [127506, 1, "\u53CC"], [127507, 1, "\u30C7"], [127508, 1, "\u4E8C"], [127509, 1, "\u591A"], [127510, 1, "\u89E3"], [127511, 1, "\u5929"], [127512, 1, "\u4EA4"], [127513, 1, "\u6620"], [127514, 1, "\u7121"], [127515, 1, "\u6599"], [127516, 1, "\u524D"], [127517, 1, "\u5F8C"], [127518, 1, "\u518D"], [127519, 1, "\u65B0"], [127520, 1, "\u521D"], [127521, 1, "\u7D42"], [127522, 1, "\u751F"], [127523, 1, "\u8CA9"], [127524, 1, "\u58F0"], [127525, 1, "\u5439"], [127526, 1, "\u6F14"], [127527, 1, "\u6295"], [127528, 1, "\u6355"], [127529, 1, "\u4E00"], [127530, 1, "\u4E09"], [127531, 1, "\u904A"], [127532, 1, "\u5DE6"], [127533, 1, "\u4E2D"], [127534, 1, "\u53F3"], [127535, 1, "\u6307"], [127536, 1, "\u8D70"], [127537, 1, "\u6253"], [127538, 1, "\u7981"], [127539, 1, "\u7A7A"], [127540, 1, "\u5408"], [127541, 1, "\u6E80"], [127542, 1, "\u6709"], [127543, 1, "\u6708"], [127544, 1, "\u7533"], [127545, 1, "\u5272"], [127546, 1, "\u55B6"], [127547, 1, "\u914D"], [[127548, 127551], 3], [127552, 1, "\u3014\u672C\u3015"], [127553, 1, "\u3014\u4E09\u3015"], [127554, 1, "\u3014\u4E8C\u3015"], [127555, 1, "\u3014\u5B89\u3015"], [127556, 1, "\u3014\u70B9\u3015"], [127557, 1, "\u3014\u6253\u3015"], [127558, 1, "\u3014\u76D7\u3015"], [127559, 1, "\u3014\u52DD\u3015"], [127560, 1, "\u3014\u6557\u3015"], [[127561, 127567], 3], [127568, 1, "\u5F97"], [127569, 1, "\u53EF"], [[127570, 127583], 3], [[127584, 127589], 2], [[127590, 127743], 3], [[127744, 127776], 2], [[127777, 127788], 2], [[127789, 127791], 2], [[127792, 127797], 2], [127798, 2], [[127799, 127868], 2], [127869, 2], [[127870, 127871], 2], [[127872, 127891], 2], [[127892, 127903], 2], [[127904, 127940], 2], [127941, 2], [[127942, 127946], 2], [[127947, 127950], 2], [[127951, 127955], 2], [[127956, 127967], 2], [[127968, 127984], 2], [[127985, 127991], 2], [[127992, 127999], 2], [[128e3, 128062], 2], [128063, 2], [128064, 2], [128065, 2], [[128066, 128247], 2], [128248, 2], [[128249, 128252], 2], [[128253, 128254], 2], [128255, 2], [[128256, 128317], 2], [[128318, 128319], 2], [[128320, 128323], 2], [[128324, 128330], 2], [[128331, 128335], 2], [[128336, 128359], 2], [[128360, 128377], 2], [128378, 2], [[128379, 128419], 2], [128420, 2], [[128421, 128506], 2], [[128507, 128511], 2], [128512, 2], [[128513, 128528], 2], [128529, 2], [[128530, 128532], 2], [128533, 2], [128534, 2], [128535, 2], [128536, 2], [128537, 2], [128538, 2], [128539, 2], [[128540, 128542], 2], [128543, 2], [[128544, 128549], 2], [[128550, 128551], 2], [[128552, 128555], 2], [128556, 2], [128557, 2], [[128558, 128559], 2], [[128560, 128563], 2], [128564, 2], [[128565, 128576], 2], [[128577, 128578], 2], [[128579, 128580], 2], [[128581, 128591], 2], [[128592, 128639], 2], [[128640, 128709], 2], [[128710, 128719], 2], [128720, 2], [[128721, 128722], 2], [[128723, 128724], 2], [128725, 2], [[128726, 128727], 2], [[128728, 128731], 3], [128732, 2], [[128733, 128735], 2], [[128736, 128748], 2], [[128749, 128751], 3], [[128752, 128755], 2], [[128756, 128758], 2], [[128759, 128760], 2], [128761, 2], [128762, 2], [[128763, 128764], 2], [[128765, 128767], 3], [[128768, 128883], 2], [[128884, 128886], 2], [[128887, 128890], 3], [[128891, 128895], 2], [[128896, 128980], 2], [[128981, 128984], 2], [128985, 2], [[128986, 128991], 3], [[128992, 129003], 2], [[129004, 129007], 3], [129008, 2], [[129009, 129023], 3], [[129024, 129035], 2], [[129036, 129039], 3], [[129040, 129095], 2], [[129096, 129103], 3], [[129104, 129113], 2], [[129114, 129119], 3], [[129120, 129159], 2], [[129160, 129167], 3], [[129168, 129197], 2], [[129198, 129199], 3], [[129200, 129201], 2], [[129202, 129279], 3], [[129280, 129291], 2], [129292, 2], [[129293, 129295], 2], [[129296, 129304], 2], [[129305, 129310], 2], [129311, 2], [[129312, 129319], 2], [[129320, 129327], 2], [129328, 2], [[129329, 129330], 2], [[129331, 129342], 2], [129343, 2], [[129344, 129355], 2], [129356, 2], [[129357, 129359], 2], [[129360, 129374], 2], [[129375, 129387], 2], [[129388, 129392], 2], [129393, 2], [129394, 2], [[129395, 129398], 2], [[129399, 129400], 2], [129401, 2], [129402, 2], [129403, 2], [[129404, 129407], 2], [[129408, 129412], 2], [[129413, 129425], 2], [[129426, 129431], 2], [[129432, 129442], 2], [[129443, 129444], 2], [[129445, 129450], 2], [[129451, 129453], 2], [[129454, 129455], 2], [[129456, 129465], 2], [[129466, 129471], 2], [129472, 2], [[129473, 129474], 2], [[129475, 129482], 2], [129483, 2], [129484, 2], [[129485, 129487], 2], [[129488, 129510], 2], [[129511, 129535], 2], [[129536, 129619], 2], [[129620, 129631], 3], [[129632, 129645], 2], [[129646, 129647], 3], [[129648, 129651], 2], [129652, 2], [[129653, 129655], 2], [[129656, 129658], 2], [[129659, 129660], 2], [[129661, 129663], 3], [[129664, 129666], 2], [[129667, 129670], 2], [[129671, 129672], 2], [[129673, 129679], 3], [[129680, 129685], 2], [[129686, 129704], 2], [[129705, 129708], 2], [[129709, 129711], 2], [[129712, 129718], 2], [[129719, 129722], 2], [[129723, 129725], 2], [129726, 3], [129727, 2], [[129728, 129730], 2], [[129731, 129733], 2], [[129734, 129741], 3], [[129742, 129743], 2], [[129744, 129750], 2], [[129751, 129753], 2], [[129754, 129755], 2], [[129756, 129759], 3], [[129760, 129767], 2], [129768, 2], [[129769, 129775], 3], [[129776, 129782], 2], [[129783, 129784], 2], [[129785, 129791], 3], [[129792, 129938], 2], [129939, 3], [[129940, 129994], 2], [[129995, 130031], 3], [130032, 1, "0"], [130033, 1, "1"], [130034, 1, "2"], [130035, 1, "3"], [130036, 1, "4"], [130037, 1, "5"], [130038, 1, "6"], [130039, 1, "7"], [130040, 1, "8"], [130041, 1, "9"], [[130042, 131069], 3], [[131070, 131071], 3], [[131072, 173782], 2], [[173783, 173789], 2], [[173790, 173791], 2], [[173792, 173823], 3], [[173824, 177972], 2], [[177973, 177976], 2], [177977, 2], [[177978, 177983], 3], [[177984, 178205], 2], [[178206, 178207], 3], [[178208, 183969], 2], [[183970, 183983], 3], [[183984, 191456], 2], [[191457, 194559], 3], [194560, 1, "\u4E3D"], [194561, 1, "\u4E38"], [194562, 1, "\u4E41"], [194563, 1, "\u{20122}"], [194564, 1, "\u4F60"], [194565, 1, "\u4FAE"], [194566, 1, "\u4FBB"], [194567, 1, "\u5002"], [194568, 1, "\u507A"], [194569, 1, "\u5099"], [194570, 1, "\u50E7"], [194571, 1, "\u50CF"], [194572, 1, "\u349E"], [194573, 1, "\u{2063A}"], [194574, 1, "\u514D"], [194575, 1, "\u5154"], [194576, 1, "\u5164"], [194577, 1, "\u5177"], [194578, 1, "\u{2051C}"], [194579, 1, "\u34B9"], [194580, 1, "\u5167"], [194581, 1, "\u518D"], [194582, 1, "\u{2054B}"], [194583, 1, "\u5197"], [194584, 1, "\u51A4"], [194585, 1, "\u4ECC"], [194586, 1, "\u51AC"], [194587, 1, "\u51B5"], [194588, 1, "\u{291DF}"], [194589, 1, "\u51F5"], [194590, 1, "\u5203"], [194591, 1, "\u34DF"], [194592, 1, "\u523B"], [194593, 1, "\u5246"], [194594, 1, "\u5272"], [194595, 1, "\u5277"], [194596, 1, "\u3515"], [194597, 1, "\u52C7"], [194598, 1, "\u52C9"], [194599, 1, "\u52E4"], [194600, 1, "\u52FA"], [194601, 1, "\u5305"], [194602, 1, "\u5306"], [194603, 1, "\u5317"], [194604, 1, "\u5349"], [194605, 1, "\u5351"], [194606, 1, "\u535A"], [194607, 1, "\u5373"], [194608, 1, "\u537D"], [[194609, 194611], 1, "\u537F"], [194612, 1, "\u{20A2C}"], [194613, 1, "\u7070"], [194614, 1, "\u53CA"], [194615, 1, "\u53DF"], [194616, 1, "\u{20B63}"], [194617, 1, "\u53EB"], [194618, 1, "\u53F1"], [194619, 1, "\u5406"], [194620, 1, "\u549E"], [194621, 1, "\u5438"], [194622, 1, "\u5448"], [194623, 1, "\u5468"], [194624, 1, "\u54A2"], [194625, 1, "\u54F6"], [194626, 1, "\u5510"], [194627, 1, "\u5553"], [194628, 1, "\u5563"], [[194629, 194630], 1, "\u5584"], [194631, 1, "\u5599"], [194632, 1, "\u55AB"], [194633, 1, "\u55B3"], [194634, 1, "\u55C2"], [194635, 1, "\u5716"], [194636, 1, "\u5606"], [194637, 1, "\u5717"], [194638, 1, "\u5651"], [194639, 1, "\u5674"], [194640, 1, "\u5207"], [194641, 1, "\u58EE"], [194642, 1, "\u57CE"], [194643, 1, "\u57F4"], [194644, 1, "\u580D"], [194645, 1, "\u578B"], [194646, 1, "\u5832"], [194647, 1, "\u5831"], [194648, 1, "\u58AC"], [194649, 1, "\u{214E4}"], [194650, 1, "\u58F2"], [194651, 1, "\u58F7"], [194652, 1, "\u5906"], [194653, 1, "\u591A"], [194654, 1, "\u5922"], [194655, 1, "\u5962"], [194656, 1, "\u{216A8}"], [194657, 1, "\u{216EA}"], [194658, 1, "\u59EC"], [194659, 1, "\u5A1B"], [194660, 1, "\u5A27"], [194661, 1, "\u59D8"], [194662, 1, "\u5A66"], [194663, 1, "\u36EE"], [194664, 3], [194665, 1, "\u5B08"], [[194666, 194667], 1, "\u5B3E"], [194668, 1, "\u{219C8}"], [194669, 1, "\u5BC3"], [194670, 1, "\u5BD8"], [194671, 1, "\u5BE7"], [194672, 1, "\u5BF3"], [194673, 1, "\u{21B18}"], [194674, 1, "\u5BFF"], [194675, 1, "\u5C06"], [194676, 3], [194677, 1, "\u5C22"], [194678, 1, "\u3781"], [194679, 1, "\u5C60"], [194680, 1, "\u5C6E"], [194681, 1, "\u5CC0"], [194682, 1, "\u5C8D"], [194683, 1, "\u{21DE4}"], [194684, 1, "\u5D43"], [194685, 1, "\u{21DE6}"], [194686, 1, "\u5D6E"], [194687, 1, "\u5D6B"], [194688, 1, "\u5D7C"], [194689, 1, "\u5DE1"], [194690, 1, "\u5DE2"], [194691, 1, "\u382F"], [194692, 1, "\u5DFD"], [194693, 1, "\u5E28"], [194694, 1, "\u5E3D"], [194695, 1, "\u5E69"], [194696, 1, "\u3862"], [194697, 1, "\u{22183}"], [194698, 1, "\u387C"], [194699, 1, "\u5EB0"], [194700, 1, "\u5EB3"], [194701, 1, "\u5EB6"], [194702, 1, "\u5ECA"], [194703, 1, "\u{2A392}"], [194704, 1, "\u5EFE"], [[194705, 194706], 1, "\u{22331}"], [194707, 1, "\u8201"], [[194708, 194709], 1, "\u5F22"], [194710, 1, "\u38C7"], [194711, 1, "\u{232B8}"], [194712, 1, "\u{261DA}"], [194713, 1, "\u5F62"], [194714, 1, "\u5F6B"], [194715, 1, "\u38E3"], [194716, 1, "\u5F9A"], [194717, 1, "\u5FCD"], [194718, 1, "\u5FD7"], [194719, 1, "\u5FF9"], [194720, 1, "\u6081"], [194721, 1, "\u393A"], [194722, 1, "\u391C"], [194723, 1, "\u6094"], [194724, 1, "\u{226D4}"], [194725, 1, "\u60C7"], [194726, 1, "\u6148"], [194727, 1, "\u614C"], [194728, 1, "\u614E"], [194729, 1, "\u614C"], [194730, 1, "\u617A"], [194731, 1, "\u618E"], [194732, 1, "\u61B2"], [194733, 1, "\u61A4"], [194734, 1, "\u61AF"], [194735, 1, "\u61DE"], [194736, 1, "\u61F2"], [194737, 1, "\u61F6"], [194738, 1, "\u6210"], [194739, 1, "\u621B"], [194740, 1, "\u625D"], [194741, 1, "\u62B1"], [194742, 1, "\u62D4"], [194743, 1, "\u6350"], [194744, 1, "\u{22B0C}"], [194745, 1, "\u633D"], [194746, 1, "\u62FC"], [194747, 1, "\u6368"], [194748, 1, "\u6383"], [194749, 1, "\u63E4"], [194750, 1, "\u{22BF1}"], [194751, 1, "\u6422"], [194752, 1, "\u63C5"], [194753, 1, "\u63A9"], [194754, 1, "\u3A2E"], [194755, 1, "\u6469"], [194756, 1, "\u647E"], [194757, 1, "\u649D"], [194758, 1, "\u6477"], [194759, 1, "\u3A6C"], [194760, 1, "\u654F"], [194761, 1, "\u656C"], [194762, 1, "\u{2300A}"], [194763, 1, "\u65E3"], [194764, 1, "\u66F8"], [194765, 1, "\u6649"], [194766, 1, "\u3B19"], [194767, 1, "\u6691"], [194768, 1, "\u3B08"], [194769, 1, "\u3AE4"], [194770, 1, "\u5192"], [194771, 1, "\u5195"], [194772, 1, "\u6700"], [194773, 1, "\u669C"], [194774, 1, "\u80AD"], [194775, 1, "\u43D9"], [194776, 1, "\u6717"], [194777, 1, "\u671B"], [194778, 1, "\u6721"], [194779, 1, "\u675E"], [194780, 1, "\u6753"], [194781, 1, "\u{233C3}"], [194782, 1, "\u3B49"], [194783, 1, "\u67FA"], [194784, 1, "\u6785"], [194785, 1, "\u6852"], [194786, 1, "\u6885"], [194787, 1, "\u{2346D}"], [194788, 1, "\u688E"], [194789, 1, "\u681F"], [194790, 1, "\u6914"], [194791, 1, "\u3B9D"], [194792, 1, "\u6942"], [194793, 1, "\u69A3"], [194794, 1, "\u69EA"], [194795, 1, "\u6AA8"], [194796, 1, "\u{236A3}"], [194797, 1, "\u6ADB"], [194798, 1, "\u3C18"], [194799, 1, "\u6B21"], [194800, 1, "\u{238A7}"], [194801, 1, "\u6B54"], [194802, 1, "\u3C4E"], [194803, 1, "\u6B72"], [194804, 1, "\u6B9F"], [194805, 1, "\u6BBA"], [194806, 1, "\u6BBB"], [194807, 1, "\u{23A8D}"], [194808, 1, "\u{21D0B}"], [194809, 1, "\u{23AFA}"], [194810, 1, "\u6C4E"], [194811, 1, "\u{23CBC}"], [194812, 1, "\u6CBF"], [194813, 1, "\u6CCD"], [194814, 1, "\u6C67"], [194815, 1, "\u6D16"], [194816, 1, "\u6D3E"], [194817, 1, "\u6D77"], [194818, 1, "\u6D41"], [194819, 1, "\u6D69"], [194820, 1, "\u6D78"], [194821, 1, "\u6D85"], [194822, 1, "\u{23D1E}"], [194823, 1, "\u6D34"], [194824, 1, "\u6E2F"], [194825, 1, "\u6E6E"], [194826, 1, "\u3D33"], [194827, 1, "\u6ECB"], [194828, 1, "\u6EC7"], [194829, 1, "\u{23ED1}"], [194830, 1, "\u6DF9"], [194831, 1, "\u6F6E"], [194832, 1, "\u{23F5E}"], [194833, 1, "\u{23F8E}"], [194834, 1, "\u6FC6"], [194835, 1, "\u7039"], [194836, 1, "\u701E"], [194837, 1, "\u701B"], [194838, 1, "\u3D96"], [194839, 1, "\u704A"], [194840, 1, "\u707D"], [194841, 1, "\u7077"], [194842, 1, "\u70AD"], [194843, 1, "\u{20525}"], [194844, 1, "\u7145"], [194845, 1, "\u{24263}"], [194846, 1, "\u719C"], [194847, 3], [194848, 1, "\u7228"], [194849, 1, "\u7235"], [194850, 1, "\u7250"], [194851, 1, "\u{24608}"], [194852, 1, "\u7280"], [194853, 1, "\u7295"], [194854, 1, "\u{24735}"], [194855, 1, "\u{24814}"], [194856, 1, "\u737A"], [194857, 1, "\u738B"], [194858, 1, "\u3EAC"], [194859, 1, "\u73A5"], [[194860, 194861], 1, "\u3EB8"], [194862, 1, "\u7447"], [194863, 1, "\u745C"], [194864, 1, "\u7471"], [194865, 1, "\u7485"], [194866, 1, "\u74CA"], [194867, 1, "\u3F1B"], [194868, 1, "\u7524"], [194869, 1, "\u{24C36}"], [194870, 1, "\u753E"], [194871, 1, "\u{24C92}"], [194872, 1, "\u7570"], [194873, 1, "\u{2219F}"], [194874, 1, "\u7610"], [194875, 1, "\u{24FA1}"], [194876, 1, "\u{24FB8}"], [194877, 1, "\u{25044}"], [194878, 1, "\u3FFC"], [194879, 1, "\u4008"], [194880, 1, "\u76F4"], [194881, 1, "\u{250F3}"], [194882, 1, "\u{250F2}"], [194883, 1, "\u{25119}"], [194884, 1, "\u{25133}"], [194885, 1, "\u771E"], [[194886, 194887], 1, "\u771F"], [194888, 1, "\u774A"], [194889, 1, "\u4039"], [194890, 1, "\u778B"], [194891, 1, "\u4046"], [194892, 1, "\u4096"], [194893, 1, "\u{2541D}"], [194894, 1, "\u784E"], [194895, 1, "\u788C"], [194896, 1, "\u78CC"], [194897, 1, "\u40E3"], [194898, 1, "\u{25626}"], [194899, 1, "\u7956"], [194900, 1, "\u{2569A}"], [194901, 1, "\u{256C5}"], [194902, 1, "\u798F"], [194903, 1, "\u79EB"], [194904, 1, "\u412F"], [194905, 1, "\u7A40"], [194906, 1, "\u7A4A"], [194907, 1, "\u7A4F"], [194908, 1, "\u{2597C}"], [[194909, 194910], 1, "\u{25AA7}"], [194911, 3], [194912, 1, "\u4202"], [194913, 1, "\u{25BAB}"], [194914, 1, "\u7BC6"], [194915, 1, "\u7BC9"], [194916, 1, "\u4227"], [194917, 1, "\u{25C80}"], [194918, 1, "\u7CD2"], [194919, 1, "\u42A0"], [194920, 1, "\u7CE8"], [194921, 1, "\u7CE3"], [194922, 1, "\u7D00"], [194923, 1, "\u{25F86}"], [194924, 1, "\u7D63"], [194925, 1, "\u4301"], [194926, 1, "\u7DC7"], [194927, 1, "\u7E02"], [194928, 1, "\u7E45"], [194929, 1, "\u4334"], [194930, 1, "\u{26228}"], [194931, 1, "\u{26247}"], [194932, 1, "\u4359"], [194933, 1, "\u{262D9}"], [194934, 1, "\u7F7A"], [194935, 1, "\u{2633E}"], [194936, 1, "\u7F95"], [194937, 1, "\u7FFA"], [194938, 1, "\u8005"], [194939, 1, "\u{264DA}"], [194940, 1, "\u{26523}"], [194941, 1, "\u8060"], [194942, 1, "\u{265A8}"], [194943, 1, "\u8070"], [194944, 1, "\u{2335F}"], [194945, 1, "\u43D5"], [194946, 1, "\u80B2"], [194947, 1, "\u8103"], [194948, 1, "\u440B"], [194949, 1, "\u813E"], [194950, 1, "\u5AB5"], [194951, 1, "\u{267A7}"], [194952, 1, "\u{267B5}"], [194953, 1, "\u{23393}"], [194954, 1, "\u{2339C}"], [194955, 1, "\u8201"], [194956, 1, "\u8204"], [194957, 1, "\u8F9E"], [194958, 1, "\u446B"], [194959, 1, "\u8291"], [194960, 1, "\u828B"], [194961, 1, "\u829D"], [194962, 1, "\u52B3"], [194963, 1, "\u82B1"], [194964, 1, "\u82B3"], [194965, 1, "\u82BD"], [194966, 1, "\u82E6"], [194967, 1, "\u{26B3C}"], [194968, 1, "\u82E5"], [194969, 1, "\u831D"], [194970, 1, "\u8363"], [194971, 1, "\u83AD"], [194972, 1, "\u8323"], [194973, 1, "\u83BD"], [194974, 1, "\u83E7"], [194975, 1, "\u8457"], [194976, 1, "\u8353"], [194977, 1, "\u83CA"], [194978, 1, "\u83CC"], [194979, 1, "\u83DC"], [194980, 1, "\u{26C36}"], [194981, 1, "\u{26D6B}"], [194982, 1, "\u{26CD5}"], [194983, 1, "\u452B"], [194984, 1, "\u84F1"], [194985, 1, "\u84F3"], [194986, 1, "\u8516"], [194987, 1, "\u{273CA}"], [194988, 1, "\u8564"], [194989, 1, "\u{26F2C}"], [194990, 1, "\u455D"], [194991, 1, "\u4561"], [194992, 1, "\u{26FB1}"], [194993, 1, "\u{270D2}"], [194994, 1, "\u456B"], [194995, 1, "\u8650"], [194996, 1, "\u865C"], [194997, 1, "\u8667"], [194998, 1, "\u8669"], [194999, 1, "\u86A9"], [195e3, 1, "\u8688"], [195001, 1, "\u870E"], [195002, 1, "\u86E2"], [195003, 1, "\u8779"], [195004, 1, "\u8728"], [195005, 1, "\u876B"], [195006, 1, "\u8786"], [195007, 3], [195008, 1, "\u87E1"], [195009, 1, "\u8801"], [195010, 1, "\u45F9"], [195011, 1, "\u8860"], [195012, 1, "\u8863"], [195013, 1, "\u{27667}"], [195014, 1, "\u88D7"], [195015, 1, "\u88DE"], [195016, 1, "\u4635"], [195017, 1, "\u88FA"], [195018, 1, "\u34BB"], [195019, 1, "\u{278AE}"], [195020, 1, "\u{27966}"], [195021, 1, "\u46BE"], [195022, 1, "\u46C7"], [195023, 1, "\u8AA0"], [195024, 1, "\u8AED"], [195025, 1, "\u8B8A"], [195026, 1, "\u8C55"], [195027, 1, "\u{27CA8}"], [195028, 1, "\u8CAB"], [195029, 1, "\u8CC1"], [195030, 1, "\u8D1B"], [195031, 1, "\u8D77"], [195032, 1, "\u{27F2F}"], [195033, 1, "\u{20804}"], [195034, 1, "\u8DCB"], [195035, 1, "\u8DBC"], [195036, 1, "\u8DF0"], [195037, 1, "\u{208DE}"], [195038, 1, "\u8ED4"], [195039, 1, "\u8F38"], [195040, 1, "\u{285D2}"], [195041, 1, "\u{285ED}"], [195042, 1, "\u9094"], [195043, 1, "\u90F1"], [195044, 1, "\u9111"], [195045, 1, "\u{2872E}"], [195046, 1, "\u911B"], [195047, 1, "\u9238"], [195048, 1, "\u92D7"], [195049, 1, "\u92D8"], [195050, 1, "\u927C"], [195051, 1, "\u93F9"], [195052, 1, "\u9415"], [195053, 1, "\u{28BFA}"], [195054, 1, "\u958B"], [195055, 1, "\u4995"], [195056, 1, "\u95B7"], [195057, 1, "\u{28D77}"], [195058, 1, "\u49E6"], [195059, 1, "\u96C3"], [195060, 1, "\u5DB2"], [195061, 1, "\u9723"], [195062, 1, "\u{29145}"], [195063, 1, "\u{2921A}"], [195064, 1, "\u4A6E"], [195065, 1, "\u4A76"], [195066, 1, "\u97E0"], [195067, 1, "\u{2940A}"], [195068, 1, "\u4AB2"], [195069, 1, "\u{29496}"], [[195070, 195071], 1, "\u980B"], [195072, 1, "\u9829"], [195073, 1, "\u{295B6}"], [195074, 1, "\u98E2"], [195075, 1, "\u4B33"], [195076, 1, "\u9929"], [195077, 1, "\u99A7"], [195078, 1, "\u99C2"], [195079, 1, "\u99FE"], [195080, 1, "\u4BCE"], [195081, 1, "\u{29B30}"], [195082, 1, "\u9B12"], [195083, 1, "\u9C40"], [195084, 1, "\u9CFD"], [195085, 1, "\u4CCE"], [195086, 1, "\u4CED"], [195087, 1, "\u9D67"], [195088, 1, "\u{2A0CE}"], [195089, 1, "\u4CF8"], [195090, 1, "\u{2A105}"], [195091, 1, "\u{2A20E}"], [195092, 1, "\u{2A291}"], [195093, 1, "\u9EBB"], [195094, 1, "\u4D56"], [195095, 1, "\u9EF9"], [195096, 1, "\u9EFE"], [195097, 1, "\u9F05"], [195098, 1, "\u9F0F"], [195099, 1, "\u9F16"], [195100, 1, "\u9F3B"], [195101, 1, "\u{2A600}"], [[195102, 196605], 3], [[196606, 196607], 3], [[196608, 201546], 2], [[201547, 201551], 3], [[201552, 205743], 2], [[205744, 262141], 3], [[262142, 262143], 3], [[262144, 327677], 3], [[327678, 327679], 3], [[327680, 393213], 3], [[393214, 393215], 3], [[393216, 458749], 3], [[458750, 458751], 3], [[458752, 524285], 3], [[524286, 524287], 3], [[524288, 589821], 3], [[589822, 589823], 3], [[589824, 655357], 3], [[655358, 655359], 3], [[655360, 720893], 3], [[720894, 720895], 3], [[720896, 786429], 3], [[786430, 786431], 3], [[786432, 851965], 3], [[851966, 851967], 3], [[851968, 917501], 3], [[917502, 917503], 3], [917504, 3], [917505, 3], [[917506, 917535], 3], [[917536, 917631], 3], [[917632, 917759], 3], [[917760, 917999], 7], [[918e3, 983037], 3], [[983038, 983039], 3], [[983040, 1048573], 3], [[1048574, 1048575], 3], [[1048576, 1114109], 3], [[1114110, 1114111], 3]]; + module.exports = [[[0, 44], 4], [[45, 46], 2], [47, 4], [[48, 57], 2], [[58, 64], 4], [65, 1, "a"], [66, 1, "b"], [67, 1, "c"], [68, 1, "d"], [69, 1, "e"], [70, 1, "f"], [71, 1, "g"], [72, 1, "h"], [73, 1, "i"], [74, 1, "j"], [75, 1, "k"], [76, 1, "l"], [77, 1, "m"], [78, 1, "n"], [79, 1, "o"], [80, 1, "p"], [81, 1, "q"], [82, 1, "r"], [83, 1, "s"], [84, 1, "t"], [85, 1, "u"], [86, 1, "v"], [87, 1, "w"], [88, 1, "x"], [89, 1, "y"], [90, 1, "z"], [[91, 96], 4], [[97, 122], 2], [[123, 127], 4], [[128, 159], 3], [160, 5, " "], [[161, 167], 2], [168, 5, " \u0308"], [169, 2], [170, 1, "a"], [[171, 172], 2], [173, 7], [174, 2], [175, 5, " \u0304"], [[176, 177], 2], [178, 1, "2"], [179, 1, "3"], [180, 5, " \u0301"], [181, 1, "\u03BC"], [182, 2], [183, 2], [184, 5, " \u0327"], [185, 1, "1"], [186, 1, "o"], [187, 2], [188, 1, "1\u20444"], [189, 1, "1\u20442"], [190, 1, "3\u20444"], [191, 2], [192, 1, "\xE0"], [193, 1, "\xE1"], [194, 1, "\xE2"], [195, 1, "\xE3"], [196, 1, "\xE4"], [197, 1, "\xE5"], [198, 1, "\xE6"], [199, 1, "\xE7"], [200, 1, "\xE8"], [201, 1, "\xE9"], [202, 1, "\xEA"], [203, 1, "\xEB"], [204, 1, "\xEC"], [205, 1, "\xED"], [206, 1, "\xEE"], [207, 1, "\xEF"], [208, 1, "\xF0"], [209, 1, "\xF1"], [210, 1, "\xF2"], [211, 1, "\xF3"], [212, 1, "\xF4"], [213, 1, "\xF5"], [214, 1, "\xF6"], [215, 2], [216, 1, "\xF8"], [217, 1, "\xF9"], [218, 1, "\xFA"], [219, 1, "\xFB"], [220, 1, "\xFC"], [221, 1, "\xFD"], [222, 1, "\xFE"], [223, 6, "ss"], [[224, 246], 2], [247, 2], [[248, 255], 2], [256, 1, "\u0101"], [257, 2], [258, 1, "\u0103"], [259, 2], [260, 1, "\u0105"], [261, 2], [262, 1, "\u0107"], [263, 2], [264, 1, "\u0109"], [265, 2], [266, 1, "\u010B"], [267, 2], [268, 1, "\u010D"], [269, 2], [270, 1, "\u010F"], [271, 2], [272, 1, "\u0111"], [273, 2], [274, 1, "\u0113"], [275, 2], [276, 1, "\u0115"], [277, 2], [278, 1, "\u0117"], [279, 2], [280, 1, "\u0119"], [281, 2], [282, 1, "\u011B"], [283, 2], [284, 1, "\u011D"], [285, 2], [286, 1, "\u011F"], [287, 2], [288, 1, "\u0121"], [289, 2], [290, 1, "\u0123"], [291, 2], [292, 1, "\u0125"], [293, 2], [294, 1, "\u0127"], [295, 2], [296, 1, "\u0129"], [297, 2], [298, 1, "\u012B"], [299, 2], [300, 1, "\u012D"], [301, 2], [302, 1, "\u012F"], [303, 2], [304, 1, "i\u0307"], [305, 2], [[306, 307], 1, "ij"], [308, 1, "\u0135"], [309, 2], [310, 1, "\u0137"], [[311, 312], 2], [313, 1, "\u013A"], [314, 2], [315, 1, "\u013C"], [316, 2], [317, 1, "\u013E"], [318, 2], [[319, 320], 1, "l\xB7"], [321, 1, "\u0142"], [322, 2], [323, 1, "\u0144"], [324, 2], [325, 1, "\u0146"], [326, 2], [327, 1, "\u0148"], [328, 2], [329, 1, "\u02BCn"], [330, 1, "\u014B"], [331, 2], [332, 1, "\u014D"], [333, 2], [334, 1, "\u014F"], [335, 2], [336, 1, "\u0151"], [337, 2], [338, 1, "\u0153"], [339, 2], [340, 1, "\u0155"], [341, 2], [342, 1, "\u0157"], [343, 2], [344, 1, "\u0159"], [345, 2], [346, 1, "\u015B"], [347, 2], [348, 1, "\u015D"], [349, 2], [350, 1, "\u015F"], [351, 2], [352, 1, "\u0161"], [353, 2], [354, 1, "\u0163"], [355, 2], [356, 1, "\u0165"], [357, 2], [358, 1, "\u0167"], [359, 2], [360, 1, "\u0169"], [361, 2], [362, 1, "\u016B"], [363, 2], [364, 1, "\u016D"], [365, 2], [366, 1, "\u016F"], [367, 2], [368, 1, "\u0171"], [369, 2], [370, 1, "\u0173"], [371, 2], [372, 1, "\u0175"], [373, 2], [374, 1, "\u0177"], [375, 2], [376, 1, "\xFF"], [377, 1, "\u017A"], [378, 2], [379, 1, "\u017C"], [380, 2], [381, 1, "\u017E"], [382, 2], [383, 1, "s"], [384, 2], [385, 1, "\u0253"], [386, 1, "\u0183"], [387, 2], [388, 1, "\u0185"], [389, 2], [390, 1, "\u0254"], [391, 1, "\u0188"], [392, 2], [393, 1, "\u0256"], [394, 1, "\u0257"], [395, 1, "\u018C"], [[396, 397], 2], [398, 1, "\u01DD"], [399, 1, "\u0259"], [400, 1, "\u025B"], [401, 1, "\u0192"], [402, 2], [403, 1, "\u0260"], [404, 1, "\u0263"], [405, 2], [406, 1, "\u0269"], [407, 1, "\u0268"], [408, 1, "\u0199"], [[409, 411], 2], [412, 1, "\u026F"], [413, 1, "\u0272"], [414, 2], [415, 1, "\u0275"], [416, 1, "\u01A1"], [417, 2], [418, 1, "\u01A3"], [419, 2], [420, 1, "\u01A5"], [421, 2], [422, 1, "\u0280"], [423, 1, "\u01A8"], [424, 2], [425, 1, "\u0283"], [[426, 427], 2], [428, 1, "\u01AD"], [429, 2], [430, 1, "\u0288"], [431, 1, "\u01B0"], [432, 2], [433, 1, "\u028A"], [434, 1, "\u028B"], [435, 1, "\u01B4"], [436, 2], [437, 1, "\u01B6"], [438, 2], [439, 1, "\u0292"], [440, 1, "\u01B9"], [[441, 443], 2], [444, 1, "\u01BD"], [[445, 451], 2], [[452, 454], 1, "d\u017E"], [[455, 457], 1, "lj"], [[458, 460], 1, "nj"], [461, 1, "\u01CE"], [462, 2], [463, 1, "\u01D0"], [464, 2], [465, 1, "\u01D2"], [466, 2], [467, 1, "\u01D4"], [468, 2], [469, 1, "\u01D6"], [470, 2], [471, 1, "\u01D8"], [472, 2], [473, 1, "\u01DA"], [474, 2], [475, 1, "\u01DC"], [[476, 477], 2], [478, 1, "\u01DF"], [479, 2], [480, 1, "\u01E1"], [481, 2], [482, 1, "\u01E3"], [483, 2], [484, 1, "\u01E5"], [485, 2], [486, 1, "\u01E7"], [487, 2], [488, 1, "\u01E9"], [489, 2], [490, 1, "\u01EB"], [491, 2], [492, 1, "\u01ED"], [493, 2], [494, 1, "\u01EF"], [[495, 496], 2], [[497, 499], 1, "dz"], [500, 1, "\u01F5"], [501, 2], [502, 1, "\u0195"], [503, 1, "\u01BF"], [504, 1, "\u01F9"], [505, 2], [506, 1, "\u01FB"], [507, 2], [508, 1, "\u01FD"], [509, 2], [510, 1, "\u01FF"], [511, 2], [512, 1, "\u0201"], [513, 2], [514, 1, "\u0203"], [515, 2], [516, 1, "\u0205"], [517, 2], [518, 1, "\u0207"], [519, 2], [520, 1, "\u0209"], [521, 2], [522, 1, "\u020B"], [523, 2], [524, 1, "\u020D"], [525, 2], [526, 1, "\u020F"], [527, 2], [528, 1, "\u0211"], [529, 2], [530, 1, "\u0213"], [531, 2], [532, 1, "\u0215"], [533, 2], [534, 1, "\u0217"], [535, 2], [536, 1, "\u0219"], [537, 2], [538, 1, "\u021B"], [539, 2], [540, 1, "\u021D"], [541, 2], [542, 1, "\u021F"], [543, 2], [544, 1, "\u019E"], [545, 2], [546, 1, "\u0223"], [547, 2], [548, 1, "\u0225"], [549, 2], [550, 1, "\u0227"], [551, 2], [552, 1, "\u0229"], [553, 2], [554, 1, "\u022B"], [555, 2], [556, 1, "\u022D"], [557, 2], [558, 1, "\u022F"], [559, 2], [560, 1, "\u0231"], [561, 2], [562, 1, "\u0233"], [563, 2], [[564, 566], 2], [[567, 569], 2], [570, 1, "\u2C65"], [571, 1, "\u023C"], [572, 2], [573, 1, "\u019A"], [574, 1, "\u2C66"], [[575, 576], 2], [577, 1, "\u0242"], [578, 2], [579, 1, "\u0180"], [580, 1, "\u0289"], [581, 1, "\u028C"], [582, 1, "\u0247"], [583, 2], [584, 1, "\u0249"], [585, 2], [586, 1, "\u024B"], [587, 2], [588, 1, "\u024D"], [589, 2], [590, 1, "\u024F"], [591, 2], [[592, 680], 2], [[681, 685], 2], [[686, 687], 2], [688, 1, "h"], [689, 1, "\u0266"], [690, 1, "j"], [691, 1, "r"], [692, 1, "\u0279"], [693, 1, "\u027B"], [694, 1, "\u0281"], [695, 1, "w"], [696, 1, "y"], [[697, 705], 2], [[706, 709], 2], [[710, 721], 2], [[722, 727], 2], [728, 5, " \u0306"], [729, 5, " \u0307"], [730, 5, " \u030A"], [731, 5, " \u0328"], [732, 5, " \u0303"], [733, 5, " \u030B"], [734, 2], [735, 2], [736, 1, "\u0263"], [737, 1, "l"], [738, 1, "s"], [739, 1, "x"], [740, 1, "\u0295"], [[741, 745], 2], [[746, 747], 2], [748, 2], [749, 2], [750, 2], [[751, 767], 2], [[768, 831], 2], [832, 1, "\u0300"], [833, 1, "\u0301"], [834, 2], [835, 1, "\u0313"], [836, 1, "\u0308\u0301"], [837, 1, "\u03B9"], [[838, 846], 2], [847, 7], [[848, 855], 2], [[856, 860], 2], [[861, 863], 2], [[864, 865], 2], [866, 2], [[867, 879], 2], [880, 1, "\u0371"], [881, 2], [882, 1, "\u0373"], [883, 2], [884, 1, "\u02B9"], [885, 2], [886, 1, "\u0377"], [887, 2], [[888, 889], 3], [890, 5, " \u03B9"], [[891, 893], 2], [894, 5, ";"], [895, 1, "\u03F3"], [[896, 899], 3], [900, 5, " \u0301"], [901, 5, " \u0308\u0301"], [902, 1, "\u03AC"], [903, 1, "\xB7"], [904, 1, "\u03AD"], [905, 1, "\u03AE"], [906, 1, "\u03AF"], [907, 3], [908, 1, "\u03CC"], [909, 3], [910, 1, "\u03CD"], [911, 1, "\u03CE"], [912, 2], [913, 1, "\u03B1"], [914, 1, "\u03B2"], [915, 1, "\u03B3"], [916, 1, "\u03B4"], [917, 1, "\u03B5"], [918, 1, "\u03B6"], [919, 1, "\u03B7"], [920, 1, "\u03B8"], [921, 1, "\u03B9"], [922, 1, "\u03BA"], [923, 1, "\u03BB"], [924, 1, "\u03BC"], [925, 1, "\u03BD"], [926, 1, "\u03BE"], [927, 1, "\u03BF"], [928, 1, "\u03C0"], [929, 1, "\u03C1"], [930, 3], [931, 1, "\u03C3"], [932, 1, "\u03C4"], [933, 1, "\u03C5"], [934, 1, "\u03C6"], [935, 1, "\u03C7"], [936, 1, "\u03C8"], [937, 1, "\u03C9"], [938, 1, "\u03CA"], [939, 1, "\u03CB"], [[940, 961], 2], [962, 6, "\u03C3"], [[963, 974], 2], [975, 1, "\u03D7"], [976, 1, "\u03B2"], [977, 1, "\u03B8"], [978, 1, "\u03C5"], [979, 1, "\u03CD"], [980, 1, "\u03CB"], [981, 1, "\u03C6"], [982, 1, "\u03C0"], [983, 2], [984, 1, "\u03D9"], [985, 2], [986, 1, "\u03DB"], [987, 2], [988, 1, "\u03DD"], [989, 2], [990, 1, "\u03DF"], [991, 2], [992, 1, "\u03E1"], [993, 2], [994, 1, "\u03E3"], [995, 2], [996, 1, "\u03E5"], [997, 2], [998, 1, "\u03E7"], [999, 2], [1e3, 1, "\u03E9"], [1001, 2], [1002, 1, "\u03EB"], [1003, 2], [1004, 1, "\u03ED"], [1005, 2], [1006, 1, "\u03EF"], [1007, 2], [1008, 1, "\u03BA"], [1009, 1, "\u03C1"], [1010, 1, "\u03C3"], [1011, 2], [1012, 1, "\u03B8"], [1013, 1, "\u03B5"], [1014, 2], [1015, 1, "\u03F8"], [1016, 2], [1017, 1, "\u03C3"], [1018, 1, "\u03FB"], [1019, 2], [1020, 2], [1021, 1, "\u037B"], [1022, 1, "\u037C"], [1023, 1, "\u037D"], [1024, 1, "\u0450"], [1025, 1, "\u0451"], [1026, 1, "\u0452"], [1027, 1, "\u0453"], [1028, 1, "\u0454"], [1029, 1, "\u0455"], [1030, 1, "\u0456"], [1031, 1, "\u0457"], [1032, 1, "\u0458"], [1033, 1, "\u0459"], [1034, 1, "\u045A"], [1035, 1, "\u045B"], [1036, 1, "\u045C"], [1037, 1, "\u045D"], [1038, 1, "\u045E"], [1039, 1, "\u045F"], [1040, 1, "\u0430"], [1041, 1, "\u0431"], [1042, 1, "\u0432"], [1043, 1, "\u0433"], [1044, 1, "\u0434"], [1045, 1, "\u0435"], [1046, 1, "\u0436"], [1047, 1, "\u0437"], [1048, 1, "\u0438"], [1049, 1, "\u0439"], [1050, 1, "\u043A"], [1051, 1, "\u043B"], [1052, 1, "\u043C"], [1053, 1, "\u043D"], [1054, 1, "\u043E"], [1055, 1, "\u043F"], [1056, 1, "\u0440"], [1057, 1, "\u0441"], [1058, 1, "\u0442"], [1059, 1, "\u0443"], [1060, 1, "\u0444"], [1061, 1, "\u0445"], [1062, 1, "\u0446"], [1063, 1, "\u0447"], [1064, 1, "\u0448"], [1065, 1, "\u0449"], [1066, 1, "\u044A"], [1067, 1, "\u044B"], [1068, 1, "\u044C"], [1069, 1, "\u044D"], [1070, 1, "\u044E"], [1071, 1, "\u044F"], [[1072, 1103], 2], [1104, 2], [[1105, 1116], 2], [1117, 2], [[1118, 1119], 2], [1120, 1, "\u0461"], [1121, 2], [1122, 1, "\u0463"], [1123, 2], [1124, 1, "\u0465"], [1125, 2], [1126, 1, "\u0467"], [1127, 2], [1128, 1, "\u0469"], [1129, 2], [1130, 1, "\u046B"], [1131, 2], [1132, 1, "\u046D"], [1133, 2], [1134, 1, "\u046F"], [1135, 2], [1136, 1, "\u0471"], [1137, 2], [1138, 1, "\u0473"], [1139, 2], [1140, 1, "\u0475"], [1141, 2], [1142, 1, "\u0477"], [1143, 2], [1144, 1, "\u0479"], [1145, 2], [1146, 1, "\u047B"], [1147, 2], [1148, 1, "\u047D"], [1149, 2], [1150, 1, "\u047F"], [1151, 2], [1152, 1, "\u0481"], [1153, 2], [1154, 2], [[1155, 1158], 2], [1159, 2], [[1160, 1161], 2], [1162, 1, "\u048B"], [1163, 2], [1164, 1, "\u048D"], [1165, 2], [1166, 1, "\u048F"], [1167, 2], [1168, 1, "\u0491"], [1169, 2], [1170, 1, "\u0493"], [1171, 2], [1172, 1, "\u0495"], [1173, 2], [1174, 1, "\u0497"], [1175, 2], [1176, 1, "\u0499"], [1177, 2], [1178, 1, "\u049B"], [1179, 2], [1180, 1, "\u049D"], [1181, 2], [1182, 1, "\u049F"], [1183, 2], [1184, 1, "\u04A1"], [1185, 2], [1186, 1, "\u04A3"], [1187, 2], [1188, 1, "\u04A5"], [1189, 2], [1190, 1, "\u04A7"], [1191, 2], [1192, 1, "\u04A9"], [1193, 2], [1194, 1, "\u04AB"], [1195, 2], [1196, 1, "\u04AD"], [1197, 2], [1198, 1, "\u04AF"], [1199, 2], [1200, 1, "\u04B1"], [1201, 2], [1202, 1, "\u04B3"], [1203, 2], [1204, 1, "\u04B5"], [1205, 2], [1206, 1, "\u04B7"], [1207, 2], [1208, 1, "\u04B9"], [1209, 2], [1210, 1, "\u04BB"], [1211, 2], [1212, 1, "\u04BD"], [1213, 2], [1214, 1, "\u04BF"], [1215, 2], [1216, 3], [1217, 1, "\u04C2"], [1218, 2], [1219, 1, "\u04C4"], [1220, 2], [1221, 1, "\u04C6"], [1222, 2], [1223, 1, "\u04C8"], [1224, 2], [1225, 1, "\u04CA"], [1226, 2], [1227, 1, "\u04CC"], [1228, 2], [1229, 1, "\u04CE"], [1230, 2], [1231, 2], [1232, 1, "\u04D1"], [1233, 2], [1234, 1, "\u04D3"], [1235, 2], [1236, 1, "\u04D5"], [1237, 2], [1238, 1, "\u04D7"], [1239, 2], [1240, 1, "\u04D9"], [1241, 2], [1242, 1, "\u04DB"], [1243, 2], [1244, 1, "\u04DD"], [1245, 2], [1246, 1, "\u04DF"], [1247, 2], [1248, 1, "\u04E1"], [1249, 2], [1250, 1, "\u04E3"], [1251, 2], [1252, 1, "\u04E5"], [1253, 2], [1254, 1, "\u04E7"], [1255, 2], [1256, 1, "\u04E9"], [1257, 2], [1258, 1, "\u04EB"], [1259, 2], [1260, 1, "\u04ED"], [1261, 2], [1262, 1, "\u04EF"], [1263, 2], [1264, 1, "\u04F1"], [1265, 2], [1266, 1, "\u04F3"], [1267, 2], [1268, 1, "\u04F5"], [1269, 2], [1270, 1, "\u04F7"], [1271, 2], [1272, 1, "\u04F9"], [1273, 2], [1274, 1, "\u04FB"], [1275, 2], [1276, 1, "\u04FD"], [1277, 2], [1278, 1, "\u04FF"], [1279, 2], [1280, 1, "\u0501"], [1281, 2], [1282, 1, "\u0503"], [1283, 2], [1284, 1, "\u0505"], [1285, 2], [1286, 1, "\u0507"], [1287, 2], [1288, 1, "\u0509"], [1289, 2], [1290, 1, "\u050B"], [1291, 2], [1292, 1, "\u050D"], [1293, 2], [1294, 1, "\u050F"], [1295, 2], [1296, 1, "\u0511"], [1297, 2], [1298, 1, "\u0513"], [1299, 2], [1300, 1, "\u0515"], [1301, 2], [1302, 1, "\u0517"], [1303, 2], [1304, 1, "\u0519"], [1305, 2], [1306, 1, "\u051B"], [1307, 2], [1308, 1, "\u051D"], [1309, 2], [1310, 1, "\u051F"], [1311, 2], [1312, 1, "\u0521"], [1313, 2], [1314, 1, "\u0523"], [1315, 2], [1316, 1, "\u0525"], [1317, 2], [1318, 1, "\u0527"], [1319, 2], [1320, 1, "\u0529"], [1321, 2], [1322, 1, "\u052B"], [1323, 2], [1324, 1, "\u052D"], [1325, 2], [1326, 1, "\u052F"], [1327, 2], [1328, 3], [1329, 1, "\u0561"], [1330, 1, "\u0562"], [1331, 1, "\u0563"], [1332, 1, "\u0564"], [1333, 1, "\u0565"], [1334, 1, "\u0566"], [1335, 1, "\u0567"], [1336, 1, "\u0568"], [1337, 1, "\u0569"], [1338, 1, "\u056A"], [1339, 1, "\u056B"], [1340, 1, "\u056C"], [1341, 1, "\u056D"], [1342, 1, "\u056E"], [1343, 1, "\u056F"], [1344, 1, "\u0570"], [1345, 1, "\u0571"], [1346, 1, "\u0572"], [1347, 1, "\u0573"], [1348, 1, "\u0574"], [1349, 1, "\u0575"], [1350, 1, "\u0576"], [1351, 1, "\u0577"], [1352, 1, "\u0578"], [1353, 1, "\u0579"], [1354, 1, "\u057A"], [1355, 1, "\u057B"], [1356, 1, "\u057C"], [1357, 1, "\u057D"], [1358, 1, "\u057E"], [1359, 1, "\u057F"], [1360, 1, "\u0580"], [1361, 1, "\u0581"], [1362, 1, "\u0582"], [1363, 1, "\u0583"], [1364, 1, "\u0584"], [1365, 1, "\u0585"], [1366, 1, "\u0586"], [[1367, 1368], 3], [1369, 2], [[1370, 1375], 2], [1376, 2], [[1377, 1414], 2], [1415, 1, "\u0565\u0582"], [1416, 2], [1417, 2], [1418, 2], [[1419, 1420], 3], [[1421, 1422], 2], [1423, 2], [1424, 3], [[1425, 1441], 2], [1442, 2], [[1443, 1455], 2], [[1456, 1465], 2], [1466, 2], [[1467, 1469], 2], [1470, 2], [1471, 2], [1472, 2], [[1473, 1474], 2], [1475, 2], [1476, 2], [1477, 2], [1478, 2], [1479, 2], [[1480, 1487], 3], [[1488, 1514], 2], [[1515, 1518], 3], [1519, 2], [[1520, 1524], 2], [[1525, 1535], 3], [[1536, 1539], 3], [1540, 3], [1541, 3], [[1542, 1546], 2], [1547, 2], [1548, 2], [[1549, 1551], 2], [[1552, 1557], 2], [[1558, 1562], 2], [1563, 2], [1564, 3], [1565, 2], [1566, 2], [1567, 2], [1568, 2], [[1569, 1594], 2], [[1595, 1599], 2], [1600, 2], [[1601, 1618], 2], [[1619, 1621], 2], [[1622, 1624], 2], [[1625, 1630], 2], [1631, 2], [[1632, 1641], 2], [[1642, 1645], 2], [[1646, 1647], 2], [[1648, 1652], 2], [1653, 1, "\u0627\u0674"], [1654, 1, "\u0648\u0674"], [1655, 1, "\u06C7\u0674"], [1656, 1, "\u064A\u0674"], [[1657, 1719], 2], [[1720, 1721], 2], [[1722, 1726], 2], [1727, 2], [[1728, 1742], 2], [1743, 2], [[1744, 1747], 2], [1748, 2], [[1749, 1756], 2], [1757, 3], [1758, 2], [[1759, 1768], 2], [1769, 2], [[1770, 1773], 2], [[1774, 1775], 2], [[1776, 1785], 2], [[1786, 1790], 2], [1791, 2], [[1792, 1805], 2], [1806, 3], [1807, 3], [[1808, 1836], 2], [[1837, 1839], 2], [[1840, 1866], 2], [[1867, 1868], 3], [[1869, 1871], 2], [[1872, 1901], 2], [[1902, 1919], 2], [[1920, 1968], 2], [1969, 2], [[1970, 1983], 3], [[1984, 2037], 2], [[2038, 2042], 2], [[2043, 2044], 3], [2045, 2], [[2046, 2047], 2], [[2048, 2093], 2], [[2094, 2095], 3], [[2096, 2110], 2], [2111, 3], [[2112, 2139], 2], [[2140, 2141], 3], [2142, 2], [2143, 3], [[2144, 2154], 2], [[2155, 2159], 3], [[2160, 2183], 2], [2184, 2], [[2185, 2190], 2], [2191, 3], [[2192, 2193], 3], [[2194, 2199], 3], [[2200, 2207], 2], [2208, 2], [2209, 2], [[2210, 2220], 2], [[2221, 2226], 2], [[2227, 2228], 2], [2229, 2], [[2230, 2237], 2], [[2238, 2247], 2], [[2248, 2258], 2], [2259, 2], [[2260, 2273], 2], [2274, 3], [2275, 2], [[2276, 2302], 2], [2303, 2], [2304, 2], [[2305, 2307], 2], [2308, 2], [[2309, 2361], 2], [[2362, 2363], 2], [[2364, 2381], 2], [2382, 2], [2383, 2], [[2384, 2388], 2], [2389, 2], [[2390, 2391], 2], [2392, 1, "\u0915\u093C"], [2393, 1, "\u0916\u093C"], [2394, 1, "\u0917\u093C"], [2395, 1, "\u091C\u093C"], [2396, 1, "\u0921\u093C"], [2397, 1, "\u0922\u093C"], [2398, 1, "\u092B\u093C"], [2399, 1, "\u092F\u093C"], [[2400, 2403], 2], [[2404, 2405], 2], [[2406, 2415], 2], [2416, 2], [[2417, 2418], 2], [[2419, 2423], 2], [2424, 2], [[2425, 2426], 2], [[2427, 2428], 2], [2429, 2], [[2430, 2431], 2], [2432, 2], [[2433, 2435], 2], [2436, 3], [[2437, 2444], 2], [[2445, 2446], 3], [[2447, 2448], 2], [[2449, 2450], 3], [[2451, 2472], 2], [2473, 3], [[2474, 2480], 2], [2481, 3], [2482, 2], [[2483, 2485], 3], [[2486, 2489], 2], [[2490, 2491], 3], [2492, 2], [2493, 2], [[2494, 2500], 2], [[2501, 2502], 3], [[2503, 2504], 2], [[2505, 2506], 3], [[2507, 2509], 2], [2510, 2], [[2511, 2518], 3], [2519, 2], [[2520, 2523], 3], [2524, 1, "\u09A1\u09BC"], [2525, 1, "\u09A2\u09BC"], [2526, 3], [2527, 1, "\u09AF\u09BC"], [[2528, 2531], 2], [[2532, 2533], 3], [[2534, 2545], 2], [[2546, 2554], 2], [2555, 2], [2556, 2], [2557, 2], [2558, 2], [[2559, 2560], 3], [2561, 2], [2562, 2], [2563, 2], [2564, 3], [[2565, 2570], 2], [[2571, 2574], 3], [[2575, 2576], 2], [[2577, 2578], 3], [[2579, 2600], 2], [2601, 3], [[2602, 2608], 2], [2609, 3], [2610, 2], [2611, 1, "\u0A32\u0A3C"], [2612, 3], [2613, 2], [2614, 1, "\u0A38\u0A3C"], [2615, 3], [[2616, 2617], 2], [[2618, 2619], 3], [2620, 2], [2621, 3], [[2622, 2626], 2], [[2627, 2630], 3], [[2631, 2632], 2], [[2633, 2634], 3], [[2635, 2637], 2], [[2638, 2640], 3], [2641, 2], [[2642, 2648], 3], [2649, 1, "\u0A16\u0A3C"], [2650, 1, "\u0A17\u0A3C"], [2651, 1, "\u0A1C\u0A3C"], [2652, 2], [2653, 3], [2654, 1, "\u0A2B\u0A3C"], [[2655, 2661], 3], [[2662, 2676], 2], [2677, 2], [2678, 2], [[2679, 2688], 3], [[2689, 2691], 2], [2692, 3], [[2693, 2699], 2], [2700, 2], [2701, 2], [2702, 3], [[2703, 2705], 2], [2706, 3], [[2707, 2728], 2], [2729, 3], [[2730, 2736], 2], [2737, 3], [[2738, 2739], 2], [2740, 3], [[2741, 2745], 2], [[2746, 2747], 3], [[2748, 2757], 2], [2758, 3], [[2759, 2761], 2], [2762, 3], [[2763, 2765], 2], [[2766, 2767], 3], [2768, 2], [[2769, 2783], 3], [2784, 2], [[2785, 2787], 2], [[2788, 2789], 3], [[2790, 2799], 2], [2800, 2], [2801, 2], [[2802, 2808], 3], [2809, 2], [[2810, 2815], 2], [2816, 3], [[2817, 2819], 2], [2820, 3], [[2821, 2828], 2], [[2829, 2830], 3], [[2831, 2832], 2], [[2833, 2834], 3], [[2835, 2856], 2], [2857, 3], [[2858, 2864], 2], [2865, 3], [[2866, 2867], 2], [2868, 3], [2869, 2], [[2870, 2873], 2], [[2874, 2875], 3], [[2876, 2883], 2], [2884, 2], [[2885, 2886], 3], [[2887, 2888], 2], [[2889, 2890], 3], [[2891, 2893], 2], [[2894, 2900], 3], [2901, 2], [[2902, 2903], 2], [[2904, 2907], 3], [2908, 1, "\u0B21\u0B3C"], [2909, 1, "\u0B22\u0B3C"], [2910, 3], [[2911, 2913], 2], [[2914, 2915], 2], [[2916, 2917], 3], [[2918, 2927], 2], [2928, 2], [2929, 2], [[2930, 2935], 2], [[2936, 2945], 3], [[2946, 2947], 2], [2948, 3], [[2949, 2954], 2], [[2955, 2957], 3], [[2958, 2960], 2], [2961, 3], [[2962, 2965], 2], [[2966, 2968], 3], [[2969, 2970], 2], [2971, 3], [2972, 2], [2973, 3], [[2974, 2975], 2], [[2976, 2978], 3], [[2979, 2980], 2], [[2981, 2983], 3], [[2984, 2986], 2], [[2987, 2989], 3], [[2990, 2997], 2], [2998, 2], [[2999, 3001], 2], [[3002, 3005], 3], [[3006, 3010], 2], [[3011, 3013], 3], [[3014, 3016], 2], [3017, 3], [[3018, 3021], 2], [[3022, 3023], 3], [3024, 2], [[3025, 3030], 3], [3031, 2], [[3032, 3045], 3], [3046, 2], [[3047, 3055], 2], [[3056, 3058], 2], [[3059, 3066], 2], [[3067, 3071], 3], [3072, 2], [[3073, 3075], 2], [3076, 2], [[3077, 3084], 2], [3085, 3], [[3086, 3088], 2], [3089, 3], [[3090, 3112], 2], [3113, 3], [[3114, 3123], 2], [3124, 2], [[3125, 3129], 2], [[3130, 3131], 3], [3132, 2], [3133, 2], [[3134, 3140], 2], [3141, 3], [[3142, 3144], 2], [3145, 3], [[3146, 3149], 2], [[3150, 3156], 3], [[3157, 3158], 2], [3159, 3], [[3160, 3161], 2], [3162, 2], [[3163, 3164], 3], [3165, 2], [[3166, 3167], 3], [[3168, 3169], 2], [[3170, 3171], 2], [[3172, 3173], 3], [[3174, 3183], 2], [[3184, 3190], 3], [3191, 2], [[3192, 3199], 2], [3200, 2], [3201, 2], [[3202, 3203], 2], [3204, 2], [[3205, 3212], 2], [3213, 3], [[3214, 3216], 2], [3217, 3], [[3218, 3240], 2], [3241, 3], [[3242, 3251], 2], [3252, 3], [[3253, 3257], 2], [[3258, 3259], 3], [[3260, 3261], 2], [[3262, 3268], 2], [3269, 3], [[3270, 3272], 2], [3273, 3], [[3274, 3277], 2], [[3278, 3284], 3], [[3285, 3286], 2], [[3287, 3292], 3], [3293, 2], [3294, 2], [3295, 3], [[3296, 3297], 2], [[3298, 3299], 2], [[3300, 3301], 3], [[3302, 3311], 2], [3312, 3], [[3313, 3314], 2], [3315, 2], [[3316, 3327], 3], [3328, 2], [3329, 2], [[3330, 3331], 2], [3332, 2], [[3333, 3340], 2], [3341, 3], [[3342, 3344], 2], [3345, 3], [[3346, 3368], 2], [3369, 2], [[3370, 3385], 2], [3386, 2], [[3387, 3388], 2], [3389, 2], [[3390, 3395], 2], [3396, 2], [3397, 3], [[3398, 3400], 2], [3401, 3], [[3402, 3405], 2], [3406, 2], [3407, 2], [[3408, 3411], 3], [[3412, 3414], 2], [3415, 2], [[3416, 3422], 2], [3423, 2], [[3424, 3425], 2], [[3426, 3427], 2], [[3428, 3429], 3], [[3430, 3439], 2], [[3440, 3445], 2], [[3446, 3448], 2], [3449, 2], [[3450, 3455], 2], [3456, 3], [3457, 2], [[3458, 3459], 2], [3460, 3], [[3461, 3478], 2], [[3479, 3481], 3], [[3482, 3505], 2], [3506, 3], [[3507, 3515], 2], [3516, 3], [3517, 2], [[3518, 3519], 3], [[3520, 3526], 2], [[3527, 3529], 3], [3530, 2], [[3531, 3534], 3], [[3535, 3540], 2], [3541, 3], [3542, 2], [3543, 3], [[3544, 3551], 2], [[3552, 3557], 3], [[3558, 3567], 2], [[3568, 3569], 3], [[3570, 3571], 2], [3572, 2], [[3573, 3584], 3], [[3585, 3634], 2], [3635, 1, "\u0E4D\u0E32"], [[3636, 3642], 2], [[3643, 3646], 3], [3647, 2], [[3648, 3662], 2], [3663, 2], [[3664, 3673], 2], [[3674, 3675], 2], [[3676, 3712], 3], [[3713, 3714], 2], [3715, 3], [3716, 2], [3717, 3], [3718, 2], [[3719, 3720], 2], [3721, 2], [3722, 2], [3723, 3], [3724, 2], [3725, 2], [[3726, 3731], 2], [[3732, 3735], 2], [3736, 2], [[3737, 3743], 2], [3744, 2], [[3745, 3747], 2], [3748, 3], [3749, 2], [3750, 3], [3751, 2], [[3752, 3753], 2], [[3754, 3755], 2], [3756, 2], [[3757, 3762], 2], [3763, 1, "\u0ECD\u0EB2"], [[3764, 3769], 2], [3770, 2], [[3771, 3773], 2], [[3774, 3775], 3], [[3776, 3780], 2], [3781, 3], [3782, 2], [3783, 3], [[3784, 3789], 2], [3790, 2], [3791, 3], [[3792, 3801], 2], [[3802, 3803], 3], [3804, 1, "\u0EAB\u0E99"], [3805, 1, "\u0EAB\u0EA1"], [[3806, 3807], 2], [[3808, 3839], 3], [3840, 2], [[3841, 3850], 2], [3851, 2], [3852, 1, "\u0F0B"], [[3853, 3863], 2], [[3864, 3865], 2], [[3866, 3871], 2], [[3872, 3881], 2], [[3882, 3892], 2], [3893, 2], [3894, 2], [3895, 2], [3896, 2], [3897, 2], [[3898, 3901], 2], [[3902, 3906], 2], [3907, 1, "\u0F42\u0FB7"], [[3908, 3911], 2], [3912, 3], [[3913, 3916], 2], [3917, 1, "\u0F4C\u0FB7"], [[3918, 3921], 2], [3922, 1, "\u0F51\u0FB7"], [[3923, 3926], 2], [3927, 1, "\u0F56\u0FB7"], [[3928, 3931], 2], [3932, 1, "\u0F5B\u0FB7"], [[3933, 3944], 2], [3945, 1, "\u0F40\u0FB5"], [3946, 2], [[3947, 3948], 2], [[3949, 3952], 3], [[3953, 3954], 2], [3955, 1, "\u0F71\u0F72"], [3956, 2], [3957, 1, "\u0F71\u0F74"], [3958, 1, "\u0FB2\u0F80"], [3959, 1, "\u0FB2\u0F71\u0F80"], [3960, 1, "\u0FB3\u0F80"], [3961, 1, "\u0FB3\u0F71\u0F80"], [[3962, 3968], 2], [3969, 1, "\u0F71\u0F80"], [[3970, 3972], 2], [3973, 2], [[3974, 3979], 2], [[3980, 3983], 2], [[3984, 3986], 2], [3987, 1, "\u0F92\u0FB7"], [[3988, 3989], 2], [3990, 2], [3991, 2], [3992, 3], [[3993, 3996], 2], [3997, 1, "\u0F9C\u0FB7"], [[3998, 4001], 2], [4002, 1, "\u0FA1\u0FB7"], [[4003, 4006], 2], [4007, 1, "\u0FA6\u0FB7"], [[4008, 4011], 2], [4012, 1, "\u0FAB\u0FB7"], [4013, 2], [[4014, 4016], 2], [[4017, 4023], 2], [4024, 2], [4025, 1, "\u0F90\u0FB5"], [[4026, 4028], 2], [4029, 3], [[4030, 4037], 2], [4038, 2], [[4039, 4044], 2], [4045, 3], [4046, 2], [4047, 2], [[4048, 4049], 2], [[4050, 4052], 2], [[4053, 4056], 2], [[4057, 4058], 2], [[4059, 4095], 3], [[4096, 4129], 2], [4130, 2], [[4131, 4135], 2], [4136, 2], [[4137, 4138], 2], [4139, 2], [[4140, 4146], 2], [[4147, 4149], 2], [[4150, 4153], 2], [[4154, 4159], 2], [[4160, 4169], 2], [[4170, 4175], 2], [[4176, 4185], 2], [[4186, 4249], 2], [[4250, 4253], 2], [[4254, 4255], 2], [[4256, 4293], 3], [4294, 3], [4295, 1, "\u2D27"], [[4296, 4300], 3], [4301, 1, "\u2D2D"], [[4302, 4303], 3], [[4304, 4342], 2], [[4343, 4344], 2], [[4345, 4346], 2], [4347, 2], [4348, 1, "\u10DC"], [[4349, 4351], 2], [[4352, 4441], 2], [[4442, 4446], 2], [[4447, 4448], 3], [[4449, 4514], 2], [[4515, 4519], 2], [[4520, 4601], 2], [[4602, 4607], 2], [[4608, 4614], 2], [4615, 2], [[4616, 4678], 2], [4679, 2], [4680, 2], [4681, 3], [[4682, 4685], 2], [[4686, 4687], 3], [[4688, 4694], 2], [4695, 3], [4696, 2], [4697, 3], [[4698, 4701], 2], [[4702, 4703], 3], [[4704, 4742], 2], [4743, 2], [4744, 2], [4745, 3], [[4746, 4749], 2], [[4750, 4751], 3], [[4752, 4782], 2], [4783, 2], [4784, 2], [4785, 3], [[4786, 4789], 2], [[4790, 4791], 3], [[4792, 4798], 2], [4799, 3], [4800, 2], [4801, 3], [[4802, 4805], 2], [[4806, 4807], 3], [[4808, 4814], 2], [4815, 2], [[4816, 4822], 2], [4823, 3], [[4824, 4846], 2], [4847, 2], [[4848, 4878], 2], [4879, 2], [4880, 2], [4881, 3], [[4882, 4885], 2], [[4886, 4887], 3], [[4888, 4894], 2], [4895, 2], [[4896, 4934], 2], [4935, 2], [[4936, 4954], 2], [[4955, 4956], 3], [[4957, 4958], 2], [4959, 2], [4960, 2], [[4961, 4988], 2], [[4989, 4991], 3], [[4992, 5007], 2], [[5008, 5017], 2], [[5018, 5023], 3], [[5024, 5108], 2], [5109, 2], [[5110, 5111], 3], [5112, 1, "\u13F0"], [5113, 1, "\u13F1"], [5114, 1, "\u13F2"], [5115, 1, "\u13F3"], [5116, 1, "\u13F4"], [5117, 1, "\u13F5"], [[5118, 5119], 3], [5120, 2], [[5121, 5740], 2], [[5741, 5742], 2], [[5743, 5750], 2], [[5751, 5759], 2], [5760, 3], [[5761, 5786], 2], [[5787, 5788], 2], [[5789, 5791], 3], [[5792, 5866], 2], [[5867, 5872], 2], [[5873, 5880], 2], [[5881, 5887], 3], [[5888, 5900], 2], [5901, 2], [[5902, 5908], 2], [5909, 2], [[5910, 5918], 3], [5919, 2], [[5920, 5940], 2], [[5941, 5942], 2], [[5943, 5951], 3], [[5952, 5971], 2], [[5972, 5983], 3], [[5984, 5996], 2], [5997, 3], [[5998, 6e3], 2], [6001, 3], [[6002, 6003], 2], [[6004, 6015], 3], [[6016, 6067], 2], [[6068, 6069], 3], [[6070, 6099], 2], [[6100, 6102], 2], [6103, 2], [[6104, 6107], 2], [6108, 2], [6109, 2], [[6110, 6111], 3], [[6112, 6121], 2], [[6122, 6127], 3], [[6128, 6137], 2], [[6138, 6143], 3], [[6144, 6149], 2], [6150, 3], [[6151, 6154], 2], [[6155, 6157], 7], [6158, 3], [6159, 7], [[6160, 6169], 2], [[6170, 6175], 3], [[6176, 6263], 2], [6264, 2], [[6265, 6271], 3], [[6272, 6313], 2], [6314, 2], [[6315, 6319], 3], [[6320, 6389], 2], [[6390, 6399], 3], [[6400, 6428], 2], [[6429, 6430], 2], [6431, 3], [[6432, 6443], 2], [[6444, 6447], 3], [[6448, 6459], 2], [[6460, 6463], 3], [6464, 2], [[6465, 6467], 3], [[6468, 6469], 2], [[6470, 6509], 2], [[6510, 6511], 3], [[6512, 6516], 2], [[6517, 6527], 3], [[6528, 6569], 2], [[6570, 6571], 2], [[6572, 6575], 3], [[6576, 6601], 2], [[6602, 6607], 3], [[6608, 6617], 2], [6618, 2], [[6619, 6621], 3], [[6622, 6623], 2], [[6624, 6655], 2], [[6656, 6683], 2], [[6684, 6685], 3], [[6686, 6687], 2], [[6688, 6750], 2], [6751, 3], [[6752, 6780], 2], [[6781, 6782], 3], [[6783, 6793], 2], [[6794, 6799], 3], [[6800, 6809], 2], [[6810, 6815], 3], [[6816, 6822], 2], [6823, 2], [[6824, 6829], 2], [[6830, 6831], 3], [[6832, 6845], 2], [6846, 2], [[6847, 6848], 2], [[6849, 6862], 2], [[6863, 6911], 3], [[6912, 6987], 2], [6988, 2], [[6989, 6991], 3], [[6992, 7001], 2], [[7002, 7018], 2], [[7019, 7027], 2], [[7028, 7036], 2], [[7037, 7038], 2], [7039, 3], [[7040, 7082], 2], [[7083, 7085], 2], [[7086, 7097], 2], [[7098, 7103], 2], [[7104, 7155], 2], [[7156, 7163], 3], [[7164, 7167], 2], [[7168, 7223], 2], [[7224, 7226], 3], [[7227, 7231], 2], [[7232, 7241], 2], [[7242, 7244], 3], [[7245, 7293], 2], [[7294, 7295], 2], [7296, 1, "\u0432"], [7297, 1, "\u0434"], [7298, 1, "\u043E"], [7299, 1, "\u0441"], [[7300, 7301], 1, "\u0442"], [7302, 1, "\u044A"], [7303, 1, "\u0463"], [7304, 1, "\uA64B"], [[7305, 7311], 3], [7312, 1, "\u10D0"], [7313, 1, "\u10D1"], [7314, 1, "\u10D2"], [7315, 1, "\u10D3"], [7316, 1, "\u10D4"], [7317, 1, "\u10D5"], [7318, 1, "\u10D6"], [7319, 1, "\u10D7"], [7320, 1, "\u10D8"], [7321, 1, "\u10D9"], [7322, 1, "\u10DA"], [7323, 1, "\u10DB"], [7324, 1, "\u10DC"], [7325, 1, "\u10DD"], [7326, 1, "\u10DE"], [7327, 1, "\u10DF"], [7328, 1, "\u10E0"], [7329, 1, "\u10E1"], [7330, 1, "\u10E2"], [7331, 1, "\u10E3"], [7332, 1, "\u10E4"], [7333, 1, "\u10E5"], [7334, 1, "\u10E6"], [7335, 1, "\u10E7"], [7336, 1, "\u10E8"], [7337, 1, "\u10E9"], [7338, 1, "\u10EA"], [7339, 1, "\u10EB"], [7340, 1, "\u10EC"], [7341, 1, "\u10ED"], [7342, 1, "\u10EE"], [7343, 1, "\u10EF"], [7344, 1, "\u10F0"], [7345, 1, "\u10F1"], [7346, 1, "\u10F2"], [7347, 1, "\u10F3"], [7348, 1, "\u10F4"], [7349, 1, "\u10F5"], [7350, 1, "\u10F6"], [7351, 1, "\u10F7"], [7352, 1, "\u10F8"], [7353, 1, "\u10F9"], [7354, 1, "\u10FA"], [[7355, 7356], 3], [7357, 1, "\u10FD"], [7358, 1, "\u10FE"], [7359, 1, "\u10FF"], [[7360, 7367], 2], [[7368, 7375], 3], [[7376, 7378], 2], [7379, 2], [[7380, 7410], 2], [[7411, 7414], 2], [7415, 2], [[7416, 7417], 2], [7418, 2], [[7419, 7423], 3], [[7424, 7467], 2], [7468, 1, "a"], [7469, 1, "\xE6"], [7470, 1, "b"], [7471, 2], [7472, 1, "d"], [7473, 1, "e"], [7474, 1, "\u01DD"], [7475, 1, "g"], [7476, 1, "h"], [7477, 1, "i"], [7478, 1, "j"], [7479, 1, "k"], [7480, 1, "l"], [7481, 1, "m"], [7482, 1, "n"], [7483, 2], [7484, 1, "o"], [7485, 1, "\u0223"], [7486, 1, "p"], [7487, 1, "r"], [7488, 1, "t"], [7489, 1, "u"], [7490, 1, "w"], [7491, 1, "a"], [7492, 1, "\u0250"], [7493, 1, "\u0251"], [7494, 1, "\u1D02"], [7495, 1, "b"], [7496, 1, "d"], [7497, 1, "e"], [7498, 1, "\u0259"], [7499, 1, "\u025B"], [7500, 1, "\u025C"], [7501, 1, "g"], [7502, 2], [7503, 1, "k"], [7504, 1, "m"], [7505, 1, "\u014B"], [7506, 1, "o"], [7507, 1, "\u0254"], [7508, 1, "\u1D16"], [7509, 1, "\u1D17"], [7510, 1, "p"], [7511, 1, "t"], [7512, 1, "u"], [7513, 1, "\u1D1D"], [7514, 1, "\u026F"], [7515, 1, "v"], [7516, 1, "\u1D25"], [7517, 1, "\u03B2"], [7518, 1, "\u03B3"], [7519, 1, "\u03B4"], [7520, 1, "\u03C6"], [7521, 1, "\u03C7"], [7522, 1, "i"], [7523, 1, "r"], [7524, 1, "u"], [7525, 1, "v"], [7526, 1, "\u03B2"], [7527, 1, "\u03B3"], [7528, 1, "\u03C1"], [7529, 1, "\u03C6"], [7530, 1, "\u03C7"], [7531, 2], [[7532, 7543], 2], [7544, 1, "\u043D"], [[7545, 7578], 2], [7579, 1, "\u0252"], [7580, 1, "c"], [7581, 1, "\u0255"], [7582, 1, "\xF0"], [7583, 1, "\u025C"], [7584, 1, "f"], [7585, 1, "\u025F"], [7586, 1, "\u0261"], [7587, 1, "\u0265"], [7588, 1, "\u0268"], [7589, 1, "\u0269"], [7590, 1, "\u026A"], [7591, 1, "\u1D7B"], [7592, 1, "\u029D"], [7593, 1, "\u026D"], [7594, 1, "\u1D85"], [7595, 1, "\u029F"], [7596, 1, "\u0271"], [7597, 1, "\u0270"], [7598, 1, "\u0272"], [7599, 1, "\u0273"], [7600, 1, "\u0274"], [7601, 1, "\u0275"], [7602, 1, "\u0278"], [7603, 1, "\u0282"], [7604, 1, "\u0283"], [7605, 1, "\u01AB"], [7606, 1, "\u0289"], [7607, 1, "\u028A"], [7608, 1, "\u1D1C"], [7609, 1, "\u028B"], [7610, 1, "\u028C"], [7611, 1, "z"], [7612, 1, "\u0290"], [7613, 1, "\u0291"], [7614, 1, "\u0292"], [7615, 1, "\u03B8"], [[7616, 7619], 2], [[7620, 7626], 2], [[7627, 7654], 2], [[7655, 7669], 2], [[7670, 7673], 2], [7674, 2], [7675, 2], [7676, 2], [7677, 2], [[7678, 7679], 2], [7680, 1, "\u1E01"], [7681, 2], [7682, 1, "\u1E03"], [7683, 2], [7684, 1, "\u1E05"], [7685, 2], [7686, 1, "\u1E07"], [7687, 2], [7688, 1, "\u1E09"], [7689, 2], [7690, 1, "\u1E0B"], [7691, 2], [7692, 1, "\u1E0D"], [7693, 2], [7694, 1, "\u1E0F"], [7695, 2], [7696, 1, "\u1E11"], [7697, 2], [7698, 1, "\u1E13"], [7699, 2], [7700, 1, "\u1E15"], [7701, 2], [7702, 1, "\u1E17"], [7703, 2], [7704, 1, "\u1E19"], [7705, 2], [7706, 1, "\u1E1B"], [7707, 2], [7708, 1, "\u1E1D"], [7709, 2], [7710, 1, "\u1E1F"], [7711, 2], [7712, 1, "\u1E21"], [7713, 2], [7714, 1, "\u1E23"], [7715, 2], [7716, 1, "\u1E25"], [7717, 2], [7718, 1, "\u1E27"], [7719, 2], [7720, 1, "\u1E29"], [7721, 2], [7722, 1, "\u1E2B"], [7723, 2], [7724, 1, "\u1E2D"], [7725, 2], [7726, 1, "\u1E2F"], [7727, 2], [7728, 1, "\u1E31"], [7729, 2], [7730, 1, "\u1E33"], [7731, 2], [7732, 1, "\u1E35"], [7733, 2], [7734, 1, "\u1E37"], [7735, 2], [7736, 1, "\u1E39"], [7737, 2], [7738, 1, "\u1E3B"], [7739, 2], [7740, 1, "\u1E3D"], [7741, 2], [7742, 1, "\u1E3F"], [7743, 2], [7744, 1, "\u1E41"], [7745, 2], [7746, 1, "\u1E43"], [7747, 2], [7748, 1, "\u1E45"], [7749, 2], [7750, 1, "\u1E47"], [7751, 2], [7752, 1, "\u1E49"], [7753, 2], [7754, 1, "\u1E4B"], [7755, 2], [7756, 1, "\u1E4D"], [7757, 2], [7758, 1, "\u1E4F"], [7759, 2], [7760, 1, "\u1E51"], [7761, 2], [7762, 1, "\u1E53"], [7763, 2], [7764, 1, "\u1E55"], [7765, 2], [7766, 1, "\u1E57"], [7767, 2], [7768, 1, "\u1E59"], [7769, 2], [7770, 1, "\u1E5B"], [7771, 2], [7772, 1, "\u1E5D"], [7773, 2], [7774, 1, "\u1E5F"], [7775, 2], [7776, 1, "\u1E61"], [7777, 2], [7778, 1, "\u1E63"], [7779, 2], [7780, 1, "\u1E65"], [7781, 2], [7782, 1, "\u1E67"], [7783, 2], [7784, 1, "\u1E69"], [7785, 2], [7786, 1, "\u1E6B"], [7787, 2], [7788, 1, "\u1E6D"], [7789, 2], [7790, 1, "\u1E6F"], [7791, 2], [7792, 1, "\u1E71"], [7793, 2], [7794, 1, "\u1E73"], [7795, 2], [7796, 1, "\u1E75"], [7797, 2], [7798, 1, "\u1E77"], [7799, 2], [7800, 1, "\u1E79"], [7801, 2], [7802, 1, "\u1E7B"], [7803, 2], [7804, 1, "\u1E7D"], [7805, 2], [7806, 1, "\u1E7F"], [7807, 2], [7808, 1, "\u1E81"], [7809, 2], [7810, 1, "\u1E83"], [7811, 2], [7812, 1, "\u1E85"], [7813, 2], [7814, 1, "\u1E87"], [7815, 2], [7816, 1, "\u1E89"], [7817, 2], [7818, 1, "\u1E8B"], [7819, 2], [7820, 1, "\u1E8D"], [7821, 2], [7822, 1, "\u1E8F"], [7823, 2], [7824, 1, "\u1E91"], [7825, 2], [7826, 1, "\u1E93"], [7827, 2], [7828, 1, "\u1E95"], [[7829, 7833], 2], [7834, 1, "a\u02BE"], [7835, 1, "\u1E61"], [[7836, 7837], 2], [7838, 1, "\xDF"], [7839, 2], [7840, 1, "\u1EA1"], [7841, 2], [7842, 1, "\u1EA3"], [7843, 2], [7844, 1, "\u1EA5"], [7845, 2], [7846, 1, "\u1EA7"], [7847, 2], [7848, 1, "\u1EA9"], [7849, 2], [7850, 1, "\u1EAB"], [7851, 2], [7852, 1, "\u1EAD"], [7853, 2], [7854, 1, "\u1EAF"], [7855, 2], [7856, 1, "\u1EB1"], [7857, 2], [7858, 1, "\u1EB3"], [7859, 2], [7860, 1, "\u1EB5"], [7861, 2], [7862, 1, "\u1EB7"], [7863, 2], [7864, 1, "\u1EB9"], [7865, 2], [7866, 1, "\u1EBB"], [7867, 2], [7868, 1, "\u1EBD"], [7869, 2], [7870, 1, "\u1EBF"], [7871, 2], [7872, 1, "\u1EC1"], [7873, 2], [7874, 1, "\u1EC3"], [7875, 2], [7876, 1, "\u1EC5"], [7877, 2], [7878, 1, "\u1EC7"], [7879, 2], [7880, 1, "\u1EC9"], [7881, 2], [7882, 1, "\u1ECB"], [7883, 2], [7884, 1, "\u1ECD"], [7885, 2], [7886, 1, "\u1ECF"], [7887, 2], [7888, 1, "\u1ED1"], [7889, 2], [7890, 1, "\u1ED3"], [7891, 2], [7892, 1, "\u1ED5"], [7893, 2], [7894, 1, "\u1ED7"], [7895, 2], [7896, 1, "\u1ED9"], [7897, 2], [7898, 1, "\u1EDB"], [7899, 2], [7900, 1, "\u1EDD"], [7901, 2], [7902, 1, "\u1EDF"], [7903, 2], [7904, 1, "\u1EE1"], [7905, 2], [7906, 1, "\u1EE3"], [7907, 2], [7908, 1, "\u1EE5"], [7909, 2], [7910, 1, "\u1EE7"], [7911, 2], [7912, 1, "\u1EE9"], [7913, 2], [7914, 1, "\u1EEB"], [7915, 2], [7916, 1, "\u1EED"], [7917, 2], [7918, 1, "\u1EEF"], [7919, 2], [7920, 1, "\u1EF1"], [7921, 2], [7922, 1, "\u1EF3"], [7923, 2], [7924, 1, "\u1EF5"], [7925, 2], [7926, 1, "\u1EF7"], [7927, 2], [7928, 1, "\u1EF9"], [7929, 2], [7930, 1, "\u1EFB"], [7931, 2], [7932, 1, "\u1EFD"], [7933, 2], [7934, 1, "\u1EFF"], [7935, 2], [[7936, 7943], 2], [7944, 1, "\u1F00"], [7945, 1, "\u1F01"], [7946, 1, "\u1F02"], [7947, 1, "\u1F03"], [7948, 1, "\u1F04"], [7949, 1, "\u1F05"], [7950, 1, "\u1F06"], [7951, 1, "\u1F07"], [[7952, 7957], 2], [[7958, 7959], 3], [7960, 1, "\u1F10"], [7961, 1, "\u1F11"], [7962, 1, "\u1F12"], [7963, 1, "\u1F13"], [7964, 1, "\u1F14"], [7965, 1, "\u1F15"], [[7966, 7967], 3], [[7968, 7975], 2], [7976, 1, "\u1F20"], [7977, 1, "\u1F21"], [7978, 1, "\u1F22"], [7979, 1, "\u1F23"], [7980, 1, "\u1F24"], [7981, 1, "\u1F25"], [7982, 1, "\u1F26"], [7983, 1, "\u1F27"], [[7984, 7991], 2], [7992, 1, "\u1F30"], [7993, 1, "\u1F31"], [7994, 1, "\u1F32"], [7995, 1, "\u1F33"], [7996, 1, "\u1F34"], [7997, 1, "\u1F35"], [7998, 1, "\u1F36"], [7999, 1, "\u1F37"], [[8e3, 8005], 2], [[8006, 8007], 3], [8008, 1, "\u1F40"], [8009, 1, "\u1F41"], [8010, 1, "\u1F42"], [8011, 1, "\u1F43"], [8012, 1, "\u1F44"], [8013, 1, "\u1F45"], [[8014, 8015], 3], [[8016, 8023], 2], [8024, 3], [8025, 1, "\u1F51"], [8026, 3], [8027, 1, "\u1F53"], [8028, 3], [8029, 1, "\u1F55"], [8030, 3], [8031, 1, "\u1F57"], [[8032, 8039], 2], [8040, 1, "\u1F60"], [8041, 1, "\u1F61"], [8042, 1, "\u1F62"], [8043, 1, "\u1F63"], [8044, 1, "\u1F64"], [8045, 1, "\u1F65"], [8046, 1, "\u1F66"], [8047, 1, "\u1F67"], [8048, 2], [8049, 1, "\u03AC"], [8050, 2], [8051, 1, "\u03AD"], [8052, 2], [8053, 1, "\u03AE"], [8054, 2], [8055, 1, "\u03AF"], [8056, 2], [8057, 1, "\u03CC"], [8058, 2], [8059, 1, "\u03CD"], [8060, 2], [8061, 1, "\u03CE"], [[8062, 8063], 3], [8064, 1, "\u1F00\u03B9"], [8065, 1, "\u1F01\u03B9"], [8066, 1, "\u1F02\u03B9"], [8067, 1, "\u1F03\u03B9"], [8068, 1, "\u1F04\u03B9"], [8069, 1, "\u1F05\u03B9"], [8070, 1, "\u1F06\u03B9"], [8071, 1, "\u1F07\u03B9"], [8072, 1, "\u1F00\u03B9"], [8073, 1, "\u1F01\u03B9"], [8074, 1, "\u1F02\u03B9"], [8075, 1, "\u1F03\u03B9"], [8076, 1, "\u1F04\u03B9"], [8077, 1, "\u1F05\u03B9"], [8078, 1, "\u1F06\u03B9"], [8079, 1, "\u1F07\u03B9"], [8080, 1, "\u1F20\u03B9"], [8081, 1, "\u1F21\u03B9"], [8082, 1, "\u1F22\u03B9"], [8083, 1, "\u1F23\u03B9"], [8084, 1, "\u1F24\u03B9"], [8085, 1, "\u1F25\u03B9"], [8086, 1, "\u1F26\u03B9"], [8087, 1, "\u1F27\u03B9"], [8088, 1, "\u1F20\u03B9"], [8089, 1, "\u1F21\u03B9"], [8090, 1, "\u1F22\u03B9"], [8091, 1, "\u1F23\u03B9"], [8092, 1, "\u1F24\u03B9"], [8093, 1, "\u1F25\u03B9"], [8094, 1, "\u1F26\u03B9"], [8095, 1, "\u1F27\u03B9"], [8096, 1, "\u1F60\u03B9"], [8097, 1, "\u1F61\u03B9"], [8098, 1, "\u1F62\u03B9"], [8099, 1, "\u1F63\u03B9"], [8100, 1, "\u1F64\u03B9"], [8101, 1, "\u1F65\u03B9"], [8102, 1, "\u1F66\u03B9"], [8103, 1, "\u1F67\u03B9"], [8104, 1, "\u1F60\u03B9"], [8105, 1, "\u1F61\u03B9"], [8106, 1, "\u1F62\u03B9"], [8107, 1, "\u1F63\u03B9"], [8108, 1, "\u1F64\u03B9"], [8109, 1, "\u1F65\u03B9"], [8110, 1, "\u1F66\u03B9"], [8111, 1, "\u1F67\u03B9"], [[8112, 8113], 2], [8114, 1, "\u1F70\u03B9"], [8115, 1, "\u03B1\u03B9"], [8116, 1, "\u03AC\u03B9"], [8117, 3], [8118, 2], [8119, 1, "\u1FB6\u03B9"], [8120, 1, "\u1FB0"], [8121, 1, "\u1FB1"], [8122, 1, "\u1F70"], [8123, 1, "\u03AC"], [8124, 1, "\u03B1\u03B9"], [8125, 5, " \u0313"], [8126, 1, "\u03B9"], [8127, 5, " \u0313"], [8128, 5, " \u0342"], [8129, 5, " \u0308\u0342"], [8130, 1, "\u1F74\u03B9"], [8131, 1, "\u03B7\u03B9"], [8132, 1, "\u03AE\u03B9"], [8133, 3], [8134, 2], [8135, 1, "\u1FC6\u03B9"], [8136, 1, "\u1F72"], [8137, 1, "\u03AD"], [8138, 1, "\u1F74"], [8139, 1, "\u03AE"], [8140, 1, "\u03B7\u03B9"], [8141, 5, " \u0313\u0300"], [8142, 5, " \u0313\u0301"], [8143, 5, " \u0313\u0342"], [[8144, 8146], 2], [8147, 1, "\u0390"], [[8148, 8149], 3], [[8150, 8151], 2], [8152, 1, "\u1FD0"], [8153, 1, "\u1FD1"], [8154, 1, "\u1F76"], [8155, 1, "\u03AF"], [8156, 3], [8157, 5, " \u0314\u0300"], [8158, 5, " \u0314\u0301"], [8159, 5, " \u0314\u0342"], [[8160, 8162], 2], [8163, 1, "\u03B0"], [[8164, 8167], 2], [8168, 1, "\u1FE0"], [8169, 1, "\u1FE1"], [8170, 1, "\u1F7A"], [8171, 1, "\u03CD"], [8172, 1, "\u1FE5"], [8173, 5, " \u0308\u0300"], [8174, 5, " \u0308\u0301"], [8175, 5, "`"], [[8176, 8177], 3], [8178, 1, "\u1F7C\u03B9"], [8179, 1, "\u03C9\u03B9"], [8180, 1, "\u03CE\u03B9"], [8181, 3], [8182, 2], [8183, 1, "\u1FF6\u03B9"], [8184, 1, "\u1F78"], [8185, 1, "\u03CC"], [8186, 1, "\u1F7C"], [8187, 1, "\u03CE"], [8188, 1, "\u03C9\u03B9"], [8189, 5, " \u0301"], [8190, 5, " \u0314"], [8191, 3], [[8192, 8202], 5, " "], [8203, 7], [[8204, 8205], 6, ""], [[8206, 8207], 3], [8208, 2], [8209, 1, "\u2010"], [[8210, 8214], 2], [8215, 5, " \u0333"], [[8216, 8227], 2], [[8228, 8230], 3], [8231, 2], [[8232, 8238], 3], [8239, 5, " "], [[8240, 8242], 2], [8243, 1, "\u2032\u2032"], [8244, 1, "\u2032\u2032\u2032"], [8245, 2], [8246, 1, "\u2035\u2035"], [8247, 1, "\u2035\u2035\u2035"], [[8248, 8251], 2], [8252, 5, "!!"], [8253, 2], [8254, 5, " \u0305"], [[8255, 8262], 2], [8263, 5, "??"], [8264, 5, "?!"], [8265, 5, "!?"], [[8266, 8269], 2], [[8270, 8274], 2], [[8275, 8276], 2], [[8277, 8278], 2], [8279, 1, "\u2032\u2032\u2032\u2032"], [[8280, 8286], 2], [8287, 5, " "], [8288, 7], [[8289, 8291], 3], [8292, 7], [8293, 3], [[8294, 8297], 3], [[8298, 8303], 3], [8304, 1, "0"], [8305, 1, "i"], [[8306, 8307], 3], [8308, 1, "4"], [8309, 1, "5"], [8310, 1, "6"], [8311, 1, "7"], [8312, 1, "8"], [8313, 1, "9"], [8314, 5, "+"], [8315, 1, "\u2212"], [8316, 5, "="], [8317, 5, "("], [8318, 5, ")"], [8319, 1, "n"], [8320, 1, "0"], [8321, 1, "1"], [8322, 1, "2"], [8323, 1, "3"], [8324, 1, "4"], [8325, 1, "5"], [8326, 1, "6"], [8327, 1, "7"], [8328, 1, "8"], [8329, 1, "9"], [8330, 5, "+"], [8331, 1, "\u2212"], [8332, 5, "="], [8333, 5, "("], [8334, 5, ")"], [8335, 3], [8336, 1, "a"], [8337, 1, "e"], [8338, 1, "o"], [8339, 1, "x"], [8340, 1, "\u0259"], [8341, 1, "h"], [8342, 1, "k"], [8343, 1, "l"], [8344, 1, "m"], [8345, 1, "n"], [8346, 1, "p"], [8347, 1, "s"], [8348, 1, "t"], [[8349, 8351], 3], [[8352, 8359], 2], [8360, 1, "rs"], [[8361, 8362], 2], [8363, 2], [8364, 2], [[8365, 8367], 2], [[8368, 8369], 2], [[8370, 8373], 2], [[8374, 8376], 2], [8377, 2], [8378, 2], [[8379, 8381], 2], [8382, 2], [8383, 2], [8384, 2], [[8385, 8399], 3], [[8400, 8417], 2], [[8418, 8419], 2], [[8420, 8426], 2], [8427, 2], [[8428, 8431], 2], [8432, 2], [[8433, 8447], 3], [8448, 5, "a/c"], [8449, 5, "a/s"], [8450, 1, "c"], [8451, 1, "\xB0c"], [8452, 2], [8453, 5, "c/o"], [8454, 5, "c/u"], [8455, 1, "\u025B"], [8456, 2], [8457, 1, "\xB0f"], [8458, 1, "g"], [[8459, 8462], 1, "h"], [8463, 1, "\u0127"], [[8464, 8465], 1, "i"], [[8466, 8467], 1, "l"], [8468, 2], [8469, 1, "n"], [8470, 1, "no"], [[8471, 8472], 2], [8473, 1, "p"], [8474, 1, "q"], [[8475, 8477], 1, "r"], [[8478, 8479], 2], [8480, 1, "sm"], [8481, 1, "tel"], [8482, 1, "tm"], [8483, 2], [8484, 1, "z"], [8485, 2], [8486, 1, "\u03C9"], [8487, 2], [8488, 1, "z"], [8489, 2], [8490, 1, "k"], [8491, 1, "\xE5"], [8492, 1, "b"], [8493, 1, "c"], [8494, 2], [[8495, 8496], 1, "e"], [8497, 1, "f"], [8498, 3], [8499, 1, "m"], [8500, 1, "o"], [8501, 1, "\u05D0"], [8502, 1, "\u05D1"], [8503, 1, "\u05D2"], [8504, 1, "\u05D3"], [8505, 1, "i"], [8506, 2], [8507, 1, "fax"], [8508, 1, "\u03C0"], [[8509, 8510], 1, "\u03B3"], [8511, 1, "\u03C0"], [8512, 1, "\u2211"], [[8513, 8516], 2], [[8517, 8518], 1, "d"], [8519, 1, "e"], [8520, 1, "i"], [8521, 1, "j"], [[8522, 8523], 2], [8524, 2], [8525, 2], [8526, 2], [8527, 2], [8528, 1, "1\u20447"], [8529, 1, "1\u20449"], [8530, 1, "1\u204410"], [8531, 1, "1\u20443"], [8532, 1, "2\u20443"], [8533, 1, "1\u20445"], [8534, 1, "2\u20445"], [8535, 1, "3\u20445"], [8536, 1, "4\u20445"], [8537, 1, "1\u20446"], [8538, 1, "5\u20446"], [8539, 1, "1\u20448"], [8540, 1, "3\u20448"], [8541, 1, "5\u20448"], [8542, 1, "7\u20448"], [8543, 1, "1\u2044"], [8544, 1, "i"], [8545, 1, "ii"], [8546, 1, "iii"], [8547, 1, "iv"], [8548, 1, "v"], [8549, 1, "vi"], [8550, 1, "vii"], [8551, 1, "viii"], [8552, 1, "ix"], [8553, 1, "x"], [8554, 1, "xi"], [8555, 1, "xii"], [8556, 1, "l"], [8557, 1, "c"], [8558, 1, "d"], [8559, 1, "m"], [8560, 1, "i"], [8561, 1, "ii"], [8562, 1, "iii"], [8563, 1, "iv"], [8564, 1, "v"], [8565, 1, "vi"], [8566, 1, "vii"], [8567, 1, "viii"], [8568, 1, "ix"], [8569, 1, "x"], [8570, 1, "xi"], [8571, 1, "xii"], [8572, 1, "l"], [8573, 1, "c"], [8574, 1, "d"], [8575, 1, "m"], [[8576, 8578], 2], [8579, 3], [8580, 2], [[8581, 8584], 2], [8585, 1, "0\u20443"], [[8586, 8587], 2], [[8588, 8591], 3], [[8592, 8682], 2], [[8683, 8691], 2], [[8692, 8703], 2], [[8704, 8747], 2], [8748, 1, "\u222B\u222B"], [8749, 1, "\u222B\u222B\u222B"], [8750, 2], [8751, 1, "\u222E\u222E"], [8752, 1, "\u222E\u222E\u222E"], [[8753, 8945], 2], [[8946, 8959], 2], [8960, 2], [8961, 2], [[8962, 9e3], 2], [9001, 1, "\u3008"], [9002, 1, "\u3009"], [[9003, 9082], 2], [9083, 2], [9084, 2], [[9085, 9114], 2], [[9115, 9166], 2], [[9167, 9168], 2], [[9169, 9179], 2], [[9180, 9191], 2], [9192, 2], [[9193, 9203], 2], [[9204, 9210], 2], [[9211, 9214], 2], [9215, 2], [[9216, 9252], 2], [[9253, 9254], 2], [[9255, 9279], 3], [[9280, 9290], 2], [[9291, 9311], 3], [9312, 1, "1"], [9313, 1, "2"], [9314, 1, "3"], [9315, 1, "4"], [9316, 1, "5"], [9317, 1, "6"], [9318, 1, "7"], [9319, 1, "8"], [9320, 1, "9"], [9321, 1, "10"], [9322, 1, "11"], [9323, 1, "12"], [9324, 1, "13"], [9325, 1, "14"], [9326, 1, "15"], [9327, 1, "16"], [9328, 1, "17"], [9329, 1, "18"], [9330, 1, "19"], [9331, 1, "20"], [9332, 5, "(1)"], [9333, 5, "(2)"], [9334, 5, "(3)"], [9335, 5, "(4)"], [9336, 5, "(5)"], [9337, 5, "(6)"], [9338, 5, "(7)"], [9339, 5, "(8)"], [9340, 5, "(9)"], [9341, 5, "(10)"], [9342, 5, "(11)"], [9343, 5, "(12)"], [9344, 5, "(13)"], [9345, 5, "(14)"], [9346, 5, "(15)"], [9347, 5, "(16)"], [9348, 5, "(17)"], [9349, 5, "(18)"], [9350, 5, "(19)"], [9351, 5, "(20)"], [[9352, 9371], 3], [9372, 5, "(a)"], [9373, 5, "(b)"], [9374, 5, "(c)"], [9375, 5, "(d)"], [9376, 5, "(e)"], [9377, 5, "(f)"], [9378, 5, "(g)"], [9379, 5, "(h)"], [9380, 5, "(i)"], [9381, 5, "(j)"], [9382, 5, "(k)"], [9383, 5, "(l)"], [9384, 5, "(m)"], [9385, 5, "(n)"], [9386, 5, "(o)"], [9387, 5, "(p)"], [9388, 5, "(q)"], [9389, 5, "(r)"], [9390, 5, "(s)"], [9391, 5, "(t)"], [9392, 5, "(u)"], [9393, 5, "(v)"], [9394, 5, "(w)"], [9395, 5, "(x)"], [9396, 5, "(y)"], [9397, 5, "(z)"], [9398, 1, "a"], [9399, 1, "b"], [9400, 1, "c"], [9401, 1, "d"], [9402, 1, "e"], [9403, 1, "f"], [9404, 1, "g"], [9405, 1, "h"], [9406, 1, "i"], [9407, 1, "j"], [9408, 1, "k"], [9409, 1, "l"], [9410, 1, "m"], [9411, 1, "n"], [9412, 1, "o"], [9413, 1, "p"], [9414, 1, "q"], [9415, 1, "r"], [9416, 1, "s"], [9417, 1, "t"], [9418, 1, "u"], [9419, 1, "v"], [9420, 1, "w"], [9421, 1, "x"], [9422, 1, "y"], [9423, 1, "z"], [9424, 1, "a"], [9425, 1, "b"], [9426, 1, "c"], [9427, 1, "d"], [9428, 1, "e"], [9429, 1, "f"], [9430, 1, "g"], [9431, 1, "h"], [9432, 1, "i"], [9433, 1, "j"], [9434, 1, "k"], [9435, 1, "l"], [9436, 1, "m"], [9437, 1, "n"], [9438, 1, "o"], [9439, 1, "p"], [9440, 1, "q"], [9441, 1, "r"], [9442, 1, "s"], [9443, 1, "t"], [9444, 1, "u"], [9445, 1, "v"], [9446, 1, "w"], [9447, 1, "x"], [9448, 1, "y"], [9449, 1, "z"], [9450, 1, "0"], [[9451, 9470], 2], [9471, 2], [[9472, 9621], 2], [[9622, 9631], 2], [[9632, 9711], 2], [[9712, 9719], 2], [[9720, 9727], 2], [[9728, 9747], 2], [[9748, 9749], 2], [[9750, 9751], 2], [9752, 2], [9753, 2], [[9754, 9839], 2], [[9840, 9841], 2], [[9842, 9853], 2], [[9854, 9855], 2], [[9856, 9865], 2], [[9866, 9873], 2], [[9874, 9884], 2], [9885, 2], [[9886, 9887], 2], [[9888, 9889], 2], [[9890, 9905], 2], [9906, 2], [[9907, 9916], 2], [[9917, 9919], 2], [[9920, 9923], 2], [[9924, 9933], 2], [9934, 2], [[9935, 9953], 2], [9954, 2], [9955, 2], [[9956, 9959], 2], [[9960, 9983], 2], [9984, 2], [[9985, 9988], 2], [9989, 2], [[9990, 9993], 2], [[9994, 9995], 2], [[9996, 10023], 2], [10024, 2], [[10025, 10059], 2], [10060, 2], [10061, 2], [10062, 2], [[10063, 10066], 2], [[10067, 10069], 2], [10070, 2], [10071, 2], [[10072, 10078], 2], [[10079, 10080], 2], [[10081, 10087], 2], [[10088, 10101], 2], [[10102, 10132], 2], [[10133, 10135], 2], [[10136, 10159], 2], [10160, 2], [[10161, 10174], 2], [10175, 2], [[10176, 10182], 2], [[10183, 10186], 2], [10187, 2], [10188, 2], [10189, 2], [[10190, 10191], 2], [[10192, 10219], 2], [[10220, 10223], 2], [[10224, 10239], 2], [[10240, 10495], 2], [[10496, 10763], 2], [10764, 1, "\u222B\u222B\u222B\u222B"], [[10765, 10867], 2], [10868, 5, "::="], [10869, 5, "=="], [10870, 5, "==="], [[10871, 10971], 2], [10972, 1, "\u2ADD\u0338"], [[10973, 11007], 2], [[11008, 11021], 2], [[11022, 11027], 2], [[11028, 11034], 2], [[11035, 11039], 2], [[11040, 11043], 2], [[11044, 11084], 2], [[11085, 11087], 2], [[11088, 11092], 2], [[11093, 11097], 2], [[11098, 11123], 2], [[11124, 11125], 3], [[11126, 11157], 2], [11158, 3], [11159, 2], [[11160, 11193], 2], [[11194, 11196], 2], [[11197, 11208], 2], [11209, 2], [[11210, 11217], 2], [11218, 2], [[11219, 11243], 2], [[11244, 11247], 2], [[11248, 11262], 2], [11263, 2], [11264, 1, "\u2C30"], [11265, 1, "\u2C31"], [11266, 1, "\u2C32"], [11267, 1, "\u2C33"], [11268, 1, "\u2C34"], [11269, 1, "\u2C35"], [11270, 1, "\u2C36"], [11271, 1, "\u2C37"], [11272, 1, "\u2C38"], [11273, 1, "\u2C39"], [11274, 1, "\u2C3A"], [11275, 1, "\u2C3B"], [11276, 1, "\u2C3C"], [11277, 1, "\u2C3D"], [11278, 1, "\u2C3E"], [11279, 1, "\u2C3F"], [11280, 1, "\u2C40"], [11281, 1, "\u2C41"], [11282, 1, "\u2C42"], [11283, 1, "\u2C43"], [11284, 1, "\u2C44"], [11285, 1, "\u2C45"], [11286, 1, "\u2C46"], [11287, 1, "\u2C47"], [11288, 1, "\u2C48"], [11289, 1, "\u2C49"], [11290, 1, "\u2C4A"], [11291, 1, "\u2C4B"], [11292, 1, "\u2C4C"], [11293, 1, "\u2C4D"], [11294, 1, "\u2C4E"], [11295, 1, "\u2C4F"], [11296, 1, "\u2C50"], [11297, 1, "\u2C51"], [11298, 1, "\u2C52"], [11299, 1, "\u2C53"], [11300, 1, "\u2C54"], [11301, 1, "\u2C55"], [11302, 1, "\u2C56"], [11303, 1, "\u2C57"], [11304, 1, "\u2C58"], [11305, 1, "\u2C59"], [11306, 1, "\u2C5A"], [11307, 1, "\u2C5B"], [11308, 1, "\u2C5C"], [11309, 1, "\u2C5D"], [11310, 1, "\u2C5E"], [11311, 1, "\u2C5F"], [[11312, 11358], 2], [11359, 2], [11360, 1, "\u2C61"], [11361, 2], [11362, 1, "\u026B"], [11363, 1, "\u1D7D"], [11364, 1, "\u027D"], [[11365, 11366], 2], [11367, 1, "\u2C68"], [11368, 2], [11369, 1, "\u2C6A"], [11370, 2], [11371, 1, "\u2C6C"], [11372, 2], [11373, 1, "\u0251"], [11374, 1, "\u0271"], [11375, 1, "\u0250"], [11376, 1, "\u0252"], [11377, 2], [11378, 1, "\u2C73"], [11379, 2], [11380, 2], [11381, 1, "\u2C76"], [[11382, 11383], 2], [[11384, 11387], 2], [11388, 1, "j"], [11389, 1, "v"], [11390, 1, "\u023F"], [11391, 1, "\u0240"], [11392, 1, "\u2C81"], [11393, 2], [11394, 1, "\u2C83"], [11395, 2], [11396, 1, "\u2C85"], [11397, 2], [11398, 1, "\u2C87"], [11399, 2], [11400, 1, "\u2C89"], [11401, 2], [11402, 1, "\u2C8B"], [11403, 2], [11404, 1, "\u2C8D"], [11405, 2], [11406, 1, "\u2C8F"], [11407, 2], [11408, 1, "\u2C91"], [11409, 2], [11410, 1, "\u2C93"], [11411, 2], [11412, 1, "\u2C95"], [11413, 2], [11414, 1, "\u2C97"], [11415, 2], [11416, 1, "\u2C99"], [11417, 2], [11418, 1, "\u2C9B"], [11419, 2], [11420, 1, "\u2C9D"], [11421, 2], [11422, 1, "\u2C9F"], [11423, 2], [11424, 1, "\u2CA1"], [11425, 2], [11426, 1, "\u2CA3"], [11427, 2], [11428, 1, "\u2CA5"], [11429, 2], [11430, 1, "\u2CA7"], [11431, 2], [11432, 1, "\u2CA9"], [11433, 2], [11434, 1, "\u2CAB"], [11435, 2], [11436, 1, "\u2CAD"], [11437, 2], [11438, 1, "\u2CAF"], [11439, 2], [11440, 1, "\u2CB1"], [11441, 2], [11442, 1, "\u2CB3"], [11443, 2], [11444, 1, "\u2CB5"], [11445, 2], [11446, 1, "\u2CB7"], [11447, 2], [11448, 1, "\u2CB9"], [11449, 2], [11450, 1, "\u2CBB"], [11451, 2], [11452, 1, "\u2CBD"], [11453, 2], [11454, 1, "\u2CBF"], [11455, 2], [11456, 1, "\u2CC1"], [11457, 2], [11458, 1, "\u2CC3"], [11459, 2], [11460, 1, "\u2CC5"], [11461, 2], [11462, 1, "\u2CC7"], [11463, 2], [11464, 1, "\u2CC9"], [11465, 2], [11466, 1, "\u2CCB"], [11467, 2], [11468, 1, "\u2CCD"], [11469, 2], [11470, 1, "\u2CCF"], [11471, 2], [11472, 1, "\u2CD1"], [11473, 2], [11474, 1, "\u2CD3"], [11475, 2], [11476, 1, "\u2CD5"], [11477, 2], [11478, 1, "\u2CD7"], [11479, 2], [11480, 1, "\u2CD9"], [11481, 2], [11482, 1, "\u2CDB"], [11483, 2], [11484, 1, "\u2CDD"], [11485, 2], [11486, 1, "\u2CDF"], [11487, 2], [11488, 1, "\u2CE1"], [11489, 2], [11490, 1, "\u2CE3"], [[11491, 11492], 2], [[11493, 11498], 2], [11499, 1, "\u2CEC"], [11500, 2], [11501, 1, "\u2CEE"], [[11502, 11505], 2], [11506, 1, "\u2CF3"], [11507, 2], [[11508, 11512], 3], [[11513, 11519], 2], [[11520, 11557], 2], [11558, 3], [11559, 2], [[11560, 11564], 3], [11565, 2], [[11566, 11567], 3], [[11568, 11621], 2], [[11622, 11623], 2], [[11624, 11630], 3], [11631, 1, "\u2D61"], [11632, 2], [[11633, 11646], 3], [11647, 2], [[11648, 11670], 2], [[11671, 11679], 3], [[11680, 11686], 2], [11687, 3], [[11688, 11694], 2], [11695, 3], [[11696, 11702], 2], [11703, 3], [[11704, 11710], 2], [11711, 3], [[11712, 11718], 2], [11719, 3], [[11720, 11726], 2], [11727, 3], [[11728, 11734], 2], [11735, 3], [[11736, 11742], 2], [11743, 3], [[11744, 11775], 2], [[11776, 11799], 2], [[11800, 11803], 2], [[11804, 11805], 2], [[11806, 11822], 2], [11823, 2], [11824, 2], [11825, 2], [[11826, 11835], 2], [[11836, 11842], 2], [[11843, 11844], 2], [[11845, 11849], 2], [[11850, 11854], 2], [11855, 2], [[11856, 11858], 2], [[11859, 11869], 2], [[11870, 11903], 3], [[11904, 11929], 2], [11930, 3], [[11931, 11934], 2], [11935, 1, "\u6BCD"], [[11936, 12018], 2], [12019, 1, "\u9F9F"], [[12020, 12031], 3], [12032, 1, "\u4E00"], [12033, 1, "\u4E28"], [12034, 1, "\u4E36"], [12035, 1, "\u4E3F"], [12036, 1, "\u4E59"], [12037, 1, "\u4E85"], [12038, 1, "\u4E8C"], [12039, 1, "\u4EA0"], [12040, 1, "\u4EBA"], [12041, 1, "\u513F"], [12042, 1, "\u5165"], [12043, 1, "\u516B"], [12044, 1, "\u5182"], [12045, 1, "\u5196"], [12046, 1, "\u51AB"], [12047, 1, "\u51E0"], [12048, 1, "\u51F5"], [12049, 1, "\u5200"], [12050, 1, "\u529B"], [12051, 1, "\u52F9"], [12052, 1, "\u5315"], [12053, 1, "\u531A"], [12054, 1, "\u5338"], [12055, 1, "\u5341"], [12056, 1, "\u535C"], [12057, 1, "\u5369"], [12058, 1, "\u5382"], [12059, 1, "\u53B6"], [12060, 1, "\u53C8"], [12061, 1, "\u53E3"], [12062, 1, "\u56D7"], [12063, 1, "\u571F"], [12064, 1, "\u58EB"], [12065, 1, "\u5902"], [12066, 1, "\u590A"], [12067, 1, "\u5915"], [12068, 1, "\u5927"], [12069, 1, "\u5973"], [12070, 1, "\u5B50"], [12071, 1, "\u5B80"], [12072, 1, "\u5BF8"], [12073, 1, "\u5C0F"], [12074, 1, "\u5C22"], [12075, 1, "\u5C38"], [12076, 1, "\u5C6E"], [12077, 1, "\u5C71"], [12078, 1, "\u5DDB"], [12079, 1, "\u5DE5"], [12080, 1, "\u5DF1"], [12081, 1, "\u5DFE"], [12082, 1, "\u5E72"], [12083, 1, "\u5E7A"], [12084, 1, "\u5E7F"], [12085, 1, "\u5EF4"], [12086, 1, "\u5EFE"], [12087, 1, "\u5F0B"], [12088, 1, "\u5F13"], [12089, 1, "\u5F50"], [12090, 1, "\u5F61"], [12091, 1, "\u5F73"], [12092, 1, "\u5FC3"], [12093, 1, "\u6208"], [12094, 1, "\u6236"], [12095, 1, "\u624B"], [12096, 1, "\u652F"], [12097, 1, "\u6534"], [12098, 1, "\u6587"], [12099, 1, "\u6597"], [12100, 1, "\u65A4"], [12101, 1, "\u65B9"], [12102, 1, "\u65E0"], [12103, 1, "\u65E5"], [12104, 1, "\u66F0"], [12105, 1, "\u6708"], [12106, 1, "\u6728"], [12107, 1, "\u6B20"], [12108, 1, "\u6B62"], [12109, 1, "\u6B79"], [12110, 1, "\u6BB3"], [12111, 1, "\u6BCB"], [12112, 1, "\u6BD4"], [12113, 1, "\u6BDB"], [12114, 1, "\u6C0F"], [12115, 1, "\u6C14"], [12116, 1, "\u6C34"], [12117, 1, "\u706B"], [12118, 1, "\u722A"], [12119, 1, "\u7236"], [12120, 1, "\u723B"], [12121, 1, "\u723F"], [12122, 1, "\u7247"], [12123, 1, "\u7259"], [12124, 1, "\u725B"], [12125, 1, "\u72AC"], [12126, 1, "\u7384"], [12127, 1, "\u7389"], [12128, 1, "\u74DC"], [12129, 1, "\u74E6"], [12130, 1, "\u7518"], [12131, 1, "\u751F"], [12132, 1, "\u7528"], [12133, 1, "\u7530"], [12134, 1, "\u758B"], [12135, 1, "\u7592"], [12136, 1, "\u7676"], [12137, 1, "\u767D"], [12138, 1, "\u76AE"], [12139, 1, "\u76BF"], [12140, 1, "\u76EE"], [12141, 1, "\u77DB"], [12142, 1, "\u77E2"], [12143, 1, "\u77F3"], [12144, 1, "\u793A"], [12145, 1, "\u79B8"], [12146, 1, "\u79BE"], [12147, 1, "\u7A74"], [12148, 1, "\u7ACB"], [12149, 1, "\u7AF9"], [12150, 1, "\u7C73"], [12151, 1, "\u7CF8"], [12152, 1, "\u7F36"], [12153, 1, "\u7F51"], [12154, 1, "\u7F8A"], [12155, 1, "\u7FBD"], [12156, 1, "\u8001"], [12157, 1, "\u800C"], [12158, 1, "\u8012"], [12159, 1, "\u8033"], [12160, 1, "\u807F"], [12161, 1, "\u8089"], [12162, 1, "\u81E3"], [12163, 1, "\u81EA"], [12164, 1, "\u81F3"], [12165, 1, "\u81FC"], [12166, 1, "\u820C"], [12167, 1, "\u821B"], [12168, 1, "\u821F"], [12169, 1, "\u826E"], [12170, 1, "\u8272"], [12171, 1, "\u8278"], [12172, 1, "\u864D"], [12173, 1, "\u866B"], [12174, 1, "\u8840"], [12175, 1, "\u884C"], [12176, 1, "\u8863"], [12177, 1, "\u897E"], [12178, 1, "\u898B"], [12179, 1, "\u89D2"], [12180, 1, "\u8A00"], [12181, 1, "\u8C37"], [12182, 1, "\u8C46"], [12183, 1, "\u8C55"], [12184, 1, "\u8C78"], [12185, 1, "\u8C9D"], [12186, 1, "\u8D64"], [12187, 1, "\u8D70"], [12188, 1, "\u8DB3"], [12189, 1, "\u8EAB"], [12190, 1, "\u8ECA"], [12191, 1, "\u8F9B"], [12192, 1, "\u8FB0"], [12193, 1, "\u8FB5"], [12194, 1, "\u9091"], [12195, 1, "\u9149"], [12196, 1, "\u91C6"], [12197, 1, "\u91CC"], [12198, 1, "\u91D1"], [12199, 1, "\u9577"], [12200, 1, "\u9580"], [12201, 1, "\u961C"], [12202, 1, "\u96B6"], [12203, 1, "\u96B9"], [12204, 1, "\u96E8"], [12205, 1, "\u9751"], [12206, 1, "\u975E"], [12207, 1, "\u9762"], [12208, 1, "\u9769"], [12209, 1, "\u97CB"], [12210, 1, "\u97ED"], [12211, 1, "\u97F3"], [12212, 1, "\u9801"], [12213, 1, "\u98A8"], [12214, 1, "\u98DB"], [12215, 1, "\u98DF"], [12216, 1, "\u9996"], [12217, 1, "\u9999"], [12218, 1, "\u99AC"], [12219, 1, "\u9AA8"], [12220, 1, "\u9AD8"], [12221, 1, "\u9ADF"], [12222, 1, "\u9B25"], [12223, 1, "\u9B2F"], [12224, 1, "\u9B32"], [12225, 1, "\u9B3C"], [12226, 1, "\u9B5A"], [12227, 1, "\u9CE5"], [12228, 1, "\u9E75"], [12229, 1, "\u9E7F"], [12230, 1, "\u9EA5"], [12231, 1, "\u9EBB"], [12232, 1, "\u9EC3"], [12233, 1, "\u9ECD"], [12234, 1, "\u9ED1"], [12235, 1, "\u9EF9"], [12236, 1, "\u9EFD"], [12237, 1, "\u9F0E"], [12238, 1, "\u9F13"], [12239, 1, "\u9F20"], [12240, 1, "\u9F3B"], [12241, 1, "\u9F4A"], [12242, 1, "\u9F52"], [12243, 1, "\u9F8D"], [12244, 1, "\u9F9C"], [12245, 1, "\u9FA0"], [[12246, 12271], 3], [[12272, 12283], 3], [[12284, 12287], 3], [12288, 5, " "], [12289, 2], [12290, 1, "."], [[12291, 12292], 2], [[12293, 12295], 2], [[12296, 12329], 2], [[12330, 12333], 2], [[12334, 12341], 2], [12342, 1, "\u3012"], [12343, 2], [12344, 1, "\u5341"], [12345, 1, "\u5344"], [12346, 1, "\u5345"], [12347, 2], [12348, 2], [12349, 2], [12350, 2], [12351, 2], [12352, 3], [[12353, 12436], 2], [[12437, 12438], 2], [[12439, 12440], 3], [[12441, 12442], 2], [12443, 5, " \u3099"], [12444, 5, " \u309A"], [[12445, 12446], 2], [12447, 1, "\u3088\u308A"], [12448, 2], [[12449, 12542], 2], [12543, 1, "\u30B3\u30C8"], [[12544, 12548], 3], [[12549, 12588], 2], [12589, 2], [12590, 2], [12591, 2], [12592, 3], [12593, 1, "\u1100"], [12594, 1, "\u1101"], [12595, 1, "\u11AA"], [12596, 1, "\u1102"], [12597, 1, "\u11AC"], [12598, 1, "\u11AD"], [12599, 1, "\u1103"], [12600, 1, "\u1104"], [12601, 1, "\u1105"], [12602, 1, "\u11B0"], [12603, 1, "\u11B1"], [12604, 1, "\u11B2"], [12605, 1, "\u11B3"], [12606, 1, "\u11B4"], [12607, 1, "\u11B5"], [12608, 1, "\u111A"], [12609, 1, "\u1106"], [12610, 1, "\u1107"], [12611, 1, "\u1108"], [12612, 1, "\u1121"], [12613, 1, "\u1109"], [12614, 1, "\u110A"], [12615, 1, "\u110B"], [12616, 1, "\u110C"], [12617, 1, "\u110D"], [12618, 1, "\u110E"], [12619, 1, "\u110F"], [12620, 1, "\u1110"], [12621, 1, "\u1111"], [12622, 1, "\u1112"], [12623, 1, "\u1161"], [12624, 1, "\u1162"], [12625, 1, "\u1163"], [12626, 1, "\u1164"], [12627, 1, "\u1165"], [12628, 1, "\u1166"], [12629, 1, "\u1167"], [12630, 1, "\u1168"], [12631, 1, "\u1169"], [12632, 1, "\u116A"], [12633, 1, "\u116B"], [12634, 1, "\u116C"], [12635, 1, "\u116D"], [12636, 1, "\u116E"], [12637, 1, "\u116F"], [12638, 1, "\u1170"], [12639, 1, "\u1171"], [12640, 1, "\u1172"], [12641, 1, "\u1173"], [12642, 1, "\u1174"], [12643, 1, "\u1175"], [12644, 3], [12645, 1, "\u1114"], [12646, 1, "\u1115"], [12647, 1, "\u11C7"], [12648, 1, "\u11C8"], [12649, 1, "\u11CC"], [12650, 1, "\u11CE"], [12651, 1, "\u11D3"], [12652, 1, "\u11D7"], [12653, 1, "\u11D9"], [12654, 1, "\u111C"], [12655, 1, "\u11DD"], [12656, 1, "\u11DF"], [12657, 1, "\u111D"], [12658, 1, "\u111E"], [12659, 1, "\u1120"], [12660, 1, "\u1122"], [12661, 1, "\u1123"], [12662, 1, "\u1127"], [12663, 1, "\u1129"], [12664, 1, "\u112B"], [12665, 1, "\u112C"], [12666, 1, "\u112D"], [12667, 1, "\u112E"], [12668, 1, "\u112F"], [12669, 1, "\u1132"], [12670, 1, "\u1136"], [12671, 1, "\u1140"], [12672, 1, "\u1147"], [12673, 1, "\u114C"], [12674, 1, "\u11F1"], [12675, 1, "\u11F2"], [12676, 1, "\u1157"], [12677, 1, "\u1158"], [12678, 1, "\u1159"], [12679, 1, "\u1184"], [12680, 1, "\u1185"], [12681, 1, "\u1188"], [12682, 1, "\u1191"], [12683, 1, "\u1192"], [12684, 1, "\u1194"], [12685, 1, "\u119E"], [12686, 1, "\u11A1"], [12687, 3], [[12688, 12689], 2], [12690, 1, "\u4E00"], [12691, 1, "\u4E8C"], [12692, 1, "\u4E09"], [12693, 1, "\u56DB"], [12694, 1, "\u4E0A"], [12695, 1, "\u4E2D"], [12696, 1, "\u4E0B"], [12697, 1, "\u7532"], [12698, 1, "\u4E59"], [12699, 1, "\u4E19"], [12700, 1, "\u4E01"], [12701, 1, "\u5929"], [12702, 1, "\u5730"], [12703, 1, "\u4EBA"], [[12704, 12727], 2], [[12728, 12730], 2], [[12731, 12735], 2], [[12736, 12751], 2], [[12752, 12771], 2], [[12772, 12782], 3], [12783, 3], [[12784, 12799], 2], [12800, 5, "(\u1100)"], [12801, 5, "(\u1102)"], [12802, 5, "(\u1103)"], [12803, 5, "(\u1105)"], [12804, 5, "(\u1106)"], [12805, 5, "(\u1107)"], [12806, 5, "(\u1109)"], [12807, 5, "(\u110B)"], [12808, 5, "(\u110C)"], [12809, 5, "(\u110E)"], [12810, 5, "(\u110F)"], [12811, 5, "(\u1110)"], [12812, 5, "(\u1111)"], [12813, 5, "(\u1112)"], [12814, 5, "(\uAC00)"], [12815, 5, "(\uB098)"], [12816, 5, "(\uB2E4)"], [12817, 5, "(\uB77C)"], [12818, 5, "(\uB9C8)"], [12819, 5, "(\uBC14)"], [12820, 5, "(\uC0AC)"], [12821, 5, "(\uC544)"], [12822, 5, "(\uC790)"], [12823, 5, "(\uCC28)"], [12824, 5, "(\uCE74)"], [12825, 5, "(\uD0C0)"], [12826, 5, "(\uD30C)"], [12827, 5, "(\uD558)"], [12828, 5, "(\uC8FC)"], [12829, 5, "(\uC624\uC804)"], [12830, 5, "(\uC624\uD6C4)"], [12831, 3], [12832, 5, "(\u4E00)"], [12833, 5, "(\u4E8C)"], [12834, 5, "(\u4E09)"], [12835, 5, "(\u56DB)"], [12836, 5, "(\u4E94)"], [12837, 5, "(\u516D)"], [12838, 5, "(\u4E03)"], [12839, 5, "(\u516B)"], [12840, 5, "(\u4E5D)"], [12841, 5, "(\u5341)"], [12842, 5, "(\u6708)"], [12843, 5, "(\u706B)"], [12844, 5, "(\u6C34)"], [12845, 5, "(\u6728)"], [12846, 5, "(\u91D1)"], [12847, 5, "(\u571F)"], [12848, 5, "(\u65E5)"], [12849, 5, "(\u682A)"], [12850, 5, "(\u6709)"], [12851, 5, "(\u793E)"], [12852, 5, "(\u540D)"], [12853, 5, "(\u7279)"], [12854, 5, "(\u8CA1)"], [12855, 5, "(\u795D)"], [12856, 5, "(\u52B4)"], [12857, 5, "(\u4EE3)"], [12858, 5, "(\u547C)"], [12859, 5, "(\u5B66)"], [12860, 5, "(\u76E3)"], [12861, 5, "(\u4F01)"], [12862, 5, "(\u8CC7)"], [12863, 5, "(\u5354)"], [12864, 5, "(\u796D)"], [12865, 5, "(\u4F11)"], [12866, 5, "(\u81EA)"], [12867, 5, "(\u81F3)"], [12868, 1, "\u554F"], [12869, 1, "\u5E7C"], [12870, 1, "\u6587"], [12871, 1, "\u7B8F"], [[12872, 12879], 2], [12880, 1, "pte"], [12881, 1, "21"], [12882, 1, "22"], [12883, 1, "23"], [12884, 1, "24"], [12885, 1, "25"], [12886, 1, "26"], [12887, 1, "27"], [12888, 1, "28"], [12889, 1, "29"], [12890, 1, "30"], [12891, 1, "31"], [12892, 1, "32"], [12893, 1, "33"], [12894, 1, "34"], [12895, 1, "35"], [12896, 1, "\u1100"], [12897, 1, "\u1102"], [12898, 1, "\u1103"], [12899, 1, "\u1105"], [12900, 1, "\u1106"], [12901, 1, "\u1107"], [12902, 1, "\u1109"], [12903, 1, "\u110B"], [12904, 1, "\u110C"], [12905, 1, "\u110E"], [12906, 1, "\u110F"], [12907, 1, "\u1110"], [12908, 1, "\u1111"], [12909, 1, "\u1112"], [12910, 1, "\uAC00"], [12911, 1, "\uB098"], [12912, 1, "\uB2E4"], [12913, 1, "\uB77C"], [12914, 1, "\uB9C8"], [12915, 1, "\uBC14"], [12916, 1, "\uC0AC"], [12917, 1, "\uC544"], [12918, 1, "\uC790"], [12919, 1, "\uCC28"], [12920, 1, "\uCE74"], [12921, 1, "\uD0C0"], [12922, 1, "\uD30C"], [12923, 1, "\uD558"], [12924, 1, "\uCC38\uACE0"], [12925, 1, "\uC8FC\uC758"], [12926, 1, "\uC6B0"], [12927, 2], [12928, 1, "\u4E00"], [12929, 1, "\u4E8C"], [12930, 1, "\u4E09"], [12931, 1, "\u56DB"], [12932, 1, "\u4E94"], [12933, 1, "\u516D"], [12934, 1, "\u4E03"], [12935, 1, "\u516B"], [12936, 1, "\u4E5D"], [12937, 1, "\u5341"], [12938, 1, "\u6708"], [12939, 1, "\u706B"], [12940, 1, "\u6C34"], [12941, 1, "\u6728"], [12942, 1, "\u91D1"], [12943, 1, "\u571F"], [12944, 1, "\u65E5"], [12945, 1, "\u682A"], [12946, 1, "\u6709"], [12947, 1, "\u793E"], [12948, 1, "\u540D"], [12949, 1, "\u7279"], [12950, 1, "\u8CA1"], [12951, 1, "\u795D"], [12952, 1, "\u52B4"], [12953, 1, "\u79D8"], [12954, 1, "\u7537"], [12955, 1, "\u5973"], [12956, 1, "\u9069"], [12957, 1, "\u512A"], [12958, 1, "\u5370"], [12959, 1, "\u6CE8"], [12960, 1, "\u9805"], [12961, 1, "\u4F11"], [12962, 1, "\u5199"], [12963, 1, "\u6B63"], [12964, 1, "\u4E0A"], [12965, 1, "\u4E2D"], [12966, 1, "\u4E0B"], [12967, 1, "\u5DE6"], [12968, 1, "\u53F3"], [12969, 1, "\u533B"], [12970, 1, "\u5B97"], [12971, 1, "\u5B66"], [12972, 1, "\u76E3"], [12973, 1, "\u4F01"], [12974, 1, "\u8CC7"], [12975, 1, "\u5354"], [12976, 1, "\u591C"], [12977, 1, "36"], [12978, 1, "37"], [12979, 1, "38"], [12980, 1, "39"], [12981, 1, "40"], [12982, 1, "41"], [12983, 1, "42"], [12984, 1, "43"], [12985, 1, "44"], [12986, 1, "45"], [12987, 1, "46"], [12988, 1, "47"], [12989, 1, "48"], [12990, 1, "49"], [12991, 1, "50"], [12992, 1, "1\u6708"], [12993, 1, "2\u6708"], [12994, 1, "3\u6708"], [12995, 1, "4\u6708"], [12996, 1, "5\u6708"], [12997, 1, "6\u6708"], [12998, 1, "7\u6708"], [12999, 1, "8\u6708"], [13e3, 1, "9\u6708"], [13001, 1, "10\u6708"], [13002, 1, "11\u6708"], [13003, 1, "12\u6708"], [13004, 1, "hg"], [13005, 1, "erg"], [13006, 1, "ev"], [13007, 1, "ltd"], [13008, 1, "\u30A2"], [13009, 1, "\u30A4"], [13010, 1, "\u30A6"], [13011, 1, "\u30A8"], [13012, 1, "\u30AA"], [13013, 1, "\u30AB"], [13014, 1, "\u30AD"], [13015, 1, "\u30AF"], [13016, 1, "\u30B1"], [13017, 1, "\u30B3"], [13018, 1, "\u30B5"], [13019, 1, "\u30B7"], [13020, 1, "\u30B9"], [13021, 1, "\u30BB"], [13022, 1, "\u30BD"], [13023, 1, "\u30BF"], [13024, 1, "\u30C1"], [13025, 1, "\u30C4"], [13026, 1, "\u30C6"], [13027, 1, "\u30C8"], [13028, 1, "\u30CA"], [13029, 1, "\u30CB"], [13030, 1, "\u30CC"], [13031, 1, "\u30CD"], [13032, 1, "\u30CE"], [13033, 1, "\u30CF"], [13034, 1, "\u30D2"], [13035, 1, "\u30D5"], [13036, 1, "\u30D8"], [13037, 1, "\u30DB"], [13038, 1, "\u30DE"], [13039, 1, "\u30DF"], [13040, 1, "\u30E0"], [13041, 1, "\u30E1"], [13042, 1, "\u30E2"], [13043, 1, "\u30E4"], [13044, 1, "\u30E6"], [13045, 1, "\u30E8"], [13046, 1, "\u30E9"], [13047, 1, "\u30EA"], [13048, 1, "\u30EB"], [13049, 1, "\u30EC"], [13050, 1, "\u30ED"], [13051, 1, "\u30EF"], [13052, 1, "\u30F0"], [13053, 1, "\u30F1"], [13054, 1, "\u30F2"], [13055, 1, "\u4EE4\u548C"], [13056, 1, "\u30A2\u30D1\u30FC\u30C8"], [13057, 1, "\u30A2\u30EB\u30D5\u30A1"], [13058, 1, "\u30A2\u30F3\u30DA\u30A2"], [13059, 1, "\u30A2\u30FC\u30EB"], [13060, 1, "\u30A4\u30CB\u30F3\u30B0"], [13061, 1, "\u30A4\u30F3\u30C1"], [13062, 1, "\u30A6\u30A9\u30F3"], [13063, 1, "\u30A8\u30B9\u30AF\u30FC\u30C9"], [13064, 1, "\u30A8\u30FC\u30AB\u30FC"], [13065, 1, "\u30AA\u30F3\u30B9"], [13066, 1, "\u30AA\u30FC\u30E0"], [13067, 1, "\u30AB\u30A4\u30EA"], [13068, 1, "\u30AB\u30E9\u30C3\u30C8"], [13069, 1, "\u30AB\u30ED\u30EA\u30FC"], [13070, 1, "\u30AC\u30ED\u30F3"], [13071, 1, "\u30AC\u30F3\u30DE"], [13072, 1, "\u30AE\u30AC"], [13073, 1, "\u30AE\u30CB\u30FC"], [13074, 1, "\u30AD\u30E5\u30EA\u30FC"], [13075, 1, "\u30AE\u30EB\u30C0\u30FC"], [13076, 1, "\u30AD\u30ED"], [13077, 1, "\u30AD\u30ED\u30B0\u30E9\u30E0"], [13078, 1, "\u30AD\u30ED\u30E1\u30FC\u30C8\u30EB"], [13079, 1, "\u30AD\u30ED\u30EF\u30C3\u30C8"], [13080, 1, "\u30B0\u30E9\u30E0"], [13081, 1, "\u30B0\u30E9\u30E0\u30C8\u30F3"], [13082, 1, "\u30AF\u30EB\u30BC\u30A4\u30ED"], [13083, 1, "\u30AF\u30ED\u30FC\u30CD"], [13084, 1, "\u30B1\u30FC\u30B9"], [13085, 1, "\u30B3\u30EB\u30CA"], [13086, 1, "\u30B3\u30FC\u30DD"], [13087, 1, "\u30B5\u30A4\u30AF\u30EB"], [13088, 1, "\u30B5\u30F3\u30C1\u30FC\u30E0"], [13089, 1, "\u30B7\u30EA\u30F3\u30B0"], [13090, 1, "\u30BB\u30F3\u30C1"], [13091, 1, "\u30BB\u30F3\u30C8"], [13092, 1, "\u30C0\u30FC\u30B9"], [13093, 1, "\u30C7\u30B7"], [13094, 1, "\u30C9\u30EB"], [13095, 1, "\u30C8\u30F3"], [13096, 1, "\u30CA\u30CE"], [13097, 1, "\u30CE\u30C3\u30C8"], [13098, 1, "\u30CF\u30A4\u30C4"], [13099, 1, "\u30D1\u30FC\u30BB\u30F3\u30C8"], [13100, 1, "\u30D1\u30FC\u30C4"], [13101, 1, "\u30D0\u30FC\u30EC\u30EB"], [13102, 1, "\u30D4\u30A2\u30B9\u30C8\u30EB"], [13103, 1, "\u30D4\u30AF\u30EB"], [13104, 1, "\u30D4\u30B3"], [13105, 1, "\u30D3\u30EB"], [13106, 1, "\u30D5\u30A1\u30E9\u30C3\u30C9"], [13107, 1, "\u30D5\u30A3\u30FC\u30C8"], [13108, 1, "\u30D6\u30C3\u30B7\u30A7\u30EB"], [13109, 1, "\u30D5\u30E9\u30F3"], [13110, 1, "\u30D8\u30AF\u30BF\u30FC\u30EB"], [13111, 1, "\u30DA\u30BD"], [13112, 1, "\u30DA\u30CB\u30D2"], [13113, 1, "\u30D8\u30EB\u30C4"], [13114, 1, "\u30DA\u30F3\u30B9"], [13115, 1, "\u30DA\u30FC\u30B8"], [13116, 1, "\u30D9\u30FC\u30BF"], [13117, 1, "\u30DD\u30A4\u30F3\u30C8"], [13118, 1, "\u30DC\u30EB\u30C8"], [13119, 1, "\u30DB\u30F3"], [13120, 1, "\u30DD\u30F3\u30C9"], [13121, 1, "\u30DB\u30FC\u30EB"], [13122, 1, "\u30DB\u30FC\u30F3"], [13123, 1, "\u30DE\u30A4\u30AF\u30ED"], [13124, 1, "\u30DE\u30A4\u30EB"], [13125, 1, "\u30DE\u30C3\u30CF"], [13126, 1, "\u30DE\u30EB\u30AF"], [13127, 1, "\u30DE\u30F3\u30B7\u30E7\u30F3"], [13128, 1, "\u30DF\u30AF\u30ED\u30F3"], [13129, 1, "\u30DF\u30EA"], [13130, 1, "\u30DF\u30EA\u30D0\u30FC\u30EB"], [13131, 1, "\u30E1\u30AC"], [13132, 1, "\u30E1\u30AC\u30C8\u30F3"], [13133, 1, "\u30E1\u30FC\u30C8\u30EB"], [13134, 1, "\u30E4\u30FC\u30C9"], [13135, 1, "\u30E4\u30FC\u30EB"], [13136, 1, "\u30E6\u30A2\u30F3"], [13137, 1, "\u30EA\u30C3\u30C8\u30EB"], [13138, 1, "\u30EA\u30E9"], [13139, 1, "\u30EB\u30D4\u30FC"], [13140, 1, "\u30EB\u30FC\u30D6\u30EB"], [13141, 1, "\u30EC\u30E0"], [13142, 1, "\u30EC\u30F3\u30C8\u30B2\u30F3"], [13143, 1, "\u30EF\u30C3\u30C8"], [13144, 1, "0\u70B9"], [13145, 1, "1\u70B9"], [13146, 1, "2\u70B9"], [13147, 1, "3\u70B9"], [13148, 1, "4\u70B9"], [13149, 1, "5\u70B9"], [13150, 1, "6\u70B9"], [13151, 1, "7\u70B9"], [13152, 1, "8\u70B9"], [13153, 1, "9\u70B9"], [13154, 1, "10\u70B9"], [13155, 1, "11\u70B9"], [13156, 1, "12\u70B9"], [13157, 1, "13\u70B9"], [13158, 1, "14\u70B9"], [13159, 1, "15\u70B9"], [13160, 1, "16\u70B9"], [13161, 1, "17\u70B9"], [13162, 1, "18\u70B9"], [13163, 1, "19\u70B9"], [13164, 1, "20\u70B9"], [13165, 1, "21\u70B9"], [13166, 1, "22\u70B9"], [13167, 1, "23\u70B9"], [13168, 1, "24\u70B9"], [13169, 1, "hpa"], [13170, 1, "da"], [13171, 1, "au"], [13172, 1, "bar"], [13173, 1, "ov"], [13174, 1, "pc"], [13175, 1, "dm"], [13176, 1, "dm2"], [13177, 1, "dm3"], [13178, 1, "iu"], [13179, 1, "\u5E73\u6210"], [13180, 1, "\u662D\u548C"], [13181, 1, "\u5927\u6B63"], [13182, 1, "\u660E\u6CBB"], [13183, 1, "\u682A\u5F0F\u4F1A\u793E"], [13184, 1, "pa"], [13185, 1, "na"], [13186, 1, "\u03BCa"], [13187, 1, "ma"], [13188, 1, "ka"], [13189, 1, "kb"], [13190, 1, "mb"], [13191, 1, "gb"], [13192, 1, "cal"], [13193, 1, "kcal"], [13194, 1, "pf"], [13195, 1, "nf"], [13196, 1, "\u03BCf"], [13197, 1, "\u03BCg"], [13198, 1, "mg"], [13199, 1, "kg"], [13200, 1, "hz"], [13201, 1, "khz"], [13202, 1, "mhz"], [13203, 1, "ghz"], [13204, 1, "thz"], [13205, 1, "\u03BCl"], [13206, 1, "ml"], [13207, 1, "dl"], [13208, 1, "kl"], [13209, 1, "fm"], [13210, 1, "nm"], [13211, 1, "\u03BCm"], [13212, 1, "mm"], [13213, 1, "cm"], [13214, 1, "km"], [13215, 1, "mm2"], [13216, 1, "cm2"], [13217, 1, "m2"], [13218, 1, "km2"], [13219, 1, "mm3"], [13220, 1, "cm3"], [13221, 1, "m3"], [13222, 1, "km3"], [13223, 1, "m\u2215s"], [13224, 1, "m\u2215s2"], [13225, 1, "pa"], [13226, 1, "kpa"], [13227, 1, "mpa"], [13228, 1, "gpa"], [13229, 1, "rad"], [13230, 1, "rad\u2215s"], [13231, 1, "rad\u2215s2"], [13232, 1, "ps"], [13233, 1, "ns"], [13234, 1, "\u03BCs"], [13235, 1, "ms"], [13236, 1, "pv"], [13237, 1, "nv"], [13238, 1, "\u03BCv"], [13239, 1, "mv"], [13240, 1, "kv"], [13241, 1, "mv"], [13242, 1, "pw"], [13243, 1, "nw"], [13244, 1, "\u03BCw"], [13245, 1, "mw"], [13246, 1, "kw"], [13247, 1, "mw"], [13248, 1, "k\u03C9"], [13249, 1, "m\u03C9"], [13250, 3], [13251, 1, "bq"], [13252, 1, "cc"], [13253, 1, "cd"], [13254, 1, "c\u2215kg"], [13255, 3], [13256, 1, "db"], [13257, 1, "gy"], [13258, 1, "ha"], [13259, 1, "hp"], [13260, 1, "in"], [13261, 1, "kk"], [13262, 1, "km"], [13263, 1, "kt"], [13264, 1, "lm"], [13265, 1, "ln"], [13266, 1, "log"], [13267, 1, "lx"], [13268, 1, "mb"], [13269, 1, "mil"], [13270, 1, "mol"], [13271, 1, "ph"], [13272, 3], [13273, 1, "ppm"], [13274, 1, "pr"], [13275, 1, "sr"], [13276, 1, "sv"], [13277, 1, "wb"], [13278, 1, "v\u2215m"], [13279, 1, "a\u2215m"], [13280, 1, "1\u65E5"], [13281, 1, "2\u65E5"], [13282, 1, "3\u65E5"], [13283, 1, "4\u65E5"], [13284, 1, "5\u65E5"], [13285, 1, "6\u65E5"], [13286, 1, "7\u65E5"], [13287, 1, "8\u65E5"], [13288, 1, "9\u65E5"], [13289, 1, "10\u65E5"], [13290, 1, "11\u65E5"], [13291, 1, "12\u65E5"], [13292, 1, "13\u65E5"], [13293, 1, "14\u65E5"], [13294, 1, "15\u65E5"], [13295, 1, "16\u65E5"], [13296, 1, "17\u65E5"], [13297, 1, "18\u65E5"], [13298, 1, "19\u65E5"], [13299, 1, "20\u65E5"], [13300, 1, "21\u65E5"], [13301, 1, "22\u65E5"], [13302, 1, "23\u65E5"], [13303, 1, "24\u65E5"], [13304, 1, "25\u65E5"], [13305, 1, "26\u65E5"], [13306, 1, "27\u65E5"], [13307, 1, "28\u65E5"], [13308, 1, "29\u65E5"], [13309, 1, "30\u65E5"], [13310, 1, "31\u65E5"], [13311, 1, "gal"], [[13312, 19893], 2], [[19894, 19903], 2], [[19904, 19967], 2], [[19968, 40869], 2], [[40870, 40891], 2], [[40892, 40899], 2], [[40900, 40907], 2], [40908, 2], [[40909, 40917], 2], [[40918, 40938], 2], [[40939, 40943], 2], [[40944, 40956], 2], [[40957, 40959], 2], [[40960, 42124], 2], [[42125, 42127], 3], [[42128, 42145], 2], [[42146, 42147], 2], [[42148, 42163], 2], [42164, 2], [[42165, 42176], 2], [42177, 2], [[42178, 42180], 2], [42181, 2], [42182, 2], [[42183, 42191], 3], [[42192, 42237], 2], [[42238, 42239], 2], [[42240, 42508], 2], [[42509, 42511], 2], [[42512, 42539], 2], [[42540, 42559], 3], [42560, 1, "\uA641"], [42561, 2], [42562, 1, "\uA643"], [42563, 2], [42564, 1, "\uA645"], [42565, 2], [42566, 1, "\uA647"], [42567, 2], [42568, 1, "\uA649"], [42569, 2], [42570, 1, "\uA64B"], [42571, 2], [42572, 1, "\uA64D"], [42573, 2], [42574, 1, "\uA64F"], [42575, 2], [42576, 1, "\uA651"], [42577, 2], [42578, 1, "\uA653"], [42579, 2], [42580, 1, "\uA655"], [42581, 2], [42582, 1, "\uA657"], [42583, 2], [42584, 1, "\uA659"], [42585, 2], [42586, 1, "\uA65B"], [42587, 2], [42588, 1, "\uA65D"], [42589, 2], [42590, 1, "\uA65F"], [42591, 2], [42592, 1, "\uA661"], [42593, 2], [42594, 1, "\uA663"], [42595, 2], [42596, 1, "\uA665"], [42597, 2], [42598, 1, "\uA667"], [42599, 2], [42600, 1, "\uA669"], [42601, 2], [42602, 1, "\uA66B"], [42603, 2], [42604, 1, "\uA66D"], [[42605, 42607], 2], [[42608, 42611], 2], [[42612, 42619], 2], [[42620, 42621], 2], [42622, 2], [42623, 2], [42624, 1, "\uA681"], [42625, 2], [42626, 1, "\uA683"], [42627, 2], [42628, 1, "\uA685"], [42629, 2], [42630, 1, "\uA687"], [42631, 2], [42632, 1, "\uA689"], [42633, 2], [42634, 1, "\uA68B"], [42635, 2], [42636, 1, "\uA68D"], [42637, 2], [42638, 1, "\uA68F"], [42639, 2], [42640, 1, "\uA691"], [42641, 2], [42642, 1, "\uA693"], [42643, 2], [42644, 1, "\uA695"], [42645, 2], [42646, 1, "\uA697"], [42647, 2], [42648, 1, "\uA699"], [42649, 2], [42650, 1, "\uA69B"], [42651, 2], [42652, 1, "\u044A"], [42653, 1, "\u044C"], [42654, 2], [42655, 2], [[42656, 42725], 2], [[42726, 42735], 2], [[42736, 42737], 2], [[42738, 42743], 2], [[42744, 42751], 3], [[42752, 42774], 2], [[42775, 42778], 2], [[42779, 42783], 2], [[42784, 42785], 2], [42786, 1, "\uA723"], [42787, 2], [42788, 1, "\uA725"], [42789, 2], [42790, 1, "\uA727"], [42791, 2], [42792, 1, "\uA729"], [42793, 2], [42794, 1, "\uA72B"], [42795, 2], [42796, 1, "\uA72D"], [42797, 2], [42798, 1, "\uA72F"], [[42799, 42801], 2], [42802, 1, "\uA733"], [42803, 2], [42804, 1, "\uA735"], [42805, 2], [42806, 1, "\uA737"], [42807, 2], [42808, 1, "\uA739"], [42809, 2], [42810, 1, "\uA73B"], [42811, 2], [42812, 1, "\uA73D"], [42813, 2], [42814, 1, "\uA73F"], [42815, 2], [42816, 1, "\uA741"], [42817, 2], [42818, 1, "\uA743"], [42819, 2], [42820, 1, "\uA745"], [42821, 2], [42822, 1, "\uA747"], [42823, 2], [42824, 1, "\uA749"], [42825, 2], [42826, 1, "\uA74B"], [42827, 2], [42828, 1, "\uA74D"], [42829, 2], [42830, 1, "\uA74F"], [42831, 2], [42832, 1, "\uA751"], [42833, 2], [42834, 1, "\uA753"], [42835, 2], [42836, 1, "\uA755"], [42837, 2], [42838, 1, "\uA757"], [42839, 2], [42840, 1, "\uA759"], [42841, 2], [42842, 1, "\uA75B"], [42843, 2], [42844, 1, "\uA75D"], [42845, 2], [42846, 1, "\uA75F"], [42847, 2], [42848, 1, "\uA761"], [42849, 2], [42850, 1, "\uA763"], [42851, 2], [42852, 1, "\uA765"], [42853, 2], [42854, 1, "\uA767"], [42855, 2], [42856, 1, "\uA769"], [42857, 2], [42858, 1, "\uA76B"], [42859, 2], [42860, 1, "\uA76D"], [42861, 2], [42862, 1, "\uA76F"], [42863, 2], [42864, 1, "\uA76F"], [[42865, 42872], 2], [42873, 1, "\uA77A"], [42874, 2], [42875, 1, "\uA77C"], [42876, 2], [42877, 1, "\u1D79"], [42878, 1, "\uA77F"], [42879, 2], [42880, 1, "\uA781"], [42881, 2], [42882, 1, "\uA783"], [42883, 2], [42884, 1, "\uA785"], [42885, 2], [42886, 1, "\uA787"], [[42887, 42888], 2], [[42889, 42890], 2], [42891, 1, "\uA78C"], [42892, 2], [42893, 1, "\u0265"], [42894, 2], [42895, 2], [42896, 1, "\uA791"], [42897, 2], [42898, 1, "\uA793"], [42899, 2], [[42900, 42901], 2], [42902, 1, "\uA797"], [42903, 2], [42904, 1, "\uA799"], [42905, 2], [42906, 1, "\uA79B"], [42907, 2], [42908, 1, "\uA79D"], [42909, 2], [42910, 1, "\uA79F"], [42911, 2], [42912, 1, "\uA7A1"], [42913, 2], [42914, 1, "\uA7A3"], [42915, 2], [42916, 1, "\uA7A5"], [42917, 2], [42918, 1, "\uA7A7"], [42919, 2], [42920, 1, "\uA7A9"], [42921, 2], [42922, 1, "\u0266"], [42923, 1, "\u025C"], [42924, 1, "\u0261"], [42925, 1, "\u026C"], [42926, 1, "\u026A"], [42927, 2], [42928, 1, "\u029E"], [42929, 1, "\u0287"], [42930, 1, "\u029D"], [42931, 1, "\uAB53"], [42932, 1, "\uA7B5"], [42933, 2], [42934, 1, "\uA7B7"], [42935, 2], [42936, 1, "\uA7B9"], [42937, 2], [42938, 1, "\uA7BB"], [42939, 2], [42940, 1, "\uA7BD"], [42941, 2], [42942, 1, "\uA7BF"], [42943, 2], [42944, 1, "\uA7C1"], [42945, 2], [42946, 1, "\uA7C3"], [42947, 2], [42948, 1, "\uA794"], [42949, 1, "\u0282"], [42950, 1, "\u1D8E"], [42951, 1, "\uA7C8"], [42952, 2], [42953, 1, "\uA7CA"], [42954, 2], [[42955, 42959], 3], [42960, 1, "\uA7D1"], [42961, 2], [42962, 3], [42963, 2], [42964, 3], [42965, 2], [42966, 1, "\uA7D7"], [42967, 2], [42968, 1, "\uA7D9"], [42969, 2], [[42970, 42993], 3], [42994, 1, "c"], [42995, 1, "f"], [42996, 1, "q"], [42997, 1, "\uA7F6"], [42998, 2], [42999, 2], [43e3, 1, "\u0127"], [43001, 1, "\u0153"], [43002, 2], [[43003, 43007], 2], [[43008, 43047], 2], [[43048, 43051], 2], [43052, 2], [[43053, 43055], 3], [[43056, 43065], 2], [[43066, 43071], 3], [[43072, 43123], 2], [[43124, 43127], 2], [[43128, 43135], 3], [[43136, 43204], 2], [43205, 2], [[43206, 43213], 3], [[43214, 43215], 2], [[43216, 43225], 2], [[43226, 43231], 3], [[43232, 43255], 2], [[43256, 43258], 2], [43259, 2], [43260, 2], [43261, 2], [[43262, 43263], 2], [[43264, 43309], 2], [[43310, 43311], 2], [[43312, 43347], 2], [[43348, 43358], 3], [43359, 2], [[43360, 43388], 2], [[43389, 43391], 3], [[43392, 43456], 2], [[43457, 43469], 2], [43470, 3], [[43471, 43481], 2], [[43482, 43485], 3], [[43486, 43487], 2], [[43488, 43518], 2], [43519, 3], [[43520, 43574], 2], [[43575, 43583], 3], [[43584, 43597], 2], [[43598, 43599], 3], [[43600, 43609], 2], [[43610, 43611], 3], [[43612, 43615], 2], [[43616, 43638], 2], [[43639, 43641], 2], [[43642, 43643], 2], [[43644, 43647], 2], [[43648, 43714], 2], [[43715, 43738], 3], [[43739, 43741], 2], [[43742, 43743], 2], [[43744, 43759], 2], [[43760, 43761], 2], [[43762, 43766], 2], [[43767, 43776], 3], [[43777, 43782], 2], [[43783, 43784], 3], [[43785, 43790], 2], [[43791, 43792], 3], [[43793, 43798], 2], [[43799, 43807], 3], [[43808, 43814], 2], [43815, 3], [[43816, 43822], 2], [43823, 3], [[43824, 43866], 2], [43867, 2], [43868, 1, "\uA727"], [43869, 1, "\uAB37"], [43870, 1, "\u026B"], [43871, 1, "\uAB52"], [[43872, 43875], 2], [[43876, 43877], 2], [[43878, 43879], 2], [43880, 2], [43881, 1, "\u028D"], [[43882, 43883], 2], [[43884, 43887], 3], [43888, 1, "\u13A0"], [43889, 1, "\u13A1"], [43890, 1, "\u13A2"], [43891, 1, "\u13A3"], [43892, 1, "\u13A4"], [43893, 1, "\u13A5"], [43894, 1, "\u13A6"], [43895, 1, "\u13A7"], [43896, 1, "\u13A8"], [43897, 1, "\u13A9"], [43898, 1, "\u13AA"], [43899, 1, "\u13AB"], [43900, 1, "\u13AC"], [43901, 1, "\u13AD"], [43902, 1, "\u13AE"], [43903, 1, "\u13AF"], [43904, 1, "\u13B0"], [43905, 1, "\u13B1"], [43906, 1, "\u13B2"], [43907, 1, "\u13B3"], [43908, 1, "\u13B4"], [43909, 1, "\u13B5"], [43910, 1, "\u13B6"], [43911, 1, "\u13B7"], [43912, 1, "\u13B8"], [43913, 1, "\u13B9"], [43914, 1, "\u13BA"], [43915, 1, "\u13BB"], [43916, 1, "\u13BC"], [43917, 1, "\u13BD"], [43918, 1, "\u13BE"], [43919, 1, "\u13BF"], [43920, 1, "\u13C0"], [43921, 1, "\u13C1"], [43922, 1, "\u13C2"], [43923, 1, "\u13C3"], [43924, 1, "\u13C4"], [43925, 1, "\u13C5"], [43926, 1, "\u13C6"], [43927, 1, "\u13C7"], [43928, 1, "\u13C8"], [43929, 1, "\u13C9"], [43930, 1, "\u13CA"], [43931, 1, "\u13CB"], [43932, 1, "\u13CC"], [43933, 1, "\u13CD"], [43934, 1, "\u13CE"], [43935, 1, "\u13CF"], [43936, 1, "\u13D0"], [43937, 1, "\u13D1"], [43938, 1, "\u13D2"], [43939, 1, "\u13D3"], [43940, 1, "\u13D4"], [43941, 1, "\u13D5"], [43942, 1, "\u13D6"], [43943, 1, "\u13D7"], [43944, 1, "\u13D8"], [43945, 1, "\u13D9"], [43946, 1, "\u13DA"], [43947, 1, "\u13DB"], [43948, 1, "\u13DC"], [43949, 1, "\u13DD"], [43950, 1, "\u13DE"], [43951, 1, "\u13DF"], [43952, 1, "\u13E0"], [43953, 1, "\u13E1"], [43954, 1, "\u13E2"], [43955, 1, "\u13E3"], [43956, 1, "\u13E4"], [43957, 1, "\u13E5"], [43958, 1, "\u13E6"], [43959, 1, "\u13E7"], [43960, 1, "\u13E8"], [43961, 1, "\u13E9"], [43962, 1, "\u13EA"], [43963, 1, "\u13EB"], [43964, 1, "\u13EC"], [43965, 1, "\u13ED"], [43966, 1, "\u13EE"], [43967, 1, "\u13EF"], [[43968, 44010], 2], [44011, 2], [[44012, 44013], 2], [[44014, 44015], 3], [[44016, 44025], 2], [[44026, 44031], 3], [[44032, 55203], 2], [[55204, 55215], 3], [[55216, 55238], 2], [[55239, 55242], 3], [[55243, 55291], 2], [[55292, 55295], 3], [[55296, 57343], 3], [[57344, 63743], 3], [63744, 1, "\u8C48"], [63745, 1, "\u66F4"], [63746, 1, "\u8ECA"], [63747, 1, "\u8CC8"], [63748, 1, "\u6ED1"], [63749, 1, "\u4E32"], [63750, 1, "\u53E5"], [[63751, 63752], 1, "\u9F9C"], [63753, 1, "\u5951"], [63754, 1, "\u91D1"], [63755, 1, "\u5587"], [63756, 1, "\u5948"], [63757, 1, "\u61F6"], [63758, 1, "\u7669"], [63759, 1, "\u7F85"], [63760, 1, "\u863F"], [63761, 1, "\u87BA"], [63762, 1, "\u88F8"], [63763, 1, "\u908F"], [63764, 1, "\u6A02"], [63765, 1, "\u6D1B"], [63766, 1, "\u70D9"], [63767, 1, "\u73DE"], [63768, 1, "\u843D"], [63769, 1, "\u916A"], [63770, 1, "\u99F1"], [63771, 1, "\u4E82"], [63772, 1, "\u5375"], [63773, 1, "\u6B04"], [63774, 1, "\u721B"], [63775, 1, "\u862D"], [63776, 1, "\u9E1E"], [63777, 1, "\u5D50"], [63778, 1, "\u6FEB"], [63779, 1, "\u85CD"], [63780, 1, "\u8964"], [63781, 1, "\u62C9"], [63782, 1, "\u81D8"], [63783, 1, "\u881F"], [63784, 1, "\u5ECA"], [63785, 1, "\u6717"], [63786, 1, "\u6D6A"], [63787, 1, "\u72FC"], [63788, 1, "\u90CE"], [63789, 1, "\u4F86"], [63790, 1, "\u51B7"], [63791, 1, "\u52DE"], [63792, 1, "\u64C4"], [63793, 1, "\u6AD3"], [63794, 1, "\u7210"], [63795, 1, "\u76E7"], [63796, 1, "\u8001"], [63797, 1, "\u8606"], [63798, 1, "\u865C"], [63799, 1, "\u8DEF"], [63800, 1, "\u9732"], [63801, 1, "\u9B6F"], [63802, 1, "\u9DFA"], [63803, 1, "\u788C"], [63804, 1, "\u797F"], [63805, 1, "\u7DA0"], [63806, 1, "\u83C9"], [63807, 1, "\u9304"], [63808, 1, "\u9E7F"], [63809, 1, "\u8AD6"], [63810, 1, "\u58DF"], [63811, 1, "\u5F04"], [63812, 1, "\u7C60"], [63813, 1, "\u807E"], [63814, 1, "\u7262"], [63815, 1, "\u78CA"], [63816, 1, "\u8CC2"], [63817, 1, "\u96F7"], [63818, 1, "\u58D8"], [63819, 1, "\u5C62"], [63820, 1, "\u6A13"], [63821, 1, "\u6DDA"], [63822, 1, "\u6F0F"], [63823, 1, "\u7D2F"], [63824, 1, "\u7E37"], [63825, 1, "\u964B"], [63826, 1, "\u52D2"], [63827, 1, "\u808B"], [63828, 1, "\u51DC"], [63829, 1, "\u51CC"], [63830, 1, "\u7A1C"], [63831, 1, "\u7DBE"], [63832, 1, "\u83F1"], [63833, 1, "\u9675"], [63834, 1, "\u8B80"], [63835, 1, "\u62CF"], [63836, 1, "\u6A02"], [63837, 1, "\u8AFE"], [63838, 1, "\u4E39"], [63839, 1, "\u5BE7"], [63840, 1, "\u6012"], [63841, 1, "\u7387"], [63842, 1, "\u7570"], [63843, 1, "\u5317"], [63844, 1, "\u78FB"], [63845, 1, "\u4FBF"], [63846, 1, "\u5FA9"], [63847, 1, "\u4E0D"], [63848, 1, "\u6CCC"], [63849, 1, "\u6578"], [63850, 1, "\u7D22"], [63851, 1, "\u53C3"], [63852, 1, "\u585E"], [63853, 1, "\u7701"], [63854, 1, "\u8449"], [63855, 1, "\u8AAA"], [63856, 1, "\u6BBA"], [63857, 1, "\u8FB0"], [63858, 1, "\u6C88"], [63859, 1, "\u62FE"], [63860, 1, "\u82E5"], [63861, 1, "\u63A0"], [63862, 1, "\u7565"], [63863, 1, "\u4EAE"], [63864, 1, "\u5169"], [63865, 1, "\u51C9"], [63866, 1, "\u6881"], [63867, 1, "\u7CE7"], [63868, 1, "\u826F"], [63869, 1, "\u8AD2"], [63870, 1, "\u91CF"], [63871, 1, "\u52F5"], [63872, 1, "\u5442"], [63873, 1, "\u5973"], [63874, 1, "\u5EEC"], [63875, 1, "\u65C5"], [63876, 1, "\u6FFE"], [63877, 1, "\u792A"], [63878, 1, "\u95AD"], [63879, 1, "\u9A6A"], [63880, 1, "\u9E97"], [63881, 1, "\u9ECE"], [63882, 1, "\u529B"], [63883, 1, "\u66C6"], [63884, 1, "\u6B77"], [63885, 1, "\u8F62"], [63886, 1, "\u5E74"], [63887, 1, "\u6190"], [63888, 1, "\u6200"], [63889, 1, "\u649A"], [63890, 1, "\u6F23"], [63891, 1, "\u7149"], [63892, 1, "\u7489"], [63893, 1, "\u79CA"], [63894, 1, "\u7DF4"], [63895, 1, "\u806F"], [63896, 1, "\u8F26"], [63897, 1, "\u84EE"], [63898, 1, "\u9023"], [63899, 1, "\u934A"], [63900, 1, "\u5217"], [63901, 1, "\u52A3"], [63902, 1, "\u54BD"], [63903, 1, "\u70C8"], [63904, 1, "\u88C2"], [63905, 1, "\u8AAA"], [63906, 1, "\u5EC9"], [63907, 1, "\u5FF5"], [63908, 1, "\u637B"], [63909, 1, "\u6BAE"], [63910, 1, "\u7C3E"], [63911, 1, "\u7375"], [63912, 1, "\u4EE4"], [63913, 1, "\u56F9"], [63914, 1, "\u5BE7"], [63915, 1, "\u5DBA"], [63916, 1, "\u601C"], [63917, 1, "\u73B2"], [63918, 1, "\u7469"], [63919, 1, "\u7F9A"], [63920, 1, "\u8046"], [63921, 1, "\u9234"], [63922, 1, "\u96F6"], [63923, 1, "\u9748"], [63924, 1, "\u9818"], [63925, 1, "\u4F8B"], [63926, 1, "\u79AE"], [63927, 1, "\u91B4"], [63928, 1, "\u96B8"], [63929, 1, "\u60E1"], [63930, 1, "\u4E86"], [63931, 1, "\u50DA"], [63932, 1, "\u5BEE"], [63933, 1, "\u5C3F"], [63934, 1, "\u6599"], [63935, 1, "\u6A02"], [63936, 1, "\u71CE"], [63937, 1, "\u7642"], [63938, 1, "\u84FC"], [63939, 1, "\u907C"], [63940, 1, "\u9F8D"], [63941, 1, "\u6688"], [63942, 1, "\u962E"], [63943, 1, "\u5289"], [63944, 1, "\u677B"], [63945, 1, "\u67F3"], [63946, 1, "\u6D41"], [63947, 1, "\u6E9C"], [63948, 1, "\u7409"], [63949, 1, "\u7559"], [63950, 1, "\u786B"], [63951, 1, "\u7D10"], [63952, 1, "\u985E"], [63953, 1, "\u516D"], [63954, 1, "\u622E"], [63955, 1, "\u9678"], [63956, 1, "\u502B"], [63957, 1, "\u5D19"], [63958, 1, "\u6DEA"], [63959, 1, "\u8F2A"], [63960, 1, "\u5F8B"], [63961, 1, "\u6144"], [63962, 1, "\u6817"], [63963, 1, "\u7387"], [63964, 1, "\u9686"], [63965, 1, "\u5229"], [63966, 1, "\u540F"], [63967, 1, "\u5C65"], [63968, 1, "\u6613"], [63969, 1, "\u674E"], [63970, 1, "\u68A8"], [63971, 1, "\u6CE5"], [63972, 1, "\u7406"], [63973, 1, "\u75E2"], [63974, 1, "\u7F79"], [63975, 1, "\u88CF"], [63976, 1, "\u88E1"], [63977, 1, "\u91CC"], [63978, 1, "\u96E2"], [63979, 1, "\u533F"], [63980, 1, "\u6EBA"], [63981, 1, "\u541D"], [63982, 1, "\u71D0"], [63983, 1, "\u7498"], [63984, 1, "\u85FA"], [63985, 1, "\u96A3"], [63986, 1, "\u9C57"], [63987, 1, "\u9E9F"], [63988, 1, "\u6797"], [63989, 1, "\u6DCB"], [63990, 1, "\u81E8"], [63991, 1, "\u7ACB"], [63992, 1, "\u7B20"], [63993, 1, "\u7C92"], [63994, 1, "\u72C0"], [63995, 1, "\u7099"], [63996, 1, "\u8B58"], [63997, 1, "\u4EC0"], [63998, 1, "\u8336"], [63999, 1, "\u523A"], [64e3, 1, "\u5207"], [64001, 1, "\u5EA6"], [64002, 1, "\u62D3"], [64003, 1, "\u7CD6"], [64004, 1, "\u5B85"], [64005, 1, "\u6D1E"], [64006, 1, "\u66B4"], [64007, 1, "\u8F3B"], [64008, 1, "\u884C"], [64009, 1, "\u964D"], [64010, 1, "\u898B"], [64011, 1, "\u5ED3"], [64012, 1, "\u5140"], [64013, 1, "\u55C0"], [[64014, 64015], 2], [64016, 1, "\u585A"], [64017, 2], [64018, 1, "\u6674"], [[64019, 64020], 2], [64021, 1, "\u51DE"], [64022, 1, "\u732A"], [64023, 1, "\u76CA"], [64024, 1, "\u793C"], [64025, 1, "\u795E"], [64026, 1, "\u7965"], [64027, 1, "\u798F"], [64028, 1, "\u9756"], [64029, 1, "\u7CBE"], [64030, 1, "\u7FBD"], [64031, 2], [64032, 1, "\u8612"], [64033, 2], [64034, 1, "\u8AF8"], [[64035, 64036], 2], [64037, 1, "\u9038"], [64038, 1, "\u90FD"], [[64039, 64041], 2], [64042, 1, "\u98EF"], [64043, 1, "\u98FC"], [64044, 1, "\u9928"], [64045, 1, "\u9DB4"], [64046, 1, "\u90DE"], [64047, 1, "\u96B7"], [64048, 1, "\u4FAE"], [64049, 1, "\u50E7"], [64050, 1, "\u514D"], [64051, 1, "\u52C9"], [64052, 1, "\u52E4"], [64053, 1, "\u5351"], [64054, 1, "\u559D"], [64055, 1, "\u5606"], [64056, 1, "\u5668"], [64057, 1, "\u5840"], [64058, 1, "\u58A8"], [64059, 1, "\u5C64"], [64060, 1, "\u5C6E"], [64061, 1, "\u6094"], [64062, 1, "\u6168"], [64063, 1, "\u618E"], [64064, 1, "\u61F2"], [64065, 1, "\u654F"], [64066, 1, "\u65E2"], [64067, 1, "\u6691"], [64068, 1, "\u6885"], [64069, 1, "\u6D77"], [64070, 1, "\u6E1A"], [64071, 1, "\u6F22"], [64072, 1, "\u716E"], [64073, 1, "\u722B"], [64074, 1, "\u7422"], [64075, 1, "\u7891"], [64076, 1, "\u793E"], [64077, 1, "\u7949"], [64078, 1, "\u7948"], [64079, 1, "\u7950"], [64080, 1, "\u7956"], [64081, 1, "\u795D"], [64082, 1, "\u798D"], [64083, 1, "\u798E"], [64084, 1, "\u7A40"], [64085, 1, "\u7A81"], [64086, 1, "\u7BC0"], [64087, 1, "\u7DF4"], [64088, 1, "\u7E09"], [64089, 1, "\u7E41"], [64090, 1, "\u7F72"], [64091, 1, "\u8005"], [64092, 1, "\u81ED"], [[64093, 64094], 1, "\u8279"], [64095, 1, "\u8457"], [64096, 1, "\u8910"], [64097, 1, "\u8996"], [64098, 1, "\u8B01"], [64099, 1, "\u8B39"], [64100, 1, "\u8CD3"], [64101, 1, "\u8D08"], [64102, 1, "\u8FB6"], [64103, 1, "\u9038"], [64104, 1, "\u96E3"], [64105, 1, "\u97FF"], [64106, 1, "\u983B"], [64107, 1, "\u6075"], [64108, 1, "\u{242EE}"], [64109, 1, "\u8218"], [[64110, 64111], 3], [64112, 1, "\u4E26"], [64113, 1, "\u51B5"], [64114, 1, "\u5168"], [64115, 1, "\u4F80"], [64116, 1, "\u5145"], [64117, 1, "\u5180"], [64118, 1, "\u52C7"], [64119, 1, "\u52FA"], [64120, 1, "\u559D"], [64121, 1, "\u5555"], [64122, 1, "\u5599"], [64123, 1, "\u55E2"], [64124, 1, "\u585A"], [64125, 1, "\u58B3"], [64126, 1, "\u5944"], [64127, 1, "\u5954"], [64128, 1, "\u5A62"], [64129, 1, "\u5B28"], [64130, 1, "\u5ED2"], [64131, 1, "\u5ED9"], [64132, 1, "\u5F69"], [64133, 1, "\u5FAD"], [64134, 1, "\u60D8"], [64135, 1, "\u614E"], [64136, 1, "\u6108"], [64137, 1, "\u618E"], [64138, 1, "\u6160"], [64139, 1, "\u61F2"], [64140, 1, "\u6234"], [64141, 1, "\u63C4"], [64142, 1, "\u641C"], [64143, 1, "\u6452"], [64144, 1, "\u6556"], [64145, 1, "\u6674"], [64146, 1, "\u6717"], [64147, 1, "\u671B"], [64148, 1, "\u6756"], [64149, 1, "\u6B79"], [64150, 1, "\u6BBA"], [64151, 1, "\u6D41"], [64152, 1, "\u6EDB"], [64153, 1, "\u6ECB"], [64154, 1, "\u6F22"], [64155, 1, "\u701E"], [64156, 1, "\u716E"], [64157, 1, "\u77A7"], [64158, 1, "\u7235"], [64159, 1, "\u72AF"], [64160, 1, "\u732A"], [64161, 1, "\u7471"], [64162, 1, "\u7506"], [64163, 1, "\u753B"], [64164, 1, "\u761D"], [64165, 1, "\u761F"], [64166, 1, "\u76CA"], [64167, 1, "\u76DB"], [64168, 1, "\u76F4"], [64169, 1, "\u774A"], [64170, 1, "\u7740"], [64171, 1, "\u78CC"], [64172, 1, "\u7AB1"], [64173, 1, "\u7BC0"], [64174, 1, "\u7C7B"], [64175, 1, "\u7D5B"], [64176, 1, "\u7DF4"], [64177, 1, "\u7F3E"], [64178, 1, "\u8005"], [64179, 1, "\u8352"], [64180, 1, "\u83EF"], [64181, 1, "\u8779"], [64182, 1, "\u8941"], [64183, 1, "\u8986"], [64184, 1, "\u8996"], [64185, 1, "\u8ABF"], [64186, 1, "\u8AF8"], [64187, 1, "\u8ACB"], [64188, 1, "\u8B01"], [64189, 1, "\u8AFE"], [64190, 1, "\u8AED"], [64191, 1, "\u8B39"], [64192, 1, "\u8B8A"], [64193, 1, "\u8D08"], [64194, 1, "\u8F38"], [64195, 1, "\u9072"], [64196, 1, "\u9199"], [64197, 1, "\u9276"], [64198, 1, "\u967C"], [64199, 1, "\u96E3"], [64200, 1, "\u9756"], [64201, 1, "\u97DB"], [64202, 1, "\u97FF"], [64203, 1, "\u980B"], [64204, 1, "\u983B"], [64205, 1, "\u9B12"], [64206, 1, "\u9F9C"], [64207, 1, "\u{2284A}"], [64208, 1, "\u{22844}"], [64209, 1, "\u{233D5}"], [64210, 1, "\u3B9D"], [64211, 1, "\u4018"], [64212, 1, "\u4039"], [64213, 1, "\u{25249}"], [64214, 1, "\u{25CD0}"], [64215, 1, "\u{27ED3}"], [64216, 1, "\u9F43"], [64217, 1, "\u9F8E"], [[64218, 64255], 3], [64256, 1, "ff"], [64257, 1, "fi"], [64258, 1, "fl"], [64259, 1, "ffi"], [64260, 1, "ffl"], [[64261, 64262], 1, "st"], [[64263, 64274], 3], [64275, 1, "\u0574\u0576"], [64276, 1, "\u0574\u0565"], [64277, 1, "\u0574\u056B"], [64278, 1, "\u057E\u0576"], [64279, 1, "\u0574\u056D"], [[64280, 64284], 3], [64285, 1, "\u05D9\u05B4"], [64286, 2], [64287, 1, "\u05F2\u05B7"], [64288, 1, "\u05E2"], [64289, 1, "\u05D0"], [64290, 1, "\u05D3"], [64291, 1, "\u05D4"], [64292, 1, "\u05DB"], [64293, 1, "\u05DC"], [64294, 1, "\u05DD"], [64295, 1, "\u05E8"], [64296, 1, "\u05EA"], [64297, 5, "+"], [64298, 1, "\u05E9\u05C1"], [64299, 1, "\u05E9\u05C2"], [64300, 1, "\u05E9\u05BC\u05C1"], [64301, 1, "\u05E9\u05BC\u05C2"], [64302, 1, "\u05D0\u05B7"], [64303, 1, "\u05D0\u05B8"], [64304, 1, "\u05D0\u05BC"], [64305, 1, "\u05D1\u05BC"], [64306, 1, "\u05D2\u05BC"], [64307, 1, "\u05D3\u05BC"], [64308, 1, "\u05D4\u05BC"], [64309, 1, "\u05D5\u05BC"], [64310, 1, "\u05D6\u05BC"], [64311, 3], [64312, 1, "\u05D8\u05BC"], [64313, 1, "\u05D9\u05BC"], [64314, 1, "\u05DA\u05BC"], [64315, 1, "\u05DB\u05BC"], [64316, 1, "\u05DC\u05BC"], [64317, 3], [64318, 1, "\u05DE\u05BC"], [64319, 3], [64320, 1, "\u05E0\u05BC"], [64321, 1, "\u05E1\u05BC"], [64322, 3], [64323, 1, "\u05E3\u05BC"], [64324, 1, "\u05E4\u05BC"], [64325, 3], [64326, 1, "\u05E6\u05BC"], [64327, 1, "\u05E7\u05BC"], [64328, 1, "\u05E8\u05BC"], [64329, 1, "\u05E9\u05BC"], [64330, 1, "\u05EA\u05BC"], [64331, 1, "\u05D5\u05B9"], [64332, 1, "\u05D1\u05BF"], [64333, 1, "\u05DB\u05BF"], [64334, 1, "\u05E4\u05BF"], [64335, 1, "\u05D0\u05DC"], [[64336, 64337], 1, "\u0671"], [[64338, 64341], 1, "\u067B"], [[64342, 64345], 1, "\u067E"], [[64346, 64349], 1, "\u0680"], [[64350, 64353], 1, "\u067A"], [[64354, 64357], 1, "\u067F"], [[64358, 64361], 1, "\u0679"], [[64362, 64365], 1, "\u06A4"], [[64366, 64369], 1, "\u06A6"], [[64370, 64373], 1, "\u0684"], [[64374, 64377], 1, "\u0683"], [[64378, 64381], 1, "\u0686"], [[64382, 64385], 1, "\u0687"], [[64386, 64387], 1, "\u068D"], [[64388, 64389], 1, "\u068C"], [[64390, 64391], 1, "\u068E"], [[64392, 64393], 1, "\u0688"], [[64394, 64395], 1, "\u0698"], [[64396, 64397], 1, "\u0691"], [[64398, 64401], 1, "\u06A9"], [[64402, 64405], 1, "\u06AF"], [[64406, 64409], 1, "\u06B3"], [[64410, 64413], 1, "\u06B1"], [[64414, 64415], 1, "\u06BA"], [[64416, 64419], 1, "\u06BB"], [[64420, 64421], 1, "\u06C0"], [[64422, 64425], 1, "\u06C1"], [[64426, 64429], 1, "\u06BE"], [[64430, 64431], 1, "\u06D2"], [[64432, 64433], 1, "\u06D3"], [[64434, 64449], 2], [64450, 2], [[64451, 64466], 3], [[64467, 64470], 1, "\u06AD"], [[64471, 64472], 1, "\u06C7"], [[64473, 64474], 1, "\u06C6"], [[64475, 64476], 1, "\u06C8"], [64477, 1, "\u06C7\u0674"], [[64478, 64479], 1, "\u06CB"], [[64480, 64481], 1, "\u06C5"], [[64482, 64483], 1, "\u06C9"], [[64484, 64487], 1, "\u06D0"], [[64488, 64489], 1, "\u0649"], [[64490, 64491], 1, "\u0626\u0627"], [[64492, 64493], 1, "\u0626\u06D5"], [[64494, 64495], 1, "\u0626\u0648"], [[64496, 64497], 1, "\u0626\u06C7"], [[64498, 64499], 1, "\u0626\u06C6"], [[64500, 64501], 1, "\u0626\u06C8"], [[64502, 64504], 1, "\u0626\u06D0"], [[64505, 64507], 1, "\u0626\u0649"], [[64508, 64511], 1, "\u06CC"], [64512, 1, "\u0626\u062C"], [64513, 1, "\u0626\u062D"], [64514, 1, "\u0626\u0645"], [64515, 1, "\u0626\u0649"], [64516, 1, "\u0626\u064A"], [64517, 1, "\u0628\u062C"], [64518, 1, "\u0628\u062D"], [64519, 1, "\u0628\u062E"], [64520, 1, "\u0628\u0645"], [64521, 1, "\u0628\u0649"], [64522, 1, "\u0628\u064A"], [64523, 1, "\u062A\u062C"], [64524, 1, "\u062A\u062D"], [64525, 1, "\u062A\u062E"], [64526, 1, "\u062A\u0645"], [64527, 1, "\u062A\u0649"], [64528, 1, "\u062A\u064A"], [64529, 1, "\u062B\u062C"], [64530, 1, "\u062B\u0645"], [64531, 1, "\u062B\u0649"], [64532, 1, "\u062B\u064A"], [64533, 1, "\u062C\u062D"], [64534, 1, "\u062C\u0645"], [64535, 1, "\u062D\u062C"], [64536, 1, "\u062D\u0645"], [64537, 1, "\u062E\u062C"], [64538, 1, "\u062E\u062D"], [64539, 1, "\u062E\u0645"], [64540, 1, "\u0633\u062C"], [64541, 1, "\u0633\u062D"], [64542, 1, "\u0633\u062E"], [64543, 1, "\u0633\u0645"], [64544, 1, "\u0635\u062D"], [64545, 1, "\u0635\u0645"], [64546, 1, "\u0636\u062C"], [64547, 1, "\u0636\u062D"], [64548, 1, "\u0636\u062E"], [64549, 1, "\u0636\u0645"], [64550, 1, "\u0637\u062D"], [64551, 1, "\u0637\u0645"], [64552, 1, "\u0638\u0645"], [64553, 1, "\u0639\u062C"], [64554, 1, "\u0639\u0645"], [64555, 1, "\u063A\u062C"], [64556, 1, "\u063A\u0645"], [64557, 1, "\u0641\u062C"], [64558, 1, "\u0641\u062D"], [64559, 1, "\u0641\u062E"], [64560, 1, "\u0641\u0645"], [64561, 1, "\u0641\u0649"], [64562, 1, "\u0641\u064A"], [64563, 1, "\u0642\u062D"], [64564, 1, "\u0642\u0645"], [64565, 1, "\u0642\u0649"], [64566, 1, "\u0642\u064A"], [64567, 1, "\u0643\u0627"], [64568, 1, "\u0643\u062C"], [64569, 1, "\u0643\u062D"], [64570, 1, "\u0643\u062E"], [64571, 1, "\u0643\u0644"], [64572, 1, "\u0643\u0645"], [64573, 1, "\u0643\u0649"], [64574, 1, "\u0643\u064A"], [64575, 1, "\u0644\u062C"], [64576, 1, "\u0644\u062D"], [64577, 1, "\u0644\u062E"], [64578, 1, "\u0644\u0645"], [64579, 1, "\u0644\u0649"], [64580, 1, "\u0644\u064A"], [64581, 1, "\u0645\u062C"], [64582, 1, "\u0645\u062D"], [64583, 1, "\u0645\u062E"], [64584, 1, "\u0645\u0645"], [64585, 1, "\u0645\u0649"], [64586, 1, "\u0645\u064A"], [64587, 1, "\u0646\u062C"], [64588, 1, "\u0646\u062D"], [64589, 1, "\u0646\u062E"], [64590, 1, "\u0646\u0645"], [64591, 1, "\u0646\u0649"], [64592, 1, "\u0646\u064A"], [64593, 1, "\u0647\u062C"], [64594, 1, "\u0647\u0645"], [64595, 1, "\u0647\u0649"], [64596, 1, "\u0647\u064A"], [64597, 1, "\u064A\u062C"], [64598, 1, "\u064A\u062D"], [64599, 1, "\u064A\u062E"], [64600, 1, "\u064A\u0645"], [64601, 1, "\u064A\u0649"], [64602, 1, "\u064A\u064A"], [64603, 1, "\u0630\u0670"], [64604, 1, "\u0631\u0670"], [64605, 1, "\u0649\u0670"], [64606, 5, " \u064C\u0651"], [64607, 5, " \u064D\u0651"], [64608, 5, " \u064E\u0651"], [64609, 5, " \u064F\u0651"], [64610, 5, " \u0650\u0651"], [64611, 5, " \u0651\u0670"], [64612, 1, "\u0626\u0631"], [64613, 1, "\u0626\u0632"], [64614, 1, "\u0626\u0645"], [64615, 1, "\u0626\u0646"], [64616, 1, "\u0626\u0649"], [64617, 1, "\u0626\u064A"], [64618, 1, "\u0628\u0631"], [64619, 1, "\u0628\u0632"], [64620, 1, "\u0628\u0645"], [64621, 1, "\u0628\u0646"], [64622, 1, "\u0628\u0649"], [64623, 1, "\u0628\u064A"], [64624, 1, "\u062A\u0631"], [64625, 1, "\u062A\u0632"], [64626, 1, "\u062A\u0645"], [64627, 1, "\u062A\u0646"], [64628, 1, "\u062A\u0649"], [64629, 1, "\u062A\u064A"], [64630, 1, "\u062B\u0631"], [64631, 1, "\u062B\u0632"], [64632, 1, "\u062B\u0645"], [64633, 1, "\u062B\u0646"], [64634, 1, "\u062B\u0649"], [64635, 1, "\u062B\u064A"], [64636, 1, "\u0641\u0649"], [64637, 1, "\u0641\u064A"], [64638, 1, "\u0642\u0649"], [64639, 1, "\u0642\u064A"], [64640, 1, "\u0643\u0627"], [64641, 1, "\u0643\u0644"], [64642, 1, "\u0643\u0645"], [64643, 1, "\u0643\u0649"], [64644, 1, "\u0643\u064A"], [64645, 1, "\u0644\u0645"], [64646, 1, "\u0644\u0649"], [64647, 1, "\u0644\u064A"], [64648, 1, "\u0645\u0627"], [64649, 1, "\u0645\u0645"], [64650, 1, "\u0646\u0631"], [64651, 1, "\u0646\u0632"], [64652, 1, "\u0646\u0645"], [64653, 1, "\u0646\u0646"], [64654, 1, "\u0646\u0649"], [64655, 1, "\u0646\u064A"], [64656, 1, "\u0649\u0670"], [64657, 1, "\u064A\u0631"], [64658, 1, "\u064A\u0632"], [64659, 1, "\u064A\u0645"], [64660, 1, "\u064A\u0646"], [64661, 1, "\u064A\u0649"], [64662, 1, "\u064A\u064A"], [64663, 1, "\u0626\u062C"], [64664, 1, "\u0626\u062D"], [64665, 1, "\u0626\u062E"], [64666, 1, "\u0626\u0645"], [64667, 1, "\u0626\u0647"], [64668, 1, "\u0628\u062C"], [64669, 1, "\u0628\u062D"], [64670, 1, "\u0628\u062E"], [64671, 1, "\u0628\u0645"], [64672, 1, "\u0628\u0647"], [64673, 1, "\u062A\u062C"], [64674, 1, "\u062A\u062D"], [64675, 1, "\u062A\u062E"], [64676, 1, "\u062A\u0645"], [64677, 1, "\u062A\u0647"], [64678, 1, "\u062B\u0645"], [64679, 1, "\u062C\u062D"], [64680, 1, "\u062C\u0645"], [64681, 1, "\u062D\u062C"], [64682, 1, "\u062D\u0645"], [64683, 1, "\u062E\u062C"], [64684, 1, "\u062E\u0645"], [64685, 1, "\u0633\u062C"], [64686, 1, "\u0633\u062D"], [64687, 1, "\u0633\u062E"], [64688, 1, "\u0633\u0645"], [64689, 1, "\u0635\u062D"], [64690, 1, "\u0635\u062E"], [64691, 1, "\u0635\u0645"], [64692, 1, "\u0636\u062C"], [64693, 1, "\u0636\u062D"], [64694, 1, "\u0636\u062E"], [64695, 1, "\u0636\u0645"], [64696, 1, "\u0637\u062D"], [64697, 1, "\u0638\u0645"], [64698, 1, "\u0639\u062C"], [64699, 1, "\u0639\u0645"], [64700, 1, "\u063A\u062C"], [64701, 1, "\u063A\u0645"], [64702, 1, "\u0641\u062C"], [64703, 1, "\u0641\u062D"], [64704, 1, "\u0641\u062E"], [64705, 1, "\u0641\u0645"], [64706, 1, "\u0642\u062D"], [64707, 1, "\u0642\u0645"], [64708, 1, "\u0643\u062C"], [64709, 1, "\u0643\u062D"], [64710, 1, "\u0643\u062E"], [64711, 1, "\u0643\u0644"], [64712, 1, "\u0643\u0645"], [64713, 1, "\u0644\u062C"], [64714, 1, "\u0644\u062D"], [64715, 1, "\u0644\u062E"], [64716, 1, "\u0644\u0645"], [64717, 1, "\u0644\u0647"], [64718, 1, "\u0645\u062C"], [64719, 1, "\u0645\u062D"], [64720, 1, "\u0645\u062E"], [64721, 1, "\u0645\u0645"], [64722, 1, "\u0646\u062C"], [64723, 1, "\u0646\u062D"], [64724, 1, "\u0646\u062E"], [64725, 1, "\u0646\u0645"], [64726, 1, "\u0646\u0647"], [64727, 1, "\u0647\u062C"], [64728, 1, "\u0647\u0645"], [64729, 1, "\u0647\u0670"], [64730, 1, "\u064A\u062C"], [64731, 1, "\u064A\u062D"], [64732, 1, "\u064A\u062E"], [64733, 1, "\u064A\u0645"], [64734, 1, "\u064A\u0647"], [64735, 1, "\u0626\u0645"], [64736, 1, "\u0626\u0647"], [64737, 1, "\u0628\u0645"], [64738, 1, "\u0628\u0647"], [64739, 1, "\u062A\u0645"], [64740, 1, "\u062A\u0647"], [64741, 1, "\u062B\u0645"], [64742, 1, "\u062B\u0647"], [64743, 1, "\u0633\u0645"], [64744, 1, "\u0633\u0647"], [64745, 1, "\u0634\u0645"], [64746, 1, "\u0634\u0647"], [64747, 1, "\u0643\u0644"], [64748, 1, "\u0643\u0645"], [64749, 1, "\u0644\u0645"], [64750, 1, "\u0646\u0645"], [64751, 1, "\u0646\u0647"], [64752, 1, "\u064A\u0645"], [64753, 1, "\u064A\u0647"], [64754, 1, "\u0640\u064E\u0651"], [64755, 1, "\u0640\u064F\u0651"], [64756, 1, "\u0640\u0650\u0651"], [64757, 1, "\u0637\u0649"], [64758, 1, "\u0637\u064A"], [64759, 1, "\u0639\u0649"], [64760, 1, "\u0639\u064A"], [64761, 1, "\u063A\u0649"], [64762, 1, "\u063A\u064A"], [64763, 1, "\u0633\u0649"], [64764, 1, "\u0633\u064A"], [64765, 1, "\u0634\u0649"], [64766, 1, "\u0634\u064A"], [64767, 1, "\u062D\u0649"], [64768, 1, "\u062D\u064A"], [64769, 1, "\u062C\u0649"], [64770, 1, "\u062C\u064A"], [64771, 1, "\u062E\u0649"], [64772, 1, "\u062E\u064A"], [64773, 1, "\u0635\u0649"], [64774, 1, "\u0635\u064A"], [64775, 1, "\u0636\u0649"], [64776, 1, "\u0636\u064A"], [64777, 1, "\u0634\u062C"], [64778, 1, "\u0634\u062D"], [64779, 1, "\u0634\u062E"], [64780, 1, "\u0634\u0645"], [64781, 1, "\u0634\u0631"], [64782, 1, "\u0633\u0631"], [64783, 1, "\u0635\u0631"], [64784, 1, "\u0636\u0631"], [64785, 1, "\u0637\u0649"], [64786, 1, "\u0637\u064A"], [64787, 1, "\u0639\u0649"], [64788, 1, "\u0639\u064A"], [64789, 1, "\u063A\u0649"], [64790, 1, "\u063A\u064A"], [64791, 1, "\u0633\u0649"], [64792, 1, "\u0633\u064A"], [64793, 1, "\u0634\u0649"], [64794, 1, "\u0634\u064A"], [64795, 1, "\u062D\u0649"], [64796, 1, "\u062D\u064A"], [64797, 1, "\u062C\u0649"], [64798, 1, "\u062C\u064A"], [64799, 1, "\u062E\u0649"], [64800, 1, "\u062E\u064A"], [64801, 1, "\u0635\u0649"], [64802, 1, "\u0635\u064A"], [64803, 1, "\u0636\u0649"], [64804, 1, "\u0636\u064A"], [64805, 1, "\u0634\u062C"], [64806, 1, "\u0634\u062D"], [64807, 1, "\u0634\u062E"], [64808, 1, "\u0634\u0645"], [64809, 1, "\u0634\u0631"], [64810, 1, "\u0633\u0631"], [64811, 1, "\u0635\u0631"], [64812, 1, "\u0636\u0631"], [64813, 1, "\u0634\u062C"], [64814, 1, "\u0634\u062D"], [64815, 1, "\u0634\u062E"], [64816, 1, "\u0634\u0645"], [64817, 1, "\u0633\u0647"], [64818, 1, "\u0634\u0647"], [64819, 1, "\u0637\u0645"], [64820, 1, "\u0633\u062C"], [64821, 1, "\u0633\u062D"], [64822, 1, "\u0633\u062E"], [64823, 1, "\u0634\u062C"], [64824, 1, "\u0634\u062D"], [64825, 1, "\u0634\u062E"], [64826, 1, "\u0637\u0645"], [64827, 1, "\u0638\u0645"], [[64828, 64829], 1, "\u0627\u064B"], [[64830, 64831], 2], [[64832, 64847], 2], [64848, 1, "\u062A\u062C\u0645"], [[64849, 64850], 1, "\u062A\u062D\u062C"], [64851, 1, "\u062A\u062D\u0645"], [64852, 1, "\u062A\u062E\u0645"], [64853, 1, "\u062A\u0645\u062C"], [64854, 1, "\u062A\u0645\u062D"], [64855, 1, "\u062A\u0645\u062E"], [[64856, 64857], 1, "\u062C\u0645\u062D"], [64858, 1, "\u062D\u0645\u064A"], [64859, 1, "\u062D\u0645\u0649"], [64860, 1, "\u0633\u062D\u062C"], [64861, 1, "\u0633\u062C\u062D"], [64862, 1, "\u0633\u062C\u0649"], [[64863, 64864], 1, "\u0633\u0645\u062D"], [64865, 1, "\u0633\u0645\u062C"], [[64866, 64867], 1, "\u0633\u0645\u0645"], [[64868, 64869], 1, "\u0635\u062D\u062D"], [64870, 1, "\u0635\u0645\u0645"], [[64871, 64872], 1, "\u0634\u062D\u0645"], [64873, 1, "\u0634\u062C\u064A"], [[64874, 64875], 1, "\u0634\u0645\u062E"], [[64876, 64877], 1, "\u0634\u0645\u0645"], [64878, 1, "\u0636\u062D\u0649"], [[64879, 64880], 1, "\u0636\u062E\u0645"], [[64881, 64882], 1, "\u0637\u0645\u062D"], [64883, 1, "\u0637\u0645\u0645"], [64884, 1, "\u0637\u0645\u064A"], [64885, 1, "\u0639\u062C\u0645"], [[64886, 64887], 1, "\u0639\u0645\u0645"], [64888, 1, "\u0639\u0645\u0649"], [64889, 1, "\u063A\u0645\u0645"], [64890, 1, "\u063A\u0645\u064A"], [64891, 1, "\u063A\u0645\u0649"], [[64892, 64893], 1, "\u0641\u062E\u0645"], [64894, 1, "\u0642\u0645\u062D"], [64895, 1, "\u0642\u0645\u0645"], [64896, 1, "\u0644\u062D\u0645"], [64897, 1, "\u0644\u062D\u064A"], [64898, 1, "\u0644\u062D\u0649"], [[64899, 64900], 1, "\u0644\u062C\u062C"], [[64901, 64902], 1, "\u0644\u062E\u0645"], [[64903, 64904], 1, "\u0644\u0645\u062D"], [64905, 1, "\u0645\u062D\u062C"], [64906, 1, "\u0645\u062D\u0645"], [64907, 1, "\u0645\u062D\u064A"], [64908, 1, "\u0645\u062C\u062D"], [64909, 1, "\u0645\u062C\u0645"], [64910, 1, "\u0645\u062E\u062C"], [64911, 1, "\u0645\u062E\u0645"], [[64912, 64913], 3], [64914, 1, "\u0645\u062C\u062E"], [64915, 1, "\u0647\u0645\u062C"], [64916, 1, "\u0647\u0645\u0645"], [64917, 1, "\u0646\u062D\u0645"], [64918, 1, "\u0646\u062D\u0649"], [[64919, 64920], 1, "\u0646\u062C\u0645"], [64921, 1, "\u0646\u062C\u0649"], [64922, 1, "\u0646\u0645\u064A"], [64923, 1, "\u0646\u0645\u0649"], [[64924, 64925], 1, "\u064A\u0645\u0645"], [64926, 1, "\u0628\u062E\u064A"], [64927, 1, "\u062A\u062C\u064A"], [64928, 1, "\u062A\u062C\u0649"], [64929, 1, "\u062A\u062E\u064A"], [64930, 1, "\u062A\u062E\u0649"], [64931, 1, "\u062A\u0645\u064A"], [64932, 1, "\u062A\u0645\u0649"], [64933, 1, "\u062C\u0645\u064A"], [64934, 1, "\u062C\u062D\u0649"], [64935, 1, "\u062C\u0645\u0649"], [64936, 1, "\u0633\u062E\u0649"], [64937, 1, "\u0635\u062D\u064A"], [64938, 1, "\u0634\u062D\u064A"], [64939, 1, "\u0636\u062D\u064A"], [64940, 1, "\u0644\u062C\u064A"], [64941, 1, "\u0644\u0645\u064A"], [64942, 1, "\u064A\u062D\u064A"], [64943, 1, "\u064A\u062C\u064A"], [64944, 1, "\u064A\u0645\u064A"], [64945, 1, "\u0645\u0645\u064A"], [64946, 1, "\u0642\u0645\u064A"], [64947, 1, "\u0646\u062D\u064A"], [64948, 1, "\u0642\u0645\u062D"], [64949, 1, "\u0644\u062D\u0645"], [64950, 1, "\u0639\u0645\u064A"], [64951, 1, "\u0643\u0645\u064A"], [64952, 1, "\u0646\u062C\u062D"], [64953, 1, "\u0645\u062E\u064A"], [64954, 1, "\u0644\u062C\u0645"], [64955, 1, "\u0643\u0645\u0645"], [64956, 1, "\u0644\u062C\u0645"], [64957, 1, "\u0646\u062C\u062D"], [64958, 1, "\u062C\u062D\u064A"], [64959, 1, "\u062D\u062C\u064A"], [64960, 1, "\u0645\u062C\u064A"], [64961, 1, "\u0641\u0645\u064A"], [64962, 1, "\u0628\u062D\u064A"], [64963, 1, "\u0643\u0645\u0645"], [64964, 1, "\u0639\u062C\u0645"], [64965, 1, "\u0635\u0645\u0645"], [64966, 1, "\u0633\u062E\u064A"], [64967, 1, "\u0646\u062C\u064A"], [[64968, 64974], 3], [64975, 2], [[64976, 65007], 3], [65008, 1, "\u0635\u0644\u06D2"], [65009, 1, "\u0642\u0644\u06D2"], [65010, 1, "\u0627\u0644\u0644\u0647"], [65011, 1, "\u0627\u0643\u0628\u0631"], [65012, 1, "\u0645\u062D\u0645\u062F"], [65013, 1, "\u0635\u0644\u0639\u0645"], [65014, 1, "\u0631\u0633\u0648\u0644"], [65015, 1, "\u0639\u0644\u064A\u0647"], [65016, 1, "\u0648\u0633\u0644\u0645"], [65017, 1, "\u0635\u0644\u0649"], [65018, 5, "\u0635\u0644\u0649 \u0627\u0644\u0644\u0647 \u0639\u0644\u064A\u0647 \u0648\u0633\u0644\u0645"], [65019, 5, "\u062C\u0644 \u062C\u0644\u0627\u0644\u0647"], [65020, 1, "\u0631\u06CC\u0627\u0644"], [65021, 2], [[65022, 65023], 2], [[65024, 65039], 7], [65040, 5, ","], [65041, 1, "\u3001"], [65042, 3], [65043, 5, ":"], [65044, 5, ";"], [65045, 5, "!"], [65046, 5, "?"], [65047, 1, "\u3016"], [65048, 1, "\u3017"], [65049, 3], [[65050, 65055], 3], [[65056, 65059], 2], [[65060, 65062], 2], [[65063, 65069], 2], [[65070, 65071], 2], [65072, 3], [65073, 1, "\u2014"], [65074, 1, "\u2013"], [[65075, 65076], 5, "_"], [65077, 5, "("], [65078, 5, ")"], [65079, 5, "{"], [65080, 5, "}"], [65081, 1, "\u3014"], [65082, 1, "\u3015"], [65083, 1, "\u3010"], [65084, 1, "\u3011"], [65085, 1, "\u300A"], [65086, 1, "\u300B"], [65087, 1, "\u3008"], [65088, 1, "\u3009"], [65089, 1, "\u300C"], [65090, 1, "\u300D"], [65091, 1, "\u300E"], [65092, 1, "\u300F"], [[65093, 65094], 2], [65095, 5, "["], [65096, 5, "]"], [[65097, 65100], 5, " \u0305"], [[65101, 65103], 5, "_"], [65104, 5, ","], [65105, 1, "\u3001"], [65106, 3], [65107, 3], [65108, 5, ";"], [65109, 5, ":"], [65110, 5, "?"], [65111, 5, "!"], [65112, 1, "\u2014"], [65113, 5, "("], [65114, 5, ")"], [65115, 5, "{"], [65116, 5, "}"], [65117, 1, "\u3014"], [65118, 1, "\u3015"], [65119, 5, "#"], [65120, 5, "&"], [65121, 5, "*"], [65122, 5, "+"], [65123, 1, "-"], [65124, 5, "<"], [65125, 5, ">"], [65126, 5, "="], [65127, 3], [65128, 5, "\\"], [65129, 5, "$"], [65130, 5, "%"], [65131, 5, "@"], [[65132, 65135], 3], [65136, 5, " \u064B"], [65137, 1, "\u0640\u064B"], [65138, 5, " \u064C"], [65139, 2], [65140, 5, " \u064D"], [65141, 3], [65142, 5, " \u064E"], [65143, 1, "\u0640\u064E"], [65144, 5, " \u064F"], [65145, 1, "\u0640\u064F"], [65146, 5, " \u0650"], [65147, 1, "\u0640\u0650"], [65148, 5, " \u0651"], [65149, 1, "\u0640\u0651"], [65150, 5, " \u0652"], [65151, 1, "\u0640\u0652"], [65152, 1, "\u0621"], [[65153, 65154], 1, "\u0622"], [[65155, 65156], 1, "\u0623"], [[65157, 65158], 1, "\u0624"], [[65159, 65160], 1, "\u0625"], [[65161, 65164], 1, "\u0626"], [[65165, 65166], 1, "\u0627"], [[65167, 65170], 1, "\u0628"], [[65171, 65172], 1, "\u0629"], [[65173, 65176], 1, "\u062A"], [[65177, 65180], 1, "\u062B"], [[65181, 65184], 1, "\u062C"], [[65185, 65188], 1, "\u062D"], [[65189, 65192], 1, "\u062E"], [[65193, 65194], 1, "\u062F"], [[65195, 65196], 1, "\u0630"], [[65197, 65198], 1, "\u0631"], [[65199, 65200], 1, "\u0632"], [[65201, 65204], 1, "\u0633"], [[65205, 65208], 1, "\u0634"], [[65209, 65212], 1, "\u0635"], [[65213, 65216], 1, "\u0636"], [[65217, 65220], 1, "\u0637"], [[65221, 65224], 1, "\u0638"], [[65225, 65228], 1, "\u0639"], [[65229, 65232], 1, "\u063A"], [[65233, 65236], 1, "\u0641"], [[65237, 65240], 1, "\u0642"], [[65241, 65244], 1, "\u0643"], [[65245, 65248], 1, "\u0644"], [[65249, 65252], 1, "\u0645"], [[65253, 65256], 1, "\u0646"], [[65257, 65260], 1, "\u0647"], [[65261, 65262], 1, "\u0648"], [[65263, 65264], 1, "\u0649"], [[65265, 65268], 1, "\u064A"], [[65269, 65270], 1, "\u0644\u0622"], [[65271, 65272], 1, "\u0644\u0623"], [[65273, 65274], 1, "\u0644\u0625"], [[65275, 65276], 1, "\u0644\u0627"], [[65277, 65278], 3], [65279, 7], [65280, 3], [65281, 5, "!"], [65282, 5, '"'], [65283, 5, "#"], [65284, 5, "$"], [65285, 5, "%"], [65286, 5, "&"], [65287, 5, "'"], [65288, 5, "("], [65289, 5, ")"], [65290, 5, "*"], [65291, 5, "+"], [65292, 5, ","], [65293, 1, "-"], [65294, 1, "."], [65295, 5, "/"], [65296, 1, "0"], [65297, 1, "1"], [65298, 1, "2"], [65299, 1, "3"], [65300, 1, "4"], [65301, 1, "5"], [65302, 1, "6"], [65303, 1, "7"], [65304, 1, "8"], [65305, 1, "9"], [65306, 5, ":"], [65307, 5, ";"], [65308, 5, "<"], [65309, 5, "="], [65310, 5, ">"], [65311, 5, "?"], [65312, 5, "@"], [65313, 1, "a"], [65314, 1, "b"], [65315, 1, "c"], [65316, 1, "d"], [65317, 1, "e"], [65318, 1, "f"], [65319, 1, "g"], [65320, 1, "h"], [65321, 1, "i"], [65322, 1, "j"], [65323, 1, "k"], [65324, 1, "l"], [65325, 1, "m"], [65326, 1, "n"], [65327, 1, "o"], [65328, 1, "p"], [65329, 1, "q"], [65330, 1, "r"], [65331, 1, "s"], [65332, 1, "t"], [65333, 1, "u"], [65334, 1, "v"], [65335, 1, "w"], [65336, 1, "x"], [65337, 1, "y"], [65338, 1, "z"], [65339, 5, "["], [65340, 5, "\\"], [65341, 5, "]"], [65342, 5, "^"], [65343, 5, "_"], [65344, 5, "`"], [65345, 1, "a"], [65346, 1, "b"], [65347, 1, "c"], [65348, 1, "d"], [65349, 1, "e"], [65350, 1, "f"], [65351, 1, "g"], [65352, 1, "h"], [65353, 1, "i"], [65354, 1, "j"], [65355, 1, "k"], [65356, 1, "l"], [65357, 1, "m"], [65358, 1, "n"], [65359, 1, "o"], [65360, 1, "p"], [65361, 1, "q"], [65362, 1, "r"], [65363, 1, "s"], [65364, 1, "t"], [65365, 1, "u"], [65366, 1, "v"], [65367, 1, "w"], [65368, 1, "x"], [65369, 1, "y"], [65370, 1, "z"], [65371, 5, "{"], [65372, 5, "|"], [65373, 5, "}"], [65374, 5, "~"], [65375, 1, "\u2985"], [65376, 1, "\u2986"], [65377, 1, "."], [65378, 1, "\u300C"], [65379, 1, "\u300D"], [65380, 1, "\u3001"], [65381, 1, "\u30FB"], [65382, 1, "\u30F2"], [65383, 1, "\u30A1"], [65384, 1, "\u30A3"], [65385, 1, "\u30A5"], [65386, 1, "\u30A7"], [65387, 1, "\u30A9"], [65388, 1, "\u30E3"], [65389, 1, "\u30E5"], [65390, 1, "\u30E7"], [65391, 1, "\u30C3"], [65392, 1, "\u30FC"], [65393, 1, "\u30A2"], [65394, 1, "\u30A4"], [65395, 1, "\u30A6"], [65396, 1, "\u30A8"], [65397, 1, "\u30AA"], [65398, 1, "\u30AB"], [65399, 1, "\u30AD"], [65400, 1, "\u30AF"], [65401, 1, "\u30B1"], [65402, 1, "\u30B3"], [65403, 1, "\u30B5"], [65404, 1, "\u30B7"], [65405, 1, "\u30B9"], [65406, 1, "\u30BB"], [65407, 1, "\u30BD"], [65408, 1, "\u30BF"], [65409, 1, "\u30C1"], [65410, 1, "\u30C4"], [65411, 1, "\u30C6"], [65412, 1, "\u30C8"], [65413, 1, "\u30CA"], [65414, 1, "\u30CB"], [65415, 1, "\u30CC"], [65416, 1, "\u30CD"], [65417, 1, "\u30CE"], [65418, 1, "\u30CF"], [65419, 1, "\u30D2"], [65420, 1, "\u30D5"], [65421, 1, "\u30D8"], [65422, 1, "\u30DB"], [65423, 1, "\u30DE"], [65424, 1, "\u30DF"], [65425, 1, "\u30E0"], [65426, 1, "\u30E1"], [65427, 1, "\u30E2"], [65428, 1, "\u30E4"], [65429, 1, "\u30E6"], [65430, 1, "\u30E8"], [65431, 1, "\u30E9"], [65432, 1, "\u30EA"], [65433, 1, "\u30EB"], [65434, 1, "\u30EC"], [65435, 1, "\u30ED"], [65436, 1, "\u30EF"], [65437, 1, "\u30F3"], [65438, 1, "\u3099"], [65439, 1, "\u309A"], [65440, 3], [65441, 1, "\u1100"], [65442, 1, "\u1101"], [65443, 1, "\u11AA"], [65444, 1, "\u1102"], [65445, 1, "\u11AC"], [65446, 1, "\u11AD"], [65447, 1, "\u1103"], [65448, 1, "\u1104"], [65449, 1, "\u1105"], [65450, 1, "\u11B0"], [65451, 1, "\u11B1"], [65452, 1, "\u11B2"], [65453, 1, "\u11B3"], [65454, 1, "\u11B4"], [65455, 1, "\u11B5"], [65456, 1, "\u111A"], [65457, 1, "\u1106"], [65458, 1, "\u1107"], [65459, 1, "\u1108"], [65460, 1, "\u1121"], [65461, 1, "\u1109"], [65462, 1, "\u110A"], [65463, 1, "\u110B"], [65464, 1, "\u110C"], [65465, 1, "\u110D"], [65466, 1, "\u110E"], [65467, 1, "\u110F"], [65468, 1, "\u1110"], [65469, 1, "\u1111"], [65470, 1, "\u1112"], [[65471, 65473], 3], [65474, 1, "\u1161"], [65475, 1, "\u1162"], [65476, 1, "\u1163"], [65477, 1, "\u1164"], [65478, 1, "\u1165"], [65479, 1, "\u1166"], [[65480, 65481], 3], [65482, 1, "\u1167"], [65483, 1, "\u1168"], [65484, 1, "\u1169"], [65485, 1, "\u116A"], [65486, 1, "\u116B"], [65487, 1, "\u116C"], [[65488, 65489], 3], [65490, 1, "\u116D"], [65491, 1, "\u116E"], [65492, 1, "\u116F"], [65493, 1, "\u1170"], [65494, 1, "\u1171"], [65495, 1, "\u1172"], [[65496, 65497], 3], [65498, 1, "\u1173"], [65499, 1, "\u1174"], [65500, 1, "\u1175"], [[65501, 65503], 3], [65504, 1, "\xA2"], [65505, 1, "\xA3"], [65506, 1, "\xAC"], [65507, 5, " \u0304"], [65508, 1, "\xA6"], [65509, 1, "\xA5"], [65510, 1, "\u20A9"], [65511, 3], [65512, 1, "\u2502"], [65513, 1, "\u2190"], [65514, 1, "\u2191"], [65515, 1, "\u2192"], [65516, 1, "\u2193"], [65517, 1, "\u25A0"], [65518, 1, "\u25CB"], [[65519, 65528], 3], [[65529, 65531], 3], [65532, 3], [65533, 3], [[65534, 65535], 3], [[65536, 65547], 2], [65548, 3], [[65549, 65574], 2], [65575, 3], [[65576, 65594], 2], [65595, 3], [[65596, 65597], 2], [65598, 3], [[65599, 65613], 2], [[65614, 65615], 3], [[65616, 65629], 2], [[65630, 65663], 3], [[65664, 65786], 2], [[65787, 65791], 3], [[65792, 65794], 2], [[65795, 65798], 3], [[65799, 65843], 2], [[65844, 65846], 3], [[65847, 65855], 2], [[65856, 65930], 2], [[65931, 65932], 2], [[65933, 65934], 2], [65935, 3], [[65936, 65947], 2], [65948, 2], [[65949, 65951], 3], [65952, 2], [[65953, 65999], 3], [[66e3, 66044], 2], [66045, 2], [[66046, 66175], 3], [[66176, 66204], 2], [[66205, 66207], 3], [[66208, 66256], 2], [[66257, 66271], 3], [66272, 2], [[66273, 66299], 2], [[66300, 66303], 3], [[66304, 66334], 2], [66335, 2], [[66336, 66339], 2], [[66340, 66348], 3], [[66349, 66351], 2], [[66352, 66368], 2], [66369, 2], [[66370, 66377], 2], [66378, 2], [[66379, 66383], 3], [[66384, 66426], 2], [[66427, 66431], 3], [[66432, 66461], 2], [66462, 3], [66463, 2], [[66464, 66499], 2], [[66500, 66503], 3], [[66504, 66511], 2], [[66512, 66517], 2], [[66518, 66559], 3], [66560, 1, "\u{10428}"], [66561, 1, "\u{10429}"], [66562, 1, "\u{1042A}"], [66563, 1, "\u{1042B}"], [66564, 1, "\u{1042C}"], [66565, 1, "\u{1042D}"], [66566, 1, "\u{1042E}"], [66567, 1, "\u{1042F}"], [66568, 1, "\u{10430}"], [66569, 1, "\u{10431}"], [66570, 1, "\u{10432}"], [66571, 1, "\u{10433}"], [66572, 1, "\u{10434}"], [66573, 1, "\u{10435}"], [66574, 1, "\u{10436}"], [66575, 1, "\u{10437}"], [66576, 1, "\u{10438}"], [66577, 1, "\u{10439}"], [66578, 1, "\u{1043A}"], [66579, 1, "\u{1043B}"], [66580, 1, "\u{1043C}"], [66581, 1, "\u{1043D}"], [66582, 1, "\u{1043E}"], [66583, 1, "\u{1043F}"], [66584, 1, "\u{10440}"], [66585, 1, "\u{10441}"], [66586, 1, "\u{10442}"], [66587, 1, "\u{10443}"], [66588, 1, "\u{10444}"], [66589, 1, "\u{10445}"], [66590, 1, "\u{10446}"], [66591, 1, "\u{10447}"], [66592, 1, "\u{10448}"], [66593, 1, "\u{10449}"], [66594, 1, "\u{1044A}"], [66595, 1, "\u{1044B}"], [66596, 1, "\u{1044C}"], [66597, 1, "\u{1044D}"], [66598, 1, "\u{1044E}"], [66599, 1, "\u{1044F}"], [[66600, 66637], 2], [[66638, 66717], 2], [[66718, 66719], 3], [[66720, 66729], 2], [[66730, 66735], 3], [66736, 1, "\u{104D8}"], [66737, 1, "\u{104D9}"], [66738, 1, "\u{104DA}"], [66739, 1, "\u{104DB}"], [66740, 1, "\u{104DC}"], [66741, 1, "\u{104DD}"], [66742, 1, "\u{104DE}"], [66743, 1, "\u{104DF}"], [66744, 1, "\u{104E0}"], [66745, 1, "\u{104E1}"], [66746, 1, "\u{104E2}"], [66747, 1, "\u{104E3}"], [66748, 1, "\u{104E4}"], [66749, 1, "\u{104E5}"], [66750, 1, "\u{104E6}"], [66751, 1, "\u{104E7}"], [66752, 1, "\u{104E8}"], [66753, 1, "\u{104E9}"], [66754, 1, "\u{104EA}"], [66755, 1, "\u{104EB}"], [66756, 1, "\u{104EC}"], [66757, 1, "\u{104ED}"], [66758, 1, "\u{104EE}"], [66759, 1, "\u{104EF}"], [66760, 1, "\u{104F0}"], [66761, 1, "\u{104F1}"], [66762, 1, "\u{104F2}"], [66763, 1, "\u{104F3}"], [66764, 1, "\u{104F4}"], [66765, 1, "\u{104F5}"], [66766, 1, "\u{104F6}"], [66767, 1, "\u{104F7}"], [66768, 1, "\u{104F8}"], [66769, 1, "\u{104F9}"], [66770, 1, "\u{104FA}"], [66771, 1, "\u{104FB}"], [[66772, 66775], 3], [[66776, 66811], 2], [[66812, 66815], 3], [[66816, 66855], 2], [[66856, 66863], 3], [[66864, 66915], 2], [[66916, 66926], 3], [66927, 2], [66928, 1, "\u{10597}"], [66929, 1, "\u{10598}"], [66930, 1, "\u{10599}"], [66931, 1, "\u{1059A}"], [66932, 1, "\u{1059B}"], [66933, 1, "\u{1059C}"], [66934, 1, "\u{1059D}"], [66935, 1, "\u{1059E}"], [66936, 1, "\u{1059F}"], [66937, 1, "\u{105A0}"], [66938, 1, "\u{105A1}"], [66939, 3], [66940, 1, "\u{105A3}"], [66941, 1, "\u{105A4}"], [66942, 1, "\u{105A5}"], [66943, 1, "\u{105A6}"], [66944, 1, "\u{105A7}"], [66945, 1, "\u{105A8}"], [66946, 1, "\u{105A9}"], [66947, 1, "\u{105AA}"], [66948, 1, "\u{105AB}"], [66949, 1, "\u{105AC}"], [66950, 1, "\u{105AD}"], [66951, 1, "\u{105AE}"], [66952, 1, "\u{105AF}"], [66953, 1, "\u{105B0}"], [66954, 1, "\u{105B1}"], [66955, 3], [66956, 1, "\u{105B3}"], [66957, 1, "\u{105B4}"], [66958, 1, "\u{105B5}"], [66959, 1, "\u{105B6}"], [66960, 1, "\u{105B7}"], [66961, 1, "\u{105B8}"], [66962, 1, "\u{105B9}"], [66963, 3], [66964, 1, "\u{105BB}"], [66965, 1, "\u{105BC}"], [66966, 3], [[66967, 66977], 2], [66978, 3], [[66979, 66993], 2], [66994, 3], [[66995, 67001], 2], [67002, 3], [[67003, 67004], 2], [[67005, 67071], 3], [[67072, 67382], 2], [[67383, 67391], 3], [[67392, 67413], 2], [[67414, 67423], 3], [[67424, 67431], 2], [[67432, 67455], 3], [67456, 2], [67457, 1, "\u02D0"], [67458, 1, "\u02D1"], [67459, 1, "\xE6"], [67460, 1, "\u0299"], [67461, 1, "\u0253"], [67462, 3], [67463, 1, "\u02A3"], [67464, 1, "\uAB66"], [67465, 1, "\u02A5"], [67466, 1, "\u02A4"], [67467, 1, "\u0256"], [67468, 1, "\u0257"], [67469, 1, "\u1D91"], [67470, 1, "\u0258"], [67471, 1, "\u025E"], [67472, 1, "\u02A9"], [67473, 1, "\u0264"], [67474, 1, "\u0262"], [67475, 1, "\u0260"], [67476, 1, "\u029B"], [67477, 1, "\u0127"], [67478, 1, "\u029C"], [67479, 1, "\u0267"], [67480, 1, "\u0284"], [67481, 1, "\u02AA"], [67482, 1, "\u02AB"], [67483, 1, "\u026C"], [67484, 1, "\u{1DF04}"], [67485, 1, "\uA78E"], [67486, 1, "\u026E"], [67487, 1, "\u{1DF05}"], [67488, 1, "\u028E"], [67489, 1, "\u{1DF06}"], [67490, 1, "\xF8"], [67491, 1, "\u0276"], [67492, 1, "\u0277"], [67493, 1, "q"], [67494, 1, "\u027A"], [67495, 1, "\u{1DF08}"], [67496, 1, "\u027D"], [67497, 1, "\u027E"], [67498, 1, "\u0280"], [67499, 1, "\u02A8"], [67500, 1, "\u02A6"], [67501, 1, "\uAB67"], [67502, 1, "\u02A7"], [67503, 1, "\u0288"], [67504, 1, "\u2C71"], [67505, 3], [67506, 1, "\u028F"], [67507, 1, "\u02A1"], [67508, 1, "\u02A2"], [67509, 1, "\u0298"], [67510, 1, "\u01C0"], [67511, 1, "\u01C1"], [67512, 1, "\u01C2"], [67513, 1, "\u{1DF0A}"], [67514, 1, "\u{1DF1E}"], [[67515, 67583], 3], [[67584, 67589], 2], [[67590, 67591], 3], [67592, 2], [67593, 3], [[67594, 67637], 2], [67638, 3], [[67639, 67640], 2], [[67641, 67643], 3], [67644, 2], [[67645, 67646], 3], [67647, 2], [[67648, 67669], 2], [67670, 3], [[67671, 67679], 2], [[67680, 67702], 2], [[67703, 67711], 2], [[67712, 67742], 2], [[67743, 67750], 3], [[67751, 67759], 2], [[67760, 67807], 3], [[67808, 67826], 2], [67827, 3], [[67828, 67829], 2], [[67830, 67834], 3], [[67835, 67839], 2], [[67840, 67861], 2], [[67862, 67865], 2], [[67866, 67867], 2], [[67868, 67870], 3], [67871, 2], [[67872, 67897], 2], [[67898, 67902], 3], [67903, 2], [[67904, 67967], 3], [[67968, 68023], 2], [[68024, 68027], 3], [[68028, 68029], 2], [[68030, 68031], 2], [[68032, 68047], 2], [[68048, 68049], 3], [[68050, 68095], 2], [[68096, 68099], 2], [68100, 3], [[68101, 68102], 2], [[68103, 68107], 3], [[68108, 68115], 2], [68116, 3], [[68117, 68119], 2], [68120, 3], [[68121, 68147], 2], [[68148, 68149], 2], [[68150, 68151], 3], [[68152, 68154], 2], [[68155, 68158], 3], [68159, 2], [[68160, 68167], 2], [68168, 2], [[68169, 68175], 3], [[68176, 68184], 2], [[68185, 68191], 3], [[68192, 68220], 2], [[68221, 68223], 2], [[68224, 68252], 2], [[68253, 68255], 2], [[68256, 68287], 3], [[68288, 68295], 2], [68296, 2], [[68297, 68326], 2], [[68327, 68330], 3], [[68331, 68342], 2], [[68343, 68351], 3], [[68352, 68405], 2], [[68406, 68408], 3], [[68409, 68415], 2], [[68416, 68437], 2], [[68438, 68439], 3], [[68440, 68447], 2], [[68448, 68466], 2], [[68467, 68471], 3], [[68472, 68479], 2], [[68480, 68497], 2], [[68498, 68504], 3], [[68505, 68508], 2], [[68509, 68520], 3], [[68521, 68527], 2], [[68528, 68607], 3], [[68608, 68680], 2], [[68681, 68735], 3], [68736, 1, "\u{10CC0}"], [68737, 1, "\u{10CC1}"], [68738, 1, "\u{10CC2}"], [68739, 1, "\u{10CC3}"], [68740, 1, "\u{10CC4}"], [68741, 1, "\u{10CC5}"], [68742, 1, "\u{10CC6}"], [68743, 1, "\u{10CC7}"], [68744, 1, "\u{10CC8}"], [68745, 1, "\u{10CC9}"], [68746, 1, "\u{10CCA}"], [68747, 1, "\u{10CCB}"], [68748, 1, "\u{10CCC}"], [68749, 1, "\u{10CCD}"], [68750, 1, "\u{10CCE}"], [68751, 1, "\u{10CCF}"], [68752, 1, "\u{10CD0}"], [68753, 1, "\u{10CD1}"], [68754, 1, "\u{10CD2}"], [68755, 1, "\u{10CD3}"], [68756, 1, "\u{10CD4}"], [68757, 1, "\u{10CD5}"], [68758, 1, "\u{10CD6}"], [68759, 1, "\u{10CD7}"], [68760, 1, "\u{10CD8}"], [68761, 1, "\u{10CD9}"], [68762, 1, "\u{10CDA}"], [68763, 1, "\u{10CDB}"], [68764, 1, "\u{10CDC}"], [68765, 1, "\u{10CDD}"], [68766, 1, "\u{10CDE}"], [68767, 1, "\u{10CDF}"], [68768, 1, "\u{10CE0}"], [68769, 1, "\u{10CE1}"], [68770, 1, "\u{10CE2}"], [68771, 1, "\u{10CE3}"], [68772, 1, "\u{10CE4}"], [68773, 1, "\u{10CE5}"], [68774, 1, "\u{10CE6}"], [68775, 1, "\u{10CE7}"], [68776, 1, "\u{10CE8}"], [68777, 1, "\u{10CE9}"], [68778, 1, "\u{10CEA}"], [68779, 1, "\u{10CEB}"], [68780, 1, "\u{10CEC}"], [68781, 1, "\u{10CED}"], [68782, 1, "\u{10CEE}"], [68783, 1, "\u{10CEF}"], [68784, 1, "\u{10CF0}"], [68785, 1, "\u{10CF1}"], [68786, 1, "\u{10CF2}"], [[68787, 68799], 3], [[68800, 68850], 2], [[68851, 68857], 3], [[68858, 68863], 2], [[68864, 68903], 2], [[68904, 68911], 3], [[68912, 68921], 2], [[68922, 69215], 3], [[69216, 69246], 2], [69247, 3], [[69248, 69289], 2], [69290, 3], [[69291, 69292], 2], [69293, 2], [[69294, 69295], 3], [[69296, 69297], 2], [[69298, 69372], 3], [[69373, 69375], 2], [[69376, 69404], 2], [[69405, 69414], 2], [69415, 2], [[69416, 69423], 3], [[69424, 69456], 2], [[69457, 69465], 2], [[69466, 69487], 3], [[69488, 69509], 2], [[69510, 69513], 2], [[69514, 69551], 3], [[69552, 69572], 2], [[69573, 69579], 2], [[69580, 69599], 3], [[69600, 69622], 2], [[69623, 69631], 3], [[69632, 69702], 2], [[69703, 69709], 2], [[69710, 69713], 3], [[69714, 69733], 2], [[69734, 69743], 2], [[69744, 69749], 2], [[69750, 69758], 3], [69759, 2], [[69760, 69818], 2], [[69819, 69820], 2], [69821, 3], [[69822, 69825], 2], [69826, 2], [[69827, 69836], 3], [69837, 3], [[69838, 69839], 3], [[69840, 69864], 2], [[69865, 69871], 3], [[69872, 69881], 2], [[69882, 69887], 3], [[69888, 69940], 2], [69941, 3], [[69942, 69951], 2], [[69952, 69955], 2], [[69956, 69958], 2], [69959, 2], [[69960, 69967], 3], [[69968, 70003], 2], [[70004, 70005], 2], [70006, 2], [[70007, 70015], 3], [[70016, 70084], 2], [[70085, 70088], 2], [[70089, 70092], 2], [70093, 2], [[70094, 70095], 2], [[70096, 70105], 2], [70106, 2], [70107, 2], [70108, 2], [[70109, 70111], 2], [70112, 3], [[70113, 70132], 2], [[70133, 70143], 3], [[70144, 70161], 2], [70162, 3], [[70163, 70199], 2], [[70200, 70205], 2], [70206, 2], [[70207, 70209], 2], [[70210, 70271], 3], [[70272, 70278], 2], [70279, 3], [70280, 2], [70281, 3], [[70282, 70285], 2], [70286, 3], [[70287, 70301], 2], [70302, 3], [[70303, 70312], 2], [70313, 2], [[70314, 70319], 3], [[70320, 70378], 2], [[70379, 70383], 3], [[70384, 70393], 2], [[70394, 70399], 3], [70400, 2], [[70401, 70403], 2], [70404, 3], [[70405, 70412], 2], [[70413, 70414], 3], [[70415, 70416], 2], [[70417, 70418], 3], [[70419, 70440], 2], [70441, 3], [[70442, 70448], 2], [70449, 3], [[70450, 70451], 2], [70452, 3], [[70453, 70457], 2], [70458, 3], [70459, 2], [[70460, 70468], 2], [[70469, 70470], 3], [[70471, 70472], 2], [[70473, 70474], 3], [[70475, 70477], 2], [[70478, 70479], 3], [70480, 2], [[70481, 70486], 3], [70487, 2], [[70488, 70492], 3], [[70493, 70499], 2], [[70500, 70501], 3], [[70502, 70508], 2], [[70509, 70511], 3], [[70512, 70516], 2], [[70517, 70655], 3], [[70656, 70730], 2], [[70731, 70735], 2], [[70736, 70745], 2], [70746, 2], [70747, 2], [70748, 3], [70749, 2], [70750, 2], [70751, 2], [[70752, 70753], 2], [[70754, 70783], 3], [[70784, 70853], 2], [70854, 2], [70855, 2], [[70856, 70863], 3], [[70864, 70873], 2], [[70874, 71039], 3], [[71040, 71093], 2], [[71094, 71095], 3], [[71096, 71104], 2], [[71105, 71113], 2], [[71114, 71127], 2], [[71128, 71133], 2], [[71134, 71167], 3], [[71168, 71232], 2], [[71233, 71235], 2], [71236, 2], [[71237, 71247], 3], [[71248, 71257], 2], [[71258, 71263], 3], [[71264, 71276], 2], [[71277, 71295], 3], [[71296, 71351], 2], [71352, 2], [71353, 2], [[71354, 71359], 3], [[71360, 71369], 2], [[71370, 71423], 3], [[71424, 71449], 2], [71450, 2], [[71451, 71452], 3], [[71453, 71467], 2], [[71468, 71471], 3], [[71472, 71481], 2], [[71482, 71487], 2], [[71488, 71494], 2], [[71495, 71679], 3], [[71680, 71738], 2], [71739, 2], [[71740, 71839], 3], [71840, 1, "\u{118C0}"], [71841, 1, "\u{118C1}"], [71842, 1, "\u{118C2}"], [71843, 1, "\u{118C3}"], [71844, 1, "\u{118C4}"], [71845, 1, "\u{118C5}"], [71846, 1, "\u{118C6}"], [71847, 1, "\u{118C7}"], [71848, 1, "\u{118C8}"], [71849, 1, "\u{118C9}"], [71850, 1, "\u{118CA}"], [71851, 1, "\u{118CB}"], [71852, 1, "\u{118CC}"], [71853, 1, "\u{118CD}"], [71854, 1, "\u{118CE}"], [71855, 1, "\u{118CF}"], [71856, 1, "\u{118D0}"], [71857, 1, "\u{118D1}"], [71858, 1, "\u{118D2}"], [71859, 1, "\u{118D3}"], [71860, 1, "\u{118D4}"], [71861, 1, "\u{118D5}"], [71862, 1, "\u{118D6}"], [71863, 1, "\u{118D7}"], [71864, 1, "\u{118D8}"], [71865, 1, "\u{118D9}"], [71866, 1, "\u{118DA}"], [71867, 1, "\u{118DB}"], [71868, 1, "\u{118DC}"], [71869, 1, "\u{118DD}"], [71870, 1, "\u{118DE}"], [71871, 1, "\u{118DF}"], [[71872, 71913], 2], [[71914, 71922], 2], [[71923, 71934], 3], [71935, 2], [[71936, 71942], 2], [[71943, 71944], 3], [71945, 2], [[71946, 71947], 3], [[71948, 71955], 2], [71956, 3], [[71957, 71958], 2], [71959, 3], [[71960, 71989], 2], [71990, 3], [[71991, 71992], 2], [[71993, 71994], 3], [[71995, 72003], 2], [[72004, 72006], 2], [[72007, 72015], 3], [[72016, 72025], 2], [[72026, 72095], 3], [[72096, 72103], 2], [[72104, 72105], 3], [[72106, 72151], 2], [[72152, 72153], 3], [[72154, 72161], 2], [72162, 2], [[72163, 72164], 2], [[72165, 72191], 3], [[72192, 72254], 2], [[72255, 72262], 2], [72263, 2], [[72264, 72271], 3], [[72272, 72323], 2], [[72324, 72325], 2], [[72326, 72345], 2], [[72346, 72348], 2], [72349, 2], [[72350, 72354], 2], [[72355, 72367], 3], [[72368, 72383], 2], [[72384, 72440], 2], [[72441, 72447], 3], [[72448, 72457], 2], [[72458, 72703], 3], [[72704, 72712], 2], [72713, 3], [[72714, 72758], 2], [72759, 3], [[72760, 72768], 2], [[72769, 72773], 2], [[72774, 72783], 3], [[72784, 72793], 2], [[72794, 72812], 2], [[72813, 72815], 3], [[72816, 72817], 2], [[72818, 72847], 2], [[72848, 72849], 3], [[72850, 72871], 2], [72872, 3], [[72873, 72886], 2], [[72887, 72959], 3], [[72960, 72966], 2], [72967, 3], [[72968, 72969], 2], [72970, 3], [[72971, 73014], 2], [[73015, 73017], 3], [73018, 2], [73019, 3], [[73020, 73021], 2], [73022, 3], [[73023, 73031], 2], [[73032, 73039], 3], [[73040, 73049], 2], [[73050, 73055], 3], [[73056, 73061], 2], [73062, 3], [[73063, 73064], 2], [73065, 3], [[73066, 73102], 2], [73103, 3], [[73104, 73105], 2], [73106, 3], [[73107, 73112], 2], [[73113, 73119], 3], [[73120, 73129], 2], [[73130, 73439], 3], [[73440, 73462], 2], [[73463, 73464], 2], [[73465, 73471], 3], [[73472, 73488], 2], [73489, 3], [[73490, 73530], 2], [[73531, 73533], 3], [[73534, 73538], 2], [[73539, 73551], 2], [[73552, 73561], 2], [[73562, 73647], 3], [73648, 2], [[73649, 73663], 3], [[73664, 73713], 2], [[73714, 73726], 3], [73727, 2], [[73728, 74606], 2], [[74607, 74648], 2], [74649, 2], [[74650, 74751], 3], [[74752, 74850], 2], [[74851, 74862], 2], [74863, 3], [[74864, 74867], 2], [74868, 2], [[74869, 74879], 3], [[74880, 75075], 2], [[75076, 77711], 3], [[77712, 77808], 2], [[77809, 77810], 2], [[77811, 77823], 3], [[77824, 78894], 2], [78895, 2], [[78896, 78904], 3], [[78905, 78911], 3], [[78912, 78933], 2], [[78934, 82943], 3], [[82944, 83526], 2], [[83527, 92159], 3], [[92160, 92728], 2], [[92729, 92735], 3], [[92736, 92766], 2], [92767, 3], [[92768, 92777], 2], [[92778, 92781], 3], [[92782, 92783], 2], [[92784, 92862], 2], [92863, 3], [[92864, 92873], 2], [[92874, 92879], 3], [[92880, 92909], 2], [[92910, 92911], 3], [[92912, 92916], 2], [92917, 2], [[92918, 92927], 3], [[92928, 92982], 2], [[92983, 92991], 2], [[92992, 92995], 2], [[92996, 92997], 2], [[92998, 93007], 3], [[93008, 93017], 2], [93018, 3], [[93019, 93025], 2], [93026, 3], [[93027, 93047], 2], [[93048, 93052], 3], [[93053, 93071], 2], [[93072, 93759], 3], [93760, 1, "\u{16E60}"], [93761, 1, "\u{16E61}"], [93762, 1, "\u{16E62}"], [93763, 1, "\u{16E63}"], [93764, 1, "\u{16E64}"], [93765, 1, "\u{16E65}"], [93766, 1, "\u{16E66}"], [93767, 1, "\u{16E67}"], [93768, 1, "\u{16E68}"], [93769, 1, "\u{16E69}"], [93770, 1, "\u{16E6A}"], [93771, 1, "\u{16E6B}"], [93772, 1, "\u{16E6C}"], [93773, 1, "\u{16E6D}"], [93774, 1, "\u{16E6E}"], [93775, 1, "\u{16E6F}"], [93776, 1, "\u{16E70}"], [93777, 1, "\u{16E71}"], [93778, 1, "\u{16E72}"], [93779, 1, "\u{16E73}"], [93780, 1, "\u{16E74}"], [93781, 1, "\u{16E75}"], [93782, 1, "\u{16E76}"], [93783, 1, "\u{16E77}"], [93784, 1, "\u{16E78}"], [93785, 1, "\u{16E79}"], [93786, 1, "\u{16E7A}"], [93787, 1, "\u{16E7B}"], [93788, 1, "\u{16E7C}"], [93789, 1, "\u{16E7D}"], [93790, 1, "\u{16E7E}"], [93791, 1, "\u{16E7F}"], [[93792, 93823], 2], [[93824, 93850], 2], [[93851, 93951], 3], [[93952, 94020], 2], [[94021, 94026], 2], [[94027, 94030], 3], [94031, 2], [[94032, 94078], 2], [[94079, 94087], 2], [[94088, 94094], 3], [[94095, 94111], 2], [[94112, 94175], 3], [94176, 2], [94177, 2], [94178, 2], [94179, 2], [94180, 2], [[94181, 94191], 3], [[94192, 94193], 2], [[94194, 94207], 3], [[94208, 100332], 2], [[100333, 100337], 2], [[100338, 100343], 2], [[100344, 100351], 3], [[100352, 101106], 2], [[101107, 101589], 2], [[101590, 101631], 3], [[101632, 101640], 2], [[101641, 110575], 3], [[110576, 110579], 2], [110580, 3], [[110581, 110587], 2], [110588, 3], [[110589, 110590], 2], [110591, 3], [[110592, 110593], 2], [[110594, 110878], 2], [[110879, 110882], 2], [[110883, 110897], 3], [110898, 2], [[110899, 110927], 3], [[110928, 110930], 2], [[110931, 110932], 3], [110933, 2], [[110934, 110947], 3], [[110948, 110951], 2], [[110952, 110959], 3], [[110960, 111355], 2], [[111356, 113663], 3], [[113664, 113770], 2], [[113771, 113775], 3], [[113776, 113788], 2], [[113789, 113791], 3], [[113792, 113800], 2], [[113801, 113807], 3], [[113808, 113817], 2], [[113818, 113819], 3], [113820, 2], [[113821, 113822], 2], [113823, 2], [[113824, 113827], 7], [[113828, 118527], 3], [[118528, 118573], 2], [[118574, 118575], 3], [[118576, 118598], 2], [[118599, 118607], 3], [[118608, 118723], 2], [[118724, 118783], 3], [[118784, 119029], 2], [[119030, 119039], 3], [[119040, 119078], 2], [[119079, 119080], 3], [119081, 2], [[119082, 119133], 2], [119134, 1, "\u{1D157}\u{1D165}"], [119135, 1, "\u{1D158}\u{1D165}"], [119136, 1, "\u{1D158}\u{1D165}\u{1D16E}"], [119137, 1, "\u{1D158}\u{1D165}\u{1D16F}"], [119138, 1, "\u{1D158}\u{1D165}\u{1D170}"], [119139, 1, "\u{1D158}\u{1D165}\u{1D171}"], [119140, 1, "\u{1D158}\u{1D165}\u{1D172}"], [[119141, 119154], 2], [[119155, 119162], 3], [[119163, 119226], 2], [119227, 1, "\u{1D1B9}\u{1D165}"], [119228, 1, "\u{1D1BA}\u{1D165}"], [119229, 1, "\u{1D1B9}\u{1D165}\u{1D16E}"], [119230, 1, "\u{1D1BA}\u{1D165}\u{1D16E}"], [119231, 1, "\u{1D1B9}\u{1D165}\u{1D16F}"], [119232, 1, "\u{1D1BA}\u{1D165}\u{1D16F}"], [[119233, 119261], 2], [[119262, 119272], 2], [[119273, 119274], 2], [[119275, 119295], 3], [[119296, 119365], 2], [[119366, 119487], 3], [[119488, 119507], 2], [[119508, 119519], 3], [[119520, 119539], 2], [[119540, 119551], 3], [[119552, 119638], 2], [[119639, 119647], 3], [[119648, 119665], 2], [[119666, 119672], 2], [[119673, 119807], 3], [119808, 1, "a"], [119809, 1, "b"], [119810, 1, "c"], [119811, 1, "d"], [119812, 1, "e"], [119813, 1, "f"], [119814, 1, "g"], [119815, 1, "h"], [119816, 1, "i"], [119817, 1, "j"], [119818, 1, "k"], [119819, 1, "l"], [119820, 1, "m"], [119821, 1, "n"], [119822, 1, "o"], [119823, 1, "p"], [119824, 1, "q"], [119825, 1, "r"], [119826, 1, "s"], [119827, 1, "t"], [119828, 1, "u"], [119829, 1, "v"], [119830, 1, "w"], [119831, 1, "x"], [119832, 1, "y"], [119833, 1, "z"], [119834, 1, "a"], [119835, 1, "b"], [119836, 1, "c"], [119837, 1, "d"], [119838, 1, "e"], [119839, 1, "f"], [119840, 1, "g"], [119841, 1, "h"], [119842, 1, "i"], [119843, 1, "j"], [119844, 1, "k"], [119845, 1, "l"], [119846, 1, "m"], [119847, 1, "n"], [119848, 1, "o"], [119849, 1, "p"], [119850, 1, "q"], [119851, 1, "r"], [119852, 1, "s"], [119853, 1, "t"], [119854, 1, "u"], [119855, 1, "v"], [119856, 1, "w"], [119857, 1, "x"], [119858, 1, "y"], [119859, 1, "z"], [119860, 1, "a"], [119861, 1, "b"], [119862, 1, "c"], [119863, 1, "d"], [119864, 1, "e"], [119865, 1, "f"], [119866, 1, "g"], [119867, 1, "h"], [119868, 1, "i"], [119869, 1, "j"], [119870, 1, "k"], [119871, 1, "l"], [119872, 1, "m"], [119873, 1, "n"], [119874, 1, "o"], [119875, 1, "p"], [119876, 1, "q"], [119877, 1, "r"], [119878, 1, "s"], [119879, 1, "t"], [119880, 1, "u"], [119881, 1, "v"], [119882, 1, "w"], [119883, 1, "x"], [119884, 1, "y"], [119885, 1, "z"], [119886, 1, "a"], [119887, 1, "b"], [119888, 1, "c"], [119889, 1, "d"], [119890, 1, "e"], [119891, 1, "f"], [119892, 1, "g"], [119893, 3], [119894, 1, "i"], [119895, 1, "j"], [119896, 1, "k"], [119897, 1, "l"], [119898, 1, "m"], [119899, 1, "n"], [119900, 1, "o"], [119901, 1, "p"], [119902, 1, "q"], [119903, 1, "r"], [119904, 1, "s"], [119905, 1, "t"], [119906, 1, "u"], [119907, 1, "v"], [119908, 1, "w"], [119909, 1, "x"], [119910, 1, "y"], [119911, 1, "z"], [119912, 1, "a"], [119913, 1, "b"], [119914, 1, "c"], [119915, 1, "d"], [119916, 1, "e"], [119917, 1, "f"], [119918, 1, "g"], [119919, 1, "h"], [119920, 1, "i"], [119921, 1, "j"], [119922, 1, "k"], [119923, 1, "l"], [119924, 1, "m"], [119925, 1, "n"], [119926, 1, "o"], [119927, 1, "p"], [119928, 1, "q"], [119929, 1, "r"], [119930, 1, "s"], [119931, 1, "t"], [119932, 1, "u"], [119933, 1, "v"], [119934, 1, "w"], [119935, 1, "x"], [119936, 1, "y"], [119937, 1, "z"], [119938, 1, "a"], [119939, 1, "b"], [119940, 1, "c"], [119941, 1, "d"], [119942, 1, "e"], [119943, 1, "f"], [119944, 1, "g"], [119945, 1, "h"], [119946, 1, "i"], [119947, 1, "j"], [119948, 1, "k"], [119949, 1, "l"], [119950, 1, "m"], [119951, 1, "n"], [119952, 1, "o"], [119953, 1, "p"], [119954, 1, "q"], [119955, 1, "r"], [119956, 1, "s"], [119957, 1, "t"], [119958, 1, "u"], [119959, 1, "v"], [119960, 1, "w"], [119961, 1, "x"], [119962, 1, "y"], [119963, 1, "z"], [119964, 1, "a"], [119965, 3], [119966, 1, "c"], [119967, 1, "d"], [[119968, 119969], 3], [119970, 1, "g"], [[119971, 119972], 3], [119973, 1, "j"], [119974, 1, "k"], [[119975, 119976], 3], [119977, 1, "n"], [119978, 1, "o"], [119979, 1, "p"], [119980, 1, "q"], [119981, 3], [119982, 1, "s"], [119983, 1, "t"], [119984, 1, "u"], [119985, 1, "v"], [119986, 1, "w"], [119987, 1, "x"], [119988, 1, "y"], [119989, 1, "z"], [119990, 1, "a"], [119991, 1, "b"], [119992, 1, "c"], [119993, 1, "d"], [119994, 3], [119995, 1, "f"], [119996, 3], [119997, 1, "h"], [119998, 1, "i"], [119999, 1, "j"], [12e4, 1, "k"], [120001, 1, "l"], [120002, 1, "m"], [120003, 1, "n"], [120004, 3], [120005, 1, "p"], [120006, 1, "q"], [120007, 1, "r"], [120008, 1, "s"], [120009, 1, "t"], [120010, 1, "u"], [120011, 1, "v"], [120012, 1, "w"], [120013, 1, "x"], [120014, 1, "y"], [120015, 1, "z"], [120016, 1, "a"], [120017, 1, "b"], [120018, 1, "c"], [120019, 1, "d"], [120020, 1, "e"], [120021, 1, "f"], [120022, 1, "g"], [120023, 1, "h"], [120024, 1, "i"], [120025, 1, "j"], [120026, 1, "k"], [120027, 1, "l"], [120028, 1, "m"], [120029, 1, "n"], [120030, 1, "o"], [120031, 1, "p"], [120032, 1, "q"], [120033, 1, "r"], [120034, 1, "s"], [120035, 1, "t"], [120036, 1, "u"], [120037, 1, "v"], [120038, 1, "w"], [120039, 1, "x"], [120040, 1, "y"], [120041, 1, "z"], [120042, 1, "a"], [120043, 1, "b"], [120044, 1, "c"], [120045, 1, "d"], [120046, 1, "e"], [120047, 1, "f"], [120048, 1, "g"], [120049, 1, "h"], [120050, 1, "i"], [120051, 1, "j"], [120052, 1, "k"], [120053, 1, "l"], [120054, 1, "m"], [120055, 1, "n"], [120056, 1, "o"], [120057, 1, "p"], [120058, 1, "q"], [120059, 1, "r"], [120060, 1, "s"], [120061, 1, "t"], [120062, 1, "u"], [120063, 1, "v"], [120064, 1, "w"], [120065, 1, "x"], [120066, 1, "y"], [120067, 1, "z"], [120068, 1, "a"], [120069, 1, "b"], [120070, 3], [120071, 1, "d"], [120072, 1, "e"], [120073, 1, "f"], [120074, 1, "g"], [[120075, 120076], 3], [120077, 1, "j"], [120078, 1, "k"], [120079, 1, "l"], [120080, 1, "m"], [120081, 1, "n"], [120082, 1, "o"], [120083, 1, "p"], [120084, 1, "q"], [120085, 3], [120086, 1, "s"], [120087, 1, "t"], [120088, 1, "u"], [120089, 1, "v"], [120090, 1, "w"], [120091, 1, "x"], [120092, 1, "y"], [120093, 3], [120094, 1, "a"], [120095, 1, "b"], [120096, 1, "c"], [120097, 1, "d"], [120098, 1, "e"], [120099, 1, "f"], [120100, 1, "g"], [120101, 1, "h"], [120102, 1, "i"], [120103, 1, "j"], [120104, 1, "k"], [120105, 1, "l"], [120106, 1, "m"], [120107, 1, "n"], [120108, 1, "o"], [120109, 1, "p"], [120110, 1, "q"], [120111, 1, "r"], [120112, 1, "s"], [120113, 1, "t"], [120114, 1, "u"], [120115, 1, "v"], [120116, 1, "w"], [120117, 1, "x"], [120118, 1, "y"], [120119, 1, "z"], [120120, 1, "a"], [120121, 1, "b"], [120122, 3], [120123, 1, "d"], [120124, 1, "e"], [120125, 1, "f"], [120126, 1, "g"], [120127, 3], [120128, 1, "i"], [120129, 1, "j"], [120130, 1, "k"], [120131, 1, "l"], [120132, 1, "m"], [120133, 3], [120134, 1, "o"], [[120135, 120137], 3], [120138, 1, "s"], [120139, 1, "t"], [120140, 1, "u"], [120141, 1, "v"], [120142, 1, "w"], [120143, 1, "x"], [120144, 1, "y"], [120145, 3], [120146, 1, "a"], [120147, 1, "b"], [120148, 1, "c"], [120149, 1, "d"], [120150, 1, "e"], [120151, 1, "f"], [120152, 1, "g"], [120153, 1, "h"], [120154, 1, "i"], [120155, 1, "j"], [120156, 1, "k"], [120157, 1, "l"], [120158, 1, "m"], [120159, 1, "n"], [120160, 1, "o"], [120161, 1, "p"], [120162, 1, "q"], [120163, 1, "r"], [120164, 1, "s"], [120165, 1, "t"], [120166, 1, "u"], [120167, 1, "v"], [120168, 1, "w"], [120169, 1, "x"], [120170, 1, "y"], [120171, 1, "z"], [120172, 1, "a"], [120173, 1, "b"], [120174, 1, "c"], [120175, 1, "d"], [120176, 1, "e"], [120177, 1, "f"], [120178, 1, "g"], [120179, 1, "h"], [120180, 1, "i"], [120181, 1, "j"], [120182, 1, "k"], [120183, 1, "l"], [120184, 1, "m"], [120185, 1, "n"], [120186, 1, "o"], [120187, 1, "p"], [120188, 1, "q"], [120189, 1, "r"], [120190, 1, "s"], [120191, 1, "t"], [120192, 1, "u"], [120193, 1, "v"], [120194, 1, "w"], [120195, 1, "x"], [120196, 1, "y"], [120197, 1, "z"], [120198, 1, "a"], [120199, 1, "b"], [120200, 1, "c"], [120201, 1, "d"], [120202, 1, "e"], [120203, 1, "f"], [120204, 1, "g"], [120205, 1, "h"], [120206, 1, "i"], [120207, 1, "j"], [120208, 1, "k"], [120209, 1, "l"], [120210, 1, "m"], [120211, 1, "n"], [120212, 1, "o"], [120213, 1, "p"], [120214, 1, "q"], [120215, 1, "r"], [120216, 1, "s"], [120217, 1, "t"], [120218, 1, "u"], [120219, 1, "v"], [120220, 1, "w"], [120221, 1, "x"], [120222, 1, "y"], [120223, 1, "z"], [120224, 1, "a"], [120225, 1, "b"], [120226, 1, "c"], [120227, 1, "d"], [120228, 1, "e"], [120229, 1, "f"], [120230, 1, "g"], [120231, 1, "h"], [120232, 1, "i"], [120233, 1, "j"], [120234, 1, "k"], [120235, 1, "l"], [120236, 1, "m"], [120237, 1, "n"], [120238, 1, "o"], [120239, 1, "p"], [120240, 1, "q"], [120241, 1, "r"], [120242, 1, "s"], [120243, 1, "t"], [120244, 1, "u"], [120245, 1, "v"], [120246, 1, "w"], [120247, 1, "x"], [120248, 1, "y"], [120249, 1, "z"], [120250, 1, "a"], [120251, 1, "b"], [120252, 1, "c"], [120253, 1, "d"], [120254, 1, "e"], [120255, 1, "f"], [120256, 1, "g"], [120257, 1, "h"], [120258, 1, "i"], [120259, 1, "j"], [120260, 1, "k"], [120261, 1, "l"], [120262, 1, "m"], [120263, 1, "n"], [120264, 1, "o"], [120265, 1, "p"], [120266, 1, "q"], [120267, 1, "r"], [120268, 1, "s"], [120269, 1, "t"], [120270, 1, "u"], [120271, 1, "v"], [120272, 1, "w"], [120273, 1, "x"], [120274, 1, "y"], [120275, 1, "z"], [120276, 1, "a"], [120277, 1, "b"], [120278, 1, "c"], [120279, 1, "d"], [120280, 1, "e"], [120281, 1, "f"], [120282, 1, "g"], [120283, 1, "h"], [120284, 1, "i"], [120285, 1, "j"], [120286, 1, "k"], [120287, 1, "l"], [120288, 1, "m"], [120289, 1, "n"], [120290, 1, "o"], [120291, 1, "p"], [120292, 1, "q"], [120293, 1, "r"], [120294, 1, "s"], [120295, 1, "t"], [120296, 1, "u"], [120297, 1, "v"], [120298, 1, "w"], [120299, 1, "x"], [120300, 1, "y"], [120301, 1, "z"], [120302, 1, "a"], [120303, 1, "b"], [120304, 1, "c"], [120305, 1, "d"], [120306, 1, "e"], [120307, 1, "f"], [120308, 1, "g"], [120309, 1, "h"], [120310, 1, "i"], [120311, 1, "j"], [120312, 1, "k"], [120313, 1, "l"], [120314, 1, "m"], [120315, 1, "n"], [120316, 1, "o"], [120317, 1, "p"], [120318, 1, "q"], [120319, 1, "r"], [120320, 1, "s"], [120321, 1, "t"], [120322, 1, "u"], [120323, 1, "v"], [120324, 1, "w"], [120325, 1, "x"], [120326, 1, "y"], [120327, 1, "z"], [120328, 1, "a"], [120329, 1, "b"], [120330, 1, "c"], [120331, 1, "d"], [120332, 1, "e"], [120333, 1, "f"], [120334, 1, "g"], [120335, 1, "h"], [120336, 1, "i"], [120337, 1, "j"], [120338, 1, "k"], [120339, 1, "l"], [120340, 1, "m"], [120341, 1, "n"], [120342, 1, "o"], [120343, 1, "p"], [120344, 1, "q"], [120345, 1, "r"], [120346, 1, "s"], [120347, 1, "t"], [120348, 1, "u"], [120349, 1, "v"], [120350, 1, "w"], [120351, 1, "x"], [120352, 1, "y"], [120353, 1, "z"], [120354, 1, "a"], [120355, 1, "b"], [120356, 1, "c"], [120357, 1, "d"], [120358, 1, "e"], [120359, 1, "f"], [120360, 1, "g"], [120361, 1, "h"], [120362, 1, "i"], [120363, 1, "j"], [120364, 1, "k"], [120365, 1, "l"], [120366, 1, "m"], [120367, 1, "n"], [120368, 1, "o"], [120369, 1, "p"], [120370, 1, "q"], [120371, 1, "r"], [120372, 1, "s"], [120373, 1, "t"], [120374, 1, "u"], [120375, 1, "v"], [120376, 1, "w"], [120377, 1, "x"], [120378, 1, "y"], [120379, 1, "z"], [120380, 1, "a"], [120381, 1, "b"], [120382, 1, "c"], [120383, 1, "d"], [120384, 1, "e"], [120385, 1, "f"], [120386, 1, "g"], [120387, 1, "h"], [120388, 1, "i"], [120389, 1, "j"], [120390, 1, "k"], [120391, 1, "l"], [120392, 1, "m"], [120393, 1, "n"], [120394, 1, "o"], [120395, 1, "p"], [120396, 1, "q"], [120397, 1, "r"], [120398, 1, "s"], [120399, 1, "t"], [120400, 1, "u"], [120401, 1, "v"], [120402, 1, "w"], [120403, 1, "x"], [120404, 1, "y"], [120405, 1, "z"], [120406, 1, "a"], [120407, 1, "b"], [120408, 1, "c"], [120409, 1, "d"], [120410, 1, "e"], [120411, 1, "f"], [120412, 1, "g"], [120413, 1, "h"], [120414, 1, "i"], [120415, 1, "j"], [120416, 1, "k"], [120417, 1, "l"], [120418, 1, "m"], [120419, 1, "n"], [120420, 1, "o"], [120421, 1, "p"], [120422, 1, "q"], [120423, 1, "r"], [120424, 1, "s"], [120425, 1, "t"], [120426, 1, "u"], [120427, 1, "v"], [120428, 1, "w"], [120429, 1, "x"], [120430, 1, "y"], [120431, 1, "z"], [120432, 1, "a"], [120433, 1, "b"], [120434, 1, "c"], [120435, 1, "d"], [120436, 1, "e"], [120437, 1, "f"], [120438, 1, "g"], [120439, 1, "h"], [120440, 1, "i"], [120441, 1, "j"], [120442, 1, "k"], [120443, 1, "l"], [120444, 1, "m"], [120445, 1, "n"], [120446, 1, "o"], [120447, 1, "p"], [120448, 1, "q"], [120449, 1, "r"], [120450, 1, "s"], [120451, 1, "t"], [120452, 1, "u"], [120453, 1, "v"], [120454, 1, "w"], [120455, 1, "x"], [120456, 1, "y"], [120457, 1, "z"], [120458, 1, "a"], [120459, 1, "b"], [120460, 1, "c"], [120461, 1, "d"], [120462, 1, "e"], [120463, 1, "f"], [120464, 1, "g"], [120465, 1, "h"], [120466, 1, "i"], [120467, 1, "j"], [120468, 1, "k"], [120469, 1, "l"], [120470, 1, "m"], [120471, 1, "n"], [120472, 1, "o"], [120473, 1, "p"], [120474, 1, "q"], [120475, 1, "r"], [120476, 1, "s"], [120477, 1, "t"], [120478, 1, "u"], [120479, 1, "v"], [120480, 1, "w"], [120481, 1, "x"], [120482, 1, "y"], [120483, 1, "z"], [120484, 1, "\u0131"], [120485, 1, "\u0237"], [[120486, 120487], 3], [120488, 1, "\u03B1"], [120489, 1, "\u03B2"], [120490, 1, "\u03B3"], [120491, 1, "\u03B4"], [120492, 1, "\u03B5"], [120493, 1, "\u03B6"], [120494, 1, "\u03B7"], [120495, 1, "\u03B8"], [120496, 1, "\u03B9"], [120497, 1, "\u03BA"], [120498, 1, "\u03BB"], [120499, 1, "\u03BC"], [120500, 1, "\u03BD"], [120501, 1, "\u03BE"], [120502, 1, "\u03BF"], [120503, 1, "\u03C0"], [120504, 1, "\u03C1"], [120505, 1, "\u03B8"], [120506, 1, "\u03C3"], [120507, 1, "\u03C4"], [120508, 1, "\u03C5"], [120509, 1, "\u03C6"], [120510, 1, "\u03C7"], [120511, 1, "\u03C8"], [120512, 1, "\u03C9"], [120513, 1, "\u2207"], [120514, 1, "\u03B1"], [120515, 1, "\u03B2"], [120516, 1, "\u03B3"], [120517, 1, "\u03B4"], [120518, 1, "\u03B5"], [120519, 1, "\u03B6"], [120520, 1, "\u03B7"], [120521, 1, "\u03B8"], [120522, 1, "\u03B9"], [120523, 1, "\u03BA"], [120524, 1, "\u03BB"], [120525, 1, "\u03BC"], [120526, 1, "\u03BD"], [120527, 1, "\u03BE"], [120528, 1, "\u03BF"], [120529, 1, "\u03C0"], [120530, 1, "\u03C1"], [[120531, 120532], 1, "\u03C3"], [120533, 1, "\u03C4"], [120534, 1, "\u03C5"], [120535, 1, "\u03C6"], [120536, 1, "\u03C7"], [120537, 1, "\u03C8"], [120538, 1, "\u03C9"], [120539, 1, "\u2202"], [120540, 1, "\u03B5"], [120541, 1, "\u03B8"], [120542, 1, "\u03BA"], [120543, 1, "\u03C6"], [120544, 1, "\u03C1"], [120545, 1, "\u03C0"], [120546, 1, "\u03B1"], [120547, 1, "\u03B2"], [120548, 1, "\u03B3"], [120549, 1, "\u03B4"], [120550, 1, "\u03B5"], [120551, 1, "\u03B6"], [120552, 1, "\u03B7"], [120553, 1, "\u03B8"], [120554, 1, "\u03B9"], [120555, 1, "\u03BA"], [120556, 1, "\u03BB"], [120557, 1, "\u03BC"], [120558, 1, "\u03BD"], [120559, 1, "\u03BE"], [120560, 1, "\u03BF"], [120561, 1, "\u03C0"], [120562, 1, "\u03C1"], [120563, 1, "\u03B8"], [120564, 1, "\u03C3"], [120565, 1, "\u03C4"], [120566, 1, "\u03C5"], [120567, 1, "\u03C6"], [120568, 1, "\u03C7"], [120569, 1, "\u03C8"], [120570, 1, "\u03C9"], [120571, 1, "\u2207"], [120572, 1, "\u03B1"], [120573, 1, "\u03B2"], [120574, 1, "\u03B3"], [120575, 1, "\u03B4"], [120576, 1, "\u03B5"], [120577, 1, "\u03B6"], [120578, 1, "\u03B7"], [120579, 1, "\u03B8"], [120580, 1, "\u03B9"], [120581, 1, "\u03BA"], [120582, 1, "\u03BB"], [120583, 1, "\u03BC"], [120584, 1, "\u03BD"], [120585, 1, "\u03BE"], [120586, 1, "\u03BF"], [120587, 1, "\u03C0"], [120588, 1, "\u03C1"], [[120589, 120590], 1, "\u03C3"], [120591, 1, "\u03C4"], [120592, 1, "\u03C5"], [120593, 1, "\u03C6"], [120594, 1, "\u03C7"], [120595, 1, "\u03C8"], [120596, 1, "\u03C9"], [120597, 1, "\u2202"], [120598, 1, "\u03B5"], [120599, 1, "\u03B8"], [120600, 1, "\u03BA"], [120601, 1, "\u03C6"], [120602, 1, "\u03C1"], [120603, 1, "\u03C0"], [120604, 1, "\u03B1"], [120605, 1, "\u03B2"], [120606, 1, "\u03B3"], [120607, 1, "\u03B4"], [120608, 1, "\u03B5"], [120609, 1, "\u03B6"], [120610, 1, "\u03B7"], [120611, 1, "\u03B8"], [120612, 1, "\u03B9"], [120613, 1, "\u03BA"], [120614, 1, "\u03BB"], [120615, 1, "\u03BC"], [120616, 1, "\u03BD"], [120617, 1, "\u03BE"], [120618, 1, "\u03BF"], [120619, 1, "\u03C0"], [120620, 1, "\u03C1"], [120621, 1, "\u03B8"], [120622, 1, "\u03C3"], [120623, 1, "\u03C4"], [120624, 1, "\u03C5"], [120625, 1, "\u03C6"], [120626, 1, "\u03C7"], [120627, 1, "\u03C8"], [120628, 1, "\u03C9"], [120629, 1, "\u2207"], [120630, 1, "\u03B1"], [120631, 1, "\u03B2"], [120632, 1, "\u03B3"], [120633, 1, "\u03B4"], [120634, 1, "\u03B5"], [120635, 1, "\u03B6"], [120636, 1, "\u03B7"], [120637, 1, "\u03B8"], [120638, 1, "\u03B9"], [120639, 1, "\u03BA"], [120640, 1, "\u03BB"], [120641, 1, "\u03BC"], [120642, 1, "\u03BD"], [120643, 1, "\u03BE"], [120644, 1, "\u03BF"], [120645, 1, "\u03C0"], [120646, 1, "\u03C1"], [[120647, 120648], 1, "\u03C3"], [120649, 1, "\u03C4"], [120650, 1, "\u03C5"], [120651, 1, "\u03C6"], [120652, 1, "\u03C7"], [120653, 1, "\u03C8"], [120654, 1, "\u03C9"], [120655, 1, "\u2202"], [120656, 1, "\u03B5"], [120657, 1, "\u03B8"], [120658, 1, "\u03BA"], [120659, 1, "\u03C6"], [120660, 1, "\u03C1"], [120661, 1, "\u03C0"], [120662, 1, "\u03B1"], [120663, 1, "\u03B2"], [120664, 1, "\u03B3"], [120665, 1, "\u03B4"], [120666, 1, "\u03B5"], [120667, 1, "\u03B6"], [120668, 1, "\u03B7"], [120669, 1, "\u03B8"], [120670, 1, "\u03B9"], [120671, 1, "\u03BA"], [120672, 1, "\u03BB"], [120673, 1, "\u03BC"], [120674, 1, "\u03BD"], [120675, 1, "\u03BE"], [120676, 1, "\u03BF"], [120677, 1, "\u03C0"], [120678, 1, "\u03C1"], [120679, 1, "\u03B8"], [120680, 1, "\u03C3"], [120681, 1, "\u03C4"], [120682, 1, "\u03C5"], [120683, 1, "\u03C6"], [120684, 1, "\u03C7"], [120685, 1, "\u03C8"], [120686, 1, "\u03C9"], [120687, 1, "\u2207"], [120688, 1, "\u03B1"], [120689, 1, "\u03B2"], [120690, 1, "\u03B3"], [120691, 1, "\u03B4"], [120692, 1, "\u03B5"], [120693, 1, "\u03B6"], [120694, 1, "\u03B7"], [120695, 1, "\u03B8"], [120696, 1, "\u03B9"], [120697, 1, "\u03BA"], [120698, 1, "\u03BB"], [120699, 1, "\u03BC"], [120700, 1, "\u03BD"], [120701, 1, "\u03BE"], [120702, 1, "\u03BF"], [120703, 1, "\u03C0"], [120704, 1, "\u03C1"], [[120705, 120706], 1, "\u03C3"], [120707, 1, "\u03C4"], [120708, 1, "\u03C5"], [120709, 1, "\u03C6"], [120710, 1, "\u03C7"], [120711, 1, "\u03C8"], [120712, 1, "\u03C9"], [120713, 1, "\u2202"], [120714, 1, "\u03B5"], [120715, 1, "\u03B8"], [120716, 1, "\u03BA"], [120717, 1, "\u03C6"], [120718, 1, "\u03C1"], [120719, 1, "\u03C0"], [120720, 1, "\u03B1"], [120721, 1, "\u03B2"], [120722, 1, "\u03B3"], [120723, 1, "\u03B4"], [120724, 1, "\u03B5"], [120725, 1, "\u03B6"], [120726, 1, "\u03B7"], [120727, 1, "\u03B8"], [120728, 1, "\u03B9"], [120729, 1, "\u03BA"], [120730, 1, "\u03BB"], [120731, 1, "\u03BC"], [120732, 1, "\u03BD"], [120733, 1, "\u03BE"], [120734, 1, "\u03BF"], [120735, 1, "\u03C0"], [120736, 1, "\u03C1"], [120737, 1, "\u03B8"], [120738, 1, "\u03C3"], [120739, 1, "\u03C4"], [120740, 1, "\u03C5"], [120741, 1, "\u03C6"], [120742, 1, "\u03C7"], [120743, 1, "\u03C8"], [120744, 1, "\u03C9"], [120745, 1, "\u2207"], [120746, 1, "\u03B1"], [120747, 1, "\u03B2"], [120748, 1, "\u03B3"], [120749, 1, "\u03B4"], [120750, 1, "\u03B5"], [120751, 1, "\u03B6"], [120752, 1, "\u03B7"], [120753, 1, "\u03B8"], [120754, 1, "\u03B9"], [120755, 1, "\u03BA"], [120756, 1, "\u03BB"], [120757, 1, "\u03BC"], [120758, 1, "\u03BD"], [120759, 1, "\u03BE"], [120760, 1, "\u03BF"], [120761, 1, "\u03C0"], [120762, 1, "\u03C1"], [[120763, 120764], 1, "\u03C3"], [120765, 1, "\u03C4"], [120766, 1, "\u03C5"], [120767, 1, "\u03C6"], [120768, 1, "\u03C7"], [120769, 1, "\u03C8"], [120770, 1, "\u03C9"], [120771, 1, "\u2202"], [120772, 1, "\u03B5"], [120773, 1, "\u03B8"], [120774, 1, "\u03BA"], [120775, 1, "\u03C6"], [120776, 1, "\u03C1"], [120777, 1, "\u03C0"], [[120778, 120779], 1, "\u03DD"], [[120780, 120781], 3], [120782, 1, "0"], [120783, 1, "1"], [120784, 1, "2"], [120785, 1, "3"], [120786, 1, "4"], [120787, 1, "5"], [120788, 1, "6"], [120789, 1, "7"], [120790, 1, "8"], [120791, 1, "9"], [120792, 1, "0"], [120793, 1, "1"], [120794, 1, "2"], [120795, 1, "3"], [120796, 1, "4"], [120797, 1, "5"], [120798, 1, "6"], [120799, 1, "7"], [120800, 1, "8"], [120801, 1, "9"], [120802, 1, "0"], [120803, 1, "1"], [120804, 1, "2"], [120805, 1, "3"], [120806, 1, "4"], [120807, 1, "5"], [120808, 1, "6"], [120809, 1, "7"], [120810, 1, "8"], [120811, 1, "9"], [120812, 1, "0"], [120813, 1, "1"], [120814, 1, "2"], [120815, 1, "3"], [120816, 1, "4"], [120817, 1, "5"], [120818, 1, "6"], [120819, 1, "7"], [120820, 1, "8"], [120821, 1, "9"], [120822, 1, "0"], [120823, 1, "1"], [120824, 1, "2"], [120825, 1, "3"], [120826, 1, "4"], [120827, 1, "5"], [120828, 1, "6"], [120829, 1, "7"], [120830, 1, "8"], [120831, 1, "9"], [[120832, 121343], 2], [[121344, 121398], 2], [[121399, 121402], 2], [[121403, 121452], 2], [[121453, 121460], 2], [121461, 2], [[121462, 121475], 2], [121476, 2], [[121477, 121483], 2], [[121484, 121498], 3], [[121499, 121503], 2], [121504, 3], [[121505, 121519], 2], [[121520, 122623], 3], [[122624, 122654], 2], [[122655, 122660], 3], [[122661, 122666], 2], [[122667, 122879], 3], [[122880, 122886], 2], [122887, 3], [[122888, 122904], 2], [[122905, 122906], 3], [[122907, 122913], 2], [122914, 3], [[122915, 122916], 2], [122917, 3], [[122918, 122922], 2], [[122923, 122927], 3], [122928, 1, "\u0430"], [122929, 1, "\u0431"], [122930, 1, "\u0432"], [122931, 1, "\u0433"], [122932, 1, "\u0434"], [122933, 1, "\u0435"], [122934, 1, "\u0436"], [122935, 1, "\u0437"], [122936, 1, "\u0438"], [122937, 1, "\u043A"], [122938, 1, "\u043B"], [122939, 1, "\u043C"], [122940, 1, "\u043E"], [122941, 1, "\u043F"], [122942, 1, "\u0440"], [122943, 1, "\u0441"], [122944, 1, "\u0442"], [122945, 1, "\u0443"], [122946, 1, "\u0444"], [122947, 1, "\u0445"], [122948, 1, "\u0446"], [122949, 1, "\u0447"], [122950, 1, "\u0448"], [122951, 1, "\u044B"], [122952, 1, "\u044D"], [122953, 1, "\u044E"], [122954, 1, "\uA689"], [122955, 1, "\u04D9"], [122956, 1, "\u0456"], [122957, 1, "\u0458"], [122958, 1, "\u04E9"], [122959, 1, "\u04AF"], [122960, 1, "\u04CF"], [122961, 1, "\u0430"], [122962, 1, "\u0431"], [122963, 1, "\u0432"], [122964, 1, "\u0433"], [122965, 1, "\u0434"], [122966, 1, "\u0435"], [122967, 1, "\u0436"], [122968, 1, "\u0437"], [122969, 1, "\u0438"], [122970, 1, "\u043A"], [122971, 1, "\u043B"], [122972, 1, "\u043E"], [122973, 1, "\u043F"], [122974, 1, "\u0441"], [122975, 1, "\u0443"], [122976, 1, "\u0444"], [122977, 1, "\u0445"], [122978, 1, "\u0446"], [122979, 1, "\u0447"], [122980, 1, "\u0448"], [122981, 1, "\u044A"], [122982, 1, "\u044B"], [122983, 1, "\u0491"], [122984, 1, "\u0456"], [122985, 1, "\u0455"], [122986, 1, "\u045F"], [122987, 1, "\u04AB"], [122988, 1, "\uA651"], [122989, 1, "\u04B1"], [[122990, 123022], 3], [123023, 2], [[123024, 123135], 3], [[123136, 123180], 2], [[123181, 123183], 3], [[123184, 123197], 2], [[123198, 123199], 3], [[123200, 123209], 2], [[123210, 123213], 3], [123214, 2], [123215, 2], [[123216, 123535], 3], [[123536, 123566], 2], [[123567, 123583], 3], [[123584, 123641], 2], [[123642, 123646], 3], [123647, 2], [[123648, 124111], 3], [[124112, 124153], 2], [[124154, 124895], 3], [[124896, 124902], 2], [124903, 3], [[124904, 124907], 2], [124908, 3], [[124909, 124910], 2], [124911, 3], [[124912, 124926], 2], [124927, 3], [[124928, 125124], 2], [[125125, 125126], 3], [[125127, 125135], 2], [[125136, 125142], 2], [[125143, 125183], 3], [125184, 1, "\u{1E922}"], [125185, 1, "\u{1E923}"], [125186, 1, "\u{1E924}"], [125187, 1, "\u{1E925}"], [125188, 1, "\u{1E926}"], [125189, 1, "\u{1E927}"], [125190, 1, "\u{1E928}"], [125191, 1, "\u{1E929}"], [125192, 1, "\u{1E92A}"], [125193, 1, "\u{1E92B}"], [125194, 1, "\u{1E92C}"], [125195, 1, "\u{1E92D}"], [125196, 1, "\u{1E92E}"], [125197, 1, "\u{1E92F}"], [125198, 1, "\u{1E930}"], [125199, 1, "\u{1E931}"], [125200, 1, "\u{1E932}"], [125201, 1, "\u{1E933}"], [125202, 1, "\u{1E934}"], [125203, 1, "\u{1E935}"], [125204, 1, "\u{1E936}"], [125205, 1, "\u{1E937}"], [125206, 1, "\u{1E938}"], [125207, 1, "\u{1E939}"], [125208, 1, "\u{1E93A}"], [125209, 1, "\u{1E93B}"], [125210, 1, "\u{1E93C}"], [125211, 1, "\u{1E93D}"], [125212, 1, "\u{1E93E}"], [125213, 1, "\u{1E93F}"], [125214, 1, "\u{1E940}"], [125215, 1, "\u{1E941}"], [125216, 1, "\u{1E942}"], [125217, 1, "\u{1E943}"], [[125218, 125258], 2], [125259, 2], [[125260, 125263], 3], [[125264, 125273], 2], [[125274, 125277], 3], [[125278, 125279], 2], [[125280, 126064], 3], [[126065, 126132], 2], [[126133, 126208], 3], [[126209, 126269], 2], [[126270, 126463], 3], [126464, 1, "\u0627"], [126465, 1, "\u0628"], [126466, 1, "\u062C"], [126467, 1, "\u062F"], [126468, 3], [126469, 1, "\u0648"], [126470, 1, "\u0632"], [126471, 1, "\u062D"], [126472, 1, "\u0637"], [126473, 1, "\u064A"], [126474, 1, "\u0643"], [126475, 1, "\u0644"], [126476, 1, "\u0645"], [126477, 1, "\u0646"], [126478, 1, "\u0633"], [126479, 1, "\u0639"], [126480, 1, "\u0641"], [126481, 1, "\u0635"], [126482, 1, "\u0642"], [126483, 1, "\u0631"], [126484, 1, "\u0634"], [126485, 1, "\u062A"], [126486, 1, "\u062B"], [126487, 1, "\u062E"], [126488, 1, "\u0630"], [126489, 1, "\u0636"], [126490, 1, "\u0638"], [126491, 1, "\u063A"], [126492, 1, "\u066E"], [126493, 1, "\u06BA"], [126494, 1, "\u06A1"], [126495, 1, "\u066F"], [126496, 3], [126497, 1, "\u0628"], [126498, 1, "\u062C"], [126499, 3], [126500, 1, "\u0647"], [[126501, 126502], 3], [126503, 1, "\u062D"], [126504, 3], [126505, 1, "\u064A"], [126506, 1, "\u0643"], [126507, 1, "\u0644"], [126508, 1, "\u0645"], [126509, 1, "\u0646"], [126510, 1, "\u0633"], [126511, 1, "\u0639"], [126512, 1, "\u0641"], [126513, 1, "\u0635"], [126514, 1, "\u0642"], [126515, 3], [126516, 1, "\u0634"], [126517, 1, "\u062A"], [126518, 1, "\u062B"], [126519, 1, "\u062E"], [126520, 3], [126521, 1, "\u0636"], [126522, 3], [126523, 1, "\u063A"], [[126524, 126529], 3], [126530, 1, "\u062C"], [[126531, 126534], 3], [126535, 1, "\u062D"], [126536, 3], [126537, 1, "\u064A"], [126538, 3], [126539, 1, "\u0644"], [126540, 3], [126541, 1, "\u0646"], [126542, 1, "\u0633"], [126543, 1, "\u0639"], [126544, 3], [126545, 1, "\u0635"], [126546, 1, "\u0642"], [126547, 3], [126548, 1, "\u0634"], [[126549, 126550], 3], [126551, 1, "\u062E"], [126552, 3], [126553, 1, "\u0636"], [126554, 3], [126555, 1, "\u063A"], [126556, 3], [126557, 1, "\u06BA"], [126558, 3], [126559, 1, "\u066F"], [126560, 3], [126561, 1, "\u0628"], [126562, 1, "\u062C"], [126563, 3], [126564, 1, "\u0647"], [[126565, 126566], 3], [126567, 1, "\u062D"], [126568, 1, "\u0637"], [126569, 1, "\u064A"], [126570, 1, "\u0643"], [126571, 3], [126572, 1, "\u0645"], [126573, 1, "\u0646"], [126574, 1, "\u0633"], [126575, 1, "\u0639"], [126576, 1, "\u0641"], [126577, 1, "\u0635"], [126578, 1, "\u0642"], [126579, 3], [126580, 1, "\u0634"], [126581, 1, "\u062A"], [126582, 1, "\u062B"], [126583, 1, "\u062E"], [126584, 3], [126585, 1, "\u0636"], [126586, 1, "\u0638"], [126587, 1, "\u063A"], [126588, 1, "\u066E"], [126589, 3], [126590, 1, "\u06A1"], [126591, 3], [126592, 1, "\u0627"], [126593, 1, "\u0628"], [126594, 1, "\u062C"], [126595, 1, "\u062F"], [126596, 1, "\u0647"], [126597, 1, "\u0648"], [126598, 1, "\u0632"], [126599, 1, "\u062D"], [126600, 1, "\u0637"], [126601, 1, "\u064A"], [126602, 3], [126603, 1, "\u0644"], [126604, 1, "\u0645"], [126605, 1, "\u0646"], [126606, 1, "\u0633"], [126607, 1, "\u0639"], [126608, 1, "\u0641"], [126609, 1, "\u0635"], [126610, 1, "\u0642"], [126611, 1, "\u0631"], [126612, 1, "\u0634"], [126613, 1, "\u062A"], [126614, 1, "\u062B"], [126615, 1, "\u062E"], [126616, 1, "\u0630"], [126617, 1, "\u0636"], [126618, 1, "\u0638"], [126619, 1, "\u063A"], [[126620, 126624], 3], [126625, 1, "\u0628"], [126626, 1, "\u062C"], [126627, 1, "\u062F"], [126628, 3], [126629, 1, "\u0648"], [126630, 1, "\u0632"], [126631, 1, "\u062D"], [126632, 1, "\u0637"], [126633, 1, "\u064A"], [126634, 3], [126635, 1, "\u0644"], [126636, 1, "\u0645"], [126637, 1, "\u0646"], [126638, 1, "\u0633"], [126639, 1, "\u0639"], [126640, 1, "\u0641"], [126641, 1, "\u0635"], [126642, 1, "\u0642"], [126643, 1, "\u0631"], [126644, 1, "\u0634"], [126645, 1, "\u062A"], [126646, 1, "\u062B"], [126647, 1, "\u062E"], [126648, 1, "\u0630"], [126649, 1, "\u0636"], [126650, 1, "\u0638"], [126651, 1, "\u063A"], [[126652, 126703], 3], [[126704, 126705], 2], [[126706, 126975], 3], [[126976, 127019], 2], [[127020, 127023], 3], [[127024, 127123], 2], [[127124, 127135], 3], [[127136, 127150], 2], [[127151, 127152], 3], [[127153, 127166], 2], [127167, 2], [127168, 3], [[127169, 127183], 2], [127184, 3], [[127185, 127199], 2], [[127200, 127221], 2], [[127222, 127231], 3], [127232, 3], [127233, 5, "0,"], [127234, 5, "1,"], [127235, 5, "2,"], [127236, 5, "3,"], [127237, 5, "4,"], [127238, 5, "5,"], [127239, 5, "6,"], [127240, 5, "7,"], [127241, 5, "8,"], [127242, 5, "9,"], [[127243, 127244], 2], [[127245, 127247], 2], [127248, 5, "(a)"], [127249, 5, "(b)"], [127250, 5, "(c)"], [127251, 5, "(d)"], [127252, 5, "(e)"], [127253, 5, "(f)"], [127254, 5, "(g)"], [127255, 5, "(h)"], [127256, 5, "(i)"], [127257, 5, "(j)"], [127258, 5, "(k)"], [127259, 5, "(l)"], [127260, 5, "(m)"], [127261, 5, "(n)"], [127262, 5, "(o)"], [127263, 5, "(p)"], [127264, 5, "(q)"], [127265, 5, "(r)"], [127266, 5, "(s)"], [127267, 5, "(t)"], [127268, 5, "(u)"], [127269, 5, "(v)"], [127270, 5, "(w)"], [127271, 5, "(x)"], [127272, 5, "(y)"], [127273, 5, "(z)"], [127274, 1, "\u3014s\u3015"], [127275, 1, "c"], [127276, 1, "r"], [127277, 1, "cd"], [127278, 1, "wz"], [127279, 2], [127280, 1, "a"], [127281, 1, "b"], [127282, 1, "c"], [127283, 1, "d"], [127284, 1, "e"], [127285, 1, "f"], [127286, 1, "g"], [127287, 1, "h"], [127288, 1, "i"], [127289, 1, "j"], [127290, 1, "k"], [127291, 1, "l"], [127292, 1, "m"], [127293, 1, "n"], [127294, 1, "o"], [127295, 1, "p"], [127296, 1, "q"], [127297, 1, "r"], [127298, 1, "s"], [127299, 1, "t"], [127300, 1, "u"], [127301, 1, "v"], [127302, 1, "w"], [127303, 1, "x"], [127304, 1, "y"], [127305, 1, "z"], [127306, 1, "hv"], [127307, 1, "mv"], [127308, 1, "sd"], [127309, 1, "ss"], [127310, 1, "ppv"], [127311, 1, "wc"], [[127312, 127318], 2], [127319, 2], [[127320, 127326], 2], [127327, 2], [[127328, 127337], 2], [127338, 1, "mc"], [127339, 1, "md"], [127340, 1, "mr"], [[127341, 127343], 2], [[127344, 127352], 2], [127353, 2], [127354, 2], [[127355, 127356], 2], [[127357, 127358], 2], [127359, 2], [[127360, 127369], 2], [[127370, 127373], 2], [[127374, 127375], 2], [127376, 1, "dj"], [[127377, 127386], 2], [[127387, 127404], 2], [127405, 2], [[127406, 127461], 3], [[127462, 127487], 2], [127488, 1, "\u307B\u304B"], [127489, 1, "\u30B3\u30B3"], [127490, 1, "\u30B5"], [[127491, 127503], 3], [127504, 1, "\u624B"], [127505, 1, "\u5B57"], [127506, 1, "\u53CC"], [127507, 1, "\u30C7"], [127508, 1, "\u4E8C"], [127509, 1, "\u591A"], [127510, 1, "\u89E3"], [127511, 1, "\u5929"], [127512, 1, "\u4EA4"], [127513, 1, "\u6620"], [127514, 1, "\u7121"], [127515, 1, "\u6599"], [127516, 1, "\u524D"], [127517, 1, "\u5F8C"], [127518, 1, "\u518D"], [127519, 1, "\u65B0"], [127520, 1, "\u521D"], [127521, 1, "\u7D42"], [127522, 1, "\u751F"], [127523, 1, "\u8CA9"], [127524, 1, "\u58F0"], [127525, 1, "\u5439"], [127526, 1, "\u6F14"], [127527, 1, "\u6295"], [127528, 1, "\u6355"], [127529, 1, "\u4E00"], [127530, 1, "\u4E09"], [127531, 1, "\u904A"], [127532, 1, "\u5DE6"], [127533, 1, "\u4E2D"], [127534, 1, "\u53F3"], [127535, 1, "\u6307"], [127536, 1, "\u8D70"], [127537, 1, "\u6253"], [127538, 1, "\u7981"], [127539, 1, "\u7A7A"], [127540, 1, "\u5408"], [127541, 1, "\u6E80"], [127542, 1, "\u6709"], [127543, 1, "\u6708"], [127544, 1, "\u7533"], [127545, 1, "\u5272"], [127546, 1, "\u55B6"], [127547, 1, "\u914D"], [[127548, 127551], 3], [127552, 1, "\u3014\u672C\u3015"], [127553, 1, "\u3014\u4E09\u3015"], [127554, 1, "\u3014\u4E8C\u3015"], [127555, 1, "\u3014\u5B89\u3015"], [127556, 1, "\u3014\u70B9\u3015"], [127557, 1, "\u3014\u6253\u3015"], [127558, 1, "\u3014\u76D7\u3015"], [127559, 1, "\u3014\u52DD\u3015"], [127560, 1, "\u3014\u6557\u3015"], [[127561, 127567], 3], [127568, 1, "\u5F97"], [127569, 1, "\u53EF"], [[127570, 127583], 3], [[127584, 127589], 2], [[127590, 127743], 3], [[127744, 127776], 2], [[127777, 127788], 2], [[127789, 127791], 2], [[127792, 127797], 2], [127798, 2], [[127799, 127868], 2], [127869, 2], [[127870, 127871], 2], [[127872, 127891], 2], [[127892, 127903], 2], [[127904, 127940], 2], [127941, 2], [[127942, 127946], 2], [[127947, 127950], 2], [[127951, 127955], 2], [[127956, 127967], 2], [[127968, 127984], 2], [[127985, 127991], 2], [[127992, 127999], 2], [[128e3, 128062], 2], [128063, 2], [128064, 2], [128065, 2], [[128066, 128247], 2], [128248, 2], [[128249, 128252], 2], [[128253, 128254], 2], [128255, 2], [[128256, 128317], 2], [[128318, 128319], 2], [[128320, 128323], 2], [[128324, 128330], 2], [[128331, 128335], 2], [[128336, 128359], 2], [[128360, 128377], 2], [128378, 2], [[128379, 128419], 2], [128420, 2], [[128421, 128506], 2], [[128507, 128511], 2], [128512, 2], [[128513, 128528], 2], [128529, 2], [[128530, 128532], 2], [128533, 2], [128534, 2], [128535, 2], [128536, 2], [128537, 2], [128538, 2], [128539, 2], [[128540, 128542], 2], [128543, 2], [[128544, 128549], 2], [[128550, 128551], 2], [[128552, 128555], 2], [128556, 2], [128557, 2], [[128558, 128559], 2], [[128560, 128563], 2], [128564, 2], [[128565, 128576], 2], [[128577, 128578], 2], [[128579, 128580], 2], [[128581, 128591], 2], [[128592, 128639], 2], [[128640, 128709], 2], [[128710, 128719], 2], [128720, 2], [[128721, 128722], 2], [[128723, 128724], 2], [128725, 2], [[128726, 128727], 2], [[128728, 128731], 3], [128732, 2], [[128733, 128735], 2], [[128736, 128748], 2], [[128749, 128751], 3], [[128752, 128755], 2], [[128756, 128758], 2], [[128759, 128760], 2], [128761, 2], [128762, 2], [[128763, 128764], 2], [[128765, 128767], 3], [[128768, 128883], 2], [[128884, 128886], 2], [[128887, 128890], 3], [[128891, 128895], 2], [[128896, 128980], 2], [[128981, 128984], 2], [128985, 2], [[128986, 128991], 3], [[128992, 129003], 2], [[129004, 129007], 3], [129008, 2], [[129009, 129023], 3], [[129024, 129035], 2], [[129036, 129039], 3], [[129040, 129095], 2], [[129096, 129103], 3], [[129104, 129113], 2], [[129114, 129119], 3], [[129120, 129159], 2], [[129160, 129167], 3], [[129168, 129197], 2], [[129198, 129199], 3], [[129200, 129201], 2], [[129202, 129279], 3], [[129280, 129291], 2], [129292, 2], [[129293, 129295], 2], [[129296, 129304], 2], [[129305, 129310], 2], [129311, 2], [[129312, 129319], 2], [[129320, 129327], 2], [129328, 2], [[129329, 129330], 2], [[129331, 129342], 2], [129343, 2], [[129344, 129355], 2], [129356, 2], [[129357, 129359], 2], [[129360, 129374], 2], [[129375, 129387], 2], [[129388, 129392], 2], [129393, 2], [129394, 2], [[129395, 129398], 2], [[129399, 129400], 2], [129401, 2], [129402, 2], [129403, 2], [[129404, 129407], 2], [[129408, 129412], 2], [[129413, 129425], 2], [[129426, 129431], 2], [[129432, 129442], 2], [[129443, 129444], 2], [[129445, 129450], 2], [[129451, 129453], 2], [[129454, 129455], 2], [[129456, 129465], 2], [[129466, 129471], 2], [129472, 2], [[129473, 129474], 2], [[129475, 129482], 2], [129483, 2], [129484, 2], [[129485, 129487], 2], [[129488, 129510], 2], [[129511, 129535], 2], [[129536, 129619], 2], [[129620, 129631], 3], [[129632, 129645], 2], [[129646, 129647], 3], [[129648, 129651], 2], [129652, 2], [[129653, 129655], 2], [[129656, 129658], 2], [[129659, 129660], 2], [[129661, 129663], 3], [[129664, 129666], 2], [[129667, 129670], 2], [[129671, 129672], 2], [[129673, 129679], 3], [[129680, 129685], 2], [[129686, 129704], 2], [[129705, 129708], 2], [[129709, 129711], 2], [[129712, 129718], 2], [[129719, 129722], 2], [[129723, 129725], 2], [129726, 3], [129727, 2], [[129728, 129730], 2], [[129731, 129733], 2], [[129734, 129741], 3], [[129742, 129743], 2], [[129744, 129750], 2], [[129751, 129753], 2], [[129754, 129755], 2], [[129756, 129759], 3], [[129760, 129767], 2], [129768, 2], [[129769, 129775], 3], [[129776, 129782], 2], [[129783, 129784], 2], [[129785, 129791], 3], [[129792, 129938], 2], [129939, 3], [[129940, 129994], 2], [[129995, 130031], 3], [130032, 1, "0"], [130033, 1, "1"], [130034, 1, "2"], [130035, 1, "3"], [130036, 1, "4"], [130037, 1, "5"], [130038, 1, "6"], [130039, 1, "7"], [130040, 1, "8"], [130041, 1, "9"], [[130042, 131069], 3], [[131070, 131071], 3], [[131072, 173782], 2], [[173783, 173789], 2], [[173790, 173791], 2], [[173792, 173823], 3], [[173824, 177972], 2], [[177973, 177976], 2], [177977, 2], [[177978, 177983], 3], [[177984, 178205], 2], [[178206, 178207], 3], [[178208, 183969], 2], [[183970, 183983], 3], [[183984, 191456], 2], [[191457, 191471], 3], [[191472, 192093], 2], [[192094, 194559], 3], [194560, 1, "\u4E3D"], [194561, 1, "\u4E38"], [194562, 1, "\u4E41"], [194563, 1, "\u{20122}"], [194564, 1, "\u4F60"], [194565, 1, "\u4FAE"], [194566, 1, "\u4FBB"], [194567, 1, "\u5002"], [194568, 1, "\u507A"], [194569, 1, "\u5099"], [194570, 1, "\u50E7"], [194571, 1, "\u50CF"], [194572, 1, "\u349E"], [194573, 1, "\u{2063A}"], [194574, 1, "\u514D"], [194575, 1, "\u5154"], [194576, 1, "\u5164"], [194577, 1, "\u5177"], [194578, 1, "\u{2051C}"], [194579, 1, "\u34B9"], [194580, 1, "\u5167"], [194581, 1, "\u518D"], [194582, 1, "\u{2054B}"], [194583, 1, "\u5197"], [194584, 1, "\u51A4"], [194585, 1, "\u4ECC"], [194586, 1, "\u51AC"], [194587, 1, "\u51B5"], [194588, 1, "\u{291DF}"], [194589, 1, "\u51F5"], [194590, 1, "\u5203"], [194591, 1, "\u34DF"], [194592, 1, "\u523B"], [194593, 1, "\u5246"], [194594, 1, "\u5272"], [194595, 1, "\u5277"], [194596, 1, "\u3515"], [194597, 1, "\u52C7"], [194598, 1, "\u52C9"], [194599, 1, "\u52E4"], [194600, 1, "\u52FA"], [194601, 1, "\u5305"], [194602, 1, "\u5306"], [194603, 1, "\u5317"], [194604, 1, "\u5349"], [194605, 1, "\u5351"], [194606, 1, "\u535A"], [194607, 1, "\u5373"], [194608, 1, "\u537D"], [[194609, 194611], 1, "\u537F"], [194612, 1, "\u{20A2C}"], [194613, 1, "\u7070"], [194614, 1, "\u53CA"], [194615, 1, "\u53DF"], [194616, 1, "\u{20B63}"], [194617, 1, "\u53EB"], [194618, 1, "\u53F1"], [194619, 1, "\u5406"], [194620, 1, "\u549E"], [194621, 1, "\u5438"], [194622, 1, "\u5448"], [194623, 1, "\u5468"], [194624, 1, "\u54A2"], [194625, 1, "\u54F6"], [194626, 1, "\u5510"], [194627, 1, "\u5553"], [194628, 1, "\u5563"], [[194629, 194630], 1, "\u5584"], [194631, 1, "\u5599"], [194632, 1, "\u55AB"], [194633, 1, "\u55B3"], [194634, 1, "\u55C2"], [194635, 1, "\u5716"], [194636, 1, "\u5606"], [194637, 1, "\u5717"], [194638, 1, "\u5651"], [194639, 1, "\u5674"], [194640, 1, "\u5207"], [194641, 1, "\u58EE"], [194642, 1, "\u57CE"], [194643, 1, "\u57F4"], [194644, 1, "\u580D"], [194645, 1, "\u578B"], [194646, 1, "\u5832"], [194647, 1, "\u5831"], [194648, 1, "\u58AC"], [194649, 1, "\u{214E4}"], [194650, 1, "\u58F2"], [194651, 1, "\u58F7"], [194652, 1, "\u5906"], [194653, 1, "\u591A"], [194654, 1, "\u5922"], [194655, 1, "\u5962"], [194656, 1, "\u{216A8}"], [194657, 1, "\u{216EA}"], [194658, 1, "\u59EC"], [194659, 1, "\u5A1B"], [194660, 1, "\u5A27"], [194661, 1, "\u59D8"], [194662, 1, "\u5A66"], [194663, 1, "\u36EE"], [194664, 3], [194665, 1, "\u5B08"], [[194666, 194667], 1, "\u5B3E"], [194668, 1, "\u{219C8}"], [194669, 1, "\u5BC3"], [194670, 1, "\u5BD8"], [194671, 1, "\u5BE7"], [194672, 1, "\u5BF3"], [194673, 1, "\u{21B18}"], [194674, 1, "\u5BFF"], [194675, 1, "\u5C06"], [194676, 3], [194677, 1, "\u5C22"], [194678, 1, "\u3781"], [194679, 1, "\u5C60"], [194680, 1, "\u5C6E"], [194681, 1, "\u5CC0"], [194682, 1, "\u5C8D"], [194683, 1, "\u{21DE4}"], [194684, 1, "\u5D43"], [194685, 1, "\u{21DE6}"], [194686, 1, "\u5D6E"], [194687, 1, "\u5D6B"], [194688, 1, "\u5D7C"], [194689, 1, "\u5DE1"], [194690, 1, "\u5DE2"], [194691, 1, "\u382F"], [194692, 1, "\u5DFD"], [194693, 1, "\u5E28"], [194694, 1, "\u5E3D"], [194695, 1, "\u5E69"], [194696, 1, "\u3862"], [194697, 1, "\u{22183}"], [194698, 1, "\u387C"], [194699, 1, "\u5EB0"], [194700, 1, "\u5EB3"], [194701, 1, "\u5EB6"], [194702, 1, "\u5ECA"], [194703, 1, "\u{2A392}"], [194704, 1, "\u5EFE"], [[194705, 194706], 1, "\u{22331}"], [194707, 1, "\u8201"], [[194708, 194709], 1, "\u5F22"], [194710, 1, "\u38C7"], [194711, 1, "\u{232B8}"], [194712, 1, "\u{261DA}"], [194713, 1, "\u5F62"], [194714, 1, "\u5F6B"], [194715, 1, "\u38E3"], [194716, 1, "\u5F9A"], [194717, 1, "\u5FCD"], [194718, 1, "\u5FD7"], [194719, 1, "\u5FF9"], [194720, 1, "\u6081"], [194721, 1, "\u393A"], [194722, 1, "\u391C"], [194723, 1, "\u6094"], [194724, 1, "\u{226D4}"], [194725, 1, "\u60C7"], [194726, 1, "\u6148"], [194727, 1, "\u614C"], [194728, 1, "\u614E"], [194729, 1, "\u614C"], [194730, 1, "\u617A"], [194731, 1, "\u618E"], [194732, 1, "\u61B2"], [194733, 1, "\u61A4"], [194734, 1, "\u61AF"], [194735, 1, "\u61DE"], [194736, 1, "\u61F2"], [194737, 1, "\u61F6"], [194738, 1, "\u6210"], [194739, 1, "\u621B"], [194740, 1, "\u625D"], [194741, 1, "\u62B1"], [194742, 1, "\u62D4"], [194743, 1, "\u6350"], [194744, 1, "\u{22B0C}"], [194745, 1, "\u633D"], [194746, 1, "\u62FC"], [194747, 1, "\u6368"], [194748, 1, "\u6383"], [194749, 1, "\u63E4"], [194750, 1, "\u{22BF1}"], [194751, 1, "\u6422"], [194752, 1, "\u63C5"], [194753, 1, "\u63A9"], [194754, 1, "\u3A2E"], [194755, 1, "\u6469"], [194756, 1, "\u647E"], [194757, 1, "\u649D"], [194758, 1, "\u6477"], [194759, 1, "\u3A6C"], [194760, 1, "\u654F"], [194761, 1, "\u656C"], [194762, 1, "\u{2300A}"], [194763, 1, "\u65E3"], [194764, 1, "\u66F8"], [194765, 1, "\u6649"], [194766, 1, "\u3B19"], [194767, 1, "\u6691"], [194768, 1, "\u3B08"], [194769, 1, "\u3AE4"], [194770, 1, "\u5192"], [194771, 1, "\u5195"], [194772, 1, "\u6700"], [194773, 1, "\u669C"], [194774, 1, "\u80AD"], [194775, 1, "\u43D9"], [194776, 1, "\u6717"], [194777, 1, "\u671B"], [194778, 1, "\u6721"], [194779, 1, "\u675E"], [194780, 1, "\u6753"], [194781, 1, "\u{233C3}"], [194782, 1, "\u3B49"], [194783, 1, "\u67FA"], [194784, 1, "\u6785"], [194785, 1, "\u6852"], [194786, 1, "\u6885"], [194787, 1, "\u{2346D}"], [194788, 1, "\u688E"], [194789, 1, "\u681F"], [194790, 1, "\u6914"], [194791, 1, "\u3B9D"], [194792, 1, "\u6942"], [194793, 1, "\u69A3"], [194794, 1, "\u69EA"], [194795, 1, "\u6AA8"], [194796, 1, "\u{236A3}"], [194797, 1, "\u6ADB"], [194798, 1, "\u3C18"], [194799, 1, "\u6B21"], [194800, 1, "\u{238A7}"], [194801, 1, "\u6B54"], [194802, 1, "\u3C4E"], [194803, 1, "\u6B72"], [194804, 1, "\u6B9F"], [194805, 1, "\u6BBA"], [194806, 1, "\u6BBB"], [194807, 1, "\u{23A8D}"], [194808, 1, "\u{21D0B}"], [194809, 1, "\u{23AFA}"], [194810, 1, "\u6C4E"], [194811, 1, "\u{23CBC}"], [194812, 1, "\u6CBF"], [194813, 1, "\u6CCD"], [194814, 1, "\u6C67"], [194815, 1, "\u6D16"], [194816, 1, "\u6D3E"], [194817, 1, "\u6D77"], [194818, 1, "\u6D41"], [194819, 1, "\u6D69"], [194820, 1, "\u6D78"], [194821, 1, "\u6D85"], [194822, 1, "\u{23D1E}"], [194823, 1, "\u6D34"], [194824, 1, "\u6E2F"], [194825, 1, "\u6E6E"], [194826, 1, "\u3D33"], [194827, 1, "\u6ECB"], [194828, 1, "\u6EC7"], [194829, 1, "\u{23ED1}"], [194830, 1, "\u6DF9"], [194831, 1, "\u6F6E"], [194832, 1, "\u{23F5E}"], [194833, 1, "\u{23F8E}"], [194834, 1, "\u6FC6"], [194835, 1, "\u7039"], [194836, 1, "\u701E"], [194837, 1, "\u701B"], [194838, 1, "\u3D96"], [194839, 1, "\u704A"], [194840, 1, "\u707D"], [194841, 1, "\u7077"], [194842, 1, "\u70AD"], [194843, 1, "\u{20525}"], [194844, 1, "\u7145"], [194845, 1, "\u{24263}"], [194846, 1, "\u719C"], [194847, 3], [194848, 1, "\u7228"], [194849, 1, "\u7235"], [194850, 1, "\u7250"], [194851, 1, "\u{24608}"], [194852, 1, "\u7280"], [194853, 1, "\u7295"], [194854, 1, "\u{24735}"], [194855, 1, "\u{24814}"], [194856, 1, "\u737A"], [194857, 1, "\u738B"], [194858, 1, "\u3EAC"], [194859, 1, "\u73A5"], [[194860, 194861], 1, "\u3EB8"], [194862, 1, "\u7447"], [194863, 1, "\u745C"], [194864, 1, "\u7471"], [194865, 1, "\u7485"], [194866, 1, "\u74CA"], [194867, 1, "\u3F1B"], [194868, 1, "\u7524"], [194869, 1, "\u{24C36}"], [194870, 1, "\u753E"], [194871, 1, "\u{24C92}"], [194872, 1, "\u7570"], [194873, 1, "\u{2219F}"], [194874, 1, "\u7610"], [194875, 1, "\u{24FA1}"], [194876, 1, "\u{24FB8}"], [194877, 1, "\u{25044}"], [194878, 1, "\u3FFC"], [194879, 1, "\u4008"], [194880, 1, "\u76F4"], [194881, 1, "\u{250F3}"], [194882, 1, "\u{250F2}"], [194883, 1, "\u{25119}"], [194884, 1, "\u{25133}"], [194885, 1, "\u771E"], [[194886, 194887], 1, "\u771F"], [194888, 1, "\u774A"], [194889, 1, "\u4039"], [194890, 1, "\u778B"], [194891, 1, "\u4046"], [194892, 1, "\u4096"], [194893, 1, "\u{2541D}"], [194894, 1, "\u784E"], [194895, 1, "\u788C"], [194896, 1, "\u78CC"], [194897, 1, "\u40E3"], [194898, 1, "\u{25626}"], [194899, 1, "\u7956"], [194900, 1, "\u{2569A}"], [194901, 1, "\u{256C5}"], [194902, 1, "\u798F"], [194903, 1, "\u79EB"], [194904, 1, "\u412F"], [194905, 1, "\u7A40"], [194906, 1, "\u7A4A"], [194907, 1, "\u7A4F"], [194908, 1, "\u{2597C}"], [[194909, 194910], 1, "\u{25AA7}"], [194911, 3], [194912, 1, "\u4202"], [194913, 1, "\u{25BAB}"], [194914, 1, "\u7BC6"], [194915, 1, "\u7BC9"], [194916, 1, "\u4227"], [194917, 1, "\u{25C80}"], [194918, 1, "\u7CD2"], [194919, 1, "\u42A0"], [194920, 1, "\u7CE8"], [194921, 1, "\u7CE3"], [194922, 1, "\u7D00"], [194923, 1, "\u{25F86}"], [194924, 1, "\u7D63"], [194925, 1, "\u4301"], [194926, 1, "\u7DC7"], [194927, 1, "\u7E02"], [194928, 1, "\u7E45"], [194929, 1, "\u4334"], [194930, 1, "\u{26228}"], [194931, 1, "\u{26247}"], [194932, 1, "\u4359"], [194933, 1, "\u{262D9}"], [194934, 1, "\u7F7A"], [194935, 1, "\u{2633E}"], [194936, 1, "\u7F95"], [194937, 1, "\u7FFA"], [194938, 1, "\u8005"], [194939, 1, "\u{264DA}"], [194940, 1, "\u{26523}"], [194941, 1, "\u8060"], [194942, 1, "\u{265A8}"], [194943, 1, "\u8070"], [194944, 1, "\u{2335F}"], [194945, 1, "\u43D5"], [194946, 1, "\u80B2"], [194947, 1, "\u8103"], [194948, 1, "\u440B"], [194949, 1, "\u813E"], [194950, 1, "\u5AB5"], [194951, 1, "\u{267A7}"], [194952, 1, "\u{267B5}"], [194953, 1, "\u{23393}"], [194954, 1, "\u{2339C}"], [194955, 1, "\u8201"], [194956, 1, "\u8204"], [194957, 1, "\u8F9E"], [194958, 1, "\u446B"], [194959, 1, "\u8291"], [194960, 1, "\u828B"], [194961, 1, "\u829D"], [194962, 1, "\u52B3"], [194963, 1, "\u82B1"], [194964, 1, "\u82B3"], [194965, 1, "\u82BD"], [194966, 1, "\u82E6"], [194967, 1, "\u{26B3C}"], [194968, 1, "\u82E5"], [194969, 1, "\u831D"], [194970, 1, "\u8363"], [194971, 1, "\u83AD"], [194972, 1, "\u8323"], [194973, 1, "\u83BD"], [194974, 1, "\u83E7"], [194975, 1, "\u8457"], [194976, 1, "\u8353"], [194977, 1, "\u83CA"], [194978, 1, "\u83CC"], [194979, 1, "\u83DC"], [194980, 1, "\u{26C36}"], [194981, 1, "\u{26D6B}"], [194982, 1, "\u{26CD5}"], [194983, 1, "\u452B"], [194984, 1, "\u84F1"], [194985, 1, "\u84F3"], [194986, 1, "\u8516"], [194987, 1, "\u{273CA}"], [194988, 1, "\u8564"], [194989, 1, "\u{26F2C}"], [194990, 1, "\u455D"], [194991, 1, "\u4561"], [194992, 1, "\u{26FB1}"], [194993, 1, "\u{270D2}"], [194994, 1, "\u456B"], [194995, 1, "\u8650"], [194996, 1, "\u865C"], [194997, 1, "\u8667"], [194998, 1, "\u8669"], [194999, 1, "\u86A9"], [195e3, 1, "\u8688"], [195001, 1, "\u870E"], [195002, 1, "\u86E2"], [195003, 1, "\u8779"], [195004, 1, "\u8728"], [195005, 1, "\u876B"], [195006, 1, "\u8786"], [195007, 3], [195008, 1, "\u87E1"], [195009, 1, "\u8801"], [195010, 1, "\u45F9"], [195011, 1, "\u8860"], [195012, 1, "\u8863"], [195013, 1, "\u{27667}"], [195014, 1, "\u88D7"], [195015, 1, "\u88DE"], [195016, 1, "\u4635"], [195017, 1, "\u88FA"], [195018, 1, "\u34BB"], [195019, 1, "\u{278AE}"], [195020, 1, "\u{27966}"], [195021, 1, "\u46BE"], [195022, 1, "\u46C7"], [195023, 1, "\u8AA0"], [195024, 1, "\u8AED"], [195025, 1, "\u8B8A"], [195026, 1, "\u8C55"], [195027, 1, "\u{27CA8}"], [195028, 1, "\u8CAB"], [195029, 1, "\u8CC1"], [195030, 1, "\u8D1B"], [195031, 1, "\u8D77"], [195032, 1, "\u{27F2F}"], [195033, 1, "\u{20804}"], [195034, 1, "\u8DCB"], [195035, 1, "\u8DBC"], [195036, 1, "\u8DF0"], [195037, 1, "\u{208DE}"], [195038, 1, "\u8ED4"], [195039, 1, "\u8F38"], [195040, 1, "\u{285D2}"], [195041, 1, "\u{285ED}"], [195042, 1, "\u9094"], [195043, 1, "\u90F1"], [195044, 1, "\u9111"], [195045, 1, "\u{2872E}"], [195046, 1, "\u911B"], [195047, 1, "\u9238"], [195048, 1, "\u92D7"], [195049, 1, "\u92D8"], [195050, 1, "\u927C"], [195051, 1, "\u93F9"], [195052, 1, "\u9415"], [195053, 1, "\u{28BFA}"], [195054, 1, "\u958B"], [195055, 1, "\u4995"], [195056, 1, "\u95B7"], [195057, 1, "\u{28D77}"], [195058, 1, "\u49E6"], [195059, 1, "\u96C3"], [195060, 1, "\u5DB2"], [195061, 1, "\u9723"], [195062, 1, "\u{29145}"], [195063, 1, "\u{2921A}"], [195064, 1, "\u4A6E"], [195065, 1, "\u4A76"], [195066, 1, "\u97E0"], [195067, 1, "\u{2940A}"], [195068, 1, "\u4AB2"], [195069, 1, "\u{29496}"], [[195070, 195071], 1, "\u980B"], [195072, 1, "\u9829"], [195073, 1, "\u{295B6}"], [195074, 1, "\u98E2"], [195075, 1, "\u4B33"], [195076, 1, "\u9929"], [195077, 1, "\u99A7"], [195078, 1, "\u99C2"], [195079, 1, "\u99FE"], [195080, 1, "\u4BCE"], [195081, 1, "\u{29B30}"], [195082, 1, "\u9B12"], [195083, 1, "\u9C40"], [195084, 1, "\u9CFD"], [195085, 1, "\u4CCE"], [195086, 1, "\u4CED"], [195087, 1, "\u9D67"], [195088, 1, "\u{2A0CE}"], [195089, 1, "\u4CF8"], [195090, 1, "\u{2A105}"], [195091, 1, "\u{2A20E}"], [195092, 1, "\u{2A291}"], [195093, 1, "\u9EBB"], [195094, 1, "\u4D56"], [195095, 1, "\u9EF9"], [195096, 1, "\u9EFE"], [195097, 1, "\u9F05"], [195098, 1, "\u9F0F"], [195099, 1, "\u9F16"], [195100, 1, "\u9F3B"], [195101, 1, "\u{2A600}"], [[195102, 196605], 3], [[196606, 196607], 3], [[196608, 201546], 2], [[201547, 201551], 3], [[201552, 205743], 2], [[205744, 262141], 3], [[262142, 262143], 3], [[262144, 327677], 3], [[327678, 327679], 3], [[327680, 393213], 3], [[393214, 393215], 3], [[393216, 458749], 3], [[458750, 458751], 3], [[458752, 524285], 3], [[524286, 524287], 3], [[524288, 589821], 3], [[589822, 589823], 3], [[589824, 655357], 3], [[655358, 655359], 3], [[655360, 720893], 3], [[720894, 720895], 3], [[720896, 786429], 3], [[786430, 786431], 3], [[786432, 851965], 3], [[851966, 851967], 3], [[851968, 917501], 3], [[917502, 917503], 3], [917504, 3], [917505, 3], [[917506, 917535], 3], [[917536, 917631], 3], [[917632, 917759], 3], [[917760, 917999], 7], [[918e3, 983037], 3], [[983038, 983039], 3], [[983040, 1048573], 3], [[1048574, 1048575], 3], [[1048576, 1114109], 3], [[1114110, 1114111], 3]]; } }); @@ -845,23 +845,25 @@ var require_tr46 = __commonJS({ } return null; } - function mapChars(domainName, { useSTD3ASCIIRules, processingOption }) { - let hasError = false; + function mapChars(domainName, { useSTD3ASCIIRules, transitionalProcessing }) { let processed = ""; for (const ch of domainName) { const [status, mapping] = findStatus(ch.codePointAt(0), { useSTD3ASCIIRules }); switch (status) { case STATUS_MAPPING.disallowed: - hasError = true; processed += ch; break; case STATUS_MAPPING.ignored: break; case STATUS_MAPPING.mapped: - processed += mapping; + if (transitionalProcessing && ch === "\u1E9E") { + processed += "ss"; + } else { + processed += mapping; + } break; case STATUS_MAPPING.deviation: - if (processingOption === "transitional") { + if (transitionalProcessing) { processed += mapping; } else { processed += ch; @@ -872,12 +874,19 @@ var require_tr46 = __commonJS({ break; } } - return { - string: processed, - error: hasError - }; + return processed; } - function validateLabel(label, { checkHyphens, checkBidi, checkJoiners, processingOption, useSTD3ASCIIRules }) { + function validateLabel(label, { + checkHyphens, + checkBidi, + checkJoiners, + transitionalProcessing, + useSTD3ASCIIRules, + isBidi + }) { + if (label.length === 0) { + return true; + } if (label.normalize("NFC") !== label) { return false; } @@ -887,12 +896,19 @@ var require_tr46 = __commonJS({ return false; } } - if (label.includes(".") || codePoints.length > 0 && regexes.combiningMarks.test(codePoints[0])) { + if (label.includes(".")) { + return false; + } + if (regexes.combiningMarks.test(codePoints[0])) { return false; } for (const ch of codePoints) { const [status] = findStatus(ch.codePointAt(0), { useSTD3ASCIIRules }); - if (processingOption === "transitional" && status !== STATUS_MAPPING.valid || processingOption === "nontransitional" && status !== STATUS_MAPPING.valid && status !== STATUS_MAPPING.deviation) { + if (transitionalProcessing) { + if (status !== STATUS_MAPPING.valid) { + return false; + } + } else if (status !== STATUS_MAPPING.valid && status !== STATUS_MAPPING.deviation) { return false; } } @@ -917,7 +933,7 @@ var require_tr46 = __commonJS({ } } } - if (checkBidi && codePoints.length > 0) { + if (checkBidi && isBidi) { let rtl; if (regexes.bidiS1LTR.test(codePoints[0])) { rtl = false; @@ -950,31 +966,37 @@ var require_tr46 = __commonJS({ return regexes.bidiDomain.test(domain); } function processing(domainName, options) { - const { processingOption } = options; - let { string, error } = mapChars(domainName, options); + let string = mapChars(domainName, options); string = string.normalize("NFC"); const labels = string.split("."); const isBidi = isBidiDomain(labels); + let error = false; for (const [i, origLabel] of labels.entries()) { let label = origLabel; - let curProcessing = processingOption; + let transitionalProcessingForThisLabel = options.transitionalProcessing; if (label.startsWith("xn--")) { - try { - label = punycode.decode(label.substring(4)); - labels[i] = label; - } catch (err) { + if (containsNonASCII(label)) { error = true; continue; } - curProcessing = "nontransitional"; + try { + label = punycode.decode(label.substring(4)); + } catch { + if (!options.ignoreInvalidPunycode) { + error = true; + continue; + } + } + labels[i] = label; + transitionalProcessingForThisLabel = false; } if (error) { continue; } const validation = validateLabel(label, { ...options, - processingOption: curProcessing, - checkBidi: options.checkBidi && isBidi + transitionalProcessing: transitionalProcessingForThisLabel, + isBidi }); if (!validation) { error = true; @@ -990,18 +1012,17 @@ var require_tr46 = __commonJS({ checkBidi = false, checkJoiners = false, useSTD3ASCIIRules = false, - processingOption = "nontransitional", - verifyDNSLength = false + verifyDNSLength = false, + transitionalProcessing = false, + ignoreInvalidPunycode = false } = {}) { - if (processingOption !== "transitional" && processingOption !== "nontransitional") { - throw new RangeError("processingOption must be either transitional or nontransitional"); - } const result = processing(domainName, { - processingOption, checkHyphens, checkBidi, checkJoiners, - useSTD3ASCIIRules + useSTD3ASCIIRules, + transitionalProcessing, + ignoreInvalidPunycode }); let labels = result.string.split("."); labels = labels.map((l) => { @@ -1036,14 +1057,16 @@ var require_tr46 = __commonJS({ checkBidi = false, checkJoiners = false, useSTD3ASCIIRules = false, - processingOption = "nontransitional" + transitionalProcessing = false, + ignoreInvalidPunycode = false } = {}) { const result = processing(domainName, { - processingOption, checkHyphens, checkBidi, checkJoiners, - useSTD3ASCIIRules + useSTD3ASCIIRules, + transitionalProcessing, + ignoreInvalidPunycode }); return { domain: result.string, diff --git a/api/url/urlpattern/urlpattern.js b/api/url/urlpattern/urlpattern.js index a955f62a5a..f0ae245e9d 100644 --- a/api/url/urlpattern/urlpattern.js +++ b/api/url/urlpattern/urlpattern.js @@ -1 +1 @@ -var k=class{type=3;name="";prefix="";value="";suffix="";modifier=3;constructor(t,r,n,o,c,l){this.type=t,this.name=r,this.prefix=n,this.value=o,this.suffix=c,this.modifier=l}hasCustomName(){return this.name!==""&&typeof this.name!="number"}},Pe=/[$_\p{ID_Start}]/u,Se=/[$_\u200C\u200D\p{ID_Continue}]/u,M=".*";function ke(e,t){return(t?/^[\x00-\xFF]*$/:/^[\x00-\x7F]*$/).test(e)}function v(e,t=!1){let r=[],n=0;for(;n{if(la("OTHER_MODIFIER")??a("ASTERISK"),p=f=>{let u=a(f);if(u!==void 0)return u;let{type:d,index:T}=r[l];throw new TypeError(`Unexpected ${d} at ${T}, expected ${f}`)},O=()=>{let f="",u;for(;u=a("CHAR")??a("ESCAPED_CHAR");)f+=u;return f},xe=f=>f,L=t.encodePart||xe,I="",H=f=>{I+=f},$=()=>{I.length&&(o.push(new k(3,"","",L(I),"",3)),I="")},G=(f,u,d,T,Y)=>{let g=3;switch(Y){case"?":g=1;break;case"*":g=0;break;case"+":g=2;break}if(!u&&!d&&g===3){H(f);return}if($(),!u&&!d){if(!f)return;o.push(new k(3,"","",L(f),"",g));return}let m;d?d==="*"?m=M:m=d:m=n;let R=2;m===n?(R=1,m=""):m===M&&(R=0,m="");let S;if(u?S=u:d&&(S=c++),i.has(S))throw new TypeError(`Duplicate name '${S}'.`);i.add(S),o.push(new k(R,S,L(f),m,L(T),g))};for(;l-1)}return l||(n+=`(?=${c}|${o})`),new RegExp(n,X(r))}var b={delimiter:"",prefixes:"",sensitive:!0,strict:!0},B={delimiter:".",prefixes:"",sensitive:!0,strict:!0},q={delimiter:"/",prefixes:"/",sensitive:!0,strict:!0};function J(e,t){return e.length?e[0]==="/"?!0:!t||e.length<2?!1:(e[0]=="\\"||e[0]=="{")&&e[1]=="/":!1}function Q(e,t){return e.startsWith(t)?e.substring(t.length,e.length):e}function Ee(e,t){return e.endsWith(t)?e.substr(0,e.length-t.length):e}function W(e){return!e||e.length<2?!1:e[0]==="["||(e[0]==="\\"||e[0]==="{")&&e[1]==="["}var ee=["ftp","file","http","https","ws","wss"];function N(e){if(!e)return!0;for(let t of ee)if(e.test(t))return!0;return!1}function te(e,t){if(e=Q(e,"#"),t||e==="")return e;let r=new URL("https://example.com");return r.hash=e,r.hash?r.hash.substring(1,r.hash.length):""}function re(e,t){if(e=Q(e,"?"),t||e==="")return e;let r=new URL("https://example.com");return r.search=e,r.search?r.search.substring(1,r.search.length):""}function ne(e,t){return t||e===""?e:W(e)?j(e):z(e)}function se(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.password=e,r.password}function ie(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.username=e,r.username}function ae(e,t,r){if(r||e==="")return e;if(t&&!ee.includes(t))return new URL(`${t}:${e}`).pathname;let n=e[0]=="/";return e=new URL(n?e:"/-"+e,"https://example.com").pathname,n||(e=e.substring(2,e.length)),e}function oe(e,t,r){return _(t)===e&&(e=""),r||e===""?e:K(e)}function ce(e,t){return e=Ee(e,":"),t||e===""?e:A(e)}function _(e){switch(e){case"ws":case"http":return"80";case"wws":case"https":return"443";case"ftp":return"21";default:return""}}function A(e){if(e==="")return e;if(/^[-+.A-Za-z0-9]*$/.test(e))return e.toLowerCase();throw new TypeError(`Invalid protocol '${e}'.`)}function le(e){if(e==="")return e;let t=new URL("https://example.com");return t.username=e,t.username}function he(e){if(e==="")return e;let t=new URL("https://example.com");return t.password=e,t.password}function z(e){if(e==="")return e;if(/[\t\n\r #%/:<>?@[\]^\\|]/g.test(e))throw new TypeError(`Invalid hostname '${e}'`);let t=new URL("https://example.com");return t.hostname=e,t.hostname}function j(e){if(e==="")return e;if(/[^0-9a-fA-F[\]:]/g.test(e))throw new TypeError(`Invalid IPv6 hostname '${e}'`);return e.toLowerCase()}function K(e){if(e===""||/^[0-9]*$/.test(e)&&parseInt(e)<=65535)return e;throw new TypeError(`Invalid port '${e}'.`)}function fe(e){if(e==="")return e;let t=new URL("https://example.com");return t.pathname=e[0]!=="/"?"/-"+e:e,e[0]!=="/"?t.pathname.substring(2,t.pathname.length):t.pathname}function ue(e){return e===""?e:new URL(`data:${e}`).pathname}function pe(e){if(e==="")return e;let t=new URL("https://example.com");return t.search=e,t.search.substring(1,t.search.length)}function de(e){if(e==="")return e;let t=new URL("https://example.com");return t.hash=e,t.hash.substring(1,t.hash.length)}var U=class{#i;#n=[];#t={};#e=0;#s=1;#u=0;#c=0;#p=0;#d=0;#g=!1;constructor(t){this.#i=t}get result(){return this.#t}parse(){for(this.#n=v(this.#i,!0);this.#e0)if(this.#T())this.#p-=1;else continue;if(this.#O()){this.#p+=1;continue}switch(this.#c){case 0:this.#S()&&(this.#t.username="",this.#t.password="",this.#t.hostname="",this.#t.port="",this.#t.pathname="",this.#t.search="",this.#t.hash="",this.#f(1));break;case 1:if(this.#S()){this.#C();let t=7,r=1;this.#g&&(this.#t.pathname="/"),this.#E()?(t=2,r=3):this.#g&&(t=2),this.#r(t,r)}break;case 2:this.#x()?this.#f(3):(this.#b()||this.#h()||this.#l())&&this.#f(5);break;case 3:this.#R()?this.#r(4,1):this.#x()&&this.#r(5,1);break;case 4:this.#x()&&this.#r(5,1);break;case 5:this.#A()?this.#d+=1:this.#w()&&(this.#d-=1),this.#y()&&!this.#d?this.#r(6,1):this.#b()?this.#r(7,0):this.#h()?this.#r(8,1):this.#l()&&this.#r(9,1);break;case 6:this.#b()?this.#r(7,0):this.#h()?this.#r(8,1):this.#l()&&this.#r(9,1);break;case 7:this.#h()?this.#r(8,1):this.#l()&&this.#r(9,1);break;case 8:this.#l()&&this.#r(9,1);break;case 9:break;case 10:break}}}#r(t,r){switch(this.#c){case 0:break;case 1:this.#t.protocol=this.#o();break;case 2:break;case 3:this.#t.username=this.#o();break;case 4:this.#t.password=this.#o();break;case 5:this.#t.hostname=this.#o();break;case 6:this.#t.port=this.#o();break;case 7:this.#t.pathname=this.#o();break;case 8:this.#t.search=this.#o();break;case 9:this.#t.hash=this.#o();break;case 10:break}this.#k(t,r)}#k(t,r){this.#c=t,this.#u=this.#e+r,this.#e+=r,this.#s=0}#P(){this.#e=this.#u,this.#s=0}#f(t){this.#P(),this.#c=t}#m(t){return t<0&&(t=this.#n.length-t),t=0&&(e.pathname=P(n.pathname.substring(0,o+1),r)+e.pathname)}e.pathname=ae(e.pathname,e.protocol,r)}return typeof t.search=="string"&&(e.search=re(t.search,r)),typeof t.hash=="string"&&(e.hash=te(t.hash,r)),e}function C(e){return e.replace(/([+*?:{}()\\])/g,"\\$1")}function Re(e){return e.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}function ye(e,t){t.delimiter??="/#?",t.prefixes??="./",t.sensitive??=!1,t.strict??=!1,t.end??=!0,t.start??=!0,t.endsWith="";let r=".*",n=`[^${Re(t.delimiter)}]+?`,o=/[$_\u200C\u200D\p{ID_Continue}]/u,c="";for(let l=0;l0?e[l-1]:null,p=l0?p.value[0]:"";a=o.test(O)}else a=!p.hasCustomName();if(!a&&!s.prefix.length&&h&&h.type===3){let O=h.value[h.value.length-1];a=t.prefixes.includes(O)}a&&(c+="{"),c+=C(s.prefix),i&&(c+=`:${s.name}`),s.type===2?c+=`(${s.value})`:s.type===1?i||(c+=`(${n})`):s.type===0&&(!i&&(!h||h.type===3||h.modifier!==3||a||s.prefix!=="")?c+="*":c+=`(${r})`),s.type===1&&i&&s.suffix.length&&o.test(s.suffix[0])&&(c+="\\"),c+=C(s.suffix),a&&(c+="}"),s.modifier!==3&&(c+=y(s.modifier))}return c}var me=class{#i;#n={};#t={};#e={};#s={};constructor(t={},r,n){try{let o;if(typeof r=="string"?o=r:n=r,typeof t=="string"){let i=new U(t);if(i.parse(),t=i.result,o===void 0&&typeof t.protocol!="string")throw new TypeError("A base URL must be provided for a relative constructor string.");t.baseURL=o}else{if(!t||typeof t!="object")throw new TypeError("parameter 1 is not of type 'string' and cannot convert to dictionary.");if(o)throw new TypeError("parameter 1 is not of type 'string'.")}typeof n>"u"&&(n={ignoreCase:!1});let c={ignoreCase:n.ignoreCase===!0},l={pathname:E,protocol:E,username:E,password:E,hostname:E,port:E,search:E,hash:E};this.#i=w(l,t,!0),_(this.#i.protocol)===this.#i.port&&(this.#i.port="");let s;for(s of V){if(!(s in this.#i))continue;let i={},a=this.#i[s];switch(this.#t[s]=[],s){case"protocol":Object.assign(i,b),i.encodePart=A;break;case"username":Object.assign(i,b),i.encodePart=le;break;case"password":Object.assign(i,b),i.encodePart=he;break;case"hostname":Object.assign(i,B),W(a)?i.encodePart=j:i.encodePart=z;break;case"port":Object.assign(i,b),i.encodePart=K;break;case"pathname":N(this.#n.protocol)?(Object.assign(i,q,c),i.encodePart=fe):(Object.assign(i,b,c),i.encodePart=ue);break;case"search":Object.assign(i,b,c),i.encodePart=pe;break;case"hash":Object.assign(i,b,c),i.encodePart=de;break}try{this.#s[s]=D(a,i),this.#n[s]=F(this.#s[s],this.#t[s],i),this.#e[s]=ye(this.#s[s],i)}catch{throw new TypeError(`invalid ${s} pattern '${this.#i[s]}'.`)}}}catch(o){throw new TypeError(`Failed to construct 'URLPattern': ${o.message}`)}}test(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return!1;try{typeof t=="object"?n=w(n,t,!1):n=w(n,ge(t,r),!1)}catch{return!1}let o;for(o of V)if(!this.#n[o].exec(n[o]))return!1;return!0}exec(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return;try{typeof t=="object"?n=w(n,t,!1):n=w(n,ge(t,r),!1)}catch{return null}let o={};r?o.inputs=[t,r]:o.inputs=[t];let c;for(c of V){let l=this.#n[c].exec(n[c]);if(!l)return null;let s={};for(let[i,a]of this.#t[c].entries())if(typeof a=="string"||typeof a=="number"){let h=l[i+1];s[a]=h}o[c]={input:n[c]??"",groups:s}}return o}static compareComponent(t,r,n){let o=(i,a)=>{for(let h of["type","modifier","prefix","value","suffix"]){if(i[h]{let h=0;for(;h{if(la("OTHER_MODIFIER")??a("ASTERISK"),d=h=>{let u=a(h);if(u!==void 0)return u;let{type:p,index:A}=r[l];throw new TypeError(`Unexpected ${p} at ${A}, expected ${h}`)},T=()=>{let h="",u;for(;u=a("CHAR")??a("ESCAPED_CHAR");)h+=u;return h},Se=h=>h,L=t.encodePart||Se,I="",U=h=>{I+=h},$=()=>{I.length&&(o.push(new R(3,"","",L(I),"",3)),I="")},V=(h,u,p,A,Y)=>{let g=3;switch(Y){case"?":g=1;break;case"*":g=0;break;case"+":g=2;break}if(!u&&!p&&g===3){U(h);return}if($(),!u&&!p){if(!h)return;o.push(new R(3,"","",L(h),"",g));return}let m;p?p==="*"?m=M:m=p:m=n;let O=2;m===n?(O=1,m=""):m===M&&(O=0,m="");let P;if(u?P=u:p&&(P=c++),i.has(P))throw new TypeError(`Duplicate name '${P}'.`);i.add(P),o.push(new R(O,P,L(h),m,L(A),g))};for(;l-1)}return l||(n+=`(?=${c}|${o})`),new RegExp(n,X(r))}var x={delimiter:"",prefixes:"",sensitive:!0,strict:!0},B={delimiter:".",prefixes:"",sensitive:!0,strict:!0},q={delimiter:"/",prefixes:"/",sensitive:!0,strict:!0};function J(e,t){return e.length?e[0]==="/"?!0:!t||e.length<2?!1:(e[0]=="\\"||e[0]=="{")&&e[1]=="/":!1}function Q(e,t){return e.startsWith(t)?e.substring(t.length,e.length):e}function Ee(e,t){return e.endsWith(t)?e.substr(0,e.length-t.length):e}function W(e){return!e||e.length<2?!1:e[0]==="["||(e[0]==="\\"||e[0]==="{")&&e[1]==="["}var ee=["ftp","file","http","https","ws","wss"];function N(e){if(!e)return!0;for(let t of ee)if(e.test(t))return!0;return!1}function te(e,t){if(e=Q(e,"#"),t||e==="")return e;let r=new URL("https://example.com");return r.hash=e,r.hash?r.hash.substring(1,r.hash.length):""}function re(e,t){if(e=Q(e,"?"),t||e==="")return e;let r=new URL("https://example.com");return r.search=e,r.search?r.search.substring(1,r.search.length):""}function ne(e,t){return t||e===""?e:W(e)?j(e):z(e)}function se(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.password=e,r.password}function ie(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.username=e,r.username}function ae(e,t,r){if(r||e==="")return e;if(t&&!ee.includes(t))return new URL(`${t}:${e}`).pathname;let n=e[0]=="/";return e=new URL(n?e:"/-"+e,"https://example.com").pathname,n||(e=e.substring(2,e.length)),e}function oe(e,t,r){return _(t)===e&&(e=""),r||e===""?e:K(e)}function ce(e,t){return e=Ee(e,":"),t||e===""?e:y(e)}function _(e){switch(e){case"ws":case"http":return"80";case"wws":case"https":return"443";case"ftp":return"21";default:return""}}function y(e){if(e==="")return e;if(/^[-+.A-Za-z0-9]*$/.test(e))return e.toLowerCase();throw new TypeError(`Invalid protocol '${e}'.`)}function le(e){if(e==="")return e;let t=new URL("https://example.com");return t.username=e,t.username}function fe(e){if(e==="")return e;let t=new URL("https://example.com");return t.password=e,t.password}function z(e){if(e==="")return e;if(/[\t\n\r #%/:<>?@[\]^\\|]/g.test(e))throw new TypeError(`Invalid hostname '${e}'`);let t=new URL("https://example.com");return t.hostname=e,t.hostname}function j(e){if(e==="")return e;if(/[^0-9a-fA-F[\]:]/g.test(e))throw new TypeError(`Invalid IPv6 hostname '${e}'`);return e.toLowerCase()}function K(e){if(e===""||/^[0-9]*$/.test(e)&&parseInt(e)<=65535)return e;throw new TypeError(`Invalid port '${e}'.`)}function he(e){if(e==="")return e;let t=new URL("https://example.com");return t.pathname=e[0]!=="/"?"/-"+e:e,e[0]!=="/"?t.pathname.substring(2,t.pathname.length):t.pathname}function ue(e){return e===""?e:new URL(`data:${e}`).pathname}function de(e){if(e==="")return e;let t=new URL("https://example.com");return t.search=e,t.search.substring(1,t.search.length)}function pe(e){if(e==="")return e;let t=new URL("https://example.com");return t.hash=e,t.hash.substring(1,t.hash.length)}var H=class{#i;#n=[];#t={};#e=0;#s=1;#l=0;#o=0;#d=0;#p=0;#g=!1;constructor(t){this.#i=t}get result(){return this.#t}parse(){for(this.#n=v(this.#i,!0);this.#e0)if(this.#A())this.#d-=1;else continue;if(this.#T()){this.#d+=1;continue}switch(this.#o){case 0:this.#P()&&this.#u(1);break;case 1:if(this.#P()){this.#C();let t=7,r=1;this.#E()?(t=2,r=3):this.#g&&(t=2),this.#r(t,r)}break;case 2:this.#S()?this.#u(3):(this.#x()||this.#h()||this.#f())&&this.#u(5);break;case 3:this.#O()?this.#r(4,1):this.#S()&&this.#r(5,1);break;case 4:this.#S()&&this.#r(5,1);break;case 5:this.#y()?this.#p+=1:this.#w()&&(this.#p-=1),this.#k()&&!this.#p?this.#r(6,1):this.#x()?this.#r(7,0):this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 6:this.#x()?this.#r(7,0):this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 7:this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 8:this.#f()&&this.#r(9,1);break;case 9:break;case 10:break}}this.#t.hostname!==void 0&&this.#t.port===void 0&&(this.#t.port="")}#r(t,r){switch(this.#o){case 0:break;case 1:this.#t.protocol=this.#c();break;case 2:break;case 3:this.#t.username=this.#c();break;case 4:this.#t.password=this.#c();break;case 5:this.#t.hostname=this.#c();break;case 6:this.#t.port=this.#c();break;case 7:this.#t.pathname=this.#c();break;case 8:this.#t.search=this.#c();break;case 9:this.#t.hash=this.#c();break;case 10:break}this.#o!==0&&t!==10&&([1,2,3,4].includes(this.#o)&&[6,7,8,9].includes(t)&&(this.#t.hostname??=""),[1,2,3,4,5,6].includes(this.#o)&&[8,9].includes(t)&&(this.#t.pathname??=this.#g?"/":""),[1,2,3,4,5,6,7].includes(this.#o)&&t===9&&(this.#t.search??="")),this.#R(t,r)}#R(t,r){this.#o=t,this.#l=this.#e+r,this.#e+=r,this.#s=0}#b(){this.#e=this.#l,this.#s=0}#u(t){this.#b(),this.#o=t}#m(t){return t<0&&(t=this.#n.length-t),t=0&&(e.pathname=b(n.pathname.substring(0,o+1),r)+e.pathname)}e.pathname=ae(e.pathname,e.protocol,r)}return typeof t.search=="string"&&(e.search=re(t.search,r)),typeof t.hash=="string"&&(e.hash=te(t.hash,r)),e}function C(e){return e.replace(/([+*?:{}()\\])/g,"\\$1")}function Oe(e){return e.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}function ke(e,t){t.delimiter??="/#?",t.prefixes??="./",t.sensitive??=!1,t.strict??=!1,t.end??=!0,t.start??=!0,t.endsWith="";let r=".*",n=`[^${Oe(t.delimiter)}]+?`,o=/[$_\u200C\u200D\p{ID_Continue}]/u,c="";for(let l=0;l0?e[l-1]:null,d=l0?d.value[0]:"";a=o.test(T)}else a=!d.hasCustomName();if(!a&&!s.prefix.length&&f&&f.type===3){let T=f.value[f.value.length-1];a=t.prefixes.includes(T)}a&&(c+="{"),c+=C(s.prefix),i&&(c+=`:${s.name}`),s.type===2?c+=`(${s.value})`:s.type===1?i||(c+=`(${n})`):s.type===0&&(!i&&(!f||f.type===3||f.modifier!==3||a||s.prefix!=="")?c+="*":c+=`(${r})`),s.type===1&&i&&s.suffix.length&&o.test(s.suffix[0])&&(c+="\\"),c+=C(s.suffix),a&&(c+="}"),s.modifier!==3&&(c+=k(s.modifier))}return c}var me=class{#i;#n={};#t={};#e={};#s={};#l=!1;constructor(t={},r,n){try{let o;if(typeof r=="string"?o=r:n=r,typeof t=="string"){let i=new H(t);if(i.parse(),t=i.result,o===void 0&&typeof t.protocol!="string")throw new TypeError("A base URL must be provided for a relative constructor string.");t.baseURL=o}else{if(!t||typeof t!="object")throw new TypeError("parameter 1 is not of type 'string' and cannot convert to dictionary.");if(o)throw new TypeError("parameter 1 is not of type 'string'.")}typeof n>"u"&&(n={ignoreCase:!1});let c={ignoreCase:n.ignoreCase===!0},l={pathname:E,protocol:E,username:E,password:E,hostname:E,port:E,search:E,hash:E};this.#i=w(l,t,!0),_(this.#i.protocol)===this.#i.port&&(this.#i.port="");let s;for(s of G){if(!(s in this.#i))continue;let i={},a=this.#i[s];switch(this.#t[s]=[],s){case"protocol":Object.assign(i,x),i.encodePart=y;break;case"username":Object.assign(i,x),i.encodePart=le;break;case"password":Object.assign(i,x),i.encodePart=fe;break;case"hostname":Object.assign(i,B),W(a)?i.encodePart=j:i.encodePart=z;break;case"port":Object.assign(i,x),i.encodePart=K;break;case"pathname":N(this.#n.protocol)?(Object.assign(i,q,c),i.encodePart=he):(Object.assign(i,x,c),i.encodePart=ue);break;case"search":Object.assign(i,x,c),i.encodePart=de;break;case"hash":Object.assign(i,x,c),i.encodePart=pe;break}try{this.#s[s]=D(a,i),this.#n[s]=F(this.#s[s],this.#t[s],i),this.#e[s]=ke(this.#s[s],i),this.#l=this.#l||this.#s[s].some(f=>f.type===2)}catch{throw new TypeError(`invalid ${s} pattern '${this.#i[s]}'.`)}}}catch(o){throw new TypeError(`Failed to construct 'URLPattern': ${o.message}`)}}test(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return!1;try{typeof t=="object"?n=w(n,t,!1):n=w(n,ge(t,r),!1)}catch{return!1}let o;for(o of G)if(!this.#n[o].exec(n[o]))return!1;return!0}exec(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return;try{typeof t=="object"?n=w(n,t,!1):n=w(n,ge(t,r),!1)}catch{return null}let o={};r?o.inputs=[t,r]:o.inputs=[t];let c;for(c of G){let l=this.#n[c].exec(n[c]);if(!l)return null;let s={};for(let[i,a]of this.#t[c].entries())if(typeof a=="string"||typeof a=="number"){let f=l[i+1];s[a]=f}o[c]={input:n[c]??"",groups:s}}return o}static compareComponent(t,r,n){let o=(i,a)=>{for(let f of["type","modifier","prefix","value","suffix"]){if(i[f]{let f=0;for(;f Date: Tue, 9 Apr 2024 18:36:19 -0400 Subject: [PATCH 0538/1178] refactor(api/events.js): handle async context in emit --- api/events.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/api/events.js b/api/events.js index 711b7c9fcb..b107cfa077 100644 --- a/api/events.js +++ b/api/events.js @@ -1,3 +1,5 @@ +import { AsyncContext } from './async/context.js' + // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -54,6 +56,7 @@ function EventEmitter () { EventEmitter.EventEmitter = EventEmitter EventEmitter.prototype._events = undefined +EventEmitter.prototype._contexts = undefined EventEmitter.prototype._eventsCount = 0 EventEmitter.prototype._maxListeners = undefined @@ -87,6 +90,13 @@ EventEmitter.init = function () { this._eventsCount = 0 } + if ( + this._contexts === undefined || + this._contexts === Object.getPrototypeOf(this)._contexts + ) { + this._contexts = new Map() + } + this._maxListeners = this._maxListeners || undefined } @@ -137,11 +147,27 @@ EventEmitter.prototype.emit = function emit (type) { if (handler === undefined) { return false } if (typeof handler === 'function') { - ReflectApply(handler, this, args) + const context = this._contexts.get(handler) + if (context) { + context.run(() => { + ReflectApply(handler, this, args) + }) + } else { + ReflectApply(handler, this, args) + } } else { const len = handler.length const listeners = arrayClone(handler, len) - for (let i = 0; i < len; ++i) { ReflectApply(listeners[i], this, args) } + for (let i = 0; i < len; ++i) { + const context = this._contexts.get(listeners[i]) + if (context) { + context.run(() => { + ReflectApply(listeners[i], this, args) + }) + } else { + ReflectApply(listeners[i], this, args) + } + } } return true @@ -206,6 +232,8 @@ function _addListener (target, type, listener, prepend) { } } + target._contexts.set(listener, new AsyncContext.Snapshot()) + return target } @@ -298,6 +326,8 @@ EventEmitter.prototype.removeListener = if (events.removeListener !== undefined) { this.emit('removeListener', type, originalListener || listener) } } + this._contexts.delete(listener) + return this } @@ -334,6 +364,7 @@ EventEmitter.prototype.removeAllListeners = } this.removeAllListeners('removeListener') this._events = Object.create(null) + this._contexts.clear() this._eventsCount = 0 return this } From f2f95ad9e443b5934e817cba4fdd3d74d9f4040c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:36:34 -0400 Subject: [PATCH 0539/1178] refactor(api/module.js): improve exports --- api/module.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/module.js b/api/module.js index b50e76314b..9173c310bb 100644 --- a/api/module.js +++ b/api/module.js @@ -4,8 +4,15 @@ import { createRequire, Module } from './commonjs/module.js' import builtins, { isBuiltin } from './commonjs/builtins.js' -export { createRequire, Module, builtins, isBuiltin } +/** + * @typedef {import('./commonjs/module.js').ModuleOptions} ModuleOptions + * @typedef {import('./commonjs/module.js').ModuleResolver} ModuleResolver + * @typedef {import('./commonjs/module.js').ModuleLoadOptions} ModuleLoadOptions + * @typedef {import('./commonjs/module.js').RequireFunction} RequireFunction + * @typedef {import('./commonjs/module.js').CreateRequireOptions} CreateRequireOptions + */ +export { createRequire, Module, builtins, isBuiltin } export const builtinModules = builtins export default Module From 6f4f09124af71457c24490fce96f475884bfa4de Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:37:19 -0400 Subject: [PATCH 0540/1178] feat(api/service-worker): introduce 'localStorage', 'sessionStorage' and 'memoryStorage' --- api/{service-worker => internal}/database.js | 0 api/service-worker.js | 3 - api/service-worker/container.js | 4 +- api/service-worker/env.js | 54 +- api/service-worker/storage.js | 935 +++++++++++++++++++ api/service-worker/worker.js | 43 +- 6 files changed, 1021 insertions(+), 18 deletions(-) rename api/{service-worker => internal}/database.js (100%) create mode 100644 api/service-worker/storage.js diff --git a/api/service-worker/database.js b/api/internal/database.js similarity index 100% rename from api/service-worker/database.js rename to api/internal/database.js diff --git a/api/service-worker.js b/api/service-worker.js index 0a93681efb..22e47b93c4 100644 --- a/api/service-worker.js +++ b/api/service-worker.js @@ -1,13 +1,11 @@ import { ExtendableEvent, FetchEvent } from './service-worker/events.js' import { Environment } from './service-worker/env.js' -import { Database } from './service-worker/database.js' import { Context } from './service-worker/context.js' export { ExtendableEvent, FetchEvent, Environment, - Database, Context } @@ -15,6 +13,5 @@ export default { ExtendableEvent, FetchEvent, Environment, - Database, Context } diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 87845e53d0..39430acc96 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -195,7 +195,6 @@ export class ServiceWorkerContainer extends EventTarget { * contains private implementation properties relevant to the runtime * `ServiceWorkerContainer` internal state implementations. * @ignore - * @private */ async init () { if (internal.get(this)) { @@ -332,6 +331,7 @@ export class ServiceWorkerContainer extends EventTarget { let scope = clientURL let currentScope = null + // @ts-ignore if (scope && URL.canParse(scope, globalThis.location.href)) { scope = new URL(scope, globalThis.location.href).pathname } @@ -367,7 +367,7 @@ export class ServiceWorkerContainer extends EventTarget { } const serviceWorker = createServiceWorker(state.serviceWorker.state, { - subscribe: scope === currentScope, + subscribe: clientURL || scope === currentScope, scriptURL: info.registration.scriptURL }) diff --git a/api/service-worker/env.js b/api/service-worker/env.js index eaad01f3ea..2bfce19c6b 100644 --- a/api/service-worker/env.js +++ b/api/service-worker/env.js @@ -1,5 +1,5 @@ /* global ErrorEvent, EventTarget */ -import database from './database.js' +import database from '../internal/database.js' /** * @typedef {{ @@ -7,6 +7,21 @@ import database from './database.js' * }} EnvironmentOptions */ +/** + * An event dispatched when a environment value is updated (set, delete) + */ +export class EnvironmentEvent extends Event { + /** + * `EnvironmentEvent` class constructor. + * @param {'set'|'delete'} type + * @param {object=} [entry] + */ + constructor (type, entry = null) { + super(type) + this.entry = entry + } +} + /** * Awaits a promise forwarding errors to the `Environment` instance. * @ignore @@ -26,6 +41,13 @@ async function forward (env, promise) { * for service worker environments. */ export class Environment extends EventTarget { + /** + * Maximum entries that will be restored from storage into the environment + * context object. + * @type {number} + */ + static MAX_CONTEXT_ENTRIES = 16 * 1024 + /** * Opens an environment for a particular scope. * @param {EnvironmentOptions} options @@ -67,15 +89,25 @@ export class Environment extends EventTarget { set: (target, property, value) => { target[property] = value if (this.database && this.database.opened) { - forward(this.database.put(property, value)) + forward(this, this.database.put(property, value)) } + + this.dispatchEvent(new EnvironmentEvent('set', { + key: property, + value + })) return true }, deleteProperty: (target, property) => { if (this.database && this.database.opened) { - forward(this.database.delete(property)) + forward(this, this.database.delete(property)) } + + this.dispatchEvent(new EnvironmentEvent('delete', { + key: property + })) + return Reflect.deleteProperty(target, property) }, @@ -87,7 +119,7 @@ export class Environment extends EventTarget { /** * A reference to the currently opened environment database. - * @type {import('./database.js').Database} + * @type {import('../internal/database.js').Database} */ get database () { return this.#database @@ -127,11 +159,15 @@ export class Environment extends EventTarget { * @ignore */ async open () { - this.#database = await database.open(this.name) - const entries = await this.#database.entries() - - for (const [key, value] of entries) { - this.#context[key] = value + if (!this.#database) { + this.#database = await database.open(this.name) + const entries = await this.#database.get(undefined, { + count: Environment.MAX_CONTEXT_ENTRIES + }) + + for (const [key, value] of entries) { + this.#context[key] = value + } } } diff --git a/api/service-worker/storage.js b/api/service-worker/storage.js new file mode 100644 index 0000000000..1c1dcc374f --- /dev/null +++ b/api/service-worker/storage.js @@ -0,0 +1,935 @@ +/* global DOMException */ +import { IllegalConstructorError } from '../errors.js' +import { Environment } from './env.js' +import { Deferred } from '../async/deferred.js' +import state from './state.js' +import ipc from '../ipc.js' + +// used to ensure `Storage` is not illegally constructed +const createStorageSymbol = Symbol('Storage.create') + +/** + * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult + */ + +/** + * An iterator interface for an `Index` instance. + */ +export class IndexIterator { + #index = null + #current = 0 + + /** + * `IndexIterator` class constructor. + * @ignore + * @param {Index} index + */ + constructor (index) { + this.#index = index + } + + /** + * `true` if the iterator is "done", otherwise `false`. + * @type {boolean} + */ + get done () { + return this.#current === -1 || this.#current >= this.#index.length + } + + /** + * Returns the next `IndexIteratorResult`. + * @return {IndexIteratorResult} + */ + next () { + if (this.done) { + return { done: true, value: undefined } + } + + const value = this.#index.entry(this.#current++) + return { done: false, value } + } + + /** + * Mark `IndexIterator` as "done" + * @return {IndexIteratorResult} + */ + return () { + this.#current = -1 + return { done: true, value: undefined } + } +} + +/** + * A container used by the `Provider` to index keys and values + */ +export class Index { + #keys = [] + #values = [] + + /** + * A reference to the keys in this index. + * @type {string[]} + */ + get keys () { + return this.#keys + } + + /** + * A reference to the values in this index. + * @type {string[]} + */ + get values () { + return this.#values + } + + /** + * The number of entries in this index. + * @type {number} + */ + get length () { + return this.#keys.length + } + + /** + * Returns the key at a given `index`, if it exists otherwise `null`. + * @param {number} index} + * @return {string?} + */ + key (index) { + return this.#keys[index] ?? null + } + + /** + * Returns the value at a given `index`, if it exists otherwise `null`. + * @param {number} index} + * @return {string?} + */ + value (index) { + return this.#values[index] ?? null + } + + /** + * Inserts a value in the index. + * @param {string} key + * @param {string} value + */ + insert (key, value) { + const index = this.#keys.indexOf(key) + if (index >= 0) { + this.#values[index] = value + } else { + this.#keys.push(key) + this.#values.push(value) + } + } + + /** + * Computes the index of a key in this index. + * @param {string} key + * @return {number} + */ + indexOf (key) { + return this.#keys.indexOf(key) + } + + /** + * Clears all keys and values in the index. + */ + clear () { + this.#keys.splice(0, this.#keys.length) + this.#values.splice(0, this.#values.length) + } + + /** + * Returns an entry at `index` if it exists, otherwise `null`. + * @param {number} index + * @return {string[]|null} + */ + entry (index) { + const key = this.key(index) + const value = this.value(index) + + if (key) { + return [key, value] + } + + return null + } + + /** + * Removes entries at a given `index`. + * @param {number} index + * @return {boolean} + */ + remove (index) { + if (index >= 0 && index < this.#keys.length) { + this.#keys.splice(index, 1) + this.#values.splice(index, 1) + return true + } + + return false + } + + /** + * Returns an array of computed entries in this index. + * @return {IndexIterator} + */ + entries () { + return this[Symbol.iterator]() + } + + /** + * @ignore + * @return {IndexIterator} + */ + [Symbol.iterator] () { + return new IndexIterator(this) + } +} + +/** + * A base class for a storage provider. + */ +export class Provider { + #id = state.id + #index = new Index() + #ready = new Deferred() + + /** + * @type {{ error: Error | null }} + */ + #state = { error: null } + + /** + * `Provider` class constructor. + */ + constructor () { + try { + const request = this.load() + + if (typeof request.then === 'function') { + request.then(() => this.#ready.resolve()) + } else { + queueMicrotask(() => this.#ready.resolve()) + } + + if (typeof request?.catch === 'function') { + request.catch((err) => { + this.#state.error = err + state.reportError(err) + }) + } + } catch (err) { + this.#state.error = err + state.reportError(err) + } + } + + /** + * An error currently associated with the provider, likely from an + * async operation. + * @type {Error?} + */ + get error () { + return this.#state.error + } + + /** + * A promise that resolves when the provider is ready. + * @type {Promise} + */ + get ready () { + return this.#ready.promise + } + + /** + * A reference the service worker storage ID, which is the service worker + * registration ID. + * @type {string} + * @throws DOMException + */ + get id () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#id + } + + /** + * A reference to the provider `Index` + * @type {Index} + * @throws DOMException + */ + get index () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#index + } + + /** + * The number of entries in the provider. + * @type {number} + * @throws DOMException + */ + get length () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#index.length + } + + /** + * Returns `true` if the provider has a value for a given `key`. + * @param {string} key} + * @return {boolean} + * @throws DOMException + */ + has (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#index.indexOf(key) >= 0 + } + + /** + * Get a value by `key`. + * @param {string} key + * @return {string?} + * @throws DOMException + */ + get (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + const index = this.#index.indexOf(key) + return this.#index.value(index) + } + + /** + * Sets a `value` by `key` + * @param {string} key + * @param {string} value + * @throws DOMException + */ + set (key, value) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + this.#index.insert(key, value) + } + + /** + * Removes a value by `key`. + * @param {string} key + * @return {boolean} + * @throws DOMException + */ + remove (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + const index = this.#index.indexOf(key) + return this.#index.remove(index) + } + + /** + * Clear all keys and values. + * @throws DOMException + */ + clear () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + this.#index.clear() + } + + /** + * The keys in the provider index. + * @return {string[]} + * @throws DOMException + */ + keys () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#index.keys + } + + /** + * The values in the provider index. + * @return {string[]} + * @throws DOMException + */ + values () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#index.values + } + + /** + * Returns the key at a given `index` + * @param {number} index + * @return {string|null} + * @throws DOMException + */ + key (index) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + return this.#index.keys[index] ?? null + } + + /** + * Loads the internal index with keys and values. + * @return {Promise} + */ + async load () { + // no-op + } +} + +/** + * An in-memory storage provider. It just used the built-in provider `Index` + * for storing key-value entries. + */ +export class MemoryStorageProvider extends Provider {} + +/** + * A session storage provider that persists for the runtime of the + * application and through service worker restarts. + */ +export class SessionStorageProvider extends Provider { + /** + * Get a value by `key`. + * @param {string} key + * @return {string?} + * @throws DOMException + * @throws NotFoundError + */ + get (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + if (super.has(key)) { + return super.get(key) + } + + const { id } = this + const result = ipc.sendSync('serviceWorker.storage.get', { id, key }) + + // @ts-ignore + if (result.err && result.err.code !== 'NOT_FOUND_ERR') { + throw result.err + } + + const value = result.data?.value ?? null + + if (value !== null) { + super.set(key, value) + } + + return value + } + + /** + * Sets a `value` by `key` + * @param {string} key + * @param {string} value + * @throws DOMException + * @throws NotFoundError + */ + set (key, value) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + value = String(value) + + const { id } = this + // send async + ipc.send('serviceWorker.storage.set', { id, key, value }) + return super.set(key, value) + } + + /** + * Remove a value by `key`. + * @param {string} key + * @return {string?} + * @throws DOMException + * @throws NotFoundError + */ + remove (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + const { id } = this + const result = ipc.sendSync('serviceWorker.storage.remove', { id, key }) + + if (result.err) { + throw result.err + } + + return super.remove(key) + } + + /** + * Clear all keys and values. + * @throws DOMException + * @throws NotFoundError + */ + clear () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + const { id } = this + const result = ipc.sendSync('serviceWorker.storage.clear', { id }) + + if (result.err) { + throw result.err + } + + return super.clear() + } + + /** + * Loads the internal index with keys and values. + * @return {Promise} + * @throws NotFoundError + */ + async load () { + const { id } = this + const result = await ipc.request('serviceWorker.storage', { id }) + + if (result.err) { + throw result.err + } + + if (result.data && typeof result.data === 'object') { + for (const key in result.data) { + const value = result.data[key] + this.index.insert(key, value) + } + } + + return await super.load() + } +} + +/** + * A local storage provider that persists until the data is cleared. + */ +export class LocalStorageProvider extends Provider { + /** + * Get a value by `key`. + * @param {string} key + * @return {string?} + * @throws DOMException + */ + get (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + // @ts-ignore + const { localStorage } = Environment.instance.context + + if (localStorage && typeof localStorage === 'object') { + if (key in localStorage) { + return localStorage[key] + } + } + + const value = super.get(key) + + if (value !== null) { + super.set(key, value) + } + + return value + } + + /** + * Sets a `value` by `key` + * @param {string} key + * @param {string} value + * @throws DOMException + */ + set (key, value) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + // @ts-ignore + const { localStorage } = Environment.instance.context + + if (localStorage) { + // @ts-ignore + Environment.instance.context.localStorage = { + ...localStorage, + [key]: String(value) + } + } + + return super.set(key, value) + } + + /** + * Remove a value by `key`. + * @param {string} key + * @return {boolean} + * @throws DOMException + */ + remove (key) { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + // @ts-ignore + const { localStorage } = Environment.instance.context + + if (localStorage && typeof localStorage === 'object') { + if (key in localStorage) { + delete localStorage[key] + // @ts-ignore + Environment.instance.context.localStorage = localStorage + } + } + + return super.remove(key) + } + + /** + * Clear all keys and values. + * @throws DOMException + */ + clear () { + if (this.error) { + throw Object.assign(new DOMException( + 'Storage provider in error state', 'InvalidStateError' + ), { cause: this.error }) + } + + // @ts-ignore + Environment.instance.context.localStorage = {} + return super.clear() + } + + /** + * Loads the internal index with keys and values. + * @return {Promise} + * @throws DOMException + */ + async load () { + if (!Environment.instance) { + throw Object.assign(new DOMException( + 'Storage provider is missing Environment', 'InvalidStateError' + ), { cause: this.error }) + } + + // ensure `Environment` is opened + await Environment.instance.open() + + // @ts-ignore + const localStorage = Environment.instance.context.localStorage || {} + + for (const key in localStorage) { + const value = localStorage[key] + this.index.insert(key, value) + } + } +} + +/** + * A generic interface for storage implementations + */ +export class Storage { + /** + * A factory for creating a `Storage` instance that is backed + * by a storage provider. Extending classes should define a `Provider` + * class that is statically available on the extended `Storage` class. + * @param {symbol} token + * @return {Promise>} + */ + static async create (token) { + // @ts-ignore + const { Provider } = this + + if (typeof Provider !== 'function') { + throw new TypeError('Storage implementation is missing Provider implementation') + } + + const provider = new Provider() + const instance = new this(token, provider) + + await provider.ready + + return new Proxy(instance, { + get (_, property) { + if (instance.hasItem(property)) { + return instance.getItem(property) + } + + if (property in instance) { + const value = instance[property] + if (typeof value === 'function') { + return value.bind(instance) + } + + return value + } + + return undefined + }, + + set (_, property, value) { + if (property in Storage.prototype) { + return false + } + + instance.setItem(property, value) + return true + }, + + has (_, property) { + return instance.hasItem(property) + }, + + deleteProperty (_, property) { + if (instance.hasItem(property)) { + instance.removeItem(property) + return true + } + + return false + }, + + getOwnPropertyDescriptor (_, property) { + if (instance.hasItem(property)) { + return { + configurable: true, + enumerable: true, + writable: true, + value: instance.getItem(property) + } + } + }, + + ownKeys (_) { + return Array.from(instance.provider.keys()) + } + }) + } + + #provider = null + + /** + * `Storage` class constructor. + * @ignore + * @param {symbol} token + * @param {Provider} provider + */ + constructor (token, provider) { + if (token !== createStorageSymbol) { + throw new IllegalConstructorError('Illegal constructor') + } + + this.#provider = provider + } + + /** + * A readonly reference to the storage provider. + * @type {Provider} + */ + get provider () { + return this.#provider + } + + /** + * The number of entries in the storage. + * @type {number} + */ + get length () { + return this.provider.length + } + + /** + * Returns `true` if the storage has a value for a given `key`. + * @param {string} key + * @return {boolean} + * @throws TypeError + */ + hasItem (key) { + if (arguments.length === 0) { + throw new TypeError('Not enough arguments') + } + + return this.provider.has(key) + } + + /** + * Clears the storage of all entries + */ + clear () { + this.provider.clear() + } + + /** + * Returns the key at a given `index` + * @param {number} index + * @return {string|null} + */ + key (index) { + if (arguments.length === 0) { + throw new TypeError('Not enough arguments') + } + + return this.provider.key(index) + } + + /** + * Get a storage value item for a given `key`. + * @param {string} key + * @return {string|null} + */ + getItem (key) { + if (arguments.length === 0) { + throw new TypeError('Not enough arguments') + } + + return this.provider.get(key) + } + + /** + * Removes a storage value entry for a given `key`. + * @param {string} + * @return {boolean} + */ + removeItem (key) { + if (arguments.length === 0) { + throw new TypeError('Not enough arguments') + } + + this.provider.remove(key) + } + + /** + * Sets a storage item `value` for a given `key`. + * @param {string} key + * @param {string} value + */ + setItem (key, value) { + if (arguments.length === 0) { + throw new TypeError('Not enough arguments') + } + + this.provider.set(key, value) + } + + /** + * @ignore + */ + get [Symbol.toStringTag] () { + return 'Storage' + } +} + +/** + * An in-memory `Storage` interface. + */ +export class MemoryStorage extends Storage { + static Provider = MemoryStorageProvider +} + +/** + * A locally persisted `Storage` interface. + */ +export class LocalStorage extends Storage { + static Provider = LocalStorageProvider +} + +/** + * A session `Storage` interface. + */ +export class SessionStorage extends Storage { + static Provider = SessionStorageProvider +} + +/** + * A factory for creating storage interfaces. + * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type + * @return {Promise} + */ +export async function createStorageInterface (type) { + if (type === 'memoryStorage') { + return await MemoryStorage.create(createStorageSymbol) + } else if (type === 'localStorage') { + return await LocalStorage.create(createStorageSymbol) + } else if (type === 'sessionStorage') { + return await SessionStorage.create(createStorageSymbol) + } + + throw new TypeError( + `Invalid 'Storage' interface type given: Received: ${type}` + ) +} + +export default { + Storage, + LocalStorage, + MemoryStorage, + SessionStorage, + createStorageInterface +} diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 992fbdfb65..74882acbce 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -1,10 +1,12 @@ import { ExtendableEvent, FetchEvent } from './events.js' import { ServiceWorkerGlobalScope } from './global.js' +import { createStorageInterface } from './storage.js' import { Module, createRequire } from '../module.js' import { STATUS_CODES } from '../http.js' import { Environment } from './env.js' import { Deferred } from '../async.js' import { Buffer } from '../buffer.js' +import { Cache } from '../commonjs/cache.js' import globals from '../internal/globals.js' import process from '../process.js' import clients from './clients.js' @@ -22,13 +24,17 @@ Object.defineProperties( Object.getOwnPropertyDescriptors(ServiceWorkerGlobalScope.prototype) ) +const module = { exports: {} } const events = new Set() +// event listeners hooks.onReady(onReady) globalThis.addEventListener('message', onMessage) +// service worker globals globals.register('ServiceWorker.state', state) globals.register('ServiceWorker.events', events) +globals.register('ServiceWorker.module', module) function onReady () { globalThis.postMessage(SERVICE_WORKER_READY_TOKEN) @@ -38,14 +44,15 @@ async function onMessage (event) { const { data } = event if (data?.register) { + // preload commonjs cache + await Cache.restore(['loader.status', 'loader.response']) + const { id, scope, scriptURL } = data.register const url = new URL(scriptURL) - const module = { exports: {} } state.id = id state.serviceWorker.scope = scope state.serviceWorker.scriptURL = scriptURL - globals.register('ServiceWorker.module', module) Module.main.addEventListener('error', (event) => { if (event.error) { @@ -109,10 +116,40 @@ async function onMessage (event) { }) try { + // define the actual location of the worker. not `blob:...` globalThis.RUNTIME_WORKER_LOCATION = scriptURL + + // update and notify initial state change state.serviceWorker.state = 'registering' await state.notify('serviceWorker') + // open envirnoment + await Environment.open({ id, scope }) + + // install storage interfaces + Object.defineProperties(globalThis, { + localStorage: { + configurable: false, + enumerable: false, + writable: false, + value: createStorageInterface('localStorage') + }, + + sessionStorage: { + configurable: false, + enumerable: false, + writable: false, + value: createStorageInterface('sessionStorage') + }, + + memoryStorage: { + configurable: false, + enumerable: false, + writable: false, + value: createStorageInterface('memoryStorage') + } + }) + // import module, which could be ESM, CommonJS, // or a simple ServiceWorker const result = await import(scriptURL) @@ -134,8 +171,6 @@ async function onMessage (event) { return } - await Environment.open({ id, scope }) - if (module.exports.default && typeof module.exports.default === 'object') { if (typeof module.exports.default.fetch === 'function') { state.fetch = module.exports.default.fetch From 8c86bb0653d1efd60b7d1dd4b8b394a95c2a362a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:37:56 -0400 Subject: [PATCH 0541/1178] feat(core/service_worker_container.*): introduce session storage per registration --- src/core/service_worker_container.cc | 26 ++++++++++++++++++++++++++ src/core/service_worker_container.hh | 10 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 692cdce068..7f38960852 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -131,6 +131,32 @@ namespace SSC { return stateString; }; + const JSON::Object ServiceWorkerContainer::Registration::Storage::json () const { + return JSON::Object { this->data }; + } + + void ServiceWorkerContainer::Registration::Storage::set (const String& key, const String& value) { + this->data.insert_or_assign(key, value); + } + + const String ServiceWorkerContainer::Registration::Storage::get (const String& key) const { + if (this->data.contains(key)) { + return this->data.at(key); + } + + return key; + } + + void ServiceWorkerContainer::Registration::Storage::remove (const String& key) { + if (this->data.contains(key)) { + this->data.erase(key); + } + } + + void ServiceWorkerContainer::Registration::Storage::clear () { + this->data.clear(); + } + ServiceWorkerContainer::ServiceWorkerContainer (Core* core) { this->core = core; } diff --git a/src/core/service_worker_container.hh b/src/core/service_worker_container.hh index 7a15cecd0c..94d30449e0 100644 --- a/src/core/service_worker_container.hh +++ b/src/core/service_worker_container.hh @@ -41,8 +41,18 @@ namespace SSC { Activated }; + struct Storage { + Map data; + const JSON::Object json () const; + void set (const String& key, const String& value); + const String get (const String& key) const; + void remove (const String& key); + void clear (); + }; + ID id = 0; String scriptURL; + Storage storage; Atomic state = State::None; RegistrationOptions options; From bddbed4d7cd1ac55964ed0673a53badf970dd994 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:38:27 -0400 Subject: [PATCH 0542/1178] fix(src/core/preload.cc): prevent duplicate emit 'load' event --- src/core/preload.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/preload.cc b/src/core/preload.cc index 3c35d1fbdd..27cbf56b86 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -229,7 +229,9 @@ namespace SSC { " \n" " if (typeof value === 'function') { \n" " onload = value; \n" - " globalThis.addEventListener(event, onload, opts); \n" + " globalThis.addEventListener(event, onload, opts, { \n" + " once: true \n" + " }); \n" " } \n" " } \n" " }); \n" From 6f75eb6e7d7f42264e77f7d379538b8a5b90998a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:39:11 -0400 Subject: [PATCH 0543/1178] feat(src/ipc/bridge.cc): introduce 'serviceWorker.storage' IPC interface --- src/ipc/bridge.cc | 162 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 9c34a58fe0..0fbde63477 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -2740,6 +2740,168 @@ static void initRouterTable (Router *router) { reply(Result::Data { message, JSON::Object {}}); }); + /** + * Sets storage for a service worker. + * @param id + * @param key + * @param value + */ + router->map("serviceWorker.storage.set", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "key", "value"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + registration.storage.set(message.get("key"), message.get("value")); + return reply(Result::Data { message, JSON::Object {}}); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Gets a storage value for a service worker. + * @param id + * @param key + */ + router->map("serviceWorker.storage.get", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "key"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + return reply(Result::Data { + message, + JSON::Object::Entries { + {"value", registration.storage.get(message.get("key"))} + } + }); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Remoes a storage value for a service worker. + * @param id + * @param key + */ + router->map("serviceWorker.storage.remove", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "key"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + registration.storage.remove(message.get("key")); + return reply(Result::Data {message, JSON::Object {}}); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Clears all storage values for a service worker. + * @param id + */ + router->map("serviceWorker.storage.clear", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + registration.storage.clear(); + return reply(Result::Data { message, JSON::Object {} }); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Gets all storage values for a service worker. + * @param id + */ + router->map("serviceWorker.storage", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + return reply(Result::Data { message, registration.storage.json() }); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + router->map("timers.setTimeout", [=](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"timeout"}); From 5d6256520254009f529f4799f887eb94267f3b95 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:39:33 -0400 Subject: [PATCH 0544/1178] refactor(api/internal/database.js): improve 'get()' and 'entries()' --- api/internal/database.js | 70 +++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/api/internal/database.js b/api/internal/database.js index b6d6898795..a916d92866 100644 --- a/api/internal/database.js +++ b/api/internal/database.js @@ -453,7 +453,7 @@ export class Database extends EventTarget { /** * `Database` class constructor. * @param {string} name - * @param {?DatabaseOptions | undefiend} [options] + * @param {?DatabaseOptions | undefined} [options] */ constructor (name, options = null) { if (!name || typeof name !== 'string') { @@ -574,9 +574,11 @@ export class Database extends EventTarget { }) this.#opening = false - this.dispatchEvent(new DatabaseEvent('open', this)) - gc.ref(this) + + queueMicrotask(() => { + this.dispatchEvent(new DatabaseEvent('open', this)) + }) } /** @@ -622,7 +624,7 @@ export class Database extends EventTarget { /** * Gets a "readonly" value by `key` in the `Database` object storage. * @param {string} key - * @param {?DatabaseGetOptions|undefiend} [options] + * @param {?DatabaseGetOptions|undefined} [options] * @return {Promise} */ async get (key, options = null) { @@ -661,7 +663,7 @@ export class Database extends EventTarget { for (const store of stores) { if (count > 1) { - pending.push(this.#queue.push(store.getAll(key, { count }))) + pending.push(this.#queue.push(store.getAll(key, count))) } else { pending.push(this.#queue.push(store.get(key))) } @@ -676,8 +678,14 @@ export class Database extends EventTarget { } return results - .filter((result) => result !== null) - .map((result) => result?.value ?? null) + .map(function map (result) { + return Array.isArray(result) ? result.flatMap(map) : result + }) + .reduce((a, b) => a.concat(b), []) + .filter((value) => value) + .map((entry) => { + return [entry.key, entry.value] + }) } /** @@ -685,7 +693,7 @@ export class Database extends EventTarget { * "inserting" it into the `Database` instance. * @param {string} key * @param {any} value - * @param {?DatabasePutOptions|undefiend} [options] + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ async put (key, value, options = null) { @@ -733,7 +741,7 @@ export class Database extends EventTarget { * already exists. * @param {string} key * @param {any} value - * @param {?DatabasePutOptions|undefiend} [options] + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ async insert (key, value, options = null) { @@ -781,7 +789,7 @@ export class Database extends EventTarget { * "inserting" it into the `Database` instance. * @param {string} key * @param {any} value - * @param {?DatabasePutOptions|undefiend} [options] + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ async update (key, value, options = null) { @@ -841,7 +849,7 @@ export class Database extends EventTarget { /** * Delete a value at `key`. * @param {string} key - * @param {?DatabaseDeleteOptions|undefiend} [options] + * @param {?DatabaseDeleteOptions|undefined} [options] * @return {Promise} */ async delete (key, options = null) { @@ -887,7 +895,7 @@ export class Database extends EventTarget { /** * Gets a "readonly" value by `key` in the `Database` object storage. * @param {string} key - * @param {?DatabaseEntriesOptions|undefiend} [options] + * @param {?DatabaseEntriesOptions|undefined} [options] * @return {Promise} */ async entries (options = null) { @@ -921,15 +929,25 @@ export class Database extends EventTarget { } for (const store of stores) { - pending.push(this.#queue.push(store.openCursor())) + const request = store.openCursor() + let cursor = await this.#queue.push(request) + + while (cursor) { + if (!cursor.value) { + break + } + pending.push(Promise.resolve(cursor.value)) + cursor.continue() + cursor = await this.#queue.push(request) + } } await this.#queue.push(transaction) const results = await Promise.all(pending) return results - .filter((result) => result?.value) - .map((result) => [result.key, result.value.value]) + .filter((value) => value) + .map((entry) => [entry.key, entry.value]) } /** @@ -950,7 +968,7 @@ export class Database extends EventTarget { /** * Creates an opens a named `Database` instance. * @param {string} name - * @param {?DatabaseOptions | undefiend} [options] + * @param {?DatabaseOptions | undefined} [options] * @return {Promise} */ export async function open (name, options) { @@ -958,17 +976,22 @@ export async function open (name, options) { // return already opened instance if still referenced somehow if (ref && ref.deref()) { - return ref.deref() + const database = ref.deref() + if (database.opened) { + return database + } + + return new Promise((resolve) => { + database.addEventListener('open', () => { + resolve(database) + }, { once: true }) + }) } const database = new Database(name, options) opened.set(name, new WeakRef(database)) - database.addEventListener('open', () => { - opened.set(name, new WeakRef(database)) - }, { once: true }) - database.addEventListener('close', () => { opened.delete(name) }, { once: true }) @@ -986,7 +1009,7 @@ export async function open (name, options) { /** * Complete deletes a named `Database` instance. * @param {string} name - * @param {?DatabaseOptions | undefiend} [options] + * @param {?DatabaseOptions|undefined} [options] */ export async function drop (name, options) { const ref = opened.get(name) @@ -997,5 +1020,6 @@ export async function drop (name, options) { export default { Database, - open + open, + drop } From 776deb17397e4711479af927b2bb8b5f06bfc1d1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:39:59 -0400 Subject: [PATCH 0545/1178] feat(api/internal/serialize.js): initial 'serialize' internal module --- api/internal/serialize.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 api/internal/serialize.js diff --git a/api/internal/serialize.js b/api/internal/serialize.js new file mode 100644 index 0000000000..0d0a0f18cd --- /dev/null +++ b/api/internal/serialize.js @@ -0,0 +1,39 @@ +export default function serialize (value) { + if (!value || typeof value !== 'object') { + return value + } + + return map(value, (value) => { + if (typeof value[Symbol.serialize] === 'function') { + return value[Symbol.serialize]() + } + + if (typeof value.toJSON === 'function') { + return value.toJSON() + } + + return value + }) +} + +function map (object, callback) { + if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + object[i] = map(object[i], callback) + } + } else if (object && typeof object === 'object') { + object = callback(object) + for (const key in object) { + const descriptor = Object.getOwnPropertyDescriptor(object, key) + if (descriptor && descriptor.writable) { + object[key] = map(object[key], callback) + } + } + } + + if (object && typeof object === 'object') { + return callback(object) + } else { + return object + } +} From 1271002000c88f0eef17e2c140c2c08a812a99f2 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:40:53 -0400 Subject: [PATCH 0546/1178] fix(api/internal/promise.js): export extended 'Promise' instead of patching globalThis --- api/internal/promise.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/internal/promise.js b/api/internal/promise.js index 69c982a033..5bef0f01a2 100644 --- a/api/internal/promise.js +++ b/api/internal/promise.js @@ -1,3 +1,4 @@ +// @ts-nocheck import * as asyncHooks from './async/hooks.js' const resourceSymbol = Symbol('PromiseResource') @@ -12,7 +13,8 @@ export const NativePromisePrototype = { export const NativePromiseAll = globalThis.Promise.all.bind(globalThis.Promise) export const NativePromiseAny = globalThis.Promise.any.bind(globalThis.Promise) -globalThis.Promise = class Promise extends NativePromise { +// @ts-ignore +export class Promise extends NativePromise { constructor (...args) { super(...args) // eslint-disable-next-line @@ -24,7 +26,7 @@ globalThis.Promise = class Promise extends NativePromise { } } -globalThis.Promise.all = function (iterable) { +Promise.all = function (iterable) { return NativePromiseAll.call(NativePromise, Array.from(iterable).map((promise, index) => { if (!promise || typeof promise.catch !== 'function') { return promise @@ -50,7 +52,7 @@ globalThis.Promise.all = function (iterable) { })) } -globalThis.Promise.any = function (iterable) { +Promise.any = function (iterable) { return NativePromiseAny.call(NativePromise, Array.from(iterable).map((promise, index) => { if (!promise || typeof promise.catch !== 'function') { return promise @@ -77,7 +79,7 @@ globalThis.Promise.any = function (iterable) { } function wrapNativePromiseFunction (name) { - const prototype = globalThis.Promise.prototype + const prototype = Promise.prototype if (prototype[name].__async_wrapped__) { return } From 3a62d72259b89cc6a6d82818d2b124ef904a8b3e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:41:11 -0400 Subject: [PATCH 0547/1178] refactor(api/internal/post-message.js): use 'serialize' internal module --- api/internal/post-message.js | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/api/internal/post-message.js b/api/internal/post-message.js index 6fb6e4b152..36ac2a4792 100644 --- a/api/internal/post-message.js +++ b/api/internal/post-message.js @@ -1,3 +1,5 @@ +import serialize from './serialize.js' + const platform = { BroadcastChannelPostMessage: globalThis.BroadcastChannel.prototype.postMessage, MessageChannelPostMessage: globalThis.MessageChannel.prototype.postMessage, @@ -18,39 +20,7 @@ globalThis.postMessage = function (message, ...args) { } function handlePostMessage (message) { - if (!message || typeof message !== 'object') { - return message - } - - return map(message, (value) => { - if (typeof value[Symbol.serialize] !== 'function') { - return value - } - - return value[Symbol.serialize]() - }) -} - -function map (object, callback) { - if (Array.isArray(object)) { - for (let i = 0; i < object.length; ++i) { - object[i] = map(object[i], callback) - } - } else if (object && typeof object === 'object') { - object = callback(object) - for (const key in object) { - const descriptor = Object.getOwnPropertyDescriptor(object, key) - if (descriptor && descriptor.writable) { - object[key] = map(object[key], callback) - } - } - } - - if (object && typeof object === 'object') { - return callback(object) - } else { - return object - } + return serialize(message) } export default null From d7cc35754e1c60b32d936867d3866d9ba0af62bb Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:41:29 -0400 Subject: [PATCH 0548/1178] refactor(api/internal/primitives.js): install internal 'Promise' --- api/internal/primitives.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 7b1c2c5a76..8791e5b7ee 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -1,6 +1,5 @@ /* global MutationObserver */ import './post-message.js' -import './promise.js' import './error.js' import { fetch, Headers, Request, Response } from '../fetch.js' @@ -14,6 +13,7 @@ import permissions from './permissions.js' import WebAssembly from './webassembly.js' import { Buffer } from '../buffer.js' import scheduler from './scheduler.js' +import Promise from './promise.js' import symbols from './symbols.js' import { @@ -136,7 +136,11 @@ export function init () { ) { const nativeDescriptors = Object.getOwnPropertyDescriptors(nativeImplementation.prototype) const descriptors = Object.getOwnPropertyDescriptors(implementation.prototype) - implementation[Symbol.species] = nativeImplementation + + try { + implementation[Symbol.species] = nativeImplementation + } catch {} + for (const key in nativeDescriptors) { const nativeDescriptor = nativeDescriptors[key] const descriptor = descriptors[key] @@ -210,6 +214,9 @@ export function init () { URLPattern, URLSearchParams, + // Promise + Promise, + // fetch fetch, Headers, From 80035a88e19c42db47f2bb6bdc0f97f3c3531d27 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:41:43 -0400 Subject: [PATCH 0549/1178] chore(package.json): upgrade vendor deps --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fc7a7ea4c9..1dfc3e9d6f 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "acorn": "8.10.0", "acorn-walk": "8.2.0", "standard": "^17.1.0", - "typescript": "5.3.2", - "urlpattern-polyfill": "^9.0.0", + "typescript": "5.4.4", + "urlpattern-polyfill": "^10.0.0", "web-streams-polyfill": "^4.0.0", - "whatwg-fetch": "^3.6.17", - "whatwg-url": "^13.0.0" + "whatwg-fetch": "^3.6.20", + "whatwg-url": "^14.0.0" }, "optionalDependencies": { "@socketsupply/stream-relay": "^1.0.23-0" From 372e532ad8a727f928164f278a173ce85fbd6937 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 9 Apr 2024 18:42:24 -0400 Subject: [PATCH 0550/1178] chore(api): generate types + docs --- api/README.md | 36 +- api/index.d.ts | 908 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 780 insertions(+), 164 deletions(-) diff --git a/api/README.md b/api/README.md index bed844a467..f4bf5a6c01 100644 --- a/api/README.md +++ b/api/README.md @@ -1014,7 +1014,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L667) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L666) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1027,7 +1027,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L722) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L721) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1040,7 +1040,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L750) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L749) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1054,7 +1054,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L785) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L784) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1068,7 +1068,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L819) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L818) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1081,7 +1081,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L871) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L870) @@ -1094,7 +1094,7 @@ Asynchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L914) +## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L913) @@ -1106,7 +1106,7 @@ Asynchronously read all entries in a directory. | options.flag ? r | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L970) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L969) Reads link at `path` @@ -1115,7 +1115,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L990) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L989) Computes real path for `path` @@ -1124,7 +1124,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1010) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1009) Renames file or directory at `src` to `dest`. @@ -1134,7 +1134,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1036) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1035) Removes directory at `path`. @@ -1143,7 +1143,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1059) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1058) Synchronously get the stats of a file @@ -1154,7 +1154,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1079) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1078) Get the stats of a file @@ -1167,7 +1167,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1117) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1116) Get the stats of a symbolic link @@ -1180,7 +1180,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1151) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1150) Creates a symlink of `src` at `dest`. @@ -1189,7 +1189,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1194) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1193) Unlinks (removes) file at `path`. @@ -1198,7 +1198,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1221) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1220) @@ -1213,7 +1213,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1263) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1262) Watch for changes at `path` calling `callback` diff --git a/api/index.d.ts b/api/index.d.ts index d98a709fe4..4924a1ab74 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -162,83 +162,6 @@ declare module "socket:buffer" { function byteLength(string: any, encoding: any, ...args: any[]): any; } -declare module "socket:events" { - export const Event: { - new (type: string, eventInitDict?: EventInit): Event; - prototype: Event; - readonly NONE: 0; - readonly CAPTURING_PHASE: 1; - readonly AT_TARGET: 2; - readonly BUBBLING_PHASE: 3; - } | { - new (): {}; - }; - export const EventTarget: { - new (): {}; - }; - export const CustomEvent: { - new (type: string, eventInitDict?: CustomEventInit): CustomEvent; - prototype: CustomEvent; - } | { - new (type: any, options: any): { - "__#1@#detail": any; - readonly detail: any; - }; - }; - export const MessageEvent: { - new (type: string, eventInitDict?: MessageEventInit): MessageEvent; - prototype: MessageEvent; - } | { - new (type: any, options: any): { - "__#2@#detail": any; - "__#2@#data": any; - readonly detail: any; - readonly data: any; - }; - }; - export const ErrorEvent: { - new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; - prototype: ErrorEvent; - } | { - new (type: any, options: any): { - "__#3@#detail": any; - "__#3@#error": any; - readonly detail: any; - readonly error: any; - }; - }; - export default EventEmitter; - export function EventEmitter(): void; - export class EventEmitter { - _events: any; - _eventsCount: number; - _maxListeners: number; - setMaxListeners(n: any): this; - getMaxListeners(): any; - emit(type: any, ...args: any[]): boolean; - addListener(type: any, listener: any): any; - on(arg0: any, arg1: any): any; - prependListener(type: any, listener: any): any; - once(type: any, listener: any): this; - prependOnceListener(type: any, listener: any): this; - removeListener(type: any, listener: any): this; - off(type: any, listener: any): this; - removeAllListeners(type: any, ...args: any[]): this; - listeners(type: any): any[]; - rawListeners(type: any): any[]; - listenerCount(type: any): any; - eventNames(): any; - } - export namespace EventEmitter { - export { EventEmitter }; - export let defaultMaxListeners: number; - export function init(): void; - export function listenerCount(emitter: any, type: any): any; - export { once }; - } - export function once(emitter: any, name: any): Promise; -} - declare module "socket:async/context" { /** * @module Async.AsyncContext @@ -400,7 +323,7 @@ declare module "socket:async/context" { * The current `Mapping` for this `AsyncContext`. * @type {Mapping} */ - static "__#7@#current": Mapping; + static "__#4@#current": Mapping; /** * Returns `true` if the current `Mapping` has a * `AsyncContext.Variable` at `key`, @@ -564,6 +487,84 @@ declare module "socket:async/context" { export type AnyFunc = () => any; } +declare module "socket:events" { + export const Event: { + new (type: string, eventInitDict?: EventInit): Event; + prototype: Event; + readonly NONE: 0; + readonly CAPTURING_PHASE: 1; + readonly AT_TARGET: 2; + readonly BUBBLING_PHASE: 3; + } | { + new (): {}; + }; + export const EventTarget: { + new (): {}; + }; + export const CustomEvent: { + new (type: string, eventInitDict?: CustomEventInit): CustomEvent; + prototype: CustomEvent; + } | { + new (type: any, options: any): { + "__#7@#detail": any; + readonly detail: any; + }; + }; + export const MessageEvent: { + new (type: string, eventInitDict?: MessageEventInit): MessageEvent; + prototype: MessageEvent; + } | { + new (type: any, options: any): { + "__#8@#detail": any; + "__#8@#data": any; + readonly detail: any; + readonly data: any; + }; + }; + export const ErrorEvent: { + new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; + prototype: ErrorEvent; + } | { + new (type: any, options: any): { + "__#9@#detail": any; + "__#9@#error": any; + readonly detail: any; + readonly error: any; + }; + }; + export default EventEmitter; + export function EventEmitter(): void; + export class EventEmitter { + _events: any; + _contexts: any; + _eventsCount: number; + _maxListeners: number; + setMaxListeners(n: any): this; + getMaxListeners(): any; + emit(type: any, ...args: any[]): boolean; + addListener(type: any, listener: any): any; + on(arg0: any, arg1: any): any; + prependListener(type: any, listener: any): any; + once(type: any, listener: any): this; + prependOnceListener(type: any, listener: any): this; + removeListener(type: any, listener: any): this; + off(type: any, listener: any): this; + removeAllListeners(type: any, ...args: any[]): this; + listeners(type: any): any[]; + rawListeners(type: any): any[]; + listenerCount(type: any): any; + eventNames(): (string | symbol)[]; + } + export namespace EventEmitter { + export { EventEmitter }; + export let defaultMaxListeners: number; + export function init(): void; + export function listenerCount(emitter: any, type: any): any; + export { once }; + } + export function once(emitter: any, name: any): Promise; +} + declare module "socket:async/wrap" { /** * Returns `true` if a given function `fn` has the "async" wrapped tag, @@ -2366,6 +2367,7 @@ declare module "socket:url/urlpattern/urlpattern" { "__#21@#t": {}; "__#21@#e": {}; "__#21@#s": {}; + "__#21@#l": boolean; test(t: {}, r: any): boolean; exec(t: {}, r: any): { inputs: any[] | {}[]; @@ -2378,6 +2380,7 @@ declare module "socket:url/urlpattern/urlpattern" { readonly pathname: any; readonly search: any; readonly hash: any; + readonly hasRegExpGroups: boolean; }; compareComponent(t: any, r: any, n: any): number; }; @@ -2433,6 +2436,7 @@ declare module "socket:url/index" { "__#21@#t": {}; "__#21@#e": {}; "__#21@#s": {}; + "__#21@#l": boolean; test(t: {}, r: any): boolean; exec(t: {}, r: any): { inputs: any[] | {}[]; @@ -2445,6 +2449,7 @@ declare module "socket:url/index" { readonly pathname: any; readonly search: any; readonly hash: any; + readonly hasRegExpGroups: boolean; }; compareComponent(t: any, r: any, n: any): number; }; @@ -4316,8 +4321,10 @@ declare module "socket:fs/index" { export function mkdir(path: any, options: any, callback: any): void; /** * @ignore + * @param {string|URL} path + * @param {object=} [options] */ - export function mkdirSync(path: any, options: any): void; + export function mkdirSync(path: string | URL, options?: object | undefined): void; /** * Asynchronously open a file calling `callback` upon success or error. * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} @@ -7435,7 +7442,6 @@ declare module "socket:vm" { filename?: string; context?: object; }; - import { SharedWorker } from "socket:internal/shared-worker"; } declare module "socket:worker_threads/init" { @@ -7474,7 +7480,7 @@ declare module "socket:worker_threads" { * A pool of known worker threads. * @type {} */ - export const workers: () => () => any; + export const workers: () => () => any; /** * `true` if this is the "main" thread, otherwise `false` * The "main" thread is the top level webview window. @@ -7631,7 +7637,6 @@ declare module "socket:worker_threads" { import { Readable } from "socket:stream"; import { SHARE_ENV } from "socket:worker_threads/init"; import init from "socket:worker_threads/init"; - import { env } from "socket:process"; export { SHARE_ENV, init }; } @@ -7819,7 +7824,6 @@ declare module "socket:child_process" { import { AsyncResource } from "socket:async/resource"; import { EventEmitter } from "socket:events"; import { Worker } from "socket:worker_threads"; - import signal from "socket:signal"; } declare module "socket:constants" { @@ -8334,7 +8338,7 @@ declare module "socket:fs/web" { export function createFile(filename: string, options?: { fd: fs.FileHandle; highWaterMark?: number; - }): File; + } | undefined): File; /** * Creates a `FileSystemWritableFileStream` instance backed * by `socket:fs:` module from a given `FileSystemFileHandle` instance. @@ -8630,13 +8634,9 @@ declare module "socket:extension" { * @typedef {number} Pointer */ const $loaded: unique symbol; - import path from "socket:path"; } declare module "socket:fetch/fetch" { - export class DOMException { - private constructor(); - } export function Headers(headers: any): void; export class Headers { constructor(headers: any); @@ -8694,12 +8694,19 @@ declare module "socket:fetch/fetch" { function redirect(url: any, status: any): Response; } export function fetch(input: any, init: any): Promise; - export namespace fetch { - let polyfill: boolean; + export class DOMException { + private constructor(); + } + namespace _default { + export { fetch }; + export { Headers }; + export { Request }; + export { Response }; } + export default _default; } -declare module "socket:service-worker/database" { +declare module "socket:internal/database" { /** * A typed container for optional options given to the `Database` * class constructor. @@ -8768,16 +8775,16 @@ declare module "socket:service-worker/database" { /** * Creates an opens a named `Database` instance. * @param {string} name - * @param {?DatabaseOptions | undefiend} [options] + * @param {?DatabaseOptions | undefined} [options] * @return {Promise} */ - export function open(name: string, options?: (DatabaseOptions | undefiend) | null): Promise; + export function open(name: string, options?: (DatabaseOptions | undefined) | null): Promise; /** * Complete deletes a named `Database` instance. * @param {string} name - * @param {?DatabaseOptions | undefiend} [options] + * @param {?DatabaseOptions|undefined} [options] */ - export function drop(name: string, options?: (DatabaseOptions | undefiend) | null): Promise; + export function drop(name: string, options?: (DatabaseOptions | undefined) | null): Promise; /** * A mapping of named `Database` instances that are currently opened * @type {Map>} @@ -8898,9 +8905,9 @@ declare module "socket:service-worker/database" { /** * `Database` class constructor. * @param {string} name - * @param {?DatabaseOptions | undefiend} [options] + * @param {?DatabaseOptions | undefined} [options] */ - constructor(name: string, options?: (DatabaseOptions | undefiend) | null); + constructor(name: string, options?: (DatabaseOptions | undefined) | null); /** * `true` if the `Database` is currently opening, otherwise `false`. * A `Database` instance should not attempt to be opened if this property value @@ -8968,56 +8975,57 @@ declare module "socket:service-worker/database" { /** * Gets a "readonly" value by `key` in the `Database` object storage. * @param {string} key - * @param {?DatabaseGetOptions|undefiend} [options] + * @param {?DatabaseGetOptions|undefined} [options] * @return {Promise} */ - get(key: string, options?: (DatabaseGetOptions | undefiend) | null): Promise; + get(key: string, options?: (DatabaseGetOptions | undefined) | null): Promise; /** * Put a `value` at `key`, updating if it already exists, otherwise * "inserting" it into the `Database` instance. * @param {string} key * @param {any} value - * @param {?DatabasePutOptions|undefiend} [options] + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ - put(key: string, value: any, options?: (DatabasePutOptions | undefiend) | null): Promise; + put(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; /** * Inserts a new `value` at `key`. This function throws if a value at `key` * already exists. * @param {string} key * @param {any} value - * @param {?DatabasePutOptions|undefiend} [options] + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ - insert(key: string, value: any, options?: (DatabasePutOptions | undefiend) | null): Promise; + insert(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; /** * Update a `value` at `key`, updating if it already exists, otherwise * "inserting" it into the `Database` instance. * @param {string} key * @param {any} value - * @param {?DatabasePutOptions|undefiend} [options] + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ - update(key: string, value: any, options?: (DatabasePutOptions | undefiend) | null): Promise; + update(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; /** * Delete a value at `key`. * @param {string} key - * @param {?DatabaseDeleteOptions|undefiend} [options] + * @param {?DatabaseDeleteOptions|undefined} [options] * @return {Promise} */ - delete(key: string, options?: (DatabaseDeleteOptions | undefiend) | null): Promise; + delete(key: string, options?: (DatabaseDeleteOptions | undefined) | null): Promise; /** * Gets a "readonly" value by `key` in the `Database` object storage. * @param {string} key - * @param {?DatabaseEntriesOptions|undefiend} [options] + * @param {?DatabaseEntriesOptions|undefined} [options] * @return {Promise} */ - entries(options?: (DatabaseEntriesOptions | undefiend) | null): Promise; + entries(options?: (DatabaseEntriesOptions | undefined) | null): Promise; #private; } namespace _default { export { Database }; export { open }; + export { drop }; } export default _default; /** @@ -9092,11 +9100,34 @@ declare module "socket:service-worker/env" { * @return {Promise} */ export function reset(): Promise; + /** + * @typedef {{ + * scope: string + * }} EnvironmentOptions + */ + /** + * An event dispatched when a environment value is updated (set, delete) + */ + export class EnvironmentEvent extends Event { + /** + * `EnvironmentEvent` class constructor. + * @param {'set'|'delete'} type + * @param {object=} [entry] + */ + constructor(type: 'set' | 'delete', entry?: object | undefined); + entry: any; + } /** * An environment context object with persistence and durability * for service worker environments. */ export class Environment extends EventTarget { + /** + * Maximum entries that will be restored from storage into the environment + * context object. + * @type {number} + */ + static MAX_CONTEXT_ENTRIES: number; /** * Opens an environment for a particular scope. * @param {EnvironmentOptions} options @@ -9116,9 +9147,9 @@ declare module "socket:service-worker/env" { constructor(options: EnvironmentOptions); /** * A reference to the currently opened environment database. - * @type {import('./database.js').Database} + * @type {import('../internal/database.js').Database} */ - get database(): import("socket:service-worker/database").Database; + get database(): import("socket:internal/database").Database; /** * A proxied object for reading and writing environment state. * Values written to this object must be cloneable with respect to the @@ -11817,7 +11848,32 @@ declare module "socket:index" { export { network, Cache, sha256, Encryption, Packet, NAT }; } +declare module "socket:internal/serialize" { + export default function serialize(value: any): any; +} + declare module "socket:commonjs/cache" { + /** + * Computes a commonjs session storage cache key. + * @ignore + * @param {Cache=} [cache] + * @param {string=} [key] + * @return {string} + */ + export function sessionStorageCacheKey(cache?: Cache | undefined, key?: string | undefined): string; + /** + * Restores values in a session storage into the cache. + * @ignore + * @param {Cache} cache + */ + export function restoreFromSessionStorage(cache: Cache): void; + /** + * Computes a commonjs session storage cache key. + * @ignore + * @param {Cache} cache + * @param {string} key + */ + export function updateSessionStorage(cache: Cache, key: string): void; /** * @typedef {{ * types?: object, @@ -11826,14 +11882,204 @@ declare module "socket:commonjs/cache" { */ export const CACHE_CHANNEL_MESSAGE_ID: "id"; export const CACHE_CHANNEL_MESSAGE_REPLICATE: "replicate"; + /** + * @typedef {{ + * name: string + * }} StorageOptions + */ + /** + * An storage context object with persistence and durability + * for service worker storages. + */ + export class Storage extends EventTarget { + /** + * Maximum entries that will be restored from storage into the context object. + * @type {number} + */ + static MAX_CONTEXT_ENTRIES: number; + /** + * A mapping of known `Storage` instances. + * @type {Map} + */ + static instances: Map; + /** + * Opens an storage for a particular name. + * @param {StorageOptions} options + * @return {Promise} + */ + static open(options: StorageOptions): Promise; + /** + * `Storage` class constructor + * @ignore + * @param {StorageOptions} options + */ + constructor(options: StorageOptions); + /** + * A reference to the currently opened storage database. + * @type {import('../internal/database.js').Database} + */ + get database(): import("socket:internal/database").Database; + /** + * `true` if the storage is opened, otherwise `false`. + * @type {boolean} + */ + get opened(): boolean; + /** + * A proxied object for reading and writing storage state. + * Values written to this object must be cloneable with respect to the + * structured clone algorithm. + * @see {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm} + * @type {Proxy} + */ + get context(): ProxyConstructor; + /** + * The current storage name. This value is also used as the + * internal database name. + * @type {string} + */ + get name(): string; + /** + * A promise that resolves when the storage is opened. + * @type {Promise?} + */ + get ready(): Promise; + /** + * @ignore + * @param {Promise} promise + */ + forwardRequest(promise: Promise): Promise; + /** + * Resets the current storage to an empty state. + */ + reset(): Promise; + /** + * Synchronizes database entries into the storage context. + */ + sync(): Promise; + /** + * Opens the storage. + * @ignore + */ + open(): Promise; + /** + * Closes the storage database, purging existing state. + * @ignore + */ + close(): Promise; + #private; + } + /** + * A container for `Snapshot` data storage. + */ + export class SnapshotData { + /** + * `SnapshotData` class constructor. + * @param {object=} [data] + */ + constructor(data?: object | undefined); + toJSON: () => this; + [Symbol.toStringTag]: string; + } + /** + * A container for storing a snapshot of the cache data. + */ + export class Snapshot { + /** + * @type {typeof SnapshotData} + */ + static Data: typeof SnapshotData; + /** + * A reference to the snapshot data. + * @type {Snapshot.Data} + */ + get data(): typeof SnapshotData; + /** + * @ignore + * @return {object} + */ + toJSON(): object; + #private; + } + /** + * An interface for managing and performing operations on a collection + * of `Cache` objects. + */ + export class CacheCollection { + /** + * `CacheCollection` class constructor. + * @ignore + * @param {Cache[]|Record} collection + */ + constructor(collection: Cache[] | Record); + /** + * Adds a `Cache` instance to the collection. + * @param {string|Cache} name + * @param {Cache=} [cache] + * @param {boolean} + */ + add(name: string | Cache, cache?: Cache | undefined): any; + /** + * Calls a method on each `Cache` object in the collection. + * @param {string} method + * @param {...any} args + * @return {Promise>} + */ + call(method: string, ...args: any[]): Promise>; + restore(): Promise>; + reset(): Promise>; + snapshot(): Promise>; + get(key: any): Promise>; + delete(key: any): Promise>; + keys(key: any): Promise>; + values(key: any): Promise>; + clear(key: any): Promise>; + } /** * A container for a shared cache that lives for the life time of * application execution. Updates to this storage are replicated to other * instances in the application context, including windows and workers. */ export class Cache { - static types: Map; - static shared: any; + /** + * A globally shared type mapping for the cache to use when + * derserializing a value. + * @type {Map} + */ + static types: Map; + /** + * A globally shared cache store keyed by cache name. This is useful so + * when multiple instances of a `Cache` are created, they can share the + * same data store, reducing duplications. + * @type {Record} + */ + static shared: Record>; + /** + * A mapping of opened `Storage` instances. + * @type {Map} + */ + static storages: Map; + /** + * The `Cache.Snapshot` class. + * @type {typeof Snapshot} + */ + static Snapshot: typeof Snapshot; + /** + * The `Cache.Storage` class + * @type {typeof Storage} + */ + static Storage: typeof Storage; + /** + * Creates a snapshot of the current cache which can be serialized and + * stored in persistent storage. + * @return {Snapshot} + */ + static snapshot(): Snapshot; + /** + * Restore caches from persistent storage. + * @param {string[]} names + * @return {Promise} + */ + static restore(names: string[]): Promise; /** * `Cache` class constructor. * @param {string} name @@ -11850,6 +12096,11 @@ declare module "socket:commonjs/cache" { * @type {import('./loader.js').Loader} */ get loader(): import("socket:commonjs/loader").Loader; + /** + * A reference to the persisted storage. + * @type {Storage} + */ + get storage(): Storage; /** * The cache name * @type {string} @@ -11870,6 +12121,24 @@ declare module "socket:commonjs/cache" { * @type {number} */ get size(): number; + /** + * @type {Map} + */ + get types(): Map; + /** + * Resets the cache map, persisted storage, and session storage. + */ + reset(): Promise; + /** + * Restores cache data from session storage. + */ + restore(): Promise; + /** + * Creates a snapshot of the current cache which can be serialized and + * stored in persistent storage. + * @return {Snapshot.Data} + */ + snapshot(): typeof SnapshotData; /** * Get a value at `key`. * @param {string} key @@ -11893,9 +12162,9 @@ declare module "socket:commonjs/cache" { * Delete a value at `key`. * This does not replicate to shared caches. * @param {string} key - * @return {object|undefined} + * @return {boolean} */ - delete(key: string): object | undefined; + delete(key: string): boolean; /** * Returns an iterator for all cache keys. * @return {object} @@ -11942,6 +12211,9 @@ declare module "socket:commonjs/cache" { types?: object; loader?: import("socket:commonjs/loader").Loader; }; + export type StorageOptions = { + name: string; + }; } declare module "socket:commonjs/loader" { @@ -11991,7 +12263,7 @@ declare module "socket:commonjs/loader" { * @param {object} json * @return {RequestStatus} */ - static from(json: object): RequestStatus; + static from(json: object, options: any): RequestStatus; /** * `RequestStatus` class constructor. * @param {Request} request @@ -12415,7 +12687,7 @@ declare module "socket:commonjs/package" { static parse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - }): ParsedPackageName | null; + } | undefined): ParsedPackageName | null; /** * Returns `true` if the given `input` can be parsed by `Name.parse` or given * as input to the `Name` class constructor. @@ -12426,7 +12698,7 @@ declare module "socket:commonjs/package" { static canParse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - }): boolean; + } | undefined): boolean; /** * Creates a new `Name` from input. * @param {string|URL} input @@ -14080,12 +14352,16 @@ declare module "socket:commonjs/module" { import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import builtins from "socket:commonjs/builtins"; } declare module "socket:module" { export const builtinModules: any; export default Module; + export type ModuleOptions = import("socket:commonjs/module").ModuleOptions; + export type ModuleResolver = import("socket:commonjs/module").ModuleResolver; + export type ModuleLoadOptions = import("socket:commonjs/module").ModuleLoadOptions; + export type RequireFunction = import("socket:commonjs/module").RequireFunction; + export type CreateRequireOptions = import("socket:commonjs/module").CreateRequireOptions; import { createRequire } from "socket:commonjs/module"; import { Module } from "socket:commonjs/module"; import builtins from "socket:commonjs/builtins"; @@ -14560,16 +14836,14 @@ declare module "socket:service-worker" { export { ExtendableEvent }; export { FetchEvent }; export { Environment }; - export { Database }; export { Context }; } export default _default; import { ExtendableEvent } from "socket:service-worker/events"; import { FetchEvent } from "socket:service-worker/events"; import { Environment } from "socket:service-worker/env"; - import { Database } from "socket:service-worker/database"; import { Context } from "socket:service-worker/context"; - export { ExtendableEvent, FetchEvent, Environment, Database, Context }; + export { ExtendableEvent, FetchEvent, Environment, Context }; } declare module "socket:stream-relay" { @@ -15136,27 +15410,6 @@ declare module "socket:internal/post-message" { export default _default; } -declare module "socket:internal/promise" { - export const NativePromise: PromiseConstructor; - export namespace NativePromisePrototype { - export let then: (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise; - let _catch: (onrejected?: (reason: any) => TResult | PromiseLike) => Promise; - export { _catch as catch }; - let _finally: (onfinally?: () => void) => Promise; - export { _finally as finally }; - } - export const NativePromiseAll: any; - export const NativePromiseAny: any; - export default Promise; - var Promise: PromiseConstructor; - interface Promise { - then(onfulfilled?: (value: T) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike): Promise; - catch(onrejected?: (reason: any) => TResult | PromiseLike): Promise; - finally(onfinally?: () => void): Promise; - readonly [Symbol.toStringTag]: string; - } -} - declare module "socket:service-worker/registration" { export class ServiceWorkerRegistration { constructor(info: any, serviceWorker: any); @@ -15200,9 +15453,8 @@ declare module "socket:service-worker/container" { * contains private implementation properties relevant to the runtime * `ServiceWorkerContainer` internal state implementations. * @ignore - * @private */ - private init; + init(): Promise; register(scriptURL: any, options?: any): Promise; getRegistration(clientURL: any): Promise; getRegistrations(): Promise; @@ -15248,6 +15500,43 @@ declare module "socket:internal/scheduler" { import scheduler from "socket:timers/scheduler"; } +declare module "socket:internal/promise" { + export const NativePromise: PromiseConstructor; + export namespace NativePromisePrototype { + export let then: (onfulfilled?: (value: any) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => globalThis.Promise; + let _catch: (onrejected?: (reason: any) => TResult | PromiseLike) => globalThis.Promise; + export { _catch as catch }; + let _finally: (onfinally?: () => void) => globalThis.Promise; + export { _finally as finally }; + } + export const NativePromiseAll: any; + export const NativePromiseAny: any; + export class Promise extends globalThis.Promise { + constructor(...args: any[]); + [resourceSymbol]: { + "__#11@#type": any; + "__#11@#destroyed": boolean; + "__#11@#asyncId": number; + "__#11@#triggerAsyncId": any; + "__#11@#requireManualDestroy": boolean; + readonly type: string; + readonly destroyed: boolean; + asyncId(): number; + triggerAsyncId(): number; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + }; + } + export namespace Promise { + function all(iterable: any): any; + function any(iterable: any): any; + } + export default Promise; + const resourceSymbol: unique symbol; + import * as asyncHooks from "socket:internal/async/hooks"; +} + declare module "socket:internal/timers" { export function setTimeout(callback: any, ...args: any[]): number; export function clearTimeout(timeout: any): any; @@ -15358,7 +15647,6 @@ declare module "socket:internal/pickers" { [keyof]; }>; }; - import { FileSystemHandle } from "socket:fs/web"; } declare module "socket:internal/primitives" { @@ -15533,6 +15821,334 @@ declare const TypedArrayPrototype: any; declare const TypedArray: any; declare const ports: any[]; +declare module "socket:service-worker/storage" { + /** + * A factory for creating storage interfaces. + * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type + * @return {Promise} + */ + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise; + /** + * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult + */ + /** + * An iterator interface for an `Index` instance. + */ + export class IndexIterator { + /** + * `IndexIterator` class constructor. + * @ignore + * @param {Index} index + */ + constructor(index: Index); + /** + * `true` if the iterator is "done", otherwise `false`. + * @type {boolean} + */ + get done(): boolean; + /** + * Returns the next `IndexIteratorResult`. + * @return {IndexIteratorResult} + */ + next(): IndexIteratorResult; + /** + * Mark `IndexIterator` as "done" + * @return {IndexIteratorResult} + */ + return(): IndexIteratorResult; + #private; + } + /** + * A container used by the `Provider` to index keys and values + */ + export class Index { + /** + * A reference to the keys in this index. + * @type {string[]} + */ + get keys(): string[]; + /** + * A reference to the values in this index. + * @type {string[]} + */ + get values(): string[]; + /** + * The number of entries in this index. + * @type {number} + */ + get length(): number; + /** + * Returns the key at a given `index`, if it exists otherwise `null`. + * @param {number} index} + * @return {string?} + */ + key(index: number): string | null; + /** + * Returns the value at a given `index`, if it exists otherwise `null`. + * @param {number} index} + * @return {string?} + */ + value(index: number): string | null; + /** + * Inserts a value in the index. + * @param {string} key + * @param {string} value + */ + insert(key: string, value: string): void; + /** + * Computes the index of a key in this index. + * @param {string} key + * @return {number} + */ + indexOf(key: string): number; + /** + * Clears all keys and values in the index. + */ + clear(): void; + /** + * Returns an entry at `index` if it exists, otherwise `null`. + * @param {number} index + * @return {string[]|null} + */ + entry(index: number): string[] | null; + /** + * Removes entries at a given `index`. + * @param {number} index + * @return {boolean} + */ + remove(index: number): boolean; + /** + * Returns an array of computed entries in this index. + * @return {IndexIterator} + */ + entries(): IndexIterator; + /** + * @ignore + * @return {IndexIterator} + */ + [Symbol.iterator](): IndexIterator; + #private; + } + /** + * A base class for a storage provider. + */ + export class Provider { + /** + * An error currently associated with the provider, likely from an + * async operation. + * @type {Error?} + */ + get error(): Error; + /** + * A promise that resolves when the provider is ready. + * @type {Promise} + */ + get ready(): Promise; + /** + * A reference the service worker storage ID, which is the service worker + * registration ID. + * @type {string} + * @throws DOMException + */ + get id(): string; + /** + * A reference to the provider `Index` + * @type {Index} + * @throws DOMException + */ + get index(): Index; + /** + * The number of entries in the provider. + * @type {number} + * @throws DOMException + */ + get length(): number; + /** + * Returns `true` if the provider has a value for a given `key`. + * @param {string} key} + * @return {boolean} + * @throws DOMException + */ + has(key: string): boolean; + /** + * Get a value by `key`. + * @param {string} key + * @return {string?} + * @throws DOMException + */ + get(key: string): string | null; + /** + * Sets a `value` by `key` + * @param {string} key + * @param {string} value + * @throws DOMException + */ + set(key: string, value: string): void; + /** + * Removes a value by `key`. + * @param {string} key + * @return {boolean} + * @throws DOMException + */ + remove(key: string): boolean; + /** + * Clear all keys and values. + * @throws DOMException + */ + clear(): void; + /** + * The keys in the provider index. + * @return {string[]} + * @throws DOMException + */ + keys(): string[]; + /** + * The values in the provider index. + * @return {string[]} + * @throws DOMException + */ + values(): string[]; + /** + * Returns the key at a given `index` + * @param {number} index + * @return {string|null} + * @throws DOMException + */ + key(index: number): string | null; + /** + * Loads the internal index with keys and values. + * @return {Promise} + */ + load(): Promise; + #private; + } + /** + * An in-memory storage provider. It just used the built-in provider `Index` + * for storing key-value entries. + */ + export class MemoryStorageProvider extends Provider { + } + /** + * A session storage provider that persists for the runtime of the + * application and through service worker restarts. + */ + export class SessionStorageProvider extends Provider { + /** + * Remove a value by `key`. + * @param {string} key + * @return {string?} + * @throws DOMException + * @throws NotFoundError + */ + remove(key: string): string | null; + } + /** + * A local storage provider that persists until the data is cleared. + */ + export class LocalStorageProvider extends Provider { + } + /** + * A generic interface for storage implementations + */ + export class Storage { + /** + * A factory for creating a `Storage` instance that is backed + * by a storage provider. Extending classes should define a `Provider` + * class that is statically available on the extended `Storage` class. + * @param {symbol} token + * @return {Promise>} + */ + static create(token: symbol): Promise; + /** + * `Storage` class constructor. + * @ignore + * @param {symbol} token + * @param {Provider} provider + */ + constructor(token: symbol, provider: Provider); + /** + * A readonly reference to the storage provider. + * @type {Provider} + */ + get provider(): Provider; + /** + * The number of entries in the storage. + * @type {number} + */ + get length(): number; + /** + * Returns `true` if the storage has a value for a given `key`. + * @param {string} key + * @return {boolean} + * @throws TypeError + */ + hasItem(key: string, ...args: any[]): boolean; + /** + * Clears the storage of all entries + */ + clear(): void; + /** + * Returns the key at a given `index` + * @param {number} index + * @return {string|null} + */ + key(index: number, ...args: any[]): string | null; + /** + * Get a storage value item for a given `key`. + * @param {string} key + * @return {string|null} + */ + getItem(key: string, ...args: any[]): string | null; + /** + * Removes a storage value entry for a given `key`. + * @param {string} + * @return {boolean} + */ + removeItem(key: any, ...args: any[]): boolean; + /** + * Sets a storage item `value` for a given `key`. + * @param {string} key + * @param {string} value + */ + setItem(key: string, value: string, ...args: any[]): void; + /** + * @ignore + */ + get [Symbol.toStringTag](): string; + #private; + } + /** + * An in-memory `Storage` interface. + */ + export class MemoryStorage extends Storage { + static Provider: typeof MemoryStorageProvider; + } + /** + * A locally persisted `Storage` interface. + */ + export class LocalStorage extends Storage { + static Provider: typeof LocalStorageProvider; + } + /** + * A session `Storage` interface. + */ + export class SessionStorage extends Storage { + static Provider: typeof SessionStorageProvider; + } + namespace _default { + export { Storage }; + export { LocalStorage }; + export { MemoryStorage }; + export { SessionStorage }; + export { createStorageInterface }; + } + export default _default; + export type IndexIteratorResult = { + done: boolean; + value: string | undefined; + }; +} + declare module "socket:service-worker/worker" { export {}; } From 357522b60d0f32a5e1c664f9c734f7c955157ba1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:09:38 -0400 Subject: [PATCH 0551/1178] refactor(api/commonjs): improve loader cache --- api/commonjs/cache.js | 27 +++++++++++++++++++-------- api/commonjs/loader.js | 4 +++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index 0181e49fb3..b7a1a165e5 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -67,6 +67,7 @@ export class Storage extends EventTarget { } #database = null + #opening = false #context = {} #proxy = null #ready = null @@ -89,7 +90,7 @@ export class Storage extends EventTarget { set: (_, property, value) => { this.#context[property] = value if (this.database && this.database.opened) { - this.forwardRequest(this.database.put(property, value)) + this.forwardRequest(this.database.put(property, value, { durability: 'relaxed' })) } return true }, @@ -136,7 +137,15 @@ export class Storage extends EventTarget { * @type {boolean} */ get opened () { - return this.#database !== null + return this.#database?.opened === true + } + + /** + * `true` if the storage is opening, otherwise `false`. + * @type {boolean} + */ + get opening () { + return this.#opening } /** @@ -192,8 +201,8 @@ export class Storage extends EventTarget { /** * Synchronizes database entries into the storage context. */ - async sync () { - const entries = await this.#database.get(undefined, { + async sync (options = null) { + const entries = await this.#database.get(options?.query ?? undefined, { count: Storage.MAX_CONTEXT_ENTRIES }) @@ -211,7 +220,7 @@ export class Storage extends EventTarget { for (const key of delta) { const value = this.#context[key] - promises.push(this.forwardRequest(this.database.put(key, value))) + promises.push(this.forwardRequest(this.database.put(key, value, { durability: 'relaxed' }))) } await Promise.all(promises) @@ -221,11 +230,13 @@ export class Storage extends EventTarget { * Opens the storage. * @ignore */ - async open () { - if (!this.#database) { + async open (options = null) { + if (!this.opening && !this.#database) { + this.#opening = true this.#ready = new Deferred() this.#database = await database.open(this.name) - await this.sync() + await this.sync(options?.sync ?? null) + this.#opening = false this.#ready.resolve() } diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 45c2a4f337..56b129bbd3 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -442,7 +442,9 @@ export class Request { // @ts-ignore responseText = request.responseText // can throw `InvalidStateError` error } catch { - responseText = request.response + if (typeof request.response === 'string') { + responseText = request.response + } } return new Response(this, { From 69a096310feebc04d2bb9da1f5ab985d55ba3a0e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:10:05 -0400 Subject: [PATCH 0552/1178] refactor(api/internal/database.js): allow 'durability' to be configurable --- api/internal/database.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/internal/database.js b/api/internal/database.js index a916d92866..38cb0a695d 100644 --- a/api/internal/database.js +++ b/api/internal/database.js @@ -27,7 +27,8 @@ import gc from '../gc.js' * * @typedef {{ * store?: string | undefined, - * stores?: string[] | undefined + * stores?: string[] | undefined, + * durability?: 'strict' | 'relaxed' | undefined * }} DatabasePutOptions */ @@ -714,7 +715,7 @@ export class Database extends EventTarget { const transaction = this.#storage.transaction( options?.store ?? options?.stores ?? this.name, 'readwrite', - { durability: 'strict' } + { durability: optinos?.durability ?? 'strict' } ) if (options?.store) { @@ -762,7 +763,7 @@ export class Database extends EventTarget { const transaction = this.#storage.transaction( options?.store ?? options?.stores ?? this.name, 'readwrite', - { durability: 'strict' } + { durability: optinos?.durability ?? 'strict' } ) if (options?.store) { From cc41c6d3a12b104be57c5d4776569b81012bbc84 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:11:06 -0400 Subject: [PATCH 0553/1178] refactor(api/internal/worker.js): support 'importScripts' in module mode --- api/internal/worker.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/internal/worker.js b/api/internal/worker.js index ef6d1d367b..1410ff9e63 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -1,5 +1,6 @@ /* global reportError, EventTarget, CustomEvent, MessageEvent */ import { rand64 } from '../crypto.js' +import { Loader } from '../commonjs/loader.js' import globals from './globals.js' import hooks from '../hooks.js' import ipc from '../ipc.js' @@ -172,6 +173,12 @@ Object.defineProperties(WorkerGlobalScopePrototype, { configurable: false, enumerable: false, value: postMessage + }, + + importScripts: { + configurable: false, + enumerable: false, + value: importScripts } }) @@ -262,10 +269,22 @@ export function close () { return worker.close() } +export function importScripts (...scripts) { + const loader = new Loader(source) + for (const script of scripts) { + const response = loader.load(script) + if (response.ok && response.text) { + // eslint-disable-next-line + eval(response.text) + } + } +} + export default { RUNTIME_WORKER_ID, removeEventListener, addEventListener, + importScripts, dispatchEvent, postMessage, source, From 9a7b5b08b58ba1946203ef572967537dae2d2984 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:12:09 -0400 Subject: [PATCH 0554/1178] fix(bin/docs-generator/config.js): fix typo --- bin/docs-generator/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/docs-generator/config.js b/bin/docs-generator/config.js index 1d2aa64366..ffbf9f3516 100644 --- a/bin/docs-generator/config.js +++ b/bin/docs-generator/config.js @@ -37,7 +37,7 @@ function parseIni (iniText) { function createConfigMd (sections) { let md = '\n' md += '\n\n' - md += '# Configuration\n', + md += '# Configuration\n' md += '## Overview\n' md += ` From 7eaed04e2e72ae03f6bf539f3406ec680b836935 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:12:27 -0400 Subject: [PATCH 0555/1178] fix(api/internal/database.js): fix typos --- api/internal/database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/internal/database.js b/api/internal/database.js index 38cb0a695d..316ceb4c4f 100644 --- a/api/internal/database.js +++ b/api/internal/database.js @@ -715,7 +715,7 @@ export class Database extends EventTarget { const transaction = this.#storage.transaction( options?.store ?? options?.stores ?? this.name, 'readwrite', - { durability: optinos?.durability ?? 'strict' } + { durability: options?.durability ?? 'strict' } ) if (options?.store) { @@ -763,7 +763,7 @@ export class Database extends EventTarget { const transaction = this.#storage.transaction( options?.store ?? options?.stores ?? this.name, 'readwrite', - { durability: optinos?.durability ?? 'strict' } + { durability: options?.durability ?? 'strict' } ) if (options?.store) { From d194d87fa710e078649131879958447c67a60033 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:13:38 -0400 Subject: [PATCH 0556/1178] refactor(api/internal/worker.js): simplify --- api/internal/worker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/internal/worker.js b/api/internal/worker.js index 1410ff9e63..65a8412a32 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -272,10 +272,10 @@ export function close () { export function importScripts (...scripts) { const loader = new Loader(source) for (const script of scripts) { - const response = loader.load(script) - if (response.ok && response.text) { + const { text, ok } = loader.load(script) + if (ok && text) { // eslint-disable-next-line - eval(response.text) + eval(text) } } } From 58d2bbb07f09c0b3940d53eae774e3e244b0912d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 14:14:57 -0400 Subject: [PATCH 0557/1178] chore(api): generate types --- api/index.d.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 4924a1ab74..fb41891999 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -8731,7 +8731,8 @@ declare module "socket:internal/database" { * * @typedef {{ * store?: string | undefined, - * stores?: string[] | undefined + * stores?: string[] | undefined, + * durability?: 'strict' | 'relaxed' | undefined * }} DatabasePutOptions */ /** @@ -9051,6 +9052,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -11924,6 +11926,11 @@ declare module "socket:commonjs/cache" { * @type {boolean} */ get opened(): boolean; + /** + * `true` if the storage is opening, otherwise `false`. + * @type {boolean} + */ + get opening(): boolean; /** * A proxied object for reading and writing storage state. * Values written to this object must be cloneable with respect to the @@ -11955,12 +11962,12 @@ declare module "socket:commonjs/cache" { /** * Synchronizes database entries into the storage context. */ - sync(): Promise; + sync(options?: any): Promise; /** * Opens the storage. * @ignore */ - open(): Promise; + open(options?: any): Promise; /** * Closes the storage database, purging existing state. * @ignore @@ -15678,6 +15685,7 @@ declare module "socket:internal/worker" { export function dispatchEvent(event: any): any; export function postMessage(message: any, ...args: any[]): any; export function close(): any; + export function importScripts(...scripts: any[]): void; export const WorkerGlobalScopePrototype: any; /** * The absolute `URL` of the internal worker initialization entry. @@ -15712,6 +15720,7 @@ declare module "socket:internal/worker" { export { RUNTIME_WORKER_ID }; export { removeEventListener }; export { addEventListener }; + export { importScripts }; export { dispatchEvent }; export { postMessage }; export { source }; From adbe219785510955101a93fe96dadbae685441a9 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 17:57:22 -0400 Subject: [PATCH 0558/1178] refactor(api/commonjs): drop use of 'sessionStorage', fix 'ModuleScope' --- api/commonjs/cache.js | 84 +----------------------------------------- api/commonjs/module.js | 6 +-- 2 files changed, 5 insertions(+), 85 deletions(-) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index b7a1a165e5..00c063b544 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -255,77 +255,6 @@ export class Storage extends EventTarget { } } -/** - * Computes a commonjs session storage cache key. - * @ignore - * @param {Cache=} [cache] - * @param {string=} [key] - * @return {string} - */ -export function sessionStorageCacheKey (cache = null, key = null) { - if (cache && key) { - return `commonjs:cache:${cache.name}:${key}` - } else if (cache) { - return `commonjs:cache:${cache.name}` - } else { - return 'commonjs:cache' - } -} - -/** - * Restores values in a session storage into the cache. - * @ignore - * @param {Cache} cache - */ -export function restoreFromSessionStorage (cache) { - if ( - globalThis.sessionStorage && - typeof globalThis.sessionStorage === 'object' - ) { - const prefix = `${sessionStorageCacheKey(cache)}:` - for (const cacheKey in globalThis.sessionStorage) { - if (cacheKey.startsWith(prefix)) { - const value = parseJSON(globalThis.sessionStorage[cacheKey]) - const key = cacheKey.replace(prefix, '') - if (value && !cache.has(key)) { - if (value?.__type__) { - cache.data.set(key, cache.types.get(value.__type__).from(value, { - loader: cache.loader - })) - } else { - cache.data.set(key, value) - } - } - } - } - } -} - -/** - * Computes a commonjs session storage cache key. - * @ignore - * @param {Cache} cache - * @param {string} key - */ -export function updateSessionStorage (cache, key) { - if ( - globalThis.sessionStorage && - typeof globalThis.sessionStorage === 'object' - ) { - const cacheKey = sessionStorageCacheKey(cache, key) - if (cache.has(key)) { - const value = cache.get(key) - try { - globalThis.sessionStorage[cacheKey] = ( - JSON.stringify(serialize(value)) - ) - } catch {} - } else { - delete globalThis.sessionStorage[cacheKey] - } - } -} - /** * A container for `Snapshot` data storage. */ @@ -718,26 +647,19 @@ export class Cache { } /** - * Resets the cache map, persisted storage, and session storage. + * Resets the cache map and persisted storage. */ async reset () { const keys = this.keys() this.#data.clear() - for (const key of keys) { - // will call `delete` - updateSessionStorage(this, key) - } - await this.#storage.reset() } /** - * Restores cache data from session storage. + * Restores cache data from storage. */ async restore () { - restoreFromSessionStorage(this) - if (!this.#storage.opened) { await this.#storage.open() } @@ -819,7 +741,6 @@ export class Cache { set (key, value) { this.#data.set(key, value) this.#storage.context[key] = serialize(value) - updateSessionStorage(this, key) return this } @@ -841,7 +762,6 @@ export class Cache { delete (key) { delete this.#storage.context[key] if (this.#data.delete(key)) { - updateSessionStorage(this, key) return true } diff --git a/api/commonjs/module.js b/api/commonjs/module.js index ae20521c3d..db5077a9c9 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -196,7 +196,7 @@ export class ModuleScope { id: this.id, filename: this.filename, children: this.children, - exports: this.#exports + exports: this.exports } } } @@ -541,7 +541,7 @@ export class Module extends EventTarget { } #id = null - #scope = new ModuleScope() + #scope = null #state = new State() #cache = Object.create(null) #loader = null @@ -579,8 +579,8 @@ export class Module extends EventTarget { this.#state = options.state } + this.#scope new ModuleScope(this) this.#loader = new Loader(this.#id, options?.loader) - this.#package = options.package instanceof Package ? options.package : this.#parent?.package ?? new Package(options.name ?? this.#id, options.package) From 0574b5072b6687286b5811b668f3853ccc7ec32e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 17:57:57 -0400 Subject: [PATCH 0559/1178] fix(api/service-worker): fix storage APIs during init, improve startup --- api/service-worker/container.js | 7 ++++--- api/service-worker/global.js | 4 +++- api/service-worker/instance.js | 5 ++--- api/service-worker/worker.js | 14 ++++++++------ 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 39430acc96..faf2e07520 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -47,7 +47,7 @@ class ServiceWorkerContainerRealm { return await realm.init(container) } - async init (container) { + async init () { if (ServiceWorkerContainerRealm.instance) { return } @@ -58,7 +58,7 @@ class ServiceWorkerContainerRealm { return } - const frameId = `__${os.platform()}-service-worker-frame__` + const frameId = '__service-worker-frame__' const existingFrame = globalThis.top.document.querySelector( `iframe[id="${frameId}"]` ) @@ -82,6 +82,7 @@ class ServiceWorkerContainerRealm { })) this.frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') + this.frame.setAttribute('loading', 'eager') this.frame.src = SERVICE_WINDOW_PATH this.frame.id = frameId @@ -97,7 +98,7 @@ class ServiceWorkerContainerRealm { globalThis.top.document ) - target.appendChild(this.frame) + target.prepend(this.frame) await Promise.all(pending) } diff --git a/api/service-worker/global.js b/api/service-worker/global.js index 6bc6e35b1a..d0b17e5ec5 100644 --- a/api/service-worker/global.js +++ b/api/service-worker/global.js @@ -1,6 +1,6 @@ import { ExtendableEvent, FetchEvent } from './events.js' import { ServiceWorkerRegistration } from './registration.js' -import serviceWorker from './instance.js' +import { createServiceWorker } from './instance.js' import clients from './clients.js' import state from './state.js' import ipc from '../ipc.js' @@ -14,6 +14,8 @@ let onfetch = null // this is set one time let registration = null +const serviceWorker = createServiceWorker(state.serviceWorker.state) + export class ServiceWorkerGlobalScope { get isServiceWorkerScope () { return true diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index bed9f0cb61..b862bbaac5 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -1,4 +1,3 @@ -import { SharedWorker } from '../internal/shared-worker.js' import state from './state.js' export const SHARED_WORKER_URL = `${globalThis.origin}/socket/service-worker/shared-worker.js` @@ -20,7 +19,7 @@ export function createServiceWorker ( options = null ) { // client message bus worker - const sharedWorker = new SharedWorker(SHARED_WORKER_URL) + const sharedWorker = new globalThis.SharedWorker(SHARED_WORKER_URL) // events const eventTarget = new EventTarget() @@ -134,4 +133,4 @@ export function createServiceWorker ( return serviceWorker } -export default createServiceWorker(state.serviceWorker.state) +export default createServiceWorker diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 74882acbce..63ea8da727 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -44,12 +44,14 @@ async function onMessage (event) { const { data } = event if (data?.register) { - // preload commonjs cache - await Cache.restore(['loader.status', 'loader.response']) - const { id, scope, scriptURL } = data.register const url = new URL(scriptURL) + if (!url.pathname.startsWith('/socket/')) { + // preload commonjs cache for user space server workers + Cache.restore(['loader.status', 'loader.response']) + } + state.id = id state.serviceWorker.scope = scope state.serviceWorker.scriptURL = scriptURL @@ -132,21 +134,21 @@ async function onMessage (event) { configurable: false, enumerable: false, writable: false, - value: createStorageInterface('localStorage') + value: await createStorageInterface('localStorage') }, sessionStorage: { configurable: false, enumerable: false, writable: false, - value: createStorageInterface('sessionStorage') + value: await createStorageInterface('sessionStorage') }, memoryStorage: { configurable: false, enumerable: false, writable: false, - value: createStorageInterface('memoryStorage') + value: await createStorageInterface('memoryStorage') } }) From 79a80b64900780ef7e9e28ca22fd6c58c2281c91 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 17:58:12 -0400 Subject: [PATCH 0560/1178] refactor(api/vm.js): prepend iframe to head --- api/vm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/vm.js b/api/vm.js index 1150722ff3..e534b221ed 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1227,7 +1227,7 @@ export async function getContextWindow () { globalThis.top.document ) - target.appendChild(frame) + target.prepend(frame) contextWindow.frame = frame contextWindow.ready = new Promise((resolve, reject) => { frame.onload = resolve From d3efcd5543ca6b3afd4799c6082e6dadfe498075 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 17:58:52 -0400 Subject: [PATCH 0561/1178] fix(src/core/preload.cc): fix global commonjs scope --- src/core/preload.cc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/core/preload.cc b/src/core/preload.cc index 27cbf56b86..0b1cec7f47 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -310,12 +310,7 @@ namespace SSC { " configurable: true, \n" " enumerable: false, \n" " writable: false, \n" - " value: Module.main, \n" - " }, \n" - " exports: { \n" - " configurable: true, \n" - " enumerable: false, \n" - " get: () => Module.main.exports, \n" + " value: Module.main.scope, \n" " }, \n" " process: { \n" " configurable: true, \n" From e1a1ac8fdd93922e9f87c9714204590b26bc0284 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 17:59:37 -0400 Subject: [PATCH 0562/1178] fix(api/commonjs/module.js): fix typo --- api/commonjs/module.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/commonjs/module.js b/api/commonjs/module.js index db5077a9c9..e760a28a64 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -579,7 +579,7 @@ export class Module extends EventTarget { this.#state = options.state } - this.#scope new ModuleScope(this) + this.#scope = new ModuleScope(this) this.#loader = new Loader(this.#id, options?.loader) this.#package = options.package instanceof Package ? options.package From 0d8b804e9c65246e55e79abbc4b7a015c7d03f16 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 18:20:38 -0400 Subject: [PATCH 0563/1178] chore(api): generate types --- api/index.d.ts | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index fb41891999..0263651da8 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -9224,8 +9224,7 @@ declare module "socket:service-worker/instance" { removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; - const _default: any; - export default _default; + export default createServiceWorker; } declare module "socket:service-worker/clients" { @@ -11855,27 +11854,6 @@ declare module "socket:internal/serialize" { } declare module "socket:commonjs/cache" { - /** - * Computes a commonjs session storage cache key. - * @ignore - * @param {Cache=} [cache] - * @param {string=} [key] - * @return {string} - */ - export function sessionStorageCacheKey(cache?: Cache | undefined, key?: string | undefined): string; - /** - * Restores values in a session storage into the cache. - * @ignore - * @param {Cache} cache - */ - export function restoreFromSessionStorage(cache: Cache): void; - /** - * Computes a commonjs session storage cache key. - * @ignore - * @param {Cache} cache - * @param {string} key - */ - export function updateSessionStorage(cache: Cache, key: string): void; /** * @typedef {{ * types?: object, @@ -12133,11 +12111,11 @@ declare module "socket:commonjs/cache" { */ get types(): Map; /** - * Resets the cache map, persisted storage, and session storage. + * Resets the cache map and persisted storage. */ reset(): Promise; /** - * Restores cache data from session storage. + * Restores cache data from storage. */ restore(): Promise; /** From 589e31c02354712a19f972793ee6bb209a44193d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 10 Apr 2024 18:21:38 -0400 Subject: [PATCH 0564/1178] refactor(api/commonjs/cache.js): clean up dead code --- api/commonjs/cache.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index 00c063b544..8c4c0c4777 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -4,17 +4,6 @@ import serialize from '../internal/serialize.js' import database from '../internal/database.js' import gc from '../gc.js' -/** - * @ignore - * @param {string?} - * @return {object|null} - */ -function parseJSON (source) { - try { - return JSON.parse(source) - } catch { return null } -} - /** * @typedef {{ * types?: object, @@ -650,9 +639,7 @@ export class Cache { * Resets the cache map and persisted storage. */ async reset () { - const keys = this.keys() this.#data.clear() - await this.#storage.reset() } From 6ad30ac4e87b0cf076a3fc8192255f3835f2f097 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Thu, 11 Apr 2024 12:18:28 +0200 Subject: [PATCH 0565/1178] fix(window): fixes crash when exiter window is deleted before exit of all other windows completes --- api/CONFIG.md | 4 ++-- bin/docs-generator/config.js | 2 +- src/cli/templates.hh | 4 ++-- src/ipc/bridge.cc | 45 ++++++++++++++++++++++-------------- src/window/window.hh | 5 +++- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/api/CONFIG.md b/api/CONFIG.md index ce3fb7c3bf..634fe32ad8 100644 --- a/api/CONFIG.md +++ b/api/CONFIG.md @@ -35,7 +35,7 @@ platform = ios ; override the `ssc build --platform` CLI option [settings.ios] ; override the `[ios]` section in `socket.ini` codesign_identity = "iPhone Developer: John Doe (XXXXXXXXXX)" -distribution_method = "ad-hoc" +distribution_method = "release-testing" provisioning_profile = "johndoe.mobileprovision" simulator_device = "iPhone 15" ``` @@ -155,7 +155,7 @@ icon_sizes | | The various sizes and scales of the icons to create, required m Key | Default Value | Description :--- | :--- | :--- codesign_identity | | signing guide: https://socketsupply.co/guides/#ios-1 -distribution_method | | Describes how Xcode should export the archive. Available options: app-store, package, ad-hoc, enterprise, development, and developer-id. +distribution_method | | Describes how Xcode should export the archive. Available options: app-store, package, release-testing, enterprise, development, and developer-id. provisioning_profile | | A path to the provisioning profile used for signing iOS app. simulator_device | | which device to target when building for the simulator. nonexempt_encryption | false | Indicate to Apple if you are using encryption that is not exempt. diff --git a/bin/docs-generator/config.js b/bin/docs-generator/config.js index ffbf9f3516..d1aad7c3a1 100644 --- a/bin/docs-generator/config.js +++ b/bin/docs-generator/config.js @@ -71,7 +71,7 @@ platform = ios ; override the \`ssc build --platform\` CLI option [settings.ios] ; override the \`[ios]\` section in \`socket.ini\` codesign_identity = "iPhone Developer: John Doe (XXXXXXXXXX)" -distribution_method = "ad-hoc" +distribution_method = "release-testing" provisioning_profile = "johndoe.mobileprovision" simulator_device = "iPhone 15" \`\`\` diff --git a/src/cli/templates.hh b/src/cli/templates.hh index d21668b7ad..7f53bf5594 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1888,8 +1888,8 @@ icon_sizes = "512@1x" ; signing guide: https://socketsupply.co/guides/#ios-1 codesign_identity = "" -; Describes how Xcode should export the archive. Available options: app-store, package, ad-hoc, enterprise, development, and developer-id. -distribution_method = "ad-hoc" +; Describes how Xcode should export the archive. Available options: app-store, package, release-testing, enterprise, development, and developer-id. +distribution_method = "release-testing" ; A path to the provisioning profile used for signing iOS app. provisioning_profile = "" diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 0fbde63477..ff52d30261 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -3964,10 +3964,19 @@ static void registerSchemeHandler (Router *router) { - (void) webView: (SSCBridgedWebView*) webview startURLSchemeTask: (Task) task { static auto fileManager = [[NSFileManager alloc] init]; + + if (!self.router) return; + if (!self.router->core) return; + if (!self.router->bridge) return; + auto userConfig = self.router->bridge->userConfig; + + if (!userConfig.count("meta_bundle_identifier")) return; + const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); + auto rawHeaders = userConfig.count("webview_headers") ? userConfig["webview_headers"] : ""; + const auto webviewHeaders = split(rawHeaders, '\n'); const auto request = task.request; const auto scheme = String(request.URL.scheme.UTF8String); const auto url = String(request.URL.absoluteString.UTF8String); @@ -4021,8 +4030,10 @@ static void registerSchemeHandler (Router *router) { return; } + const bool hasHandler = self.router->core->protocolHandlers.hasHandler(scheme); + // handle 'npm:' and custom protocol schemes - POST/PUT bodies are ignored - if (scheme == "npm" || self.router->core->protocolHandlers.hasHandler(scheme)) { + if (scheme == "npm" || hasHandler) { auto absoluteURL = String(request.URL.absoluteString.UTF8String); auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; @@ -4034,12 +4045,12 @@ static void registerSchemeHandler (Router *router) { if (request.URL.path != nullptr) { fetchRequest.pathname = String(request.URL.path.UTF8String); - fetchRequest.host = userConfig["meta_bundle_identifier"]; + fetchRequest.host = bundleIdentifier; } else if (request.URL.host != nullptr) { fetchRequest.pathname = String("/") + String(request.URL.host.UTF8String); - fetchRequest.host = userConfig["meta_bundle_identifier"]; + fetchRequest.host = bundleIdentifier; } else { - fetchRequest.host = userConfig["meta_bundle_identifier"]; + fetchRequest.host = bundleIdentifier; if (absoluteURL.starts_with(scheme + "://")) { fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + "://", ""); } else if (absoluteURL.starts_with(scheme + ":/")) { @@ -4052,7 +4063,7 @@ static void registerSchemeHandler (Router *router) { if (request.URL.host != nullptr && request.URL.path != nullptr) { fetchRequest.host = String(request.URL.host.UTF8String); } else { - fetchRequest.host = userConfig["meta_bundle_identifier"]; + fetchRequest.host = bundleIdentifier; } if (scheme == "npm") { @@ -4136,7 +4147,7 @@ static void registerSchemeHandler (Router *router) { if (res.statusCode == 0) { @try { [task didFailWithError: [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + errorWithDomain: @(bundleIdentifier.c_str()) code: 1 userInfo: @{NSLocalizedDescriptionKey: @(res.buffer.bytes)} ]]; @@ -4388,7 +4399,7 @@ static void registerSchemeHandler (Router *router) { const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; auto script = self.router->bridge->preload; - if (userConfig["webview_importmap"].size() > 0) { + if (userConfig.count("webview_importmap") > 0 && userConfig["webview_importmap"].size() > 0) { const auto filename = userConfig["webview_importmap"]; const auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath stringByAppendingPathComponent: [NSString @@ -4525,7 +4536,7 @@ static void registerSchemeHandler (Router *router) { if (res.statusCode == 0) { @try { [task didFailWithError: [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + errorWithDomain: @(bundleIdentifier.c_str()) code: 1 userInfo: @{NSLocalizedDescriptionKey: @(res.buffer.bytes)} ]]; @@ -4608,7 +4619,7 @@ static void registerSchemeHandler (Router *router) { @try { [self finalizeTask: task]; [task didFailWithError: [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + errorWithDomain: @(bundleIdentifier.c_str()) code: 1 userInfo: @{NSLocalizedDescriptionKey: @"ServiceWorker request timed out."} ]]; @@ -4801,7 +4812,7 @@ static void registerSchemeHandler (Router *router) { components.scheme = @("socket"); headers[@"content-location"] = components.URL.path; - const auto socketModulePrefix = "socket://" + userConfig["meta_bundle_identifier"] + "/socket/"; + const auto socketModulePrefix = "socket://" + bundleIdentifier + "/socket/"; const auto absoluteURL = String(components.URL.absoluteString.UTF8String); const auto absoluteURLPathExtension = components.URL.pathExtension != nullptr @@ -4817,7 +4828,7 @@ static void registerSchemeHandler (Router *router) { const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; auto script = self.router->bridge->preload; - if (userConfig["webview_importmap"].size() > 0) { + if (userConfig.count("webview_importmap") > 0 && userConfig["webview_importmap"].size() > 0) { const auto filename = Path(userConfig["webview_importmap"]).filename(); const auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath stringByAppendingPathComponent: [NSString @@ -5062,10 +5073,10 @@ static void registerSchemeHandler (Router *router) { ]; if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; + #if !__has_feature(objc_arc) + [response release]; + #endif + return; } [task didReceiveResponse: response]; @@ -5090,7 +5101,7 @@ static void registerSchemeHandler (Router *router) { } #if !__has_feature(objc_arc) - [response release]; + if (response != nullptr) [response release]; #endif } @catch (::id e) {} }); diff --git a/src/window/window.hh b/src/window/window.hh index dbc7dfde95..dfc1469ebc 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -560,6 +560,7 @@ namespace SSC { void destroyWindow (Window* window) { Lock lock(this->mutex); if (destroyed) return; + if (window != nullptr && windows[window->index] != nullptr) { auto metadata = reinterpret_cast(window); inits[window->index] = false; @@ -573,7 +574,9 @@ namespace SSC { window->kill(); } - delete window; + if (!window->opts.canExit) { + delete window; + } } } From 14661c8efdd49faf72d429d0f66595250a697648 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Thu, 11 Apr 2024 13:07:25 +0200 Subject: [PATCH 0566/1178] fix(cli): require running after packaging --- src/cli/cli.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 4d39ba3343..61ee6ae3bd 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2713,7 +2713,6 @@ int main (const int argc, const char* argv[]) { log("Please use reverse DNS notation (https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleidentifier#discussion)"); exit(1); } - } String argvForward = ""; @@ -2738,6 +2737,11 @@ int main (const int argc, const char* argv[]) { flagShouldPackage = true; } + if (flagShouldRun && flagShouldPackage) { + log("ERROR: use the 'run' command after packaging."); + exit(1); + } + if (flagBuildTest && testFile.size() == 0) { log("ERROR: --test value is required."); exit(1); From a88bd3fede2199115eda51ef0da36d00f714ff98 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:24:34 -0400 Subject: [PATCH 0567/1178] refactor(api/location.js): make 'location' dynamic --- api/location.js | 77 +++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/api/location.js b/api/location.js index cb1a5fd3bd..eb41584385 100644 --- a/api/location.js +++ b/api/location.js @@ -1,42 +1,49 @@ -export const globalLocation = globalThis.location ?? { - origin: 'socket:///', - host: '', - hostname: '', - pathname: '/', - href: '' -} +export class Location { + get url () { + return globalThis.location.href.startsWith('blob:') + ? new URL(globalThis.RUNTIME_WORKER_LOCATION || globalThis.location.pathname) + : new URL(globalThis.location.href) + } -export const href = globalLocation.href - .replace(/https?:/, 'socket:') - .replace(/^blob:/, '') - .replace(/socket:\/\/?/, 'socket://') + get protocol () { + return 'socket:' + } -export const protocol = 'socket:' -export const hostname = ( - // eslint-disable-next-line - href.match(/^[a-zA-Z]+:[\/]{2}?(.*)(\/.*)$/) ?? [] -)[1] ?? '' + get host () { + return this.url.host + } -export const host = hostname -export const search = href.split('?')[1] ?? '' -export const hash = href.split('#')[1] ?? '' -export const pathname = globalLocation.protocol === 'blob:' - ? '/' - : href.slice(href.indexOf(hostname) + hostname.length) + get hostname () { + return this.url.hostname + } -export const origin = `${protocol}//${(host + pathname).replace(/\/\//g, '/')}` + get port () { + return this.url.port + } -export function toString () { - return href -} + get pathname () { + return this.url.pathname + } + + get search () { + return this.url.search + } + + get origin () { + return this.url.origin + } -export default { - origin, - href, - protocol, - hostname, - host, - search, - pathname, - toString + get href () { + return this.url.hlref + } + + get hash () { + return this.url.hash + } + + toString () { + return this.href + } } + +export default new Location() From 40d5227d0a1b826986604da10ff333c1ec663af1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:25:03 -0400 Subject: [PATCH 0568/1178] fix(api/commonjs): fix package name parser --- api/commonjs/builtins.js | 4 ++-- api/commonjs/package.js | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index 97dfa6f16b..006bbac027 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -201,7 +201,7 @@ export function getBuiltin (name, options = null) { !originalName.startsWith('socket:') ) { throw new ModuleNotFoundError( - `Cannnot find builtin module '${originalName}` + `Cannot find builtin module '${originalName}` ) } @@ -210,7 +210,7 @@ export function getBuiltin (name, options = null) { } throw new ModuleNotFoundError( - `Cannnot find builtin module '${originalName}` + `Cannot find builtin module '${originalName}` ) } diff --git a/api/commonjs/package.js b/api/commonjs/package.js index e8b981fd38..6c081c5f81 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -149,6 +149,12 @@ export class Name { let pathname = url.pathname.replace(new URL(origin).pathname, '') + if (isRelative && pathname.startsWith('/') && input.startsWith('.')) { + pathname = `./${pathname.slice(1)}` + } else if (pathname.startsWith('/')) { + pathname = pathname.slice(1) + } + // manifest was given in name, just use the directory name if (pathname.endsWith(`/${manifest}`)) { hasManifest = true @@ -245,7 +251,7 @@ export class Name { return { organization: null, - name: name ?? pathname, + name: name || pathname, version, pathname: name && pathname ? pathname : null, url, From 3a20bb974ab914b131e31eedf76eff44cb75b36f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:25:31 -0400 Subject: [PATCH 0569/1178] refactor(api/internal/init.js): fix how worker locations and imports resolve --- api/internal/init.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 7c87a3ceea..f4bd799c25 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -240,7 +240,7 @@ class RuntimeWorker extends GlobalWorker { options = { ...options } const workerType = options[Symbol.for('socket.runtime.internal.worker.type')] ?? 'worker' - const url = encodeURIComponent(new URL(filename, location.href || '/').toString()) + const url = encodeURIComponent(new URL(filename, globalThis.location.href).toString()) const id = String(rand64()) const preload = ` @@ -298,14 +298,14 @@ class RuntimeWorker extends GlobalWorker { } try { - await import('${globalThis.location.protocol}//${globalThis.location.hostname}/socket/internal/init.js') - const hooks = await import('${globalThis.location.protocol}//${globalThis.location.hostname}/socket/hooks.js') + await import('${location.origin}/socket/internal/init.js') + const hooks = await import('${location.origin}/socket/hooks.js') hooks.onReady(() => { globalThis.removeEventListener('message', onInitialWorkerMessages) }) - await import('${globalThis.location.protocol}//${globalThis.location.hostname}/socket/internal/worker.js?source=${url}') + await import('${location.origin}/socket/internal/worker.js?source=${url}') } catch (err) { globalThis.reportError(err) } From bdc9e61e66aea4e987bf43cf3cb3d90cb64578f1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:25:57 -0400 Subject: [PATCH 0570/1178] refactor(api/service-worker): improve debug experience --- api/service-worker/index.html | 21 +++++++++++++++++++++ api/service-worker/init.js | 18 ++++++++++++++++-- api/service-worker/state.js | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/api/service-worker/index.html b/api/service-worker/index.html index 43a85b141e..80b01585ba 100644 --- a/api/service-worker/index.html +++ b/api/service-worker/index.html @@ -62,6 +62,10 @@ } } + summary::-webkit-details-marker { + display: none; + } + nav { -webkit-touch-callout: none; -webkit-user-select: none; @@ -195,6 +199,23 @@ } } } + + & details { + &[open] span { + opacity: 1; + transition: all 0.05s ease; + & code { + color: rgb(225, 225, 225); + opacity: 1; + transition: all 0.025s ease; + } + } + + & > summary { + cursor: pointer; + list-style: none; + } + } } } diff --git a/api/service-worker/init.js b/api/service-worker/init.js index 7255dd4f4a..d1ed4741f8 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -63,14 +63,28 @@ export async function onRegister (event) { for (const entry of event.data.__service_worker_debug) { const lines = entry.split('\n') const span = document.createElement('span') + let target = span + for (const line of lines) { if (!line) continue const item = document.createElement('code') item.innerHTML = line .replace(/\s/g, ' ') - .replace(/(Error|TypeError|SyntaxError|ReferenceError|RangeError)/g, '$1') - span.appendChild(item) + .replace(/\\s/g, ' ') + .replace(//g, '<anonymous>') + .replace(/([a-z|A-Z|_|0-9]+(Error|Exception)):/g, '$1:') + + if (target === span && lines.length > 1) { + target = document.createElement('details') + const summary = document.createElement('summary') + summary.appendChild(item) + target.appendChild(summary) + span.appendChild(target) + } else { + target.appendChild(item) + } } + log.appendChild(span) } diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 89e9df9c28..641987a7a7 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -28,7 +28,7 @@ const descriptors = { if (this.id && type === 'serviceWorker') { debug( '[%s]: ServiceWorker (%s) updated state to "%s"', - this.serviceWorker.scriptURL, + new URL(this.serviceWorker.scriptURL).pathname.replace(/^\/socket\//, 'socket:'), this.id, this.serviceWorker.state ) From 8f72adf1db17b0bb5bf1b22dab1533c76d910250 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:27:39 -0400 Subject: [PATCH 0571/1178] refactor(api/npm/service-worker.js): more debug --- api/npm/service-worker.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index f269c95f77..9c0b848f08 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -1,8 +1,11 @@ import { resolve } from './module.js' import process from '../process.js' +import debug from '../service-worker/debug.js' import http from '../http.js' import util from '../util.js' +const DEBUG_LABEL = ' npm' + export async function onRequest (request, env, ctx) { if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { console.debug(request.url) @@ -14,6 +17,8 @@ export async function onRequest (request, env, ctx) { const specifier = url.pathname.replace('/socket/npm/', '') const importOrigins = url.searchParams.getAll('origin').concat(url.searchParams.getAll('origin[]')) + debug(`${DEBUG_LABEL}: fetch: %s`, specifier) + let resolved = null let origins = [] @@ -75,11 +80,13 @@ export async function onRequest (request, env, ctx) { // not found if (!resolved) { if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { - console.debug('not found: npm:%s', specifier) + console.debug('not found: npm: %s', specifier) } return } + debug(`${DEBUG_LABEL}: resolve: %s (%s): %s`, specifier, resolved.type, resolved.url) + if (resolved.type === 'module') { const response = await fetch(resolved.url) const text = await response.text() From a96c32a417c7e50f6ed4f1f07200c34914fbfbef Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:28:06 -0400 Subject: [PATCH 0572/1178] refactor(api/util.js): trim error stack trace --- api/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/util.js b/api/util.js index 71f73053da..028867ea0f 100644 --- a/api/util.js +++ b/api/util.js @@ -716,7 +716,7 @@ export function inspect (value, options) { output.push('') } - output = output.filter(Boolean) + output = output.map((entry) => entry.trim()).filter(Boolean) if (output.length) { output.unshift(' at') From 760815500aae5640f0d7156dad6dec9e0fd216bd Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:28:23 -0400 Subject: [PATCH 0573/1178] feat(src/core/types.hh): introduce 'Set' alias type --- src/core/types.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/types.hh b/src/core/types.hh index 167e460ee7..f431d248c2 100644 --- a/src/core/types.hh +++ b/src/core/types.hh @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ namespace SSC { template using Queue = std::queue; template using Vector = std::vector; template using Function = std::function; + template using Set = std::set; using ExitCallback = Function; using MessageCallback = Function; From d06889fdd540cfc70bf1f751b7e2c2f88740b1f2 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:30:30 -0400 Subject: [PATCH 0574/1178] refactor(src/ipc/bridge.cc): handle custom protocols on linux --- src/ipc/bridge.cc | 248 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 234 insertions(+), 14 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index ff52d30261..57bc4af59d 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -12,6 +12,7 @@ #include "../extension/extension.hh" #include "../window/window.hh" +#include "../core/protocol_handlers.hh" #include "ipc.hh" #define SOCKET_MODULE_CONTENT_TYPE "text/javascript" @@ -3277,11 +3278,11 @@ static void registerSchemeHandler (Router *router) { auto body = webkit_uri_scheme_request_get_http_body(request); if (body) { GError* error = nullptr; - message.buffer.bytes = bytes; + message.buffer.bytes = new char[MAX_BODY_BYTES]{0}; const auto success = g_input_stream_read_all( body, - bytes, + message.buffer.bytes, MAX_BODY_BYTES, &message.buffer.size, nullptr, @@ -3289,6 +3290,7 @@ static void registerSchemeHandler (Router *router) { ); if (!success) { + delete message.buffer.bytes; webkit_uri_scheme_request_finish_error( request, error @@ -3541,11 +3543,11 @@ static void registerSchemeHandler (Router *router) { auto body = webkit_uri_scheme_request_get_http_body(request); if (body) { GError* error = nullptr; - fetchRequest.buffer.bytes = bytes; + fetchRequest.buffer.bytes = new char[MAX_BODY_BYTES]{0}; const auto success = g_input_stream_read_all( body, - bytes, + fetchRequest.buffer.bytes, MAX_BODY_BYTES, &fetchRequest.buffer.size, nullptr, @@ -3553,6 +3555,7 @@ static void registerSchemeHandler (Router *router) { ); if (!success) { + delete fetchRequest.buffer.bytes; webkit_uri_scheme_request_finish_error( request, error @@ -3622,6 +3625,10 @@ static void registerSchemeHandler (Router *router) { if (fetched) { return; + } else { + if (fetchRequest.buffer.bytes != nullptr) { + delete fetchRequest.buffer.bytes; + } } } } @@ -4032,7 +4039,7 @@ static void registerSchemeHandler (Router *router) { const bool hasHandler = self.router->core->protocolHandlers.hasHandler(scheme); - // handle 'npm:' and custom protocol schemes - POST/PUT bodies are ignored + // handle 'npm:' and custom protocol schemes if (scheme == "npm" || hasHandler) { auto absoluteURL = String(request.URL.absoluteString.UTF8String); auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; @@ -5952,22 +5959,26 @@ namespace SSC::IPC { registerSchemeHandler(this); #if SSC_PLATFORM_LINUX - ProtocolHandlers::Mapping protocolHandlerMappings; + ProtocolHandlers::Mapping protocolHandlerMappings = { + {"npm", ProtocolHandlers::Protocol { "npm" }} + }; + + static Set registeredProtocolHandlerMappings; - for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { + for (const auto& entry : split(bridge->userConfig["webview_protocol-handlers"], " ")) { const auto scheme = replace(trim(entry), ":", ""); - protocolHandlerMappings.insert_or_assign(scheme, { scheme }); + protocolHandlerMappings.insert_or_assign(scheme, ProtocolHandlers::Protocol { scheme }); } - for (const auto& entry : opts.userConfig) { + for (const auto& entry : bridge->userConfig) { const auto& key = entry.first; if (key.starts_with("webview_protocol-handlers_")) { const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; const auto data = entry.second; if (data.starts_with(".") || data.starts_with("/")) { - protocolHandlerMappings.insert_or_assign(scheme, { scheme }); + protocolHandlerMappings.insert_or_assign(scheme, ProtocolHandlers::Protocol { scheme }); } else { - protocolHandlerMappings.insert_or_assign(scheme, { scheme, { data } }); + protocolHandlerMappings.insert_or_assign(scheme, ProtocolHandlers::Protocol { scheme, { data } }); } } } @@ -5975,20 +5986,229 @@ namespace SSC::IPC { for (const auto& entry : protocolHandlerMappings) { const auto& scheme = entry.first; const auto& data = entry.second.data; + + if (registeredProtocolHandlerMappings.contains(scheme)) { + continue; + } + // manually handle NPM here - if (scheme == "npm" || app.core->protocolHandlers.registerHandler(scheme, data)) { + if (scheme == "npm" || bridge->core->protocolHandlers.registerHandler(scheme, data)) { + auto ctx = webkit_web_context_get_default(); + auto security = webkit_web_context_get_security_manager(ctx); + registeredProtocolHandlerMappings.insert(scheme); webkit_security_manager_register_uri_scheme_as_display_isolated(security, scheme.c_str()); webkit_security_manager_register_uri_scheme_as_cors_enabled(security, scheme.c_str()); webkit_security_manager_register_uri_scheme_as_secure(security, scheme.c_str()); webkit_security_manager_register_uri_scheme_as_local(security, scheme.c_str()); webkit_web_context_register_uri_scheme(ctx, scheme.c_str(), [](auto request, auto ptr) { - // auto protocol = ... + static const auto MAX_BODY_BYTES = 4 * 1024 * 1024; + IPC::Router* router = nullptr; + String protocol; + String pathname; + + auto webview = webkit_uri_scheme_request_get_web_view(request); + + for (auto& window : App::instance()->getWindowManager()->windows) { + if ( + window != nullptr && + window->bridge != nullptr && + WEBKIT_WEB_VIEW(window->webview) == webview + ) { + router = &window->bridge->router; + break; + } + } + + if (!router) { + auto userConfig = SSC::getUserConfig(); + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Missing router in request" + ) + ); + } + + auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; + auto userConfig = router->bridge->userConfig; + auto headers = webkit_uri_scheme_request_get_http_headers(request); + auto method = String(webkit_uri_scheme_request_get_http_method(request)); + auto uri = String(webkit_uri_scheme_request_get_uri(request)); + auto cwd = getcwd(); + + if (uri.find_first_of(":") != String::npos) { + protocol = uri.substr(0, uri.find_first_of(":")); + } + + fetchRequest.client.id = router->bridge->id; + fetchRequest.client.preload = router->bridge->preload; + fetchRequest.method = method; + fetchRequest.scheme = protocol; + + pathname = uri.substr(protocol.size() + 1, uri.size()); + + if (pathname.starts_with("//")) { + pathname = pathname.substr(2, pathname.size()); + if (pathname.find_first_of("/") != String::npos) { + fetchRequest.host = pathname.substr(0, pathname.find_first_of("/")); + pathname = pathname.substr(pathname.find_first_of("/"), pathname.size()); + } else { + fetchRequest.host = pathname; + pathname = "/"; + } + } else { + fetchRequest.host = userConfig["meta_bundle_identifier"]; + } + + if (!pathname.starts_with("/")) { + pathname = String("/") + pathname; + } + + // handle internal `npm:` protocol scheme + if (protocol == "npm") { + pathname = String("/socket/npm") + pathname; + } + + const auto scope = router->core->protocolHandlers.getServiceWorkerScope(protocol); + const auto parsed = Router::parseURL(pathname); + + fetchRequest.query = parsed.queryString; + + if (scope.size() > 0) { + fetchRequest.pathname = scope + parsed.path; + } else { + fetchRequest.pathname = parsed.path; + } + + soup_message_headers_foreach( + headers, + [](auto name, auto value, auto userData) { + auto fetchRequest = reinterpret_cast(userData); + const auto entry = String(name) + ": " + String(value); + fetchRequest->headers.push_back(entry); + }, + &fetchRequest + ); + + if (method == "POST" || method == "PUT") { + auto body = webkit_uri_scheme_request_get_http_body(request); + if (body) { + GError* error = nullptr; + fetchRequest.buffer.bytes = new char[MAX_BODY_BYTES]{0}; + + const auto success = g_input_stream_read_all( + body, + fetchRequest.buffer.bytes, + MAX_BODY_BYTES, + &fetchRequest.buffer.size, + nullptr, + &error + ); + + if (!success) { + delete fetchRequest.buffer.bytes; + webkit_uri_scheme_request_finish_error( + request, + error + ); + return; + } + } + } + + const auto fetched = router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { + if (res.statusCode == 0) { + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "%.*s", + (int) res.buffer.size, + res.buffer.bytes + ) + ); + return; + } + + const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); + auto stream = g_memory_input_stream_new_from_data(res.buffer.bytes, res.buffer.size, 0); + + if (!stream) { + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Failed to create response stream" + ) + ); + return; + } + + auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); + auto response = webkit_uri_scheme_response_new(stream, (gint64) res.buffer.size); + + for (const auto& line : webviewHeaders) { + auto pair = split(trim(line), ':'); + auto key = trim(pair[0]); + auto value = trim(pair[1]); + soup_message_headers_append(headers, key.c_str(), value.c_str()); + } + + for (const auto& line : res.headers) { + auto pair = split(trim(line), ':'); + auto key = trim(pair[0]); + auto value = trim(pair[1]); + + if (key == "content-type" || key == "Content-Type") { + webkit_uri_scheme_response_set_content_type(response, value.c_str()); + } + + soup_message_headers_append(headers, key.c_str(), value.c_str()); + } + + webkit_uri_scheme_response_set_http_headers(response, headers); + webkit_uri_scheme_request_finish_with_response(request, response); + + g_object_unref(stream); + }); + + if (fetched) { + return; + } else { + if (fetchRequest.buffer.bytes != nullptr) { + delete fetchRequest.buffer.bytes; + } + } + + auto stream = g_memory_input_stream_new_from_data(nullptr, 0, 0); + + if (!stream) { + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Failed to create response stream" + ) + ); + return; + } + + auto response = webkit_uri_scheme_response_new(stream, 0); + + webkit_uri_scheme_response_set_status(response, 404, "Not found"); + webkit_uri_scheme_request_finish_with_response(request, response); + g_object_unref(stream); }, nullptr, 0); } } -#endif + #endif this->preserveCurrentTable(); From 1c8a71e211cabd62b14b7ca249002e82bf3a82da Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:30:48 -0400 Subject: [PATCH 0575/1178] fix(src/window/linux.cc): fix applicatin protocol policy check --- src/window/apple.mm | 18 ------------------ src/window/linux.cc | 6 +++++- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 350815be72..76cc74746e 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -1035,24 +1035,6 @@ - (void) webView: (WKWebView*) webView } } -/* -TODO(@jwerle): figure out if this is even needed anymore? - [config.processPool - performSelector: @selector(_registerURLSchemeAsSecure:) - withObject: @"socket" - ]; - - [config.processPool - performSelector: @selector(_registerURLSchemeAsSecure:) - withObject: @"ipc" - ]; - - [config.processPool - performSelector: @selector(_registerURLSchemeAsSecure:) - withObject: @"node" - ]; -*/ - static const auto devHost = SSC::getDevHost(); if (devHost.starts_with("http:")) { [config.processPool diff --git a/src/window/linux.cc b/src/window/linux.cc index 2ed3c24ea1..9ae8abf75b 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -434,8 +434,12 @@ namespace SSC { const auto action = webkit_navigation_policy_decision_get_navigation_action(nav); const auto req = webkit_navigation_action_get_request(action); const auto uri = String(webkit_uri_request_get_uri(req)); + const auto applicationProtocol = window->opts.userConfig["meta_application_protocol"]; - if (uri.starts_with(window->opts.userConfig["meta_application_protocol"])) { + if ( + applicationProtocol.size() > 0 && + uri.starts_with(window->opts.userConfig["meta_application_protocol"]) + ) { webkit_policy_decision_ignore(decision); if (window != nullptr && window->bridge != nullptr) { From 0104c1da6467ba163848c00c213ebc1244201a35 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 11 Apr 2024 18:31:50 -0400 Subject: [PATCH 0576/1178] chore(api): generate types --- api/index.d.ts | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 0263651da8..44b0aeabd0 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -2471,32 +2471,20 @@ declare module "socket:url" { } declare module "socket:location" { - export function toString(): string; - export const globalLocation: Location | { - origin: string; - host: string; - hostname: string; - pathname: string; - href: string; - }; - export const href: string; - export const protocol: "socket:"; - export const hostname: string; - export const host: string; - export const search: string; - export const hash: string; - export const pathname: string; - export const origin: string; - namespace _default { - export { origin }; - export { href }; - export { protocol }; - export { hostname }; - export { host }; - export { search }; - export { pathname }; - export { toString }; + export class Location { + get url(): URL; + get protocol(): string; + get host(): string; + get hostname(): string; + get port(): string; + get pathname(): string; + get search(): string; + get origin(): string; + get href(): any; + get hash(): string; + toString(): any; } + const _default: Location; export default _default; } @@ -2900,7 +2888,7 @@ declare module "socket:path/index" { } declare module "socket:path" { - export const sep: "/" | "\\"; + export const sep: "\\" | "/"; export const delimiter: ":" | ";"; export const resolve: typeof posix.win32.resolve; export const join: typeof posix.win32.join; From a884db890f91251158f26560bcd37fb5d86f89a9 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Fri, 12 Apr 2024 01:48:43 -0700 Subject: [PATCH 0577/1178] fix(window): handle light/dark best as possible on linux --- src/window/linux.cc | 59 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 9ae8abf75b..5334d3ed57 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1,4 +1,5 @@ #include "window.hh" +#include static GtkTargetEntry droppableTypes[] = { { (char*) "text/uri-list", 0, 0 } @@ -930,17 +931,59 @@ namespace SSC { bool hasLightValue = this->opts.backgroundColorLight.size(); bool isDarkMode = false; + auto is_kde_dark_mode = []() -> bool { + std::string home = std::getenv("HOME") ? std::getenv("HOME") : ""; + std::string filepath = home + "/.config/kdeglobals"; + std::ifstream file(filepath); + + if (!file.is_open()) { + std::cerr << "Failed to open file: " << filepath << std::endl; + return false; + } + + std::string line; + + while (getline(file, line)) { + std::string lower_line; + + std::transform( + line.begin(), + line.end(), + std::back_inserter(lower_line), + [](unsigned char c) { return std::tolower(c); } + ); + + if (lower_line.find("dark") != std::string::npos) { + std::cout << "Found dark setting in line: " << line << std::endl; + return true; + } + } + + return false; + }; + + auto is_gnome_dark_mode = [&]() -> bool { + GtkStyleContext *context = gtk_widget_get_style_context(window); + + GdkRGBA background_color; + gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, &background_color); + + bool is_dark_theme = (0.299 * background_color.red + + 0.587 * background_color.green + + 0.114 * background_color.blue) < 0.5; + + return FALSE; + }; + if (hasDarkValue || hasLightValue) { GdkRGBA color = {0}; - if (getenv("GNOME_DESKTOP_SESSION_ID") != NULL) { - GSettings *settings = g_settings_new("org.gnome.desktop.interface"); - isDarkMode = g_settings_get_boolean(settings, "gtk-application-prefer-dark-theme"); - } else if (getenv("KDE_SESSION_VERSION") != NULL) { - // - // TODO(@heapwolf): KDE doesnt have a dark or light mode, it just has dark and light themes. - // the only reliable option to determine how to report this is to read the ini file ~/.config/kdeglobals - // + const gchar *desktop_env = getenv("XDG_CURRENT_DESKTOP"); + + if (desktop_env != NULL && g_str_has_prefix(desktop_env, "GNOME")) { + isDarkMode = is_gnome_dark_mode(); + } else { + isDarkMode = is_kde_dark_mode(); } if (isDarkMode && hasDarkValue) { From 5ea97cfd964327787d4267b8f359f5bf5af6d59f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 12 Apr 2024 07:52:47 -0400 Subject: [PATCH 0578/1178] fix(api/location.js): fix typo --- api/location.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/location.js b/api/location.js index eb41584385..7fdecaa625 100644 --- a/api/location.js +++ b/api/location.js @@ -34,7 +34,7 @@ export class Location { } get href () { - return this.url.hlref + return this.url.href } get hash () { From 2241ce08e0012a996f78c8d2c68fafebad7c02d1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 12 Apr 2024 09:43:09 -0400 Subject: [PATCH 0579/1178] refactor(api/service-worker): remove broken shared worker implementation --- api/service-worker/clients.js | 8 +------- api/service-worker/container.js | 12 ++---------- api/service-worker/instance.js | 10 +--------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/api/service-worker/clients.js b/api/service-worker/clients.js index fba3e7dd59..2b57351e6f 100644 --- a/api/service-worker/clients.js +++ b/api/service-worker/clients.js @@ -1,5 +1,3 @@ -import { SHARED_WORKER_URL } from './instance.js' -import { SharedWorker } from '../internal/shared-worker.js' import application from '../application.js' import state from './state.js' @@ -32,15 +30,12 @@ export class Client { #url = null #type = null #frameType = null - #sharedWorker = null constructor (options) { this.#id = options?.id ?? null this.#url = options?.url ?? null this.#type = options?.type ?? null this.#frameType = options?.frameType ?? null - this.#sharedWorker = new SharedWorker(SHARED_WORKER_URL) - this.#sharedWorker.port.start() } get id () { @@ -60,8 +55,7 @@ export class Client { } postMessage (message, optionsOrTransferables = null) { - // TODO(@jwerle) - // return this.#sharedWorker.port.postMessage(message, optionsOrTransferables) + // FIXME(@jwerle) } } diff --git a/api/service-worker/container.js b/api/service-worker/container.js index faf2e07520..2773d48060 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -1,7 +1,6 @@ /* global EventTarget */ -import { createServiceWorker, SHARED_WORKER_URL } from './instance.js' import { ServiceWorkerRegistration } from './registration.js' -import { SharedWorker } from '../internal/shared-worker.js' +import { createServiceWorker } from './instance.js' import { Deferred } from '../async.js' import application from '../application.js' import state from './state.js' @@ -22,7 +21,6 @@ class ServiceWorkerContainerInternalStateMap extends Map { class ServiceWorkerContainerInternalState { currentWindow = null - sharedWorker = null controller = null channel = new BroadcastChannel('socket.runtime.ServiceWorkerContainer') ready = new Deferred() @@ -293,7 +291,6 @@ export class ServiceWorkerContainer extends EventTarget { internal.get(this).ready.then(async (registration) => { if (registration) { internal.get(this).controller = registration.active - internal.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) internal.get(this).currentWindow = await application.getCurrentWindow() } }) @@ -482,12 +479,7 @@ export class ServiceWorkerContainer extends EventTarget { return globalThis.top.navigator.serviceWorker.startMessages() } - internal.get(this).ready.then(() => { - internal.get(this).sharedWorker.port.start() - internal.get(this).sharedWorker.port.addEventListener('message', (event) => { - this.dispatchEvent(new MessageEvent(event.type, event)) - }) - }) + // FIXME(@jwerle) } } diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index b862bbaac5..2798a386fe 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -18,9 +18,6 @@ export function createServiceWorker ( currentState = state.serviceWorker.state, options = null ) { - // client message bus worker - const sharedWorker = new globalThis.SharedWorker(SHARED_WORKER_URL) - // events const eventTarget = new EventTarget() let onstatechange = null @@ -29,17 +26,12 @@ export function createServiceWorker ( // state let scriptURL = options?.scriptURL ?? null - sharedWorker.port.start() - sharedWorker.port.addEventListener('message', (event) => { - eventTarget.dispatchEvent(new MessageEvent('message', event.data)) - }) - const serviceWorker = Object.create(ServiceWorker.prototype, { postMessage: { enumerable: false, configurable: false, value (message, ...args) { - sharedWorker.postMessage(message, ...args) + // FIXME(@jwerle) } }, From 2359d1b4cca9657a8fecc80e3451033ed2f39645 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Fri, 12 Apr 2024 09:43:30 -0400 Subject: [PATCH 0580/1178] chore(api): generate types --- api/index.d.ts | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 44b0aeabd0..037b031d01 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -2480,9 +2480,9 @@ declare module "socket:location" { get pathname(): string; get search(): string; get origin(): string; - get href(): any; + get href(): string; get hash(): string; - toString(): any; + toString(): string; } const _default: Location; export default _default; @@ -9193,28 +9193,6 @@ declare module "socket:service-worker/state" { export default state; } -declare module "socket:service-worker/instance" { - export function createServiceWorker(currentState?: any, options?: any): any; - export const SHARED_WORKER_URL: string; - export const ServiceWorker: { - new (): ServiceWorker; - prototype: ServiceWorker; - } | { - new (): { - onmessage: any; - onerror: any; - onstatechange: any; - readonly state: any; - readonly scriptURL: any; - postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; - }; - }; - export default createServiceWorker; -} - declare module "socket:service-worker/clients" { export class Client { constructor(options: any); @@ -14825,6 +14803,28 @@ declare module "socket:stream-relay" { import def from "socket:stream-relay/index"; } +declare module "socket:service-worker/instance" { + export function createServiceWorker(currentState?: any, options?: any): any; + export const SHARED_WORKER_URL: string; + export const ServiceWorker: { + new (): ServiceWorker; + prototype: ServiceWorker; + } | { + new (): { + onmessage: any; + onerror: any; + onstatechange: any; + readonly state: any; + readonly scriptURL: any; + postMessage(): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + dispatchEvent(event: Event): boolean; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + }; + }; + export default createServiceWorker; +} + declare module "socket:worker" { export default Worker; import { SharedWorker } from "socket:internal/shared-worker"; From 48945063ff721bf1336710e46662d7202a281884 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:27:13 -0400 Subject: [PATCH 0581/1178] refactor(api/console.js): clean up IPC URI --- api/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/console.js b/api/console.js index c82e6414b6..2a19e372fc 100644 --- a/api/console.js +++ b/api/console.js @@ -184,7 +184,7 @@ export class Console { const output = pending.shift() try { const value = encodeURIComponent(output) - const uri = `ipc://${destination}?value=${value}&${extra}&resolve=false` + const uri = `ipc://${destination}?value=${value}&${extra ? extra + '&' : ''}resolve=false` this.postMessage?.(uri) } catch (err) { this.console?.warn?.(`Failed to write to ${destination}: ${err.message}`) From cce6cf96eff69f309174277480d06a3e07f93181 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:27:33 -0400 Subject: [PATCH 0582/1178] fix(api/hooks.js): ensure nested frames receive events from top level --- api/hooks.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/api/hooks.js b/api/hooks.js index c3cf279e16..9dda903d5d 100644 --- a/api/hooks.js +++ b/api/hooks.js @@ -107,13 +107,16 @@ function dispatchReadyEvent (target) { function proxyGlobalEvents (global, target) { for (const type of GLOBAL_EVENTS) { - addEventListener(global, type, (event) => { + const globalObject = GLOBAL_TOP_LEVEL_EVENTS.includes(type) + ? global.top ?? global + : global + + addEventListener(globalObject, type, (event) => { const { type, data, detail = null, error } = event const { origin } = location if (type === 'applicationurl') { dispatchEvent(target, new ApplicationURLEvent(type, { - ...event, origin, data: event.data, url: event.url.toString() @@ -121,7 +124,6 @@ function proxyGlobalEvents (global, target) { } else if (type === 'error' || error) { const { message, filename = import.meta.url || globalThis.location.href } = error || {} dispatchEvent(target, new ErrorEvent(type, { - ...event, message, filename, error, @@ -129,11 +131,11 @@ function proxyGlobalEvents (global, target) { origin })) } else if (data || type === 'message') { - dispatchEvent(target, new MessageEvent(type, { ...event, origin })) + dispatchEvent(target, new MessageEvent(type, event)) } else if (detail) { - dispatchEvent(target, new CustomEvent(type, { ...event, origin })) + dispatchEvent(target, new CustomEvent(type, event)) } else { - dispatchEvent(target, new Event(type, { ...event, origin })) + dispatchEvent(target, new Event(type, event)) } }) } @@ -162,6 +164,18 @@ export const GLOBAL_EVENTS = [ 'unhandledrejection' ] +const GLOBAL_TOP_LEVEL_EVENTS = [ + 'applicationurl', + 'data', + 'languagechange', + 'notificationpresented', + 'notificationresponse', + 'offline', + 'online', + 'permissionchange', + 'unhandledrejection' +] + /** * An event dispatched when the runtime has been initialized. */ @@ -242,7 +256,7 @@ export class Hooks extends EventTarget { * @type {object} */ get global () { - return globalThis || new EventTarget() + return globalThis } /** From 5c4458f3b6c2a4e504b44f384354ab30c03f000b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:28:25 -0400 Subject: [PATCH 0583/1178] refactor(api/http/adapters.js): expose 'event' to 'IncomingMessage' and 'request' event --- api/http/adapters.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/http/adapters.js b/api/http/adapters.js index d738a57001..f23243c71e 100644 --- a/api/http/adapters.js +++ b/api/http/adapters.js @@ -175,6 +175,8 @@ export class ServiceWorkerServerAdapter extends ServerAdapter { url: event.request.url }) + incomingMessage.event = event + const serverResponse = new this.httpInterface.ServerResponse({ request: incomingMessage, server: this.server @@ -190,7 +192,7 @@ export class ServiceWorkerServerAdapter extends ServerAdapter { this.context.run({ connection, incomingMessage, serverResponse, event }, () => { incomingMessage.context.run({ event }, () => { this.server.emit('connection', connection) - this.server.emit('request', incomingMessage, serverResponse) + this.server.emit('request', incomingMessage, serverResponse, event) }) }) }) From 6b616844c9d3f1ed6215ec76f7bb5749ebe40a86 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:28:52 -0400 Subject: [PATCH 0584/1178] refactor(api/internal/init.js): do not await for IPC messages that do not resolve --- api/internal/init.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/internal/init.js b/api/internal/init.js index f4bd799c25..593bd750e7 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -257,24 +257,28 @@ class RuntimeWorker extends GlobalWorker { Object.defineProperty(globalThis, 'isWorkerScope', { configurable: false, enumerable: false, + writable: false, value: true }) Object.defineProperty(globalThis, 'isSocketRuntime', { configurable: false, enumerable: false, + writable: false, value: true }) Object.defineProperty(globalThis, 'RUNTIME_WORKER_ID', { configurable: false, enumerable: false, + writable: false, value: '${id}' }) Object.defineProperty(globalThis, 'RUNTIME_WORKER_TYPE', { configurable: false, enumerable: false, + writable: false, value: '${workerType}' }) @@ -412,7 +416,13 @@ class RuntimeWorker extends GlobalWorker { const transfer = [] const message = ipc.Message.from(request.message, request.bytes) const options = { bytes: message.bytes } - const result = await ipc.send(message.name, message.rawParams, options) + const promise = ipc.send(message.name, message.rawParams, options) + + if (message.get('resolve') === false) { + return + } + + const result = await promise ipc.findMessageTransfers(transfer, result) From 4857f4b0f1d042796e8fdcfdd8ee1cdc409236ce Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:31:01 -0400 Subject: [PATCH 0585/1178] fix(api/internal/permissions.js): fix incorrect 'signal' option --- api/internal/permissions.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/api/internal/permissions.js b/api/internal/permissions.js index 52150a89dd..1261c80c0e 100644 --- a/api/internal/permissions.js +++ b/api/internal/permissions.js @@ -238,7 +238,13 @@ export async function query (descriptor, options) { } } - const result = await ipc.request('permissions.query', { name, signal: options?.signal }) + const { signal } = options || {} + + if (options?.signal) { + delete options.signal + } + + const result = await ipc.request('permissions.query', { name }, { signal }) if (result.err) { throw result.err @@ -316,7 +322,11 @@ export async function request (descriptor, options) { delete options.signal } - const result = await ipc.request('permissions.request', { ...options, name, signal }) + const result = await ipc.request( + 'permissions.request', + { ...options, name }, + { signal } + ) if (result.err) { throw result.err From 97ba384269fda84bd22862a35925191a6bbe1637 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:31:42 -0400 Subject: [PATCH 0586/1178] refactor(api/internal/primitives.js): only set 'geolocation' on navigator when a 'window' exists --- api/internal/primitives.js | 91 +++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 8791e5b7ee..516a692ca9 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -207,6 +207,86 @@ export function init () { globalThis.SpeechRecognition = globalThis.webkitSpeechRecognition } + if (globalThis.RUNTIME_WORKER_TYPE !== 'sharedWorker') { + // globals + install({ + // url + URL, + URLPattern, + URLSearchParams, + + // Promise + Promise, + + // fetch + fetch, + Headers, + Request, + Response, + + // notifications + Notification, + + // pickers + showDirectoryPicker, + showOpenFilePicker, + showSaveFilePicker, + + // events + ApplicationURLEvent, + MenuItemEvent, + SignalEvent, + HotKeyEvent, + + // file + File, + FileSystemHandle, + FileSystemFileHandle, + FileSystemDirectoryHandle, + FileSystemWritableFileStream, + + // buffer + Buffer, + + // workers + SharedWorker, + ServiceWorker, + + // timers + setTimeout, + setInterval, + setImmediate, + clearTimeout, + clearInterval, + clearImmediate, + + // streams + ReadableStream, + ReadableStreamBYOBReader, + ReadableByteStreamController, + ReadableStreamBYOBRequest, + ReadableStreamDefaultController, + ReadableStreamDefaultReader, + WritableStream, + WritableStreamDefaultController, + WritableStreamDefaultWriter, + TransformStream, + TransformStreamDefaultController, + ByteLengthQueuingStrategy, + CountQueuingStrategy, + + // async + AsyncContext, + AsyncResource, + AsyncHook, + AsyncLocalStorage, + Deferred, + + // platform detection + isSocketRuntime: true + }) + } + // globals install({ // url @@ -290,12 +370,11 @@ export function init () { } if (globalThis.navigator) { - // environment navigator - install({ - geolocation, - permissions, - serviceWorker - }, globalThis.navigator, 'navigator') + if (globalThis.window) { + install({ geolocation }, globalThis.navigator, 'geolocation') + } + + install({ permissions, serviceWorker }, globalThis.navigator, 'navigator') // manually install 'navigator.serviceWorker' accessors from prototype Object.defineProperties( From 282b3f7eca7d0f5f53c167cfb9bdbf7755a11fa5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:32:02 -0400 Subject: [PATCH 0587/1178] fix(api/internal/shared-worker.js): set internal worker type to 'sharedWorker' --- api/internal/shared-worker.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/internal/shared-worker.js b/api/internal/shared-worker.js index 481d17fe48..4f36d72b44 100644 --- a/api/internal/shared-worker.js +++ b/api/internal/shared-worker.js @@ -159,7 +159,10 @@ export class SharedHybridWorker extends EventTarget { // id is based on current origin and worker path name this.#id = murmur3(globalThis.origin + this.#url.pathname) - this.#worker = workers.get(this.#id) ?? new Worker(this.#url.toString()) + this.#worker = workers.get(this.#id) ?? new Worker(this.#url.toString(), { + [Symbol.for('socket.runtime.internal.worker.type')]: 'sharedWorker' + }) + this.#channel = new MessageChannel() workers.set(this.#id, this.#worker) From e1505c80e44003b0a66ad2c4c1ca87974a40f35d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:33:32 -0400 Subject: [PATCH 0588/1178] refactor(api/notification.js): make service worker friendly and subscribe to events in BroadcastChannel for state changes --- api/notification.js | 244 ++++++++++++++++++++++++++++++-------------- 1 file changed, 168 insertions(+), 76 deletions(-) diff --git a/api/notification.js b/api/notification.js index 4ab02a17dc..d3cb149d0d 100644 --- a/api/notification.js +++ b/api/notification.js @@ -21,12 +21,6 @@ const NativeNotification = ( class NativeNotification extends EventTarget {} ) -/** - * Used to determine if notification beign created in a `ServiceWorker`. - * @ignore - */ -const isServiceWorkerGlobalScope = typeof globalThis.registration?.active === 'string' - /** * Default number of max actions a notification can perform. * @ignore @@ -263,7 +257,7 @@ export class NotificationOptions { * @param {boolean=} [options.silent = false] * @param {number[]=} [options.vibrate = []] */ - constructor (options = {}) { + constructor (options = {}, allowServiceWorkerGlobalScope = false) { if ('dir' in options) { // @ts-ignore if (!(options.dir in NotificationDirection)) { @@ -305,19 +299,21 @@ export class NotificationOptions { } } - if (this.#actions.length && !isServiceWorkerGlobalScope) { - throw new TypeError( - 'Failed to construct \'Notification\': Actions are only supported ' + - 'for persistent notifications shown using ' + - 'ServiceWorkerRegistration.showNotification().' - ) + if (allowServiceWorkerGlobalScope !== true) { + if (this.#actions.length && globalThis.isServiceWorkerScope) { + throw new TypeError( + 'Failed to construct \'Notification\': Actions are only supported ' + + 'for persistent notifications shown using ' + + 'ServiceWorkerRegistration.showNotification().' + ) + } } - if ('badge' in options && options.badge !== undefined) { + if ('badge' in options && options.badge) { this.#badge = String(new URL(String(options.badge), location.href)) } - if ('body' in options && options.body !== undefined) { + if ('body' in options && options.body) { this.#body = String(options.body) } @@ -325,11 +321,11 @@ export class NotificationOptions { this.#data = options.data } - if ('icon' in options && options.icon !== undefined) { + if ('icon' in options && options.icon) { this.#icon = String(new URL(String(options.icon), location.href)) } - if ('image' in options && options.image !== undefined) { + if ('image' in options && options.image) { this.#image = String(new URL(String(options.image), location.href)) } @@ -421,7 +417,7 @@ export class NotificationOptions { get dir () { return this.#dir } /** - * A string containing the URL of an icon to be displayed in the notification. + A string containing the URL of an icon to be displayed in the notification. * @type {string} */ get icon () { return this.#icon } @@ -479,6 +475,28 @@ export class NotificationOptions { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API#vibration_patterns} */ get vibrate () { return this.#vibrate } + + /** + * @ignore + * @return {object} + */ + toJSON () { + return { + actions: this.#actions, + badge: this.#badge, + body: this.#body, + data: this.#data, + dir: this.#dir, + icon: this.#icon, + image: this.#image, + lang: this.#lang, + renotify: this.#renotify, + requireInteraction: this.#requireInteraction, + silent: this.#silent, + tag: this.#tag, + vibrate: this.#vibrate + } + } } /** @@ -558,9 +576,8 @@ export class Notification extends EventTarget { // any result state changes further updates const controller = new AbortController() // query for 'granted' status and return early - const query = await permissions.query({ - signal: controller.signal, - name: 'notifications' + const query = await permissions.query({ name: 'notifications' }, { + signal: controller.signal }) // if already granted, return early @@ -573,9 +590,8 @@ export class Notification extends EventTarget { // request permission and resolve the normalized `state.permission` value // when the query status changes - const request = await permissions.request({ + const request = await permissions.request({ name: 'notifications' }, { signal: controller.signal, - name: 'notifications', // macOS/iOS only options alert: Boolean(options?.alert !== false), // (defaults to `true`) @@ -611,6 +627,7 @@ export class Notification extends EventTarget { #title = null #id = null + #closed = false #proxy = null /** @@ -618,7 +635,7 @@ export class Notification extends EventTarget { * @param {string} title * @param {NotificationOptions=} [options] */ - constructor (title, options = {}) { + constructor (title, options = {}, existingState = null) { super() if (arguments.length === 0) { @@ -642,7 +659,7 @@ export class Notification extends EventTarget { } try { - this.#options = new NotificationOptions(options) + this.#options = new NotificationOptions(options, existingState !== null) } catch (err) { throw new TypeError( `Failed to construct 'Notification': ${err.message}` @@ -650,67 +667,120 @@ export class Notification extends EventTarget { } // @ts-ignore - this.#id = (rand64() & 0xFFFFn).toString() + this.#id = existingState?.id ?? (rand64() & 0xFFFFn).toString() + this.#timestamp = existingState?.timestamp ?? this.#timestamp - if (isLinux) { - const proxy = new NativeNotificationProxy(this) - const request = new Promise((resolve) => { - // @ts-ignore - proxy.addEventListener('show', () => resolve({})) - // @ts-ignore - proxy.addEventListener('error', (e) => resolve({ err: e.error })) - }) + const channel = new BroadcastChannel('socket.runtime.notification') - this.#proxy = proxy - this[Symbol.for('Notification.request')] = request - } else { - const request = ipc.request('notification.show', { - body: this.body, - icon: this.icon, - id: this.#id, - image: this.image, - lang: this.lang, - tag: this.tag || '', - title: this.title, - silent: this.silent + // if internal `existingState` is present, then this is just a view over the instance + if (existingState) { + channel.addEventListener('message', async (event) => { + if (event.data?.id === this.#id) { + if (!this.#closed) { + if (event.data.action === 'close') { + this.#closed = true + } + + this.dispatchEvent(new Event(event.data.action)) + } + } }) + } else { + if (globalThis.isServiceWorkerScope) { + throw new TypeError( + 'Failed to construct \'Notification\': Illegal constructor. ' + + 'Use ServiceWorkerRegistration.showNotification() instead.' + ) + } - this[Symbol.for('Notification.request')] = request - } + if (isLinux) { + const proxy = new NativeNotificationProxy(this) + const request = new Promise((resolve) => { + // @ts-ignore + proxy.addEventListener('show', () => resolve({})) + // @ts-ignore + proxy.addEventListener('error', (e) => resolve({ err: e.error })) + }) - state.pending.set(this.id, this) + this.#proxy = proxy + this[Symbol.for('Notification.request')] = request + } else { + const request = ipc.request('notification.show', { + body: this.body, + icon: this.icon, + id: this.#id, + image: this.image, + lang: this.lang, + tag: this.tag || '', + title: this.title, + silent: this.silent + }) - const removeNotificationPresentedListener = hooks.onNotificationPresented((event) => { - if (event.detail.id === this.id) { - removeNotificationPresentedListener() - return this.dispatchEvent(new Event('show')) + this[Symbol.for('Notification.request')] = request } - }) - const removeNotificationResponseListener = hooks.onNotificationResponse((event) => { - if (event.detail.id === this.id) { - const eventName = event.detail.action === 'dismiss' ? 'close' : 'click' - removeNotificationResponseListener() - this.dispatchEvent(new Event(eventName)) - if (eventName === 'click') { - queueMicrotask(() => this.dispatchEvent(new Event('close'))) + state.pending.set(this.id, this) + + const removeNotificationPresentedListener = hooks.onNotificationPresented((event) => { + if (event.detail.id === this.id) { + removeNotificationPresentedListener() + return this.dispatchEvent(new Event('show')) } - } - }) + }) - // propagate error to caller - this[Symbol.for('Notification.request')].then((result) => { - if (result?.err) { - // @ts-ignore - state.pending.delete(this.id, this) - removeNotificationPresentedListener() - removeNotificationResponseListener() - return this.dispatchEvent(new ErrorEvent('error', { - message: result.err.message, - error: result.err - })) - } - }) + const removeNotificationResponseListener = hooks.onNotificationResponse((event) => { + if (event.detail.id === this.id) { + this.#closed = true + const eventName = event.detail.action === 'dismiss' ? 'close' : 'click' + removeNotificationResponseListener() + this.dispatchEvent(new Event(eventName)) + + if (eventName === 'click') { + queueMicrotask(() => { + this.dispatchEvent(new Event('close')) + channel.postMessage({ id: this.id, action: 'close' }) + }) + } + + channel.postMessage({ id: this.id, action: eventName }) + } + }) + + // propagate error to caller + this[Symbol.for('Notification.request')].then((result) => { + if (result?.err) { + // @ts-ignore + state.pending.delete(this.id, this) + removeNotificationPresentedListener() + removeNotificationResponseListener() + return this.dispatchEvent(new ErrorEvent('error', { + message: result.err.message, + error: result.err + })) + } + }) + + channel.addEventListener('message', async (event) => { + if (event.data?.id === this.#id) { + if (!this.#closed) { + if (event.data.action === 'close') { + removeNotificationPresentedListener() + removeNotificationResponseListener() + await this.close() + } + + this.dispatchEvent(new Event(event.data.action)) + } + } + }) + } + } + + /** + * @ignore + */ + get options () { + return this.#options } /** @@ -721,6 +791,14 @@ export class Notification extends EventTarget { return this.#id } + /** + * `true` if the notification was closed, otherwise `false`. + * @type {boolea} + */ + get closed () { + return this.#closed + } + /** * The click event is dispatched when the user clicks on * displayed notification. @@ -922,6 +1000,20 @@ export class Notification extends EventTarget { * Closes the notification programmatically. */ async close () { + if (this.#closed) { + return + } + + this.#closed = true + + if (globalThis.isServiceWorkerScope) { + return globalThis.postMessage({ + notificationclose: { + id: this.id + } + }) + } + if (isLinux) { if (this.#proxy) { return this.#proxy.close() From 987d36ea99957611d8e6fd6e0571aac4867c6fc5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:34:20 -0400 Subject: [PATCH 0589/1178] refactor(api/service-worker): introduce support for Notification API --- api/service-worker/clients.js | 13 +- api/service-worker/container.js | 80 ++++++++--- api/service-worker/events.js | 96 ++++++++++++- api/service-worker/global.js | 5 +- api/service-worker/init.js | 224 ++++++++++++++++++++++++----- api/service-worker/instance.js | 73 ++++++++-- api/service-worker/registration.js | 10 +- api/service-worker/state.js | 9 +- api/service-worker/worker.js | 117 +++++++++++---- 9 files changed, 525 insertions(+), 102 deletions(-) diff --git a/api/service-worker/clients.js b/api/service-worker/clients.js index 2b57351e6f..a5e60ab5cc 100644 --- a/api/service-worker/clients.js +++ b/api/service-worker/clients.js @@ -55,7 +55,16 @@ export class Client { } postMessage (message, optionsOrTransferables = null) { - // FIXME(@jwerle) + globalThis.postMessage({ + from: 'serviceWorker', + registration: { id: state.id }, + client: { + id: this.#id, + type: this.#type, + frameType: this.#frameType + }, + message + }, optionsOrTransferables) } } @@ -147,7 +156,7 @@ export class Clients { if (event.data?.clients?.get?.result?.client?.id === id) { clearTimeout(timeout) state.channel.removeEventListener('message', onMessage) - resolve(event.data.client) + resolve(event.data.clients.get.result.client) } } diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 2773d48060..52f7663d76 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -1,8 +1,10 @@ /* global EventTarget */ import { ServiceWorkerRegistration } from './registration.js' -import { createServiceWorker } from './instance.js' +import { createServiceWorker, SHARED_WORKER_URL } from './instance.js' +import { SharedWorker } from '../internal/shared-worker.js' import { Deferred } from '../async.js' import application from '../application.js' +import location from '../location.js' import state from './state.js' import ipc from '../ipc.js' import os from '../os.js' @@ -22,6 +24,7 @@ class ServiceWorkerContainerInternalStateMap extends Map { class ServiceWorkerContainerInternalState { currentWindow = null controller = null + sharedWorker = null channel = new BroadcastChannel('socket.runtime.ServiceWorkerContainer') ready = new Deferred() init = new Deferred() @@ -39,12 +42,12 @@ class ServiceWorkerContainerInternalState { class ServiceWorkerContainerRealm { static instance = null - frame = null static async init (container) { const realm = new ServiceWorkerContainerRealm() return await realm.init(container) } + frame = null async init () { if (ServiceWorkerContainerRealm.instance) { return @@ -67,8 +70,11 @@ class ServiceWorkerContainerRealm { } const pending = [] - - this.frame = globalThis.top.document.createElement('iframe') + const target = ( + globalThis.top.document.head ?? + globalThis.top.document.body ?? + globalThis.top.document + ) pending.push(new Promise((resolve) => { globalThis.top.addEventListener('message', function onMessage (event) { @@ -79,10 +85,11 @@ class ServiceWorkerContainerRealm { }) })) - this.frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') - this.frame.setAttribute('loading', 'eager') - this.frame.src = SERVICE_WINDOW_PATH + this.frame = globalThis.top.document.createElement('iframe') this.frame.id = frameId + this.frame.src = SERVICE_WINDOW_PATH + this.frame.setAttribute('loading', 'eager') + this.frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') Object.assign(this.frame.style, { display: 'none', @@ -90,12 +97,6 @@ class ServiceWorkerContainerRealm { width: 0 }) - const target = ( - globalThis.top.document.head ?? - globalThis.top.document.body ?? - globalThis.top.document - ) - target.prepend(this.frame) await Promise.all(pending) @@ -265,6 +266,7 @@ export class ServiceWorkerContainer extends EventTarget { if (typeof onmessage === 'function') { this.addEventListener('message', onmessage) internal.get(this).onmessage = onmessage + this.startMessages() } } }) @@ -295,7 +297,7 @@ export class ServiceWorkerContainer extends EventTarget { } }) - if (isServiceWorkerAllowed()) { + if (!globalThis.isServiceWorkerScope && isServiceWorkerAllowed()) { state.channel.addEventListener('message', (event) => { if (event.data?.clients?.claim?.scope) { const { scope } = event.data.clients.claim @@ -366,7 +368,8 @@ export class ServiceWorkerContainer extends EventTarget { const serviceWorker = createServiceWorker(state.serviceWorker.state, { subscribe: clientURL || scope === currentScope, - scriptURL: info.registration.scriptURL + scriptURL: info.registration.scriptURL, + id: info.registration.id }) return new ServiceWorkerRegistration(info, serviceWorker) @@ -390,7 +393,8 @@ export class ServiceWorkerContainer extends EventTarget { const info = { registration } info.registration.state = info.registration.state.replace('registered', 'installing') const serviceWorker = createServiceWorker(registration.state, { - scriptURL: info.registration.scriptURL + scriptURL: info.registration.scriptURL, + id: info.registration.id }) registrations.push(new ServiceWorkerRegistration(info, serviceWorker)) } @@ -449,7 +453,8 @@ export class ServiceWorkerContainer extends EventTarget { state.serviceWorker.scriptURL = info.registration.scriptURL const serviceWorker = createServiceWorker(state.serviceWorker.state, { - scriptURL: info.registration.scriptURL + scriptURL: info.registration.scriptURL, + id: info.registration.id }) const registration = new ServiceWorkerRegistration(info, serviceWorker) @@ -479,7 +484,46 @@ export class ServiceWorkerContainer extends EventTarget { return globalThis.top.navigator.serviceWorker.startMessages() } - // FIXME(@jwerle) + if (!internal.get(this).sharedWorker && globalThis.RUNTIME_WORKER_LOCATION !== SHARED_WORKER_URL) { + internal.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) + internal.get(this).sharedWorker.port.start() + internal.get(this).sharedWorker.port.onmessage = async (event) => { + if ( + event.data?.from === 'realm' && + event.data?.registration?.id && + event.data?.client?.id === globalThis.__args.client.id && + event.data?.client?.type === globalThis.__args.client.type && + event.data?.client?.frameType === globalThis.__args.client.frameType + ) { + const registrations = await this.getRegistrations() + for (const registration of registrations) { + const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] + if (info?.id === event.data.registration.id) { + const serviceWorker = createServiceWorker(state.serviceWorker.state, { + subscribe: false, + scriptURL: info.scriptURL, + id: info.id + }) + + const messageEvent = new MessageEvent('message', { + origin: new URL(info.scriptURL, location.origin).origin, + data: event.data.message + }) + + Object.defineProperty(messageEvent, 'source', { + configurable: false, + enumerable: false, + writable: false, + value: serviceWorker + }) + + this.dispatchEvent(messageEvent) + break + } + } + } + } + } } } diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 83711f57fa..9f6c2982d2 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -1,3 +1,4 @@ +/* global MessagePort */ import { Deferred } from '../async.js' import { Context } from './context.js' import application from '../application.js' @@ -133,7 +134,7 @@ export class FetchEvent extends ExtendableEvent { /** * `FetchEvent` class constructor. * @ignore - * @param {stirng=} [type = 'fetch'] + * @param {string=} [type = 'fetch'] * @param {object=} [options] */ constructor (type = 'fetch', options = null) { @@ -251,12 +252,19 @@ export class FetchEvent extends ExtendableEvent { .concat('Access-Control-Allow-Headers:*') .join('\n') - const result = await ipc.request('serviceWorker.fetch.response', { + const params = { statusCode, clientId, headers, id - }) + } + + params['runtime-preload-injection'] = ( + response.headers.get('runtime-preload-injection') || + 'auto' + ) + + const result = await ipc.request('serviceWorker.fetch.response', params) if (result.err) { state.reportError(result.err) @@ -312,7 +320,89 @@ export class FetchEvent extends ExtendableEvent { } } +export class ExtendableMessageEvent extends ExtendableEvent { + #data = null + #ports = [] + #origin = null + #source = null + #lastEventId = '' + + /** + * `ExtendableMessageEvent` class constructor. + * @param {string=} [type = 'message'] + * @param {object=} [options] + */ + constructor (type = 'message', options = null) { + super(type, options) + this.#data = options?.data ?? null + + if (Array.isArray(options?.ports)) { + for (const port of options.ports) { + if (port instanceof MessagePort) { + this.#ports.push(port) + } + } + } + + if (options?.source) { + this.#source = options.source + } + } + + /** + * @type {any} + */ + get data () { + return this.#data + } + + /** + * @type {MessagePort[]} + */ + get ports () { + return this.#ports + } + + /** + * @type {import('./clients.js').Client?} + */ + get source () { + return this.#source + } + + /** + * @type {string} + */ + get lastEventId () { + return this.#lastEventId + } +} + +export class NotificationEvent extends ExtendableEvent { + #action = '' + #notification = null + + constructor (type, options) { + super(type, options) + + if (typeof options?.action === 'string') { + this.#action = options.action + } + + this.#notification = options.notification + } + + get action () { + return this.#action + } + + get notification () { + return this.#notification + } +} + export default { + ExtendableMessageEvent, ExtendableEvent, FetchEvent } diff --git a/api/service-worker/global.js b/api/service-worker/global.js index d0b17e5ec5..6994ed127a 100644 --- a/api/service-worker/global.js +++ b/api/service-worker/global.js @@ -13,8 +13,7 @@ let onfetch = null // this is set one time let registration = null - -const serviceWorker = createServiceWorker(state.serviceWorker.state) +let serviceWorker = null export class ServiceWorkerGlobalScope { get isServiceWorkerScope () { @@ -40,6 +39,8 @@ export class ServiceWorkerGlobalScope { set registration (value) { if (!registration) { const info = { registration: value } + + serviceWorker = createServiceWorker(state.serviceWorker.state, info.registration) registration = new ServiceWorkerRegistration(info, serviceWorker) } } diff --git a/api/service-worker/init.js b/api/service-worker/init.js index d1ed4741f8..f82808e7f4 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -1,4 +1,7 @@ /* global Worker */ +import { SHARED_WORKER_URL } from './instance.js' +import { SharedWorker } from '../internal/shared-worker.js' +import { Notification } from '../notification.js' import { sleep } from '../timers.js' import globals from '../internal/globals.js' import crypto from '../crypto.js' @@ -10,53 +13,57 @@ export const workers = new Map() globals.register('ServiceWorkerContext.workers', workers) globals.register('ServiceWorkerContext.info', new Map()) -export class ServiceWorkerInfo { - id = null - url = null - hash = null - scope = null - scriptURL = null - - constructor (data) { - for (const key in data) { - const value = data[key] - if (key in this) { - this[key] = value +const sharedWorker = new SharedWorker(SHARED_WORKER_URL) +sharedWorker.port.start() +sharedWorker.port.onmessage = (event) => { + if (event.data?.from === 'instance' && event.data.registration?.id) { + for (const worker of workers.values()) { + if (worker.info.id === event.data.registration.id) { + worker.postMessage(event.data) + break } } - - const url = new URL(this.scriptURL) - this.url = url.toString() - this.hash = crypto.murmur3(url.pathname + (this.scope || '')) - - globals.get('ServiceWorkerContext.info').set(this.hash, this) - } - - get pathname () { - return new URL(this.url).pathname + } else if (event.data?.showNotification && event.data.registration?.id) { + onNotificationShow(event, sharedWorker.port) + } else if (event.data?.getNotifications && event.data.registration?.id) { + onGetNotifications(event, sharedWorker.port) } } -export async function onRegister (event) { - const info = new ServiceWorkerInfo(event.detail) +export class ServiceWorkerInstance extends Worker { + #info = null + #notifications = [] - if (!info.id || workers.has(info.hash)) { - return + constructor (filename, options) { + super(filename, { + name: `ServiceWorker (${options?.info?.pathname ?? filename})`, + ...options, + [Symbol.for('socket.runtime.internal.worker.type')]: 'serviceWorker' + }) + + this.#info = options?.info ?? null + this.addEventListener('message', this.onMessage.bind(this)) } - const worker = new Worker('./worker.js', { - name: `ServiceWorker (${info.pathname})`, - [Symbol.for('socket.runtime.internal.worker.type')]: 'serviceWorker' - }) + get info () { + return this.#info + } - workers.set(info.hash, worker) - worker.addEventListener('message', onMessage) + get notifications () { + return this.#notifications + } - async function onMessage (event) { + async onMessage (event) { + const { info } = this if (event.data.__service_worker_ready === true) { - worker.postMessage({ register: info }) + this.postMessage({ register: info }) } else if (event.data.__service_worker_registered?.id === info.id) { - worker.postMessage({ install: info }) + this.postMessage({ install: info }) + } else if (event.data?.message && event?.data.client?.id) { + sharedWorker.port.postMessage({ + ...event.data, + from: 'realm' + }) } else if (Array.isArray(event.data.__service_worker_debug)) { const log = document.querySelector('#log') if (log) { @@ -90,10 +97,56 @@ export async function onRegister (event) { log.scrollTop = log.scrollHeight } + } else if (event.data?.showNotification && event.data.registration?.id) { + onNotificationShow(event, this) + } else if (event.data?.getNotifications && event.data.registration?.id) { + onGetNotifications(event, this) + } else if (event.data?.notificationclose?.id) { + onNotificationClose(event, this) } } } +export class ServiceWorkerInfo { + id = null + url = null + hash = null + scope = null + scriptURL = null + + constructor (data) { + for (const key in data) { + const value = data[key] + if (key in this) { + this[key] = value + } + } + + const url = new URL(this.scriptURL) + this.url = url.toString() + this.hash = crypto.murmur3(url.pathname + (this.scope || '')) + } + + get pathname () { + return new URL(this.url).pathname + } +} + +export async function onRegister (event) { + const info = new ServiceWorkerInfo(event.detail) + + if (!info.id || workers.has(info.hash)) { + return + } + + const worker = new ServiceWorkerInstance('./worker.js', { + info + }) + + workers.set(info.hash, worker) + globals.get('ServiceWorkerContext.info').set(info.hash, info) +} + export async function onUnregister (event) { const info = new ServiceWorkerInfo(event.detail) @@ -186,6 +239,107 @@ export async function onFetch (event) { worker.postMessage({ fetch: { ...info, client, request } }) } +export function onNotificationShow (event, target) { + for (const worker of workers.values()) { + if (worker.info.id === event.data.registration.id) { + let notification = null + + try { + notification = new Notification( + event.data.showNotification.title, + event.data.showNotification + ) + } catch (error) { + return target.postMessage({ + nonce: event.data.nonce, + notification: { + error: { message: error.message } + } + }) + } + + worker.notifications.push(notification) + notification.onshow = () => { + notification.onshow = null + return target.postMessage({ + nonce: event.data.nonce, + notification: { + id: notification.id + } + }) + } + + notification.onclick = (event) => { + worker.postMessage({ + notificationclick: { + title: notification.title, + options: notification.options.toJSON(), + data: { + id: notification.id, + timestamp: notification.timestamp + } + } + }) + } + + notification.onclose = (event) => { + notification.onclose = null + notification.onclick = null + worker.postMessage({ + notificationclose: { + title: notification.title, + options: notification.options.toJSON(), + data: { + id: notification.id, + timestamp: notification.timestamp + } + } + }) + + const index = worker.notifications.indexOf(notification) + if (index >= 0) { + worker.notifications.splice(index, 1) + } + } + break + } + } +} + +export function onNotificationClose (event, target) { + for (const worker of workers.values()) { + for (const notification of worker.notifications) { + if (event.data.notificationclose.id === notification.id) { + notification.close() + return + } + } + } +} + +export function onGetNotifications (event, target) { + for (const worker of workers.values()) { + if (worker.info.id === event.data.registration.id) { + return target.postMessage({ + nonce: event.data.nonce, + notifications: worker.notifications + .filter((notification) => + !event.data.getNotifications?.tag || + event.data.getNotifications.tag === notification.tag + ) + .map((notification) => ({ + title: notification.title, + options: notification.options.toJSON(), + data: { + id: notification.id, + timestamp: notification.timestamp + } + })) + }) + } + } +} + export default null globalThis.top.addEventListener('serviceWorker.register', onRegister) diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 2798a386fe..7fdeb715a2 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -1,5 +1,10 @@ +import { SharedWorker } from '../internal/shared-worker.js' +import location from '../location.js' import state from './state.js' +const serviceWorkers = new Map() +let sharedWorker = null + export const SHARED_WORKER_URL = `${globalThis.origin}/socket/service-worker/shared-worker.js` export const ServiceWorker = globalThis.ServiceWorker ?? class ServiceWorker extends EventTarget { @@ -18,27 +23,60 @@ export function createServiceWorker ( currentState = state.serviceWorker.state, options = null ) { + const id = options?.id ?? state.id ?? null + + if (!globalThis.isServiceWorkerScope) { + if (id && serviceWorkers.has(id)) { + return serviceWorkers.get(id) + } + } + + const channel = new BroadcastChannel('socket.runtime.serviceWorker.state') + // events const eventTarget = new EventTarget() let onstatechange = null let onerror = null // state + let serviceWorker = null let scriptURL = options?.scriptURL ?? null - const serviceWorker = Object.create(ServiceWorker.prototype, { + if ( + globalThis.RUNTIME_WORKER_LOCATION !== SHARED_WORKER_URL && + globalThis.location.pathname !== '/socket/service-worker/index.html' + ) { + sharedWorker = new SharedWorker(SHARED_WORKER_URL) + sharedWorker.port.start() + } + + serviceWorker = Object.create(ServiceWorker.prototype, { postMessage: { enumerable: false, configurable: false, value (message, ...args) { - // FIXME(@jwerle) + if (sharedWorker && globalThis.__args?.client) { + sharedWorker.port.postMessage({ + message, + from: 'instance', + registration: { id }, + client: { + id: globalThis.__args.client.id, + url: location.pathname + location.search, + type: globalThis.__args.client.type, + index: globalThis.__args.index, + origin: location.origin, + frameType: globalThis.__args.client.frameType + } + }, ...args) + } } }, state: { configurable: true, enumerable: false, - get: () => currentState + get: () => currentState === null ? state.serviceWorker.state : currentState }, scriptURL: { @@ -101,27 +139,36 @@ export function createServiceWorker ( }) if (options?.subscribe !== false) { - state.channel.addEventListener('message', (event) => { + channel.addEventListener('message', (event) => { const { data } = event - if (data?.serviceWorker) { + if (data?.serviceWorker?.id === id) { if (data.serviceWorker.state && data.serviceWorker.state !== currentState) { - const scope = new URL(globalThis.location.href).pathname + const scope = new URL(location.href).pathname if (scope.startsWith(data.serviceWorker.scope)) { - currentState = data.serviceWorker.state - scriptURL = data.serviceWorker.scriptURL - const event = new Event('statechange') + if (data.serviceWorker.scriptURL) { + scriptURL = data.serviceWorker.scriptURL + } - Object.defineProperties(event, { - target: { value: serviceWorker } - }) + if (data.serviceWorker.state !== currentState) { + currentState = data.serviceWorker.state + const event = new Event('statechange') - eventTarget.dispatchEvent(event) + Object.defineProperties(event, { + target: { value: serviceWorker } + }) + + eventTarget.dispatchEvent(event) + } } } } }) } + if (!globalThis.isServiceWorkerScope && id) { + serviceWorkers.set(id, serviceWorker) + } + return serviceWorker } diff --git a/api/service-worker/registration.js b/api/service-worker/registration.js index a4a066fa8a..4ec6eda4e1 100644 --- a/api/service-worker/registration.js +++ b/api/service-worker/registration.js @@ -1,3 +1,4 @@ +import { showNotification, getNotifications } from './notification.js' import ipc from '../ipc.js' export class ServiceWorkerRegistration { @@ -42,6 +43,10 @@ export class ServiceWorkerRegistration { }) } + get [Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] () { + return this.#info?.registration ?? null + } + get scope () { return this.#info.registration.scope } @@ -84,10 +89,11 @@ export class ServiceWorkerRegistration { } async getNotifications () { - return [] + return await getNotifications(this) } - async showNotification () { + async showNotification (title, options) { + return await showNotification(this, title, options) } async unregister () { diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 641987a7a7..797354506e 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -69,12 +69,19 @@ const descriptors = { enumerable: true, writable: true, value: 'parsed' + }, + + id: { + configurable: false, + enumerable: true, + writable: true, + value: null } }) } } -if (!globalThis.window) { +if (globalThis.isServiceWorkerScope) { Object.assign(descriptors, { id: { configurable: false, diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 63ea8da727..72f7535472 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -1,8 +1,11 @@ -import { ExtendableEvent, FetchEvent } from './events.js' +/* eslint-disable import/first */ +globalThis.isServiceWorkerScope = true + import { ServiceWorkerGlobalScope } from './global.js' import { createStorageInterface } from './storage.js' import { Module, createRequire } from '../module.js' import { STATUS_CODES } from '../http.js' +import { Notification } from '../notification.js' import { Environment } from './env.js' import { Deferred } from '../async.js' import { Buffer } from '../buffer.js' @@ -17,6 +20,15 @@ import path from '../path.js' import util from '../util.js' import ipc from '../ipc.js' +import { + ExtendableMessageEvent, + NotificationEvent, + ExtendableEvent, + FetchEvent +} from './events.js' + +import '../console.js' + const SERVICE_WORKER_READY_TOKEN = { __service_worker_ready: true } Object.defineProperties( @@ -41,9 +53,15 @@ function onReady () { } async function onMessage (event) { + if (event instanceof ExtendableMessageEvent) { + return + } + const { data } = event if (data?.register) { + event.stopImmediatePropagation() + const { id, scope, scriptURL } = data.register const url = new URL(scriptURL) @@ -53,6 +71,7 @@ async function onMessage (event) { } state.id = id + state.serviceWorker.id = id state.serviceWorker.scope = scope state.serviceWorker.scriptURL = scriptURL @@ -117,6 +136,9 @@ async function onMessage (event) { } }) + // create global registration from construct + globalThis.registration = data.register + try { // define the actual location of the worker. not `blob:...` globalThis.RUNTIME_WORKER_LOCATION = scriptURL @@ -175,36 +197,36 @@ async function onMessage (event) { if (module.exports.default && typeof module.exports.default === 'object') { if (typeof module.exports.default.fetch === 'function') { - state.fetch = module.exports.default.fetch + state.fetch = module.exports.default.fetch.bind(module.exports.default) } } else if (typeof module.exports.default === 'function') { state.fetch = module.exports.default } else if (typeof module.exports.fetch === 'function') { - state.fetch = module.exports.fetch + state.fetch = module.exports.fetch.bind(module.exports) } if (module.exports.default && typeof module.exports.default === 'object') { if (typeof module.exports.default.install === 'function') { - state.install = module.exports.default.install + state.install = module.exports.default.install.bind(module.exports.default) } } else if (typeof module.exports.install === 'function') { - state.install = module.exports.install + state.install = module.exports.install.bind(module.exports) } if (module.exports.default && typeof module.exports.default === 'object') { if (typeof module.exports.default.activate === 'function') { - state.activate = module.exports.default.activate + state.activate = module.exports.default.activate.bind(module.exports.default) } } else if (typeof module.exports.activate === 'function') { - state.activate = module.exports.activate + state.activate = module.exports.activate.bind(module.exports) } if (module.exports.default && typeof module.exports.default === 'object') { if (typeof module.exports.default.reportError === 'function') { - state.reportError = module.exports.default.reportError + state.reportError = module.exports.default.reportError.bind(module.exports.default) } } else if (typeof module.exports.reportError === 'function') { - state.reportError = module.reportError + state.reportError = module.reportError.bind(module.exports) } if (typeof state.activate === 'function') { @@ -275,42 +297,48 @@ async function onMessage (event) { }) } - globalThis.registration = data.register globalThis.postMessage({ __service_worker_registered: { id } }) return } if (data?.unregister) { + event.stopImmediatePropagation() + state.serviceWorker.state = 'none' + await state.notify('serviceWorker') globalThis.close() return } if (data?.install?.id === state.id) { - const event = new ExtendableEvent('install') + event.stopImmediatePropagation() + const installEvent = new ExtendableEvent('install') + events.add(installEvent) state.serviceWorker.state = 'installing' await state.notify('serviceWorker') - globalThis.dispatchEvent(event) - await event.waitsFor() + globalThis.dispatchEvent(installEvent) + await installEvent.waitsFor() state.serviceWorker.state = 'installed' await state.notify('serviceWorker') - events.delete(event) + events.delete(installEvent) return } if (data?.activate?.id === state.id) { - const event = new ExtendableEvent('activate') + event.stopImmediatePropagation() + const activateEvent = new ExtendableEvent('activate') + events.add(activateEvent) state.serviceWorker.state = 'activating' await state.notify('serviceWorker') - events.add(event) - globalThis.dispatchEvent(event) - await event.waitsFor() + globalThis.dispatchEvent(activateEvent) + await activateEvent.waitsFor() state.serviceWorker.state = 'activated' await state.notify('serviceWorker') - events.delete(event) + events.delete(activateEvent) return } if (data?.fetch?.request) { + event.stopImmediatePropagation() if (/post|put/i.test(data.fetch.request.method)) { const result = await ipc.request('serviceWorker.fetch.request.body', { id: data.fetch.request.id @@ -336,7 +364,7 @@ async function onMessage (event) { } const url = new URL(data.fetch.request.url) - const event = new FetchEvent('fetch', { + const fetchEvent = new FetchEvent('fetch', { clientId: data.fetch.client.id, fetchId: data.fetch.request.id, request: new Request(data.fetch.request.url, { @@ -346,6 +374,7 @@ async function onMessage (event) { }) }) + events.add(fetchEvent) if (url.protocol !== 'socket:') { const result = await ipc.request('protocol.getData', { scheme: url.protocol.replace(':', '') @@ -353,22 +382,58 @@ async function onMessage (event) { if (result.data !== null && result.data !== undefined) { try { - event.context.data = JSON.parse(result.data) + fetchEvent.context.data = JSON.parse(result.data) } catch { - event.context.data = result.data + fetchEvent.context.data = result.data } } } - globalThis.dispatchEvent(event) + globalThis.dispatchEvent(fetchEvent) + await fetchEvent.waitsFor() + events.delete(fetchEvent) return } if (event.data?.notificationclick) { - // TODO(@jwerle) + event.stopImmediatePropagation() + globalThis.dispatchEvent(new NotificationEvent('notificationclick', { + action: event.data.notificationclick.action, + notification: new Notification( + event.data.notificationclick.title, + event.data.notificationclick.options, + event.data.notificationclick.data + ) + })) + return } - if (event.data?.notificationshow) { - // TODO(@jwerle) + if (event.data?.notificationclose) { + event.stopImmediatePropagation() + globalThis.dispatchEvent(new NotificationEvent('notificationclose', { + action: event.data.notificationclose.action, + notification: new Notification( + event.data.notificationclose.title, + event.data.notificationclose.options, + event.data.notificationclose.data + ) + })) + return + } + + if ( + typeof event.data?.from === 'string' && + event.data.message && + event.data.client + ) { + event.stopImmediatePropagation() + globalThis.dispatchEvent(new ExtendableMessageEvent('message', { + source: await clients.get(event.data.client.id), + origin: event.data.client.origin, + ports: event.ports, + data: event.data.message + })) + // eslint-disable-next-line + return } } From 606aff12f3600d91b53e7e613ca876d99639fa72 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:35:15 -0400 Subject: [PATCH 0590/1178] refactor(src/cli/cli.cc): improve 'Assets.xcassets' compilation on macos --- src/cli/cli.cc | 94 +++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 61ee6ae3bd..bb0ee1af56 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2942,35 +2942,33 @@ int main (const int argc, const char* argv[]) { // Apple requires you to compile XCAssets/AppIcon.appiconset with a catalog for iOS and MacOS. // auto compileIconAssets = [&]() { - auto src = isForDesktop - ? paths.platformSpecificOutputPath / fs::path(std::string(settings["build_name"] + ".app")) - : paths.platformSpecificOutputPath; - - std::vector> iconTypes = {}; - - const std::string prefix = isForDesktop ? "mac" : "ios"; - const std::string key = std::string(prefix + "_icon_sizes"); - const auto sizeTypes = split(settings[key], " "); + auto src = paths.platformSpecificOutputPath; + Vector> types = {}; JSON::Array images; - for (const auto& type : sizeTypes) { - auto pair = split(type, '@'); + const String prefix = isForDesktop ? "mac" : "ios"; + const String key = prefix + "_icon_sizes"; + const auto sizes = split(settings[key], " "); - if (pair.size() != 2 || pair.size() == 0) return log("icon size requires @"); + for (const auto& type : sizes) { + const auto pair = split(type, '@'); + + if (pair.size() != 2 || pair.size() == 0) { + return log("icon size requires @"); + } - const std::string size = pair[0]; - const std::string scale = pair[1]; - const std::string scaled = std::to_string(std::stoi(scale) * std::stoi(size)); + const String size = pair[0]; + const String scale = pair[1]; images.push(JSON::Object::Entries { + { "size", size + "x" + size }, { "idiom", isForDesktop ? "mac" : "iphone" }, - { "size", scaled + "x" + scaled }, - { "scale", scale }, - { "filename", "Icon-" + size + "@" + scale + ".png" } + { "filename", "Icon-" + size + "x" + size + "@" + scale + ".png" }, + { "scale", scale } }); - iconTypes.push_back(std::make_tuple(stoi(pair[0]), stoi(pair[1]))); + types.push_back(std::make_tuple(stoi(pair[0]), stoi(pair[1]))); } auto assetsPath = fs::path { src / "Assets.xcassets" }; @@ -2987,51 +2985,61 @@ int main (const int argc, const char* argv[]) { }; writeFile(iconsPath / "Contents.json", json.str()); - if (Env::get("DEBUG") == "1") log(json.str()); - - for (auto& iconType : iconTypes) { - StringStream sipsCommand; + if (Env::get("DEBUG") == "1" || Env::get("VERBOSE") == "1") { + log(json.str()); + } - auto size = std::get<0>(iconType); - auto scale = std::get<1>(iconType); - auto scaled = std::to_string(size * scale); - auto destFileName = "Icon-" + scaled + "@" + std::to_string(scale) + "x.png"; - auto destFilePath = fs::path { iconsPath / destFileName }; + for (const auto& type : types) { + const auto size = std::get<0>(type); + const auto scale = std::get<1>(type); + const auto scaled = std::to_string(size * scale); + const auto destFileName = "Icon-" + std::to_string(size) + "x" + std::to_string(size) + "@" + std::to_string(scale) + "x.png"; + const auto destFilePath = Path { iconsPath / destFileName }; - auto src = platform.mac ? settings["mac_icon"] : settings["ios_icon"]; + const auto src = platform.mac ? settings["mac_icon"] : settings["ios_icon"]; + StringStream sipsCommand; sipsCommand << "sips" << " -z " << size << " " << size << " " << src << " --out " << destFilePath; - if (Env::get("DEBUG") == "1") log(sipsCommand.str()); - auto rSip = exec(sipsCommand.str().c_str()); + if (Env::get("DEBUG") == "1" || Env::get("VERBOSE") == "1") { + log(sipsCommand.str()); + } + + const auto r = exec(sipsCommand.str().c_str()); - if (rSip.exitCode != 0) { + if (r.exitCode != 0) { log("ERROR: failed to create project icons"); - log(rSip.output); + log(r.output); exit(1); } } - auto dest = paths.pathResourcesRelativeToUserBuild; - if (!isForDesktop) dest = src; + const auto dest = isForDesktop + ? paths.pathResourcesRelativeToUserBuild + : paths.platformSpecificOutputPath; StringStream compileAssetsCommand; - compileAssetsCommand << "xcrun " - << "actool \"" << assetsPath.string() << "\" " - << "--compile \"" << dest.string() << "\" " - << "--platform " << (platform.mac ? "macosx" : "iphone") << " " - << "--minimum-deployment-target 10.15 " - << "--app-icon AppIcon " - << "--output-partial-info-plist /tmp/partial-dev.plist" + << "actool \"" << assetsPath.string() << "\" " + << "--compile \"" << dest.string() << "\" " + << "--platform " << (platform.mac ? "macosx" : "iphone") << " " + << "--minimum-deployment-target 10.15 " + << "--app-icon AppIcon " + << "--output-partial-info-plist " + << "\"" + << (paths.platformSpecificOutputPath / "assets-partial-info.plist").string() + << "\"" ; - if (Env::get("DEBUG") == "1") log(compileAssetsCommand.str()); + if (Env::get("DEBUG") == "1" || Env::get("VERBOSE") == "1") { + log(compileAssetsCommand.str()); + } + auto r = exec(compileAssetsCommand.str().c_str()); if (r.exitCode != 0) { From a229742cd786ca8bc026d1a0a4ccbcb132f923e1 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:36:01 -0400 Subject: [PATCH 0591/1178] refactor(src/core/service_worker_container): support 'runtime-preload-injection' header --- src/core/service_worker_container.cc | 2 ++ src/core/types.hh | 1 + 2 files changed, 3 insertions(+) diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 7f38960852..21484acbde 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -424,7 +424,9 @@ namespace SSC { if ( html.size() > 0 && + message.get("runtime-preload-injection") != "disabled" && ( + message.get("runtime-preload-injection") == "always" || (extname.ends_with("html") || contentType == "text/html") || (html.find(" using Queue = std::queue; template using Vector = std::vector; template using Function = std::function; + template using Tuple = std::tuple; template using Set = std::set; using ExitCallback = Function; From dbd332e7ce323a96da9bfaeaec27c08a3f72cb53 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:36:31 -0400 Subject: [PATCH 0592/1178] feat(api/service-worker/notification.js): introduce 'showNotification' and 'getNotifications' APIs --- api/service-worker/notification.js | 131 +++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 api/service-worker/notification.js diff --git a/api/service-worker/notification.js b/api/service-worker/notification.js new file mode 100644 index 0000000000..558e467dfc --- /dev/null +++ b/api/service-worker/notification.js @@ -0,0 +1,131 @@ +import { Notification, NotificationOptions } from '../notification.js' +import { SHARED_WORKER_URL } from './instance.js' +import { NotAllowedError } from '../errors.js' +import { SharedWorker } from '../internal/shared-worker.js' +import permissions from '../internal/permissions.js' + +let sharedWorker = null + +const observedNotifications = new Set() + +if (globalThis.isServiceWorkerScope) { + globalThis.addEventListener('notificationclose', (event) => { + for (const notification of observedNotifications) { + if (notification.id === event.notification.id) { + notification.dispatchEvent(new Event('close')) + observedNotifications.delete(notification) + } + } + }) +} + +function ensureSharedWorker () { + if (!globalThis.isServiceWorkerScope && !sharedWorker) { + sharedWorker = new SharedWorker(SHARED_WORKER_URL) + sharedWorker.port.start() + } +} + +export async function showNotification (registration, title, options) { + ensureSharedWorker() + + if (title && typeof title === 'object') { + options = title + title = options.title ?? '' + } + + const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] + + if (!info) { + throw new TypeError('Invalid ServiceWorkerRegistration instance given') + } + + if (!registration.active) { + throw new TypeError('ServiceWorkerRegistration is not active') + } + + console.log('before query') + const query = await permissions.query({ name: 'notifications' }) + console.log('after query') + + if (query.state !== 'granted') { + throw new NotAllowedError('Operation not permitted') + } + + // will throw if invalid options are given + options = new NotificationOptions(options, /* allowServiceWorkerGlobalScope= */ true) + const nonce = Math.random().toString(16).slice(2) + const target = globalThis.isServiceWorkerScope ? globalThis : sharedWorker.port + const message = { + nonce, + registration: { id: info.id }, + showNotification: { title, ...options.toJSON() } + } + + target.postMessage(message) + + await new Promise((resolve, reject) => { + target.addEventListener('message', function onMessage (event) { + if (event.data?.nonce === nonce) { + target.removeEventListener('message', onMessage) + if (event.data.error) { + reject(new Error(event.data.error.message)) + } else { + resolve(event.data.notification) + } + } + }) + }) +} + +export async function getNotifications (registration, options = null) { + ensureSharedWorker() + + const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] + + if (!info) { + throw new TypeError('Invalid ServiceWorkerRegistration instance given') + } + + if (!registration.active) { + throw new TypeError('ServiceWorkerRegistration is not active') + } + + const nonce = Math.random().toString(16).slice(2) + const target = globalThis.isServiceWorkerScope ? globalThis : sharedWorker.port + const message = { + nonce, + registration: { id: info.id }, + getNotifications: { tag: options?.tag ?? null } + } + + target.postMessage(message) + + const notifications = await new Promise((resolve, reject) => { + target.addEventListener('message', function onMessage (event) { + if (event.data?.nonce === nonce) { + target.removeEventListener('message', onMessage) + if (event.data.error) { + reject(new Error(event.data.message)) + } else { + resolve(event.data.notifications.map((notification) => new Notification( + notification.title, + notification.options, + notification.data + ))) + } + } + }) + }) + + for (const notification of notifications) { + observedNotifications.add(notification) + } + + return notifications +} + +export default { + showNotification, + getNotifications +} From 5ce16263f02e99481ed57aaf7ae9709c66c87e66 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:36:48 -0400 Subject: [PATCH 0593/1178] chore(api): generate types --- api/index.d.ts | 80 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 037b031d01..2f7e31b12e 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -9340,10 +9340,10 @@ declare module "socket:service-worker/events" { /** * `FetchEvent` class constructor. * @ignore - * @param {stirng=} [type = 'fetch'] + * @param {string=} [type = 'fetch'] * @param {object=} [options] */ - constructor(type?: stirng, options?: object | undefined); + constructor(type?: string | undefined, options?: object | undefined); /** * The handled property of the `FetchEvent` interface returns a promise * indicating if the event has been handled by the fetch algorithm or not. @@ -9393,7 +9393,39 @@ declare module "socket:service-worker/events" { respondWith(response: Response | Promise): void; #private; } + export class ExtendableMessageEvent extends ExtendableEvent { + /** + * `ExtendableMessageEvent` class constructor. + * @param {string=} [type = 'message'] + * @param {object=} [options] + */ + constructor(type?: string | undefined, options?: object | undefined); + /** + * @type {any} + */ + get data(): any; + /** + * @type {MessagePort[]} + */ + get ports(): MessagePort[]; + /** + * @type {import('./clients.js').Client?} + */ + get source(): import("socket:service-worker/clients").Client; + /** + * @type {string} + */ + get lastEventId(): string; + #private; + } + export class NotificationEvent extends ExtendableEvent { + constructor(type: any, options: any); + get action(): string; + get notification(): any; + #private; + } namespace _default { + export { ExtendableMessageEvent }; export { ExtendableEvent }; export { FetchEvent }; } @@ -14525,7 +14557,7 @@ declare module "socket:notification" { requireInteraction?: boolean | undefined; silent?: boolean | undefined; vibrate?: number[] | undefined; - }); + }, allowServiceWorkerGlobalScope?: boolean); /** * An array of actions to display in the notification. * @type {NotificationAction[]} @@ -14559,7 +14591,7 @@ declare module "socket:notification" { */ get dir(): "auto" | "ltr" | "rtl"; /** - * A string containing the URL of an icon to be displayed in the notification. + A string containing the URL of an icon to be displayed in the notification. * @type {string} */ get icon(): string; @@ -14610,6 +14642,11 @@ declare module "socket:notification" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API#vibration_patterns} */ get vibrate(): number[]; + /** + * @ignore + * @return {object} + */ + toJSON(): object; #private; } /** @@ -14643,12 +14680,21 @@ declare module "socket:notification" { * @param {string} title * @param {NotificationOptions=} [options] */ - constructor(title: string, options?: NotificationOptions | undefined, ...args: any[]); + constructor(title: string, options?: NotificationOptions | undefined, existingState?: any, ...args: any[]); + /** + * @ignore + */ + get options(): any; /** * A unique identifier for this notification. * @type {string} */ get id(): string; + /** + * `true` if the notification was closed, otherwise `false`. + * @type {boolea} + */ + get closed(): boolea; set onclick(onclick: Function); /** * The click event is dispatched when the user clicks on @@ -15383,6 +15429,16 @@ declare module "socket:internal/post-message" { export default _default; } +declare module "socket:service-worker/notification" { + export function showNotification(registration: any, title: any, options: any): Promise; + export function getNotifications(registration: any, options?: any): Promise; + namespace _default { + export { showNotification }; + export { getNotifications }; + } + export default _default; +} + declare module "socket:service-worker/registration" { export class ServiceWorkerRegistration { constructor(info: any, serviceWorker: any); @@ -15394,8 +15450,8 @@ declare module "socket:service-worker/registration" { set onupdatefound(onupdatefound: any); get onupdatefound(): any; get navigationPreload(): any; - getNotifications(): Promise; - showNotification(): Promise; + getNotifications(): Promise; + showNotification(title: any, options: any): Promise; unregister(): Promise; update(): Promise; #private; @@ -15772,7 +15828,17 @@ declare module "socket:service-worker/init" { export function onSkipWaiting(event: any): Promise; export function onActivate(event: any): Promise; export function onFetch(event: any): Promise; + export function onNotificationShow(event: any, target: any): any; + export function onNotificationClose(event: any, target: any): void; + export function onGetNotifications(event: any, target: any): any; export const workers: Map; + export class ServiceWorkerInstance extends Worker { + constructor(filename: any, options: any); + get info(): any; + get notifications(): any[]; + onMessage(event: any): Promise; + #private; + } export class ServiceWorkerInfo { constructor(data: any); id: any; From eebc65ddc10e81b014eb18a73c76f98ed6cb539f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Sun, 14 Apr 2024 21:48:27 -0400 Subject: [PATCH 0594/1178] chore(api/service-worker/notification.js: clean up --- api/service-worker/notification.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/service-worker/notification.js b/api/service-worker/notification.js index 558e467dfc..6a324b15b5 100644 --- a/api/service-worker/notification.js +++ b/api/service-worker/notification.js @@ -44,9 +44,7 @@ export async function showNotification (registration, title, options) { throw new TypeError('ServiceWorkerRegistration is not active') } - console.log('before query') const query = await permissions.query({ name: 'notifications' }) - console.log('after query') if (query.state !== 'granted') { throw new NotAllowedError('Operation not permitted') From fb212d549b3902546394b01ccf2c64127d5d2155 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Fri, 12 Apr 2024 13:59:40 +0200 Subject: [PATCH 0595/1178] fixe(core): improve stdin handling, window themes --- src/core/core.hh | 1 + src/core/platform.cc | 2 ++ src/desktop/main.cc | 16 ++++++--- src/window/apple.mm | 81 ++++++++++++++++++++++++-------------------- src/window/linux.cc | 4 +++ src/window/win.cc | 4 +++ src/window/window.hh | 1 + 7 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/core/core.hh b/src/core/core.hh index e21d5dc6db..eb86fb9c17 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -862,6 +862,7 @@ namespace SSC { Atomic didTimersStart = false; Atomic isLoopRunning = false; Atomic shuttingDown = false; + Atomic domReady = false; uv_loop_t eventLoop; uv_async_t eventLoopAsync; diff --git a/src/core/platform.cc b/src/core/platform.cc index a7efcbb088..38e1eb1121 100644 --- a/src/core/platform.cc +++ b/src/core/platform.cc @@ -91,6 +91,8 @@ namespace SSC { if (event == "domcontentloaded") { Lock lock(this->core->fs.mutex); + this->core->domReady = true; + for (auto const &tuple : this->core->fs.descriptors) { auto desc = tuple.second; if (desc != nullptr) { diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 5731571abc..245cd49398 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -1621,10 +1622,18 @@ MAIN { if (isReadingStdin) { String value; - Thread t([&] () { + std::getline(std::cin, value); + + Thread t([&](String value) { + auto app = App::instance(); + + while (!app->core->domReady) { + std::this_thread::sleep_for(std::chrono::milliseconds(128)); + } + do { if (value.size() == 0) { - std::cin >> value; + std::getline(std::cin, value); } if (value.size() > 0) { @@ -1632,9 +1641,8 @@ MAIN { value.clear(); } } while (true); - }); + }, value); - std::cin >> value; t.detach(); } diff --git a/src/window/apple.mm b/src/window/apple.mm index 76cc74746e..8ca73a446e 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -129,6 +129,19 @@ - (void)sendEvent:(NSEvent *)event { [super sendEvent:event]; } + + /* - (void)viewDidChangeEffectiveAppearance { + [super viewDidChangeEffectiveAppearance]; + + NSAppearance *currentAppearance = [self effectiveAppearance]; + SSC::Window *w = (SSCW::Window*) self.window; + + if ([[currentAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] isEqualToString:NSAppearanceNameDarkAqua]) { + if (w->opts.backgroundColorDark.size()) w->setBackgroundColor(w->opts.backgroundColorDark); + } else { + if (w->opts.backgroundColorLight.size()) w->setBackgroundColor(w->opts.backgroundColorLight); + } + } */ @end @implementation SSCWindowDelegate - (void) userContentController: (WKUserContentController*) userContentController didReceiveScriptMessage: (WKScriptMessage*) scriptMessage { @@ -1082,45 +1095,13 @@ - (void) webView: (WKWebView*) webView // // Initial setup of the window background color // - NSTextCheckingResult *rgbaMatch = nil; - - if (@available(macOS 10.14, *) && (opts.backgroundColorDark.size() || opts.backgroundColorLight.size())) { - NSString *rgba; - NSRegularExpression *regex = - [NSRegularExpression regularExpressionWithPattern: @"rgba\\((\\d+),\\s*(\\d+),\\s*(\\d+),\\s*([\\d.]+)\\)" - options: NSRegularExpressionCaseInsensitive - error: nil]; - NSAppearance *appearance = [NSAppearance currentAppearance]; + NSAppearance *appearance = [NSAppearance currentAppearance]; - if ([appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameDarkAqua]]) { - if (opts.backgroundColorDark.size()) rgba = @(opts.backgroundColorDark.c_str()); - } else { - if (opts.backgroundColorLight.size()) rgba = @(opts.backgroundColorLight.c_str()); - } - - if (rgba) { - NSTextCheckingResult *rgbaMatch = - [regex firstMatchInString: rgba - options: 0 - range: NSMakeRange(0, [rgba length])]; - - if (rgbaMatch) { - int r = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:1]] intValue]; - int g = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:2]] intValue]; - int b = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:3]] intValue]; - float a = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:4]] floatValue]; - - this->setBackgroundColor(r, g, b, a); - } else { - debug("invalid arguments for window background color"); - } - } - } - - if (rgbaMatch == nil) { - webview.layer.backgroundColor = [NSColor clearColor].CGColor; - [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground"]; + if ([appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameDarkAqua]]) { + if (opts.backgroundColorDark.size()) this->setBackgroundColor(opts.backgroundColorDark); + } else { + if (opts.backgroundColorLight.size()) this->setBackgroundColor(opts.backgroundColorLight); } // [webview registerForDraggedTypes: @@ -1582,6 +1563,32 @@ - (void) webView: (WKWebView*) webView } } + void Window::setBackgroundColor (const String& rgbaString) { + NSString *rgba = @(rgbaString.c_str()); + NSRegularExpression *regex = + [NSRegularExpression regularExpressionWithPattern: @"rgba\\((\\d+),\\s*(\\d+),\\s*(\\d+),\\s*([\\d.]+)\\)" + options: NSRegularExpressionCaseInsensitive + error: nil]; + + NSTextCheckingResult *rgbaMatch = + [regex firstMatchInString: rgba + options: 0 + range: NSMakeRange(0, [rgba length])]; + + if (rgbaMatch) { + int r = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:1]] intValue]; + int g = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:2]] intValue]; + int b = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:3]] intValue]; + float a = [[rgba substringWithRange:[rgbaMatch rangeAtIndex:4]] floatValue]; + + this->setBackgroundColor(r, g, b, a); + } else { + debug("invalid arguments for window background color"); + webview.layer.backgroundColor = [NSColor clearColor].CGColor; + [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground"]; + } + } + void Window::setBackgroundColor (int r, int g, int b, float a) { if (this->window) { CGFloat sRGBComponents[4] = { r / 255.0, g / 255.0, b / 255.0, a }; diff --git a/src/window/linux.cc b/src/window/linux.cc index 5334d3ed57..6f08444778 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1116,6 +1116,10 @@ namespace SSC { this->eval(getEmitToRenderProcessJavaScript("windowHide", "{}")); } + void Window::setBackgroundColor (const String& rgba) { + + } + void Window::setBackgroundColor (int r, int g, int b, float a) { GdkRGBA color; color.red = r / 255.0; diff --git a/src/window/win.cc b/src/window/win.cc index 779b3ecc1e..668be96c07 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1985,6 +1985,10 @@ namespace SSC { this->eval(getResolveMenuSelectionJavaScript(seq, lookup.at(selection), "contextMenu", "context")); } + void Window::setBackgroundColor (const String& rgba) { + + } + void Window::setBackgroundColor (int r, int g, int b, float a) { SetBkColor(GetDC(window), RGB(r, g, b)); app.wcex.hbrBackground = CreateSolidBrush(RGB(r, g, b)); diff --git a/src/window/window.hh b/src/window/window.hh index dfc1469ebc..4fedc2d130 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -255,6 +255,7 @@ namespace SSC { void closeContextMenu (GtkWidget *, const String&); #endif void setBackgroundColor (int r, int g, int b, float a); + void setBackgroundColor (const String& rgba); String getBackgroundColor (); void setSystemMenuItemEnabled (bool enabled, int barPos, int menuPos); void setSystemMenu (const String& seq, const String& dsl); From df4e23c2d14b0f55ddad9fad95acb5efb6bb46eb Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 15 Apr 2024 14:40:31 +0200 Subject: [PATCH 0596/1178] refactor(window): trafficLightPosition -> windowControlOffsets, titleBarStyle -> titlebarStyle (also as snake_case in config), fix(api): gc bug, fix(window): background colors on appearance changes --- api/CONFIG.md | 4 +-- api/README.md | 4 +-- api/application.js | 8 +++--- api/gc.js | 2 ++ api/index.d.ts | 24 ++++++++++------ src/cli/templates.hh | 7 +++-- src/desktop/main.cc | 12 ++++---- src/window/apple.mm | 64 +++++++++++++++++++++++++------------------ src/window/options.hh | 4 +-- src/window/win.cc | 2 +- src/window/window.hh | 10 +++---- 11 files changed, 81 insertions(+), 60 deletions(-) diff --git a/api/CONFIG.md b/api/CONFIG.md index 634fe32ad8..ddc535d0b4 100644 --- a/api/CONFIG.md +++ b/api/CONFIG.md @@ -180,7 +180,7 @@ cmd | | The command to execute to spawn the "back-end" process. codesign_identity | | TODO Signing guide: https://socketsupply.co/guides/#code-signing-certificates codesign_paths | | Additional paths to codesign minimum_supported_version | "13.0.0" | Minimum supported MacOS version -trafficLightPosition | | If titleBarStyle is "hiddenInset", this will determine the x and y offsets of the traffic lights. +window_control_offsets | | If titlebar_style is "hiddenInset", this will determine the x and y offsets of the window controls (traffic lights). icon | | The icon to use for identifying your app on MacOS. icon_sizes | | The various sizes and scales of the icons to create, required minimum are listed by default. @@ -209,7 +209,7 @@ height | | The initial height of the first window in pixels or as a percentage width | | The initial width of the first window in pixels or as a percentage of the screen. backgroundColorDark | "" | The initial color of the window in dark mode. If not provided, matches the current theme. backgroundColorLight | "" | The initial color of the window in light mode. If not provided, matches the current theme. -titleBarStyle | "" | Determine if the titlebar style (hidden, hiddenInset) +titlebar_style | "" | Determine if the titlebar style (hidden, hiddenInset) max_height | 100% | Maximum height of the window in pixels or as a percentage of the screen. max_width | 100% | Maximum width of the window in pixels or as a percentage of the screen. min_height | 0 | Minimum height of the window in pixels or as a percentage of the screen. diff --git a/api/README.md b/api/README.md index f4bf5a6c01..fb94ea50df 100644 --- a/api/README.md +++ b/api/README.md @@ -45,8 +45,8 @@ Creates a new window and returns an instance of ApplicationWindow. | opts.index | number | | false | the index of the window. | | opts.path | string | | false | the path to the HTML file to load into the window. | | opts.title | string | | true | the title of the window. | -| opts.titleBarStyle | string | | true | determines the style of the titlebar (MacOS only). | -| opts.trafficLightPosition | string | | true | a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). | +| opts.titlebarStyle | string | | true | determines the style of the titlebar (MacOS only). | +| opts.windowControlOffsets | string | | true | a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). | | opts.backgroundColorDark | string | | true | determines the background color of the window in dark mode. | | opts.backgroundColorLight | string | | true | determines the background color of the window in light mode. | | opts.width | number \| string | | true | the width of the window. If undefined, the window will have the main window width. | diff --git a/api/application.js b/api/application.js index a7e8fe898f..a997cae758 100644 --- a/api/application.js +++ b/api/application.js @@ -177,8 +177,8 @@ export function getCurrentWindowIndex () { * @param {number} opts.index - the index of the window. * @param {string} opts.path - the path to the HTML file to load into the window. * @param {string=} opts.title - the title of the window. - * @param {string=} opts.titleBarStyle - determines the style of the titlebar (MacOS only). - * @param {string=} opts.trafficLightPosition - a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). + * @param {string=} opts.titlebarStyle - determines the style of the titlebar (MacOS only). + * @param {string=} opts.windowControlOffsets - a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). * @param {string=} opts.backgroundColorDark - determines the background color of the window in dark mode. * @param {string=} opts.backgroundColorLight - determines the background color of the window in light mode. * @param {(number|string)=} opts.width - the width of the window. If undefined, the window will have the main window width. @@ -213,8 +213,8 @@ export async function createWindow (opts) { minimizable: opts.minimizable ?? true, frameless: opts.frameless ?? false, aspectRatio: opts.aspectRatio ?? '', - titleBarStyle: opts.titleBarStyle ?? '', - trafficLightPosition: opts.trafficLightPosition ?? '', + titlebarStyle: opts.titlebarStyle ?? '', + windowControlOffsets: opts.windowControlOffsets ?? '', backgroundColorDark: opts.backgroundColorDark ?? '', backgroundColorLight: opts.backgroundColorLight ?? '', utility: opts.utility ?? false, diff --git a/api/gc.js b/api/gc.js index 989ed8755c..deb6eed013 100644 --- a/api/gc.js +++ b/api/gc.js @@ -58,6 +58,8 @@ export default gc * @param {Finalizer} finalizer */ async function finalizationRegistryCallback (finalizer) { + if (!finalizer) return // potentially called when finalizer is already gone + dc.channel('finalizer.start').publish({ finalizer }) if (typeof finalizer.handle === 'function') { diff --git a/api/index.d.ts b/api/index.d.ts index 2f7e31b12e..2720e4b9eb 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -553,7 +553,7 @@ declare module "socket:events" { listeners(type: any): any[]; rawListeners(type: any): any[]; listenerCount(type: any): any; - eventNames(): (string | symbol)[]; + eventNames(): any; } export namespace EventEmitter { export { EventEmitter }; @@ -6578,8 +6578,8 @@ declare module "socket:application" { * @param {number} opts.index - the index of the window. * @param {string} opts.path - the path to the HTML file to load into the window. * @param {string=} opts.title - the title of the window. - * @param {string=} opts.titleBarStyle - determines the style of the titlebar (MacOS only). - * @param {string=} opts.trafficLightPosition - a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). + * @param {string=} opts.titlebarStyle - determines the style of the titlebar (MacOS only). + * @param {string=} opts.windowControlOffsets - a string (split on 'x') provides the x and y position of the traffic lights (MacOS only). * @param {string=} opts.backgroundColorDark - determines the background color of the window in dark mode. * @param {string=} opts.backgroundColorLight - determines the background color of the window in light mode. * @param {(number|string)=} opts.width - the width of the window. If undefined, the window will have the main window width. @@ -6607,8 +6607,8 @@ declare module "socket:application" { index: number; path: string; title?: string | undefined; - titleBarStyle?: string | undefined; - trafficLightPosition?: string | undefined; + titlebarStyle?: string | undefined; + windowControlOffsets?: string | undefined; backgroundColorDark?: string | undefined; backgroundColorLight?: string | undefined; width?: (number | string) | undefined; @@ -7430,6 +7430,7 @@ declare module "socket:vm" { filename?: string; context?: object; }; + import { SharedWorker } from "socket:internal/shared-worker"; } declare module "socket:worker_threads/init" { @@ -7468,7 +7469,7 @@ declare module "socket:worker_threads" { * A pool of known worker threads. * @type {} */ - export const workers: () => () => any; + export const workers: () => () => any; /** * `true` if this is the "main" thread, otherwise `false` * The "main" thread is the top level webview window. @@ -7625,6 +7626,7 @@ declare module "socket:worker_threads" { import { Readable } from "socket:stream"; import { SHARE_ENV } from "socket:worker_threads/init"; import init from "socket:worker_threads/init"; + import { env } from "socket:process"; export { SHARE_ENV, init }; } @@ -7812,6 +7814,7 @@ declare module "socket:child_process" { import { AsyncResource } from "socket:async/resource"; import { EventEmitter } from "socket:events"; import { Worker } from "socket:worker_threads"; + import signal from "socket:signal"; } declare module "socket:constants" { @@ -8326,7 +8329,7 @@ declare module "socket:fs/web" { export function createFile(filename: string, options?: { fd: fs.FileHandle; highWaterMark?: number; - } | undefined): File; + }): File; /** * Creates a `FileSystemWritableFileStream` instance backed * by `socket:fs:` module from a given `FileSystemFileHandle` instance. @@ -8622,6 +8625,7 @@ declare module "socket:extension" { * @typedef {number} Pointer */ const $loaded: unique symbol; + import path from "socket:path"; } declare module "socket:fetch/fetch" { @@ -12670,7 +12674,7 @@ declare module "socket:commonjs/package" { static parse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - } | undefined): ParsedPackageName | null; + }): ParsedPackageName | null; /** * Returns `true` if the given `input` can be parsed by `Name.parse` or given * as input to the `Name` class constructor. @@ -12681,7 +12685,7 @@ declare module "socket:commonjs/package" { static canParse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - } | undefined): boolean; + }): boolean; /** * Creates a new `Name` from input. * @param {string|URL} input @@ -14335,6 +14339,7 @@ declare module "socket:commonjs/module" { import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import builtins from "socket:commonjs/builtins"; } declare module "socket:module" { @@ -15676,6 +15681,7 @@ declare module "socket:internal/pickers" { [keyof]; }>; }; + import { FileSystemHandle } from "socket:fs/web"; } declare module "socket:internal/primitives" { diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 7f53bf5594..765234b7b1 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -219,6 +219,7 @@ constexpr auto gHelloWorld = R"HTML( overflow: hidden; } + <

Hello, World.

@@ -1940,8 +1941,8 @@ codesign_paths = "" ; default value: "13.0.0" ; minimum_supported_version = "13.0.0" -; If titleBarStyle is "hiddenInset", this will determine the x and y offsets of the traffic lights. -; trafficLightPosition = "10x24" +; If titlebar_style is "hiddenInset", this will determine the x and y offsets of the window controls (traffic lights). +; window_control_offsets = "10x24" ; The icon to use for identifying your app on MacOS. icon = "src/icon.png" @@ -1998,7 +1999,7 @@ width = 50% ; Determine if the titlebar style (hidden, hiddenInset) ; default value: "" -; titleBarStyle = "hiddenInset" +; titlebar_style = "hiddenInset" ; Maximum height of the window in pixels or as a percentage of the screen. ; default value: 100% diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 245cd49398..aa0ff2786e 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1045,9 +1045,9 @@ MAIN { options.maximizable = message.get("maximizable") == "true" ? true : false; options.minimizable = message.get("minimizable") == "true" ? true : false; options.aspectRatio = message.get("aspectRatio"); - options.titleBarStyle = message.get("titleBarStyle"); + options.titlebarStyle = message.get("titlebarStyle"); options.title = message.get("title"); - options.trafficLightPosition = message.get("trafficLightPosition"); + options.windowControlOffsets = message.get("windowControlOffsets"); options.backgroundColorLight = message.get("backgroundColorLight"); options.backgroundColorDark = message.get("backgroundColorDark"); options.utility = message.get("utility") == "true" ? true : false; @@ -1559,10 +1559,10 @@ MAIN { .frameless = getProperty("window_frameless") == "true" ? true : false, .utility = getProperty("window_utility") == "true" ? true : false, .canExit = true, - .titleBarStyle = getProperty("window_titleBarStyle"), - .trafficLightPosition = getProperty("mac_trafficLightPosition"), - .backgroundColorLight = getProperty("window_backgroundColorLight"), - .backgroundColorDark = getProperty("window_backgroundColorDark"), + .titlebarStyle = getProperty("window_titlebar_style"), + .windowControlOffsets = getProperty("mac_window_control_offsets"), + .backgroundColorLight = getProperty("window_background_color_light"), + .backgroundColorDark = getProperty("window_background_color_dark"), .userConfig = userConfig, .onExit = shutdownHandler }); diff --git a/src/window/apple.mm b/src/window/apple.mm index 8ca73a446e..8add79dff6 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -129,19 +129,6 @@ - (void)sendEvent:(NSEvent *)event { [super sendEvent:event]; } - - /* - (void)viewDidChangeEffectiveAppearance { - [super viewDidChangeEffectiveAppearance]; - - NSAppearance *currentAppearance = [self effectiveAppearance]; - SSC::Window *w = (SSCW::Window*) self.window; - - if ([[currentAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] isEqualToString:NSAppearanceNameDarkAqua]) { - if (w->opts.backgroundColorDark.size()) w->setBackgroundColor(w->opts.backgroundColorDark); - } else { - if (w->opts.backgroundColorLight.size()) w->setBackgroundColor(w->opts.backgroundColorLight); - } - } */ @end @implementation SSCWindowDelegate - (void) userContentController: (WKUserContentController*) userContentController didReceiveScriptMessage: (WKScriptMessage*) scriptMessage { @@ -156,6 +143,20 @@ @implementation SSCBridgedWebView int lastX = 0; int lastY = 0; +- (void)viewDidChangeEffectiveAppearance { + [super viewDidChangeEffectiveAppearance]; + + SSCWindow *window = (SSCWindow*) self.window; + + if (@available(macOS 10.14, *)) { + if ([window.effectiveAppearance.name containsString:@"Dark"]) { + [window setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:1.0]]; // Dark mode color + } else { + [window setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]]; // Light mode color + } + } +} + - (void)resizeSubviewsWithOldSize:(NSSize)oldSize { [super resizeSubviewsWithOldSize:oldSize]; @@ -164,7 +165,7 @@ - (void)resizeSubviewsWithOldSize:(NSSize)oldSize { CGFloat viewWidth = w.titleBarView.frame.size.width; CGFloat viewHeight = w.titleBarView.frame.size.height; - CGFloat newX = w.trafficLightPosition.x; + CGFloat newX = w.windowControlOffsets.x; CGFloat newY = 0.f; NSButton *closeButton = [w standardWindowButton:NSWindowCloseButton]; @@ -1097,13 +1098,27 @@ - (void) webView: (WKWebView*) webView // NSAppearance *appearance = [NSAppearance currentAppearance]; + bool didSetBackgroundColor = false; if ([appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameDarkAqua]]) { - if (opts.backgroundColorDark.size()) this->setBackgroundColor(opts.backgroundColorDark); + if (opts.backgroundColorDark.size()) { + this->setBackgroundColor(opts.backgroundColorDark); + didSetBackgroundColor = true; + } } else { - if (opts.backgroundColorLight.size()) this->setBackgroundColor(opts.backgroundColorLight); + if (opts.backgroundColorLight.size()) { + this->setBackgroundColor(opts.backgroundColorLight); + didSetBackgroundColor = true; + } + } + + if (!didSetBackgroundColor) { + [window setBackgroundColor: [NSColor windowBackgroundColor]]; } + webview.layer.backgroundColor = [NSColor clearColor].CGColor; + [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground"]; + // [webview registerForDraggedTypes: // [NSArray arrayWithObject:NSPasteboardTypeFileURL]]; // @@ -1270,7 +1285,6 @@ - (void) webView: (WKWebView*) webView // Minimum window size [window setContentMinSize: NSMakeSize(opts.minWidth, opts.minHeight)]; - // [window setBackgroundColor: [NSColor controlBackgroundColor]]; [window registerForDraggedTypes: draggableTypes]; // [window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]]; @@ -1292,7 +1306,7 @@ - (void) webView: (WKWebView*) webView // // results in a hidden title bar and a full-size content window. // - if (opts.titleBarStyle == "hidden") { + if (opts.titlebarStyle == "hidden") { style |= NSWindowStyleMaskFullSizeContentView; style |= NSWindowStyleMaskResizable; [window setStyleMask: style]; @@ -1300,9 +1314,9 @@ - (void) webView: (WKWebView*) webView } // - // We don't support hiddenInset because the same thing can be accomplished by specifying trafficLightPosition + // We don't support hiddenInset because the same thing can be accomplished by specifying windowControlOffsets // - else if (opts.titleBarStyle == "hiddenInset") { + else if (opts.titlebarStyle == "hiddenInset") { style |= NSWindowStyleMaskFullSizeContentView; style |= NSWindowStyleMaskTitled; style |= NSWindowStyleMaskResizable; @@ -1313,13 +1327,13 @@ - (void) webView: (WKWebView*) webView CGFloat x = 16.f; CGFloat y = 42.f; - if (opts.trafficLightPosition.size() > 0) { - auto parts = split(opts.trafficLightPosition, 'x'); + if (opts.windowControlOffsets.size() > 0) { + auto parts = split(opts.windowControlOffsets, 'x'); try { x = std::stof(parts[0]); y = std::stof(parts[1]); } catch (...) { - debug("invalid arguments for trafficLightPosition"); + debug("invalid arguments for windowControlOffsets"); } } @@ -1345,7 +1359,7 @@ - (void) webView: (WKWebView*) webView titleBarView.frame = NSMakeRect(newX, newY, viewWidth, viewHeight); - window.trafficLightPosition = NSMakePoint(x, y); + window.windowControlOffsets = NSMakePoint(x, y); window.titleBarView = titleBarView; [window.contentView addSubview:titleBarView]; @@ -1584,8 +1598,6 @@ - (void) webView: (WKWebView*) webView this->setBackgroundColor(r, g, b, a); } else { debug("invalid arguments for window background color"); - webview.layer.backgroundColor = [NSColor clearColor].CGColor; - [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground"]; } } diff --git a/src/window/options.hh b/src/window/options.hh index f70f4b158e..d2b7148d63 100644 --- a/src/window/options.hh +++ b/src/window/options.hh @@ -27,8 +27,8 @@ namespace SSC { bool isTest = false; bool headless = false; String aspectRatio = ""; - String titleBarStyle = ""; - String trafficLightPosition = ""; + String titlebarStyle = ""; + String windowControlOffsets = ""; String backgroundColorLight = ""; String backgroundColorDark = ""; String cwd = ""; diff --git a/src/window/win.cc b/src/window/win.cc index 668be96c07..e5febdd444 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -646,7 +646,7 @@ namespace SSC { if (!opts.frameless) { style |= WS_OVERLAPPED; - if (opts.titleBarStyle == "hidden" || opts.titleBarStyle == "hiddenInset") { + if (opts.titlebarStyle == "hidden" || opts.titlebarStyle == "hiddenInset") { // Windows does not have the ability to reposition the decorations // In this case, we can assume that the user will draw their own controls. } else if (opts.closable) { diff --git a/src/window/window.hh b/src/window/window.hh index 4fedc2d130..6d0c80c613 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -77,7 +77,7 @@ namespace SSC { @interface SSCWindow : NSWindow @property (nonatomic, assign) SSCBridgedWebView *webview; @property (nonatomic, retain) NSView *titleBarView; -@property (nonatomic) NSPoint trafficLightPosition; +@property (nonatomic) NSPoint windowControlOffsets; @end @interface SSCBridgedWebView : WKWebView< @@ -662,8 +662,8 @@ namespace SSC { .isTest = this->options.isTest, .headless = opts.headless, .aspectRatio = opts.aspectRatio, - .titleBarStyle = opts.titleBarStyle, - .trafficLightPosition = opts.trafficLightPosition, + .titlebarStyle = opts.titlebarStyle, + .windowControlOffsets = opts.windowControlOffsets, .backgroundColorLight = opts.backgroundColorLight, .backgroundColorDark = opts.backgroundColorDark, .cwd = this->options.cwd, @@ -714,8 +714,8 @@ namespace SSC { .port = PORT, #endif .headless = opts.userConfig["build_headless"] == "true", - .titleBarStyle = opts.titleBarStyle, - .trafficLightPosition = opts.trafficLightPosition, + .titlebarStyle = opts.titlebarStyle, + .windowControlOffsets = opts.windowControlOffsets, .backgroundColorLight = opts.backgroundColorLight, .backgroundColorDark = opts.backgroundColorDark, .userConfig = opts.userConfig From f5fe27f8008b2a490c914ec55a900b9ccecac2a5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 08:55:16 -0400 Subject: [PATCH 0597/1178] refactor(api/commonjs/package.js): export 'detectESMSource' --- api/commonjs/package.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 6c081c5f81..bdd6cdcfb6 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -18,8 +18,8 @@ const isWorkerScope = globalThis.self === globalThis && !globalThis.window * @param {string} source * @return {boolean} */ -function detectESM (source) { - if (/(import\s|export[{|\s]|export\sdefault|(from\s['|"]))\s/.test(source)) { +export function detectESMSource (source) { + if (/(import\s|export[{|\s]|export\sdefault|export\s?\*\s?from|(from\s['|"]))\s/.test(source)) { return true } @@ -838,7 +838,7 @@ export class Package { module: entry } - if (detectESM(response.text)) { + if (detectESMSource(response.text)) { this.#info.type = 'module' } else { this.#info.type = 'commonjs' @@ -1014,7 +1014,7 @@ export class Package { if (this.#type === 'module') { if (this.#info.type !== 'module' && this.entry) { const source = this.loader.load(this.entry, origin, options).text - if (!detectESM(source)) { + if (!detectESMSource(source)) { this.#type = 'commonjs' } } From 073eb584302cdabcdc979dd6342439a3b85dfb99 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 08:55:35 -0400 Subject: [PATCH 0598/1178] fix(api/vm.js): fix 'detectFunctionSourceType' --- api/vm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/vm.js b/api/vm.js index e534b221ed..f173b581e3 100644 --- a/api/vm.js +++ b/api/vm.js @@ -27,6 +27,7 @@ /* global ErrorEvent, EventTarget, MessagePort */ import { maybeMakeError } from './ipc.js' import { SharedWorker } from './internal/shared-worker.js' +import { detectESMSource } from './commonjs/package.js' import application from './application.js' import globals from './internal/globals.js' import process from './process.js' @@ -1706,7 +1707,7 @@ export function createGlobalObject (context, options) { * @return {boolean} */ export function detectFunctionSourceType (source) { - if (/(import\s|export[{|\s]|export\sdefault|(from\s['|"]))\s/.test(source)) { + if (detectESMSource(source)) { return 'module' } From 0efbb4097a4de5b8d80049737797270ef9f157b4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 08:56:08 -0400 Subject: [PATCH 0599/1178] chore(api): generate types --- api/index.d.ts | 9790 +++++++++++++++++++------------------- api/internal/iterator.js | 0 2 files changed, 4895 insertions(+), 4895 deletions(-) create mode 100644 api/internal/iterator.js diff --git a/api/index.d.ts b/api/index.d.ts index 2720e4b9eb..b4f1bfbd41 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -553,7 +553,7 @@ declare module "socket:events" { listeners(type: any): any[]; rawListeners(type: any): any[]; listenerCount(type: any): any; - eventNames(): any; + eventNames(): (string | symbol)[]; } export namespace EventEmitter { export { EventEmitter }; @@ -6995,5954 +6995,5956 @@ declare module "socket:internal/shared-worker" { export default SharedWorker; } -declare module "socket:console" { - export function patchGlobalConsole(globalConsole: any, options?: {}): any; - export const globalConsole: globalThis.Console; - export class Console { - /** - * @ignore - */ - constructor(options: any); - /** - * @type {import('dom').Console} - */ - console: any; - /** - * @type {Map} - */ - timers: Map; - /** - * @type {Map} - */ - counters: Map; - /** - * @type {function?} - */ - postMessage: Function | null; - write(destination: any, ...args: any[]): any; - assert(assertion: any, ...args: any[]): void; - clear(): void; - count(label?: string): void; - countReset(label?: string): void; - debug(...args: any[]): void; - dir(...args: any[]): void; - dirxml(...args: any[]): void; - error(...args: any[]): void; - info(...args: any[]): void; - log(...args: any[]): void; - table(...args: any[]): any; - time(label?: string): void; - timeEnd(label?: string): void; - timeLog(label?: string): void; - trace(...objects: any[]): void; - warn(...args: any[]): void; - } - const _default: Console & { - Console: typeof Console; - globalConsole: globalThis.Console; - }; - export default _default; +declare module "socket:internal/serialize" { + export default function serialize(value: any): any; } -declare module "socket:vm" { +declare module "socket:internal/database" { /** - * @ignore - * @param {object[]} transfer - * @param {object} object - * @param {object=} [options] - * @return {object[]} + * A typed container for optional options given to the `Database` + * class constructor. + * + * @typedef {{ + * version?: string | undefined + * }} DatabaseOptions */ - export function findMessageTransfers(transfers: any, object: object, options?: object | undefined): object[]; /** - * @ignore - * @param {object} context + * A typed container for various optional options made to a `get()` function + * on a `Database` instance. + * + * @typedef {{ + * store?: string | undefined, + * stores?: string[] | undefined, + * count?: number | undefined + * }} DatabaseGetOptions */ - export function applyInputContextReferences(context: object): void; /** - * @ignore - * @param {object} context + * A typed container for various optional options made to a `put()` function + * on a `Database` instance. + * + * @typedef {{ + * store?: string | undefined, + * stores?: string[] | undefined, + * durability?: 'strict' | 'relaxed' | undefined + * }} DatabasePutOptions */ - export function applyOutputContextReferences(context: object): void; /** - * @ignore - * @param {object} context + * A typed container for various optional options made to a `delete()` function + * on a `Database` instance. + * + * @typedef {{ + * store?: string | undefined, + * stores?: string[] | undefined + * }} DatabaseDeleteOptions */ - export function filterNonTransferableValues(context: object): void; /** - * @ignore - * @param {object=} [currentContext] - * @param {object=} [updatedContext] - * @param {object=} [contextReference] - * @return {{ deletions: string[], merges: string[] }} + * A typed container for optional options given to the `Database` + * class constructor. + * + * @typedef {{ + * offset?: number | undefined, + * backlog?: number | undefined + * }} DatabaseRequestQueueWaitOptions */ - export function applyContextDifferences(currentContext?: object | undefined, updatedContext?: object | undefined, contextReference?: object | undefined, preserveScriptArgs?: boolean): { - deletions: string[]; - merges: string[]; - }; /** - * Wrap a JavaScript function source. - * @ignore - * @param {string} source - * @param {object=} [options] + * A typed container for various optional options made to a `entries()` function + * on a `Database` instance. + * + * @typedef {{ + * store?: string | undefined, + * stores?: string[] | undefined + * }} DatabaseEntriesOptions */ - export function wrapFunctionSource(source: string, options?: object | undefined): string; /** - * Gets the VM context window. - * This function will create it if it does not already exist. - * The current window will be used on Android or iOS platforms as there can - * only be one window. - * @return {Promise; /** - * Gets the `SharedWorker` that for the VM context. - * @return {Promise} + * Waits for an event of `eventType` to be dispatched on a given `EventTarget`. + * @param {EventTarget} target + * @param {string} eventType + * @return {Promise} */ - export function getContextWorker(): Promise; + export function waitFor(target: EventTarget, eventType: string): Promise; /** - * Terminates the VM script context window. - * @ignore + * Creates an opens a named `Database` instance. + * @param {string} name + * @param {?DatabaseOptions | undefined} [options] + * @return {Promise} */ - export function terminateContextWindow(): Promise; + export function open(name: string, options?: (DatabaseOptions | undefined) | null): Promise; /** - * Terminates the VM script context worker. - * @ignore + * Complete deletes a named `Database` instance. + * @param {string} name + * @param {?DatabaseOptions|undefined} [options] */ - export function terminateContextWorker(): Promise; + export function drop(name: string, options?: (DatabaseOptions | undefined) | null): Promise; /** - * Creates a prototype object of known global reserved intrinsics. - * @ignore + * A mapping of named `Database` instances that are currently opened + * @type {Map>} */ - export function createIntrinsics(options: any): any; + export const opened: Map>; /** - * Returns `true` if value is an intrinsic, otherwise `false`. - * @param {any} value - * @return {boolean} + * A container for conflict resolution for a `DatabaseRequestQueue` instance + * `IDBRequest` instance. */ - export function isIntrinsic(value: any): boolean; + export class DatabaseRequestQueueRequestConflict { + /** + * `DatabaseRequestQueueRequestConflict` class constructor + * @param {function(any): void)} resolve + * @param {function(Error): void)} reject + * @param {function(): void)} cleanup + */ + constructor(resolve: any, reject: any, cleanup: any); + /** + * Called when a conflict is resolved. + * @param {any} argument + */ + resolve(argument?: any): void; + /** + * Called when a conflict is rejected + * @param {Error} error + */ + reject(error: Error): void; + #private; + } /** - * Get the intrinsic type of a given `value`. - * @param {any} - * @return {function|object|null|undefined} + * An event dispatched on a `DatabaseRequestQueue` */ - export function getIntrinsicType(value: any): Function | object | null | undefined; + export class DatabaseRequestQueueEvent extends Event { + /** + * `DatabaseRequestQueueEvent` class constructor. + * @param {string} type + * @param {IDBRequest|IDBTransaction} request + */ + constructor(type: string, request: IDBRequest | IDBTransaction); + /** + * A reference to the underlying request for this event. + * @type {IDBRequest|IDBTransaction} + */ + get request(): IDBRequest | IDBTransaction; + #private; + } /** - * Get the intrinsic type string of a given `value`. - * @param {any} - * @return {string|null} + * An event dispatched on a `Database` */ - export function getIntrinsicTypeString(value: any): string | null; + export class DatabaseEvent extends Event { + /** + * `DatabaseEvent` class constructor. + * @param {string} type + * @param {Database} database + */ + constructor(type: string, database: Database); + /** + * A reference to the underlying database for this event. + * @type {Database} + */ + get database(): Database; + #private; + } /** - * Creates a global proxy object for context execution. - * @ignore - * @param {object} context - * @return {Proxy} + * An error event dispatched on a `DatabaseRequestQueue` */ - export function createGlobalObject(context: object, options: any): ProxyConstructor; + export class DatabaseRequestQueueErrorEvent extends ErrorEvent { + /** + * `DatabaseRequestQueueErrorEvent` class constructor. + * @param {string} type + * @param {IDBRequest|IDBTransaction} request + * @param {{ error: Error, cause?: Error }} options + */ + constructor(type: string, request: IDBRequest | IDBTransaction, options: { + error: Error; + cause?: Error; + }); + /** + * A reference to the underlying request for this error event. + * @type {IDBRequest|IDBTransaction} + */ + get request(): IDBRequest | IDBTransaction; + #private; + } /** - * @ignore - * @param {string} source - * @return {boolean} + * A container for various `IDBRequest` and `IDBTransaction` instances + * occurring during the life cycles of a `Database` instance. */ - export function detectFunctionSourceType(source: string): boolean; - /** - * Compiles `source` with `options` into a function. - * @ignore - * @param {string} source - * @param {object=} [options] - * @return {function} - */ - export function compileFunction(source: string, options?: object | undefined): Function; - /** - * Run `source` JavaScript in given context. The script context execution - * context is preserved until the `context` object that points to it is - * garbage collected or there are no longer any references to it and its - * associated `Script` instance. - * @param {string|object|function} source - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} - */ - export function runInContext(source: string | object | Function, options?: ScriptOptions | undefined, context?: object | undefined): Promise; - /** - * Run `source` JavaScript in new context. The script context is destroyed after - * execution. This is typically a "one off" isolated run. - * @param {string} source - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} - */ - export function runInNewContext(source: string, options?: ScriptOptions | undefined, context?: object | undefined): Promise; - /** - * Run `source` JavaScript in this current context (`globalThis`). - * @param {string} source - * @param {ScriptOptions=} [options] - * @return {Promise} - */ - export function runInThisContext(source: string, options?: ScriptOptions | undefined): Promise; - /** - * @ignore - * @param {Reference} reference - */ - export function putReference(reference: Reference): void; - /** - * Create a `Reference` for a `value` in a script `context`. - * @param {any} value - * @param {object} context - * @param {object=} [options] - * @return {Reference} - */ - export function createReference(value: any, context: object, options?: object | undefined): Reference; - /** - * Get a script context by ID or values - * @param {string|object|function} id - * @return {Reference?} - */ - export function getReference(id: string | object | Function): Reference | null; - /** - * Remove a script context reference by ID. - * @param {string} id - */ - export function removeReference(id: string): void; - /** - * Get all transferable values in the `object` hierarchy. - * @param {object} object - * @return {object[]} - */ - export function getTransferables(object: object): object[]; - /** - * @ignore - * @param {object} object - * @return {object} - */ - export function createContext(object: object): object; - /** - * Returns `true` if `object` is a "context" object. - * @param {object} - * @return {boolean} - */ - export function isContext(object: any): boolean; - /** - * A container for a context worker message channel that looks like a "worker". - * @ignore - */ - export class ContextWorkerInterface extends EventTarget { - get channel(): any; - get port(): any; - destroy(): void; - #private; - } - /** - * A container proxy for a context worker message channel that - * looks like a "worker". - * @ignore - */ - export class ContextWorkerInterfaceProxy extends EventTarget { - constructor(globals: any); - get port(): any; + export class DatabaseRequestQueue extends EventTarget { + /** + * Computed queue length + * @type {number} + */ + get length(): number; + /** + * Pushes an `IDBRequest` or `IDBTransaction onto the queue and returns a + * `Promise` that resolves upon a 'success' or 'complete' event and rejects + * upon an error' event. + * @param {IDBRequest|IDBTransaction} + * @param {?DatabaseRequestQueueConflictResolutionCallback} [conflictResolutionCallback] + * @return {Promise} + */ + push(request: any, conflictResolutionCallback?: DatabaseRequestQueueConflictResolutionCallback | null): Promise; + /** + * Waits for all pending requests to complete. This function will throw when + * an `IDBRequest` or `IDBTransaction` instance emits an 'error' event. + * Callers of this function can optionally specify a maximum backlog to wait + * for instead of waiting for all requests to finish. + * @param {?DatabaseRequestQueueWaitOptions | undefined} [options] + */ + wait(options?: (DatabaseRequestQueueWaitOptions | undefined) | null): Promise; #private; } /** - * Global reserved values that a script context may not modify. - * @type {string[]} - */ - export const RESERVED_GLOBAL_INTRINSICS: string[]; - /** - * A unique reference to a value owner by a "context object" and a - * `Script` instance. + * An interface for reading from named databases backed by IndexedDB. */ - export class Reference { + export class Database extends EventTarget { /** - * Predicate function to determine if a `value` is an internal or external - * script reference value. - * @param {amy} value - * @return {boolean} + * `Database` class constructor. + * @param {string} name + * @param {?DatabaseOptions | undefined} [options] */ - static isReference(value: amy): boolean; + constructor(name: string, options?: (DatabaseOptions | undefined) | null); /** - * `Reference` class constructor. - * @param {string} id - * @param {any} value - * @param {object=} [context] - * @param {object=} [options] + * `true` if the `Database` is currently opening, otherwise `false`. + * A `Database` instance should not attempt to be opened if this property value + * is `true`. + * @type {boolean} */ - constructor(id: string, value: any, context?: object | undefined, options?: object | undefined); + get opening(): boolean; /** - * The unique id of the reference - * @type {string} + * `true` if the `Database` instance was successfully opened such that the + * internal `IDBDatabase` storage instance was created and can be referenced + * on the `Database` instance, otherwise `false`. + * @type {boolean} */ - get id(): string; + get opened(): boolean; /** - * The underling primitive type of the reference value. - * @ignore - * @type {'undefined'|'object'|'number'|'boolean'|'function'|'symbol'} + * `true` if the `Database` instance was closed or has not been opened such + * that the internal `IDBDatabase` storage instance was not created or cannot + * be referenced on the `Database` instance, otherwise `false`. + * @type {boolean} */ - get type(): "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; + get closed(): boolean; /** - * The underlying value of the reference. - * @type {any?} + * `true` if the `Database` is currently closing, otherwise `false`. + * A `Database` instance should not attempt to be closed if this property value + * is `true`. + * @type {boolean} */ - get value(): any; + get closing(): boolean; /** - * The name of the type. - * @type {string?} + * The name of the `IDBDatabase` database. This value cannot be `null`. + * @type {string} */ get name(): string; /** - * The `Script` this value belongs to, if available. - * @type {Script?} + * The version of the `IDBDatabase` database. This value may be `null`. + * @type {?string} */ - get script(): Script; + get version(): string; /** - * The "context object" this reference value belongs to. - * @type {object?} + * A reference to the `IDBDatabase`, if the `Database` instance was opened. + * This value may ba `null`. + * @type {?IDBDatabase} */ - get context(): any; + get storage(): IDBDatabase; /** - * A boolean value to indicate if the underlying reference value is an - * intrinsic value. - * @type {boolean} + * Opens the `IDBDatabase` database optionally at a specific "version" if + * one was given upon construction of the `Database` instance. This function + * is not idempotent and will throw if the underlying `IDBDatabase` instance + * was created successfully or is in the process of opening. + * @return {Promise} */ - get isIntrinsic(): boolean; + open(): Promise; /** - * A boolean value to indicate if the underlying reference value is an - * external reference value. - * @type {boolean} + * Closes the `IDBDatabase` database storage, if opened. This function is not + * idempotent and will throw if the underlying `IDBDatabase` instance is + * already closed (not opened) or currently closing. + * @return {Promise} */ - get isExternal(): boolean; + close(): Promise; /** - * The intrinsic type this reference may be an instance of or directly refer to. - * @type {function|object} + * Deletes entire `Database` instance and closes after successfully + * delete storage. */ - get intrinsicType(): any; + drop(): Promise; /** - * Releases strongly held value and weak references - * to the "context object". + * Gets a "readonly" value by `key` in the `Database` object storage. + * @param {string} key + * @param {?DatabaseGetOptions|undefined} [options] + * @return {Promise} */ - release(): void; + get(key: string, options?: (DatabaseGetOptions | undefined) | null): Promise; /** - * Converts this `Reference` to a JSON object. - * @param {boolean=} [includeValue = false] + * Put a `value` at `key`, updating if it already exists, otherwise + * "inserting" it into the `Database` instance. + * @param {string} key + * @param {any} value + * @param {?DatabasePutOptions|undefined} [options] + * @return {Promise} */ - toJSON(includeValue?: boolean | undefined): { - __vmScriptReference__: boolean; - id: string; - type: "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; - name: string; - isIntrinsic: boolean; - intrinsicType: string; - }; - #private; - } - /** - * @typedef {{ - * filename?: string, - * context?: object - * }} ScriptOptions - */ - /** - * A `Script` is a container for raw JavaScript to be executed in - * a completely isolated virtual machine context, optionally with - * user supplied context. Context objects references are not actually - * shared, but instead provided to the script execution context using the - * structured cloning algorithm used by the Message Channel API. Context - * differences are computed and applied after execution so the user supplied - * context object realizes context changes after script execution. All script - * sources run in an "async" context so a "top level await" should work. - */ - export class Script extends EventTarget { - /** - * `Script` class constructor - * @param {string} source - * @param {ScriptOptions} [options] - */ - constructor(source: string, options?: ScriptOptions); - /** - * The script identifier. - */ - get id(): any; - /** - * The source for this script. - * @type {string} - */ - get source(): string; - /** - * The filename for this script. - * @type {string} - */ - get filename(): string; - /** - * A promise that resolves when the script is ready. - * @type {Promise} - */ - get ready(): Promise; + put(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; /** - * Destroy the script execution context. + * Inserts a new `value` at `key`. This function throws if a value at `key` + * already exists. + * @param {string} key + * @param {any} value + * @param {?DatabasePutOptions|undefined} [options] * @return {Promise} */ - destroy(): Promise; + insert(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; /** - * Run `source` JavaScript in given context. The script context execution - * context is preserved until the `context` object that points to it is - * garbage collected or there are no longer any references to it and its - * associated `Script` instance. - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} + * Update a `value` at `key`, updating if it already exists, otherwise + * "inserting" it into the `Database` instance. + * @param {string} key + * @param {any} value + * @param {?DatabasePutOptions|undefined} [options] + * @return {Promise} */ - runInContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + update(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; /** - * Run `source` JavaScript in new context. The script context is destroyed after - * execution. This is typically a "one off" isolated run. - * @param {ScriptOptions=} [options] - * @param {object=} [context] - * @return {Promise} + * Delete a value at `key`. + * @param {string} key + * @param {?DatabaseDeleteOptions|undefined} [options] + * @return {Promise} */ - runInNewContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + delete(key: string, options?: (DatabaseDeleteOptions | undefined) | null): Promise; /** - * Run `source` JavaScript in this current context (`globalThis`). - * @param {ScriptOptions=} [options] - * @return {Promise} + * Gets a "readonly" value by `key` in the `Database` object storage. + * @param {string} key + * @param {?DatabaseEntriesOptions|undefined} [options] + * @return {Promise} */ - runInThisContext(options?: ScriptOptions | undefined): Promise; + entries(options?: (DatabaseEntriesOptions | undefined) | null): Promise; #private; } namespace _default { - export { createGlobalObject }; - export { compileFunction }; - export { createReference }; - export { getContextWindow }; - export { getContextWorker }; - export { getReference }; - export { getTransferables }; - export { putReference }; - export { Reference }; - export { removeReference }; - export { runInContext }; - export { runInNewContext }; - export { runInThisContext }; - export { Script }; - export { createContext }; - export { isContext }; - } - export default _default; - export type ScriptOptions = { - filename?: string; - context?: object; - }; - import { SharedWorker } from "socket:internal/shared-worker"; -} - -declare module "socket:worker_threads/init" { - export const SHARE_ENV: unique symbol; - export const isMainThread: boolean; - export namespace state { - export { isMainThread }; - export let parentPort: any; - export let mainPort: any; - export let workerData: any; - export let url: any; - export let env: {}; - export let id: number; - } - namespace _default { - export { state }; + export { Database }; + export { open }; + export { drop }; } export default _default; -} - -declare module "socket:worker_threads" { - /** - * Set shared worker environment data. - * @param {string} key - * @param {any} value - */ - export function setEnvironmentData(key: string, value: any): void; /** - * Get shared worker environment data. - * @param {string} key - * @return {any} + * A typed container for optional options given to the `Database` + * class constructor. */ - export function getEnvironmentData(key: string): any; + export type DatabaseOptions = { + version?: string | undefined; + }; /** - - * A pool of known worker threads. - * @type {} + * A typed container for various optional options made to a `get()` function + * on a `Database` instance. */ - export const workers: () => () => any; + export type DatabaseGetOptions = { + store?: string | undefined; + stores?: string[] | undefined; + count?: number | undefined; + }; /** - * `true` if this is the "main" thread, otherwise `false` - * The "main" thread is the top level webview window. - * @type {boolean} + * A typed container for various optional options made to a `put()` function + * on a `Database` instance. */ - export const isMainThread: boolean; + export type DatabasePutOptions = { + store?: string | undefined; + stores?: string[] | undefined; + durability?: 'strict' | 'relaxed' | undefined; + }; /** - * The main thread `MessagePort` which is `null` when the - * current context is not the "main thread". - * @type {MessagePort?} + * A typed container for various optional options made to a `delete()` function + * on a `Database` instance. */ - export const mainPort: MessagePort | null; + export type DatabaseDeleteOptions = { + store?: string | undefined; + stores?: string[] | undefined; + }; /** - * A worker thread `BroadcastChannel` class. + * A typed container for optional options given to the `Database` + * class constructor. */ - export class BroadcastChannel extends globalThis.BroadcastChannel { - } + export type DatabaseRequestQueueWaitOptions = { + offset?: number | undefined; + backlog?: number | undefined; + }; /** - * A worker thread `MessageChannel` class. + * A typed container for various optional options made to a `entries()` function + * on a `Database` instance. */ - export class MessageChannel extends globalThis.MessageChannel { - } + export type DatabaseEntriesOptions = { + store?: string | undefined; + stores?: string[] | undefined; + }; /** - * A worker thread `MessagePort` class. + * A `DatabaseRequestQueueRequestConflict` callback function type. */ - export class MessagePort extends globalThis.MessagePort { - } + export type DatabaseRequestQueueConflictResolutionCallback = (arg0: Event, arg1: DatabaseRequestQueueRequestConflict) => any; +} + +declare module "socket:commonjs/cache" { /** - * The current unique thread ID. - * @type {number} + * @typedef {{ + * types?: object, + * loader?: import('./loader.js').Loader + * }} CacheOptions */ - export const threadId: number; + export const CACHE_CHANNEL_MESSAGE_ID: "id"; + export const CACHE_CHANNEL_MESSAGE_REPLICATE: "replicate"; /** - * The parent `MessagePort` instance - * @type {MessagePort?} + * @typedef {{ + * name: string + * }} StorageOptions */ - export const parentPort: MessagePort | null; /** - * Transferred "worker data" when creating a new `Worker` instance. - * @type {any?} + * An storage context object with persistence and durability + * for service worker storages. */ - export const workerData: any | null; - export class Pipe extends AsyncResource { - /** - * `Pipe` class constructor. - * @param {Childworker} worker - * @ignore - */ - constructor(worker: Childworker); + export class Storage extends EventTarget { /** - * `true` if the pipe is still reading, otherwise `false`. - * @type {boolean} + * Maximum entries that will be restored from storage into the context object. + * @type {number} */ - get reading(): boolean; + static MAX_CONTEXT_ENTRIES: number; /** - * Destroys the pipe + * A mapping of known `Storage` instances. + * @type {Map} */ - destroy(): void; - #private; - } - /** - * @typedef {{ - * env?: object, - * stdin?: boolean = false, - * stdout?: boolean = false, - * stderr?: boolean = false, - * workerData?: any, - * transferList?: any[], - * eval?: boolean = false - * }} WorkerOptions - - /** - * A worker thread that can communicate directly with a parent thread, - * share environment data, and process streamed data. - */ - export class Worker extends EventEmitter { + static instances: Map; /** - * `Worker` class constructor. - * @param {string} filename - * @param {WorkerOptions=} [options] + * Opens an storage for a particular name. + * @param {StorageOptions} options + * @return {Promise} */ - constructor(filename: string, options?: WorkerOptions | undefined); + static open(options: StorageOptions): Promise; /** - * Handles incoming worker messages. + * `Storage` class constructor * @ignore - * @param {MessageEvent} event + * @param {StorageOptions} options */ - onWorkerMessage(event: MessageEvent): boolean; + constructor(options: StorageOptions); /** - * Handles process environment change events - * @ignore - * @param {import('./process.js').ProcessEnvironmentEvent} event + * A reference to the currently opened storage database. + * @type {import('../internal/database.js').Database} */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + get database(): import("socket:internal/database").Database; /** - * The unique ID for this `Worker` thread instace. - * @type {number} + * `true` if the storage is opened, otherwise `false`. + * @type {boolean} */ - get id(): number; - get threadId(): number; + get opened(): boolean; /** - * A `Writable` standard input stream if `{ stdin: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Writable?} + * `true` if the storage is opening, otherwise `false`. + * @type {boolean} */ - get stdin(): Writable; + get opening(): boolean; /** - * A `Readable` standard output stream if `{ stdout: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Readable?} + * A proxied object for reading and writing storage state. + * Values written to this object must be cloneable with respect to the + * structured clone algorithm. + * @see {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm} + * @type {Proxy} */ - get stdout(): Readable; + get context(): ProxyConstructor; /** - * A `Readable` standard error stream if `{ stderr: true }` was set when - * creating this `Worker` instance. - * @type {import('./stream.js').Readable?} + * The current storage name. This value is also used as the + * internal database name. + * @type {string} */ - get stderr(): Readable; + get name(): string; /** - * Terminates the `Worker` instance + * A promise that resolves when the storage is opened. + * @type {Promise?} */ - terminate(): void; - postMessage(...args: any[]): void; - #private; - } - namespace _default { - export { Worker }; - export { isMainThread }; - export { parentPort }; - export { setEnvironmentData }; - export { getEnvironmentData }; - export { workerData }; - export { threadId }; - export { SHARE_ENV }; - } - export default _default; - /** - * /** - * A worker thread that can communicate directly with a parent thread, - * share environment data, and process streamed data. - */ - export type WorkerOptions = { - env?: object; - stdin?: boolean; - stdout?: boolean; - stderr?: boolean; - workerData?: any; - transferList?: any[]; - eval?: boolean; - }; - import { AsyncResource } from "socket:async/resource"; - import { EventEmitter } from "socket:events"; - import { Writable } from "socket:stream"; - import { Readable } from "socket:stream"; - import { SHARE_ENV } from "socket:worker_threads/init"; - import init from "socket:worker_threads/init"; - import { env } from "socket:process"; - export { SHARE_ENV, init }; -} - -declare module "socket:child_process" { - /** - * Spawns a child process exeucting `command` with `args` - * @param {string} command - * @param {string[]|object=} [args] - * @param {object=} [options - * @return {ChildProcess} - */ - export function spawn(command: string, args?: (string[] | object) | undefined, options?: object | undefined): ChildProcess; - export function exec(command: any, options: any, callback: any): ChildProcess & { - then(resolve: any, reject: any): Promise; - catch(reject: any): Promise; - finally(next: any): Promise; - }; - export function execSync(command: any, options: any): any; - export class Pipe extends AsyncResource { + get ready(): Promise; /** - * `Pipe` class constructor. - * @param {ChildProcess} process * @ignore + * @param {Promise} promise */ - constructor(process: ChildProcess); + forwardRequest(promise: Promise): Promise; /** - * `true` if the pipe is still reading, otherwise `false`. - * @type {boolean} + * Resets the current storage to an empty state. */ - get reading(): boolean; + reset(): Promise; /** - * @type {import('./process')} + * Synchronizes database entries into the storage context. */ - get process(): typeof import("socket:process"); + sync(options?: any): Promise; /** - * Destroys the pipe + * Opens the storage. + * @ignore */ - destroy(): void; + open(options?: any): Promise; + /** + * Closes the storage database, purging existing state. + * @ignore + */ + close(): Promise; #private; } - export class ChildProcess extends EventEmitter { + /** + * A container for `Snapshot` data storage. + */ + export class SnapshotData { /** - * `ChildProcess` class constructor. - * @param {{ - * env?: object, - * stdin?: boolean, - * stdout?: boolean, - * stderr?: boolean, - * signal?: AbortSigal, - * }=} [options] + * `SnapshotData` class constructor. + * @param {object=} [data] */ - constructor(options?: { - env?: object; - stdin?: boolean; - stdout?: boolean; - stderr?: boolean; - signal?: AbortSigal; - } | undefined); + constructor(data?: object | undefined); + toJSON: () => this; + [Symbol.toStringTag]: string; + } + /** + * A container for storing a snapshot of the cache data. + */ + export class Snapshot { /** - * @ignore - * @type {Pipe} + * @type {typeof SnapshotData} */ - get pipe(): Pipe; + static Data: typeof SnapshotData; /** - * `true` if the child process was killed with kill()`, - * otherwise `false`. - * @type {boolean} + * A reference to the snapshot data. + * @type {Snapshot.Data} */ - get killed(): boolean; + get data(): typeof SnapshotData; /** - * The process identifier for the child process. This value is - * `> 0` if the process was spawned successfully, otherwise `0`. - * @type {number} + * @ignore + * @return {object} */ - get pid(): number; + toJSON(): object; + #private; + } + /** + * An interface for managing and performing operations on a collection + * of `Cache` objects. + */ + export class CacheCollection { /** - * The executable file name of the child process that is launched. This - * value is `null` until the child process has successfully been spawned. - * @type {string?} + * `CacheCollection` class constructor. + * @ignore + * @param {Cache[]|Record} collection */ - get spawnfile(): string; + constructor(collection: Cache[] | Record); /** - * The full list of command-line arguments the child process was spawned with. - * This value is an empty array until the child process has successfully been - * spawned. - * @type {string[]} + * Adds a `Cache` instance to the collection. + * @param {string|Cache} name + * @param {Cache=} [cache] + * @param {boolean} */ - get spawnargs(): string[]; + add(name: string | Cache, cache?: Cache | undefined): any; /** - * Always `false` as the IPC messaging is not supported. - * @type {boolean} + * Calls a method on each `Cache` object in the collection. + * @param {string} method + * @param {...any} args + * @return {Promise>} */ - get connected(): boolean; + call(method: string, ...args: any[]): Promise>; + restore(): Promise>; + reset(): Promise>; + snapshot(): Promise>; + get(key: any): Promise>; + delete(key: any): Promise>; + keys(key: any): Promise>; + values(key: any): Promise>; + clear(key: any): Promise>; + } + /** + * A container for a shared cache that lives for the life time of + * application execution. Updates to this storage are replicated to other + * instances in the application context, including windows and workers. + */ + export class Cache { /** - * The child process exit code. This value is `null` if the child process - * is still running, otherwise it is a positive integer. - * @type {number?} + * A globally shared type mapping for the cache to use when + * derserializing a value. + * @type {Map} */ - get exitCode(): number; + static types: Map; /** - * If available, the underlying `stdin` writable stream for - * the child process. - * @type {import('./stream').Writable?} + * A globally shared cache store keyed by cache name. This is useful so + * when multiple instances of a `Cache` are created, they can share the + * same data store, reducing duplications. + * @type {Record} */ - get stdin(): import("socket:stream").Writable; + static shared: Record>; /** - * If available, the underlying `stdout` readable stream for - * the child process. - * @type {import('./stream').Readable?} + * A mapping of opened `Storage` instances. + * @type {Map} */ - get stdout(): import("socket:stream").Readable; + static storages: Map; /** - * If available, the underlying `stderr` readable stream for - * the child process. - * @type {import('./stream').Readable?} + * The `Cache.Snapshot` class. + * @type {typeof Snapshot} */ - get stderr(): import("socket:stream").Readable; + static Snapshot: typeof Snapshot; /** - * The underlying worker thread. - * @ignore - * @type {import('./worker_threads').Worker} + * The `Cache.Storage` class + * @type {typeof Storage} */ - get worker(): Worker; + static Storage: typeof Storage; /** - * This function does nothing, but is present for nodejs compat. + * Creates a snapshot of the current cache which can be serialized and + * stored in persistent storage. + * @return {Snapshot} */ - disconnect(): boolean; + static snapshot(): Snapshot; /** - * This function does nothing, but is present for nodejs compat. + * Restore caches from persistent storage. + * @param {string[]} names + * @return {Promise} + */ + static restore(names: string[]): Promise; + /** + * `Cache` class constructor. + * @param {string} name + * @param {CacheOptions=} [options] + */ + constructor(name: string, options?: CacheOptions | undefined); + /** + * The unique ID for this cache. + * @type {string} + */ + get id(): string; + /** + * The loader associated with this cache. + * @type {import('./loader.js').Loader} + */ + get loader(): import("socket:commonjs/loader").Loader; + /** + * A reference to the persisted storage. + * @type {Storage} + */ + get storage(): Storage; + /** + * The cache name + * @type {string} + */ + get name(): string; + /** + * The underlying cache data map. + * @type {Map} + */ + get data(): Map; + /** + * The broadcast channel associated with this cach. + * @type {BroadcastChannel} + */ + get channel(): BroadcastChannel; + /** + * The size of the cache. + * @type {number} + */ + get size(): number; + /** + * @type {Map} + */ + get types(): Map; + /** + * Resets the cache map and persisted storage. + */ + reset(): Promise; + /** + * Restores cache data from storage. + */ + restore(): Promise; + /** + * Creates a snapshot of the current cache which can be serialized and + * stored in persistent storage. + * @return {Snapshot.Data} + */ + snapshot(): typeof SnapshotData; + /** + * Get a value at `key`. + * @param {string} key + * @return {object|undefined} + */ + get(key: string): object | undefined; + /** + * Set `value` at `key`. + * @param {string} key + * @param {object} value + * @return {Cache} + */ + set(key: string, value: object): Cache; + /** + * Returns `true` if `key` is in cache, otherwise `false`. + * @param {string} * @return {boolean} */ - send(): boolean; + has(key: any): boolean; /** - * This function does nothing, but is present for nodejs compat. + * Delete a value at `key`. + * This does not replicate to shared caches. + * @param {string} key + * @return {boolean} */ - ref(): boolean; + delete(key: string): boolean; /** - * This function does nothing, but is present for nodejs compat. + * Returns an iterator for all cache keys. + * @return {object} */ - unref(): boolean; + keys(): object; /** - * Kills the child process. This function throws an error if the child - * process has not been spawned or is already killed. - * @param {number|string} signal + * Returns an iterator for all cache values. + * @return {object} */ - kill(...args: any[]): this; + values(): object; /** - * Spawns the child process. This function will thrown an error if the process - * is already spawned. - * @param {string} command - * @param {string[]=} [args] - * @return {ChildProcess} + * Returns an iterator for all cache entries. + * @return {object} */ - spawn(...args?: string[] | undefined): ChildProcess; + entries(): object; /** - * `EventTarget` based `addEventListener` method. - * @param {string} event - * @param {function(Event)} callback - * @param {{ once?: false }} [options] + * Clears all entries in the cache. + * This does not replicate to shared caches. + * @return {undefined} */ - addEventListener(event: string, callback: (arg0: Event) => any, options?: { - once?: false; - }): void; + clear(): undefined; /** - * `EventTarget` based `removeEventListener` method. - * @param {string} event - * @param {function(Event)} callback - * @param {{ once?: false }} [options] + * Enumerates entries in map calling `callback(value, key + * @param {function(object, string, Cache): any} callback */ - removeEventListener(event: string, callback: (arg0: Event) => any): void; + forEach(callback: (arg0: object, arg1: string, arg2: Cache) => any): void; + /** + * Broadcasts a replication to other shared caches. + */ + replicate(): this; + /** + * Destroys the cache. This function stops the broadcast channel and removes + * and listeners + */ + destroy(): void; + /** + * @ignore + */ + [Symbol.iterator](): any; #private; } - export function execFile(command: any, options: any, callback: any): ChildProcess & { - then(resolve: any, reject: any): Promise; - catch(reject: any): Promise; - finally(next: any): Promise; + export default Cache; + export type CacheOptions = { + types?: object; + loader?: import("socket:commonjs/loader").Loader; + }; + export type StorageOptions = { + name: string; }; - namespace _default { - export { ChildProcess }; - export { spawn }; - export { execFile }; - export { exec }; - } - export default _default; - import { AsyncResource } from "socket:async/resource"; - import { EventEmitter } from "socket:events"; - import { Worker } from "socket:worker_threads"; - import signal from "socket:signal"; } -declare module "socket:constants" { - export * from "socket:fs/constants"; - export * from "socket:window/constants"; - export const E2BIG: any; - export const EACCES: any; - export const EADDRINUSE: any; - export const EADDRNOTAVAIL: any; - export const EAFNOSUPPORT: any; - export const EAGAIN: any; - export const EALREADY: any; - export const EBADF: any; - export const EBADMSG: any; - export const EBUSY: any; - export const ECANCELED: any; - export const ECHILD: any; - export const ECONNABORTED: any; - export const ECONNREFUSED: any; - export const ECONNRESET: any; - export const EDEADLK: any; - export const EDESTADDRREQ: any; - export const EDOM: any; - export const EDQUOT: any; - export const EEXIST: any; - export const EFAULT: any; - export const EFBIG: any; - export const EHOSTUNREACH: any; - export const EIDRM: any; - export const EILSEQ: any; - export const EINPROGRESS: any; - export const EINTR: any; - export const EINVAL: any; - export const EIO: any; - export const EISCONN: any; - export const EISDIR: any; - export const ELOOP: any; - export const EMFILE: any; - export const EMLINK: any; - export const EMSGSIZE: any; - export const EMULTIHOP: any; - export const ENAMETOOLONG: any; - export const ENETDOWN: any; - export const ENETRESET: any; - export const ENETUNREACH: any; - export const ENFILE: any; - export const ENOBUFS: any; - export const ENODATA: any; - export const ENODEV: any; - export const ENOENT: any; - export const ENOEXEC: any; - export const ENOLCK: any; - export const ENOLINK: any; - export const ENOMEM: any; - export const ENOMSG: any; - export const ENOPROTOOPT: any; - export const ENOSPC: any; - export const ENOSR: any; - export const ENOSTR: any; - export const ENOSYS: any; - export const ENOTCONN: any; - export const ENOTDIR: any; - export const ENOTEMPTY: any; - export const ENOTSOCK: any; - export const ENOTSUP: any; - export const ENOTTY: any; - export const ENXIO: any; - export const EOPNOTSUPP: any; - export const EOVERFLOW: any; - export const EPERM: any; - export const EPIPE: any; - export const EPROTO: any; - export const EPROTONOSUPPORT: any; - export const EPROTOTYPE: any; - export const ERANGE: any; - export const EROFS: any; - export const ESPIPE: any; - export const ESRCH: any; - export const ESTALE: any; - export const ETIME: any; - export const ETIMEDOUT: any; - export const ETXTBSY: any; - export const EWOULDBLOCK: any; - export const EXDEV: any; - export const SIGHUP: any; - export const SIGINT: any; - export const SIGQUIT: any; - export const SIGILL: any; - export const SIGTRAP: any; - export const SIGABRT: any; - export const SIGIOT: any; - export const SIGBUS: any; - export const SIGFPE: any; - export const SIGKILL: any; - export const SIGUSR1: any; - export const SIGSEGV: any; - export const SIGUSR2: any; - export const SIGPIPE: any; - export const SIGALRM: any; - export const SIGTERM: any; - export const SIGCHLD: any; - export const SIGCONT: any; - export const SIGSTOP: any; - export const SIGTSTP: any; - export const SIGTTIN: any; - export const SIGTTOU: any; - export const SIGURG: any; - export const SIGXCPU: any; - export const SIGXFSZ: any; - export const SIGVTALRM: any; - export const SIGPROF: any; - export const SIGWINCH: any; - export const SIGIO: any; - export const SIGINFO: any; - export const SIGSYS: any; - const _default: any; - export default _default; -} - -declare module "socket:ip" { +declare module "socket:commonjs/loader" { /** - * Normalizes input as an IPv4 address string - * @param {string|object|string[]|Uint8Array} input - * @return {string} + * @typedef {{ + * extensions?: string[] | Set + * origin?: URL | string, + * statuses?: Cache + * cache?: { response?: Cache, status?: Cache }, + * headers?: Headers | Map | object | string[][] + * }} LoaderOptions */ - export function normalizeIPv4(input: string | object | string[] | Uint8Array): string; /** - * Determines if an input `string` is in IP address version 4 format. - * @param {string|object|string[]|Uint8Array} input - * @return {boolean} + * @typedef {{ + * loader?: Loader, + * origin?: URL | string + * }} RequestOptions */ - export function isIPv4(input: string | object | string[] | Uint8Array): boolean; - namespace _default { - export { normalizeIPv4 }; - export { isIPv4 }; - } - export default _default; -} - -declare module "socket:dns/promises" { /** - * @async - * @see {@link https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options} - * @param {string} hostname - The host name to resolve. - * @param {Object=} opts - An options object. - * @param {(number|string)=} [opts.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. - * @returns {Promise} + * @typedef {{ + * headers?: Headers | object | array[], + * status?: number + * }} RequestStatusOptions */ - export function lookup(hostname: string, opts?: any | undefined): Promise; - export default exports; - import * as exports from "socket:dns/promises"; - -} - -declare module "socket:dns/index" { /** - * Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or - * AAAA (IPv6) record. All option properties are optional. If options is an - * integer, then it must be 4 or 6 – if options is 0 or not provided, then IPv4 - * and IPv6 addresses are both returned if found. - * - * From the node.js website... - * - * > With the all option set to true, the arguments for callback change to (err, - * addresses), with addresses being an array of objects with the properties - * address and family. - * - * > On error, err is an Error object, where err.code is the error code. Keep in - * mind that err.code will be set to 'ENOTFOUND' not only when the host name does - * not exist but also when the lookup fails in other ways such as no available - * file descriptors. dns.lookup() does not necessarily have anything to do with - * the DNS protocol. The implementation uses an operating system facility that - * can associate names with addresses and vice versa. This implementation can - * have subtle but important consequences on the behavior of any Node.js program. - * Please take some time to consult the Implementation considerations section - * before using dns.lookup(). - * - * @see {@link https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback} - * @param {string} hostname - The host name to resolve. - * @param {(object|intenumberger)=} [options] - An options object or record family. - * @param {(number|string)=} [options.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. - * @param {function} cb - The function to call after the method is complete. - * @returns {void} + * @typedef {{ + * headers?: Headers | object + * }} RequestLoadOptions */ - export function lookup(hostname: string, options?: (object | intenumberger) | undefined, cb: Function): void; - export { promises }; - export default exports; - import * as promises from "socket:dns/promises"; - import * as exports from "socket:dns/index"; - -} - -declare module "socket:dns" { - export * from "socket:dns/index"; - export default exports; - import * as exports from "socket:dns/index"; -} - -declare module "socket:dgram" { - export function createSocket(options: string | any, callback?: Function | undefined): Socket; /** - * New instances of dgram.Socket are created using dgram.createSocket(). - * The new keyword is not to be used to create dgram.Socket instances. + * @typedef {{ + * request?: Request, + * headers?: Headers, + * status?: number, + * buffer?: ArrayBuffer, + * text?: string + * }} ResponseOptions */ - export class Socket extends EventEmitter { - constructor(options: any, callback: any); - id: any; - knownIdWasGivenInSocketConstruction: boolean; - type: any; - signal: any; - state: { - recvBufferSize: any; - sendBufferSize: any; - bindState: number; - connectState: number; - reuseAddr: boolean; - ipv6Only: boolean; - }; + /** + * A container for the status of a CommonJS resource. A `RequestStatus` object + * represents meta data for a `Request` that comes from a preflight + * HTTP HEAD request. + */ + export class RequestStatus { /** - * Listen for datagram messages on a named port and optional address - * If the address is not specified, the operating system will attempt to - * listen on all addresses. Once the binding is complete, a 'listening' - * event is emitted and the optional callback function is called. - * - * If binding fails, an 'error' event is emitted. - * - * @param {number} port - The port to listen for messages on - * @param {string} address - The address to bind to (0.0.0.0) - * @param {function} callback - With no parameters. Called when binding is complete. - * @see {@link https://nodejs.org/api/dgram.html#socketbindport-address-callback} + * Creates a `RequestStatus` from JSON input. + * @param {object} json + * @return {RequestStatus} */ - bind(arg1: any, arg2: any, arg3: any): this; - dataListener: ({ detail }: { - detail: any; - }) => any; + static from(json: object, options: any): RequestStatus; /** - * Associates the dgram.Socket to a remote address and port. Every message sent - * by this handle is automatically sent to that destination. Also, the socket - * will only receive messages from that remote peer. Trying to call connect() - * on an already connected socket will result in an ERR_SOCKET_DGRAM_IS_CONNECTED - * exception. If the address is not provided, '0.0.0.0' (for udp4 sockets) or '::1' - * (for udp6 sockets) will be used by default. Once the connection is complete, - * a 'connect' event is emitted and the optional callback function is called. - * In case of failure, the callback is called or, failing this, an 'error' event - * is emitted. - * - * @param {number} port - Port the client should connect to. - * @param {string=} host - Host the client should connect to. - * @param {function=} connectListener - Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. - * @see {@link https://nodejs.org/api/dgram.html#socketconnectport-address-callback} + * `RequestStatus` class constructor. + * @param {Request} request + * @param {RequestStatusOptions} [options] */ - connect(arg1: any, arg2: any, arg3: any): void; + constructor(request: Request, options?: RequestStatusOptions); + set request(request: Request); /** - * A synchronous function that disassociates a connected dgram.Socket from - * its remote address. Trying to call disconnect() on an unbound or already - * disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. - * - * @see {@link https://nodejs.org/api/dgram.html#socketdisconnect} + * The `Request` object associated with this `RequestStatus` object. + * @type {Request} */ - disconnect(): void; + get request(): Request; /** - * Broadcasts a datagram on the socket. For connectionless sockets, the - * destination port and address must be specified. Connected sockets, on the - * other hand, will use their associated remote endpoint, so the port and - * address arguments must not be set. - * - * > The msg argument contains the message to be sent. Depending on its type, - * different behavior can apply. If msg is a Buffer, any TypedArray, or a - * DataView, the offset and length specify the offset within the Buffer where - * the message begins and the number of bytes in the message, respectively. - * If msg is a String, then it is automatically converted to a Buffer with - * 'utf8' encoding. With messages that contain multi-byte characters, offset, - * and length will be calculated with respect to byte length and not the - * character position. If msg is an array, offset and length must not be - * specified. - * - * > The address argument is a string. If the value of the address is a hostname, - * DNS will be used to resolve the address of the host. If the address is not - * provided or otherwise nullish, '0.0.0.0' (for udp4 sockets) or '::1' - * (for udp6 sockets) will be used by default. - * - * > If the socket has not been previously bound with a call to bind, the socket - * is assigned a random port number and is bound to the "all interfaces" - * address ('0.0.0.0' for udp4 sockets, '::1' for udp6 sockets.) - * - * > An optional callback function may be specified as a way of reporting DNS - * errors or for determining when it is safe to reuse the buf object. DNS - * lookups delay the time to send for at least one tick of the Node.js event - * loop. - * - * > The only way to know for sure that the datagram has been sent is by using a - * callback. If an error occurs and a callback is given, the error will be - * passed as the first argument to the callback. If a callback is not given, - * the error is emitted as an 'error' event on the socket object. - * - * > Offset and length are optional but both must be set if either is used. - * They are supported only when the first argument is a Buffer, a TypedArray, - * or a DataView. - * - * @param {Buffer | TypedArray | DataView | string | Array} msg - Message to be sent. - * @param {integer=} offset - Offset in the buffer where the message starts. - * @param {integer=} length - Number of bytes in the message. - * @param {integer=} port - Destination port. - * @param {string=} address - Destination host name or IP address. - * @param {Function=} callback - Called when the message has been sent. - * @see {@link https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback} + * The unique ID of this `RequestStatus`, which is the absolute URL as a string. + * @type {string} */ - send(buffer: any, ...args: any[]): Promise; + get id(): string; /** - * Close the underlying socket and stop listening for data on it. If a - * callback is provided, it is added as a listener for the 'close' event. - * - * @param {function=} callback - Called when the connection is completed or on error. - * - * @see {@link https://nodejs.org/api/dgram.html#socketclosecallback} + * The origin for this `RequestStatus` object. + * @type {string} */ - close(cb: any): this; + get origin(): string; /** - * - * Returns an object containing the address information for a socket. For - * UDP sockets, this object will contain address, family, and port properties. - * - * This method throws EBADF if called on an unbound socket. - * @returns {Object} socketInfo - Information about the local socket - * @returns {string} socketInfo.address - The IP address of the socket - * @returns {string} socketInfo.port - The port of the socket - * @returns {string} socketInfo.family - The IP family of the socket - * - * @see {@link https://nodejs.org/api/dgram.html#socketaddress} + * A HTTP status code for this `RequestStatus` object. + * @type {number|undefined} */ - address(): any; + get status(): number; /** - * Returns an object containing the address, family, and port of the remote - * endpoint. This method throws an ERR_SOCKET_DGRAM_NOT_CONNECTED exception - * if the socket is not connected. - * - * @returns {Object} socketInfo - Information about the remote socket - * @returns {string} socketInfo.address - The IP address of the socket - * @returns {string} socketInfo.port - The port of the socket - * @returns {string} socketInfo.family - The IP family of the socket - * @see {@link https://nodejs.org/api/dgram.html#socketremoteaddress} + * An alias for `status`. + * @type {number|undefined} */ - remoteAddress(): any; + get value(): number; /** - * Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in - * bytes. - * - * @param {number} size - The size of the new receive buffer - * @see {@link https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize} + * @ignore */ - setRecvBufferSize(size: number): Promise; + get valueOf(): number; /** - * Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in - * bytes. - * - * @param {number} size - The size of the new send buffer - * @see {@link https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize} + * The HTTP headers for this `RequestStatus` object. + * @type {Headers} */ - setSendBufferSize(size: number): Promise; + get headers(): Headers; /** - * @see {@link https://nodejs.org/api/dgram.html#socketgetrecvbuffersize} + * The resource location for this `RequestStatus` object. This value is + * determined from the 'Content-Location' header, if available, otherwise + * it is derived from the request URL pathname (including the query string). + * @type {string} */ - getRecvBufferSize(): any; + get location(): string; /** - * @returns {number} the SO_SNDBUF socket send buffer size in bytes. - * @see {@link https://nodejs.org/api/dgram.html#socketgetsendbuffersize} + * `true` if the response status is considered OK, otherwise `false`. + * @type {boolean} */ - getSendBufferSize(): number; - setBroadcast(): void; - setTTL(): void; - setMulticastTTL(): void; - setMulticastLoopback(): void; - setMulticastMembership(): void; - setMulticastInterface(): void; - addMembership(): void; - dropMembership(): void; - addSourceSpecificMembership(): void; - dropSourceSpecificMembership(): void; - ref(): this; - unref(): this; + get ok(): boolean; + /** + * Loads the internal state for this `RequestStatus` object. + * @param {RequestLoadOptions|boolean} [options] + * @return {RequestStatus} + */ + load(options?: RequestLoadOptions | boolean): RequestStatus; + /** + * Converts this `RequestStatus` to JSON. + * @ignore + * @return {{ + * id: string, + * origin: string | null, + * status: number, + * headers: Array + * request: object | null | undefined + * }} + */ + toJSON(includeRequest?: boolean): { + id: string; + origin: string | null; + status: number; + headers: Array; + request: object | null | undefined; + }; #private; } /** - * Generic error class for an error occurring on a `Socket` instance. - * @ignore + * A container for a synchronous CommonJS request to local resource or + * over the network. */ - export class SocketError extends InternalError { + export class Request { /** - * @type {string} + * Creates a `Request` instance from JSON input + * @param {object} json + * @param {RequestOptions=} [options] + * @return {Request} */ - get code(): string; - } - /** - * Thrown when a socket is already bound. - */ - export class ERR_SOCKET_ALREADY_BOUND extends exports.SocketError { - get message(): string; - } - /** - * @ignore - */ - export class ERR_SOCKET_BAD_BUFFER_SIZE extends exports.SocketError { - } - /** - * @ignore - */ - export class ERR_SOCKET_BUFFER_SIZE extends exports.SocketError { - } - /** - * Thrown when the socket is already connected. - */ - export class ERR_SOCKET_DGRAM_IS_CONNECTED extends exports.SocketError { - get message(): string; - } - /** - * Thrown when the socket is not connected. - */ - export class ERR_SOCKET_DGRAM_NOT_CONNECTED extends exports.SocketError { - syscall: string; - get message(): string; - } - /** - * Thrown when the socket is not running (not bound or connected). - */ - export class ERR_SOCKET_DGRAM_NOT_RUNNING extends exports.SocketError { - get message(): string; - } - /** - * Thrown when a bad socket type is used in an argument. - */ - export class ERR_SOCKET_BAD_TYPE extends TypeError { - code: string; - get message(): string; - } - /** - * Thrown when a bad port is given. - */ - export class ERR_SOCKET_BAD_PORT extends RangeError { - code: string; - } - export default exports; - export type SocketOptions = any; - import { EventEmitter } from "socket:events"; - import { InternalError } from "socket:errors"; - import * as exports from "socket:dgram"; - -} - -declare module "socket:enumeration" { - /** - * @module enumeration - * This module provides a data structure for enumerated unique values. - */ - /** - * A container for enumerated values. - */ - export class Enumeration extends Set { + static from(json: object, options?: RequestOptions | undefined): Request; /** - * Creates an `Enumeration` instance from arguments. - * @param {...any} values - * @return {Enumeration} + * `Request` class constructor. + * @param {URL|string} url + * @param {URL|string=} [origin] + * @param {RequestOptions=} [options] */ - static from(...values: any[]): Enumeration; + constructor(url: URL | string, origin?: (URL | string) | undefined, options?: RequestOptions | undefined); /** - * `Enumeration` class constructor. - * @param {any[]} values - * @param {object=} [options = {}] - * @param {number=} [options.start = 0] + * The unique ID of this `Request`, which is the absolute URL as a string. + * @type {string} */ - constructor(values: any[], options?: object | undefined); + get id(): string; /** - * @type {number} + * The absolute `URL` of this `Request` object. + * @type {URL} */ - get length(): number; + get url(): URL; /** - * Returns `true` if enumeration contains `value`. An alias - * for `Set.prototype.has`. - * @return {boolean} + * The origin for this `Request`. + * @type {string} */ - contains(value: any): boolean; + get origin(): string; /** - * @ignore + * The `Loader` for this `Request` object. + * @type {Loader?} */ - add(): void; + get loader(): Loader; /** - * @ignore + * The `RequestStatus` for this `Request` + * @type {RequestStatus} */ - delete(): void; + get status(): RequestStatus; /** - * JSON represenation of a `Enumeration` instance. - * @ignore - * @return {string[]} + * Loads the CommonJS source file, optionally checking the `Loader` cache + * first, unless ignored when `options.cache` is `false`. + * @param {RequestLoadOptions=} [options] + * @return {Response} */ - toJSON(): string[]; + load(options?: RequestLoadOptions | undefined): Response; /** - * Internal inspect function. + * Converts this `Request` to JSON. * @ignore - * @return {LanguageQueryResult} + * @return {{ + * url: string, + * status: object | undefined + * }} */ - inspect(): LanguageQueryResult; + toJSON(includeStatus?: boolean): { + url: string; + status: object | undefined; + }; + #private; } - export default Enumeration; -} - -declare module "socket:fs/web" { /** - * Creates a new `File` instance from `filename`. - * @param {string} filename - * @param {{ fd: fs.FileHandle, highWaterMark?: number }=} [options] - * @return {File} + * A container for a synchronous CommonJS request response for a local resource + * or over the network. */ - export function createFile(filename: string, options?: { - fd: fs.FileHandle; - highWaterMark?: number; - }): File; - /** - * Creates a `FileSystemWritableFileStream` instance backed - * by `socket:fs:` module from a given `FileSystemFileHandle` instance. - * @param {string|File} file - * @return {Promise} - */ - export function createFileSystemWritableFileStream(handle: any, options: any): Promise; - /** - * Creates a `FileSystemFileHandle` instance backed by `socket:fs:` module from - * a given `File` instance or filename string. - * @param {string|File} file - * @param {object} [options] - * @return {Promise} - */ - export function createFileSystemFileHandle(file: string | File, options?: object): Promise; - /** - * Creates a `FileSystemDirectoryHandle` instance backed by `socket:fs:` module - * from a given directory name string. - * @param {string} dirname - * @return {Promise} - */ - export function createFileSystemDirectoryHandle(dirname: string, options?: any): Promise; - export const File: { - new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; - prototype: File; - } | { - new (): { - readonly lastModifiedDate: Date; - readonly lastModified: number; - readonly name: any; - readonly size: number; - readonly type: string; - slice(): void; - arrayBuffer(): Promise; - text(): Promise; - stream(): void; - }; - }; - export const FileSystemHandle: { - new (): { - readonly name: any; - readonly kind: any; - }; - }; - export const FileSystemFileHandle: { - new (): FileSystemFileHandle; - prototype: FileSystemFileHandle; - } | { - new (): { - getFile(): void; - createWritable(options?: any): Promise; - createSyncAccessHandle(): Promise; - readonly name: any; - readonly kind: any; - }; - }; - export const FileSystemDirectoryHandle: { - new (): FileSystemDirectoryHandle; - prototype: FileSystemDirectoryHandle; - } | { - new (): { - entries(): AsyncGenerator; - values(): AsyncGenerator; - keys(): AsyncGenerator; - resolve(possibleDescendant: any): Promise; - removeEntry(name: any, options?: any): Promise; - getDirectoryHandle(name: any, options?: any): Promise; - getFileHandle(name: any, options?: any): Promise; - readonly name: any; - readonly kind: any; - }; - }; - export const FileSystemWritableFileStream: { - new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): { - seek(position: any): Promise; - truncate(size: any): Promise; - write(data: any): Promise; - readonly locked: boolean; - abort(reason?: any): Promise; - close(): Promise; - getWriter(): WritableStreamDefaultWriter; - }; - }; - namespace _default { - export { createFileSystemWritableFileStream }; - export { createFileSystemDirectoryHandle }; - export { createFileSystemFileHandle }; - export { createFile }; - } - export default _default; - import fs from "socket:fs/promises"; -} - -declare module "socket:extension" { - /** - * Load an extension by name. - * @template {Record T} - * @param {string} name - * @param {ExtensionLoadOptions} [options] - * @return {Promise>} - */ - export function load>(name: string, options?: ExtensionLoadOptions): Promise>; - /** - * Provides current stats about the loaded extensions. - * @return {Promise} - */ - export function stats(): Promise; - /** - * @typedef {{ - * allow: string[] | string, - * imports?: object, - * type?: 'shared' | 'wasm32', - * path?: string, - * stats?: object, - * instance?: WebAssembly.Instance, - * adapter?: WebAssemblyExtensionAdapter - * }} ExtensionLoadOptions - */ - /** - * @typedef {{ abi: number, version: string, description: string }} ExtensionInfo - */ - /** - * @typedef {{ abi: number, loaded: number }} ExtensionStats - */ - /** - * A interface for a native extension. - * @template {Record T} - */ - export class Extension> extends EventTarget { + export class Response { /** - * Load an extension by name. - * @template {Record T} - * @param {string} name - * @param {ExtensionLoadOptions} [options] - * @return {Promise>} + * Creates a `Response` from JSON input + * @param {obejct} json + * @param {ResponseOptions=} [options] + * @return {Response} */ - static load>(name: string, options?: ExtensionLoadOptions): Promise>; + static from(json: obejct, options?: ResponseOptions | undefined): Response; /** - * Query type of extension by name. - * @param {string} name - * @return {Promise<'shared'|'wasm32'|'unknown'|null>} + * `Response` class constructor. + * @param {Request|ResponseOptions} request + * @param {ResponseOptions=} [options] */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + constructor(request: Request | ResponseOptions, options?: ResponseOptions | undefined); /** - * Provides current stats about the loaded extensions or one by name. - * @param {?string} name - * @return {Promise} + * The unique ID of this `Response`, which is the absolute + * URL of the request as a string. + * @type {string} */ - static stats(name: string | null): Promise; + get id(): string; /** - * `Extension` class constructor. - * @param {string} name - * @param {ExtensionInfo} info - * @param {ExtensionLoadOptions} [options] + * The `Request` object associated with this `Response` object. + * @type {Request} */ - constructor(name: string, info: ExtensionInfo, options?: ExtensionLoadOptions); + get request(): Request; /** - * The name of the extension - * @type {string?} + * The response headers from the associated request. + * @type {Headers} */ - name: string | null; + get headers(): Headers; /** - * The version of the extension - * @type {string?} + * The `Loader` associated with this `Response` object. + * @type {Loader?} */ - version: string | null; + get loader(): Loader; /** - * The description of the extension - * @type {string?} + * The `Response` status code from the associated `Request` object. + * @type {number} */ - description: string | null; + get status(): number; /** - * The abi of the extension - * @type {number} + * The `Response` string from the associated `Request` + * @type {string} */ - abi: number; + get text(): string; /** - * @type {object} + * The `Response` array buffer from the associated `Request` + * @type {ArrayBuffer?} */ - options: object; + get buffer(): ArrayBuffer; /** - * @type {T} + * `true` if the response is considered OK, otherwise `false`. + * @type {boolean} */ - binding: T; + get ok(): boolean; /** - * Not `null` if extension is of type 'wasm32' - * @type {?WebAssemblyExtensionAdapter} + * Converts this `Response` to JSON. + * @ignore + * @return {{ + * id: string, + * text: string, + * status: number, + * buffer: number[] | null, + * headers: Array + * }} */ - adapter: WebAssemblyExtensionAdapter | null; + toJSON(): { + id: string; + text: string; + status: number; + buffer: number[] | null; + headers: Array; + }; + #private; + } + /** + * A container for loading CommonJS module sources + */ + export class Loader { /** - * `true` if the extension was loaded, otherwise `false` - * @type {boolean} + * A request class used by `Loader` objects. + * @type {typeof Request} */ - get loaded(): boolean; + static Request: typeof Request; /** - * The extension type: 'shared' or 'wasm32' - * @type {'shared'|'wasm32'} + * A response class used by `Loader` objects. + * @type {typeof Request} */ - get type(): "shared" | "wasm32"; + static Response: typeof Request; /** - * Unloads the loaded extension. - * @throws Error + * Resolves a given module URL to an absolute URL with an optional `origin`. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} */ - unload(): Promise; - instance: any; - [$type]: "shared" | "wasm32"; - [$loaded]: boolean; - } - namespace _default { - export { load }; - export { stats }; - } - export default _default; - export type Pointer = number; - export type ExtensionLoadOptions = { - allow: string[] | string; - imports?: object; - type?: 'shared' | 'wasm32'; - path?: string; - stats?: object; - instance?: WebAssembly.Instance; - adapter?: WebAssemblyExtensionAdapter; + static resolve(url: URL | string, origin?: URL | string): string; + /** + * Default extensions for a loader. + * @type {Set} + */ + static defaultExtensions: Set; + /** + * `Loader` class constructor. + * @param {string|URL|LoaderOptions} origin + * @param {LoaderOptions=} [options] + */ + constructor(origin: string | URL | LoaderOptions, options?: LoaderOptions | undefined); + /** + * The internal caches for this `Loader` object. + * @type {{ response: Cache, status: Cache }} + */ + get cache(): { + response: Cache; + status: Cache; + }; + /** + * Headers used in too loader requests. + * @type {Headers} + */ + get headers(): Headers; + /** + * A set of supported `Loader` extensions. + * @type {Set} + */ + get extensions(): Set; + set origin(origin: string); + /** + * The origin of this `Loader` object. + * @type {string} + */ + get origin(): string; + /** + * Loads a CommonJS module source file at `url` with an optional `origin`, which + * defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {Response} + */ + load(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): Response; + /** + * Queries the status of a CommonJS module source file at `url` with an + * optional `origin`, which defaults to the application origin. + * @param {URL|string} url + * @param {URL|string|object} [origin] + * @param {RequestOptions=} [options] + * @return {RequestStatus} + */ + status(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): RequestStatus; + /** + * Resolves a given module URL to an absolute URL based on the loader origin. + * @param {URL|string} url + * @param {URL|string} [origin] + * @return {string} + */ + resolve(url: URL | string, origin?: URL | string): string; + #private; + } + export default Loader; + export type LoaderOptions = { + extensions?: string[] | Set; + origin?: URL | string; + statuses?: Cache; + cache?: { + response?: Cache; + status?: Cache; + }; + headers?: Headers | Map | object | string[][]; }; - export type ExtensionInfo = { - abi: number; - version: string; - description: string; + export type RequestOptions = { + loader?: Loader; + origin?: URL | string; }; - export type ExtensionStats = { - abi: number; - loaded: number; + export type RequestStatusOptions = { + headers?: Headers | object | any[][]; + status?: number; + }; + export type RequestLoadOptions = { + headers?: Headers | object; + }; + export type ResponseOptions = { + request?: Request; + headers?: Headers; + status?: number; + buffer?: ArrayBuffer; + text?: string; }; + import { Headers } from "socket:ipc"; + import { Cache } from "socket:commonjs/cache"; +} + +declare module "socket:commonjs/package" { /** - * An adapter for reading and writing various values from a WebAssembly instance's - * memory buffer. * @ignore + * @param {string} source + * @return {boolean} */ - class WebAssemblyExtensionAdapter { - constructor({ instance, module, table, memory, policies }: { - instance: any; - module: any; - table: any; - memory: any; - policies: any; - }); - view: any; - heap: any; - table: any; - stack: any; - buffer: any; - module: any; - memory: any; - context: any; - policies: any[]; - externalReferences: Map; - instance: any; - exitStatus: any; - textDecoder: TextDecoder; - textEncoder: TextEncoder; - errorMessagePointers: {}; - indirectFunctionTable: any; - get globalBaseOffset(): any; - destroy(): void; - init(): boolean; - get(pointer: any, size?: number): any; - set(pointer: any, value: any): void; - createExternalReferenceValue(value: any): any; - getExternalReferenceValue(pointer: any): any; - setExternalReferenceValue(pointer: any, value: any): Map; - removeExternalReferenceValue(pointer: any): void; - getExternalReferencePointer(value: any): any; - getFloat32(pointer: any): any; - setFloat32(pointer: any, value: any): boolean; - getFloat64(pointer: any): any; - setFloat64(pointer: any, value: any): boolean; - getInt8(pointer: any): any; - setInt8(pointer: any, value: any): boolean; - getInt16(pointer: any): any; - setInt16(pointer: any, value: any): boolean; - getInt32(pointer: any): any; - setInt32(pointer: any, value: any): boolean; - getUint8(pointer: any): any; - setUint8(pointer: any, value: any): boolean; - getUint16(pointer: any): any; - setUint16(pointer: any, value: any): boolean; - getUint32(pointer: any): any; - setUint32(pointer: any, value: any): boolean; - getString(pointer: any, buffer: any, size: any): string; - setString(pointer: any, string: any, buffer?: any): boolean; - } - const $type: unique symbol; + export function detectESMSource(source: string): boolean; /** - * @typedef {number} Pointer + * @typedef {{ + * manifest?: string, + * index?: string, + * description?: string, + * version?: string, + * license?: string, + * exports?: object, + * type?: 'commonjs' | 'module', + * info?: object, + * origin?: string, + * dependencies?: Dependencies | object | Map + * }} PackageOptions */ - const $loaded: unique symbol; - import path from "socket:path"; -} - -declare module "socket:fetch/fetch" { - export function Headers(headers: any): void; - export class Headers { - constructor(headers: any); - map: {}; - append(name: any, value: any): void; - delete(name: any): void; - get(name: any): any; - has(name: any): boolean; - set(name: any, value: any): void; - forEach(callback: any, thisArg: any): void; - keys(): { - next: () => { - done: boolean; - value: any; - }; - }; - values(): { - next: () => { - done: boolean; - value: any; - }; - }; - entries(): { - next: () => { - done: boolean; - value: any; - }; - }; - } - export function Request(input: any, options: any): void; - export class Request { - constructor(input: any, options: any); - url: string; - credentials: any; - headers: Headers; - method: any; - mode: any; - signal: any; - referrer: any; - clone(): Request; - } - export function Response(bodyInit: any, options: any): void; - export class Response { - constructor(bodyInit: any, options: any); - type: string; - status: any; - ok: boolean; - statusText: string; - headers: Headers; - url: any; - clone(): Response; - } - export namespace Response { - function error(): Response; - function redirect(url: any, status: any): Response; - } - export function fetch(input: any, init: any): Promise; - export class DOMException { - private constructor(); - } - namespace _default { - export { fetch }; - export { Headers }; - export { Request }; - export { Response }; - } - export default _default; -} - -declare module "socket:internal/database" { /** - * A typed container for optional options given to the `Database` - * class constructor. - * - * @typedef {{ - * version?: string | undefined - * }} DatabaseOptions + * @typedef {import('./loader.js').RequestOptions & { + * type?: 'commonjs' | 'module' + * prefix?: string + * }} PackageLoadOptions */ /** - * A typed container for various optional options made to a `get()` function - * on a `Database` instance. - * - * @typedef {{ - * store?: string | undefined, - * stores?: string[] | undefined, - * count?: number | undefined - * }} DatabaseGetOptions - */ - /** - * A typed container for various optional options made to a `put()` function - * on a `Database` instance. - * - * @typedef {{ - * store?: string | undefined, - * stores?: string[] | undefined, - * durability?: 'strict' | 'relaxed' | undefined - * }} DatabasePutOptions - */ - /** - * A typed container for various optional options made to a `delete()` function - * on a `Database` instance. - * - * @typedef {{ - * store?: string | undefined, - * stores?: string[] | undefined - * }} DatabaseDeleteOptions - */ - /** - * A typed container for optional options given to the `Database` - * class constructor. - * - * @typedef {{ - * offset?: number | undefined, - * backlog?: number | undefined - * }} DatabaseRequestQueueWaitOptions + * {import('./loader.js').RequestOptions & { + * load?: boolean, + * type?: 'commonjs' | 'module', + * browser?: boolean, + * children?: string[] + * extensions?: string[] | Set + * }} PackageResolveOptions */ /** - * A typed container for various optional options made to a `entries()` function - * on a `Database` instance. - * * @typedef {{ - * store?: string | undefined, - * stores?: string[] | undefined - * }} DatabaseEntriesOptions - */ + * organization: string | null, + * name: string, + * version: string | null, + * pathname: string, + * url: URL, + * isRelative: boolean, + * hasManifest: boolean + * }} ParsedPackageName /** - * A `DatabaseRequestQueueRequestConflict` callback function type. - * @typedef {function(Event, DatabaseRequestQueueRequestConflict): any} DatabaseRequestQueueConflictResolutionCallback + * The default package index file such as 'index.js' + * @type {string} */ + export const DEFAULT_PACKAGE_INDEX: string; /** - * Waits for an event of `eventType` to be dispatched on a given `EventTarget`. - * @param {EventTarget} target - * @param {string} eventType - * @return {Promise} + * The default package manifest file name such as 'package.json' + * @type {string} */ - export function waitFor(target: EventTarget, eventType: string): Promise; + export const DEFAULT_PACKAGE_MANIFEST_FILE_NAME: string; /** - * Creates an opens a named `Database` instance. - * @param {string} name - * @param {?DatabaseOptions | undefined} [options] - * @return {Promise} + * The default package path prefix such as 'node_modules/' + * @type {string} */ - export function open(name: string, options?: (DatabaseOptions | undefined) | null): Promise; + export const DEFAULT_PACKAGE_PREFIX: string; /** - * Complete deletes a named `Database` instance. - * @param {string} name - * @param {?DatabaseOptions|undefined} [options] + * The default package version, when one is not provided + * @type {string} */ - export function drop(name: string, options?: (DatabaseOptions | undefined) | null): Promise; + export const DEFAULT_PACKAGE_VERSION: string; /** - * A mapping of named `Database` instances that are currently opened - * @type {Map>} + * The default license for a package' + * @type {string} */ - export const opened: Map>; + export const DEFAULT_LICENSE: string; /** - * A container for conflict resolution for a `DatabaseRequestQueue` instance - * `IDBRequest` instance. + * A container for a package name that includes a package organization identifier, + * its fully qualified name, or for relative package names, its pathname */ - export class DatabaseRequestQueueRequestConflict { + export class Name { /** - * `DatabaseRequestQueueRequestConflict` class constructor - * @param {function(any): void)} resolve - * @param {function(Error): void)} reject - * @param {function(): void)} cleanup + * Parses a package name input resolving the actual module name, including an + * organization name given. If a path includes a manifest file + * ('package.json'), then the directory containing that file is considered a + * valid package and it will be included in the returned value. If a relative + * path is given, then the path is returned if it is a valid pathname. This + * function returns `null` for bad input. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {ParsedPackageName?} */ - constructor(resolve: any, reject: any, cleanup: any); + static parse(input: string | URL, options?: { + origin?: string | URL; + manifest?: string; + } | undefined): ParsedPackageName | null; /** - * Called when a conflict is resolved. - * @param {any} argument + * Returns `true` if the given `input` can be parsed by `Name.parse` or given + * as input to the `Name` class constructor. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {boolean} */ - resolve(argument?: any): void; + static canParse(input: string | URL, options?: { + origin?: string | URL; + manifest?: string; + } | undefined): boolean; /** - * Called when a conflict is rejected - * @param {Error} error + * Creates a new `Name` from input. + * @param {string|URL} input + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @return {Name} */ - reject(error: Error): void; - #private; - } - /** - * An event dispatched on a `DatabaseRequestQueue` - */ - export class DatabaseRequestQueueEvent extends Event { + static from(input: string | URL, options?: { + origin?: string | URL; + manifest?: string; + } | undefined): Name; /** - * `DatabaseRequestQueueEvent` class constructor. - * @param {string} type - * @param {IDBRequest|IDBTransaction} request + * `Name` class constructor. + * @param {string|URL|NameOptions|Name} name + * @param {{ origin?: string | URL, manifest?: string }=} [options] + * @throws TypeError */ - constructor(type: string, request: IDBRequest | IDBTransaction); + constructor(name: string | URL | NameOptions | Name, options?: { + origin?: string | URL; + manifest?: string; + } | undefined); /** - * A reference to the underlying request for this event. - * @type {IDBRequest|IDBTransaction} + * The id of this package name. + * @type {string} */ - get request(): IDBRequest | IDBTransaction; - #private; - } - /** - * An event dispatched on a `Database` - */ - export class DatabaseEvent extends Event { + get id(): string; /** - * `DatabaseEvent` class constructor. - * @param {string} type - * @param {Database} database + * The actual package name. + * @type {string} */ - constructor(type: string, database: Database); + get name(): string; /** - * A reference to the underlying database for this event. - * @type {Database} + * An alias for 'name'. + * @type {string} */ - get database(): Database; - #private; - } - /** - * An error event dispatched on a `DatabaseRequestQueue` - */ - export class DatabaseRequestQueueErrorEvent extends ErrorEvent { + get value(): string; /** - * `DatabaseRequestQueueErrorEvent` class constructor. - * @param {string} type - * @param {IDBRequest|IDBTransaction} request - * @param {{ error: Error, cause?: Error }} options + * The origin of the package, if available. + * This value may be `null`. + * @type {string?} */ - constructor(type: string, request: IDBRequest | IDBTransaction, options: { - error: Error; - cause?: Error; - }); + get origin(): string; /** - * A reference to the underlying request for this error event. - * @type {IDBRequest|IDBTransaction} + * The package version if available. + * This value may be `null`. + * @type {string?} */ - get request(): IDBRequest | IDBTransaction; - #private; - } - /** - * A container for various `IDBRequest` and `IDBTransaction` instances - * occurring during the life cycles of a `Database` instance. - */ - export class DatabaseRequestQueue extends EventTarget { + get version(): string; /** - * Computed queue length - * @type {number} + * The actual package pathname, if given in name string. + * This value is always a string defaulting to '.' if no path + * was given in name string. + * @type {string} */ - get length(): number; + get pathname(): string; /** - * Pushes an `IDBRequest` or `IDBTransaction onto the queue and returns a - * `Promise` that resolves upon a 'success' or 'complete' event and rejects - * upon an error' event. - * @param {IDBRequest|IDBTransaction} - * @param {?DatabaseRequestQueueConflictResolutionCallback} [conflictResolutionCallback] - * @return {Promise} + * The organization name. + * This value may be `null`. + * @type {string?} */ - push(request: any, conflictResolutionCallback?: DatabaseRequestQueueConflictResolutionCallback | null): Promise; + get organization(): string; /** - * Waits for all pending requests to complete. This function will throw when - * an `IDBRequest` or `IDBTransaction` instance emits an 'error' event. - * Callers of this function can optionally specify a maximum backlog to wait - * for instead of waiting for all requests to finish. - * @param {?DatabaseRequestQueueWaitOptions | undefined} [options] + * `true` if the package name was relative, otherwise `false`. + * @type {boolean} */ - wait(options?: (DatabaseRequestQueueWaitOptions | undefined) | null): Promise; + get isRelative(): boolean; + /** + * Converts this package name to a string. + * @ignore + * @return {string} + */ + toString(): string; + /** + * Converts this `Name` instance to JSON. + * @ignore + * @return {object} + */ + toJSON(): object; #private; } /** - * An interface for reading from named databases backed by IndexedDB. + * A container for package dependencies that map a package name to a `Package` instance. */ - export class Database extends EventTarget { - /** - * `Database` class constructor. - * @param {string} name - * @param {?DatabaseOptions | undefined} [options] + export class Dependencies { + constructor(parent: any, options?: any); + get map(): Map; + get origin(): any; + add(name: any, info?: any): void; + get(name: any, options?: any): any; + entries(): IterableIterator<[any, any]>; + keys(): IterableIterator; + values(): IterableIterator; + load(options?: any): void; + [Symbol.iterator](): IterableIterator<[any, any]>; + #private; + } + /** + * A container for CommonJS module metadata, often in a `package.json` file. + */ + export class Package { + /** + * A high level class for a package name. + * @type {typeof Name} */ - constructor(name: string, options?: (DatabaseOptions | undefined) | null); + static Name: typeof Name; /** - * `true` if the `Database` is currently opening, otherwise `false`. - * A `Database` instance should not attempt to be opened if this property value - * is `true`. - * @type {boolean} + * A high level container for package dependencies. + * @type {typeof Dependencies} */ - get opening(): boolean; + static Dependencies: typeof Dependencies; /** - * `true` if the `Database` instance was successfully opened such that the - * internal `IDBDatabase` storage instance was created and can be referenced - * on the `Database` instance, otherwise `false`. - * @type {boolean} + * Creates and loads a package + * @param {string|URL|NameOptions|Name} name + * @param {PackageOptions & PackageLoadOptions=} [options] + * @return {Package} */ - get opened(): boolean; + static load(name: string | URL | NameOptions | Name, options?: (PackageOptions & PackageLoadOptions) | undefined): Package; /** - * `true` if the `Database` instance was closed or has not been opened such - * that the internal `IDBDatabase` storage instance was not created or cannot - * be referenced on the `Database` instance, otherwise `false`. - * @type {boolean} + * `Package` class constructor. + * @param {string|URL|NameOptions|Name} name + * @param {PackageOptions=} [options] */ - get closed(): boolean; + constructor(name: string | URL | NameOptions | Name, options?: PackageOptions | undefined); /** - * `true` if the `Database` is currently closing, otherwise `false`. - * A `Database` instance should not attempt to be closed if this property value - * is `true`. + * The unique ID of this `Package`, which is the absolute + * URL of the directory that contains its manifest file. + * @type {string} + */ + get id(): string; + /** + * The absolute URL to the package manifest file + * @type {string} + */ + get url(): string; + /** + * A reference to the package subpath imports and browser mappings. + * These values are typically used with its corresponding `Module` + * instance require resolvers. + * @type {object} + */ + get imports(): any; + /** + * A loader for this package, if available. This value may be `null`. + * @type {Loader} + */ + get loader(): Loader; + /** + * `true` if the package was actually "loaded", otherwise `false`. * @type {boolean} */ - get closing(): boolean; + get loaded(): boolean; /** - * The name of the `IDBDatabase` database. This value cannot be `null`. + * The name of the package. * @type {string} */ get name(): string; /** - * The version of the `IDBDatabase` database. This value may be `null`. - * @type {?string} + * The description of the package. + * @type {string} */ - get version(): string; + get description(): string; /** - * A reference to the `IDBDatabase`, if the `Database` instance was opened. - * This value may ba `null`. - * @type {?IDBDatabase} + * The organization of the package. This value may be `null`. + * @type {string?} */ - get storage(): IDBDatabase; + get organization(): string; /** - * Opens the `IDBDatabase` database optionally at a specific "version" if - * one was given upon construction of the `Database` instance. This function - * is not idempotent and will throw if the underlying `IDBDatabase` instance - * was created successfully or is in the process of opening. - * @return {Promise} + * The license of the package. + * @type {string} */ - open(): Promise; + get license(): string; /** - * Closes the `IDBDatabase` database storage, if opened. This function is not - * idempotent and will throw if the underlying `IDBDatabase` instance is - * already closed (not opened) or currently closing. - * @return {Promise} + * The version of the package. + * @type {string} */ - close(): Promise; + get version(): string; /** - * Deletes entire `Database` instance and closes after successfully - * delete storage. + * The origin for this package. + * @type {string} */ - drop(): Promise; + get origin(): string; /** - * Gets a "readonly" value by `key` in the `Database` object storage. - * @param {string} key - * @param {?DatabaseGetOptions|undefined} [options] - * @return {Promise} + * The exports mappings for the package + * @type {object} */ - get(key: string, options?: (DatabaseGetOptions | undefined) | null): Promise; + get exports(): any; /** - * Put a `value` at `key`, updating if it already exists, otherwise - * "inserting" it into the `Database` instance. - * @param {string} key - * @param {any} value - * @param {?DatabasePutOptions|undefined} [options] - * @return {Promise} + * The package type. + * @type {'commonjs'|'module'} */ - put(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; + get type(): "module" | "commonjs"; /** - * Inserts a new `value` at `key`. This function throws if a value at `key` - * already exists. - * @param {string} key - * @param {any} value - * @param {?DatabasePutOptions|undefined} [options] - * @return {Promise} + * The raw package metadata object. + * @type {object?} */ - insert(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; + get info(): any; /** - * Update a `value` at `key`, updating if it already exists, otherwise - * "inserting" it into the `Database` instance. - * @param {string} key - * @param {any} value - * @param {?DatabasePutOptions|undefined} [options] - * @return {Promise} + * @type {Dependencies} */ - update(key: string, value: any, options?: (DatabasePutOptions | undefined) | null): Promise; + get dependencies(): Dependencies; /** - * Delete a value at `key`. - * @param {string} key - * @param {?DatabaseDeleteOptions|undefined} [options] - * @return {Promise} + * An alias for `entry` + * @type {string?} */ - delete(key: string, options?: (DatabaseDeleteOptions | undefined) | null): Promise; + get main(): string; /** - * Gets a "readonly" value by `key` in the `Database` object storage. - * @param {string} key - * @param {?DatabaseEntriesOptions|undefined} [options] - * @return {Promise} + * The entry to the package + * @type {string?} */ - entries(options?: (DatabaseEntriesOptions | undefined) | null): Promise; + get entry(): string; + /** + * Load the package information at an optional `origin` with + * optional request `options`. + * @param {PackageLoadOptions=} [options] + * @throws SyntaxError + * @return {boolean} + */ + load(origin?: any, options?: PackageLoadOptions | undefined): boolean; + /** + * Resolve a file's `pathname` within the package. + * @param {string|URL} pathname + * @param {PackageResolveOptions=} [options] + * @return {string} + */ + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } - namespace _default { - export { Database }; - export { open }; - export { drop }; - } - export default _default; - /** - * A typed container for optional options given to the `Database` - * class constructor. - */ - export type DatabaseOptions = { - version?: string | undefined; - }; - /** - * A typed container for various optional options made to a `get()` function - * on a `Database` instance. - */ - export type DatabaseGetOptions = { - store?: string | undefined; - stores?: string[] | undefined; - count?: number | undefined; - }; - /** - * A typed container for various optional options made to a `put()` function - * on a `Database` instance. - */ - export type DatabasePutOptions = { - store?: string | undefined; - stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + export default Package; + export type PackageOptions = { + manifest?: string; + index?: string; + description?: string; + version?: string; + license?: string; + exports?: object; + type?: 'commonjs' | 'module'; + info?: object; + origin?: string; + dependencies?: Dependencies | object | Map; }; - /** - * A typed container for various optional options made to a `delete()` function - * on a `Database` instance. - */ - export type DatabaseDeleteOptions = { - store?: string | undefined; - stores?: string[] | undefined; + export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { + type?: 'commonjs' | 'module'; + prefix?: string; }; /** - * A typed container for optional options given to the `Database` - * class constructor. + * /** + * The default package index file such as 'index.js' */ - export type DatabaseRequestQueueWaitOptions = { - offset?: number | undefined; - backlog?: number | undefined; + export type ParsedPackageName = { + organization: string | null; + name: string; + version: string | null; + pathname: string; + url: URL; + isRelative: boolean; + hasManifest: boolean; }; - /** - * A typed container for various optional options made to a `entries()` function - * on a `Database` instance. - */ - export type DatabaseEntriesOptions = { - store?: string | undefined; - stores?: string[] | undefined; - }; - /** - * A `DatabaseRequestQueueRequestConflict` callback function type. - */ - export type DatabaseRequestQueueConflictResolutionCallback = (arg0: Event, arg1: DatabaseRequestQueueRequestConflict) => any; + import { Loader } from "socket:commonjs/loader"; } -declare module "socket:service-worker/env" { - /** - * Opens an environment for a particular scope. - * @param {EnvironmentOptions} options - * @return {Promise} - */ - export function open(options: EnvironmentOptions): Promise; - /** - * Closes an active `Environment` instance, dropping the global - * instance reference. - * @return {Promise} - */ - export function close(): Promise; - /** - * Resets an active `Environment` instance - * @return {Promise} - */ - export function reset(): Promise; - /** - * @typedef {{ - * scope: string - * }} EnvironmentOptions - */ - /** - * An event dispatched when a environment value is updated (set, delete) - */ - export class EnvironmentEvent extends Event { - /** - * `EnvironmentEvent` class constructor. - * @param {'set'|'delete'} type - * @param {object=} [entry] - */ - constructor(type: 'set' | 'delete', entry?: object | undefined); - entry: any; - } - /** - * An environment context object with persistence and durability - * for service worker environments. - */ - export class Environment extends EventTarget { - /** - * Maximum entries that will be restored from storage into the environment - * context object. - * @type {number} - */ - static MAX_CONTEXT_ENTRIES: number; - /** - * Opens an environment for a particular scope. - * @param {EnvironmentOptions} options - * @return {Environment} - */ - static open(options: EnvironmentOptions): Environment; - /** - * The current `Environment` instance - * @type {Environment?} - */ - static instance: Environment | null; +declare module "socket:console" { + export function patchGlobalConsole(globalConsole: any, options?: {}): any; + export const globalConsole: globalThis.Console; + export class Console { /** - * `Environment` class constructor * @ignore - * @param {EnvironmentOptions} options - */ - constructor(options: EnvironmentOptions); - /** - * A reference to the currently opened environment database. - * @type {import('../internal/database.js').Database} - */ - get database(): import("socket:internal/database").Database; - /** - * A proxied object for reading and writing environment state. - * Values written to this object must be cloneable with respect to the - * structured clone algorithm. - * @see {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm} - * @type {Proxy} */ - get context(): ProxyConstructor; + constructor(options: any); /** - * The current environment name. This value is also used as the - * internal database name. - * @type {string} + * @type {import('dom').Console} */ - get name(): string; + console: any; /** - * Resets the current environment to an empty state. + * @type {Map} */ - reset(): Promise; + timers: Map; /** - * Opens the environment. - * @ignore + * @type {Map} */ - open(): Promise; + counters: Map; /** - * Closes the environment database, purging existing state. - * @ignore + * @type {function?} */ - close(): Promise; - #private; - } - namespace _default { - export { Environment }; - export { close }; - export { reset }; - export { open }; + postMessage: Function | null; + write(destination: any, ...args: any[]): any; + assert(assertion: any, ...args: any[]): void; + clear(): void; + count(label?: string): void; + countReset(label?: string): void; + debug(...args: any[]): void; + dir(...args: any[]): void; + dirxml(...args: any[]): void; + error(...args: any[]): void; + info(...args: any[]): void; + log(...args: any[]): void; + table(...args: any[]): any; + time(label?: string): void; + timeEnd(label?: string): void; + timeLog(label?: string): void; + trace(...objects: any[]): void; + warn(...args: any[]): void; } - export default _default; - export type EnvironmentOptions = { - scope: string; + const _default: Console & { + Console: typeof Console; + globalConsole: globalThis.Console; }; -} - -declare module "socket:service-worker/debug" { - export function debug(...args: any[]): void; - export default debug; -} - -declare module "socket:service-worker/state" { - export const channel: BroadcastChannel; - export const state: any; - export default state; -} - -declare module "socket:service-worker/clients" { - export class Client { - constructor(options: any); - get id(): any; - get url(): any; - get type(): any; - get frameType(): any; - postMessage(message: any, optionsOrTransferables?: any): void; - #private; - } - export class WindowClient extends Client { - get focused(): boolean; - get ancestorOrigins(): any[]; - get visibilityState(): string; - focus(): Promise; - navigate(url: any): Promise; - #private; - } - export class Clients { - get(id: any): Promise; - matchAll(options?: any): Promise; - openWindow(url: any, options?: any): Promise; - claim(): Promise; - } - const _default: Clients; export default _default; } -declare module "socket:service-worker/context" { +declare module "socket:vm" { /** - * A context given to `ExtendableEvent` interfaces and provided to - * simplified service worker modules + * @ignore + * @param {object[]} transfer + * @param {object} object + * @param {object=} [options] + * @return {object[]} */ - export class Context { - /** - * `Context` class constructor. - * @param {import('./events.js').ExtendableEvent} event - */ - constructor(event: import('./events.js').ExtendableEvent); - /** - * Context data. This may be a custom protocol handler scheme data - * by default, if available. - * @type {any?} - */ - data: any | null; - /** - * The `ExtendableEvent` for this `Context` instance. - * @type {ExtendableEvent} - */ - get event(): ExtendableEvent; - /** - * An environment context object. - * @type {object?} - */ - get env(): any; - /** - * Resets the current environment context. - * @return {Promise} - */ - resetEnvironment(): Promise; - /** - * Unused, but exists for cloudflare compat. - * @ignore - */ - passThroughOnException(): void; - /** - * Tells the event dispatcher that work is ongoing. - * It can also be used to detect whether that work was successful. - * @param {Promise} promise - */ - waitUntil(promise: Promise): Promise; - /** - * TODO - */ - handled(): Promise; - /** - * Gets the client for this event context. - * @return {Promise} - */ - client(): Promise; - #private; - } - namespace _default { - export { Context }; - } - export default _default; -} - -declare module "socket:service-worker/events" { - export const textEncoder: TextEncoderStream; - export const FETCH_EVENT_TIMEOUT: number; + export function findMessageTransfers(transfers: any, object: object, options?: object | undefined): object[]; /** - * The `ExtendableEvent` interface extends the lifetime of the "install" and - * "activate" events dispatched on the global scope as part of the service - * worker lifecycle. + * @ignore + * @param {object} context */ - export class ExtendableEvent extends Event { - /** - * `ExtendableEvent` class constructor. - * @ignore - */ - constructor(...args: any[]); + export function applyInputContextReferences(context: object): void; + /** + * @ignore + * @param {object} context + */ + export function applyOutputContextReferences(context: object): void; + /** + * @ignore + * @param {object} context + */ + export function filterNonTransferableValues(context: object): void; + /** + * @ignore + * @param {object=} [currentContext] + * @param {object=} [updatedContext] + * @param {object=} [contextReference] + * @return {{ deletions: string[], merges: string[] }} + */ + export function applyContextDifferences(currentContext?: object | undefined, updatedContext?: object | undefined, contextReference?: object | undefined, preserveScriptArgs?: boolean): { + deletions: string[]; + merges: string[]; + }; + /** + * Wrap a JavaScript function source. + * @ignore + * @param {string} source + * @param {object=} [options] + */ + export function wrapFunctionSource(source: string, options?: object | undefined): string; + /** + * Gets the VM context window. + * This function will create it if it does not already exist. + * The current window will be used on Android or iOS platforms as there can + * only be one window. + * @return {Promise; + /** + * Gets the `SharedWorker` that for the VM context. + * @return {Promise} + */ + export function getContextWorker(): Promise; + /** + * Terminates the VM script context window. + * @ignore + */ + export function terminateContextWindow(): Promise; + /** + * Terminates the VM script context worker. + * @ignore + */ + export function terminateContextWorker(): Promise; + /** + * Creates a prototype object of known global reserved intrinsics. + * @ignore + */ + export function createIntrinsics(options: any): any; + /** + * Returns `true` if value is an intrinsic, otherwise `false`. + * @param {any} value + * @return {boolean} + */ + export function isIntrinsic(value: any): boolean; + /** + * Get the intrinsic type of a given `value`. + * @param {any} + * @return {function|object|null|undefined} + */ + export function getIntrinsicType(value: any): Function | object | null | undefined; + /** + * Get the intrinsic type string of a given `value`. + * @param {any} + * @return {string|null} + */ + export function getIntrinsicTypeString(value: any): string | null; + /** + * Creates a global proxy object for context execution. + * @ignore + * @param {object} context + * @return {Proxy} + */ + export function createGlobalObject(context: object, options: any): ProxyConstructor; + /** + * @ignore + * @param {string} source + * @return {boolean} + */ + export function detectFunctionSourceType(source: string): boolean; + /** + * Compiles `source` with `options` into a function. + * @ignore + * @param {string} source + * @param {object=} [options] + * @return {function} + */ + export function compileFunction(source: string, options?: object | undefined): Function; + /** + * Run `source` JavaScript in given context. The script context execution + * context is preserved until the `context` object that points to it is + * garbage collected or there are no longer any references to it and its + * associated `Script` instance. + * @param {string|object|function} source + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} + */ + export function runInContext(source: string | object | Function, options?: ScriptOptions | undefined, context?: object | undefined): Promise; + /** + * Run `source` JavaScript in new context. The script context is destroyed after + * execution. This is typically a "one off" isolated run. + * @param {string} source + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} + */ + export function runInNewContext(source: string, options?: ScriptOptions | undefined, context?: object | undefined): Promise; + /** + * Run `source` JavaScript in this current context (`globalThis`). + * @param {string} source + * @param {ScriptOptions=} [options] + * @return {Promise} + */ + export function runInThisContext(source: string, options?: ScriptOptions | undefined): Promise; + /** + * @ignore + * @param {Reference} reference + */ + export function putReference(reference: Reference): void; + /** + * Create a `Reference` for a `value` in a script `context`. + * @param {any} value + * @param {object} context + * @param {object=} [options] + * @return {Reference} + */ + export function createReference(value: any, context: object, options?: object | undefined): Reference; + /** + * Get a script context by ID or values + * @param {string|object|function} id + * @return {Reference?} + */ + export function getReference(id: string | object | Function): Reference | null; + /** + * Remove a script context reference by ID. + * @param {string} id + */ + export function removeReference(id: string): void; + /** + * Get all transferable values in the `object` hierarchy. + * @param {object} object + * @return {object[]} + */ + export function getTransferables(object: object): object[]; + /** + * @ignore + * @param {object} object + * @return {object} + */ + export function createContext(object: object): object; + /** + * Returns `true` if `object` is a "context" object. + * @param {object} + * @return {boolean} + */ + export function isContext(object: any): boolean; + /** + * A container for a context worker message channel that looks like a "worker". + * @ignore + */ + export class ContextWorkerInterface extends EventTarget { + get channel(): any; + get port(): any; + destroy(): void; + #private; + } + /** + * A container proxy for a context worker message channel that + * looks like a "worker". + * @ignore + */ + export class ContextWorkerInterfaceProxy extends EventTarget { + constructor(globals: any); + get port(): any; + #private; + } + /** + * Global reserved values that a script context may not modify. + * @type {string[]} + */ + export const RESERVED_GLOBAL_INTRINSICS: string[]; + /** + * A unique reference to a value owner by a "context object" and a + * `Script` instance. + */ + export class Reference { /** - * A context for this `ExtendableEvent` instance. - * @type {import('./context.js').Context} + * Predicate function to determine if a `value` is an internal or external + * script reference value. + * @param {amy} value + * @return {boolean} */ - get context(): Context; + static isReference(value: amy): boolean; /** - * A promise that can be awaited which waits for this `ExtendableEvent` - * instance no longer has pending promises. - * @type {Promise} + * `Reference` class constructor. + * @param {string} id + * @param {any} value + * @param {object=} [context] + * @param {object=} [options] */ - get awaiting(): Promise; + constructor(id: string, value: any, context?: object | undefined, options?: object | undefined); /** - * The number of pending promises - * @type {number} + * The unique id of the reference + * @type {string} */ - get pendingPromises(): number; + get id(): string; /** - * `true` if the `ExtendableEvent` instance is considered "active", - * otherwise `false`. - * @type {boolean} + * The underling primitive type of the reference value. + * @ignore + * @type {'undefined'|'object'|'number'|'boolean'|'function'|'symbol'} */ - get isActive(): boolean; + get type(): "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; /** - * Tells the event dispatcher that work is ongoing. - * It can also be used to detect whether that work was successful. - * @param {Promise} promise + * The underlying value of the reference. + * @type {any?} */ - waitUntil(promise: Promise): void; + get value(): any; /** - * Returns a promise that this `ExtendableEvent` instance is waiting for. - * @return {Promise} + * The name of the type. + * @type {string?} */ - waitsFor(): Promise; - #private; - } - /** - * This is the event type for "fetch" events dispatched on the service worker - * global scope. It contains information about the fetch, including the - * request and how the receiver will treat the response. - */ - export class FetchEvent extends ExtendableEvent { - static defaultHeaders: Headers; + get name(): string; /** - * `FetchEvent` class constructor. - * @ignore - * @param {string=} [type = 'fetch'] - * @param {object=} [options] + * The `Script` this value belongs to, if available. + * @type {Script?} */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * The handled property of the `FetchEvent` interface returns a promise - * indicating if the event has been handled by the fetch algorithm or not. - * This property allows executing code after the browser has consumed a - * response, and is usually used together with the `waitUntil()` method. - * @type {Promise} - */ - get handled(): Promise; - /** - * The request read-only property of the `FetchEvent` interface returns the - * `Request` that triggered the event handler. - * @type {Request} - */ - get request(): Request; - /** - * The `clientId` read-only property of the `FetchEvent` interface returns - * the id of the Client that the current service worker is controlling. - * @type {string} - */ - get clientId(): string; - /** - * @ignore - * @type {string} - */ - get resultingClientId(): string; + get script(): Script; /** - * @ignore - * @type {string} + * The "context object" this reference value belongs to. + * @type {object?} */ - get replacesClientId(): string; + get context(): any; /** - * @ignore + * A boolean value to indicate if the underlying reference value is an + * intrinsic value. * @type {boolean} */ - get isReload(): boolean; - /** - * @ignore - * @type {Promise} - */ - get preloadResponse(): Promise; - /** - * The `respondWith()` method of `FetchEvent` prevents the webview's - * default fetch handling, and allows you to provide a promise for a - * `Response` yourself. - * @param {Response|Promise} response - */ - respondWith(response: Response | Promise): void; - #private; - } - export class ExtendableMessageEvent extends ExtendableEvent { - /** - * `ExtendableMessageEvent` class constructor. - * @param {string=} [type = 'message'] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); + get isIntrinsic(): boolean; /** - * @type {any} + * A boolean value to indicate if the underlying reference value is an + * external reference value. + * @type {boolean} */ - get data(): any; + get isExternal(): boolean; /** - * @type {MessagePort[]} + * The intrinsic type this reference may be an instance of or directly refer to. + * @type {function|object} */ - get ports(): MessagePort[]; + get intrinsicType(): any; /** - * @type {import('./clients.js').Client?} + * Releases strongly held value and weak references + * to the "context object". */ - get source(): import("socket:service-worker/clients").Client; + release(): void; /** - * @type {string} + * Converts this `Reference` to a JSON object. + * @param {boolean=} [includeValue = false] */ - get lastEventId(): string; - #private; - } - export class NotificationEvent extends ExtendableEvent { - constructor(type: any, options: any); - get action(): string; - get notification(): any; + toJSON(includeValue?: boolean | undefined): { + __vmScriptReference__: boolean; + id: string; + type: "number" | "boolean" | "symbol" | "undefined" | "object" | "function"; + name: string; + isIntrinsic: boolean; + intrinsicType: string; + }; #private; } - namespace _default { - export { ExtendableMessageEvent }; - export { ExtendableEvent }; - export { FetchEvent }; - } - export default _default; - import { Context } from "socket:service-worker/context"; -} - -declare module "socket:http/adapters" { /** * @typedef {{ - * Connection: typeof import('../http.js').Connection, - * globalAgent: import('../http.js').Agent, - * IncomingMessage: typeof import('../http.js').IncomingMessage, - * ServerResponse: typeof import('../http.js').ServerResponse, - * STATUS_CODES: object, - * METHODS: string[] - * }} HTTPModuleInterface + * filename?: string, + * context?: object + * }} ScriptOptions */ /** - * An abstract base clase for a HTTP server adapter. + * A `Script` is a container for raw JavaScript to be executed in + * a completely isolated virtual machine context, optionally with + * user supplied context. Context objects references are not actually + * shared, but instead provided to the script execution context using the + * structured cloning algorithm used by the Message Channel API. Context + * differences are computed and applied after execution so the user supplied + * context object realizes context changes after script execution. All script + * sources run in an "async" context so a "top level await" should work. */ - export class ServerAdapter extends EventTarget { + export class Script extends EventTarget { /** - * `ServerAdapter` class constructor. - * @ignore - * @param {import('../http.js').Server} server - * @param {HTTPModuleInterface} httpInterface + * `Script` class constructor + * @param {string} source + * @param {ScriptOptions} [options] */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(source: string, options?: ScriptOptions); /** - * A readonly reference to the underlying HTTP(S) server - * for this adapter. - * @type {import('../http.js').Server} + * The script identifier. */ - get server(): import("socket:http").Server; + get id(): any; /** - * A readonly reference to the underlying HTTP(S) module interface - * for creating various HTTP module class objects. - * @type {HTTPModuleInterface} + * The source for this script. + * @type {string} */ - get httpInterface(): HTTPModuleInterface; + get source(): string; /** - * A readonly reference to the `AsyncContext.Variable` associated with this - * `ServerAdapter` instance. + * The filename for this script. + * @type {string} */ - get context(): import("socket:async/context").Variable; + get filename(): string; /** - * Called when the adapter should destroy itself. - * @abstract + * A promise that resolves when the script is ready. + * @type {Promise} */ - destroy(): Promise; - #private; - } - /** - * A HTTP adapter for running a HTTP server in a service worker that uses the - * "fetch" event for the request and response lifecycle. - */ - export class ServiceWorkerServerAdapter extends ServerAdapter { + get ready(): Promise; /** - * Handles the 'install' service worker event. - * @ignore - * @param {import('../service-worker/events.js').ExtendableEvent} event + * Destroy the script execution context. + * @return {Promise} */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise; + destroy(): Promise; /** - * Handles the 'activate' service worker event. - * @ignore - * @param {import('../service-worker/events.js').ExtendableEvent} event + * Run `source` JavaScript in given context. The script context execution + * context is preserved until the `context` object that points to it is + * garbage collected or there are no longer any references to it and its + * associated `Script` instance. + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise; + runInContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; /** - * Handles the 'fetch' service worker event. - * @ignore - * @param {import('../service-worker/events.js').FetchEvent} + * Run `source` JavaScript in new context. The script context is destroyed after + * execution. This is typically a "one off" isolated run. + * @param {ScriptOptions=} [options] + * @param {object=} [context] + * @return {Promise} */ - onFetch(event: any): Promise; + runInNewContext(context?: object | undefined, options?: ScriptOptions | undefined): Promise; + /** + * Run `source` JavaScript in this current context (`globalThis`). + * @param {ScriptOptions=} [options] + * @return {Promise} + */ + runInThisContext(options?: ScriptOptions | undefined): Promise; + #private; } namespace _default { - export { ServerAdapter }; - export { ServiceWorkerServerAdapter }; + export { createGlobalObject }; + export { compileFunction }; + export { createReference }; + export { getContextWindow }; + export { getContextWorker }; + export { getReference }; + export { getTransferables }; + export { putReference }; + export { Reference }; + export { removeReference }; + export { runInContext }; + export { runInNewContext }; + export { runInThisContext }; + export { Script }; + export { createContext }; + export { isContext }; } export default _default; - export type HTTPModuleInterface = { - Connection: typeof import("socket:http").Connection; - globalAgent: import("socket:http").Agent; - IncomingMessage: typeof import("socket:http").IncomingMessage; - ServerResponse: typeof import("socket:http").ServerResponse; - STATUS_CODES: object; - METHODS: string[]; + export type ScriptOptions = { + filename?: string; + context?: object; }; } -declare module "socket:http" { +declare module "socket:worker_threads/init" { + export const SHARE_ENV: unique symbol; + export const isMainThread: boolean; + export namespace state { + export { isMainThread }; + export let parentPort: any; + export let mainPort: any; + export let workerData: any; + export let url: any; + export let env: {}; + export let id: number; + } + namespace _default { + export { state }; + } + export default _default; +} + +declare module "socket:worker_threads" { /** - * Makes a HTTP or `socket:` GET request. A simplified alias to `request()`. - * @param {string|object} optionsOrURL - * @param {(object|function)=} [options] - * @param {function=} [callback] - * @return {ClientRequest} + * Set shared worker environment data. + * @param {string} key + * @param {any} value */ - export function get(optionsOrURL: string | object, options?: (object | Function) | undefined, callback?: Function | undefined): ClientRequest; + export function setEnvironmentData(key: string, value: any): void; /** - * Creates a HTTP server that can listen for incoming requests. - * Requests that are dispatched to this server depend on the context - * in which it is created, such as a service worker which will use a - * "fetch event" adapter. - * @param {object|function=} [options] - * @param {function=} [callback] - * @return {Server} + * Get shared worker environment data. + * @param {string} key + * @return {any} */ - export function createServer(options?: (object | Function) | undefined, callback?: Function | undefined): Server; + export function getEnvironmentData(key: string): any; /** - * All known possible HTTP methods. - * @type {string[]} + + * A pool of known worker threads. + * @type {} */ - export const METHODS: string[]; + export const workers: () => () => any; /** - * A mapping of status codes to status texts - * @type {object} + * `true` if this is the "main" thread, otherwise `false` + * The "main" thread is the top level webview window. + * @type {boolean} */ - export const STATUS_CODES: object; - export const CONTINUE: 100; - export const SWITCHING_PROTOCOLS: 101; - export const PROCESSING: 102; - export const EARLY_HINTS: 103; - export const OK: 200; - export const CREATED: 201; - export const ACCEPTED: 202; - export const NONAUTHORITATIVE_INFORMATION: 203; - export const NO_CONTENT: 204; - export const RESET_CONTENT: 205; - export const PARTIAL_CONTENT: 206; - export const MULTISTATUS: 207; - export const ALREADY_REPORTED: 208; - export const IM_USED: 226; - export const MULTIPLE_CHOICES: 300; - export const MOVED_PERMANENTLY: 301; - export const FOUND: 302; - export const SEE_OTHER: 303; - export const NOT_MODIFIED: 304; - export const USE_PROXY: 305; - export const TEMPORARY_REDIRECT: 307; - export const PERMANENT_REDIRECT: 308; - export const BAD_REQUEST: 400; - export const UNAUTHORIZED: 401; - export const PAYMENT_REQUIRED: 402; - export const FORBIDDEN: 403; - export const NOT_FOUND: 404; - export const METHOD_NOT_ALLOWED: 405; - export const NOT_ACCEPTABLE: 406; - export const PROXY_AUTHENTICATION_REQUIRED: 407; - export const REQUEST_TIMEOUT: 408; - export const CONFLICT: 409; - export const GONE: 410; - export const LENGTH_REQUIRED: 411; - export const PRECONDITION_FAILED: 412; - export const PAYLOAD_TOO_LARGE: 413; - export const URI_TOO_LONG: 414; - export const UNSUPPORTED_MEDIA_TYPE: 415; - export const RANGE_NOT_SATISFIABLE: 416; - export const EXPECTATION_FAILED: 417; - export const IM_A_TEAPOT: 418; - export const MISDIRECTED_REQUEST: 421; - export const UNPROCESSABLE_ENTITY: 422; - export const LOCKED: 423; - export const FAILED_DEPENDENCY: 424; - export const TOO_EARLY: 425; - export const UPGRADE_REQUIRED: 426; - export const PRECONDITION_REQUIRED: 428; - export const TOO_MANY_REQUESTS: 429; - export const REQUEST_HEADER_FIELDS_TOO_LARGE: 431; - export const UNAVAILABLE_FOR_LEGAL_REASONS: 451; - export const INTERNAL_SERVER_ERROR: 500; - export const NOT_IMPLEMENTED: 501; - export const BAD_GATEWAY: 502; - export const SERVICE_UNAVAILABLE: 503; - export const GATEWAY_TIMEOUT: 504; - export const HTTP_VERSION_NOT_SUPPORTED: 505; - export const VARIANT_ALSO_NEGOTIATES: 506; - export const INSUFFICIENT_STORAGE: 507; - export const LOOP_DETECTED: 508; - export const BANDWIDTH_LIMIT_EXCEEDED: 509; - export const NOT_EXTENDED: 510; - export const NETWORK_AUTHENTICATION_REQUIRED: 511; + export const isMainThread: boolean; /** - * The parent class of `ClientRequest` and `ServerResponse`. - * It is an abstract outgoing message from the perspective of the - * participants of an HTTP transaction. - * @see {@link https://nodejs.org/api/http.html#class-httpoutgoingmessage} + * The main thread `MessagePort` which is `null` when the + * current context is not the "main thread". + * @type {MessagePort?} */ - export class OutgoingMessage extends Writable { - /** - * `OutgoingMessage` class constructor. - * @ignore - */ - constructor(); - /** - * `true` if the headers were sent - * @type {boolean} - */ - headersSent: boolean; - /** - * Internal buffers - * @ignore - * @type {Buffer[]} - */ - get buffers(): Buffer[]; - /** - * An object of the outgoing message headers. - * This is equivalent to `getHeaders()` - * @type {object} - */ - get headers(): any; + export const mainPort: MessagePort | null; + /** + * A worker thread `BroadcastChannel` class. + */ + export class BroadcastChannel extends globalThis.BroadcastChannel { + } + /** + * A worker thread `MessageChannel` class. + */ + export class MessageChannel extends globalThis.MessageChannel { + } + /** + * A worker thread `MessagePort` class. + */ + export class MessagePort extends globalThis.MessagePort { + } + /** + * The current unique thread ID. + * @type {number} + */ + export const threadId: number; + /** + * The parent `MessagePort` instance + * @type {MessagePort?} + */ + export const parentPort: MessagePort | null; + /** + * Transferred "worker data" when creating a new `Worker` instance. + * @type {any?} + */ + export const workerData: any | null; + export class Pipe extends AsyncResource { /** + * `Pipe` class constructor. + * @param {Childworker} worker * @ignore */ - get socket(): this; + constructor(worker: Childworker); /** - * `true` if the write state is "ended" + * `true` if the pipe is still reading, otherwise `false`. * @type {boolean} */ - get writableEnded(): boolean; + get reading(): boolean; /** - * `true` if the write state is "finished" - * @type {boolean} + * Destroys the pipe */ - get writableFinished(): boolean; + destroy(): void; + #private; + } + /** + * @typedef {{ + * env?: object, + * stdin?: boolean = false, + * stdout?: boolean = false, + * stderr?: boolean = false, + * workerData?: any, + * transferList?: any[], + * eval?: boolean = false + * }} WorkerOptions + + /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export class Worker extends EventEmitter { /** - * The number of buffered bytes. - * @type {number} + * `Worker` class constructor. + * @param {string} filename + * @param {WorkerOptions=} [options] */ - get writableLength(): number; + constructor(filename: string, options?: WorkerOptions | undefined); /** + * Handles incoming worker messages. * @ignore - * @type {boolean} + * @param {MessageEvent} event */ - get writableObjectMode(): boolean; + onWorkerMessage(event: MessageEvent): boolean; /** + * Handles process environment change events * @ignore + * @param {import('./process.js').ProcessEnvironmentEvent} event */ - get writableCorked(): number; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** - * The `highWaterMark` of the writable stream. + * The unique ID for this `Worker` thread instace. * @type {number} */ - get writableHighWaterMark(): number; - /** - * @ignore - * @return {OutgoingMessage} - */ - addTrailers(headers: any): OutgoingMessage; - /** - * @ignore - * @return {OutgoingMessage} - */ - cork(): OutgoingMessage; - /** - * @ignore - * @return {OutgoingMessage} - */ - uncork(): OutgoingMessage; + get id(): number; + get threadId(): number; /** - * Destroys the message. - * Once a socket is associated with the message and is connected, - * that socket will be destroyed as well. - * @param {Error?} [err] - * @return {OutgoingMessage} + * A `Writable` standard input stream if `{ stdin: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Writable?} */ - destroy(err?: Error | null): OutgoingMessage; + get stdin(): Writable; /** - * Finishes the outgoing message. - * @param {(Buffer|Uint8Array|string|function)=} [chunk] - * @param {(string|function)=} [encoding] - * @param {function=} [callback] - * @return {OutgoingMessage} + * A `Readable` standard output stream if `{ stdout: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} */ - end(chunk?: (Buffer | Uint8Array | string | Function) | undefined, encoding?: (string | Function) | undefined, callback?: Function | undefined): OutgoingMessage; + get stdout(): Readable; /** - * Append a single header value for the header object. - * @param {string} name - * @param {string|string[]} value - * @return {OutgoingMessage} + * A `Readable` standard error stream if `{ stderr: true }` was set when + * creating this `Worker` instance. + * @type {import('./stream.js').Readable?} */ - appendHeader(name: string, value: string | string[]): OutgoingMessage; + get stderr(): Readable; /** - * Append a single header value for the header object. - * @param {string} name - * @param {string} value - * @return {OutgoingMessage} + * Terminates the `Worker` instance */ - setHeader(name: string, value: string): OutgoingMessage; - /** - * Flushes the message headers. - */ - flushHeaders(): void; - /** - * Gets the value of the HTTP header with the given name. - * If that header is not set, the returned value will be `undefined`. - * @param {string} - * @return {string|undefined} - */ - getHeader(name: any): string | undefined; - /** - * Returns an array containing the unique names of the current outgoing - * headers. All names are lowercase. - * @return {string[]} - */ - getHeaderNames(): string[]; + terminate(): void; + postMessage(...args: any[]): void; + #private; + } + namespace _default { + export { Worker }; + export { isMainThread }; + export { parentPort }; + export { setEnvironmentData }; + export { getEnvironmentData }; + export { workerData }; + export { threadId }; + export { SHARE_ENV }; + } + export default _default; + /** + * /** + * A worker thread that can communicate directly with a parent thread, + * share environment data, and process streamed data. + */ + export type WorkerOptions = { + env?: object; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + workerData?: any; + transferList?: any[]; + eval?: boolean; + }; + import { AsyncResource } from "socket:async/resource"; + import { EventEmitter } from "socket:events"; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; + import { SHARE_ENV } from "socket:worker_threads/init"; + import init from "socket:worker_threads/init"; + export { SHARE_ENV, init }; +} + +declare module "socket:child_process" { + /** + * Spawns a child process exeucting `command` with `args` + * @param {string} command + * @param {string[]|object=} [args] + * @param {object=} [options + * @return {ChildProcess} + */ + export function spawn(command: string, args?: (string[] | object) | undefined, options?: object | undefined): ChildProcess; + export function exec(command: any, options: any, callback: any): ChildProcess & { + then(resolve: any, reject: any): Promise; + catch(reject: any): Promise; + finally(next: any): Promise; + }; + export function execSync(command: any, options: any): any; + export class Pipe extends AsyncResource { /** + * `Pipe` class constructor. + * @param {ChildProcess} process * @ignore */ - getRawHeaderNames(): string[]; + constructor(process: ChildProcess); /** - * Returns a copy of the HTTP headers as an object. - * @return {object} + * `true` if the pipe is still reading, otherwise `false`. + * @type {boolean} */ - getHeaders(): object; + get reading(): boolean; /** - * Returns true if the header identified by name is currently set in the - * outgoing headers. The header name is case-insensitive. - * @param {string} name - * @return {boolean} + * @type {import('./process')} */ - hasHeader(name: string): boolean; + get process(): typeof import("socket:process"); /** - * Removes a header that is queued for implicit sending. - * @param {string} name + * Destroys the pipe */ - removeHeader(name: string): void; + destroy(): void; + #private; + } + export class ChildProcess extends EventEmitter { /** - * Sets the outgoing message timeout with an optional callback. - * @param {number} timeout - * @param {function=} [callback] - * @return {OutgoingMessage} + * `ChildProcess` class constructor. + * @param {{ + * env?: object, + * stdin?: boolean, + * stdout?: boolean, + * stderr?: boolean, + * signal?: AbortSigal, + * }=} [options] */ - setTimeout(timeout: number, callback?: Function | undefined): OutgoingMessage; + constructor(options?: { + env?: object; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + signal?: AbortSigal; + } | undefined); /** * @ignore + * @type {Pipe} */ - _implicitHeader(): void; - #private; - } - /** - * An `IncomingMessage` object is created by `Server` or `ClientRequest` and - * passed as the first argument to the 'request' and 'response' event - * respectively. - * It may be used to access response status, headers, and data. - * @see {@link https://nodejs.org/api/http.html#class-httpincomingmessage} - */ - export class IncomingMessage extends Readable { + get pipe(): Pipe; /** - * `IncomingMessage` class constructor. - * @ignore - * @param {object} options + * `true` if the child process was killed with kill()`, + * otherwise `false`. + * @type {boolean} */ - constructor(options: object); - set url(url: string); + get killed(): boolean; /** - * The URL for this incoming message. This value is not absolute with - * respect to the protocol and hostname. It includes the path and search - * query component parameters. - * @type {string} + * The process identifier for the child process. This value is + * `> 0` if the process was spawned successfully, otherwise `0`. + * @type {number} */ - get url(): string; + get pid(): number; /** - * @type {Server} + * The executable file name of the child process that is launched. This + * value is `null` until the child process has successfully been spawned. + * @type {string?} */ - get server(): exports.Server; + get spawnfile(): string; /** - * @type {AsyncContext.Variable} + * The full list of command-line arguments the child process was spawned with. + * This value is an empty array until the child process has successfully been + * spawned. + * @type {string[]} */ - get context(): typeof import("socket:async/context").Variable; + get spawnargs(): string[]; /** - * This property will be `true` if a complete HTTP message has been received - * and successfully parsed. + * Always `false` as the IPC messaging is not supported. * @type {boolean} */ - get complete(): boolean; + get connected(): boolean; /** - * An object of the incoming message headers. - * @type {object} + * The child process exit code. This value is `null` if the child process + * is still running, otherwise it is a positive integer. + * @type {number?} */ - get headers(): any; + get exitCode(): number; /** - * Similar to `message.headers`, but there is no join logic and the values - * are always arrays of strings, even for headers received just once. - * @type {object} + * If available, the underlying `stdin` writable stream for + * the child process. + * @type {import('./stream').Writable?} */ - get headersDistinct(): any; + get stdin(): import("socket:stream").Writable; /** - * The HTTP major version of this request. - * @type {number} + * If available, the underlying `stdout` readable stream for + * the child process. + * @type {import('./stream').Readable?} */ - get httpVersionMajor(): number; + get stdout(): import("socket:stream").Readable; /** - * The HTTP minor version of this request. - * @type {number} + * If available, the underlying `stderr` readable stream for + * the child process. + * @type {import('./stream').Readable?} */ - get httpVersionMinor(): number; + get stderr(): import("socket:stream").Readable; /** - * The HTTP version string. - * A concatenation of `httpVersionMajor` and `httpVersionMinor`. - * @type {string} + * The underlying worker thread. + * @ignore + * @type {import('./worker_threads').Worker} */ - get httpVersion(): string; + get worker(): Worker; /** - * The HTTP request method. - * @type {string} + * This function does nothing, but is present for nodejs compat. */ - get method(): string; + disconnect(): boolean; /** - * The raw request/response headers list potentially as they were received. - * @type {string[]} + * This function does nothing, but is present for nodejs compat. + * @return {boolean} */ - get rawHeaders(): string[]; + send(): boolean; /** - * @ignore + * This function does nothing, but is present for nodejs compat. */ - get rawTrailers(): any[]; + ref(): boolean; /** - * @ignore + * This function does nothing, but is present for nodejs compat. */ - get socket(): this; + unref(): boolean; /** - * The HTTP request status code. - * Only valid for response obtained from `ClientRequest`. - * @type {number} + * Kills the child process. This function throws an error if the child + * process has not been spawned or is already killed. + * @param {number|string} signal */ - get statusCode(): number; + kill(...args: any[]): this; /** - * The HTTP response status message (reason phrase). - * Such as "OK" or "Internal Server Error." - * Only valid for response obtained from `ClientRequest`. - * @type {string?} + * Spawns the child process. This function will thrown an error if the process + * is already spawned. + * @param {string} command + * @param {string[]=} [args] + * @return {ChildProcess} */ - get statusMessage(): string; + spawn(...args?: string[] | undefined): ChildProcess; /** - * An alias for `statusCode` - * @type {number} + * `EventTarget` based `addEventListener` method. + * @param {string} event + * @param {function(Event)} callback + * @param {{ once?: false }} [options] */ - get status(): number; + addEventListener(event: string, callback: (arg0: Event) => any, options?: { + once?: false; + }): void; /** - * An alias for `statusMessage` - * @type {string?} + * `EventTarget` based `removeEventListener` method. + * @param {string} event + * @param {function(Event)} callback + * @param {{ once?: false }} [options] */ - get statusText(): string; - /** - * @ignore - */ - get trailers(): {}; - /** - * @ignore - */ - get trailersDistinct(): {}; - /** - * Gets the value of the HTTP header with the given name. - * If that header is not set, the returned value will be `undefined`. - * @param {string} - * @return {string|undefined} - */ - getHeader(name: any): string | undefined; - /** - * Returns an array containing the unique names of the current outgoing - * headers. All names are lowercase. - * @return {string[]} - */ - getHeaderNames(): string[]; - /** - * @ignore - */ - getRawHeaderNames(): string[]; - /** - * Returns a copy of the HTTP headers as an object. - * @return {object} - */ - getHeaders(): object; - /** - * Returns true if the header identified by name is currently set in the - * outgoing headers. The header name is case-insensitive. - * @param {string} name - * @return {boolean} - */ - hasHeader(name: string): boolean; - /** - * Sets the incoming message timeout with an optional callback. - * @param {number} timeout - * @param {function=} [callback] - * @return {IncomingMessage} - */ - setTimeout(timeout: number, callback?: Function | undefined): IncomingMessage; - #private; - } - /** - * An object that is created internally and returned from `request()`. - * @see {@link https://nodejs.org/api/http.html#class-httpclientrequest} - */ - export class ClientRequest extends exports.OutgoingMessage { - /** - * `ClientRequest` class constructor. - * @ignore - * @param {object} options - */ - constructor(options: object); - /** - * The HTTP request method. - * @type {string} - */ - get method(): string; - /** - * The request protocol - * @type {string?} - */ - get protocol(): string; - /** - * The request path. - * @type {string} - */ - get path(): string; - /** - * The request host name (including port). - * @type {string?} - */ - get host(): string; - /** - * The URL for this outgoing message. This value is not absolute with - * respect to the protocol and hostname. It includes the path and search - * query component parameters. - * @type {string} - */ - get url(): string; - /** - * @ignore - * @type {boolean} - */ - get finished(): boolean; - /** - * @ignore - * @type {boolean} - */ - get reusedSocket(): boolean; - /** - * @ignore - * @param {boolean=} [value] - * @return {ClientRequest} - */ - setNoDelay(value?: boolean | undefined): ClientRequest; - /** - * @ignore - * @param {boolean=} [enable] - * @param {number=} [initialDelay] - * @return {ClientRequest} - */ - setSocketKeepAlive(enable?: boolean | undefined, initialDelay?: number | undefined): ClientRequest; + removeEventListener(event: string, callback: (arg0: Event) => any): void; #private; } - /** - * An object that is created internally by a `Server` instance, not by the user. - * It is passed as the second parameter to the 'request' event. - * @see {@link https://nodejs.org/api/http.html#class-httpserverresponse} - */ - export class ServerResponse extends exports.OutgoingMessage { - /** - * `ServerResponse` class constructor. - * @param {object} options - */ - constructor(options: object); - /** - * @type {Server} - */ - get server(): exports.Server; - /** - * A reference to the original HTTP request object. - * @type {IncomingMessage} - */ - get request(): exports.IncomingMessage; - /** - * A reference to the original HTTP request object. - * @type {IncomingMessage} - */ - get req(): exports.IncomingMessage; - set statusCode(statusCode: number); - /** - * The HTTP request status code. - * Only valid for response obtained from `ClientRequest`. - * @type {number} - */ - get statusCode(): number; - set statusMessage(statusMessage: string); - /** - * The HTTP response status message (reason phrase). - * Such as "OK" or "Internal Server Error." - * Only valid for response obtained from `ClientRequest`. - * @type {string?} - */ - get statusMessage(): string; - set status(status: number); - /** - * An alias for `statusCode` - * @type {number} - */ - get status(): number; - set statusText(statusText: string); - /** - * An alias for `statusMessage` - * @type {string?} - */ - get statusText(): string; - set sendDate(value: boolean); - /** - * If `true`, the "Date" header will be automatically generated and sent in - * the response if it is not already present in the headers. - * Defaults to `true`. - * @type {boolean} - */ - get sendDate(): boolean; - /** - * @ignore - */ - writeContinue(): this; - /** - * @ignore - */ - writeEarlyHints(): this; - /** - * @ignore - */ - writeProcessing(): this; - /** - * Writes the response header to the request. - * The `statusCode` is a 3-digit HTTP status code, like 200 or 404. - * The last argument, `headers`, are the response headers. - * Optionally one can give a human-readable `statusMessage` - * as the second argument. - * @param {number|string} statusCode - * @param {string|object|string[]} [statusMessage] - * @param {object|string[]} [headers] - * @return {ClientRequest} - */ - writeHead(statusCode: number | string, statusMessage?: string | object | string[], headers?: object | string[]): ClientRequest; - #private; + export function execFile(command: any, options: any, callback: any): ChildProcess & { + then(resolve: any, reject: any): Promise; + catch(reject: any): Promise; + finally(next: any): Promise; + }; + namespace _default { + export { ChildProcess }; + export { spawn }; + export { execFile }; + export { exec }; } + export default _default; + import { AsyncResource } from "socket:async/resource"; + import { EventEmitter } from "socket:events"; + import { Worker } from "socket:worker_threads"; +} + +declare module "socket:constants" { + export * from "socket:fs/constants"; + export * from "socket:window/constants"; + export const E2BIG: any; + export const EACCES: any; + export const EADDRINUSE: any; + export const EADDRNOTAVAIL: any; + export const EAFNOSUPPORT: any; + export const EAGAIN: any; + export const EALREADY: any; + export const EBADF: any; + export const EBADMSG: any; + export const EBUSY: any; + export const ECANCELED: any; + export const ECHILD: any; + export const ECONNABORTED: any; + export const ECONNREFUSED: any; + export const ECONNRESET: any; + export const EDEADLK: any; + export const EDESTADDRREQ: any; + export const EDOM: any; + export const EDQUOT: any; + export const EEXIST: any; + export const EFAULT: any; + export const EFBIG: any; + export const EHOSTUNREACH: any; + export const EIDRM: any; + export const EILSEQ: any; + export const EINPROGRESS: any; + export const EINTR: any; + export const EINVAL: any; + export const EIO: any; + export const EISCONN: any; + export const EISDIR: any; + export const ELOOP: any; + export const EMFILE: any; + export const EMLINK: any; + export const EMSGSIZE: any; + export const EMULTIHOP: any; + export const ENAMETOOLONG: any; + export const ENETDOWN: any; + export const ENETRESET: any; + export const ENETUNREACH: any; + export const ENFILE: any; + export const ENOBUFS: any; + export const ENODATA: any; + export const ENODEV: any; + export const ENOENT: any; + export const ENOEXEC: any; + export const ENOLCK: any; + export const ENOLINK: any; + export const ENOMEM: any; + export const ENOMSG: any; + export const ENOPROTOOPT: any; + export const ENOSPC: any; + export const ENOSR: any; + export const ENOSTR: any; + export const ENOSYS: any; + export const ENOTCONN: any; + export const ENOTDIR: any; + export const ENOTEMPTY: any; + export const ENOTSOCK: any; + export const ENOTSUP: any; + export const ENOTTY: any; + export const ENXIO: any; + export const EOPNOTSUPP: any; + export const EOVERFLOW: any; + export const EPERM: any; + export const EPIPE: any; + export const EPROTO: any; + export const EPROTONOSUPPORT: any; + export const EPROTOTYPE: any; + export const ERANGE: any; + export const EROFS: any; + export const ESPIPE: any; + export const ESRCH: any; + export const ESTALE: any; + export const ETIME: any; + export const ETIMEDOUT: any; + export const ETXTBSY: any; + export const EWOULDBLOCK: any; + export const EXDEV: any; + export const SIGHUP: any; + export const SIGINT: any; + export const SIGQUIT: any; + export const SIGILL: any; + export const SIGTRAP: any; + export const SIGABRT: any; + export const SIGIOT: any; + export const SIGBUS: any; + export const SIGFPE: any; + export const SIGKILL: any; + export const SIGUSR1: any; + export const SIGSEGV: any; + export const SIGUSR2: any; + export const SIGPIPE: any; + export const SIGALRM: any; + export const SIGTERM: any; + export const SIGCHLD: any; + export const SIGCONT: any; + export const SIGSTOP: any; + export const SIGTSTP: any; + export const SIGTTIN: any; + export const SIGTTOU: any; + export const SIGURG: any; + export const SIGXCPU: any; + export const SIGXFSZ: any; + export const SIGVTALRM: any; + export const SIGPROF: any; + export const SIGWINCH: any; + export const SIGIO: any; + export const SIGINFO: any; + export const SIGSYS: any; + const _default: any; + export default _default; +} + +declare module "socket:ip" { /** - * An options object container for an `Agent` instance. + * Normalizes input as an IPv4 address string + * @param {string|object|string[]|Uint8Array} input + * @return {string} */ - export class AgentOptions { - /** - * `AgentOptions` class constructor. - * @ignore - * @param {{ - * keepAlive?: boolean, - * timeout?: number - * }} [options] - */ - constructor(options?: { - keepAlive?: boolean; - timeout?: number; - }); - keepAlive: boolean; - timeout: number; - } + export function normalizeIPv4(input: string | object | string[] | Uint8Array): string; /** - * An Agent is responsible for managing connection persistence - * and reuse for HTTP clients. - * @see {@link https://nodejs.org/api/http.html#class-httpagent} + * Determines if an input `string` is in IP address version 4 format. + * @param {string|object|string[]|Uint8Array} input + * @return {boolean} */ - export class Agent extends EventEmitter { - /** - * `Agent` class constructor. - * @param {AgentOptions=} [options] - */ - constructor(options?: AgentOptions | undefined); - defaultProtocol: string; - options: any; - requests: Set; - sockets: {}; - maxFreeSockets: number; - maxTotalSockets: number; - maxSockets: number; - /** - * @ignore - */ - get freeSockets(): {}; - /** - * @ignore - * @param {object} options - */ - getName(options: object): string; - /** - * Produces a socket/stream to be used for HTTP requests. - * @param {object} options - * @param {function(Duplex)=} [callback] - * @return {Duplex} - */ - createConnection(options: object, callback?: ((arg0: Duplex) => any) | undefined): Duplex; - /** - * @ignore - */ - keepSocketAlive(): void; - /** - * @ignore - */ - reuseSocket(): void; - /** - * @ignore - */ - destroy(): void; + export function isIPv4(input: string | object | string[] | Uint8Array): boolean; + namespace _default { + export { normalizeIPv4 }; + export { isIPv4 }; } + export default _default; +} + +declare module "socket:dns/promises" { /** - * The global and default HTTP agent. - * @type {Agent} + * @async + * @see {@link https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options} + * @param {string} hostname - The host name to resolve. + * @param {Object=} opts - An options object. + * @param {(number|string)=} [opts.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. + * @returns {Promise} */ - export const globalAgent: Agent; + export function lookup(hostname: string, opts?: any | undefined): Promise; + export default exports; + import * as exports from "socket:dns/promises"; + +} + +declare module "socket:dns/index" { /** - * A duplex stream between a HTTP request `IncomingMessage` and the - * response `ServerResponse` + * Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or + * AAAA (IPv6) record. All option properties are optional. If options is an + * integer, then it must be 4 or 6 – if options is 0 or not provided, then IPv4 + * and IPv6 addresses are both returned if found. + * + * From the node.js website... + * + * > With the all option set to true, the arguments for callback change to (err, + * addresses), with addresses being an array of objects with the properties + * address and family. + * + * > On error, err is an Error object, where err.code is the error code. Keep in + * mind that err.code will be set to 'ENOTFOUND' not only when the host name does + * not exist but also when the lookup fails in other ways such as no available + * file descriptors. dns.lookup() does not necessarily have anything to do with + * the DNS protocol. The implementation uses an operating system facility that + * can associate names with addresses and vice versa. This implementation can + * have subtle but important consequences on the behavior of any Node.js program. + * Please take some time to consult the Implementation considerations section + * before using dns.lookup(). + * + * @see {@link https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback} + * @param {string} hostname - The host name to resolve. + * @param {(object|intenumberger)=} [options] - An options object or record family. + * @param {(number|string)=} [options.family=0] - The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. + * @param {function} cb - The function to call after the method is complete. + * @returns {void} */ - export class Connection extends Duplex { - /** - * `Connection` class constructor. - * @ignore - * @param {Server} server - * @param {IncomingMessage} incomingMessage - * @param {ServerResponse} serverResponse - */ - constructor(server: Server, incomingMessage: IncomingMessage, serverResponse: ServerResponse); - server: any; - active: boolean; - request: any; - response: any; - /** - * Closes the connection, destroying the underlying duplex, request, and - * response streams. - * @return {Connection} - */ - close(): Connection; - } + export function lookup(hostname: string, options?: (object | intenumberger) | undefined, cb: Function): void; + export { promises }; + export default exports; + import * as promises from "socket:dns/promises"; + import * as exports from "socket:dns/index"; + +} + +declare module "socket:dns" { + export * from "socket:dns/index"; + export default exports; + import * as exports from "socket:dns/index"; +} + +declare module "socket:dgram" { + export function createSocket(options: string | any, callback?: Function | undefined): Socket; /** - * A nodejs compat HTTP server typically intended for running in a "worker" - * environment. - * @see {@link https://nodejs.org/api/http.html#class-httpserver} + * New instances of dgram.Socket are created using dgram.createSocket(). + * The new keyword is not to be used to create dgram.Socket instances. */ - export class Server extends EventEmitter { - requestTimeout: number; - timeout: number; - maxRequestsPerSocket: number; - keepAliveTimeout: number; - headersTimeout: number; - /** - * @ignore - * @type {AsyncResource} - */ - get resource(): AsyncResource; - /** - * The adapter interface for this `Server` instance. - * @ignore - */ - get adapterInterace(): { - Connection: typeof exports.Connection; - globalAgent: exports.Agent; - IncomingMessage: typeof exports.IncomingMessage; - METHODS: string[]; - ServerResponse: typeof exports.ServerResponse; - STATUS_CODES: any; + export class Socket extends EventEmitter { + constructor(options: any, callback: any); + id: any; + knownIdWasGivenInSocketConstruction: boolean; + type: any; + signal: any; + state: { + recvBufferSize: any; + sendBufferSize: any; + bindState: number; + connectState: number; + reuseAddr: boolean; + ipv6Only: boolean; }; /** - * `true` if the server is closed, otherwise `false`. - * @type {boolean} - */ - get closed(): boolean; - /** - * The host to listen to. This value can be `null`. - * Defaults to `location.hostname`. This value - * is used to filter requests by hostname. - * @type {string?} - */ - get host(): string; - /** - * The `port` to listen on. This value can be `0`, which is the default. - * This value is used to filter requests by port, if given. A port value - * of `0` does not filter on any port. - * @type {number} - */ - get port(): number; - /** - * A readonly array of all active or inactive (idle) connections. - * @type {Connection[]} + * Listen for datagram messages on a named port and optional address + * If the address is not specified, the operating system will attempt to + * listen on all addresses. Once the binding is complete, a 'listening' + * event is emitted and the optional callback function is called. + * + * If binding fails, an 'error' event is emitted. + * + * @param {number} port - The port to listen for messages on + * @param {string} address - The address to bind to (0.0.0.0) + * @param {function} callback - With no parameters. Called when binding is complete. + * @see {@link https://nodejs.org/api/dgram.html#socketbindport-address-callback} */ - get connections(): exports.Connection[]; + bind(arg1: any, arg2: any, arg3: any): this; + dataListener: ({ detail }: { + detail: any; + }) => any; /** - * `true` if the server is listening for requests. - * @type {boolean} + * Associates the dgram.Socket to a remote address and port. Every message sent + * by this handle is automatically sent to that destination. Also, the socket + * will only receive messages from that remote peer. Trying to call connect() + * on an already connected socket will result in an ERR_SOCKET_DGRAM_IS_CONNECTED + * exception. If the address is not provided, '0.0.0.0' (for udp4 sockets) or '::1' + * (for udp6 sockets) will be used by default. Once the connection is complete, + * a 'connect' event is emitted and the optional callback function is called. + * In case of failure, the callback is called or, failing this, an 'error' event + * is emitted. + * + * @param {number} port - Port the client should connect to. + * @param {string=} host - Host the client should connect to. + * @param {function=} connectListener - Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. + * @see {@link https://nodejs.org/api/dgram.html#socketconnectport-address-callback} */ - get listening(): boolean; - set maxConnections(value: number); + connect(arg1: any, arg2: any, arg3: any): void; /** - * The number of concurrent max connections this server should handle. - * Default: Infinity - * @type {number} + * A synchronous function that disassociates a connected dgram.Socket from + * its remote address. Trying to call disconnect() on an unbound or already + * disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. + * + * @see {@link https://nodejs.org/api/dgram.html#socketdisconnect} */ - get maxConnections(): number; + disconnect(): void; /** - * Gets the HTTP server address and port that it this server is - * listening (emulated) on in the runtime with respect to the - * adapter internal being used by the server. - * @return {{ family: string, address: string, port: number}} + * Broadcasts a datagram on the socket. For connectionless sockets, the + * destination port and address must be specified. Connected sockets, on the + * other hand, will use their associated remote endpoint, so the port and + * address arguments must not be set. + * + * > The msg argument contains the message to be sent. Depending on its type, + * different behavior can apply. If msg is a Buffer, any TypedArray, or a + * DataView, the offset and length specify the offset within the Buffer where + * the message begins and the number of bytes in the message, respectively. + * If msg is a String, then it is automatically converted to a Buffer with + * 'utf8' encoding. With messages that contain multi-byte characters, offset, + * and length will be calculated with respect to byte length and not the + * character position. If msg is an array, offset and length must not be + * specified. + * + * > The address argument is a string. If the value of the address is a hostname, + * DNS will be used to resolve the address of the host. If the address is not + * provided or otherwise nullish, '0.0.0.0' (for udp4 sockets) or '::1' + * (for udp6 sockets) will be used by default. + * + * > If the socket has not been previously bound with a call to bind, the socket + * is assigned a random port number and is bound to the "all interfaces" + * address ('0.0.0.0' for udp4 sockets, '::1' for udp6 sockets.) + * + * > An optional callback function may be specified as a way of reporting DNS + * errors or for determining when it is safe to reuse the buf object. DNS + * lookups delay the time to send for at least one tick of the Node.js event + * loop. + * + * > The only way to know for sure that the datagram has been sent is by using a + * callback. If an error occurs and a callback is given, the error will be + * passed as the first argument to the callback. If a callback is not given, + * the error is emitted as an 'error' event on the socket object. + * + * > Offset and length are optional but both must be set if either is used. + * They are supported only when the first argument is a Buffer, a TypedArray, + * or a DataView. + * + * @param {Buffer | TypedArray | DataView | string | Array} msg - Message to be sent. + * @param {integer=} offset - Offset in the buffer where the message starts. + * @param {integer=} length - Number of bytes in the message. + * @param {integer=} port - Destination port. + * @param {string=} address - Destination host name or IP address. + * @param {Function=} callback - Called when the message has been sent. + * @see {@link https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback} */ - address(): { - family: string; - address: string; - port: number; - }; + send(buffer: any, ...args: any[]): Promise; /** - * Closes the server. - * @param {function=} [close] + * Close the underlying socket and stop listening for data on it. If a + * callback is provided, it is added as a listener for the 'close' event. + * + * @param {function=} callback - Called when the connection is completed or on error. + * + * @see {@link https://nodejs.org/api/dgram.html#socketclosecallback} */ - close(callback?: any): void; + close(cb: any): this; /** - * Closes all connections. + * + * Returns an object containing the address information for a socket. For + * UDP sockets, this object will contain address, family, and port properties. + * + * This method throws EBADF if called on an unbound socket. + * @returns {Object} socketInfo - Information about the local socket + * @returns {string} socketInfo.address - The IP address of the socket + * @returns {string} socketInfo.port - The port of the socket + * @returns {string} socketInfo.family - The IP family of the socket + * + * @see {@link https://nodejs.org/api/dgram.html#socketaddress} */ - closeAllConnections(): void; + address(): any; /** - * Closes all idle connections. + * Returns an object containing the address, family, and port of the remote + * endpoint. This method throws an ERR_SOCKET_DGRAM_NOT_CONNECTED exception + * if the socket is not connected. + * + * @returns {Object} socketInfo - Information about the remote socket + * @returns {string} socketInfo.address - The IP address of the socket + * @returns {string} socketInfo.port - The port of the socket + * @returns {string} socketInfo.family - The IP family of the socket + * @see {@link https://nodejs.org/api/dgram.html#socketremoteaddress} */ - closeIdleConnections(): void; + remoteAddress(): any; /** - * @ignore + * Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in + * bytes. + * + * @param {number} size - The size of the new receive buffer + * @see {@link https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize} */ - setTimeout(timeout?: number, callback?: any): this; + setRecvBufferSize(size: number): Promise; /** - * @param {number|object=} [port] - * @param {string=} [host] - * @param {function|null} [unused] - * @param {function=} [callback - * @return Server - */ - listen(port?: (number | object) | undefined, host?: string | undefined, unused?: Function | null, callback?: Function | undefined): this; - #private; - } - export default exports; - import { Writable } from "socket:stream"; - import { Buffer } from "socket:buffer"; - import { Readable } from "socket:stream"; - import { EventEmitter } from "socket:events"; - import { Duplex } from "socket:stream"; - import { AsyncResource } from "socket:async/resource"; - import * as exports from "socket:http"; - -} - -declare module "socket:fetch/index" { - export default fetch; - import { fetch } from "socket:fetch/fetch"; - import { Headers } from "socket:fetch/fetch"; - import { Request } from "socket:fetch/fetch"; - import { Response } from "socket:fetch/fetch"; - export { fetch, Headers, Request, Response }; -} - -declare module "socket:fetch" { - export * from "socket:fetch/index"; - export default fetch; - import fetch from "socket:fetch/index"; -} - -declare module "socket:https" { - /** - * Makes a HTTPS request, optionally a `socket://` for relative paths when - * `socket:` is the origin protocol. - * @param {string|object} optionsOrURL - * @param {(object|function)=} [options] - * @param {function=} [callback] - * @return {ClientRequest} - */ - export function request(optionsOrURL: string | object, options?: (object | Function) | undefined, callback?: Function | undefined): ClientRequest; - /** - * Makes a HTTPS or `socket:` GET request. A simplified alias to `request()`. - * @param {string|object} optionsOrURL - * @param {(object|function)=} [options] - * @param {function=} [callback] - * @return {ClientRequest} - */ - export function get(optionsOrURL: string | object, options?: (object | Function) | undefined, callback?: Function | undefined): ClientRequest; - /** - * Creates a HTTPS server that can listen for incoming requests. - * Requests that are dispatched to this server depend on the context - * in which it is created, such as a service worker which will use a - * "fetch event" adapter. - * @param {object|function=} [options] - * @param {function=} [callback] - * @return {Server} - */ - export function createServer(...args: any[]): Server; - export const CONTINUE: 100; - export const SWITCHING_PROTOCOLS: 101; - export const PROCESSING: 102; - export const EARLY_HINTS: 103; - export const OK: 200; - export const CREATED: 201; - export const ACCEPTED: 202; - export const NONAUTHORITATIVE_INFORMATION: 203; - export const NO_CONTENT: 204; - export const RESET_CONTENT: 205; - export const PARTIAL_CONTENT: 206; - export const MULTISTATUS: 207; - export const ALREADY_REPORTED: 208; - export const IM_USED: 226; - export const MULTIPLE_CHOICES: 300; - export const MOVED_PERMANENTLY: 301; - export const FOUND: 302; - export const SEE_OTHER: 303; - export const NOT_MODIFIED: 304; - export const USE_PROXY: 305; - export const TEMPORARY_REDIRECT: 307; - export const PERMANENT_REDIRECT: 308; - export const BAD_REQUEST: 400; - export const UNAUTHORIZED: 401; - export const PAYMENT_REQUIRED: 402; - export const FORBIDDEN: 403; - export const NOT_FOUND: 404; - export const METHOD_NOT_ALLOWED: 405; - export const NOT_ACCEPTABLE: 406; - export const PROXY_AUTHENTICATION_REQUIRED: 407; - export const REQUEST_TIMEOUT: 408; - export const CONFLICT: 409; - export const GONE: 410; - export const LENGTH_REQUIRED: 411; - export const PRECONDITION_FAILED: 412; - export const PAYLOAD_TOO_LARGE: 413; - export const URI_TOO_LONG: 414; - export const UNSUPPORTED_MEDIA_TYPE: 415; - export const RANGE_NOT_SATISFIABLE: 416; - export const EXPECTATION_FAILED: 417; - export const IM_A_TEAPOT: 418; - export const MISDIRECTED_REQUEST: 421; - export const UNPROCESSABLE_ENTITY: 422; - export const LOCKED: 423; - export const FAILED_DEPENDENCY: 424; - export const TOO_EARLY: 425; - export const UPGRADE_REQUIRED: 426; - export const PRECONDITION_REQUIRED: 428; - export const TOO_MANY_REQUESTS: 429; - export const REQUEST_HEADER_FIELDS_TOO_LARGE: 431; - export const UNAVAILABLE_FOR_LEGAL_REASONS: 451; - export const INTERNAL_SERVER_ERROR: 500; - export const NOT_IMPLEMENTED: 501; - export const BAD_GATEWAY: 502; - export const SERVICE_UNAVAILABLE: 503; - export const GATEWAY_TIMEOUT: 504; - export const HTTP_VERSION_NOT_SUPPORTED: 505; - export const VARIANT_ALSO_NEGOTIATES: 506; - export const INSUFFICIENT_STORAGE: 507; - export const LOOP_DETECTED: 508; - export const BANDWIDTH_LIMIT_EXCEEDED: 509; - export const NOT_EXTENDED: 510; - export const NETWORK_AUTHENTICATION_REQUIRED: 511; + * Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in + * bytes. + * + * @param {number} size - The size of the new send buffer + * @see {@link https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize} + */ + setSendBufferSize(size: number): Promise; + /** + * @see {@link https://nodejs.org/api/dgram.html#socketgetrecvbuffersize} + */ + getRecvBufferSize(): any; + /** + * @returns {number} the SO_SNDBUF socket send buffer size in bytes. + * @see {@link https://nodejs.org/api/dgram.html#socketgetsendbuffersize} + */ + getSendBufferSize(): number; + setBroadcast(): void; + setTTL(): void; + setMulticastTTL(): void; + setMulticastLoopback(): void; + setMulticastMembership(): void; + setMulticastInterface(): void; + addMembership(): void; + dropMembership(): void; + addSourceSpecificMembership(): void; + dropSourceSpecificMembership(): void; + ref(): this; + unref(): this; + #private; + } /** - * All known possible HTTP methods. - * @type {string[]} + * Generic error class for an error occurring on a `Socket` instance. + * @ignore */ - export const METHODS: string[]; + export class SocketError extends InternalError { + /** + * @type {string} + */ + get code(): string; + } /** - * A mapping of status codes to status texts - * @type {object} + * Thrown when a socket is already bound. */ - export const STATUS_CODES: object; + export class ERR_SOCKET_ALREADY_BOUND extends exports.SocketError { + get message(): string; + } /** - * An options object container for an `Agent` instance. + * @ignore */ - export class AgentOptions extends http.AgentOptions { + export class ERR_SOCKET_BAD_BUFFER_SIZE extends exports.SocketError { } /** - * An Agent is responsible for managing connection persistence - * and reuse for HTTPS clients. - * @see {@link https://nodejs.org/api/https.html#class-httpsagent} + * @ignore */ - export class Agent extends http.Agent { + export class ERR_SOCKET_BUFFER_SIZE extends exports.SocketError { } /** - * An object that is created internally and returned from `request()`. - * @see {@link https://nodejs.org/api/http.html#class-httpclientrequest} + * Thrown when the socket is already connected. */ - export class ClientRequest extends http.ClientRequest { + export class ERR_SOCKET_DGRAM_IS_CONNECTED extends exports.SocketError { + get message(): string; } /** - * The parent class of `ClientRequest` and `ServerResponse`. - * It is an abstract outgoing message from the perspective of the - * participants of an HTTP transaction. - * @see {@link https://nodejs.org/api/http.html#class-httpoutgoingmessage} + * Thrown when the socket is not connected. */ - export class OutgoingMessage extends http.OutgoingMessage { + export class ERR_SOCKET_DGRAM_NOT_CONNECTED extends exports.SocketError { + syscall: string; + get message(): string; } /** - * An `IncomingMessage` object is created by `Server` or `ClientRequest` and - * passed as the first argument to the 'request' and 'response' event - * respectively. - * It may be used to access response status, headers, and data. - * @see {@link https://nodejs.org/api/http.html#class-httpincomingmessage} + * Thrown when the socket is not running (not bound or connected). */ - export class IncomingMessage extends http.IncomingMessage { + export class ERR_SOCKET_DGRAM_NOT_RUNNING extends exports.SocketError { + get message(): string; } /** - * An object that is created internally by a `Server` instance, not by the user. - * It is passed as the second parameter to the 'request' event. - * @see {@link https://nodejs.org/api/http.html#class-httpserverresponse} + * Thrown when a bad socket type is used in an argument. */ - export class ServerResponse extends http.ServerResponse { + export class ERR_SOCKET_BAD_TYPE extends TypeError { + code: string; + get message(): string; } /** - * A duplex stream between a HTTP request `IncomingMessage` and the - * response `ServerResponse` + * Thrown when a bad port is given. */ - export class Connection extends http.Connection { + export class ERR_SOCKET_BAD_PORT extends RangeError { + code: string; } + export default exports; + export type SocketOptions = any; + import { EventEmitter } from "socket:events"; + import { InternalError } from "socket:errors"; + import * as exports from "socket:dgram"; + +} + +declare module "socket:enumeration" { /** - * A nodejs compat HTTP server typically intended for running in a "worker" - * environment. - * @see {@link https://nodejs.org/api/http.html#class-httpserver} + * @module enumeration + * This module provides a data structure for enumerated unique values. */ - export class Server extends http.Server { + /** + * A container for enumerated values. + */ + export class Enumeration extends Set { + /** + * Creates an `Enumeration` instance from arguments. + * @param {...any} values + * @return {Enumeration} + */ + static from(...values: any[]): Enumeration; + /** + * `Enumeration` class constructor. + * @param {any[]} values + * @param {object=} [options = {}] + * @param {number=} [options.start = 0] + */ + constructor(values: any[], options?: object | undefined); + /** + * @type {number} + */ + get length(): number; + /** + * Returns `true` if enumeration contains `value`. An alias + * for `Set.prototype.has`. + * @return {boolean} + */ + contains(value: any): boolean; + /** + * @ignore + */ + add(): void; + /** + * @ignore + */ + delete(): void; + /** + * JSON represenation of a `Enumeration` instance. + * @ignore + * @return {string[]} + */ + toJSON(): string[]; + /** + * Internal inspect function. + * @ignore + * @return {LanguageQueryResult} + */ + inspect(): LanguageQueryResult; + } + export default Enumeration; +} + +declare module "socket:fs/web" { + /** + * Creates a new `File` instance from `filename`. + * @param {string} filename + * @param {{ fd: fs.FileHandle, highWaterMark?: number }=} [options] + * @return {File} + */ + export function createFile(filename: string, options?: { + fd: fs.FileHandle; + highWaterMark?: number; + } | undefined): File; + /** + * Creates a `FileSystemWritableFileStream` instance backed + * by `socket:fs:` module from a given `FileSystemFileHandle` instance. + * @param {string|File} file + * @return {Promise} + */ + export function createFileSystemWritableFileStream(handle: any, options: any): Promise; + /** + * Creates a `FileSystemFileHandle` instance backed by `socket:fs:` module from + * a given `File` instance or filename string. + * @param {string|File} file + * @param {object} [options] + * @return {Promise} + */ + export function createFileSystemFileHandle(file: string | File, options?: object): Promise; + /** + * Creates a `FileSystemDirectoryHandle` instance backed by `socket:fs:` module + * from a given directory name string. + * @param {string} dirname + * @return {Promise} + */ + export function createFileSystemDirectoryHandle(dirname: string, options?: any): Promise; + export const File: { + new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; + prototype: File; + } | { + new (): { + readonly lastModifiedDate: Date; + readonly lastModified: number; + readonly name: any; + readonly size: number; + readonly type: string; + slice(): void; + arrayBuffer(): Promise; + text(): Promise; + stream(): void; + }; + }; + export const FileSystemHandle: { + new (): { + readonly name: any; + readonly kind: any; + }; + }; + export const FileSystemFileHandle: { + new (): FileSystemFileHandle; + prototype: FileSystemFileHandle; + } | { + new (): { + getFile(): void; + createWritable(options?: any): Promise; + createSyncAccessHandle(): Promise; + readonly name: any; + readonly kind: any; + }; + }; + export const FileSystemDirectoryHandle: { + new (): FileSystemDirectoryHandle; + prototype: FileSystemDirectoryHandle; + } | { + new (): { + entries(): AsyncGenerator; + values(): AsyncGenerator; + keys(): AsyncGenerator; + resolve(possibleDescendant: any): Promise; + removeEntry(name: any, options?: any): Promise; + getDirectoryHandle(name: any, options?: any): Promise; + getFileHandle(name: any, options?: any): Promise; + readonly name: any; + readonly kind: any; + }; + }; + export const FileSystemWritableFileStream: { + new (underlyingSink?: UnderlyingSink, strategy?: QueuingStrategy): { + seek(position: any): Promise; + truncate(size: any): Promise; + write(data: any): Promise; + readonly locked: boolean; + abort(reason?: any): Promise; + close(): Promise; + getWriter(): WritableStreamDefaultWriter; + }; + }; + namespace _default { + export { createFileSystemWritableFileStream }; + export { createFileSystemDirectoryHandle }; + export { createFileSystemFileHandle }; + export { createFile }; } - /** - * The global and default HTTPS agent. - * @type {Agent} - */ - export const globalAgent: Agent; - export default exports; - import http from "socket:http"; - import * as exports from "socket:http"; + export default _default; + import fs from "socket:fs/promises"; } -declare module "socket:language" { - /** - * Look up a language name or code by query. - * @param {string} query - * @param {object=} [options] - * @param {boolean=} [options.strict = false] - * @return {?LanguageQueryResult[]} - */ - export function lookup(query: string, options?: object | undefined, ...args: any[]): LanguageQueryResult[] | null; +declare module "socket:extension" { /** - * Describe a language by tag - * @param {string} query - * @param {object=} [options] - * @param {boolean=} [options.strict = true] - * @return {?LanguageDescription[]} + * Load an extension by name. + * @template {Record T} + * @param {string} name + * @param {ExtensionLoadOptions} [options] + * @return {Promise>} */ - export function describe(query: string, options?: object | undefined): LanguageDescription[] | null; + export function load>(name: string, options?: ExtensionLoadOptions): Promise>; /** - * A list of ISO 639-1 language names. - * @type {string[]} + * Provides current stats about the loaded extensions. + * @return {Promise} */ - export const names: string[]; + export function stats(): Promise; /** - * A list of ISO 639-1 language codes. - * @type {string[]} + * @typedef {{ + * allow: string[] | string, + * imports?: object, + * type?: 'shared' | 'wasm32', + * path?: string, + * stats?: object, + * instance?: WebAssembly.Instance, + * adapter?: WebAssemblyExtensionAdapter + * }} ExtensionLoadOptions */ - export const codes: string[]; /** - * A list of RFC 5646 language tag identifiers. - * @see {@link http://tools.ietf.org/html/rfc5646} + * @typedef {{ abi: number, version: string, description: string }} ExtensionInfo */ - export const tags: Enumeration; /** - * A list of RFC 5646 language tag titles corresponding - * to language tags. - * @see {@link http://tools.ietf.org/html/rfc5646} + * @typedef {{ abi: number, loaded: number }} ExtensionStats */ - export const descriptions: Enumeration; /** - * A container for a language query response containing an ISO language - * name and code. - * @see {@link https://www.sitepoint.com/iso-2-letter-language-codes} + * A interface for a native extension. + * @template {Record T} */ - export class LanguageQueryResult { + export class Extension> extends EventTarget { /** - * `LanguageQueryResult` class constructor. - * @param {string} code + * Load an extension by name. + * @template {Record T} * @param {string} name - * @param {string[]} [tags] + * @param {ExtensionLoadOptions} [options] + * @return {Promise>} */ - constructor(code: string, name: string, tags?: string[]); + static load>(name: string, options?: ExtensionLoadOptions): Promise>; /** - * The language code corresponding to the query. - * @type {string} + * Query type of extension by name. + * @param {string} name + * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - get code(): string; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** - * The language name corresponding to the query. - * @type {string} + * Provides current stats about the loaded extensions or one by name. + * @param {?string} name + * @return {Promise} */ - get name(): string; + static stats(name: string | null): Promise; /** - * The language tags corresponding to the query. - * @type {string[]} + * `Extension` class constructor. + * @param {string} name + * @param {ExtensionInfo} info + * @param {ExtensionLoadOptions} [options] */ - get tags(): string[]; + constructor(name: string, info: ExtensionInfo, options?: ExtensionLoadOptions); /** - * JSON represenation of a `LanguageQueryResult` instance. - * @return {{ - * code: string, - * name: string, - * tags: string[] - * }} + * The name of the extension + * @type {string?} */ - toJSON(): { - code: string; - name: string; - tags: string[]; - }; + name: string | null; /** - * Internal inspect function. - * @ignore - * @return {LanguageQueryResult} + * The version of the extension + * @type {string?} */ - inspect(): LanguageQueryResult; - #private; - } - /** - * A container for a language code, tag, and description. - */ - export class LanguageDescription { + version: string | null; /** - * `LanguageDescription` class constructor. - * @param {string} code - * @param {string} tag - * @param {string} description + * The description of the extension + * @type {string?} */ - constructor(code: string, tag: string, description: string); + description: string | null; /** - * The language code corresponding to the language - * @type {string} + * The abi of the extension + * @type {number} */ - get code(): string; + abi: number; /** - * The language tag corresponding to the language. - * @type {string} + * @type {object} */ - get tag(): string; + options: object; /** - * The language description corresponding to the language. - * @type {string} + * @type {T} */ - get description(): string; + binding: T; /** - * JSON represenation of a `LanguageDescription` instance. - * @return {{ - * code: string, - * tag: string, - * description: string - * }} + * Not `null` if extension is of type 'wasm32' + * @type {?WebAssemblyExtensionAdapter} */ - toJSON(): { - code: string; - tag: string; - description: string; - }; + adapter: WebAssemblyExtensionAdapter | null; /** - * Internal inspect function. - * @ignore - * @return {LanguageDescription} + * `true` if the extension was loaded, otherwise `false` + * @type {boolean} */ - inspect(): LanguageDescription; - #private; + get loaded(): boolean; + /** + * The extension type: 'shared' or 'wasm32' + * @type {'shared'|'wasm32'} + */ + get type(): "shared" | "wasm32"; + /** + * Unloads the loaded extension. + * @throws Error + */ + unload(): Promise; + instance: any; + [$type]: "shared" | "wasm32"; + [$loaded]: boolean; } namespace _default { - export { codes }; - export { describe }; - export { lookup }; - export { names }; - export { tags }; + export { load }; + export { stats }; } export default _default; - import Enumeration from "socket:enumeration"; -} - -declare module "socket:i18n" { - /** - * Get messages for `locale` pattern. This function could return many results - * for various locales given a `locale` pattern. such as `fr`, which could - * return results for `fr`, `fr-FR`, `fr-BE`, etc. - * @ignore - * @param {string} locale - * @return {object[]} - */ - export function getMessagesForLocale(locale: string): object[]; - /** - * Returns user preferred ISO 639 language codes or RFC 5646 language tags. - * @return {string[]} - */ - export function getAcceptLanguages(): string[]; - /** - * Returns the current user ISO 639 language code or RFC 5646 language tag. - * @return {?string} - */ - export function getUILanguage(): string | null; - /** - * Gets a localized message string for the specified message name. - * @param {string} messageName - * @param {object|string[]=} [substitutions = []] - * @param {object=} [options] - * @param {string=} [options.locale = null] - * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} - * @see {@link https://www.ibm.com/docs/en/rbd/9.5.1?topic=syslib-getmessage} - * @return {?string} - */ - export function getMessage(messageName: string, substitutions?: (object | string[]) | undefined, options?: object | undefined): string | null; - /** - * Gets a localized message description string for the specified message name. - * @param {string} messageName - * @param {object=} [options] - * @param {string=} [options.locale = null] - * @return {?string} - */ - export function getMessageDescription(messageName: string, options?: object | undefined): string | null; - /** - * A cache of loaded locale messages. - * @type {Map} - */ - export const cache: Map; + export type Pointer = number; + export type ExtensionLoadOptions = { + allow: string[] | string; + imports?: object; + type?: 'shared' | 'wasm32'; + path?: string; + stats?: object; + instance?: WebAssembly.Instance; + adapter?: WebAssemblyExtensionAdapter; + }; + export type ExtensionInfo = { + abi: number; + version: string; + description: string; + }; + export type ExtensionStats = { + abi: number; + loaded: number; + }; /** - * Default location of i18n locale messages - * @type {string} + * An adapter for reading and writing various values from a WebAssembly instance's + * memory buffer. + * @ignore */ - export const DEFAULT_LOCALES_LOCATION: string; + class WebAssemblyExtensionAdapter { + constructor({ instance, module, table, memory, policies }: { + instance: any; + module: any; + table: any; + memory: any; + policies: any; + }); + view: any; + heap: any; + table: any; + stack: any; + buffer: any; + module: any; + memory: any; + context: any; + policies: any[]; + externalReferences: Map; + instance: any; + exitStatus: any; + textDecoder: TextDecoder; + textEncoder: TextEncoder; + errorMessagePointers: {}; + indirectFunctionTable: any; + get globalBaseOffset(): any; + destroy(): void; + init(): boolean; + get(pointer: any, size?: number): any; + set(pointer: any, value: any): void; + createExternalReferenceValue(value: any): any; + getExternalReferenceValue(pointer: any): any; + setExternalReferenceValue(pointer: any, value: any): Map; + removeExternalReferenceValue(pointer: any): void; + getExternalReferencePointer(value: any): any; + getFloat32(pointer: any): any; + setFloat32(pointer: any, value: any): boolean; + getFloat64(pointer: any): any; + setFloat64(pointer: any, value: any): boolean; + getInt8(pointer: any): any; + setInt8(pointer: any, value: any): boolean; + getInt16(pointer: any): any; + setInt16(pointer: any, value: any): boolean; + getInt32(pointer: any): any; + setInt32(pointer: any, value: any): boolean; + getUint8(pointer: any): any; + setUint8(pointer: any, value: any): boolean; + getUint16(pointer: any): any; + setUint16(pointer: any, value: any): boolean; + getUint32(pointer: any): any; + setUint32(pointer: any, value: any): boolean; + getString(pointer: any, buffer: any, size: any): string; + setString(pointer: any, string: any, buffer?: any): boolean; + } + const $type: unique symbol; /** - * An enumeration of supported ISO 639 language codes or RFC 5646 language tags. - * @type {Enumeration} - * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/LanguageCode} - * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + * @typedef {number} Pointer */ - export const LanguageCode: Enumeration; - namespace _default { - export { LanguageCode }; - export { getAcceptLanguages }; - export { getMessage }; - export { getUILanguage }; - } - export default _default; - import Enumeration from "socket:enumeration"; + const $loaded: unique symbol; } -declare module "socket:stream-relay/packets" { - /** - * The magic bytes prefixing every packet. They are the - * 2nd, 3rd, 5th, and 7th, prime numbers. - * @type {number[]} - */ - export const MAGIC_BYTES_PREFIX: number[]; - /** - * The version of the protocol. - */ - export const VERSION: 6; - /** - * The size in bytes of the prefix magic bytes. - */ - export const MAGIC_BYTES: 4; - /** - * The maximum size of the user message. - */ - export const MESSAGE_BYTES: 1024; - /** - * The cache TTL in milliseconds. - */ - export const CACHE_TTL: number; - export namespace PACKET_SPEC { - namespace type { - let bytes: number; - let encoding: string; - } - namespace version { - let bytes_1: number; - export { bytes_1 as bytes }; - let encoding_1: string; - export { encoding_1 as encoding }; - export { VERSION as default }; - } - namespace clock { - let bytes_2: number; - export { bytes_2 as bytes }; - let encoding_2: string; - export { encoding_2 as encoding }; - let _default: number; - export { _default as default }; - } - namespace hops { - let bytes_3: number; - export { bytes_3 as bytes }; - let encoding_3: string; - export { encoding_3 as encoding }; - let _default_1: number; - export { _default_1 as default }; - } - namespace index { - let bytes_4: number; - export { bytes_4 as bytes }; - let encoding_4: string; - export { encoding_4 as encoding }; - let _default_2: number; - export { _default_2 as default }; - export let signed: boolean; - } - namespace ttl { - let bytes_5: number; - export { bytes_5 as bytes }; - let encoding_5: string; - export { encoding_5 as encoding }; - export { CACHE_TTL as default }; - } - namespace clusterId { - let bytes_6: number; - export { bytes_6 as bytes }; - let encoding_6: string; - export { encoding_6 as encoding }; - let _default_3: number[]; - export { _default_3 as default }; - } - namespace subclusterId { - let bytes_7: number; - export { bytes_7 as bytes }; - let encoding_7: string; - export { encoding_7 as encoding }; - let _default_4: number[]; - export { _default_4 as default }; - } - namespace previousId { - let bytes_8: number; - export { bytes_8 as bytes }; - let encoding_8: string; - export { encoding_8 as encoding }; - let _default_5: number[]; - export { _default_5 as default }; - } - namespace packetId { - let bytes_9: number; - export { bytes_9 as bytes }; - let encoding_9: string; - export { encoding_9 as encoding }; - let _default_6: number[]; - export { _default_6 as default }; - } - namespace nextId { - let bytes_10: number; - export { bytes_10 as bytes }; - let encoding_10: string; - export { encoding_10 as encoding }; - let _default_7: number[]; - export { _default_7 as default }; - } - namespace usr1 { - let bytes_11: number; - export { bytes_11 as bytes }; - let _default_8: number[]; - export { _default_8 as default }; - } - namespace usr2 { - let bytes_12: number; - export { bytes_12 as bytes }; - let _default_9: number[]; - export { _default_9 as default }; - } - namespace usr3 { - let bytes_13: number; - export { bytes_13 as bytes }; - let _default_10: number[]; - export { _default_10 as default }; - } - namespace usr4 { - let bytes_14: number; - export { bytes_14 as bytes }; - let _default_11: number[]; - export { _default_11 as default }; - } - namespace message { - let bytes_15: number; - export { bytes_15 as bytes }; - let _default_12: number[]; - export { _default_12 as default }; - } - namespace sig { - let bytes_16: number; - export { bytes_16 as bytes }; - let _default_13: number[]; - export { _default_13 as default }; - } +declare module "socket:fetch/fetch" { + export function Headers(headers: any): void; + export class Headers { + constructor(headers: any); + map: {}; + append(name: any, value: any): void; + delete(name: any): void; + get(name: any): any; + has(name: any): boolean; + set(name: any, value: any): void; + forEach(callback: any, thisArg: any): void; + keys(): { + next: () => { + done: boolean; + value: any; + }; + }; + values(): { + next: () => { + done: boolean; + value: any; + }; + }; + entries(): { + next: () => { + done: boolean; + value: any; + }; + }; + } + export function Request(input: any, options: any): void; + export class Request { + constructor(input: any, options: any); + url: string; + credentials: any; + headers: Headers; + method: any; + mode: any; + signal: any; + referrer: any; + clone(): Request; + } + export function Response(bodyInit: any, options: any): void; + export class Response { + constructor(bodyInit: any, options: any); + type: string; + status: any; + ok: boolean; + statusText: string; + headers: Headers; + url: any; + clone(): Response; + } + export namespace Response { + function error(): Response; + function redirect(url: any, status: any): Response; + } + export function fetch(input: any, init: any): Promise; + export class DOMException { + private constructor(); + } + namespace _default { + export { fetch }; + export { Headers }; + export { Request }; + export { Response }; } + export default _default; +} + +declare module "socket:service-worker/env" { /** - * The size in bytes of the total packet frame and message. + * Opens an environment for a particular scope. + * @param {EnvironmentOptions} options + * @return {Promise} */ - export const PACKET_BYTES: number; + export function open(options: EnvironmentOptions): Promise; /** - * The maximum distance that a packet can be replicated. + * Closes an active `Environment` instance, dropping the global + * instance reference. + * @return {Promise} */ - export const MAX_HOPS: 16; - export function validatePacket(o: any, constraints: { - [key: string]: { - required: boolean; - type: string; - }; - }): void; + export function close(): Promise; /** - * Computes a SHA-256 hash of input returning a hex encoded string. - * @type {function(string|Buffer|Uint8Array): Promise} + * Resets an active `Environment` instance + * @return {Promise} */ - export const sha256: (arg0: string | Buffer | Uint8Array) => Promise; - export function decode(buf: Buffer): Packet; - export function getTypeFromBytes(buf: any): any; - export class Packet { - static ttl: number; - static maxLength: number; + export function reset(): Promise; + /** + * @typedef {{ + * scope: string + * }} EnvironmentOptions + */ + /** + * An event dispatched when a environment value is updated (set, delete) + */ + export class EnvironmentEvent extends Event { /** - * Returns an empty `Packet` instance. - * @return {Packet} + * `EnvironmentEvent` class constructor. + * @param {'set'|'delete'} type + * @param {object=} [entry] */ - static empty(): Packet; + constructor(type: 'set' | 'delete', entry?: object | undefined); + entry: any; + } + /** + * An environment context object with persistence and durability + * for service worker environments. + */ + export class Environment extends EventTarget { /** - * @param {Packet|object} packet - * @return {Packet} + * Maximum entries that will be restored from storage into the environment + * context object. + * @type {number} */ - static from(packet: Packet | object): Packet; + static MAX_CONTEXT_ENTRIES: number; /** - * Determines if input is a packet. - * @param {Buffer|Uint8Array|number[]|object|Packet} packet - * @return {boolean} + * Opens an environment for a particular scope. + * @param {EnvironmentOptions} options + * @return {Environment} */ - static isPacket(packet: Buffer | Uint8Array | number[] | object | Packet): boolean; + static open(options: EnvironmentOptions): Environment; /** - */ - static encode(p: any): Promise; - static decode(buf: any): Packet; + * The current `Environment` instance + * @type {Environment?} + */ + static instance: Environment | null; /** - * `Packet` class constructor. - * @param {Packet|object?} options + * `Environment` class constructor + * @ignore + * @param {EnvironmentOptions} options */ - constructor(options?: Packet | (object | null)); + constructor(options: EnvironmentOptions); /** - * @param {Packet} packet - * @return {Packet} + * A reference to the currently opened environment database. + * @type {import('../internal/database.js').Database} */ - copy(): Packet; - timestamp: any; - isComposed: any; - isReconciled: any; - meta: any; - } - export class PacketPing extends Packet { - static type: number; - constructor({ message, clusterId, subclusterId }: { - message: any; - clusterId: any; - subclusterId: any; - }); - } - export class PacketPong extends Packet { - static type: number; - constructor({ message, clusterId, subclusterId }: { - message: any; - clusterId: any; - subclusterId: any; - }); - } - export class PacketIntro extends Packet { - static type: number; - constructor({ clock, hops, clusterId, subclusterId, usr1, message }: { - clock: any; - hops: any; - clusterId: any; - subclusterId: any; - usr1: any; - message: any; - }); - } - export class PacketJoin extends Packet { - static type: number; - constructor({ clock, hops, clusterId, subclusterId, message }: { - clock: any; - hops: any; - clusterId: any; - subclusterId: any; - message: any; - }); + get database(): import("socket:internal/database").Database; + /** + * A proxied object for reading and writing environment state. + * Values written to this object must be cloneable with respect to the + * structured clone algorithm. + * @see {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm} + * @type {Proxy} + */ + get context(): ProxyConstructor; + /** + * The current environment name. This value is also used as the + * internal database name. + * @type {string} + */ + get name(): string; + /** + * Resets the current environment to an empty state. + */ + reset(): Promise; + /** + * Opens the environment. + * @ignore + */ + open(): Promise; + /** + * Closes the environment database, purging existing state. + * @ignore + */ + close(): Promise; + #private; } - export class PacketPublish extends Packet { - static type: number; - constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }: { - message: any; - sig: any; - packetId: any; - clusterId: any; - subclusterId: any; - nextId: any; - clock: any; - hops: any; - usr1: any; - usr2: any; - ttl: any; - previousId: any; - }); + namespace _default { + export { Environment }; + export { close }; + export { reset }; + export { open }; } - export class PacketStream extends Packet { - static type: number; - constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }: { - message: any; - sig: any; - packetId: any; - clusterId: any; - subclusterId: any; - nextId: any; - clock: any; - ttl: any; - usr1: any; - usr2: any; - usr3: any; - usr4: any; - previousId: any; - }); + export default _default; + export type EnvironmentOptions = { + scope: string; + }; +} + +declare module "socket:service-worker/debug" { + export function debug(...args: any[]): void; + export default debug; +} + +declare module "socket:service-worker/state" { + export const channel: BroadcastChannel; + export const state: any; + export default state; +} + +declare module "socket:service-worker/clients" { + export class Client { + constructor(options: any); + get id(): any; + get url(): any; + get type(): any; + get frameType(): any; + postMessage(message: any, optionsOrTransferables?: any): void; + #private; } - export class PacketSync extends Packet { - static type: number; - constructor({ packetId, message }: { - packetId: any; - message?: any; - }); + export class WindowClient extends Client { + get focused(): boolean; + get ancestorOrigins(): any[]; + get visibilityState(): string; + focus(): Promise; + navigate(url: any): Promise; + #private; } - export class PacketQuery extends Packet { - static type: number; - constructor({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }: { - packetId: any; - previousId: any; - subclusterId: any; - usr1: any; - usr2: any; - usr3: any; - usr4: any; - message?: {}; - }); + export class Clients { + get(id: any): Promise; + matchAll(options?: any): Promise; + openWindow(url: any, options?: any): Promise; + claim(): Promise; } - export default Packet; - import { Buffer } from "socket:buffer"; + const _default: Clients; + export default _default; } -declare module "socket:stream-relay/encryption" { +declare module "socket:service-worker/context" { /** - * Class for handling encryption and key management. + * A context given to `ExtendableEvent` interfaces and provided to + * simplified service worker modules */ - export class Encryption { + export class Context { /** - * Creates a shared key based on the provided seed or generates a random one. - * @param {Uint8Array|string} seed - Seed for key generation. - * @returns {Promise} - Shared key. + * `Context` class constructor. + * @param {import('./events.js').ExtendableEvent} event */ - static createSharedKey(seed: Uint8Array | string): Promise; + constructor(event: import('./events.js').ExtendableEvent); /** - * Creates a key pair for signing and verification. - * @param {Uint8Array|string} seed - Seed for key generation. - * @returns {Promise<{ publicKey: Uint8Array, privateKey: Uint8Array }>} - Key pair. + * Context data. This may be a custom protocol handler scheme data + * by default, if available. + * @type {any?} */ - static createKeyPair(seed: Uint8Array | string): Promise<{ - publicKey: Uint8Array; - privateKey: Uint8Array; - }>; + data: any | null; /** - * Creates an ID using SHA-256 hash. - * @param {string} str - String to hash. - * @returns {Promise} - SHA-256 hash. + * The `ExtendableEvent` for this `Context` instance. + * @type {ExtendableEvent} */ - static createId(str: string): Promise; + get event(): ExtendableEvent; /** - * Creates a cluster ID using SHA-256 hash with specified output size. - * @param {string} str - String to hash. - * @returns {Promise} - SHA-256 hash with specified output size. + * An environment context object. + * @type {object?} */ - static createClusterId(str: string): Promise; + get env(): any; /** - * Signs a message using the given secret key. - * @param {Buffer} b - The message to sign. - * @param {Uint8Array} sk - The secret key to use. - * @returns {Uint8Array} - Signature. + * Resets the current environment context. + * @return {Promise} */ - static sign(b: Buffer, sk: Uint8Array): Uint8Array; + resetEnvironment(): Promise; /** - * Verifies the signature of a message using the given public key. - * @param {Buffer} b - The message to verify. - * @param {Uint8Array} sig - The signature to check. - * @param {Uint8Array} pk - The public key to use. - * @returns {number} - Returns non-zero if the buffer could not be verified. + * Unused, but exists for cloudflare compat. + * @ignore */ - static verify(b: Buffer, sig: Uint8Array, pk: Uint8Array): number; + passThroughOnException(): void; /** - * Mapping of public keys to key objects. - * @type {Object.} + * Tells the event dispatcher that work is ongoing. + * It can also be used to detect whether that work was successful. + * @param {Promise} promise */ - keys: { - [x: string]: { - publicKey: Uint8Array; - privateKey: Uint8Array; - ts: number; - }; - }; + waitUntil(promise: Promise): Promise; /** - * Adds a key pair to the keys mapping. - * @param {Uint8Array|string} publicKey - Public key. - * @param {Uint8Array} privateKey - Private key. + * TODO */ - add(publicKey: Uint8Array | string, privateKey: Uint8Array): void; + handled(): Promise; /** - * Removes a key from the keys mapping. - * @param {Uint8Array|string} publicKey - Public key. + * Gets the client for this event context. + * @return {Promise} */ - remove(publicKey: Uint8Array | string): void; + client(): Promise; + #private; + } + namespace _default { + export { Context }; + } + export default _default; +} + +declare module "socket:service-worker/events" { + export const textEncoder: TextEncoderStream; + export const FETCH_EVENT_TIMEOUT: number; + /** + * The `ExtendableEvent` interface extends the lifetime of the "install" and + * "activate" events dispatched on the global scope as part of the service + * worker lifecycle. + */ + export class ExtendableEvent extends Event { /** - * Checks if a key is in the keys mapping. - * @param {Uint8Array|string} to - Public key or Uint8Array. - * @returns {boolean} - True if the key is present, false otherwise. + * `ExtendableEvent` class constructor. + * @ignore */ - has(to: Uint8Array | string): boolean; + constructor(...args: any[]); /** - * Opens a sealed message using the specified key. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found. + * A context for this `ExtendableEvent` instance. + * @type {import('./context.js').Context} */ - openUnsigned(message: Buffer, v: any | string): Buffer; - sealUnsigned(message: any, v: any): any; + get context(): Context; /** - * Decrypts a sealed and signed message for a specific receiver. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. + * A promise that can be awaited which waits for this `ExtendableEvent` + * instance no longer has pending promises. + * @type {Promise} */ - open(message: Buffer, v: any | string): Buffer; + get awaiting(): Promise; /** - * Seals and signs a message for a specific receiver using their public key. - * - * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on - * a plaintext `message` for a `receiver` identity. This prevents repudiation - * attacks and doesn't rely on packet chain guarantees. - * - * let ct = Seal(sender | pt, receiver) - * let sig = Sign(ct, sk) - * let out = Seal(sig | ct) - * - * In an setup between Alice & Bob, this means: - * - Only Bob sees the plaintext - * - Alice wrote the plaintext and the ciphertext - * - Only Bob can see that Alice wrote the plaintext and ciphertext - * - Bob cannot forward the message without invalidating Alice's signature. - * - The outer encryption serves to prevent an attacker from replacing Alice's - * signature. As with _sign-encrypt-sign (SES), ESE is a variant of - * including the recipient's name inside the plaintext, which is then signed - * and encrypted Alice signs her plaintext along with her ciphertext, so as - * to protect herself from a laintext-substitution attack. At the same time, - * Alice's signed plaintext gives Bob non-repudiation. - * - * @see https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html - * - * @param {Buffer} message - The message to seal. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Sealed message. - * @throws {Error} - Throws ENOKEY if the key is not found. + * The number of pending promises + * @type {number} */ - seal(message: Buffer, v: any | string): Buffer; - } - import Buffer from "socket:buffer"; -} - -declare module "socket:stream-relay/cache" { - /** - * @typedef {Packet} CacheEntry - * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver - */ - /** - * Default cache sibling resolver that computes a delta between - * two entries clocks. - * @param {CacheEntry} a - * @param {CacheEntry} b - * @return {number} - */ - export function defaultSiblingResolver(a: CacheEntry, b: CacheEntry): number; - export function trim(buf: Buffer): any; - /** - * Default max size of a `Cache` instance. - */ - export const DEFAULT_MAX_SIZE: number; - /** - * Internal mapping of packet IDs to packet data used by `Cache`. - */ - export class CacheData extends Map { - constructor(); - constructor(entries?: readonly (readonly [any, any])[]); - constructor(); - constructor(iterable?: Iterable); - } - /** - * A class for storing a cache of packets by ID. This class includes a scheme - * for reconciling disjointed packet caches in a large distributed system. The - * following are key design characteristics. - * - * Space Efficiency: This scheme can be space-efficient because it summarizes - * the cache's contents in a compact binary format. By sharing these summaries, - * two computers can quickly determine whether their caches have common data or - * differences. - * - * Bandwidth Efficiency: Sharing summaries instead of the full data can save - * bandwidth. If the differences between the caches are small, sharing summaries - * allows for more efficient data synchronization. - * - * Time Efficiency: The time efficiency of this scheme depends on the size of - * the cache and the differences between the two caches. Generating summaries - * and comparing them can be faster than transferring and comparing the entire - * dataset, especially for large caches. - * - * Complexity: The scheme introduces some complexity due to the need to encode - * and decode summaries. In some cases, the overhead introduced by this - * complexity might outweigh the benefits, especially if the caches are - * relatively small. In this case, you should be using a query. - * - * Data Synchronization Needs: The efficiency also depends on the data - * synchronization needs. If the data needs to be synchronized in real-time, - * this scheme might not be suitable. It's more appropriate for cases where - * periodic or batch synchronization is acceptable. - * - * Scalability: The scheme's efficiency can vary depending on the scalability - * of the system. As the number of cache entries or computers involved - * increases, the complexity of generating and comparing summaries will stay - * bound to a maximum of 16Mb. - * + get pendingPromises(): number; + /** + * `true` if the `ExtendableEvent` instance is considered "active", + * otherwise `false`. + * @type {boolean} + */ + get isActive(): boolean; + /** + * Tells the event dispatcher that work is ongoing. + * It can also be used to detect whether that work was successful. + * @param {Promise} promise + */ + waitUntil(promise: Promise): void; + /** + * Returns a promise that this `ExtendableEvent` instance is waiting for. + * @return {Promise} + */ + waitsFor(): Promise; + #private; + } + /** + * This is the event type for "fetch" events dispatched on the service worker + * global scope. It contains information about the fetch, including the + * request and how the receiver will treat the response. */ - export class Cache { - static HASH_SIZE_BYTES: number; + export class FetchEvent extends ExtendableEvent { + static defaultHeaders: Headers; /** - * The encodeSummary method provides a compact binary encoding of the output - * of summary() - * - * @param {Object} summary - the output of calling summary() - * @return {Buffer} - **/ - static encodeSummary(summary: any): Buffer; + * `FetchEvent` class constructor. + * @ignore + * @param {string=} [type = 'fetch'] + * @param {object=} [options] + */ + constructor(type?: string | undefined, options?: object | undefined); /** - * The decodeSummary method decodes the output of encodeSummary() - * - * @param {Buffer} bin - the output of calling encodeSummary() - * @return {Object} summary - **/ - static decodeSummary(bin: Buffer): any; + * The handled property of the `FetchEvent` interface returns a promise + * indicating if the event has been handled by the fetch algorithm or not. + * This property allows executing code after the browser has consumed a + * response, and is usually used together with the `waitUntil()` method. + * @type {Promise} + */ + get handled(): Promise; /** - * `Cache` class constructor. - * @param {CacheData?} [data] + * The request read-only property of the `FetchEvent` interface returns the + * `Request` that triggered the event handler. + * @type {Request} */ - constructor(data?: CacheData | null, siblingResolver?: typeof defaultSiblingResolver); - data: CacheData; - maxSize: number; - siblingResolver: typeof defaultSiblingResolver; + get request(): Request; /** - * Readonly count of the number of cache entries. - * @type {number} + * The `clientId` read-only property of the `FetchEvent` interface returns + * the id of the Client that the current service worker is controlling. + * @type {string} */ - get size(): number; + get clientId(): string; /** - * Readonly size of the cache in bytes. - * @type {number} + * @ignore + * @type {string} */ - get bytes(): number; + get resultingClientId(): string; /** - * Inserts a `CacheEntry` value `v` into the cache at key `k`. - * @param {string} k - * @param {CacheEntry} v - * @return {boolean} + * @ignore + * @type {string} */ - insert(k: string, v: CacheEntry): boolean; + get replacesClientId(): string; /** - * Gets a `CacheEntry` value at key `k`. - * @param {string} k - * @return {CacheEntry?} + * @ignore + * @type {boolean} */ - get(k: string): CacheEntry | null; + get isReload(): boolean; /** - * @param {string} k - * @return {boolean} + * @ignore + * @type {Promise} */ - delete(k: string): boolean; + get preloadResponse(): Promise; /** - * Predicate to determine if cache contains an entry at key `k`. - * @param {string} k - * @return {boolean} + * The `respondWith()` method of `FetchEvent` prevents the webview's + * default fetch handling, and allows you to provide a promise for a + * `Response` yourself. + * @param {Response|Promise} response */ - has(k: string): boolean; + respondWith(response: Response | Promise): void; + #private; + } + export class ExtendableMessageEvent extends ExtendableEvent { /** - * Composes an indexed packet into a new `Packet` - * @param {Packet} packet + * `ExtendableMessageEvent` class constructor. + * @param {string=} [type = 'message'] + * @param {object=} [options] */ - compose(packet: Packet, source?: CacheData): Promise; - sha1(value: any, toHex: any): Promise; + constructor(type?: string | undefined, options?: object | undefined); /** - * - * The summarize method returns a terse yet comparable summary of the cache - * contents. - * - * Think of the cache as a trie of hex characters, the summary returns a - * checksum for the current level of the trie and for its 16 children. - * - * This is similar to a merkel tree as equal subtrees can easily be detected - * without the need for further recursion. When the subtree checksums are - * inequivalent then further negotiation at lower levels may be required, this - * process continues until the two trees become synchonized. - * - * When the prefix is empty, the summary will return an array of 16 checksums - * these checksums provide a way of comparing that subtree with other peers. - * - * When a variable-length hexidecimal prefix is provided, then only cache - * member hashes sharing this prefix will be considered. - * - * For each hex character provided in the prefix, the trie will decend by one - * level, each level divides the 2^128 address space by 16. For exmaple... - * - * ``` - * Level 0 1 2 - * ---------------- - * 2b00 - * aa0e ━┓ ━┓ - * aa1b ┃ ┃ - * aae3 ┃ ┃ ━┓ - * aaea ┃ ┃ ┃ - * aaeb ┃ ━┛ ━┛ - * ab00 ┃ ━┓ - * ab1e ┃ ┃ - * ab2a ┃ ┃ - * abef ┃ ┃ - * abf0 ━┛ ━┛ - * bff9 - * ``` - * - * @param {string} prefix - a string of lowercased hexidecimal characters - * @return {Object} - * + * @type {any} */ - summarize(prefix?: string, predicate?: (o: any) => boolean): any; + get data(): any; + /** + * @type {MessagePort[]} + */ + get ports(): MessagePort[]; + /** + * @type {import('./clients.js').Client?} + */ + get source(): import("socket:service-worker/clients").Client; + /** + * @type {string} + */ + get lastEventId(): string; + #private; } - export default Cache; - export type CacheEntry = Packet; - export type CacheEntrySiblingResolver = (arg0: CacheEntry, arg1: CacheEntry) => number; - import { Buffer } from "socket:buffer"; - import { Packet } from "socket:stream-relay/packets"; + export class NotificationEvent extends ExtendableEvent { + constructor(type: any, options: any); + get action(): string; + get notification(): any; + #private; + } + namespace _default { + export { ExtendableMessageEvent }; + export { ExtendableEvent }; + export { FetchEvent }; + } + export default _default; + import { Context } from "socket:service-worker/context"; } -declare module "socket:stream-relay/nat" { - /** - * The NAT type is encoded using 5 bits: - * - * 0b00001 : the lsb indicates if endpoint dependence information is included - * 0b00010 : the second bit indicates the endpoint dependence value - * - * 0b00100 : the third bit indicates if firewall information is included - * 0b01000 : the fourth bit describes which requests can pass the firewall, only known IPs (0) or any IP (1) - * 0b10000 : the fifth bit describes which requests can pass the firewall, only known ports (0) or any port (1) - */ - /** - * Every remote will see the same IP:PORT mapping for this peer. - * - * :3333 ┌──────┐ - * :1111 ┌───▶ │ R1 │ - * ┌──────┐ ┌───────┐ │ └──────┘ - * │ P1 ├───▶│ NAT ├──┤ - * └──────┘ └───────┘ │ ┌──────┐ - * └───▶ │ R2 │ - * :3333 └──────┘ - */ - export const MAPPING_ENDPOINT_INDEPENDENT: 3; - /** - * Every remote will see a different IP:PORT mapping for this peer. - * - * :4444 ┌──────┐ - * :1111 ┌───▶ │ R1 │ - * ┌──────┐ ┌───────┐ │ └──────┘ - * │ P1 ├───▶│ NAT ├──┤ - * └──────┘ └───────┘ │ ┌──────┐ - * └───▶ │ R2 │ - * :5555 └──────┘ - */ - export const MAPPING_ENDPOINT_DEPENDENT: 1; - /** - * The firewall allows the port mapping to be accessed by: - * - Any IP:PORT combination (FIREWALL_ALLOW_ANY) - * - Any PORT on a previously connected IP (FIREWALL_ALLOW_KNOWN_IP) - * - Only from previously connected IP:PORT combinations (FIREWALL_ALLOW_KNOWN_IP_AND_PORT) - */ - export const FIREWALL_ALLOW_ANY: 28; - export const FIREWALL_ALLOW_KNOWN_IP: 12; - export const FIREWALL_ALLOW_KNOWN_IP_AND_PORT: 4; - /** - * The initial state of the nat is unknown and its value is 0 - */ - export const UNKNOWN: 0; - /** - * Full-cone NAT, also known as one-to-one NAT - * - * Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. - * - * @summary its a packet party at this mapping and everyone's invited - */ - export const UNRESTRICTED: number; +declare module "socket:http/adapters" { /** - * (Address)-restricted-cone NAT - * - * An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only - * if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter. - * - * @summary The NAT will drop your packets unless a peer within its network has previously messaged you from *any* port. + * @typedef {{ + * Connection: typeof import('../http.js').Connection, + * globalAgent: import('../http.js').Agent, + * IncomingMessage: typeof import('../http.js').IncomingMessage, + * ServerResponse: typeof import('../http.js').ServerResponse, + * STATUS_CODES: object, + * METHODS: string[] + * }} HTTPModuleInterface */ - export const ADDR_RESTRICTED: number; /** - * Port-restricted cone NAT - * - * An external host (hAddr:hPort) can send packets to iAddr:iPort by sending - * packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to - * hAddr:hPort. - * - * @summary The NAT will drop your packets unless a peer within its network - * has previously messaged you from this *specific* port. + * An abstract base clase for a HTTP server adapter. */ - export const PORT_RESTRICTED: number; + export class ServerAdapter extends EventTarget { + /** + * `ServerAdapter` class constructor. + * @ignore + * @param {import('../http.js').Server} server + * @param {HTTPModuleInterface} httpInterface + */ + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + /** + * A readonly reference to the underlying HTTP(S) server + * for this adapter. + * @type {import('../http.js').Server} + */ + get server(): import("socket:http").Server; + /** + * A readonly reference to the underlying HTTP(S) module interface + * for creating various HTTP module class objects. + * @type {HTTPModuleInterface} + */ + get httpInterface(): HTTPModuleInterface; + /** + * A readonly reference to the `AsyncContext.Variable` associated with this + * `ServerAdapter` instance. + */ + get context(): import("socket:async/context").Variable; + /** + * Called when the adapter should destroy itself. + * @abstract + */ + destroy(): Promise; + #private; + } /** - * Symmetric NAT - * - * Only an external host that receives a packet from an internal host can send - * a packet back. - * - * @summary The NAT will only accept replies to a correspondence initialized - * by itself, the mapping it created is only valid for you. + * A HTTP adapter for running a HTTP server in a service worker that uses the + * "fetch" event for the request and response lifecycle. */ - export const ENDPOINT_RESTRICTED: number; - export function isEndpointDependenceDefined(nat: any): boolean; - export function isFirewallDefined(nat: any): boolean; - export function isValid(nat: any): boolean; - export function toString(n: any): "UNRESTRICTED" | "ADDR_RESTRICTED" | "PORT_RESTRICTED" | "ENDPOINT_RESTRICTED" | "UNKNOWN"; - export function toStringStrategy(n: any): "STRATEGY_DEFER" | "STRATEGY_DIRECT_CONNECT" | "STRATEGY_TRAVERSAL_OPEN" | "STRATEGY_TRAVERSAL_CONNECT" | "STRATEGY_PROXY" | "STRATEGY_UNKNOWN"; - export const STRATEGY_DEFER: 0; - export const STRATEGY_DIRECT_CONNECT: 1; - export const STRATEGY_TRAVERSAL_OPEN: 2; - export const STRATEGY_TRAVERSAL_CONNECT: 3; - export const STRATEGY_PROXY: 4; - export function connectionStrategy(a: any, b: any): 0 | 1 | 2 | 3 | 4; + export class ServiceWorkerServerAdapter extends ServerAdapter { + /** + * Handles the 'install' service worker event. + * @ignore + * @param {import('../service-worker/events.js').ExtendableEvent} event + */ + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise; + /** + * Handles the 'activate' service worker event. + * @ignore + * @param {import('../service-worker/events.js').ExtendableEvent} event + */ + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise; + /** + * Handles the 'fetch' service worker event. + * @ignore + * @param {import('../service-worker/events.js').FetchEvent} + */ + onFetch(event: any): Promise; + } + namespace _default { + export { ServerAdapter }; + export { ServiceWorkerServerAdapter }; + } + export default _default; + export type HTTPModuleInterface = { + Connection: typeof import("socket:http").Connection; + globalAgent: import("socket:http").Agent; + IncomingMessage: typeof import("socket:http").IncomingMessage; + ServerResponse: typeof import("socket:http").ServerResponse; + STATUS_CODES: object; + METHODS: string[]; + }; } -declare module "socket:stream-relay/index" { - /** - * Computes rate limit predicate value for a port and address pair for a given - * threshold updating an input rates map. This method is accessed concurrently, - * the rates object makes operations atomic to avoid race conditions. - * - * @param {Map} rates - * @param {number} type - * @param {number} port - * @param {string} address - * @return {boolean} - */ - export function rateLimit(rates: Map, type: number, port: number, address: string, subclusterIdQuota: any): boolean; - export function debug(pid: any, ...args: any[]): void; +declare module "socket:http" { /** - * Retry delay in milliseconds for ping. - * @type {number} + * Makes a HTTP or `socket:` GET request. A simplified alias to `request()`. + * @param {string|object} optionsOrURL + * @param {(object|function)=} [options] + * @param {function=} [callback] + * @return {ClientRequest} */ - export const PING_RETRY: number; + export function get(optionsOrURL: string | object, options?: (object | Function) | undefined, callback?: Function | undefined): ClientRequest; /** - * Probe wait timeout in milliseconds. - * @type {number} + * Creates a HTTP server that can listen for incoming requests. + * Requests that are dispatched to this server depend on the context + * in which it is created, such as a service worker which will use a + * "fetch event" adapter. + * @param {object|function=} [options] + * @param {function=} [callback] + * @return {Server} */ - export const PROBE_WAIT: number; + export function createServer(options?: (object | Function) | undefined, callback?: Function | undefined): Server; /** - * Default keep alive timeout. - * @type {number} + * All known possible HTTP methods. + * @type {string[]} */ - export const DEFAULT_KEEP_ALIVE: number; + export const METHODS: string[]; /** - * Default rate limit threshold in milliseconds. - * @type {number} + * A mapping of status codes to status texts + * @type {object} */ - export const DEFAULT_RATE_LIMIT_THRESHOLD: number; - export function getRandomPort(ports: object, p: number | null): number; + export const STATUS_CODES: object; + export const CONTINUE: 100; + export const SWITCHING_PROTOCOLS: 101; + export const PROCESSING: 102; + export const EARLY_HINTS: 103; + export const OK: 200; + export const CREATED: 201; + export const ACCEPTED: 202; + export const NONAUTHORITATIVE_INFORMATION: 203; + export const NO_CONTENT: 204; + export const RESET_CONTENT: 205; + export const PARTIAL_CONTENT: 206; + export const MULTISTATUS: 207; + export const ALREADY_REPORTED: 208; + export const IM_USED: 226; + export const MULTIPLE_CHOICES: 300; + export const MOVED_PERMANENTLY: 301; + export const FOUND: 302; + export const SEE_OTHER: 303; + export const NOT_MODIFIED: 304; + export const USE_PROXY: 305; + export const TEMPORARY_REDIRECT: 307; + export const PERMANENT_REDIRECT: 308; + export const BAD_REQUEST: 400; + export const UNAUTHORIZED: 401; + export const PAYMENT_REQUIRED: 402; + export const FORBIDDEN: 403; + export const NOT_FOUND: 404; + export const METHOD_NOT_ALLOWED: 405; + export const NOT_ACCEPTABLE: 406; + export const PROXY_AUTHENTICATION_REQUIRED: 407; + export const REQUEST_TIMEOUT: 408; + export const CONFLICT: 409; + export const GONE: 410; + export const LENGTH_REQUIRED: 411; + export const PRECONDITION_FAILED: 412; + export const PAYLOAD_TOO_LARGE: 413; + export const URI_TOO_LONG: 414; + export const UNSUPPORTED_MEDIA_TYPE: 415; + export const RANGE_NOT_SATISFIABLE: 416; + export const EXPECTATION_FAILED: 417; + export const IM_A_TEAPOT: 418; + export const MISDIRECTED_REQUEST: 421; + export const UNPROCESSABLE_ENTITY: 422; + export const LOCKED: 423; + export const FAILED_DEPENDENCY: 424; + export const TOO_EARLY: 425; + export const UPGRADE_REQUIRED: 426; + export const PRECONDITION_REQUIRED: 428; + export const TOO_MANY_REQUESTS: 429; + export const REQUEST_HEADER_FIELDS_TOO_LARGE: 431; + export const UNAVAILABLE_FOR_LEGAL_REASONS: 451; + export const INTERNAL_SERVER_ERROR: 500; + export const NOT_IMPLEMENTED: 501; + export const BAD_GATEWAY: 502; + export const SERVICE_UNAVAILABLE: 503; + export const GATEWAY_TIMEOUT: 504; + export const HTTP_VERSION_NOT_SUPPORTED: 505; + export const VARIANT_ALSO_NEGOTIATES: 506; + export const INSUFFICIENT_STORAGE: 507; + export const LOOP_DETECTED: 508; + export const BANDWIDTH_LIMIT_EXCEEDED: 509; + export const NOT_EXTENDED: 510; + export const NETWORK_AUTHENTICATION_REQUIRED: 511; /** - * A `RemotePeer` represents an initial, discovered, or connected remote peer. - * Typically, you will not need to create instances of this class directly. + * The parent class of `ClientRequest` and `ServerResponse`. + * It is an abstract outgoing message from the perspective of the + * participants of an HTTP transaction. + * @see {@link https://nodejs.org/api/http.html#class-httpoutgoingmessage} */ - export class RemotePeer { + export class OutgoingMessage extends Writable { + /** + * `OutgoingMessage` class constructor. + * @ignore + */ + constructor(); + /** + * `true` if the headers were sent + * @type {boolean} + */ + headersSent: boolean; + /** + * Internal buffers + * @ignore + * @type {Buffer[]} + */ + get buffers(): Buffer[]; + /** + * An object of the outgoing message headers. + * This is equivalent to `getHeaders()` + * @type {object} + */ + get headers(): any; + /** + * @ignore + */ + get socket(): this; + /** + * `true` if the write state is "ended" + * @type {boolean} + */ + get writableEnded(): boolean; + /** + * `true` if the write state is "finished" + * @type {boolean} + */ + get writableFinished(): boolean; /** - * `RemotePeer` class constructor. - * @param {{ - * peerId?: string, - * address?: string, - * port?: number, - * natType?: number, - * clusters: object, - * reflectionId?: string, - * distance?: number, - * publicKey?: string, - * privateKey?: string, - * clock?: number, - * lastUpdate?: number, - * lastRequest?: number - * }} o + * The number of buffered bytes. + * @type {number} */ - constructor(o: { - peerId?: string; - address?: string; - port?: number; - natType?: number; - clusters: object; - reflectionId?: string; - distance?: number; - publicKey?: string; - privateKey?: string; - clock?: number; - lastUpdate?: number; - lastRequest?: number; - }, peer: any); - peerId: any; - address: any; - port: number; - natType: any; - clusters: {}; - pingId: any; - distance: number; - connected: boolean; - opening: number; - probed: number; - proxy: any; - clock: number; - uptime: number; - lastUpdate: number; - lastRequest: number; - localPeer: any; - write(sharedKey: any, args: any): Promise; + get writableLength(): number; + /** + * @ignore + * @type {boolean} + */ + get writableObjectMode(): boolean; + /** + * @ignore + */ + get writableCorked(): number; + /** + * The `highWaterMark` of the writable stream. + * @type {number} + */ + get writableHighWaterMark(): number; + /** + * @ignore + * @return {OutgoingMessage} + */ + addTrailers(headers: any): OutgoingMessage; + /** + * @ignore + * @return {OutgoingMessage} + */ + cork(): OutgoingMessage; + /** + * @ignore + * @return {OutgoingMessage} + */ + uncork(): OutgoingMessage; + /** + * Destroys the message. + * Once a socket is associated with the message and is connected, + * that socket will be destroyed as well. + * @param {Error?} [err] + * @return {OutgoingMessage} + */ + destroy(err?: Error | null): OutgoingMessage; + /** + * Finishes the outgoing message. + * @param {(Buffer|Uint8Array|string|function)=} [chunk] + * @param {(string|function)=} [encoding] + * @param {function=} [callback] + * @return {OutgoingMessage} + */ + end(chunk?: (Buffer | Uint8Array | string | Function) | undefined, encoding?: (string | Function) | undefined, callback?: Function | undefined): OutgoingMessage; + /** + * Append a single header value for the header object. + * @param {string} name + * @param {string|string[]} value + * @return {OutgoingMessage} + */ + appendHeader(name: string, value: string | string[]): OutgoingMessage; + /** + * Append a single header value for the header object. + * @param {string} name + * @param {string} value + * @return {OutgoingMessage} + */ + setHeader(name: string, value: string): OutgoingMessage; + /** + * Flushes the message headers. + */ + flushHeaders(): void; + /** + * Gets the value of the HTTP header with the given name. + * If that header is not set, the returned value will be `undefined`. + * @param {string} + * @return {string|undefined} + */ + getHeader(name: any): string | undefined; + /** + * Returns an array containing the unique names of the current outgoing + * headers. All names are lowercase. + * @return {string[]} + */ + getHeaderNames(): string[]; + /** + * @ignore + */ + getRawHeaderNames(): string[]; + /** + * Returns a copy of the HTTP headers as an object. + * @return {object} + */ + getHeaders(): object; + /** + * Returns true if the header identified by name is currently set in the + * outgoing headers. The header name is case-insensitive. + * @param {string} name + * @return {boolean} + */ + hasHeader(name: string): boolean; + /** + * Removes a header that is queued for implicit sending. + * @param {string} name + */ + removeHeader(name: string): void; + /** + * Sets the outgoing message timeout with an optional callback. + * @param {number} timeout + * @param {function=} [callback] + * @return {OutgoingMessage} + */ + setTimeout(timeout: number, callback?: Function | undefined): OutgoingMessage; + /** + * @ignore + */ + _implicitHeader(): void; + #private; } /** - * `Peer` class factory. - * @param {{ createSocket: function('udp4', null, object?): object }} options + * An `IncomingMessage` object is created by `Server` or `ClientRequest` and + * passed as the first argument to the 'request' and 'response' event + * respectively. + * It may be used to access response status, headers, and data. + * @see {@link https://nodejs.org/api/http.html#class-httpincomingmessage} */ - export class Peer { + export class IncomingMessage extends Readable { /** - * `Peer` class constructor. - * @param {object=} opts - Options - * @param {Buffer} opts.peerId - A 32 byte buffer (ie, `Encryption.createId()`). - * @param {Buffer} opts.clusterId - A 32 byte buffer (ie, `Encryption.createClusterId()`). - * @param {number=} opts.port - A port number. - * @param {number=} opts.probeInternalPort - An internal port number (semi-private for testing). - * @param {number=} opts.probeExternalPort - An external port number (semi-private for testing). - * @param {number=} opts.natType - A nat type. - * @param {string=} opts.address - An ipv4 address. - * @param {number=} opts.keepalive - The interval of the main loop. - * @param {function=} opts.siblingResolver - A function that can be used to determine canonical data in case two packets have concurrent clock values. - * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). + * `IncomingMessage` class constructor. + * @ignore + * @param {object} options */ - constructor(persistedState: {}, dgram: object); - port: any; - address: any; - natType: number; - nextNatType: number; - clusters: {}; - reflectionId: any; - reflectionTimeout: any; - reflectionStage: number; - reflectionRetry: number; - reflectionFirstResponder: any; - peerId: string; - isListening: boolean; - ctime: number; - lastUpdate: number; - lastSync: number; - closing: boolean; - clock: number; - unpublished: {}; - cache: any; - uptime: number; - maxHops: number; - bdpCache: number[]; - dgram: () => never; - onListening: any; - onDelete: any; - sendQueue: any[]; - firewall: any; - rates: Map; - streamBuffer: Map; - gate: Map; - returnRoutes: Map; - metrics: { - i: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - REJECTED: number; - }; - o: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - }; - }; - peers: any; - encryption: Encryption; - config: any; - _onError: (err: any) => any; - socket: any; - probeSocket: any; + constructor(options: object); + set url(url: string); + /** + * The URL for this incoming message. This value is not absolute with + * respect to the protocol and hostname. It includes the path and search + * query component parameters. + * @type {string} + */ + get url(): string; /** - * An implementation for clearning an interval that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore + * @type {Server} */ - _clearInterval(tid: any): undefined; + get server(): exports.Server; /** - * An implementation for clearning a timeout that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore + * @type {AsyncContext.Variable} */ - _clearTimeout(tid: any): undefined; + get context(): typeof import("socket:async/context").Variable; /** - * An implementation of an internal timer that can be overridden by the test suite - * @return {Number} - * @ignore + * This property will be `true` if a complete HTTP message has been received + * and successfully parsed. + * @type {boolean} */ - _setInterval(fn: any, t: any): number; + get complete(): boolean; + /** + * An object of the incoming message headers. + * @type {object} + */ + get headers(): any; + /** + * Similar to `message.headers`, but there is no join logic and the values + * are always arrays of strings, even for headers received just once. + * @type {object} + */ + get headersDistinct(): any; + /** + * The HTTP major version of this request. + * @type {number} + */ + get httpVersionMajor(): number; + /** + * The HTTP minor version of this request. + * @type {number} + */ + get httpVersionMinor(): number; + /** + * The HTTP version string. + * A concatenation of `httpVersionMajor` and `httpVersionMinor`. + * @type {string} + */ + get httpVersion(): string; + /** + * The HTTP request method. + * @type {string} + */ + get method(): string; + /** + * The raw request/response headers list potentially as they were received. + * @type {string[]} + */ + get rawHeaders(): string[]; /** - * An implementation of an timeout timer that can be overridden by the test suite - * @return {Number} * @ignore */ - _setTimeout(fn: any, t: any): number; + get rawTrailers(): any[]; /** - * A method that encapsulates the listing procedure - * @return {undefined} * @ignore */ - _listen(): undefined; - init(cb: any): Promise; - onReady: any; - mainLoopTimer: number; + get socket(): this; + /** + * The HTTP request status code. + * Only valid for response obtained from `ClientRequest`. + * @type {number} + */ + get statusCode(): number; + /** + * The HTTP response status message (reason phrase). + * Such as "OK" or "Internal Server Error." + * Only valid for response obtained from `ClientRequest`. + * @type {string?} + */ + get statusMessage(): string; + /** + * An alias for `statusCode` + * @type {number} + */ + get status(): number; + /** + * An alias for `statusMessage` + * @type {string?} + */ + get statusText(): string; /** - * Continuously evaluate the state of the peer and its network - * @return {undefined} * @ignore */ - _mainLoop(ts: any): undefined; + get trailers(): {}; /** - * Enqueue packets to be sent to the network - * @param {Buffer} data - An encoded packet - * @param {number} port - The desination port of the remote host - * @param {string} address - The destination address of the remote host - * @param {Socket=this.socket} socket - The socket to send on - * @return {undefined} * @ignore */ - send(data: Buffer, port: number, address: string, socket?: any): undefined; + get trailersDistinct(): {}; /** - * @private + * Gets the value of the HTTP header with the given name. + * If that header is not set, the returned value will be `undefined`. + * @param {string} + * @return {string|undefined} */ - private _scheduleSend; - sendTimeout: number; + getHeader(name: any): string | undefined; /** - * @private + * Returns an array containing the unique names of the current outgoing + * headers. All names are lowercase. + * @return {string[]} */ - private _dequeue; + getHeaderNames(): string[]; /** - * Send any unpublished packets - * @return {undefined} * @ignore */ - sendUnpublished(): undefined; + getRawHeaderNames(): string[]; /** - * Get the serializable state of the peer (can be passed to the constructor or create method) - * @return {undefined} + * Returns a copy of the HTTP headers as an object. + * @return {object} */ - getState(): undefined; - getInfo(): Promise<{ - address: any; - port: any; - clock: number; - uptime: number; - natType: number; - natName: string; - peerId: string; - }>; - cacheInsert(packet: any): Promise; - addIndexedPeer(info: any): Promise; - reconnect(): Promise; - disconnect(): Promise; - probeReflectionTimeout: any; - sealUnsigned(...args: any[]): Promise; - openUnsigned(...args: any[]): Promise; - seal(...args: any[]): Promise; - open(...args: any[]): Promise; - addEncryptionKey(...args: any[]): Promise; + getHeaders(): object; /** - * Get a selection of known peers - * @return {Array} + * Returns true if the header identified by name is currently set in the + * outgoing headers. The header name is case-insensitive. + * @param {string} name + * @return {boolean} + */ + hasHeader(name: string): boolean; + /** + * Sets the incoming message timeout with an optional callback. + * @param {number} timeout + * @param {function=} [callback] + * @return {IncomingMessage} + */ + setTimeout(timeout: number, callback?: Function | undefined): IncomingMessage; + #private; + } + /** + * An object that is created internally and returned from `request()`. + * @see {@link https://nodejs.org/api/http.html#class-httpclientrequest} + */ + export class ClientRequest extends exports.OutgoingMessage { + /** + * `ClientRequest` class constructor. * @ignore + * @param {object} options */ - getPeers(packet: any, peers: any, ignorelist: any, filter?: (o: any) => any): Array; + constructor(options: object); + /** + * The HTTP request method. + * @type {string} + */ + get method(): string; + /** + * The request protocol + * @type {string?} + */ + get protocol(): string; + /** + * The request path. + * @type {string} + */ + get path(): string; + /** + * The request host name (including port). + * @type {string?} + */ + get host(): string; + /** + * The URL for this outgoing message. This value is not absolute with + * respect to the protocol and hostname. It includes the path and search + * query component parameters. + * @type {string} + */ + get url(): string; /** - * Send an eventually consistent packet to a selection of peers (fanout) - * @return {undefined} * @ignore + * @type {boolean} */ - mcast(packet: any, ignorelist?: any[]): undefined; + get finished(): boolean; /** - * The process of determining this peer's NAT behavior (firewall and dependentness) - * @return {undefined} * @ignore + * @type {boolean} */ - requestReflection(): undefined; + get reusedSocket(): boolean; /** - * Ping another peer - * @return {PacketPing} * @ignore + * @param {boolean=} [value] + * @return {ClientRequest} */ - ping(peer: any, withRetry: any, props: any, socket: any): PacketPing; + setNoDelay(value?: boolean | undefined): ClientRequest; /** - * Get a peer - * @return {RemotePeer} * @ignore + * @param {boolean=} [enable] + * @param {number=} [initialDelay] + * @return {ClientRequest} */ - getPeer(id: any): RemotePeer; + setSocketKeepAlive(enable?: boolean | undefined, initialDelay?: number | undefined): ClientRequest; + #private; + } + /** + * An object that is created internally by a `Server` instance, not by the user. + * It is passed as the second parameter to the 'request' event. + * @see {@link https://nodejs.org/api/http.html#class-httpserverresponse} + */ + export class ServerResponse extends exports.OutgoingMessage { /** - * This should be called at least once when an app starts to multicast - * this peer, and starts querying the network to discover peers. - * @param {object} keys - Created by `Encryption.createKeyPair()`. - * @param {object=} args - Options - * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. - * @return {RemotePeer} + * `ServerResponse` class constructor. + * @param {object} options + */ + constructor(options: object); + /** + * @type {Server} + */ + get server(): exports.Server; + /** + * A reference to the original HTTP request object. + * @type {IncomingMessage} + */ + get request(): exports.IncomingMessage; + /** + * A reference to the original HTTP request object. + * @type {IncomingMessage} */ - join(sharedKey: any, args?: object | undefined): RemotePeer; + get req(): exports.IncomingMessage; + set statusCode(statusCode: number); /** - * @param {Packet} T - The constructor to be used to create packets. - * @param {Any} message - The message to be split and packaged. - * @return {Array>} - * @ignore + * The HTTP request status code. + * Only valid for response obtained from `ClientRequest`. + * @type {number} */ - _message2packets(T: Packet, message: Any, args: any): Array>; + get statusCode(): number; + set statusMessage(statusMessage: string); /** - * Sends a packet into the network that will be replicated and buffered. - * Each peer that receives it will buffer it until TTL and then replicate - * it provided it has has not exceeded their maximum number of allowed hops. - * - * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. - * @param {object} args - The arguments to be applied. - * @param {Buffer} args.message - The message to be encrypted by keys and sent. - * @param {Packet=} args.packet - The previous packet in the packet chain. - * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. - * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. - * @return {Array} + * The HTTP response status message (reason phrase). + * Such as "OK" or "Internal Server Error." + * Only valid for response obtained from `ClientRequest`. + * @type {string?} */ - publish(sharedKey: any, args: { - message: Buffer; - packet?: Packet | undefined; - usr1: Buffer; - usr2: Buffer; - }): Array; + get statusMessage(): string; + set status(status: number); /** - * @return {undefined} + * An alias for `statusCode` + * @type {number} */ - sync(peer: any): undefined; - close(): void; + get status(): number; + set statusText(statusText: string); /** - * Deploy a query into the network - * @return {undefined} - * + * An alias for `statusMessage` + * @type {string?} */ - query(query: any): undefined; + get statusText(): string; + set sendDate(value: boolean); /** - * - * This is a default implementation for deciding what to summarize - * from the cache when receiving a request to sync. that can be overridden - * + * If `true`, the "Date" header will be automatically generated and sent in + * the response if it is not already present in the headers. + * Defaults to `true`. + * @type {boolean} */ - cachePredicate(packet: any): boolean; + get sendDate(): boolean; /** - * A connection was made, add the peer to the local list of known - * peers and call the onConnection if it is defined by the user. - * - * @return {undefined} * @ignore */ - _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; - connections: Map; + writeContinue(): this; /** - * Received a Sync Packet - * @return {undefined} * @ignore */ - _onSync(packet: any, port: any, address: any): undefined; + writeEarlyHints(): this; /** - * Received a Query Packet - * - * a -> b -> c -> (d) -> c -> b -> a - * - * @return {undefined} - * @example - * - * ```js - * peer.onQuery = (packet) => { - * // - * // read a database or something - * // - * return { - * message: Buffer.from('hello'), - * publicKey: '', - * privateKey: '' - * } - * } - * ``` + * @ignore */ - _onQuery(packet: any, port: any, address: any): undefined; + writeProcessing(): this; /** - * Received a Ping Packet - * @return {undefined} - * @ignore + * Writes the response header to the request. + * The `statusCode` is a 3-digit HTTP status code, like 200 or 404. + * The last argument, `headers`, are the response headers. + * Optionally one can give a human-readable `statusMessage` + * as the second argument. + * @param {number|string} statusCode + * @param {string|object|string[]} [statusMessage] + * @param {object|string[]} [headers] + * @return {ClientRequest} */ - _onPing(packet: any, port: any, address: any): undefined; + writeHead(statusCode: number | string, statusMessage?: string | object | string[], headers?: object | string[]): ClientRequest; + #private; + } + /** + * An options object container for an `Agent` instance. + */ + export class AgentOptions { /** - * Received a Pong Packet - * @return {undefined} + * `AgentOptions` class constructor. * @ignore + * @param {{ + * keepAlive?: boolean, + * timeout?: number + * }} [options] */ - _onPong(packet: any, port: any, address: any): undefined; + constructor(options?: { + keepAlive?: boolean; + timeout?: number; + }); + keepAlive: boolean; + timeout: number; + } + /** + * An Agent is responsible for managing connection persistence + * and reuse for HTTP clients. + * @see {@link https://nodejs.org/api/http.html#class-httpagent} + */ + export class Agent extends EventEmitter { /** - * Received an Intro Packet - * @return {undefined} - * @ignore + * `Agent` class constructor. + * @param {AgentOptions=} [options] */ - _onIntro(packet: any, port: any, address: any, _: any, opts?: { - attempts: number; - }): undefined; - socketPool: any[]; + constructor(options?: AgentOptions | undefined); + defaultProtocol: string; + options: any; + requests: Set; + sockets: {}; + maxFreeSockets: number; + maxTotalSockets: number; + maxSockets: number; /** - * Received an Join Packet - * @return {undefined} * @ignore */ - _onJoin(packet: any, port: any, address: any, data: any): undefined; + get freeSockets(): {}; /** - * Received an Publish Packet - * @return {undefined} * @ignore + * @param {object} options */ - _onPublish(packet: any, port: any, address: any, data: any): undefined; + getName(options: object): string; + /** + * Produces a socket/stream to be used for HTTP requests. + * @param {object} options + * @param {function(Duplex)=} [callback] + * @return {Duplex} + */ + createConnection(options: object, callback?: ((arg0: Duplex) => any) | undefined): Duplex; /** - * Received an Stream Packet - * @return {undefined} * @ignore */ - _onStream(packet: any, port: any, address: any, data: any): undefined; + keepSocketAlive(): void; /** - * Received any packet on the probe port to determine the firewall: - * are you port restricted, host restricted, or unrestricted. - * @return {undefined} * @ignore */ - _onProbeMessage(data: any, { port, address }: { - port: any; - address: any; - }): undefined; + reuseSocket(): void; /** - * When a packet is received it is decoded, the packet contains the type - * of the message. Based on the message type it is routed to a function. - * like WebSockets, don't answer queries unless we know its another SRP peer. - * - * @param {Buffer|Uint8Array} data - * @param {{ port: number, address: string }} info + * @ignore */ - _onMessage(data: Buffer | Uint8Array, { port, address }: { - port: number; - address: string; - }): Promise; + destroy(): void; } - export default Peer; - import { Packet } from "socket:stream-relay/packets"; - import { sha256 } from "socket:stream-relay/packets"; - import { Cache } from "socket:stream-relay/cache"; - import { Encryption } from "socket:stream-relay/encryption"; - import * as NAT from "socket:stream-relay/nat"; - import { Buffer } from "socket:buffer"; - import { PacketPing } from "socket:stream-relay/packets"; - import { PacketPublish } from "socket:stream-relay/packets"; - export { Packet, sha256, Cache, Encryption, NAT }; -} - -declare module "socket:node/index" { - export default network; - export const network: any; - import { Cache } from "socket:stream-relay/index"; - import { sha256 } from "socket:stream-relay/index"; - import { Encryption } from "socket:stream-relay/index"; - import { Packet } from "socket:stream-relay/index"; - import { NAT } from "socket:stream-relay/index"; - export { Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:index" { - import { network } from "socket:node/index"; - import { Cache } from "socket:node/index"; - import { sha256 } from "socket:node/index"; - import { Encryption } from "socket:node/index"; - import { Packet } from "socket:node/index"; - import { NAT } from "socket:node/index"; - export { network, Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:internal/serialize" { - export default function serialize(value: any): any; -} - -declare module "socket:commonjs/cache" { /** - * @typedef {{ - * types?: object, - * loader?: import('./loader.js').Loader - * }} CacheOptions + * The global and default HTTP agent. + * @type {Agent} */ - export const CACHE_CHANNEL_MESSAGE_ID: "id"; - export const CACHE_CHANNEL_MESSAGE_REPLICATE: "replicate"; + export const globalAgent: Agent; /** - * @typedef {{ - * name: string - * }} StorageOptions + * A duplex stream between a HTTP request `IncomingMessage` and the + * response `ServerResponse` */ + export class Connection extends Duplex { + /** + * `Connection` class constructor. + * @ignore + * @param {Server} server + * @param {IncomingMessage} incomingMessage + * @param {ServerResponse} serverResponse + */ + constructor(server: Server, incomingMessage: IncomingMessage, serverResponse: ServerResponse); + server: any; + active: boolean; + request: any; + response: any; + /** + * Closes the connection, destroying the underlying duplex, request, and + * response streams. + * @return {Connection} + */ + close(): Connection; + } /** - * An storage context object with persistence and durability - * for service worker storages. + * A nodejs compat HTTP server typically intended for running in a "worker" + * environment. + * @see {@link https://nodejs.org/api/http.html#class-httpserver} */ - export class Storage extends EventTarget { + export class Server extends EventEmitter { + requestTimeout: number; + timeout: number; + maxRequestsPerSocket: number; + keepAliveTimeout: number; + headersTimeout: number; /** - * Maximum entries that will be restored from storage into the context object. - * @type {number} + * @ignore + * @type {AsyncResource} */ - static MAX_CONTEXT_ENTRIES: number; + get resource(): AsyncResource; /** - * A mapping of known `Storage` instances. - * @type {Map} + * The adapter interface for this `Server` instance. + * @ignore */ - static instances: Map; + get adapterInterace(): { + Connection: typeof exports.Connection; + globalAgent: exports.Agent; + IncomingMessage: typeof exports.IncomingMessage; + METHODS: string[]; + ServerResponse: typeof exports.ServerResponse; + STATUS_CODES: any; + }; /** - * Opens an storage for a particular name. - * @param {StorageOptions} options - * @return {Promise} + * `true` if the server is closed, otherwise `false`. + * @type {boolean} */ - static open(options: StorageOptions): Promise; + get closed(): boolean; /** - * `Storage` class constructor - * @ignore - * @param {StorageOptions} options + * The host to listen to. This value can be `null`. + * Defaults to `location.hostname`. This value + * is used to filter requests by hostname. + * @type {string?} */ - constructor(options: StorageOptions); + get host(): string; /** - * A reference to the currently opened storage database. - * @type {import('../internal/database.js').Database} + * The `port` to listen on. This value can be `0`, which is the default. + * This value is used to filter requests by port, if given. A port value + * of `0` does not filter on any port. + * @type {number} */ - get database(): import("socket:internal/database").Database; + get port(): number; /** - * `true` if the storage is opened, otherwise `false`. - * @type {boolean} + * A readonly array of all active or inactive (idle) connections. + * @type {Connection[]} */ - get opened(): boolean; + get connections(): exports.Connection[]; /** - * `true` if the storage is opening, otherwise `false`. + * `true` if the server is listening for requests. * @type {boolean} */ - get opening(): boolean; - /** - * A proxied object for reading and writing storage state. - * Values written to this object must be cloneable with respect to the - * structured clone algorithm. - * @see {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm} - * @type {Proxy} - */ - get context(): ProxyConstructor; + get listening(): boolean; + set maxConnections(value: number); /** - * The current storage name. This value is also used as the - * internal database name. - * @type {string} + * The number of concurrent max connections this server should handle. + * Default: Infinity + * @type {number} */ - get name(): string; + get maxConnections(): number; /** - * A promise that resolves when the storage is opened. - * @type {Promise?} + * Gets the HTTP server address and port that it this server is + * listening (emulated) on in the runtime with respect to the + * adapter internal being used by the server. + * @return {{ family: string, address: string, port: number}} */ - get ready(): Promise; + address(): { + family: string; + address: string; + port: number; + }; /** - * @ignore - * @param {Promise} promise + * Closes the server. + * @param {function=} [close] */ - forwardRequest(promise: Promise): Promise; + close(callback?: any): void; /** - * Resets the current storage to an empty state. + * Closes all connections. */ - reset(): Promise; + closeAllConnections(): void; /** - * Synchronizes database entries into the storage context. + * Closes all idle connections. */ - sync(options?: any): Promise; + closeIdleConnections(): void; /** - * Opens the storage. * @ignore */ - open(options?: any): Promise; + setTimeout(timeout?: number, callback?: any): this; /** - * Closes the storage database, purging existing state. - * @ignore + * @param {number|object=} [port] + * @param {string=} [host] + * @param {function|null} [unused] + * @param {function=} [callback + * @return Server */ - close(): Promise; + listen(port?: (number | object) | undefined, host?: string | undefined, unused?: Function | null, callback?: Function | undefined): this; #private; } + export default exports; + import { Writable } from "socket:stream"; + import { Buffer } from "socket:buffer"; + import { Readable } from "socket:stream"; + import { EventEmitter } from "socket:events"; + import { Duplex } from "socket:stream"; + import { AsyncResource } from "socket:async/resource"; + import * as exports from "socket:http"; + +} + +declare module "socket:fetch/index" { + export default fetch; + import { fetch } from "socket:fetch/fetch"; + import { Headers } from "socket:fetch/fetch"; + import { Request } from "socket:fetch/fetch"; + import { Response } from "socket:fetch/fetch"; + export { fetch, Headers, Request, Response }; +} + +declare module "socket:fetch" { + export * from "socket:fetch/index"; + export default fetch; + import fetch from "socket:fetch/index"; +} + +declare module "socket:https" { + /** + * Makes a HTTPS request, optionally a `socket://` for relative paths when + * `socket:` is the origin protocol. + * @param {string|object} optionsOrURL + * @param {(object|function)=} [options] + * @param {function=} [callback] + * @return {ClientRequest} + */ + export function request(optionsOrURL: string | object, options?: (object | Function) | undefined, callback?: Function | undefined): ClientRequest; + /** + * Makes a HTTPS or `socket:` GET request. A simplified alias to `request()`. + * @param {string|object} optionsOrURL + * @param {(object|function)=} [options] + * @param {function=} [callback] + * @return {ClientRequest} + */ + export function get(optionsOrURL: string | object, options?: (object | Function) | undefined, callback?: Function | undefined): ClientRequest; + /** + * Creates a HTTPS server that can listen for incoming requests. + * Requests that are dispatched to this server depend on the context + * in which it is created, such as a service worker which will use a + * "fetch event" adapter. + * @param {object|function=} [options] + * @param {function=} [callback] + * @return {Server} + */ + export function createServer(...args: any[]): Server; + export const CONTINUE: 100; + export const SWITCHING_PROTOCOLS: 101; + export const PROCESSING: 102; + export const EARLY_HINTS: 103; + export const OK: 200; + export const CREATED: 201; + export const ACCEPTED: 202; + export const NONAUTHORITATIVE_INFORMATION: 203; + export const NO_CONTENT: 204; + export const RESET_CONTENT: 205; + export const PARTIAL_CONTENT: 206; + export const MULTISTATUS: 207; + export const ALREADY_REPORTED: 208; + export const IM_USED: 226; + export const MULTIPLE_CHOICES: 300; + export const MOVED_PERMANENTLY: 301; + export const FOUND: 302; + export const SEE_OTHER: 303; + export const NOT_MODIFIED: 304; + export const USE_PROXY: 305; + export const TEMPORARY_REDIRECT: 307; + export const PERMANENT_REDIRECT: 308; + export const BAD_REQUEST: 400; + export const UNAUTHORIZED: 401; + export const PAYMENT_REQUIRED: 402; + export const FORBIDDEN: 403; + export const NOT_FOUND: 404; + export const METHOD_NOT_ALLOWED: 405; + export const NOT_ACCEPTABLE: 406; + export const PROXY_AUTHENTICATION_REQUIRED: 407; + export const REQUEST_TIMEOUT: 408; + export const CONFLICT: 409; + export const GONE: 410; + export const LENGTH_REQUIRED: 411; + export const PRECONDITION_FAILED: 412; + export const PAYLOAD_TOO_LARGE: 413; + export const URI_TOO_LONG: 414; + export const UNSUPPORTED_MEDIA_TYPE: 415; + export const RANGE_NOT_SATISFIABLE: 416; + export const EXPECTATION_FAILED: 417; + export const IM_A_TEAPOT: 418; + export const MISDIRECTED_REQUEST: 421; + export const UNPROCESSABLE_ENTITY: 422; + export const LOCKED: 423; + export const FAILED_DEPENDENCY: 424; + export const TOO_EARLY: 425; + export const UPGRADE_REQUIRED: 426; + export const PRECONDITION_REQUIRED: 428; + export const TOO_MANY_REQUESTS: 429; + export const REQUEST_HEADER_FIELDS_TOO_LARGE: 431; + export const UNAVAILABLE_FOR_LEGAL_REASONS: 451; + export const INTERNAL_SERVER_ERROR: 500; + export const NOT_IMPLEMENTED: 501; + export const BAD_GATEWAY: 502; + export const SERVICE_UNAVAILABLE: 503; + export const GATEWAY_TIMEOUT: 504; + export const HTTP_VERSION_NOT_SUPPORTED: 505; + export const VARIANT_ALSO_NEGOTIATES: 506; + export const INSUFFICIENT_STORAGE: 507; + export const LOOP_DETECTED: 508; + export const BANDWIDTH_LIMIT_EXCEEDED: 509; + export const NOT_EXTENDED: 510; + export const NETWORK_AUTHENTICATION_REQUIRED: 511; + /** + * All known possible HTTP methods. + * @type {string[]} + */ + export const METHODS: string[]; + /** + * A mapping of status codes to status texts + * @type {object} + */ + export const STATUS_CODES: object; + /** + * An options object container for an `Agent` instance. + */ + export class AgentOptions extends http.AgentOptions { + } /** - * A container for `Snapshot` data storage. + * An Agent is responsible for managing connection persistence + * and reuse for HTTPS clients. + * @see {@link https://nodejs.org/api/https.html#class-httpsagent} */ - export class SnapshotData { - /** - * `SnapshotData` class constructor. - * @param {object=} [data] - */ - constructor(data?: object | undefined); - toJSON: () => this; - [Symbol.toStringTag]: string; + export class Agent extends http.Agent { } /** - * A container for storing a snapshot of the cache data. + * An object that is created internally and returned from `request()`. + * @see {@link https://nodejs.org/api/http.html#class-httpclientrequest} */ - export class Snapshot { - /** - * @type {typeof SnapshotData} - */ - static Data: typeof SnapshotData; - /** - * A reference to the snapshot data. - * @type {Snapshot.Data} - */ - get data(): typeof SnapshotData; - /** - * @ignore - * @return {object} - */ - toJSON(): object; - #private; + export class ClientRequest extends http.ClientRequest { } /** - * An interface for managing and performing operations on a collection - * of `Cache` objects. + * The parent class of `ClientRequest` and `ServerResponse`. + * It is an abstract outgoing message from the perspective of the + * participants of an HTTP transaction. + * @see {@link https://nodejs.org/api/http.html#class-httpoutgoingmessage} */ - export class CacheCollection { - /** - * `CacheCollection` class constructor. - * @ignore - * @param {Cache[]|Record} collection - */ - constructor(collection: Cache[] | Record); - /** - * Adds a `Cache` instance to the collection. - * @param {string|Cache} name - * @param {Cache=} [cache] - * @param {boolean} - */ - add(name: string | Cache, cache?: Cache | undefined): any; - /** - * Calls a method on each `Cache` object in the collection. - * @param {string} method - * @param {...any} args - * @return {Promise>} - */ - call(method: string, ...args: any[]): Promise>; - restore(): Promise>; - reset(): Promise>; - snapshot(): Promise>; - get(key: any): Promise>; - delete(key: any): Promise>; - keys(key: any): Promise>; - values(key: any): Promise>; - clear(key: any): Promise>; + export class OutgoingMessage extends http.OutgoingMessage { } /** - * A container for a shared cache that lives for the life time of - * application execution. Updates to this storage are replicated to other - * instances in the application context, including windows and workers. + * An `IncomingMessage` object is created by `Server` or `ClientRequest` and + * passed as the first argument to the 'request' and 'response' event + * respectively. + * It may be used to access response status, headers, and data. + * @see {@link https://nodejs.org/api/http.html#class-httpincomingmessage} */ - export class Cache { - /** - * A globally shared type mapping for the cache to use when - * derserializing a value. - * @type {Map} - */ - static types: Map; - /** - * A globally shared cache store keyed by cache name. This is useful so - * when multiple instances of a `Cache` are created, they can share the - * same data store, reducing duplications. - * @type {Record} - */ - static shared: Record>; - /** - * A mapping of opened `Storage` instances. - * @type {Map} - */ - static storages: Map; - /** - * The `Cache.Snapshot` class. - * @type {typeof Snapshot} - */ - static Snapshot: typeof Snapshot; - /** - * The `Cache.Storage` class - * @type {typeof Storage} - */ - static Storage: typeof Storage; - /** - * Creates a snapshot of the current cache which can be serialized and - * stored in persistent storage. - * @return {Snapshot} - */ - static snapshot(): Snapshot; - /** - * Restore caches from persistent storage. - * @param {string[]} names - * @return {Promise} - */ - static restore(names: string[]): Promise; - /** - * `Cache` class constructor. - * @param {string} name - * @param {CacheOptions=} [options] - */ - constructor(name: string, options?: CacheOptions | undefined); - /** - * The unique ID for this cache. - * @type {string} - */ - get id(): string; - /** - * The loader associated with this cache. - * @type {import('./loader.js').Loader} - */ - get loader(): import("socket:commonjs/loader").Loader; - /** - * A reference to the persisted storage. - * @type {Storage} - */ - get storage(): Storage; - /** - * The cache name - * @type {string} - */ - get name(): string; - /** - * The underlying cache data map. - * @type {Map} - */ - get data(): Map; - /** - * The broadcast channel associated with this cach. - * @type {BroadcastChannel} - */ - get channel(): BroadcastChannel; - /** - * The size of the cache. - * @type {number} - */ - get size(): number; - /** - * @type {Map} - */ - get types(): Map; - /** - * Resets the cache map and persisted storage. - */ - reset(): Promise; - /** - * Restores cache data from storage. - */ - restore(): Promise; - /** - * Creates a snapshot of the current cache which can be serialized and - * stored in persistent storage. - * @return {Snapshot.Data} - */ - snapshot(): typeof SnapshotData; + export class IncomingMessage extends http.IncomingMessage { + } + /** + * An object that is created internally by a `Server` instance, not by the user. + * It is passed as the second parameter to the 'request' event. + * @see {@link https://nodejs.org/api/http.html#class-httpserverresponse} + */ + export class ServerResponse extends http.ServerResponse { + } + /** + * A duplex stream between a HTTP request `IncomingMessage` and the + * response `ServerResponse` + */ + export class Connection extends http.Connection { + } + /** + * A nodejs compat HTTP server typically intended for running in a "worker" + * environment. + * @see {@link https://nodejs.org/api/http.html#class-httpserver} + */ + export class Server extends http.Server { + } + /** + * The global and default HTTPS agent. + * @type {Agent} + */ + export const globalAgent: Agent; + export default exports; + import http from "socket:http"; + import * as exports from "socket:http"; +} + +declare module "socket:language" { + /** + * Look up a language name or code by query. + * @param {string} query + * @param {object=} [options] + * @param {boolean=} [options.strict = false] + * @return {?LanguageQueryResult[]} + */ + export function lookup(query: string, options?: object | undefined, ...args: any[]): LanguageQueryResult[] | null; + /** + * Describe a language by tag + * @param {string} query + * @param {object=} [options] + * @param {boolean=} [options.strict = true] + * @return {?LanguageDescription[]} + */ + export function describe(query: string, options?: object | undefined): LanguageDescription[] | null; + /** + * A list of ISO 639-1 language names. + * @type {string[]} + */ + export const names: string[]; + /** + * A list of ISO 639-1 language codes. + * @type {string[]} + */ + export const codes: string[]; + /** + * A list of RFC 5646 language tag identifiers. + * @see {@link http://tools.ietf.org/html/rfc5646} + */ + export const tags: Enumeration; + /** + * A list of RFC 5646 language tag titles corresponding + * to language tags. + * @see {@link http://tools.ietf.org/html/rfc5646} + */ + export const descriptions: Enumeration; + /** + * A container for a language query response containing an ISO language + * name and code. + * @see {@link https://www.sitepoint.com/iso-2-letter-language-codes} + */ + export class LanguageQueryResult { /** - * Get a value at `key`. - * @param {string} key - * @return {object|undefined} + * `LanguageQueryResult` class constructor. + * @param {string} code + * @param {string} name + * @param {string[]} [tags] */ - get(key: string): object | undefined; + constructor(code: string, name: string, tags?: string[]); /** - * Set `value` at `key`. - * @param {string} key - * @param {object} value - * @return {Cache} + * The language code corresponding to the query. + * @type {string} */ - set(key: string, value: object): Cache; + get code(): string; /** - * Returns `true` if `key` is in cache, otherwise `false`. - * @param {string} - * @return {boolean} + * The language name corresponding to the query. + * @type {string} */ - has(key: any): boolean; + get name(): string; /** - * Delete a value at `key`. - * This does not replicate to shared caches. - * @param {string} key - * @return {boolean} + * The language tags corresponding to the query. + * @type {string[]} */ - delete(key: string): boolean; + get tags(): string[]; /** - * Returns an iterator for all cache keys. - * @return {object} + * JSON represenation of a `LanguageQueryResult` instance. + * @return {{ + * code: string, + * name: string, + * tags: string[] + * }} */ - keys(): object; + toJSON(): { + code: string; + name: string; + tags: string[]; + }; /** - * Returns an iterator for all cache values. - * @return {object} + * Internal inspect function. + * @ignore + * @return {LanguageQueryResult} */ - values(): object; + inspect(): LanguageQueryResult; + #private; + } + /** + * A container for a language code, tag, and description. + */ + export class LanguageDescription { /** - * Returns an iterator for all cache entries. - * @return {object} + * `LanguageDescription` class constructor. + * @param {string} code + * @param {string} tag + * @param {string} description */ - entries(): object; + constructor(code: string, tag: string, description: string); /** - * Clears all entries in the cache. - * This does not replicate to shared caches. - * @return {undefined} + * The language code corresponding to the language + * @type {string} */ - clear(): undefined; + get code(): string; /** - * Enumerates entries in map calling `callback(value, key - * @param {function(object, string, Cache): any} callback + * The language tag corresponding to the language. + * @type {string} */ - forEach(callback: (arg0: object, arg1: string, arg2: Cache) => any): void; + get tag(): string; /** - * Broadcasts a replication to other shared caches. + * The language description corresponding to the language. + * @type {string} */ - replicate(): this; + get description(): string; /** - * Destroys the cache. This function stops the broadcast channel and removes - * and listeners + * JSON represenation of a `LanguageDescription` instance. + * @return {{ + * code: string, + * tag: string, + * description: string + * }} */ - destroy(): void; + toJSON(): { + code: string; + tag: string; + description: string; + }; /** + * Internal inspect function. * @ignore + * @return {LanguageDescription} */ - [Symbol.iterator](): any; + inspect(): LanguageDescription; #private; } - export default Cache; - export type CacheOptions = { - types?: object; - loader?: import("socket:commonjs/loader").Loader; - }; - export type StorageOptions = { - name: string; - }; -} - -declare module "socket:commonjs/loader" { - /** - * @typedef {{ - * extensions?: string[] | Set - * origin?: URL | string, - * statuses?: Cache - * cache?: { response?: Cache, status?: Cache }, - * headers?: Headers | Map | object | string[][] - * }} LoaderOptions - */ - /** - * @typedef {{ - * loader?: Loader, - * origin?: URL | string - * }} RequestOptions - */ - /** - * @typedef {{ - * headers?: Headers | object | array[], - * status?: number - * }} RequestStatusOptions - */ - /** - * @typedef {{ - * headers?: Headers | object - * }} RequestLoadOptions - */ + namespace _default { + export { codes }; + export { describe }; + export { lookup }; + export { names }; + export { tags }; + } + export default _default; + import Enumeration from "socket:enumeration"; +} + +declare module "socket:i18n" { + /** + * Get messages for `locale` pattern. This function could return many results + * for various locales given a `locale` pattern. such as `fr`, which could + * return results for `fr`, `fr-FR`, `fr-BE`, etc. + * @ignore + * @param {string} locale + * @return {object[]} + */ + export function getMessagesForLocale(locale: string): object[]; + /** + * Returns user preferred ISO 639 language codes or RFC 5646 language tags. + * @return {string[]} + */ + export function getAcceptLanguages(): string[]; + /** + * Returns the current user ISO 639 language code or RFC 5646 language tag. + * @return {?string} + */ + export function getUILanguage(): string | null; + /** + * Gets a localized message string for the specified message name. + * @param {string} messageName + * @param {object|string[]=} [substitutions = []] + * @param {object=} [options] + * @param {string=} [options.locale = null] + * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + * @see {@link https://www.ibm.com/docs/en/rbd/9.5.1?topic=syslib-getmessage} + * @return {?string} + */ + export function getMessage(messageName: string, substitutions?: (object | string[]) | undefined, options?: object | undefined): string | null; + /** + * Gets a localized message description string for the specified message name. + * @param {string} messageName + * @param {object=} [options] + * @param {string=} [options.locale = null] + * @return {?string} + */ + export function getMessageDescription(messageName: string, options?: object | undefined): string | null; + /** + * A cache of loaded locale messages. + * @type {Map} + */ + export const cache: Map; + /** + * Default location of i18n locale messages + * @type {string} + */ + export const DEFAULT_LOCALES_LOCATION: string; + /** + * An enumeration of supported ISO 639 language codes or RFC 5646 language tags. + * @type {Enumeration} + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/LanguageCode} + * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + */ + export const LanguageCode: Enumeration; + namespace _default { + export { LanguageCode }; + export { getAcceptLanguages }; + export { getMessage }; + export { getUILanguage }; + } + export default _default; + import Enumeration from "socket:enumeration"; +} + +declare module "socket:stream-relay/packets" { + /** + * The magic bytes prefixing every packet. They are the + * 2nd, 3rd, 5th, and 7th, prime numbers. + * @type {number[]} + */ + export const MAGIC_BYTES_PREFIX: number[]; + /** + * The version of the protocol. + */ + export const VERSION: 6; + /** + * The size in bytes of the prefix magic bytes. + */ + export const MAGIC_BYTES: 4; + /** + * The maximum size of the user message. + */ + export const MESSAGE_BYTES: 1024; + /** + * The cache TTL in milliseconds. + */ + export const CACHE_TTL: number; + export namespace PACKET_SPEC { + namespace type { + let bytes: number; + let encoding: string; + } + namespace version { + let bytes_1: number; + export { bytes_1 as bytes }; + let encoding_1: string; + export { encoding_1 as encoding }; + export { VERSION as default }; + } + namespace clock { + let bytes_2: number; + export { bytes_2 as bytes }; + let encoding_2: string; + export { encoding_2 as encoding }; + let _default: number; + export { _default as default }; + } + namespace hops { + let bytes_3: number; + export { bytes_3 as bytes }; + let encoding_3: string; + export { encoding_3 as encoding }; + let _default_1: number; + export { _default_1 as default }; + } + namespace index { + let bytes_4: number; + export { bytes_4 as bytes }; + let encoding_4: string; + export { encoding_4 as encoding }; + let _default_2: number; + export { _default_2 as default }; + export let signed: boolean; + } + namespace ttl { + let bytes_5: number; + export { bytes_5 as bytes }; + let encoding_5: string; + export { encoding_5 as encoding }; + export { CACHE_TTL as default }; + } + namespace clusterId { + let bytes_6: number; + export { bytes_6 as bytes }; + let encoding_6: string; + export { encoding_6 as encoding }; + let _default_3: number[]; + export { _default_3 as default }; + } + namespace subclusterId { + let bytes_7: number; + export { bytes_7 as bytes }; + let encoding_7: string; + export { encoding_7 as encoding }; + let _default_4: number[]; + export { _default_4 as default }; + } + namespace previousId { + let bytes_8: number; + export { bytes_8 as bytes }; + let encoding_8: string; + export { encoding_8 as encoding }; + let _default_5: number[]; + export { _default_5 as default }; + } + namespace packetId { + let bytes_9: number; + export { bytes_9 as bytes }; + let encoding_9: string; + export { encoding_9 as encoding }; + let _default_6: number[]; + export { _default_6 as default }; + } + namespace nextId { + let bytes_10: number; + export { bytes_10 as bytes }; + let encoding_10: string; + export { encoding_10 as encoding }; + let _default_7: number[]; + export { _default_7 as default }; + } + namespace usr1 { + let bytes_11: number; + export { bytes_11 as bytes }; + let _default_8: number[]; + export { _default_8 as default }; + } + namespace usr2 { + let bytes_12: number; + export { bytes_12 as bytes }; + let _default_9: number[]; + export { _default_9 as default }; + } + namespace usr3 { + let bytes_13: number; + export { bytes_13 as bytes }; + let _default_10: number[]; + export { _default_10 as default }; + } + namespace usr4 { + let bytes_14: number; + export { bytes_14 as bytes }; + let _default_11: number[]; + export { _default_11 as default }; + } + namespace message { + let bytes_15: number; + export { bytes_15 as bytes }; + let _default_12: number[]; + export { _default_12 as default }; + } + namespace sig { + let bytes_16: number; + export { bytes_16 as bytes }; + let _default_13: number[]; + export { _default_13 as default }; + } + } /** - * @typedef {{ - * request?: Request, - * headers?: Headers, - * status?: number, - * buffer?: ArrayBuffer, - * text?: string - * }} ResponseOptions + * The size in bytes of the total packet frame and message. */ + export const PACKET_BYTES: number; /** - * A container for the status of a CommonJS resource. A `RequestStatus` object - * represents meta data for a `Request` that comes from a preflight - * HTTP HEAD request. + * The maximum distance that a packet can be replicated. */ - export class RequestStatus { - /** - * Creates a `RequestStatus` from JSON input. - * @param {object} json - * @return {RequestStatus} - */ - static from(json: object, options: any): RequestStatus; - /** - * `RequestStatus` class constructor. - * @param {Request} request - * @param {RequestStatusOptions} [options] - */ - constructor(request: Request, options?: RequestStatusOptions); - set request(request: Request); - /** - * The `Request` object associated with this `RequestStatus` object. - * @type {Request} - */ - get request(): Request; - /** - * The unique ID of this `RequestStatus`, which is the absolute URL as a string. - * @type {string} - */ - get id(): string; - /** - * The origin for this `RequestStatus` object. - * @type {string} - */ - get origin(): string; - /** - * A HTTP status code for this `RequestStatus` object. - * @type {number|undefined} - */ - get status(): number; - /** - * An alias for `status`. - * @type {number|undefined} - */ - get value(): number; - /** - * @ignore - */ - get valueOf(): number; - /** - * The HTTP headers for this `RequestStatus` object. - * @type {Headers} - */ - get headers(): Headers; - /** - * The resource location for this `RequestStatus` object. This value is - * determined from the 'Content-Location' header, if available, otherwise - * it is derived from the request URL pathname (including the query string). - * @type {string} - */ - get location(): string; - /** - * `true` if the response status is considered OK, otherwise `false`. - * @type {boolean} - */ - get ok(): boolean; - /** - * Loads the internal state for this `RequestStatus` object. - * @param {RequestLoadOptions|boolean} [options] - * @return {RequestStatus} - */ - load(options?: RequestLoadOptions | boolean): RequestStatus; - /** - * Converts this `RequestStatus` to JSON. - * @ignore - * @return {{ - * id: string, - * origin: string | null, - * status: number, - * headers: Array - * request: object | null | undefined - * }} - */ - toJSON(includeRequest?: boolean): { - id: string; - origin: string | null; - status: number; - headers: Array; - request: object | null | undefined; + export const MAX_HOPS: 16; + export function validatePacket(o: any, constraints: { + [key: string]: { + required: boolean; + type: string; }; - #private; - } + }): void; /** - * A container for a synchronous CommonJS request to local resource or - * over the network. + * Computes a SHA-256 hash of input returning a hex encoded string. + * @type {function(string|Buffer|Uint8Array): Promise} */ - export class Request { - /** - * Creates a `Request` instance from JSON input - * @param {object} json - * @param {RequestOptions=} [options] - * @return {Request} - */ - static from(json: object, options?: RequestOptions | undefined): Request; - /** - * `Request` class constructor. - * @param {URL|string} url - * @param {URL|string=} [origin] - * @param {RequestOptions=} [options] - */ - constructor(url: URL | string, origin?: (URL | string) | undefined, options?: RequestOptions | undefined); - /** - * The unique ID of this `Request`, which is the absolute URL as a string. - * @type {string} - */ - get id(): string; + export const sha256: (arg0: string | Buffer | Uint8Array) => Promise; + export function decode(buf: Buffer): Packet; + export function getTypeFromBytes(buf: any): any; + export class Packet { + static ttl: number; + static maxLength: number; /** - * The absolute `URL` of this `Request` object. - * @type {URL} + * Returns an empty `Packet` instance. + * @return {Packet} */ - get url(): URL; + static empty(): Packet; /** - * The origin for this `Request`. - * @type {string} + * @param {Packet|object} packet + * @return {Packet} */ - get origin(): string; + static from(packet: Packet | object): Packet; /** - * The `Loader` for this `Request` object. - * @type {Loader?} + * Determines if input is a packet. + * @param {Buffer|Uint8Array|number[]|object|Packet} packet + * @return {boolean} */ - get loader(): Loader; + static isPacket(packet: Buffer | Uint8Array | number[] | object | Packet): boolean; /** - * The `RequestStatus` for this `Request` - * @type {RequestStatus} - */ - get status(): RequestStatus; + */ + static encode(p: any): Promise; + static decode(buf: any): Packet; /** - * Loads the CommonJS source file, optionally checking the `Loader` cache - * first, unless ignored when `options.cache` is `false`. - * @param {RequestLoadOptions=} [options] - * @return {Response} + * `Packet` class constructor. + * @param {Packet|object?} options */ - load(options?: RequestLoadOptions | undefined): Response; + constructor(options?: Packet | (object | null)); /** - * Converts this `Request` to JSON. - * @ignore - * @return {{ - * url: string, - * status: object | undefined - * }} + * @param {Packet} packet + * @return {Packet} */ - toJSON(includeStatus?: boolean): { - url: string; - status: object | undefined; - }; - #private; + copy(): Packet; + timestamp: any; + isComposed: any; + isReconciled: any; + meta: any; + } + export class PacketPing extends Packet { + static type: number; + constructor({ message, clusterId, subclusterId }: { + message: any; + clusterId: any; + subclusterId: any; + }); + } + export class PacketPong extends Packet { + static type: number; + constructor({ message, clusterId, subclusterId }: { + message: any; + clusterId: any; + subclusterId: any; + }); + } + export class PacketIntro extends Packet { + static type: number; + constructor({ clock, hops, clusterId, subclusterId, usr1, message }: { + clock: any; + hops: any; + clusterId: any; + subclusterId: any; + usr1: any; + message: any; + }); + } + export class PacketJoin extends Packet { + static type: number; + constructor({ clock, hops, clusterId, subclusterId, message }: { + clock: any; + hops: any; + clusterId: any; + subclusterId: any; + message: any; + }); + } + export class PacketPublish extends Packet { + static type: number; + constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }: { + message: any; + sig: any; + packetId: any; + clusterId: any; + subclusterId: any; + nextId: any; + clock: any; + hops: any; + usr1: any; + usr2: any; + ttl: any; + previousId: any; + }); + } + export class PacketStream extends Packet { + static type: number; + constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }: { + message: any; + sig: any; + packetId: any; + clusterId: any; + subclusterId: any; + nextId: any; + clock: any; + ttl: any; + usr1: any; + usr2: any; + usr3: any; + usr4: any; + previousId: any; + }); + } + export class PacketSync extends Packet { + static type: number; + constructor({ packetId, message }: { + packetId: any; + message?: any; + }); + } + export class PacketQuery extends Packet { + static type: number; + constructor({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }: { + packetId: any; + previousId: any; + subclusterId: any; + usr1: any; + usr2: any; + usr3: any; + usr4: any; + message?: {}; + }); } + export default Packet; + import { Buffer } from "socket:buffer"; +} + +declare module "socket:stream-relay/encryption" { /** - * A container for a synchronous CommonJS request response for a local resource - * or over the network. + * Class for handling encryption and key management. */ - export class Response { + export class Encryption { /** - * Creates a `Response` from JSON input - * @param {obejct} json - * @param {ResponseOptions=} [options] - * @return {Response} + * Creates a shared key based on the provided seed or generates a random one. + * @param {Uint8Array|string} seed - Seed for key generation. + * @returns {Promise} - Shared key. */ - static from(json: obejct, options?: ResponseOptions | undefined): Response; + static createSharedKey(seed: Uint8Array | string): Promise; /** - * `Response` class constructor. - * @param {Request|ResponseOptions} request - * @param {ResponseOptions=} [options] + * Creates a key pair for signing and verification. + * @param {Uint8Array|string} seed - Seed for key generation. + * @returns {Promise<{ publicKey: Uint8Array, privateKey: Uint8Array }>} - Key pair. */ - constructor(request: Request | ResponseOptions, options?: ResponseOptions | undefined); + static createKeyPair(seed: Uint8Array | string): Promise<{ + publicKey: Uint8Array; + privateKey: Uint8Array; + }>; /** - * The unique ID of this `Response`, which is the absolute - * URL of the request as a string. - * @type {string} + * Creates an ID using SHA-256 hash. + * @param {string} str - String to hash. + * @returns {Promise} - SHA-256 hash. */ - get id(): string; + static createId(str: string): Promise; /** - * The `Request` object associated with this `Response` object. - * @type {Request} + * Creates a cluster ID using SHA-256 hash with specified output size. + * @param {string} str - String to hash. + * @returns {Promise} - SHA-256 hash with specified output size. */ - get request(): Request; + static createClusterId(str: string): Promise; /** - * The response headers from the associated request. - * @type {Headers} + * Signs a message using the given secret key. + * @param {Buffer} b - The message to sign. + * @param {Uint8Array} sk - The secret key to use. + * @returns {Uint8Array} - Signature. */ - get headers(): Headers; + static sign(b: Buffer, sk: Uint8Array): Uint8Array; /** - * The `Loader` associated with this `Response` object. - * @type {Loader?} + * Verifies the signature of a message using the given public key. + * @param {Buffer} b - The message to verify. + * @param {Uint8Array} sig - The signature to check. + * @param {Uint8Array} pk - The public key to use. + * @returns {number} - Returns non-zero if the buffer could not be verified. */ - get loader(): Loader; + static verify(b: Buffer, sig: Uint8Array, pk: Uint8Array): number; /** - * The `Response` status code from the associated `Request` object. - * @type {number} + * Mapping of public keys to key objects. + * @type {Object.} */ - get status(): number; + keys: { + [x: string]: { + publicKey: Uint8Array; + privateKey: Uint8Array; + ts: number; + }; + }; /** - * The `Response` string from the associated `Request` - * @type {string} + * Adds a key pair to the keys mapping. + * @param {Uint8Array|string} publicKey - Public key. + * @param {Uint8Array} privateKey - Private key. */ - get text(): string; + add(publicKey: Uint8Array | string, privateKey: Uint8Array): void; /** - * The `Response` array buffer from the associated `Request` - * @type {ArrayBuffer?} + * Removes a key from the keys mapping. + * @param {Uint8Array|string} publicKey - Public key. */ - get buffer(): ArrayBuffer; + remove(publicKey: Uint8Array | string): void; /** - * `true` if the response is considered OK, otherwise `false`. - * @type {boolean} + * Checks if a key is in the keys mapping. + * @param {Uint8Array|string} to - Public key or Uint8Array. + * @returns {boolean} - True if the key is present, false otherwise. */ - get ok(): boolean; + has(to: Uint8Array | string): boolean; /** - * Converts this `Response` to JSON. - * @ignore - * @return {{ - * id: string, - * text: string, - * status: number, - * buffer: number[] | null, - * headers: Array - * }} + * Opens a sealed message using the specified key. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found. + */ + openUnsigned(message: Buffer, v: any | string): Buffer; + sealUnsigned(message: any, v: any): any; + /** + * Decrypts a sealed and signed message for a specific receiver. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. + */ + open(message: Buffer, v: any | string): Buffer; + /** + * Seals and signs a message for a specific receiver using their public key. + * + * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on + * a plaintext `message` for a `receiver` identity. This prevents repudiation + * attacks and doesn't rely on packet chain guarantees. + * + * let ct = Seal(sender | pt, receiver) + * let sig = Sign(ct, sk) + * let out = Seal(sig | ct) + * + * In an setup between Alice & Bob, this means: + * - Only Bob sees the plaintext + * - Alice wrote the plaintext and the ciphertext + * - Only Bob can see that Alice wrote the plaintext and ciphertext + * - Bob cannot forward the message without invalidating Alice's signature. + * - The outer encryption serves to prevent an attacker from replacing Alice's + * signature. As with _sign-encrypt-sign (SES), ESE is a variant of + * including the recipient's name inside the plaintext, which is then signed + * and encrypted Alice signs her plaintext along with her ciphertext, so as + * to protect herself from a laintext-substitution attack. At the same time, + * Alice's signed plaintext gives Bob non-repudiation. + * + * @see https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html + * + * @param {Buffer} message - The message to seal. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Sealed message. + * @throws {Error} - Throws ENOKEY if the key is not found. */ - toJSON(): { - id: string; - text: string; - status: number; - buffer: number[] | null; - headers: Array; - }; - #private; + seal(message: Buffer, v: any | string): Buffer; } + import Buffer from "socket:buffer"; +} + +declare module "socket:stream-relay/cache" { /** - * A container for loading CommonJS module sources + * @typedef {Packet} CacheEntry + * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver */ - export class Loader { - /** - * A request class used by `Loader` objects. - * @type {typeof Request} - */ - static Request: typeof Request; + /** + * Default cache sibling resolver that computes a delta between + * two entries clocks. + * @param {CacheEntry} a + * @param {CacheEntry} b + * @return {number} + */ + export function defaultSiblingResolver(a: CacheEntry, b: CacheEntry): number; + export function trim(buf: Buffer): any; + /** + * Default max size of a `Cache` instance. + */ + export const DEFAULT_MAX_SIZE: number; + /** + * Internal mapping of packet IDs to packet data used by `Cache`. + */ + export class CacheData extends Map { + constructor(); + constructor(entries?: readonly (readonly [any, any])[]); + constructor(); + constructor(iterable?: Iterable); + } + /** + * A class for storing a cache of packets by ID. This class includes a scheme + * for reconciling disjointed packet caches in a large distributed system. The + * following are key design characteristics. + * + * Space Efficiency: This scheme can be space-efficient because it summarizes + * the cache's contents in a compact binary format. By sharing these summaries, + * two computers can quickly determine whether their caches have common data or + * differences. + * + * Bandwidth Efficiency: Sharing summaries instead of the full data can save + * bandwidth. If the differences between the caches are small, sharing summaries + * allows for more efficient data synchronization. + * + * Time Efficiency: The time efficiency of this scheme depends on the size of + * the cache and the differences between the two caches. Generating summaries + * and comparing them can be faster than transferring and comparing the entire + * dataset, especially for large caches. + * + * Complexity: The scheme introduces some complexity due to the need to encode + * and decode summaries. In some cases, the overhead introduced by this + * complexity might outweigh the benefits, especially if the caches are + * relatively small. In this case, you should be using a query. + * + * Data Synchronization Needs: The efficiency also depends on the data + * synchronization needs. If the data needs to be synchronized in real-time, + * this scheme might not be suitable. It's more appropriate for cases where + * periodic or batch synchronization is acceptable. + * + * Scalability: The scheme's efficiency can vary depending on the scalability + * of the system. As the number of cache entries or computers involved + * increases, the complexity of generating and comparing summaries will stay + * bound to a maximum of 16Mb. + * + */ + export class Cache { + static HASH_SIZE_BYTES: number; /** - * A response class used by `Loader` objects. - * @type {typeof Request} - */ - static Response: typeof Request; + * The encodeSummary method provides a compact binary encoding of the output + * of summary() + * + * @param {Object} summary - the output of calling summary() + * @return {Buffer} + **/ + static encodeSummary(summary: any): Buffer; /** - * Resolves a given module URL to an absolute URL with an optional `origin`. - * @param {URL|string} url - * @param {URL|string} [origin] - * @return {string} - */ - static resolve(url: URL | string, origin?: URL | string): string; + * The decodeSummary method decodes the output of encodeSummary() + * + * @param {Buffer} bin - the output of calling encodeSummary() + * @return {Object} summary + **/ + static decodeSummary(bin: Buffer): any; /** - * Default extensions for a loader. - * @type {Set} + * `Cache` class constructor. + * @param {CacheData?} [data] */ - static defaultExtensions: Set; + constructor(data?: CacheData | null, siblingResolver?: typeof defaultSiblingResolver); + data: CacheData; + maxSize: number; + siblingResolver: typeof defaultSiblingResolver; /** - * `Loader` class constructor. - * @param {string|URL|LoaderOptions} origin - * @param {LoaderOptions=} [options] + * Readonly count of the number of cache entries. + * @type {number} */ - constructor(origin: string | URL | LoaderOptions, options?: LoaderOptions | undefined); + get size(): number; /** - * The internal caches for this `Loader` object. - * @type {{ response: Cache, status: Cache }} + * Readonly size of the cache in bytes. + * @type {number} */ - get cache(): { - response: Cache; - status: Cache; - }; + get bytes(): number; /** - * Headers used in too loader requests. - * @type {Headers} + * Inserts a `CacheEntry` value `v` into the cache at key `k`. + * @param {string} k + * @param {CacheEntry} v + * @return {boolean} */ - get headers(): Headers; + insert(k: string, v: CacheEntry): boolean; /** - * A set of supported `Loader` extensions. - * @type {Set} + * Gets a `CacheEntry` value at key `k`. + * @param {string} k + * @return {CacheEntry?} */ - get extensions(): Set; - set origin(origin: string); + get(k: string): CacheEntry | null; /** - * The origin of this `Loader` object. - * @type {string} + * @param {string} k + * @return {boolean} */ - get origin(): string; + delete(k: string): boolean; /** - * Loads a CommonJS module source file at `url` with an optional `origin`, which - * defaults to the application origin. - * @param {URL|string} url - * @param {URL|string|object} [origin] - * @param {RequestOptions=} [options] - * @return {Response} + * Predicate to determine if cache contains an entry at key `k`. + * @param {string} k + * @return {boolean} */ - load(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): Response; + has(k: string): boolean; /** - * Queries the status of a CommonJS module source file at `url` with an - * optional `origin`, which defaults to the application origin. - * @param {URL|string} url - * @param {URL|string|object} [origin] - * @param {RequestOptions=} [options] - * @return {RequestStatus} + * Composes an indexed packet into a new `Packet` + * @param {Packet} packet */ - status(url: URL | string, origin?: URL | string | object, options?: RequestOptions | undefined): RequestStatus; + compose(packet: Packet, source?: CacheData): Promise; + sha1(value: any, toHex: any): Promise; /** - * Resolves a given module URL to an absolute URL based on the loader origin. - * @param {URL|string} url - * @param {URL|string} [origin] - * @return {string} - */ - resolve(url: URL | string, origin?: URL | string): string; - #private; - } - export default Loader; - export type LoaderOptions = { - extensions?: string[] | Set; - origin?: URL | string; - statuses?: Cache; - cache?: { - response?: Cache; - status?: Cache; - }; - headers?: Headers | Map | object | string[][]; - }; - export type RequestOptions = { - loader?: Loader; - origin?: URL | string; - }; - export type RequestStatusOptions = { - headers?: Headers | object | any[][]; - status?: number; - }; - export type RequestLoadOptions = { - headers?: Headers | object; - }; - export type ResponseOptions = { - request?: Request; - headers?: Headers; - status?: number; - buffer?: ArrayBuffer; - text?: string; - }; - import { Headers } from "socket:ipc"; - import { Cache } from "socket:commonjs/cache"; + * + * The summarize method returns a terse yet comparable summary of the cache + * contents. + * + * Think of the cache as a trie of hex characters, the summary returns a + * checksum for the current level of the trie and for its 16 children. + * + * This is similar to a merkel tree as equal subtrees can easily be detected + * without the need for further recursion. When the subtree checksums are + * inequivalent then further negotiation at lower levels may be required, this + * process continues until the two trees become synchonized. + * + * When the prefix is empty, the summary will return an array of 16 checksums + * these checksums provide a way of comparing that subtree with other peers. + * + * When a variable-length hexidecimal prefix is provided, then only cache + * member hashes sharing this prefix will be considered. + * + * For each hex character provided in the prefix, the trie will decend by one + * level, each level divides the 2^128 address space by 16. For exmaple... + * + * ``` + * Level 0 1 2 + * ---------------- + * 2b00 + * aa0e ━┓ ━┓ + * aa1b ┃ ┃ + * aae3 ┃ ┃ ━┓ + * aaea ┃ ┃ ┃ + * aaeb ┃ ━┛ ━┛ + * ab00 ┃ ━┓ + * ab1e ┃ ┃ + * ab2a ┃ ┃ + * abef ┃ ┃ + * abf0 ━┛ ━┛ + * bff9 + * ``` + * + * @param {string} prefix - a string of lowercased hexidecimal characters + * @return {Object} + * + */ + summarize(prefix?: string, predicate?: (o: any) => boolean): any; + } + export default Cache; + export type CacheEntry = Packet; + export type CacheEntrySiblingResolver = (arg0: CacheEntry, arg1: CacheEntry) => number; + import { Buffer } from "socket:buffer"; + import { Packet } from "socket:stream-relay/packets"; } -declare module "socket:commonjs/package" { +declare module "socket:stream-relay/nat" { /** - * @typedef {{ - * manifest?: string, - * index?: string, - * description?: string, - * version?: string, - * license?: string, - * exports?: object, - * type?: 'commonjs' | 'module', - * info?: object, - * origin?: string, - * dependencies?: Dependencies | object | Map - * }} PackageOptions + * The NAT type is encoded using 5 bits: + * + * 0b00001 : the lsb indicates if endpoint dependence information is included + * 0b00010 : the second bit indicates the endpoint dependence value + * + * 0b00100 : the third bit indicates if firewall information is included + * 0b01000 : the fourth bit describes which requests can pass the firewall, only known IPs (0) or any IP (1) + * 0b10000 : the fifth bit describes which requests can pass the firewall, only known ports (0) or any port (1) */ /** - * @typedef {import('./loader.js').RequestOptions & { - * type?: 'commonjs' | 'module' - * prefix?: string - * }} PackageLoadOptions + * Every remote will see the same IP:PORT mapping for this peer. + * + * :3333 ┌──────┐ + * :1111 ┌───▶ │ R1 │ + * ┌──────┐ ┌───────┐ │ └──────┘ + * │ P1 ├───▶│ NAT ├──┤ + * └──────┘ └───────┘ │ ┌──────┐ + * └───▶ │ R2 │ + * :3333 └──────┘ */ + export const MAPPING_ENDPOINT_INDEPENDENT: 3; /** - * {import('./loader.js').RequestOptions & { - * load?: boolean, - * type?: 'commonjs' | 'module', - * browser?: boolean, - * children?: string[] - * extensions?: string[] | Set - * }} PackageResolveOptions + * Every remote will see a different IP:PORT mapping for this peer. + * + * :4444 ┌──────┐ + * :1111 ┌───▶ │ R1 │ + * ┌──────┐ ┌───────┐ │ └──────┘ + * │ P1 ├───▶│ NAT ├──┤ + * └──────┘ └───────┘ │ ┌──────┐ + * └───▶ │ R2 │ + * :5555 └──────┘ */ + export const MAPPING_ENDPOINT_DEPENDENT: 1; /** - * @typedef {{ - * organization: string | null, - * name: string, - * version: string | null, - * pathname: string, - * url: URL, - * isRelative: boolean, - * hasManifest: boolean - * }} ParsedPackageName + * The firewall allows the port mapping to be accessed by: + * - Any IP:PORT combination (FIREWALL_ALLOW_ANY) + * - Any PORT on a previously connected IP (FIREWALL_ALLOW_KNOWN_IP) + * - Only from previously connected IP:PORT combinations (FIREWALL_ALLOW_KNOWN_IP_AND_PORT) + */ + export const FIREWALL_ALLOW_ANY: 28; + export const FIREWALL_ALLOW_KNOWN_IP: 12; + export const FIREWALL_ALLOW_KNOWN_IP_AND_PORT: 4; /** - * The default package index file such as 'index.js' - * @type {string} + * The initial state of the nat is unknown and its value is 0 */ - export const DEFAULT_PACKAGE_INDEX: string; + export const UNKNOWN: 0; /** - * The default package manifest file name such as 'package.json' - * @type {string} + * Full-cone NAT, also known as one-to-one NAT + * + * Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. + * + * @summary its a packet party at this mapping and everyone's invited */ - export const DEFAULT_PACKAGE_MANIFEST_FILE_NAME: string; + export const UNRESTRICTED: number; /** - * The default package path prefix such as 'node_modules/' - * @type {string} + * (Address)-restricted-cone NAT + * + * An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only + * if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter. + * + * @summary The NAT will drop your packets unless a peer within its network has previously messaged you from *any* port. */ - export const DEFAULT_PACKAGE_PREFIX: string; + export const ADDR_RESTRICTED: number; /** - * The default package version, when one is not provided - * @type {string} + * Port-restricted cone NAT + * + * An external host (hAddr:hPort) can send packets to iAddr:iPort by sending + * packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to + * hAddr:hPort. + * + * @summary The NAT will drop your packets unless a peer within its network + * has previously messaged you from this *specific* port. */ - export const DEFAULT_PACKAGE_VERSION: string; + export const PORT_RESTRICTED: number; /** - * The default license for a package' - * @type {string} + * Symmetric NAT + * + * Only an external host that receives a packet from an internal host can send + * a packet back. + * + * @summary The NAT will only accept replies to a correspondence initialized + * by itself, the mapping it created is only valid for you. */ - export const DEFAULT_LICENSE: string; + export const ENDPOINT_RESTRICTED: number; + export function isEndpointDependenceDefined(nat: any): boolean; + export function isFirewallDefined(nat: any): boolean; + export function isValid(nat: any): boolean; + export function toString(n: any): "UNRESTRICTED" | "ADDR_RESTRICTED" | "PORT_RESTRICTED" | "ENDPOINT_RESTRICTED" | "UNKNOWN"; + export function toStringStrategy(n: any): "STRATEGY_DEFER" | "STRATEGY_DIRECT_CONNECT" | "STRATEGY_TRAVERSAL_OPEN" | "STRATEGY_TRAVERSAL_CONNECT" | "STRATEGY_PROXY" | "STRATEGY_UNKNOWN"; + export const STRATEGY_DEFER: 0; + export const STRATEGY_DIRECT_CONNECT: 1; + export const STRATEGY_TRAVERSAL_OPEN: 2; + export const STRATEGY_TRAVERSAL_CONNECT: 3; + export const STRATEGY_PROXY: 4; + export function connectionStrategy(a: any, b: any): 0 | 1 | 2 | 3 | 4; +} + +declare module "socket:stream-relay/index" { /** - * A container for a package name that includes a package organization identifier, - * its fully qualified name, or for relative package names, its pathname + * Computes rate limit predicate value for a port and address pair for a given + * threshold updating an input rates map. This method is accessed concurrently, + * the rates object makes operations atomic to avoid race conditions. + * + * @param {Map} rates + * @param {number} type + * @param {number} port + * @param {string} address + * @return {boolean} */ - export class Name { - /** - * Parses a package name input resolving the actual module name, including an - * organization name given. If a path includes a manifest file - * ('package.json'), then the directory containing that file is considered a - * valid package and it will be included in the returned value. If a relative - * path is given, then the path is returned if it is a valid pathname. This - * function returns `null` for bad input. - * @param {string|URL} input - * @param {{ origin?: string | URL, manifest?: string }=} [options] - * @return {ParsedPackageName?} - */ - static parse(input: string | URL, options?: { - origin?: string | URL; - manifest?: string; - }): ParsedPackageName | null; - /** - * Returns `true` if the given `input` can be parsed by `Name.parse` or given - * as input to the `Name` class constructor. - * @param {string|URL} input - * @param {{ origin?: string | URL, manifest?: string }=} [options] - * @return {boolean} - */ - static canParse(input: string | URL, options?: { - origin?: string | URL; - manifest?: string; - }): boolean; + export function rateLimit(rates: Map, type: number, port: number, address: string, subclusterIdQuota: any): boolean; + export function debug(pid: any, ...args: any[]): void; + /** + * Retry delay in milliseconds for ping. + * @type {number} + */ + export const PING_RETRY: number; + /** + * Probe wait timeout in milliseconds. + * @type {number} + */ + export const PROBE_WAIT: number; + /** + * Default keep alive timeout. + * @type {number} + */ + export const DEFAULT_KEEP_ALIVE: number; + /** + * Default rate limit threshold in milliseconds. + * @type {number} + */ + export const DEFAULT_RATE_LIMIT_THRESHOLD: number; + export function getRandomPort(ports: object, p: number | null): number; + /** + * A `RemotePeer` represents an initial, discovered, or connected remote peer. + * Typically, you will not need to create instances of this class directly. + */ + export class RemotePeer { /** - * Creates a new `Name` from input. - * @param {string|URL} input - * @param {{ origin?: string | URL, manifest?: string }=} [options] - * @return {Name} + * `RemotePeer` class constructor. + * @param {{ + * peerId?: string, + * address?: string, + * port?: number, + * natType?: number, + * clusters: object, + * reflectionId?: string, + * distance?: number, + * publicKey?: string, + * privateKey?: string, + * clock?: number, + * lastUpdate?: number, + * lastRequest?: number + * }} o */ - static from(input: string | URL, options?: { - origin?: string | URL; - manifest?: string; - } | undefined): Name; + constructor(o: { + peerId?: string; + address?: string; + port?: number; + natType?: number; + clusters: object; + reflectionId?: string; + distance?: number; + publicKey?: string; + privateKey?: string; + clock?: number; + lastUpdate?: number; + lastRequest?: number; + }, peer: any); + peerId: any; + address: any; + port: number; + natType: any; + clusters: {}; + pingId: any; + distance: number; + connected: boolean; + opening: number; + probed: number; + proxy: any; + clock: number; + uptime: number; + lastUpdate: number; + lastRequest: number; + localPeer: any; + write(sharedKey: any, args: any): Promise; + } + /** + * `Peer` class factory. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ + export class Peer { /** - * `Name` class constructor. - * @param {string|URL|NameOptions|Name} name - * @param {{ origin?: string | URL, manifest?: string }=} [options] - * @throws TypeError + * `Peer` class constructor. + * @param {object=} opts - Options + * @param {Buffer} opts.peerId - A 32 byte buffer (ie, `Encryption.createId()`). + * @param {Buffer} opts.clusterId - A 32 byte buffer (ie, `Encryption.createClusterId()`). + * @param {number=} opts.port - A port number. + * @param {number=} opts.probeInternalPort - An internal port number (semi-private for testing). + * @param {number=} opts.probeExternalPort - An external port number (semi-private for testing). + * @param {number=} opts.natType - A nat type. + * @param {string=} opts.address - An ipv4 address. + * @param {number=} opts.keepalive - The interval of the main loop. + * @param {function=} opts.siblingResolver - A function that can be used to determine canonical data in case two packets have concurrent clock values. + * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). */ - constructor(name: string | URL | NameOptions | Name, options?: { - origin?: string | URL; - manifest?: string; - } | undefined); + constructor(persistedState: {}, dgram: object); + port: any; + address: any; + natType: number; + nextNatType: number; + clusters: {}; + reflectionId: any; + reflectionTimeout: any; + reflectionStage: number; + reflectionRetry: number; + reflectionFirstResponder: any; + peerId: string; + isListening: boolean; + ctime: number; + lastUpdate: number; + lastSync: number; + closing: boolean; + clock: number; + unpublished: {}; + cache: any; + uptime: number; + maxHops: number; + bdpCache: number[]; + dgram: () => never; + onListening: any; + onDelete: any; + sendQueue: any[]; + firewall: any; + rates: Map; + streamBuffer: Map; + gate: Map; + returnRoutes: Map; + metrics: { + i: { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; + 8: number; + REJECTED: number; + }; + o: { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; + 8: number; + }; + }; + peers: any; + encryption: Encryption; + config: any; + _onError: (err: any) => any; + socket: any; + probeSocket: any; /** - * The id of this package name. - * @type {string} + * An implementation for clearning an interval that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore */ - get id(): string; + _clearInterval(tid: any): undefined; /** - * The actual package name. - * @type {string} + * An implementation for clearning a timeout that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore */ - get name(): string; + _clearTimeout(tid: any): undefined; /** - * An alias for 'name'. - * @type {string} + * An implementation of an internal timer that can be overridden by the test suite + * @return {Number} + * @ignore */ - get value(): string; + _setInterval(fn: any, t: any): number; /** - * The origin of the package, if available. - * This value may be `null`. - * @type {string?} + * An implementation of an timeout timer that can be overridden by the test suite + * @return {Number} + * @ignore */ - get origin(): string; + _setTimeout(fn: any, t: any): number; /** - * The package version if available. - * This value may be `null`. - * @type {string?} + * A method that encapsulates the listing procedure + * @return {undefined} + * @ignore */ - get version(): string; + _listen(): undefined; + init(cb: any): Promise; + onReady: any; + mainLoopTimer: number; /** - * The actual package pathname, if given in name string. - * This value is always a string defaulting to '.' if no path - * was given in name string. - * @type {string} + * Continuously evaluate the state of the peer and its network + * @return {undefined} + * @ignore */ - get pathname(): string; + _mainLoop(ts: any): undefined; /** - * The organization name. - * This value may be `null`. - * @type {string?} + * Enqueue packets to be sent to the network + * @param {Buffer} data - An encoded packet + * @param {number} port - The desination port of the remote host + * @param {string} address - The destination address of the remote host + * @param {Socket=this.socket} socket - The socket to send on + * @return {undefined} + * @ignore */ - get organization(): string; + send(data: Buffer, port: number, address: string, socket?: any): undefined; /** - * `true` if the package name was relative, otherwise `false`. - * @type {boolean} + * @private */ - get isRelative(): boolean; + private _scheduleSend; + sendTimeout: number; /** - * Converts this package name to a string. - * @ignore - * @return {string} + * @private */ - toString(): string; + private _dequeue; /** - * Converts this `Name` instance to JSON. + * Send any unpublished packets + * @return {undefined} * @ignore - * @return {object} */ - toJSON(): object; - #private; - } - /** - * A container for package dependencies that map a package name to a `Package` instance. - */ - export class Dependencies { - constructor(parent: any, options?: any); - get map(): Map; - get origin(): any; - add(name: any, info?: any): void; - get(name: any, options?: any): any; - entries(): IterableIterator<[any, any]>; - keys(): IterableIterator; - values(): IterableIterator; - load(options?: any): void; - [Symbol.iterator](): IterableIterator<[any, any]>; - #private; - } - /** - * A container for CommonJS module metadata, often in a `package.json` file. - */ - export class Package { + sendUnpublished(): undefined; /** - * A high level class for a package name. - * @type {typeof Name} + * Get the serializable state of the peer (can be passed to the constructor or create method) + * @return {undefined} */ - static Name: typeof Name; + getState(): undefined; + getInfo(): Promise<{ + address: any; + port: any; + clock: number; + uptime: number; + natType: number; + natName: string; + peerId: string; + }>; + cacheInsert(packet: any): Promise; + addIndexedPeer(info: any): Promise; + reconnect(): Promise; + disconnect(): Promise; + probeReflectionTimeout: any; + sealUnsigned(...args: any[]): Promise; + openUnsigned(...args: any[]): Promise; + seal(...args: any[]): Promise; + open(...args: any[]): Promise; + addEncryptionKey(...args: any[]): Promise; /** - * A high level container for package dependencies. - * @type {typeof Dependencies} + * Get a selection of known peers + * @return {Array} + * @ignore */ - static Dependencies: typeof Dependencies; + getPeers(packet: any, peers: any, ignorelist: any, filter?: (o: any) => any): Array; /** - * Creates and loads a package - * @param {string|URL|NameOptions|Name} name - * @param {PackageOptions & PackageLoadOptions=} [options] - * @return {Package} + * Send an eventually consistent packet to a selection of peers (fanout) + * @return {undefined} + * @ignore */ - static load(name: string | URL | NameOptions | Name, options?: (PackageOptions & PackageLoadOptions) | undefined): Package; + mcast(packet: any, ignorelist?: any[]): undefined; /** - * `Package` class constructor. - * @param {string|URL|NameOptions|Name} name - * @param {PackageOptions=} [options] + * The process of determining this peer's NAT behavior (firewall and dependentness) + * @return {undefined} + * @ignore */ - constructor(name: string | URL | NameOptions | Name, options?: PackageOptions | undefined); + requestReflection(): undefined; /** - * The unique ID of this `Package`, which is the absolute - * URL of the directory that contains its manifest file. - * @type {string} + * Ping another peer + * @return {PacketPing} + * @ignore */ - get id(): string; + ping(peer: any, withRetry: any, props: any, socket: any): PacketPing; /** - * The absolute URL to the package manifest file - * @type {string} + * Get a peer + * @return {RemotePeer} + * @ignore */ - get url(): string; + getPeer(id: any): RemotePeer; /** - * A reference to the package subpath imports and browser mappings. - * These values are typically used with its corresponding `Module` - * instance require resolvers. - * @type {object} + * This should be called at least once when an app starts to multicast + * this peer, and starts querying the network to discover peers. + * @param {object} keys - Created by `Encryption.createKeyPair()`. + * @param {object=} args - Options + * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. + * @return {RemotePeer} */ - get imports(): any; + join(sharedKey: any, args?: object | undefined): RemotePeer; /** - * A loader for this package, if available. This value may be `null`. - * @type {Loader} + * @param {Packet} T - The constructor to be used to create packets. + * @param {Any} message - The message to be split and packaged. + * @return {Array>} + * @ignore */ - get loader(): Loader; + _message2packets(T: Packet, message: Any, args: any): Array>; /** - * `true` if the package was actually "loaded", otherwise `false`. - * @type {boolean} + * Sends a packet into the network that will be replicated and buffered. + * Each peer that receives it will buffer it until TTL and then replicate + * it provided it has has not exceeded their maximum number of allowed hops. + * + * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. + * @param {object} args - The arguments to be applied. + * @param {Buffer} args.message - The message to be encrypted by keys and sent. + * @param {Packet=} args.packet - The previous packet in the packet chain. + * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. + * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. + * @return {Array} */ - get loaded(): boolean; + publish(sharedKey: any, args: { + message: Buffer; + packet?: Packet | undefined; + usr1: Buffer; + usr2: Buffer; + }): Array; /** - * The name of the package. - * @type {string} + * @return {undefined} */ - get name(): string; + sync(peer: any): undefined; + close(): void; /** - * The description of the package. - * @type {string} + * Deploy a query into the network + * @return {undefined} + * */ - get description(): string; + query(query: any): undefined; /** - * The organization of the package. This value may be `null`. - * @type {string?} + * + * This is a default implementation for deciding what to summarize + * from the cache when receiving a request to sync. that can be overridden + * */ - get organization(): string; + cachePredicate(packet: any): boolean; /** - * The license of the package. - * @type {string} + * A connection was made, add the peer to the local list of known + * peers and call the onConnection if it is defined by the user. + * + * @return {undefined} + * @ignore */ - get license(): string; + _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; + connections: Map; /** - * The version of the package. - * @type {string} + * Received a Sync Packet + * @return {undefined} + * @ignore */ - get version(): string; + _onSync(packet: any, port: any, address: any): undefined; /** - * The origin for this package. - * @type {string} + * Received a Query Packet + * + * a -> b -> c -> (d) -> c -> b -> a + * + * @return {undefined} + * @example + * + * ```js + * peer.onQuery = (packet) => { + * // + * // read a database or something + * // + * return { + * message: Buffer.from('hello'), + * publicKey: '', + * privateKey: '' + * } + * } + * ``` */ - get origin(): string; + _onQuery(packet: any, port: any, address: any): undefined; /** - * The exports mappings for the package - * @type {object} + * Received a Ping Packet + * @return {undefined} + * @ignore */ - get exports(): any; + _onPing(packet: any, port: any, address: any): undefined; /** - * The package type. - * @type {'commonjs'|'module'} + * Received a Pong Packet + * @return {undefined} + * @ignore */ - get type(): "module" | "commonjs"; + _onPong(packet: any, port: any, address: any): undefined; /** - * The raw package metadata object. - * @type {object?} + * Received an Intro Packet + * @return {undefined} + * @ignore */ - get info(): any; + _onIntro(packet: any, port: any, address: any, _: any, opts?: { + attempts: number; + }): undefined; + socketPool: any[]; /** - * @type {Dependencies} + * Received an Join Packet + * @return {undefined} + * @ignore */ - get dependencies(): Dependencies; + _onJoin(packet: any, port: any, address: any, data: any): undefined; /** - * An alias for `entry` - * @type {string?} + * Received an Publish Packet + * @return {undefined} + * @ignore */ - get main(): string; + _onPublish(packet: any, port: any, address: any, data: any): undefined; /** - * The entry to the package - * @type {string?} + * Received an Stream Packet + * @return {undefined} + * @ignore */ - get entry(): string; + _onStream(packet: any, port: any, address: any, data: any): undefined; /** - * Load the package information at an optional `origin` with - * optional request `options`. - * @param {PackageLoadOptions=} [options] - * @throws SyntaxError - * @return {boolean} + * Received any packet on the probe port to determine the firewall: + * are you port restricted, host restricted, or unrestricted. + * @return {undefined} + * @ignore */ - load(origin?: any, options?: PackageLoadOptions | undefined): boolean; + _onProbeMessage(data: any, { port, address }: { + port: any; + address: any; + }): undefined; /** - * Resolve a file's `pathname` within the package. - * @param {string|URL} pathname - * @param {PackageResolveOptions=} [options] - * @return {string} + * When a packet is received it is decoded, the packet contains the type + * of the message. Based on the message type it is routed to a function. + * like WebSockets, don't answer queries unless we know its another SRP peer. + * + * @param {Buffer|Uint8Array} data + * @param {{ port: number, address: string }} info */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; - #private; + _onMessage(data: Buffer | Uint8Array, { port, address }: { + port: number; + address: string; + }): Promise; } - export default Package; - export type PackageOptions = { - manifest?: string; - index?: string; - description?: string; - version?: string; - license?: string; - exports?: object; - type?: 'commonjs' | 'module'; - info?: object; - origin?: string; - dependencies?: Dependencies | object | Map; - }; - export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; - prefix?: string; - }; - /** - * /** - * The default package index file such as 'index.js' - */ - export type ParsedPackageName = { - organization: string | null; - name: string; - version: string | null; - pathname: string; - url: URL; - isRelative: boolean; - hasManifest: boolean; - }; - import { Loader } from "socket:commonjs/loader"; + export default Peer; + import { Packet } from "socket:stream-relay/packets"; + import { sha256 } from "socket:stream-relay/packets"; + import { Cache } from "socket:stream-relay/cache"; + import { Encryption } from "socket:stream-relay/encryption"; + import * as NAT from "socket:stream-relay/nat"; + import { Buffer } from "socket:buffer"; + import { PacketPing } from "socket:stream-relay/packets"; + import { PacketPublish } from "socket:stream-relay/packets"; + export { Packet, sha256, Cache, Encryption, NAT }; +} + +declare module "socket:node/index" { + export default network; + export const network: any; + import { Cache } from "socket:stream-relay/index"; + import { sha256 } from "socket:stream-relay/index"; + import { Encryption } from "socket:stream-relay/index"; + import { Packet } from "socket:stream-relay/index"; + import { NAT } from "socket:stream-relay/index"; + export { Cache, sha256, Encryption, Packet, NAT }; +} + +declare module "socket:index" { + import { network } from "socket:node/index"; + import { Cache } from "socket:node/index"; + import { sha256 } from "socket:node/index"; + import { Encryption } from "socket:node/index"; + import { Packet } from "socket:node/index"; + import { NAT } from "socket:node/index"; + export { network, Cache, sha256, Encryption, Packet, NAT }; } declare module "socket:stream-relay/proxy" { @@ -14339,7 +14341,6 @@ declare module "socket:commonjs/module" { import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import builtins from "socket:commonjs/builtins"; } declare module "socket:module" { @@ -15681,7 +15682,6 @@ declare module "socket:internal/pickers" { [keyof]; }>; }; - import { FileSystemHandle } from "socket:fs/web"; } declare module "socket:internal/primitives" { diff --git a/api/internal/iterator.js b/api/internal/iterator.js new file mode 100644 index 0000000000..e69de29bb2 From 06eb460a0d5b4ddadc3685c53dc938ad7de5a1c4 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 15 Apr 2024 15:10:04 +0200 Subject: [PATCH 0600/1178] refactor(cli): produce js boilerplate, fix(window): deprecation message drawsTransparentBackground --- src/cli/cli.cc | 3 +++ src/cli/templates.hh | 7 ++++++- src/window/apple.mm | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index bb0ee1af56..b8d2b1da78 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2289,6 +2289,9 @@ int main (const int argc, const char* argv[]) { writeFile(targetPath / "src" / "index.html", gHelloWorld); log("src/index.html created in " + targetPath.string()); + writeFile(targetPath / "src" / "index.js", gHelloWorldScript); + log("src/index.js created in " + targetPath.string()); + // copy icon.png fs::copy(trim(prefixFile("assets/icon.png")), targetPath / "src" / "icon.png", fs::copy_options::overwrite_existing); log("icon.png created in " + targetPath.string() + "/src"); diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 765234b7b1..762a6b7f51 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -219,7 +219,7 @@ constexpr auto gHelloWorld = R"HTML( overflow: hidden; } - < +

Hello, World.

@@ -227,6 +227,11 @@ constexpr auto gHelloWorld = R"HTML( )HTML"; +constexpr auto gHelloWorldScript = R"JavaScript( +import process from 'socket:process' +console.log(process.platform) +)JavaScript"; + // // macOS 'Info.plist' file // diff --git a/src/window/apple.mm b/src/window/apple.mm index 8add79dff6..9eb0bef33b 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -1117,7 +1117,8 @@ - (void) webView: (WKWebView*) webView } webview.layer.backgroundColor = [NSColor clearColor].CGColor; - [webview setValue: [NSNumber numberWithBool: YES] forKey: @"drawsTransparentBackground"]; + webview.layer.opaque = NO; + [webview setValue: [NSNumber numberWithBool: NO] forKey: @"drawsBackground"]; // [webview registerForDraggedTypes: // [NSArray arrayWithObject:NSPasteboardTypeFileURL]]; From c2d9672212849c917bd367054b80811612d89d1d Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 15 Apr 2024 15:14:34 +0200 Subject: [PATCH 0601/1178] fix(api): don't GC when pool is empty --- api/gc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/gc.js b/api/gc.js index deb6eed013..710e5f0c6a 100644 --- a/api/gc.js +++ b/api/gc.js @@ -235,7 +235,7 @@ export async function finalize (object, ...args) { * and still strongly held (retained) somewhere. */ export async function release () { - for (const weakRef of pool) { + for (const weakRef of pool ?? []) { await finalizationRegistryCallback(weakRef?.deref?.()) pool.delete(weakRef) } From be0a69d61e7730d31af6c789327714657714f2f1 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 15 Apr 2024 15:21:20 +0200 Subject: [PATCH 0602/1178] chore(cli): improve template --- src/cli/templates.hh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 762a6b7f51..e2d8d2a659 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -227,9 +227,11 @@ constexpr auto gHelloWorld = R"HTML( )HTML"; -constexpr auto gHelloWorldScript = R"JavaScript( +constexpr auto gHelloWorldScript = R"JavaScript(// +// Your JavaScript goes here! +// import process from 'socket:process' -console.log(process.platform) +console.log(`Hello, ${process.platform}!`) )JavaScript"; // From c9fe1dcfb7643b902ba89cac3ae25a682017406c Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 15 Apr 2024 15:38:40 +0200 Subject: [PATCH 0603/1178] fix(api): check value sent to finializer.handle --- api/gc.js | 2 +- api/timers/timer.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/gc.js b/api/gc.js index 710e5f0c6a..deb6eed013 100644 --- a/api/gc.js +++ b/api/gc.js @@ -235,7 +235,7 @@ export async function finalize (object, ...args) { * and still strongly held (retained) somewhere. */ export async function release () { - for (const weakRef of pool ?? []) { + for (const weakRef of pool) { await finalizationRegistryCallback(weakRef?.deref?.()) pool.delete(weakRef) } diff --git a/api/timers/timer.js b/api/timers/timer.js index 7e1c895b99..5174913008 100644 --- a/api/timers/timer.js +++ b/api/timers/timer.js @@ -74,7 +74,9 @@ export class Timer extends AsyncResource { args: [this.id, this.#destroy], handle (id, destroy) { destroy(id) - finalizer.handle(...finalizer.args) + if (finalizer.args) { + finalizer.handle(...finalizer.args) + } } } } From 2e92f58272f335b36d5e52a55fde66a36b4946d0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 09:37:41 -0400 Subject: [PATCH 0604/1178] chore(api/gc.js): fix typings --- api/gc.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/gc.js b/api/gc.js index deb6eed013..91c072a0a5 100644 --- a/api/gc.js +++ b/api/gc.js @@ -20,6 +20,9 @@ const dc = diagnostics.channels.group('gc', [ export const finalizers = new WeakMap() export const kFinalizer = Symbol.for('socket.runtime.gc.finalizer') export const finalizer = kFinalizer +/** + * @type {Set} + */ export const pool = new Set() /** @@ -70,7 +73,9 @@ async function finalizationRegistryCallback (finalizer) { cause: e }) + // @ts-ignore if (typeof Error.captureStackTrace === 'function') { + // @ts-ignore Error.captureStackTrace(err, finalizationRegistryCallback) } @@ -99,6 +104,9 @@ async function finalizationRegistryCallback (finalizer) { * garbage collected. */ export class Finalizer { + args = [] + handle = null + /** * Creates a `Finalizer` from input. */ @@ -236,6 +244,7 @@ export async function finalize (object, ...args) { */ export async function release () { for (const weakRef of pool) { + // @ts-ignore await finalizationRegistryCallback(weakRef?.deref?.()) pool.delete(weakRef) } From 51d3c246f75679d6cc72a14e28d8f8528bc18b9b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 09:38:56 -0400 Subject: [PATCH 0605/1178] fix(api/timers/timer.js): fix 'gc.finalizer' callback --- api/timers/timer.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/timers/timer.js b/api/timers/timer.js index 5174913008..12550cd688 100644 --- a/api/timers/timer.js +++ b/api/timers/timer.js @@ -4,7 +4,6 @@ import gc from '../gc.js' export class Timer extends AsyncResource { #id = 0 - #closed = false #create = null #destroy = null #destroyed = false @@ -56,7 +55,6 @@ export class Timer extends AsyncResource { close () { if (this.#id) { this.#destroy(this.#id) - this.#closed = true this.#id = 0 return true } @@ -69,13 +67,15 @@ export class Timer extends AsyncResource { } [gc.finalizer] () { - const finalizer = super[gc.finalizer] + const finalizer = super[gc.finalizer] ? super[gc.finalizer]() : null return { args: [this.id, this.#destroy], handle (id, destroy) { destroy(id) - if (finalizer.args) { + if (finalizer?.handle && finalizer?.args) { finalizer.handle(...finalizer.args) + } else if (finalizer?.handle) { + finalizer.handle() } } } @@ -112,7 +112,7 @@ export class Immediate extends Timer { constructor () { super( 'Immediate', - (callback, delay, ...args) => { + (callback, _, ...args) => { return platform.setImmediate( (...args) => { this.close() From 2392861bcecce8b6457f65e6a0b7ecbbe877dfd5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 10:33:54 -0400 Subject: [PATCH 0606/1178] fix(src/desktop/main.cc): check if target window is a 'nullptr' --- src/desktop/main.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index aa0ff2786e..cb04445d27 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1231,6 +1231,20 @@ MAIN { return; } + if (targetWindow == nullptr) { + JSON::Object json = JSON::Object::Entries { + { "err", "Target window is not available" } + }; + + currentWindow->resolvePromise( + message.seq, + ERROR_STATE, + json.str() + ); + + return; + } + targetWindow->navigate(seq, url); JSON::Object json = JSON::Object::Entries { From 274ac76b19d6b8ccab7a82b0b7303f312ab88a91 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 10:34:09 -0400 Subject: [PATCH 0607/1178] chore(api): generate types --- api/index.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/index.d.ts b/api/index.d.ts index b4f1bfbd41..9836b764bd 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -968,7 +968,10 @@ declare module "socket:gc" { export const finalizers: WeakMap; export const kFinalizer: unique symbol; export const finalizer: symbol; - export const pool: Set; + /** + * @type {Set} + */ + export const pool: Set>; /** * Static registry for objects to clean up underlying resources when they * are gc'd by the environment. There is no guarantee that the `finalizer()` From b16effb43937f86c4e6f8c8fe81687efb0b2427d Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 14:57:14 -0400 Subject: [PATCH 0608/1178] fix(api/service-worker/events.js): fix how headers are built in 'FetchEvent' --- api/service-worker/events.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 9f6c2982d2..9545b75fa6 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -242,8 +242,9 @@ export class FetchEvent extends ExtendableEvent { if (response.type === 'error') { const statusCode = 0 - const headers = Array.from(response.headers.entries()) - .concat(FetchEvent.defaultHeaders.entries()) + const headers = [] + .concat(Array.from(response.headers.entries())) + .concat(Array.from(FetchEvent.defaultHeaders.entries())) .map((entry) => entry.join(':')) .concat('Runtime-Response-Source:serviceworker') .concat('Access-Control-Allow-Credentials:true') From 675b6eb189cb2eca1c2e713c1e6478db93e6a7ad Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 14:57:49 -0400 Subject: [PATCH 0609/1178] refactor(src/core/service_worker_container.cc): detect 'runtime-preload-injection' meta tag --- src/core/service_worker_container.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index 21484acbde..dc4dd4827c 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -435,7 +435,7 @@ namespace SSC { (html.find("\n") + request.client.preload ); @@ -458,6 +458,12 @@ namespace SSC { {"protocol_handlers", join(protocolHandlers, " ")} }); + if (html.find("") != String::npos) { html = replace(html, "", String("" + preload)); } else if (html.find("") != String::npos) { From d571c199a77908593e9bebe9c526388819c1c523 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 15 Apr 2024 14:58:11 -0400 Subject: [PATCH 0610/1178] refactor(src/ipc/bridge.cc): detect 'runtime-preload-injection' meta tag --- src/ipc/bridge.cc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 57bc4af59d..e1ce41aa5b 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -3754,6 +3754,12 @@ static void registerSchemeHandler (Router *router) { {"protocol_handlers", join(protocolHandlers, " ")} }); + if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); } else if (html.find("") != String::npos) { @@ -4446,6 +4452,12 @@ static void registerSchemeHandler (Router *router) { {"protocol_handlers", join(protocolHandlers, " ")} }); + if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); } else if (html.find("") != String::npos) { @@ -4875,6 +4887,12 @@ static void registerSchemeHandler (Router *router) { {"protocol_handlers", join(protocolHandlers, " ")} }); + if (html.find("") != String::npos) { html = replace(html, "", String("" + script)); } else if (html.find("") != String::npos) { From f57b6cf59ac0e9fd43886426dc0006a06f6e2590 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 15 Apr 2024 21:33:01 +0200 Subject: [PATCH 0611/1178] feature(cli): allow simulator_uuid for [ios], fix(cli): set group on child pid to parent group, fix(api): fix buffers too account for value already being a buffer in serialize method --- api/internal/serialize.js | 4 + src/cli/cli.cc | 215 ++++++++++++++++++++------------------ src/cli/templates.hh | 4 +- src/process/unix.cc | 3 +- src/window/apple.mm | 2 +- 5 files changed, 122 insertions(+), 106 deletions(-) diff --git a/api/internal/serialize.js b/api/internal/serialize.js index 0d0a0f18cd..a99b1fcb9e 100644 --- a/api/internal/serialize.js +++ b/api/internal/serialize.js @@ -1,9 +1,13 @@ +import Buffer from '../buffer.js' + export default function serialize (value) { if (!value || typeof value !== 'object') { return value } return map(value, (value) => { + if (Buffer.isBuffer(value)) return value + if (typeof value[Symbol.serialize] === 'function') { return value[Symbol.serialize]() } diff --git a/src/cli/cli.cc b/src/cli/cli.cc index b8d2b1da78..5a25847460 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -1094,125 +1094,129 @@ int runApp (const Path& path, const String& args) { void runIOSSimulator (const Path& path, Map& settings) { #ifndef _WIN32 - checkIosSimulatorDeviceAvailability(settings["ios_simulator_device"]); - - String deviceType; - StringStream listDeviceTypesCommand; - listDeviceTypesCommand - << "xcrun" - << " simctl" - << " list devicetypes"; - - auto rListDeviceTypes = exec(listDeviceTypesCommand.str().c_str()); - if (rListDeviceTypes.exitCode != 0) { - log("failed to list device types using \"" + listDeviceTypesCommand.str() + "\""); - if (rListDeviceTypes.output.size() > 0) { - log(rListDeviceTypes.output); - } - exit(rListDeviceTypes.exitCode); - } - - std::regex reDeviceType(settings["ios_simulator_device"] + "\\s\\((com.apple.CoreSimulator.SimDeviceType.(?:.+))\\)"); - std::smatch match; + String uuid; + bool booted = false; - if (std::regex_search(rListDeviceTypes.output, match, reDeviceType)) { - deviceType = match.str(1); - log("simulator device type: " + deviceType); + if (settings.count("ios_simulator_uuid") > 0) { + uuid = settings["ios_simulator_uuid"]; } else { - auto rListDevices = exec("xcrun simctl list devicetypes | grep iPhone"); - log( - "failed to find device type: " + settings["ios_simulator_device"] + ". " - "Please provide correct device name for the \"ios_simulator_device\". " - "The list of available devices:\n" + rListDevices.output - ); - if (rListDevices.output.size() > 0) { - log(rListDevices.output); - } - exit(rListDevices.exitCode); - } + checkIosSimulatorDeviceAvailability(settings["ios_simulator_device"]); - StringStream listDevicesCommand; - listDevicesCommand - << "xcrun" - << " simctl" - << " list devices available"; - auto rListDevices = exec(listDevicesCommand.str().c_str()); - if (rListDevices.exitCode != 0) { - log("failed to list available devices using \"" + listDevicesCommand.str() + "\""); - if (rListDevices.output.size() > 0) { - log(rListDevices.output); - } - exit(rListDevices.exitCode); - } - - auto iosSimulatorDeviceSuffix = settings["ios_simulator_device"]; - std::replace(iosSimulatorDeviceSuffix.begin(), iosSimulatorDeviceSuffix.end(), ' ', '_'); - std::regex reSocketSDKDevice("SocketSimulator_" + iosSimulatorDeviceSuffix + "\\s\\((.+)\\)\\s\\((.+)\\)"); + String deviceType; + StringStream listDeviceTypesCommand; + listDeviceTypesCommand + << "xcrun" + << " simctl" + << " list devicetypes"; - String uuid; - bool booted = false; + auto rListDeviceTypes = exec(listDeviceTypesCommand.str().c_str()); + if (rListDeviceTypes.exitCode != 0) { + log("failed to list device types using \"" + listDeviceTypesCommand.str() + "\""); + if (rListDeviceTypes.output.size() > 0) { + log(rListDeviceTypes.output); + } + exit(rListDeviceTypes.exitCode); + } - if (std::regex_search(rListDevices.output, match, reSocketSDKDevice)) { - uuid = match.str(1); - booted = match.str(2).find("Booted") != String::npos; + std::regex reDeviceType(settings["ios_simulator_device"] + "\\s\\((com.apple.CoreSimulator.SimDeviceType.(?:.+))\\)"); + std::smatch match; - log("found Socket simulator VM for " + settings["ios_simulator_device"] + " with uuid: " + uuid); - if (booted) { - log("Socket simulator VM is booted"); + if (std::regex_search(rListDeviceTypes.output, match, reDeviceType)) { + deviceType = match.str(1); + log("simulator device type: " + deviceType); } else { - log("Socket simulator VM is not booted"); + auto rListDevices = exec("xcrun simctl list devicetypes"); + log( + "failed to find device type: " + settings["ios_simulator_device"] + ". " + "Please provide correct device name for the \"ios_simulator_device\". " + "The list of available devices:\n" + rListDevices.output + ); + if (rListDevices.output.size() > 0) { + log(rListDevices.output); + } + exit(rListDevices.exitCode); } - } else { - log("creating a new iOS simulator VM for " + settings["ios_simulator_device"]); - StringStream listRuntimesCommand; - listRuntimesCommand + StringStream listDevicesCommand; + listDevicesCommand << "xcrun" << " simctl" - << " list runtimes available"; - auto rListRuntimes = exec(listRuntimesCommand.str().c_str()); - if (rListRuntimes.exitCode != 0) { - log("failed to list available runtimes using \"" + listRuntimesCommand.str() + "\""); - if (rListRuntimes.output.size() > 0) { - log(rListRuntimes.output); - } - exit(rListRuntimes.exitCode); - } - auto const runtimes = split(rListRuntimes.output, '\n'); - String runtime; - // TODO: improve iOS version detection - for (auto it = runtimes.rbegin(); it != runtimes.rend(); ++it) { - if (it->find("iOS") != String::npos) { - runtime = trim(*it); - log("found runtime: " + runtime); - break; + << " list devices available"; + auto rListDevices = exec(listDevicesCommand.str().c_str()); + if (rListDevices.exitCode != 0) { + log("failed to list available devices using \"" + listDevicesCommand.str() + "\""); + if (rListDevices.output.size() > 0) { + log(rListDevices.output); } + exit(rListDevices.exitCode); } - std::regex reRuntime(R"(com.apple.CoreSimulator.SimRuntime.iOS(?:.*))"); - std::smatch matchRuntime; - String runtimeId; + auto iosSimulatorDeviceSuffix = settings["ios_simulator_device"]; + std::replace(iosSimulatorDeviceSuffix.begin(), iosSimulatorDeviceSuffix.end(), ' ', '_'); + std::regex reSocketSDKDevice("SocketSimulator_" + iosSimulatorDeviceSuffix + "\\s\\((.+)\\)\\s\\((.+)\\)"); - if (std::regex_search(runtime, matchRuntime, reRuntime)) { - runtimeId = matchRuntime.str(0); - } + if (std::regex_search(rListDevices.output, match, reSocketSDKDevice)) { + uuid = match.str(1); + booted = match.str(2).find("Booted") != String::npos; + + log("found Socket simulator VM for " + settings["ios_simulator_device"] + " with uuid: " + uuid); + if (booted) { + log("Socket simulator VM is booted"); + } else { + log("Socket simulator VM is not booted"); + } + } else { + log("creating a new iOS simulator VM for " + settings["ios_simulator_device"]); + + StringStream listRuntimesCommand; + listRuntimesCommand + << "xcrun" + << " simctl" + << " list runtimes available"; + auto rListRuntimes = exec(listRuntimesCommand.str().c_str()); + if (rListRuntimes.exitCode != 0) { + log("failed to list available runtimes using \"" + listRuntimesCommand.str() + "\""); + if (rListRuntimes.output.size() > 0) { + log(rListRuntimes.output); + } + exit(rListRuntimes.exitCode); + } + auto const runtimes = split(rListRuntimes.output, '\n'); + String runtime; + // TODO: improve iOS version detection + for (auto it = runtimes.rbegin(); it != runtimes.rend(); ++it) { + if (it->find("iOS") != String::npos) { + runtime = trim(*it); + log("found runtime: " + runtime); + break; + } + } + + std::regex reRuntime(R"(com.apple.CoreSimulator.SimRuntime.iOS(?:.*))"); + std::smatch matchRuntime; + String runtimeId; + + if (std::regex_search(runtime, matchRuntime, reRuntime)) { + runtimeId = matchRuntime.str(0); + } - StringStream createSimulatorCommand; - createSimulatorCommand - << "xcrun simctl" - << " create SocketSimulator_" + iosSimulatorDeviceSuffix - << " " << deviceType - << " " << runtimeId; + StringStream createSimulatorCommand; + createSimulatorCommand + << "xcrun simctl" + << " create SocketSimulator_" + iosSimulatorDeviceSuffix + << " " << deviceType + << " " << runtimeId; - auto rCreateSimulator = exec(createSimulatorCommand.str().c_str()); - if (rCreateSimulator.exitCode != 0) { - log("unable to create simulator VM"); - if (rCreateSimulator.output.size() > 0) { - log(rCreateSimulator.output); + auto rCreateSimulator = exec(createSimulatorCommand.str().c_str()); + if (rCreateSimulator.exitCode != 0) { + log("unable to create simulator VM"); + if (rCreateSimulator.output.size() > 0) { + log(rCreateSimulator.output); + } + exit(rCreateSimulator.exitCode); } - exit(rCreateSimulator.exitCode); + uuid = rCreateSimulator.output; } - uuid = rCreateSimulator.output; } if (!booted) { @@ -5085,7 +5089,9 @@ int main (const int argc, const char* argv[]) { if (flagBuildForIOS) { if (flagBuildForSimulator) { - checkIosSimulatorDeviceAvailability(settings["ios_simulator_device"]); + if (settings.count("ios_simulator_uuid") < 0) { + checkIosSimulatorDeviceAvailability(settings["ios_simulator_device"]); + } log("building for iOS Simulator"); } else { log("building for iOS"); @@ -5166,9 +5172,14 @@ int main (const int argc, const char* argv[]) { // building, signing, bundling, archiving, noterizing, and uploading. // StringStream archiveCommand; + String deviceIdentity = settings.count("ios_simulator_uuid") > 0 + ? "id=" + settings["ios_simulator_uuid"] + : "name=" + settings["ios_simulator_device"]; + String destination = flagBuildForSimulator - ? "platform=iOS Simulator,OS=latest,name=" + settings["ios_simulator_device"] + ? "platform=iOS Simulator,OS=latest," + deviceIdentity : "generic/platform=iOS"; + String deviceType; // TODO: should be "iPhone Distribution: "? diff --git a/src/cli/templates.hh b/src/cli/templates.hh index e2d8d2a659..20f983386d 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1067,7 +1067,7 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "{{ios_provisioning_specifier}}"; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; WARNING_CFLAGS = ( "$(inherited)", "-Wno-nullability-completeness", @@ -1113,7 +1113,7 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "{{ios_provisioning_specifier}}"; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; WARNING_CFLAGS = ( "$(inherited)", "-Wno-nullability-completeness", diff --git a/src/process/unix.cc b/src/process/unix.cc index 7550043f60..e3a86b93e7 100644 --- a/src/process/unix.cc +++ b/src/process/unix.cc @@ -121,11 +121,12 @@ Process::id_type Process::open(const std::function &function) noexcept { return pid; } - setpgid(pid, 0); closed = false; id = pid; if (pid > 0) { + setpgid(pid, getpgid(0)); + auto thread = std::thread([this] { int code = 0; waitpid(this->id, &code, 0); diff --git a/src/window/apple.mm b/src/window/apple.mm index 9eb0bef33b..a946cd9819 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -1315,7 +1315,7 @@ - (void) webView: (WKWebView*) webView } // - // We don't support hiddenInset because the same thing can be accomplished by specifying windowControlOffsets + // results in a hidden titlebar with inset/offset window controls // else if (opts.titlebarStyle == "hiddenInset") { style |= NSWindowStyleMaskFullSizeContentView; From 0c90244e73e6302f008f79c2797c9ee004930a0e Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 14:02:23 -0400 Subject: [PATCH 0612/1178] feat(api/internal/promise.js): support 'Promise.withResolvers' --- api/internal/promise.js | 47 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/api/internal/promise.js b/api/internal/promise.js index 5bef0f01a2..20b8d24e5e 100644 --- a/api/internal/promise.js +++ b/api/internal/promise.js @@ -13,10 +13,53 @@ export const NativePromisePrototype = { export const NativePromiseAll = globalThis.Promise.all.bind(globalThis.Promise) export const NativePromiseAny = globalThis.Promise.any.bind(globalThis.Promise) +/** + * @typedef {function(any): void} ResolveFunction + */ + +/** + * @typedef {function(Error|string|null): void} RejectFunction + */ + +/** + * @typedef {function(ResolveFunction, RejectFunction): void} ResolverFunction + */ + +/** + * @typedef {{ + * promise: Promise, + * resolve: ResolveFunction, + * reject: RejectFunction + * }} PromiseResolvers + */ + // @ts-ignore export class Promise extends NativePromise { - constructor (...args) { - super(...args) + /** + * Creates a new `Promise` with resolver functions. + * @see {https://github.com/tc39/proposal-promise-with-resolvers} + * @return {PromiseResolvers} + */ + static withResolvers () { + if (typeof super.withResolvers === 'function') { + return super.withResolvers() + } + + const resolvers = { promise: null, resolve: null, reject: null } + resolvers.promise = new Promise((resolve, reject) => { + resolvers.resolve = resolve + resolvers.reject = reject + }) + return resolvers + } + + /** + * `Promise` class constructor. + * @ignore + * @param {ResolverFunction} resolver + */ + constructor (resolver) { + super(resolver) // eslint-disable-next-line this[resourceSymbol] = new class Promise extends asyncHooks.CoreAsyncResource { constructor () { From 1697d1a86d0c78ca6afcd562a20b15c68633aecc Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 14:02:51 -0400 Subject: [PATCH 0613/1178] feat(src/cli/templates.hh): introduce '[webview.navigator.policies]' config --- src/cli/templates.hh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 20f983386d..1baf5679fd 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1767,6 +1767,9 @@ service_worker_reload_timeout = 500 ; $HOST_CONTAINER/directory-app-container/ = /mount/path/in/navigator ; $HOST_PROCESS_WORKING_DIRECTORY/directory-in-app-process-working-directory/ = /mount/path/in/navigator +; Specify allowed navigator navigation policy patterns +[webview.navigator.policies] +; allowed[] = "https://*.example.com/*" [permissions] From 699fd6b9f856bfd5227ba27165dae379ea9b7e9c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 14:03:19 -0400 Subject: [PATCH 0614/1178] feat(src/window): support '[webview.navigator.policies] allowed[] = ...' config --- src/window/apple.mm | 6 ++++++ src/window/linux.cc | 5 +++++ src/window/win.cc | 9 +++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index a946cd9819..7c54de724e 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -76,7 +76,13 @@ - (void) webView: (WKWebView*) webview } } + if (self.bridge->router.isNavigationAllowed(request)) { + decisionHandler(WKNavigationActionPolicyAllow); + return; + } + if (!request.starts_with("socket:") && !request.starts_with(devHost)) { + debug("Navigation was ignored for: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); return; } diff --git a/src/window/linux.cc b/src/window/linux.cc index 6f08444778..6cf28147bc 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -454,7 +454,12 @@ namespace SSC { return false; } + if (window->bridge->router.isNavigationAllowed(uri)) { + return true; + } + if (uri.find("socket:") != 0 && uri.find(devHost) != 0) { + debug("Navigation was ignored for: %s", uri.c_str()); webkit_policy_decision_ignore(decision); return false; } diff --git a/src/window/win.cc b/src/window/win.cc index e5febdd444..3a16a32d92 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -865,17 +865,22 @@ namespace SSC { PWSTR uri; e->get_Uri(&uri); SSC::String url(SSC::convertWStringToString(uri)); + Window* w = reinterpret_cast(GetWindowLongPtr((HWND)window, GWLP_USERDATA)); if (url.starts_with(userConfig["meta_application_protocol"])) { e->put_Cancel(true); - Window* w = reinterpret_cast(GetWindowLongPtr((HWND)window, GWLP_USERDATA)); if (w != nullptr) { SSC::JSON::Object json = SSC::JSON::Object::Entries {{ "url", url }}; w->bridge->router.emit("applicationurl", json.str()); } - } else if (url.find("socket:") != 0 && url.find("file://") != 0 && url.find(devHost) != 0) { + } else if ( + !w->bridge->router.isNavigationAllowed(url) && + url.find("socket:") != 0 && + url.find(devHost) != 0 + ) { + debug("Navigation was ignored for: %s", url.c_str()); e->put_Cancel(true); } From 6194705171649ad04e8c379681bc500e63b083ea Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 14:03:44 -0400 Subject: [PATCH 0615/1178] feat(src/ipc): introduce 'IPC::Router::isNavigationAllowed' predicate helper --- src/ipc/bridge.cc | 23 +++++++++++++++++++++++ src/ipc/ipc.hh | 2 ++ 2 files changed, 25 insertions(+) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index e1ce41aa5b..c899bf3f1d 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -6558,6 +6558,29 @@ namespace SSC::IPC { return false; } + + bool Router::isNavigationAllowed (const String& url) const { + auto userConfig = this->bridge->userConfig; + const auto allowed = SSC::split(SSC::trim(userConfig["webview_navigator_policies_allowed"]), ' '); + for (const auto& entry : allowed) { + SSC::String pattern = entry; + pattern = SSC::replace(pattern, "\\.", "\\."); + pattern = SSC::replace(pattern, "\\*", "(.*)"); + pattern = SSC::replace(pattern, "\\.\\.\\*", "(.*)"); + pattern = SSC::replace(pattern, "\\/", "\\/"); + + try { + std::regex regex(pattern); + std::smatch match; + + if (std::regex_match(url, match, regex, std::regex_constants::match_any)) { + return true; + } + } catch (...) {} + } + + return false; + } } #if defined(__APPLE__) diff --git a/src/ipc/ipc.hh b/src/ipc/ipc.hh index 01dc607da7..0f5bfc4301 100644 --- a/src/ipc/ipc.hh +++ b/src/ipc/ipc.hh @@ -328,6 +328,8 @@ namespace SSC::IPC { size_t size, ResultCallback callback ); + + bool isNavigationAllowed (const String& url) const; }; class Bridge { From 95160fb156e516e2257969447acf89b2316f8202 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 14:04:21 -0400 Subject: [PATCH 0616/1178] chore(api): generate types + docs --- api/CONFIG.md | 6 ++++++ api/index.d.ts | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/api/CONFIG.md b/api/CONFIG.md index ddc535d0b4..b18826d7f0 100644 --- a/api/CONFIG.md +++ b/api/CONFIG.md @@ -96,6 +96,12 @@ $HOST_HOME/directory-in-home-folder/ | | $HOST_CONTAINER/directory-app-container/ | | $HOST_PROCESS_WORKING_DIRECTORY/directory-in-app-process-working-directory/ | | +### `webview.navigator.policies` + +Key | Default Value | Description +:--- | :--- | :--- +allowed[] | | + ### `permissions` Key | Default Value | Description diff --git a/api/index.d.ts b/api/index.d.ts index 9836b764bd..3a33778cac 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -15549,8 +15549,35 @@ declare module "socket:internal/promise" { } export const NativePromiseAll: any; export const NativePromiseAny: any; + /** + * @typedef {function(any): void} ResolveFunction + */ + /** + * @typedef {function(Error|string|null): void} RejectFunction + */ + /** + * @typedef {function(ResolveFunction, RejectFunction): void} ResolverFunction + */ + /** + * @typedef {{ + * promise: Promise, + * resolve: ResolveFunction, + * reject: RejectFunction + * }} PromiseResolvers + */ export class Promise extends globalThis.Promise { - constructor(...args: any[]); + /** + * Creates a new `Promise` with resolver functions. + * @see {https://github.com/tc39/proposal-promise-with-resolvers} + * @return {PromiseResolvers} + */ + static withResolvers(): PromiseResolvers; + /** + * `Promise` class constructor. + * @ignore + * @param {ResolverFunction} resolver + */ + constructor(resolver: ResolverFunction); [resourceSymbol]: { "__#11@#type": any; "__#11@#destroyed": boolean; @@ -15571,6 +15598,14 @@ declare module "socket:internal/promise" { function any(iterable: any): any; } export default Promise; + export type ResolveFunction = (arg0: any) => void; + export type RejectFunction = (arg0: Error | string | null) => void; + export type ResolverFunction = (arg0: ResolveFunction, arg1: RejectFunction) => void; + export type PromiseResolvers = { + promise: Promise; + resolve: ResolveFunction; + reject: RejectFunction; + }; const resourceSymbol: unique symbol; import * as asyncHooks from "socket:internal/async/hooks"; } From d61962588643fae7dd64a36b18d1dac9fecc7e19 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 17:33:31 -0400 Subject: [PATCH 0617/1178] fix(api/commonjs/loader.js): do not allow empty extensions --- api/commonjs/loader.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 56b129bbd3..5357295e65 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -745,8 +745,10 @@ export class Loader { if (options?.extensions && typeof options.extensions === 'object') { if (Array.isArray(options.extensions) || options instanceof Set) { for (const value of options.extensions) { - const extension = !value.startsWith('.') ? `.${value}` : value - this.#extensions.add(extension.trim()) + const extension = (!value.startsWith('.') ? `.${value}` : value).trim() + if (extension) { + this.#extensions.add(extension.trim()) + } } } } From e3aa2b86a6b02e153d810b7a0e117201c95fe3aa Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 17:34:06 -0400 Subject: [PATCH 0618/1178] refactor(api/npm): handle regular file resolution --- api/npm/module.js | 15 +++++++++++++++ api/npm/service-worker.js | 24 +++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/api/npm/module.js b/api/npm/module.js index 8f1b71cd93..d32c502b60 100644 --- a/api/npm/module.js +++ b/api/npm/module.js @@ -1,5 +1,7 @@ import { DEFAULT_PACKAGE_PREFIX, Package } from '../commonjs/package.js' +import { Loader } from '../commonjs/loader.js' import location from '../location.js' +import path from '../path.js' /** * @typedef {{ @@ -58,6 +60,19 @@ export async function resolve (specifier, origin = null, options = null) { } } catch (err) { if (err?.code === 'MODULE_NOT_FOUND') { + const url = new URL(pathname, new URL(prefix + name.value + '/', origin)) + const loader = new Loader({ extensions: [path.extname(url.href)] }) + const status = loader.status(url) + // check for regular file + if (status.ok) { + return { + package: pkg, + origin: origin + prefix, + type: 'file', + url: url.href + } + } + return null } diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index 9c0b848f08..11680ad954 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -1,6 +1,8 @@ import { resolve } from './module.js' import process from '../process.js' import debug from '../service-worker/debug.js' +import mime from '../mime.js' +import path from '../path.js' import http from '../http.js' import util from '../util.js' @@ -79,12 +81,28 @@ export async function onRequest (request, env, ctx) { // not found if (!resolved) { - if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { - console.debug('not found: npm: %s', specifier) - } + debug(`${DEBUG_LABEL}: not found: %s`, specifier) return } + const extname = path.extname(resolved.url) + const types = extname ? await mime.lookup(extname.slice(1)) : [] + + if (types.length || resolved.type === 'file') { + let redirect = true + for (const type of types) { + if (type.mime === 'text/javascript' || type.mime.endsWith('json')) { + redirect = false + break + } + } + + if (redirect) { + debug(`${DEBUG_LABEL}: resolve: %s (file): %s`, specifier, resolved.url) + return Response.redirect(resolved.url, 301) + } + } + debug(`${DEBUG_LABEL}: resolve: %s (%s): %s`, specifier, resolved.type, resolved.url) if (resolved.type === 'module') { From ed2b91e5dc604832a693ce4707a082a1cf920fe0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 17:34:58 -0400 Subject: [PATCH 0619/1178] refactor(api/service-worker): improve following redirects, default responses, and disabled preload injections --- api/service-worker/events.js | 65 +++++++++++++++++++++++-------- api/service-worker/state.js | 75 +++++++++++++++++------------------- api/service-worker/worker.js | 10 ++++- 3 files changed, 93 insertions(+), 57 deletions(-) diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 9545b75fa6..74500d119f 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -14,6 +14,12 @@ export const FETCH_EVENT_TIMEOUT = ( 30000 ) +export const FETCH_EVENT_MAX_RESPONSE_REDIRECTS = ( + // TODO(@jwerle): document this + parseInt(application.config.webview_service_worker_fetch_event_max_response_redirects) || + 16 // this aligns with WebKit +) + /** * The `ExtendableEvent` interface extends the lifetime of the "install" and * "activate" events dispatched on the global scope as part of the service @@ -260,10 +266,7 @@ export class FetchEvent extends ExtendableEvent { id } - params['runtime-preload-injection'] = ( - response.headers.get('runtime-preload-injection') || - 'auto' - ) + params['runtime-preload-injection'] = 'disabled' const result = await ipc.request('serviceWorker.fetch.response', params) @@ -276,21 +279,42 @@ export class FetchEvent extends ExtendableEvent { } let arrayBuffer = null + let statusCode = response.status ?? 200 - const statusCode = response.status ?? 200 - + // just follow the redirect here now if (statusCode >= 300 && statusCode < 400 && response.headers.has('location')) { - const redirectURL = new URL(response.headers.get('location'), location.origin) - const redirectSource = ( - `` - ) - - arrayBuffer = textEncoder.encode(redirectSource).buffer + let previousResponse = response + let remainingRedirects = FETCH_EVENT_MAX_RESPONSE_REDIRECTS + + while (remainingRedirects-- > 0) { + const redirectLocation = previousResponse.headers.get('location') + + if (!redirectLocation) { + statusCode = 404 + break + } + + const url = new URL(redirectLocation, location.origin) + previousResponse = await fetch(url.href) + + if (previousResponse.status >= 200 && previousResponse.status < 300) { + arrayBuffer = await previousResponse.arrayBuffer() + break + } else if (previousResponse.status >= 300 && statusCode < 400) { + continue + } else { + statusCode = previousResponse.statusCode + arrayBuffer = await previousResponse.arrayBuffer() + break + } + } } else { arrayBuffer = await response.arrayBuffer() } - const headers = Array.from(response.headers.entries()) + const headers = [] + .concat(Array.from(response.headers.entries())) + .concat(Array.from(FetchEvent.defaultHeaders.entries())) .map((entry) => entry.join(':')) .concat('Runtime-Response-Source:serviceworker') .concat('Access-Control-Allow-Credentials:true') @@ -300,10 +324,21 @@ export class FetchEvent extends ExtendableEvent { .concat(`Content-Length:${arrayBuffer.byteLength}`) .join('\n') - const options = { statusCode, clientId, headers, id } + const params = { + statusCode, + clientId, + headers, + id + } + + params['runtime-preload-injection'] = ( + response.headers.get('runtime-preload-injection') || + 'auto' + ) + const result = await ipc.write( 'serviceWorker.fetch.response', - options, + params, new Uint8Array(arrayBuffer) ) diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 797354506e..d9f8692cc6 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -1,4 +1,3 @@ -/* global reportError */ import application from '../application.js' import debug from './debug.js' import ipc from '../ipc.js' @@ -78,46 +77,42 @@ const descriptors = { value: null } }) - } -} + }, -if (globalThis.isServiceWorkerScope) { - Object.assign(descriptors, { - id: { - configurable: false, - enumerable: false, - writable: true, - value: null - }, - - fetch: { - configurable: false, - enumerable: false, - writable: true, - value: null - }, - - install: { - configurable: false, - enumerable: false, - writable: true, - value: null - }, - - activate: { - configurable: false, - enumerable: false, - writable: true, - value: null - }, - - reportError: { - configurable: false, - enumerable: false, - writable: true, - value: reportError.bind(globalThis) - } - }) + id: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + fetch: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + install: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + activate: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + reportError: { + configurable: false, + enumerable: false, + writable: true, + value: globalThis.reportError.bind(globalThis) + } } export const state = Object.create(null, descriptors) diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 72f7535472..e160984cc8 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -278,7 +278,10 @@ async function onMessage (event) { debug(err) response = new Response(util.inspect(err), { statusText: err.message || STATUS_CODES[500], - status: 500 + status: 500, + headers: { + 'Runtime-Preload-Injection': 'disabled' + } }) } @@ -291,7 +294,10 @@ async function onMessage (event) { } else { deferred.resolve(new Response('Not Found', { statusText: STATUS_CODES[404], - status: 404 + status: 404, + headers: { + 'Runtime-Preload-Injection': 'disabled' + } })) } }) From a99e88e0b0ee8a9476904c9cc55e001e0e092064 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 17:39:30 -0400 Subject: [PATCH 0620/1178] refactor(src/ipc/bridge.cc) handle protocol handlers, 'socket:' scheme, and dev host in 'Router::isNavigationAllowed' --- src/ipc/bridge.cc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index c899bf3f1d..b915f27cf4 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -6560,8 +6560,27 @@ namespace SSC::IPC { } bool Router::isNavigationAllowed (const String& url) const { + static const auto devHost = SSC::getDevHost(); auto userConfig = this->bridge->userConfig; const auto allowed = SSC::split(SSC::trim(userConfig["webview_navigator_policies_allowed"]), ' '); + + for (const auto& entry : split(userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (url.starts_with(scheme + ":")) { + return true; + } + } + + for (const auto& entry : userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + if (url.starts_with(scheme + ":")) { + return true; + } + } + } + for (const auto& entry : allowed) { SSC::String pattern = entry; pattern = SSC::replace(pattern, "\\.", "\\."); @@ -6579,6 +6598,10 @@ namespace SSC::IPC { } catch (...) {} } + if (url.starts_with("socket:") || url.starts_with("npm:") || url.starts_with(devHost)) { + return true; + } + return false; } } From e6df5d53b1c4a5191f5d7be7248c44644fec58f9 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 17:40:35 -0400 Subject: [PATCH 0621/1178] refactor(src/window): use 'Router::isNavigationAllowed()' in navigation policy decisions --- src/window/apple.mm | 7 +------ src/window/linux.cc | 6 +----- src/window/win.cc | 8 ++------ 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 7c54de724e..a51a9ac459 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -76,12 +76,7 @@ - (void) webView: (WKWebView*) webview } } - if (self.bridge->router.isNavigationAllowed(request)) { - decisionHandler(WKNavigationActionPolicyAllow); - return; - } - - if (!request.starts_with("socket:") && !request.starts_with(devHost)) { + if (!self.bridge->router.isNavigationAllowed(request)) { debug("Navigation was ignored for: %s", request.c_str()); decisionHandler(WKNavigationActionPolicyCancel); return; diff --git a/src/window/linux.cc b/src/window/linux.cc index 6cf28147bc..58369136c5 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -454,11 +454,7 @@ namespace SSC { return false; } - if (window->bridge->router.isNavigationAllowed(uri)) { - return true; - } - - if (uri.find("socket:") != 0 && uri.find(devHost) != 0) { + if (!window->bridge->router.isNavigationAllowed(uri)) { debug("Navigation was ignored for: %s", uri.c_str()); webkit_policy_decision_ignore(decision); return false; diff --git a/src/window/win.cc b/src/window/win.cc index 3a16a32d92..11146a0250 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -749,7 +749,7 @@ namespace SSC { // UNREACHABLE - cannot continue } - const int MAX_ALLOWED_SCHEME_ORIGINS = 5; + const int MAX_ALLOWED_SCHEME_ORIGINS = 64; int allowedSchemeOriginsCount = 4; const WCHAR* allowedSchemeOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = { L"about://*", @@ -875,11 +875,7 @@ namespace SSC { }}; w->bridge->router.emit("applicationurl", json.str()); } - } else if ( - !w->bridge->router.isNavigationAllowed(url) && - url.find("socket:") != 0 && - url.find(devHost) != 0 - ) { + } else if (!w->bridge->router.isNavigationAllowed(url)) { debug("Navigation was ignored for: %s", url.c_str()); e->put_Cancel(true); } From 994af3c190568e9775859710edb07179398c5272 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 16 Apr 2024 17:41:34 -0400 Subject: [PATCH 0622/1178] chore(api): generate types --- api/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/index.d.ts b/api/index.d.ts index 3a33778cac..ce4a8235e1 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -10387,6 +10387,7 @@ declare module "socket:service-worker/context" { declare module "socket:service-worker/events" { export const textEncoder: TextEncoderStream; export const FETCH_EVENT_TIMEOUT: number; + export const FETCH_EVENT_MAX_RESPONSE_REDIRECTS: number; /** * The `ExtendableEvent` interface extends the lifetime of the "install" and * "activate" events dispatched on the global scope as part of the service From 58e0ca6b81e283d68b9f35dd4ba4adb48eec1d73 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 17 Apr 2024 10:39:32 -0400 Subject: [PATCH 0623/1178] fix(bin/install.sh): include libuv LICENSE --- bin/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/install.sh b/bin/install.sh index 67985936d2..b2b7398ffa 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -959,6 +959,7 @@ if [[ -n "$BUILD_ANDROID" ]]; then fi mkdir -p "$SOCKET_HOME"/uv/{src/unix,include} +cp -fr "$BUILD_DIR"/uv/LICENSE "$SOCKET_HOME"/uv/LICENSE cp -fr "$BUILD_DIR"/uv/src/*.{c,h} "$SOCKET_HOME"/uv/src cp -fr "$BUILD_DIR"/uv/src/unix/*.{c,h} "$SOCKET_HOME"/uv/src/unix die $? "not ok - could not copy headers" From 94c70789027b01d473641b4c0e4b5361896ab54b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 17 Apr 2024 11:40:53 -0400 Subject: [PATCH 0624/1178] refactor(src/window/win.cc): make protocol/scheme registration dynamic --- src/window/win.cc | 103 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/src/window/win.cc b/src/window/win.cc index 11146a0250..27c8a91cc2 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -4,11 +4,13 @@ #include #include #include -#include "window.hh" #include "WebView2.h" #include "WebView2EnvironmentOptions.h" +#include "window.hh" +#include "../core/types.hh" + #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "urlmon.lib") @@ -749,38 +751,87 @@ namespace SSC { // UNREACHABLE - cannot continue } - const int MAX_ALLOWED_SCHEME_ORIGINS = 64; - int allowedSchemeOriginsCount = 4; - const WCHAR* allowedSchemeOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = { - L"about://*", - L"https://*", - L"file://*", - L"socket://*" + static const int MAX_ALLOWED_SCHEME_ORIGINS = 64; + static const int MAX_CUSTOM_SCHEME_REGISTRATIONS = 64; + + struct SchemeRegistration { + String scheme; + }; + + ICoreWebView2CustomSchemeRegistration* registrations[MAX_CUSTOM_SCHEME_REGISTRATIONS] = {}; + Vector schemeRegistrations; + + schemeRegistrations.push_back({ "ipc" }); + schemeRegistrations.push_back({ "socket" }); + schemeRegistrations.push_back({ "node" }); + schemeRegistrations.push_back({ "npm" }); + + for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (app.core->protocolHandlers.registerHandler(scheme)) { + schemeRegistrations.push_back({ scheme }); + } + } + + for (const auto& entry : opts.userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + const auto data = entry.second; + if (app.core->protocolHandlers.registerHandler(scheme, { data })) { + schemeRegistrations.push_back({ scheme }); + } + } + } + + Set origins; + Set protocols = { + "about", + "https", + "socket", + "npm", + "node" }; static const auto devHost = SSC::getDevHost(); + const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {} + int allowedOriginsCount = 0; + int registrationsCount = 0; + if (devHost.starts_with("http:")) { - allowedSchemeOrigins[allowedSchemeOriginsCount++] = convertStringToWString(devHost).c_str(); + allowedOrigins[allowedOriginsCount] = convertStringToWString(devHost).c_str(); } - auto ipcSchemeRegistration = Microsoft::WRL::Make(L"ipc"); - ipcSchemeRegistration->put_HasAuthorityComponent(TRUE); - ipcSchemeRegistration->put_TreatAsSecure(TRUE); - ipcSchemeRegistration->SetAllowedOrigins(allowedSchemeOriginsCount, allowedSchemeOrigins); - - auto socketSchemeRegistration = Microsoft::WRL::Make(L"socket"); - socketSchemeRegistration->put_HasAuthorityComponent(TRUE); - socketSchemeRegistration->put_TreatAsSecure(TRUE); - socketSchemeRegistration->SetAllowedOrigins(allowedSchemeOriginsCount, allowedSchemeOrigins); - - // If someone can figure out how to allocate this so we can do it in a loop that'd be great, but even Ms is doing it like this: - // https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.1587.40 - ICoreWebView2CustomSchemeRegistration* registrations[2] = { - ipcSchemeRegistration.Get(), - socketSchemeRegistration.Get() - }; + for (const auto& schemeRegistration : schemeRegistrations) { + protocols.insert(schemeRegistration.scheme); + } + + for (const auto& protocol : protocols) { + if (origins.size() == MAX_ALLOWED_SCHEME_ORIGINS) { + break; + } + + const auto origin = protocol + "://*"; + origins.insert(origin); + allowedOrigins[allowedOriginsCount++] = convertStringToWString(origin).c_str(); + } + + for (const auto& schemeRegistration : schemeRegistrations) { + auto registration = Microsoft::WRL::Make( + convertStringToWString(schemeRegistration.scheme).c_str() + ); + + registration->put_HasAuthorityComponent(TRUE); + registration->put_TreatAsSecure(TRUE); + registration->SetAllowedOrigins(origins.size(), allowedOrigins); - options4->SetCustomSchemeRegistrations(2, static_cast(registrations)); + registrations[registrationsCount++] = registrations.Get(); + } + + options4->SetCustomSchemeRegistrations( + registrationsCount, + static_cast(registrations) + ); auto init = [&, opts]() -> HRESULT { return CreateCoreWebView2EnvironmentWithOptions( From 08ad83e77fe812e2d40bdc3789c0849d01cac16f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 17 Apr 2024 14:09:25 -0400 Subject: [PATCH 0625/1178] chore(bin/install.sh): upgrade 'WebView2' --- bin/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/install.sh b/bin/install.sh index b2b7398ffa..f29a45a841 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -353,7 +353,7 @@ function _get_web_view2() { echo "# Downloading Webview2" - curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/1.0.2365.46 --output "$tmp/webview2.zip" + curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/1.0.2420.47 --output "$tmp/webview2.zip" cd "$tmp" || exit 1 unzip -q "$tmp/webview2.zip" mkdir -p "$BUILD_DIR/include" From d586aa9f43990e186be861716f52f30db404bf8b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 17 Apr 2024 14:09:41 -0400 Subject: [PATCH 0626/1178] fix(src/ipc/bridge.cc): fix linux guard --- src/ipc/bridge.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index b915f27cf4..80bc9b4535 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -5976,7 +5976,7 @@ namespace SSC::IPC { initRouterTable(this); registerSchemeHandler(this); - #if SSC_PLATFORM_LINUX + #if SSC_PLATFORM_LINUX && !SSC_PLATFORM_ANDROID ProtocolHandlers::Mapping protocolHandlerMappings = { {"npm", ProtocolHandlers::Protocol { "npm" }} }; From f8db1eaf944fa91b5b2922d562793d85c7d7b0d5 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 17 Apr 2024 14:10:40 -0400 Subject: [PATCH 0627/1178] refactor(src/window/win.cc): retain custom scheme registrations --- src/window/win.cc | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/window/win.cc b/src/window/win.cc index 27c8a91cc2..3e1c12d116 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -743,7 +743,7 @@ namespace SSC { this->modulePath = fs::path(modulefile); auto options = Microsoft::WRL::Make(); - options->put_AdditionalBrowserArguments(L"--allow-file-access-from-files --enable-features=msWebView2EnableDraggableRegions"); + options->put_AdditionalBrowserArguments(L"--enable-features=msWebView2EnableDraggableRegions"); Microsoft::WRL::ComPtr options4; HRESULT oeResult = options.As(&options4); @@ -794,7 +794,7 @@ namespace SSC { }; static const auto devHost = SSC::getDevHost(); - const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {} + const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {}; int allowedOriginsCount = 0; int registrationsCount = 0; @@ -816,6 +816,7 @@ namespace SSC { allowedOrigins[allowedOriginsCount++] = convertStringToWString(origin).c_str(); } + Set> customSchemeRegistrations; for (const auto& schemeRegistration : schemeRegistrations) { auto registration = Microsoft::WRL::Make( convertStringToWString(schemeRegistration.scheme).c_str() @@ -825,7 +826,11 @@ namespace SSC { registration->put_TreatAsSecure(TRUE); registration->SetAllowedOrigins(origins.size(), allowedOrigins); - registrations[registrationsCount++] = registrations.Get(); + customSchemeRegistrations.insert(registration); + } + + for (const auto& registration : customSchemeRegistrations) { + registrations[registrationsCount++] = registration.Get(); } options4->SetCustomSchemeRegistrations( @@ -836,7 +841,7 @@ namespace SSC { auto init = [&, opts]() -> HRESULT { return CreateCoreWebView2EnvironmentWithOptions( EDGE_RUNTIME_DIRECTORY.size() > 0 ? EDGE_RUNTIME_DIRECTORY.c_str() : nullptr, - (path + L"/" + filename).c_str(), + (path + L"\\" + filename).c_str(), options.Get(), Microsoft::WRL::Callback( [&, opts](HRESULT result, ICoreWebView2Environment* env) -> HRESULT { @@ -940,8 +945,6 @@ namespace SSC { EventRegistrationToken tokenSchemaFilter; webview->AddWebResourceRequestedFilter(L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL); - webview->AddWebResourceRequestedFilter(L"socket:*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL); - webview->AddWebResourceRequestedFilter(L"socket:*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_XML_HTTP_REQUEST); ICoreWebView2_22* webview22 = nullptr; webview->QueryInterface(IID_PPV_ARGS(&webview22)); @@ -1397,17 +1400,6 @@ namespace SSC { .userScript = opts.userScript }); - webview->AddScriptToExecuteOnDocumentCreated( - // Note that this may not do anything as preload goes out of scope before event fires - // Consider using w->preloadJavascript, but apps work without this - SSC::convertStringToWString(this->bridge->preload).c_str(), - Microsoft::WRL::Callback( - [&](HRESULT error, PCWSTR id) -> HRESULT { - return S_OK; - } - ).Get() - ); - EventRegistrationToken tokenMessage; webview->add_WebMessageReceived( From 94800e866a2f6b4b34c91db7eadac7fb5a062f57 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Tue, 23 Apr 2024 18:37:29 +0200 Subject: [PATCH 0628/1178] refactor(api): tidy service worker --- api/npm/service-worker.js | 4 +- api/service-worker/index.html | 207 +--------------------------------- 2 files changed, 7 insertions(+), 204 deletions(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index 11680ad954..7e21c72292 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -53,9 +53,9 @@ export async function onRequest (request, env, ctx) { origins = Array.from(new Set(origins)) if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { - console.debug('resolving: npm:%s', specifier) + console.debug('resolving: npm:%s (%o)', specifier, origins) } - + while (origins.length && resolved === null) { const potentialOrigins = [] const origin = origins.shift() diff --git a/api/service-worker/index.html b/api/service-worker/index.html index 80b01585ba..828400c539 100644 --- a/api/service-worker/index.html +++ b/api/service-worker/index.html @@ -14,7 +14,6 @@ object-src 'none'; " > - Socket Runtime Service Worker Context - -
-

You are seeing this window because you are likely debugging a Service Worker

-

-    
- -
-

Socket Runtime ()

-
+

   
 

From c402b9f00c54073061a7beea103c02e3606ef862 Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Tue, 23 Apr 2024 12:43:46 -0400
Subject: [PATCH 0629/1178] fix(api/npm/module.js): preload package and ensure
 package type requested and loaded match

---
 api/npm/module.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/api/npm/module.js b/api/npm/module.js
index d32c502b60..82e5c47cee 100644
--- a/api/npm/module.js
+++ b/api/npm/module.js
@@ -49,9 +49,12 @@ export async function resolve (specifier, origin = null, options = null) {
   const pathname = name.pathname.replace(name.value, '.') || '.'
 
   try {
+    pkg.load()
     // will call `pkg.load()` internally
     // can throw `ModuleNotFoundError`
-    const url = pkg.resolve(pathname, { prefix, type })
+    const url = pkg.type === type
+      ? pkg.resolve(pathname, { prefix, type })
+      : pkg.resolve(pathname, { prefix })
     return {
       package: pkg,
       origin: pkg.origin,

From 6f51f60d5af6aa5468515d0fb0b45849c50a8b6a Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Tue, 23 Apr 2024 12:46:15 -0400
Subject: [PATCH 0630/1178] chore(api/npm/module.js): clean up

---
 api/npm/module.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/api/npm/module.js b/api/npm/module.js
index 82e5c47cee..8875e40eed 100644
--- a/api/npm/module.js
+++ b/api/npm/module.js
@@ -50,8 +50,6 @@ export async function resolve (specifier, origin = null, options = null) {
 
   try {
     pkg.load()
-    // will call `pkg.load()` internally
-    // can throw `ModuleNotFoundError`
     const url = pkg.type === type
       ? pkg.resolve(pathname, { prefix, type })
       : pkg.resolve(pathname, { prefix })

From d20a70bf753a0e29555a0aeef8030a395b1ea399 Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Tue, 23 Apr 2024 13:09:50 -0400
Subject: [PATCH 0631/1178] fix(api/commonjs/package.js): ensure package type
 is correctly derived

---
 api/commonjs/package.js | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/api/commonjs/package.js b/api/commonjs/package.js
index bdd6cdcfb6..fc75581406 100644
--- a/api/commonjs/package.js
+++ b/api/commonjs/package.js
@@ -883,6 +883,10 @@ export class Package {
       }
     }
 
+    if (info.main && !info.module) {
+      this.#type = 'commonjs'
+    }
+
     if (info.main) {
       this.#exports['.'].require = info.main
       if (info.type === 'module') {
@@ -890,12 +894,13 @@ export class Package {
       }
     }
 
-    if (info.module) {
+    if (info.module && !info.main) {
       this.#exports['.'].import = info.module
+      this.#type = 'module'
     }
 
     if (typeof info.exports === 'string') {
-      if (type === 'commonjs') {
+      if (this.#type === 'commonjs') {
         this.#exports['.'].require = info.exports
       } else if (type === 'module') {
         this.#exports['.'].import = info.exports

From 893d9c724405ed056ff91ba8b20da7bfb56e04fb Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Tue, 23 Apr 2024 14:37:38 -0400
Subject: [PATCH 0632/1178] fix(api/commonjs/package.js): remove default entry
 exports

---
 api/commonjs/package.js | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/api/commonjs/package.js b/api/commonjs/package.js
index fc75581406..b3f649c22b 100644
--- a/api/commonjs/package.js
+++ b/api/commonjs/package.js
@@ -558,11 +558,7 @@ export class Package {
   #imports = {}
 
   #exports = {
-    '.': {
-      require: './index.js',
-      import: './index.js',
-      default: './index.js'
-    }
+    '.': {}
   }
 
   /**
@@ -883,7 +879,7 @@ export class Package {
       }
     }
 
-    if (info.main && !info.module) {
+    if (info.main && !info.module && !info.type) {
       this.#type = 'commonjs'
     }
 

From febba1a61d3e4a32a41dd692410a6e1f81a99546 Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Tue, 23 Apr 2024 17:14:37 -0400
Subject: [PATCH 0633/1178] chore(api/npm/service-worker.js): clean up lint

---
 api/npm/service-worker.js | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js
index 7e21c72292..a11d1367a8 100644
--- a/api/npm/service-worker.js
+++ b/api/npm/service-worker.js
@@ -8,7 +8,16 @@ import util from '../util.js'
 
 const DEBUG_LABEL = '  npm'
 
+/**
+ * @ignore
+ * @param {Request}
+ * @param {object} env
+ * @param {import('../service-worker/context.js').Context} ctx
+ * @return {Promise}
+ */
 export async function onRequest (request, env, ctx) {
+  // eslint-disable-next-line
+  void env, ctx;
   if (process.env.SOCKET_RUNTIME_NPM_DEBUG) {
     console.debug(request.url)
   }
@@ -25,6 +34,7 @@ export async function onRequest (request, env, ctx) {
   let origins = []
 
   if (referer && !referer.startsWith('blob:')) {
+    // @ts-ignore
     if (URL.canParse(referer, origin)) {
       const refererURL = new URL(referer, origin)
       if (refererURL.href.endsWith('/')) {
@@ -42,8 +52,10 @@ export async function onRequest (request, env, ctx) {
       origins.push(new URL(value, `socket://${url.host}${url.pathname}`).href.replace(/\/$/, ''))
     } else if (value.startsWith('/')) {
       origins.push(new URL(value, origin).href)
+      // @ts-ignore
     } else if (URL.canParse(value)) {
       origins.push(value)
+      // @ts-ignore
     } else if (URL.canParse(`socket://${value}`)) {
       origins.push(`socket://${value}`)
     }
@@ -55,7 +67,7 @@ export async function onRequest (request, env, ctx) {
   if (process.env.SOCKET_RUNTIME_NPM_DEBUG) {
     console.debug('resolving: npm:%s (%o)', specifier, origins)
   }
-  
+
   while (origins.length && resolved === null) {
     const potentialOrigins = []
     const origin = origins.shift()

From f78d284f5737451f15c64f5bcdbc92142ed0a2be Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Tue, 23 Apr 2024 17:14:50 -0400
Subject: [PATCH 0634/1178] chore(api): generate types

---
 api/index.d.ts | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/api/index.d.ts b/api/index.d.ts
index ce4a8235e1..77456b16c7 100644
--- a/api/index.d.ts
+++ b/api/index.d.ts
@@ -15831,7 +15831,14 @@ declare module "socket:npm/module" {
 }
 
 declare module "socket:npm/service-worker" {
-    export function onRequest(request: any, env: any, ctx: any): Promise;
+    /**
+     * @ignore
+     * @param {Request}
+     * @param {object} env
+     * @param {import('../service-worker/context.js').Context} ctx
+     * @return {Promise}
+     */
+    export function onRequest(request: any, env: object, ctx: import("socket:service-worker/context").Context): Promise;
     /**
      * Handles incoming 'npm:///' requests.
      * @param {Request} request

From 0df2d596d45b6813e45193bcaa85b70e7fd2fdfd Mon Sep 17 00:00:00 2001
From: Joseph Werle 
Date: Wed, 24 Apr 2024 22:01:22 -0400
Subject: [PATCH 0635/1178] refactor(core,ipc):

- move geolocation + notifications functionality to core
- modularize more core functionality
- consolidate IPC scheme handlers, service worker, and protocol handlers
---
 api/service-worker/init.js           |    2 -
 bin/build-runtime-library.sh         |    1 +
 src/android/bridge.cc                |    2 +-
 src/cli/cli.cc                       |    8 +-
 src/cli/templates.hh                 |    2 +-
 src/core/bluetooth.cc                |    9 +-
 src/core/bluetooth.hh                |   73 +
 src/core/child_process.cc            |    5 +-
 src/core/config.hh                   |    4 +-
 src/core/core.cc                     |   56 +-
 src/core/core.hh                     |  368 +-
 src/core/cwd.cc                      |   27 +
 src/core/dns.cc                      |    7 +-
 src/core/fs.cc                       |   14 +-
 src/core/geolocation.cc              |  470 ++
 src/core/geolocation.hh              |   96 +
 src/core/headers.cc                  |  120 +-
 src/core/headers.hh                  |   81 +
 src/core/ip.hh                       |   26 +
 src/core/module.cc                   |    9 +
 src/core/module.hh                   |  131 +
 src/core/network_status.cc           |   89 +
 src/core/network_status.hh           |   36 +
 src/core/notifications.cc            |  178 +
 src/core/notifications.hh            |   66 +
 src/core/os.cc                       |    4 +-
 src/core/peer.hh                     |  155 +
 src/core/platform.hh                 |   13 +-
 src/core/post.hh                     |   32 +
 src/core/preload.cc                  |   78 +
 src/core/preload.hh                  |    8 +
 src/core/resource.cc                 |  433 ++
 src/core/resource.hh                 |   52 +
 src/core/service_worker_container.cc |   83 +-
 src/core/service_worker_container.hh |   10 +-
 src/core/string.cc                   |   54 +-
 src/core/string.hh                   |    6 +
 src/core/timers.cc                   |    5 +
 src/core/types.hh                    |    9 +-
 src/core/udp.cc                      |    2 +-
 src/extension/extension.cc           |   34 -
 src/extension/ipc.cc                 |   24 +-
 src/ipc/bridge.cc                    | 7023 +++-----------------------
 src/ipc/bridge.hh                    |   45 +
 src/ipc/client.hh                    |   16 +
 src/ipc/ipc.cc                       |  249 -
 src/ipc/ipc.hh                       |  365 +-
 src/ipc/message.cc                   |   87 +
 src/ipc/message.hh                   |   58 +
 src/ipc/navigator.cc                 |  175 +
 src/ipc/navigator.hh                 |   51 +
 src/ipc/result.cc                    |  127 +
 src/ipc/result.hh                    |   53 +
 src/ipc/router.cc                    | 1046 ++++
 src/ipc/router.hh                    |  127 +
 src/ipc/routes.cc                    | 3032 +++++++++++
 src/ipc/scheme_handlers.cc           | 1227 +++++
 src/ipc/scheme_handlers.hh           |  240 +
 src/process/win.cc                   |   23 +-
 src/window/apple.mm                  |  225 +-
 src/window/manager.cc                |  389 ++
 src/window/win.cc                    |    2 +-
 src/window/window.hh                 |  399 +-
 63 files changed, 9765 insertions(+), 8076 deletions(-)
 create mode 100644 src/core/bluetooth.hh
 create mode 100644 src/core/cwd.cc
 create mode 100644 src/core/geolocation.cc
 create mode 100644 src/core/geolocation.hh
 create mode 100644 src/core/headers.hh
 create mode 100644 src/core/ip.hh
 create mode 100644 src/core/module.cc
 create mode 100644 src/core/module.hh
 create mode 100644 src/core/network_status.cc
 create mode 100644 src/core/network_status.hh
 create mode 100644 src/core/notifications.cc
 create mode 100644 src/core/notifications.hh
 create mode 100644 src/core/peer.hh
 create mode 100644 src/core/post.hh
 create mode 100644 src/core/resource.cc
 create mode 100644 src/core/resource.hh
 create mode 100644 src/ipc/bridge.hh
 create mode 100644 src/ipc/client.hh
 delete mode 100644 src/ipc/ipc.cc
 create mode 100644 src/ipc/message.cc
 create mode 100644 src/ipc/message.hh
 create mode 100644 src/ipc/navigator.cc
 create mode 100644 src/ipc/navigator.hh
 create mode 100644 src/ipc/result.cc
 create mode 100644 src/ipc/result.hh
 create mode 100644 src/ipc/router.cc
 create mode 100644 src/ipc/router.hh
 create mode 100644 src/ipc/routes.cc
 create mode 100644 src/ipc/scheme_handlers.cc
 create mode 100644 src/ipc/scheme_handlers.hh
 create mode 100644 src/window/manager.cc

diff --git a/api/service-worker/init.js b/api/service-worker/init.js
index f82808e7f4..134eb981e2 100644
--- a/api/service-worker/init.js
+++ b/api/service-worker/init.js
@@ -230,8 +230,6 @@ export async function onFetch (event) {
 
     method: event.detail.fetch.method,
     headers: event.detail.fetch.headers
-      .map((entry) => entry.split(':'))
-      .reduce((object, entry) => Object.assign(object, { [entry[0]]: entry.slice(1).join(':').trim() }), {})
   }
 
   const worker = workers.get(info.hash)
diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh
index d5f0283bac..eba27447cb 100755
--- a/bin/build-runtime-library.sh
+++ b/bin/build-runtime-library.sh
@@ -110,6 +110,7 @@ declare cflags
 
 if [[ "$platform" = "desktop" ]]; then
   sources+=("$root/src/window/hotkey.cc")
+  sources+=("$root/src/window/manager.cc")
 fi
 
 if [[ "$platform" = "android" ]]; then
diff --git a/src/android/bridge.cc b/src/android/bridge.cc
index 1ac7757de8..0dd9304174 100644
--- a/src/android/bridge.cc
+++ b/src/android/bridge.cc
@@ -118,7 +118,7 @@ extern "C" {
 
       if (!attachment.hasException()) {
         auto size = result.post.length;
-        auto body = result.post.body;
+        auto body = *result.post.body;
         auto bytes = body ? env->NewByteArray(size) : nullptr;
 
         if (bytes != nullptr) {
diff --git a/src/cli/cli.cc b/src/cli/cli.cc
index 5a25847460..c2c637f4e2 100644
--- a/src/cli/cli.cc
+++ b/src/cli/cli.cc
@@ -3078,9 +3078,9 @@ int main (const int argc, const char* argv[]) {
       flags += " -framework OSLog";
       flags += " -DMACOS=1";
       if (flagCodeSign) {
-        flags += " -DWAS_CODESIGNED=1";
+        flags += " -DSSC_PLATFORM_SANDBOXED=1";
       } else {
-        flags += " -DWAS_CODESIGNED=0";
+        flags += " -DSSC_PLATFORM_SANDBOXED=0";
       }
       flags += " -I" + prefixFile();
       flags += " -I" + prefixFile("include");
@@ -4291,7 +4291,7 @@ int main (const int argc, const char* argv[]) {
         {"SSC_SETTINGS", _settings},
         {"SSC_VERSION", VERSION_STRING},
         {"SSC_VERSION_HASH", VERSION_HASH_STRING},
-        {"WAS_CODESIGNED", flagCodeSign ? "1" : "0"},
+        {"SSC_PLATFORM_SANDBOXED", flagCodeSign ? "1" : "0"},
         {"__ios_native_extensions_build_ids", ""},
         {"__ios_native_extensions_build_refs", ""},
         {"__ios_native_extensions_build_context_refs", ""},
@@ -6424,7 +6424,7 @@ int main (const int argc, const char* argv[]) {
             }
 
             if (key.starts_with("$HOST_HOME") || key.starts_with("~")) {
-              const auto path = replace(replace(key, "$HOST_HOME", ""), "~", "");
+              const auto path = replace(replace(key, "^(\\$HOST_HOME)", ""), "^(~)", "");
               entitlementSettings["configured_entitlements"] += (
                 "    " + (path.ends_with("/") ? path : path + "/") + "\n"
               );
diff --git a/src/cli/templates.hh b/src/cli/templates.hh
index 1baf5679fd..310edbcd93 100644
--- a/src/cli/templates.hh
+++ b/src/cli/templates.hh
@@ -947,7 +947,7 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$!
           "DEBUG=1",
           "SSC_VERSION={{SSC_VERSION}}",
           "SSC_VERSION_HASH={{SSC_VERSION_HASH}}",
-          "WAS_CODESIGNED={{WAS_CODESIGNED}}",
+          "SSC_PLATFORM_SANDBOXED={{SSC_PLATFORM_SANDBOXED}}",
           "$(inherited)",
         );
         GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
diff --git a/src/core/bluetooth.cc b/src/core/bluetooth.cc
index b8366ee080..b64ecd81ff 100644
--- a/src/core/bluetooth.cc
+++ b/src/core/bluetooth.cc
@@ -1,4 +1,5 @@
 #include "core.hh"
+#include "bluetooth.hh"
 #include "../ipc/ipc.hh"
 
 using namespace SSC;
@@ -369,10 +370,14 @@ using namespace SSC;
 
   Post post = {0};
   post.id = rand64();
-  post.body = bytes;
-  post.length = length;
   post.headers = headers.str();
 
+  if (bytes != nullptr && length > 0) {
+    post.body = std::make_shared(new char[length]{0});
+    post.length = length;
+    memcpy(*post.body, bytes, length);
+  }
+
   auto json = JSON::Object::Entries {
     {"data", JSON::Object::Entries {
       {"event", "data"},
diff --git a/src/core/bluetooth.hh b/src/core/bluetooth.hh
new file mode 100644
index 0000000000..0f0c2d8d06
--- /dev/null
+++ b/src/core/bluetooth.hh
@@ -0,0 +1,73 @@
+#ifndef SSC_CORE_BLUETOOTH_H
+#define SSC_CORE_BLUETOOTH_H
+
+#include "json.hh"
+#include "platform.hh"
+#include "post.hh"
+#include "types.hh"
+
+#if SSC_PLATFORM_APPLE
+@interface SSCBluetoothController : NSObject<
+  CBCentralManagerDelegate,
+  CBPeripheralManagerDelegate,
+  CBPeripheralDelegate
+>
+@property (strong, nonatomic) CBCentralManager* centralManager;
+@property (strong, nonatomic) CBPeripheralManager* peripheralManager;
+@property (strong, nonatomic) CBPeripheral* bluetoothPeripheral;
+@property (strong, nonatomic) NSMutableArray* peripherals;
+@property (strong, nonatomic) NSMutableDictionary* services;
+@property (strong, nonatomic) NSMutableDictionary* characteristics;
+@property (strong, nonatomic) NSMutableDictionary* serviceMap;
+- (void) startAdvertising;
+- (void) startScanning;
+- (id) init;
+@end
+#endif
+
+namespace SSC {
+  class Core;
+  class Bluetooth {
+    public:
+      using SendFunction = Function;
+      using EmitFunction = Function;
+      using Callback = Function;
+
+    #if SSC_PLATFORM_APPLE
+      SSCBluetoothController* controller = nullptr;
+    #endif
+
+      Core *core = nullptr;
+      SendFunction sendFunction;
+      EmitFunction emitFunction;
+
+      Bluetooth ();
+      ~Bluetooth ();
+      bool send (const String& seq, JSON::Any json, Post post);
+      bool send (const String& seq, JSON::Any json);
+      bool emit (const String& seq, JSON::Any json);
+      void startScanning ();
+      void publishCharacteristic (
+        const String& seq,
+        char* bytes,
+        size_t size,
+        const String& serviceId,
+        const String& characteristicId,
+        Callback callback
+      );
+
+      void subscribeCharacteristic (
+        const String& seq,
+        const String& serviceId,
+        const String& characteristicId,
+        Callback callback
+      );
+
+      void startService (
+        const String& seq,
+        const String& serviceId,
+        Callback callback
+      );
+  };
+}
+#endif
diff --git a/src/core/child_process.cc b/src/core/child_process.cc
index ea3d991755..13e61c5134 100644
--- a/src/core/child_process.cc
+++ b/src/core/child_process.cc
@@ -1,4 +1,5 @@
 #include "core.hh"
+#include "../process/process.hh"
 
 #if !SSC_PLATFORM_IOS
 namespace SSC {
@@ -227,7 +228,7 @@ namespace SSC {
 
         Post post;
         post.id = rand64();
-        post.body = bytes;
+        post.body = std::make_shared(bytes);
         post.length = (int) output.size();
         post.headers = headers.str();
 
@@ -257,7 +258,7 @@ namespace SSC {
 
         Post post;
         post.id = rand64();
-        post.body = bytes;
+        post.body = std::make_shared(bytes);
         post.length = (int) output.size();
         post.headers = headers.str();
 
diff --git a/src/core/config.hh b/src/core/config.hh
index 118d5a1527..1945ec277a 100644
--- a/src/core/config.hh
+++ b/src/core/config.hh
@@ -17,8 +17,8 @@
 #endif
 
 // TODO(@jwerle): use a better name
-#if !defined(WAS_CODESIGNED)
-#define WAS_CODESIGNED 0
+#if !defined(SSC_PLATFORM_SANDBOXED)
+#define SSC_PLATFORM_SANDBOXED 0
 #endif
 
 // TODO(@jwerle): stop using this and prefer a namespaced macro
diff --git a/src/core/core.cc b/src/core/core.cc
index ce82da797e..10e97b477f 100644
--- a/src/core/core.cc
+++ b/src/core/core.cc
@@ -20,15 +20,13 @@ namespace SSC {
     return r;
   }
 
-  void msleep (uint64_t ms) {
-    std::this_thread::yield();
-    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
-  }
-
   Post Core::getPost (uint64_t id) {
-    Lock lock(postsMutex);
-    if (posts->find(id) == posts->end()) return Post{};
-    return posts->at(id);
+    Lock lock(this->postsMutex);
+    if (this->posts.find(id) == this->posts.end()) {
+      return Post{};
+    }
+
+    return posts.at(id);
   }
 
 
@@ -39,28 +37,28 @@ namespace SSC {
   }
 
   bool Core::hasPost (uint64_t id) {
-    Lock lock(postsMutex);
-    return posts->find(id) != posts->end();
+    Lock lock(this->postsMutex);
+    return posts.find(id) != posts.end();
   }
 
   bool Core::hasPostBody (const char* body) {
     Lock lock(postsMutex);
     if (body == nullptr) return false;
-    for (auto const &tuple : *posts) {
+    for (const auto& tuple : posts) {
       auto post = tuple.second;
-      if (post.body == body) return true;
+      if (*post.body == body) return true;
     }
     return false;
   }
 
   void Core::expirePosts () {
-    Lock lock(postsMutex);
-    std::vector ids;
-    auto now = std::chrono::system_clock::now()
+    Lock lock(this->postsMutex);
+    const auto now = std::chrono::system_clock::now()
       .time_since_epoch()
       .count();
 
-    for (auto const &tuple : *posts) {
+    Vector ids;
+    for (auto const &tuple : posts) {
       auto id = tuple.first;
       auto post = tuple.second;
 
@@ -82,19 +80,17 @@ namespace SSC {
     )
       .time_since_epoch()
       .count();
-    posts->insert_or_assign(id, p);
+
+    this->posts.insert_or_assign(id, p);
   }
 
   void Core::removePost (uint64_t id) {
-    Lock lock(postsMutex);
-    if (posts->find(id) == posts->end()) return;
-    auto post = getPost(id);
+    Lock lock(this->postsMutex);
 
-    if (post.body) {
-      delete [] post.body;
+    if (this->posts.find(id) == this->posts.end()) {
+      return;
     }
-
-    posts->erase(id);
+    posts.erase(id);
   }
 
   String Core::createPost (String seq, String params, Post post) {
@@ -141,7 +137,7 @@ namespace SSC {
     Lock lock(postsMutex);
     std::vector ids;
 
-    for (auto const &tuple : *posts) {
+    for (auto const &tuple : posts) {
       auto id = tuple.first;
       ids.push_back(id);
     }
@@ -211,7 +207,7 @@ namespace SSC {
       }
     });
 
-#if defined(__linux__) && !defined(__ANDROID__)
+  #if defined(__linux__) && !defined(__ANDROID__)
     GSource *source = g_source_new(&loopSourceFunctions, sizeof(UVSource));
     UVSource *uvSource = (UVSource *) source;
     uvSource->core = this;
@@ -222,7 +218,7 @@ namespace SSC {
     );
 
     g_source_attach(source, nullptr);
-#endif
+  #endif
   }
 
   uv_loop_t* Core::getEventLoop () {
@@ -309,10 +305,10 @@ namespace SSC {
       startTimers();
     });
 
-#if defined(__APPLE__)
+  #if defined(__APPLE__)
     Lock lock(loopMutex);
     dispatch_async(eventLoopQueue, ^{ pollEventLoop(this); });
-#elif defined(__ANDROID__) || !defined(__linux__)
+  #elif defined(__ANDROID__) || !defined(__linux__)
     Lock lock(loopMutex);
     // clean up old thread if still running
     if (eventLoopThread != nullptr) {
@@ -325,7 +321,7 @@ namespace SSC {
     }
 
     eventLoopThread = new std::thread(&pollEventLoop, this);
-#endif
+  #endif
   }
 
   static Timer releaseWeakDescriptors = {
diff --git a/src/core/core.hh b/src/core/core.hh
index eb86fb9c17..20625c4538 100644
--- a/src/core/core.hh
+++ b/src/core/core.hh
@@ -1,133 +1,42 @@
 #ifndef SSC_CORE_CORE_H
 #define SSC_CORE_CORE_H
 
+#include "bluetooth.hh"
 #include "codec.hh"
 #include "config.hh"
 #include "debug.hh"
 #include "env.hh"
 #include "file_system_watcher.hh"
+#include "geolocation.hh"
+#include "headers.hh"
 #include "ini.hh"
 #include "io.hh"
+#include "ip.hh"
 #include "json.hh"
+#include "module.hh"
+#include "network_status.hh"
+#include "notifications.hh"
+#include "peer.hh"
 #include "platform.hh"
+#include "post.hh"
 #include "preload.hh"
 #include "protocol_handlers.hh"
+#include "resource.hh"
 #include "service_worker_container.hh"
 #include "string.hh"
 #include "types.hh"
 #include "version.hh"
 
-#include "../process/process.hh"
-
-#if defined(__APPLE__)
-@interface SSCBluetoothController : NSObject<
-  CBCentralManagerDelegate,
-  CBPeripheralManagerDelegate,
-  CBPeripheralDelegate
->
-@property (strong, nonatomic) CBCentralManager* centralManager;
-@property (strong, nonatomic) CBPeripheralManager* peripheralManager;
-@property (strong, nonatomic) CBPeripheral* bluetoothPeripheral;
-@property (strong, nonatomic) NSMutableArray* peripherals;
-@property (strong, nonatomic) NSMutableDictionary* services;
-@property (strong, nonatomic) NSMutableDictionary* characteristics;
-@property (strong, nonatomic) NSMutableDictionary* serviceMap;
-- (void) startAdvertising;
-- (void) startScanning;
-- (id) init;
-@end
-#endif
-
 namespace SSC {
   constexpr int EVENT_LOOP_POLL_TIMEOUT = 32; // in milliseconds
 
   uint64_t rand64 ();
   void msleep (uint64_t ms);
 
-#if defined(_WIN32)
-  String FormatError (DWORD error, String source);
-#endif
-
-
   // forward
   class Core;
+  class Process;
 
-  class Headers {
-    public:
-      class Value {
-        public:
-          String string;
-          Value () = default;
-          Value (const String& value);
-          Value (const char* value);
-          Value (const Value& value);
-          Value (bool value);
-          Value (int value);
-          Value (float value);
-          Value (int64_t value);
-          Value (uint64_t value);
-          Value (double_t value);
-        #if defined(__APPLE__)
-          Value (ssize_t value);
-        #endif
-          const String& str () const;
-          const char * c_str() const;
-
-          template  void set (T value) {
-            auto v = Value(value);
-            this->string = v.string;
-          }
-      };
-
-      class Header {
-        public:
-          String key;
-          Value value;
-          Header () = default;
-          Header (const Header& header);
-          Header (const String& key, const Value& value);
-      };
-
-      using Entries = Vector
; - Entries entries; - Headers () = default; - Headers (const Headers& headers); - Headers (const String& source); - Headers (const Vector>& entries); - Headers (const Entries& entries); - size_t size () const; - String str () const; - - void set (const String& key, const String& value); - void set (const Header& header); - bool has (const String& name) const; - const Header& get (const String& name) const; - }; - - struct Post { - using EventStreamCallback = Function; - - using ChunkStreamCallback = Function; - - uint64_t id = 0; - uint64_t ttl = 0; - char* body = nullptr; - size_t length = 0; - String headers = ""; - String workerId = ""; - std::shared_ptr eventStream; - std::shared_ptr chunkStream; - }; - - using Posts = std::map; using EventLoopDispatchCallback = Function; struct Timer { @@ -139,237 +48,8 @@ namespace SSC { uv_timer_cb invoke; }; - typedef enum { - PEER_TYPE_NONE = 0, - PEER_TYPE_TCP = 1 << 1, - PEER_TYPE_UDP = 1 << 2, - PEER_TYPE_MAX = 0xF - } peer_type_t; - - typedef enum { - PEER_FLAG_NONE = 0, - PEER_FLAG_EPHEMERAL = 1 << 1 - } peer_flag_t; - - typedef enum { - PEER_STATE_NONE = 0, - // general states - PEER_STATE_CLOSED = 1 << 1, - // udp states (10) - PEER_STATE_UDP_BOUND = 1 << 10, - PEER_STATE_UDP_CONNECTED = 1 << 11, - PEER_STATE_UDP_RECV_STARTED = 1 << 12, - PEER_STATE_UDP_PAUSED = 1 << 13, - // tcp states (20) - PEER_STATE_TCP_BOUND = 1 << 20, - PEER_STATE_TCP_CONNECTED = 1 << 21, - PEER_STATE_TCP_PAUSED = 1 << 13, - PEER_STATE_MAX = 1 << 0xF - } peer_state_t; - - struct LocalPeerInfo { - struct sockaddr_storage addr; - String address = ""; - String family = ""; - int port = 0; - int err = 0; - - int getsockname (uv_udp_t *socket, struct sockaddr *addr); - int getsockname (uv_tcp_t *socket, struct sockaddr *addr); - void init (uv_udp_t *socket); - void init (uv_tcp_t *socket); - void init (const struct sockaddr_storage *addr); - }; - - struct RemotePeerInfo { - struct sockaddr_storage addr; - String address = ""; - String family = ""; - int port = 0; - int err = 0; - - int getpeername (uv_udp_t *socket, struct sockaddr *addr); - int getpeername (uv_tcp_t *socket, struct sockaddr *addr); - void init (uv_udp_t *socket); - void init (uv_tcp_t *socket); - void init (const struct sockaddr_storage *addr); - }; - - /** - * A generic structure for a bound or connected peer. - */ - class Peer { - public: - struct RequestContext { - using Callback = Function; - Callback cb; - Peer *peer = nullptr; - RequestContext (Callback cb) { this->cb = cb; } - }; - - using UDPReceiveCallback = Function; - - // uv handles - union { - uv_udp_t udp; - uv_tcp_t tcp; // XXX: FIXME - } handle; - - // sockaddr - struct sockaddr_in addr; - - // callbacks - UDPReceiveCallback receiveCallback; - std::vector> onclose; - - // instance state - uint64_t id = 0; - std::recursive_mutex mutex; - Core *core; - - struct { - struct { - bool reuseAddr = false; - bool ipv6Only = false; // @TODO - } udp; - } options; - - // peer state - LocalPeerInfo local; - RemotePeerInfo remote; - peer_type_t type = PEER_TYPE_NONE; - peer_flag_t flags = PEER_FLAG_NONE; - peer_state_t state = PEER_STATE_NONE; - - /** - * Private `Peer` class constructor - */ - Peer (Core *core, peer_type_t peerType, uint64_t peerId, bool isEphemeral); - ~Peer (); - - int init (); - int initRemotePeerInfo (); - int initLocalPeerInfo (); - void addState (peer_state_t value); - void removeState (peer_state_t value); - bool hasState (peer_state_t value); - const RemotePeerInfo* getRemotePeerInfo (); - const LocalPeerInfo* getLocalPeerInfo (); - bool isUDP (); - bool isTCP (); - bool isEphemeral (); - bool isBound (); - bool isActive (); - bool isClosing (); - bool isClosed (); - bool isConnected (); - bool isPaused (); - int bind (); - int bind (String address, int port); - int bind (String address, int port, bool reuseAddr); - int rebind (); - int connect (String address, int port); - int disconnect (); - void send ( - char *buf, - size_t size, - int port, - const String address, - Peer::RequestContext::Callback cb - ); - int recvstart (); - int recvstart (UDPReceiveCallback onrecv); - int recvstop (); - int resume (); - int pause (); - void close (); - void close (Function onclose); - }; - - static inline String addrToIPv4 (struct sockaddr_in* sin) { - char buf[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN); - return String(buf); - } - - static inline String addrToIPv6 (struct sockaddr_in6* sin) { - char buf[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, &sin->sin6_addr, buf, INET6_ADDRSTRLEN); - return String(buf); - } - - static inline void parseAddress (struct sockaddr *name, int* port, char* address) { - struct sockaddr_in *name_in = (struct sockaddr_in *) name; - *port = ntohs(name_in->sin_port); - uv_ip4_name(name_in, address, 17); - } - - class Bluetooth { - public: - using SendFunction = Function; - using EmitFunction = Function; - using Callback = Function; - - Core *core = nullptr; - #if defined(__APPLE__) - SSCBluetoothController* controller= nullptr; - #endif - - SendFunction sendFunction; - EmitFunction emitFunction; - - Bluetooth (); - ~Bluetooth (); - bool send (const String& seq, JSON::Any json, Post post); - bool send (const String& seq, JSON::Any json); - bool emit (const String& seq, JSON::Any json); - void startScanning (); - void publishCharacteristic ( - const String& seq, - char* bytes, - size_t size, - const String& serviceId, - const String& characteristicId, - Callback callback - ); - void subscribeCharacteristic ( - const String& seq, - const String& serviceId, - const String& characteristicId, - Callback callback - ); - void startService ( - const String& seq, - const String& serviceId, - Callback callback - ); - }; - class Core { public: - class Module { - public: - using Callback = Function; - struct RequestContext { - String seq; - Module::Callback cb; - RequestContext () = default; - RequestContext (String seq, Module::Callback cb) { - this->seq = seq; - this->cb = cb; - } - }; - - Core *core = nullptr; - Module (Core* core) { - this->core = core; - } - }; - class Diagnostics : public Module { public: Diagnostics (auto core) : Module(core) {} @@ -399,14 +79,15 @@ namespace SSC { struct Descriptor { uint64_t id; - std::atomic retained = false; - std::atomic stale = false; + Atomic retained = false; + Atomic stale = false; + UniquePointer resource = nullptr; Mutex mutex; uv_dir_t *dir = nullptr; uv_file fd = 0; Core *core; - Descriptor (Core *core, uint64_t id); + Descriptor (Core *core, uint64_t id, const String& filename); bool isDirectory (); bool isFile (); bool isRetained (); @@ -448,7 +129,7 @@ namespace SSC { uint32_t getBufferSize (); }; - #if !defined(__ANDROID__) + #if !SSC_PLATFORM_ANDROID std::map watchers; #endif @@ -842,6 +523,9 @@ namespace SSC { Diagnostics diagnostics; DNS dns; FS fs; + Geolocation geolocation; + NetworkStatus networkStatus; + Notifications notifications; OS os; Platform platform; ProtocolHandlers protocolHandlers; @@ -849,7 +533,7 @@ namespace SSC { Timers timers; UDP udp; - std::shared_ptr posts; + Posts posts; std::map peers; Mutex loopMutex; @@ -866,9 +550,9 @@ namespace SSC { uv_loop_t eventLoop; uv_async_t eventLoopAsync; - std::queue eventLoopDispatchQueue; + Queue eventLoopDispatchQueue; - #if defined(__APPLE__) + #if SSC_PLATFORM_APPLE dispatch_queue_attr_t eventLoopQueueAttrs = dispatch_queue_attr_make_with_qos_class( DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, @@ -890,6 +574,9 @@ namespace SSC { diagnostics(this), dns(this), fs(this), + geolocation(this), + networkStatus(this), + notifications(this), os(this), platform(this), protocolHandlers(this), @@ -897,7 +584,6 @@ namespace SSC { udp(this), serviceWorker(this) { - this->posts = std::shared_ptr(new Posts()); initEventLoop(); } @@ -971,6 +657,10 @@ namespace SSC { const String& state, const String& value ); + + void setcwd (const String& cwd); + const String getcwd (); + const String getcwd_state_value (); } // SSC #endif // SSC_CORE_CORE_H diff --git a/src/core/cwd.cc b/src/core/cwd.cc new file mode 100644 index 0000000000..341010144b --- /dev/null +++ b/src/core/cwd.cc @@ -0,0 +1,27 @@ +#include "core.hh" +#include "resource.hh" + +namespace SSC { + static struct { Mutex mutex; String value = ""; } state; + + void setcwd (const String& value) { + Lock lock(state.mutex); + state.value = value; + } + + const String getcwd_state_value () { + Lock lock(state.mutex); + return state.value; + } + + const String getcwd () { + Lock lock(state.mutex); + + if (state.value.size() > 0) { + return state.value; + } + + state.value = FileResource::getResourcesPath().string(); + return state.value; + } +} diff --git a/src/core/dns.cc b/src/core/dns.cc index 9087e029e0..f527e39937 100644 --- a/src/core/dns.cc +++ b/src/core/dns.cc @@ -1,13 +1,14 @@ #include "core.hh" +#include "module.hh" namespace SSC { void Core::DNS::lookup ( const String seq, LookupOptions options, - Core::Module::Callback cb + Module::Callback cb ) { this->core->dispatchEventLoop([=, this]() { - auto ctx = new Core::Module::RequestContext(seq, cb); + auto ctx = new Module::RequestContext(seq, cb); auto loop = this->core->getEventLoop(); struct addrinfo hints = {0}; @@ -27,7 +28,7 @@ namespace SSC { resolver->data = ctx; auto err = uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) { - auto ctx = (Core::DNS::RequestContext*) resolver->data; + auto ctx = (RequestContext*) resolver->data; if (status < 0) { auto result = JSON::Object::Entries { diff --git a/src/core/fs.cc b/src/core/fs.cc index 96d6e4d16e..16d17fd9dc 100644 --- a/src/core/fs.cc +++ b/src/core/fs.cc @@ -1,6 +1,7 @@ #include "core.hh" -namespace SSC { +#include "resource.hh" +namespace SSC { #define SET_CONSTANT(c) constants[#c] = (c); static std::map getFSConstantsMap () { std::map constants; @@ -224,7 +225,8 @@ namespace SSC { return this->buf.len; } - Core::FS::Descriptor::Descriptor (Core *core, uint64_t id) { + Core::FS::Descriptor::Descriptor (Core *core, uint64_t id, const String& filename) { + this->resource.reset(new FileResource(filename)); this->core = core; this->id = id; } @@ -585,7 +587,7 @@ namespace SSC { ) { this->core->dispatchEventLoop([=, this]() { auto filename = path.c_str(); - auto desc = new Descriptor(this->core, id); + auto desc = new Descriptor(this->core, id, filename); auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, cb); auto req = &ctx->req; @@ -649,7 +651,7 @@ namespace SSC { ) { this->core->dispatchEventLoop([=, this]() { auto filename = path.c_str(); - auto desc = new Descriptor(this->core, id); + auto desc = new Descriptor(this->core, id, filename); auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, cb); auto req = &ctx->req; @@ -1025,8 +1027,8 @@ namespace SSC { {"content-length", req->result} }}; - post.id = SSC::rand64(); - post.body = ctx->getBuffer(); + post.id = rand64(); + post.body = std::make_shared(ctx->getBuffer()); post.length = (int) req->result; post.headers = headers.str(); } diff --git a/src/core/geolocation.cc b/src/core/geolocation.cc new file mode 100644 index 0000000000..45e2f338f4 --- /dev/null +++ b/src/core/geolocation.cc @@ -0,0 +1,470 @@ +#include "geolocation.hh" +#include "debug.hh" + +#if SSC_PLATFORM_APPLE +@implementation SSCLocationPositionWatcher ++ (SSCLocationPositionWatcher*) positionWatcherWithIdentifier: (NSInteger) identifier + completion: (void (^)(CLLocation*)) completion +{ + auto watcher= [SSCLocationPositionWatcher new]; + watcher.identifier = identifier; + watcher.completion = [completion copy]; + return watcher; +} +@end + +@implementation SSCLocationObserver +- (id) init { + self = [super init]; + self.delegate = [[SSCLocationManagerDelegate alloc] initWithLocationObserver: self]; + self.isAuthorized = NO; + self.locationWatchers = [NSMutableArray new]; + self.activationCompletions = [NSMutableArray new]; + self.locationRequestCompletions = [NSMutableArray new]; + + self.locationManager = [CLLocationManager new]; + self.locationManager.delegate = self.delegate; + self.locationManager.desiredAccuracy = CLAccuracyAuthorizationFullAccuracy; + self.locationManager.pausesLocationUpdatesAutomatically = NO; + +#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + self.locationManager.allowsBackgroundLocationUpdates = YES; + self.locationManager.showsBackgroundLocationIndicator = YES; +#endif + + if ([CLLocationManager locationServicesEnabled]) { + if ( + #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR + self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorized || + #else + self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse || + #endif + self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways + ) { + self.isAuthorized = YES; + } + } + + return self; +} + +- (BOOL) attemptActivation { + if ([CLLocationManager locationServicesEnabled] == NO) { + return NO; + } + + if (self.isAuthorized) { + [self.locationManager requestLocation]; + return YES; + } + +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE + [self.locationManager requestWhenInUseAuthorization]; +#else + [self.locationManager requestAlwaysAuthorization]; +#endif + + return YES; +} + +- (BOOL) attemptActivationWithCompletion: (void (^)(BOOL)) completion { + if (self.isAuthorized) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(YES); + }); + return YES; + } + + if ([self attemptActivation]) { + [self.activationCompletions addObject: [completion copy]]; + return YES; + } + + return NO; +} + +- (BOOL) getCurrentPositionWithCompletion: (void (^)(NSError*, CLLocation*)) completion { + return [self attemptActivationWithCompletion: ^(BOOL isAuthorized) { + auto userConfig = SSC::getUserConfig(); + if (!isAuthorized) { + auto reason = @("Location observer could not be activated"); + + if (!self.locationManager) { + reason = @("Location observer manager is not initialized"); + } else if (!self.locationManager.location) { + reason = @("Location observer manager could not provide location"); + } + + auto error = [NSError + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + code: -1 + userInfo: @{ + NSLocalizedDescriptionKey: reason + } + ]; + + return completion(error, nullptr); + } + + auto location = self.locationManager.location; + if (location.timestamp.timeIntervalSince1970 > 0) { + completion(nullptr, self.locationManager.location); + } else { + [self.locationRequestCompletions addObject: [completion copy]]; + } + + [self.locationManager requestLocation]; + }]; +} + +- (int) watchPositionForIdentifier: (NSInteger) identifier + completion: (void (^)(NSError*, CLLocation*)) completion { + SSCLocationPositionWatcher* watcher = nullptr; + BOOL exists = NO; + + for (SSCLocationPositionWatcher* existing in self.locationWatchers) { + if (existing.identifier == identifier) { + watcher = existing; + exists = YES; + break; + } + } + + if (!watcher) { + watcher = [SSCLocationPositionWatcher + positionWatcherWithIdentifier: identifier + completion: ^(CLLocation* location) { + completion(nullptr, location); + }]; + } + + const auto performedActivation = [self attemptActivationWithCompletion: ^(BOOL isAuthorized) { + auto userConfig = SSC::getUserConfig(); + if (!isAuthorized) { + auto error = [NSError + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + code: -1 + userInfo: @{ + @"Error reason": @("Location observer could not be activated") + } + ]; + + return completion(error, nullptr); + } + + [self.locationManager startUpdatingLocation]; + + if (CLLocationManager.headingAvailable) { + [self.locationManager startUpdatingHeading]; + } + + [self.locationManager startMonitoringSignificantLocationChanges]; + }]; + + if (!performedActivation) { + #if !__has_feature(objc_arc) + [watcher release]; + #endif + return -1; + } + + if (!exists) { + [self.locationWatchers addObject: watcher]; + } + + return identifier; +} + +- (BOOL) clearWatch: (NSInteger) identifier { + for (SSCLocationPositionWatcher* watcher in self.locationWatchers) { + if (watcher.identifier == identifier) { + [self.locationWatchers removeObject: watcher]; + #if !__has_feature(objc_arc) + [watcher release]; + #endif + return YES; + } + } + + return NO; +} +@end + +@implementation SSCLocationManagerDelegate +- (id) initWithLocationObserver: (SSCLocationObserver*) locationObserver { + self = [super init]; + self.locationObserver = locationObserver; + locationObserver.delegate = self; + return self; +} + +- (void) locationManager: (CLLocationManager*) locationManager + didUpdateLocations: (NSArray*) locations { + auto locationRequestCompletions = [NSArray arrayWithArray: self.locationObserver.locationRequestCompletions]; + for (id item in locationRequestCompletions) { + auto completion = (void (^)(CLLocation*)) item; + completion(locations.firstObject); + [self.locationObserver.locationRequestCompletions removeObject: item]; + #if !__has_feature(objc_arc) + [completion release]; + #endif + } + + for (SSCLocationPositionWatcher* watcher in self.locationObserver.locationWatchers) { + watcher.completion(locations.firstObject); + } +} + +- (void) locationManager: (CLLocationManager*) locationManager + didFailWithError: (NSError*) error { + // TODO(@jwerle): handle location manager error + debug("locationManager:didFailWithError: %@", error); +} + +- (void) locationManager: (CLLocationManager*) locationManager + didFinishDeferredUpdatesWithError: (NSError*) error { + // TODO(@jwerle): handle deferred error + debug("locationManager:didFinishDeferredUpdatesWithError: %@", error); +} + +- (void) locationManagerDidPauseLocationUpdates: (CLLocationManager*) locationManager { + // TODO(@jwerle): handle pause for updates + debug("locationManagerDidPauseLocationUpdates"); +} + +- (void) locationManagerDidResumeLocationUpdates: (CLLocationManager*) locationManager { + // TODO(@jwerle): handle resume for updates + debug("locationManagerDidResumeLocationUpdates"); +} + +- (void) locationManager: (CLLocationManager*) locationManager + didVisit: (CLVisit*) visit { + auto locations = [NSArray arrayWithObject: locationManager.location]; + [self locationManager: locationManager didUpdateLocations: locations]; +} + +- (void) locationManager: (CLLocationManager*) locationManager + didChangeAuthorizationStatus: (CLAuthorizationStatus) status { + // XXX(@jwerle): this is a legacy callback + [self locationManagerDidChangeAuthorization: locationManager]; +} + +- (void) locationManagerDidChangeAuthorization: (CLLocationManager*) locationManager { + using namespace SSC; + auto activationCompletions = [NSArray arrayWithArray: self.locationObserver.activationCompletions]; + if ( + #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR + locationManager.authorizationStatus == kCLAuthorizationStatusAuthorized || + #else + locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse || + #endif + locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways + ) { + JSON::Object json = JSON::Object::Entries { + {"state", "granted"} + }; + + self.locationObserver.geolocation->permissionChangeObservers.dispatch(json); + self.locationObserver.isAuthorized = YES; + for (id item in activationCompletions) { + auto completion = (void (^)(BOOL)) item; + completion(YES); + [self.locationObserver.activationCompletions removeObject: item]; + #if !__has_feature(objc_arc) + [completion release]; + #endif + } + } else { + JSON::Object json = JSON::Object::Entries { + {"state", locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined + ? "prompt" + : "denied" + } + }; + + self.locationObserver.geolocation->permissionChangeObservers.dispatch(json); + self.locationObserver.isAuthorized = NO; + for (id item in activationCompletions) { + auto completion = (void (^)(BOOL)) item; + completion(NO); + [self.locationObserver.activationCompletions removeObject: item]; + #if !__has_feature(objc_arc) + [completion release]; + #endif + } + } +} +@end + +#endif + +namespace SSC { + Geolocation::Geolocation (Core* core) + : Module(core), + permissionChangeObservers() + { + #if SSC_PLATFORM_APPLE + this->locationObserver = [SSCLocationObserver new]; + this->locationPositionWatcher = [SSCLocationPositionWatcher new]; + this->locationManagerDelegate = [SSCLocationManagerDelegate new]; + this->locationObserver.geolocation = this; + #endif + } + + Geolocation::~Geolocation () { + #if SSC_PLATFORM_APPLE + #if !__has_feature(objc_arc) + [this->locationObserver release]; + [this->locationPositionWatcher release]; + [this->locationManagerDelegate release]; + #endif + this->locationObserver = nullptr; + this->locationPositionWatcher = nullptr; + this->locationManagerDelegate = nullptr; + #endif + } + + void Geolocation::getCurrentPosition (const String& seq, const Module::Callback callback) { + bool performedActivation = false; + #if SSC_PLATFORM_APPLE + performedActivation = [this->locationObserver getCurrentPositionWithCompletion: ^(NSError* error, CLLocation* location) { + if (error != nullptr) { + const auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"type", "GeolocationPositionError"}, + {"message", message} + }} + }; + + return callback(seq, json, Post {}); + } + + const auto heading = this->locationObserver.locationManager.heading; + const auto json = JSON::Object::Entries { + {"coords", JSON::Object::Entries { + {"latitude", location.coordinate.latitude}, + {"longitude", location.coordinate.longitude}, + {"altitude", location.altitude}, + {"accuracy", location.horizontalAccuracy}, + {"altitudeAccuracy", location.verticalAccuracy}, + {"floorLevel", location.floor.level}, + {"heading", heading.trueHeading}, + {"speed", location.speed} + }} + }; + + callback(seq, json, Post {}); + }]; + #endif + + if (!performedActivation) { + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"type", "GeolocationPositionError"}, + {"message", "Failed to get position"} + }} + }; + + callback(seq, json, Post {}); + } + } + + void Geolocation::watchPosition (const String& seq, uint64_t id, const Module::Callback callback) { + #if SSC_PLATFORM_APPLE + const int identifier = [this->locationObserver watchPositionForIdentifier: id completion: ^(NSError* error, CLLocation* location) { + if (error != nullptr) { + const auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"type", "GeolocationPositionError"}, + {"message", message} + }} + }; + + return callback(seq, json, Post {}); + } + + const auto heading = this->locationObserver.locationManager.heading; + const auto json = JSON::Object::Entries { + {"watch", JSON::Object::Entries { + {"identifier", identifier}, + }}, + {"coords", JSON::Object::Entries { + {"latitude", location.coordinate.latitude}, + {"longitude", location.coordinate.longitude}, + {"altitude", location.altitude}, + {"accuracy", location.horizontalAccuracy}, + {"altitudeAccuracy", location.verticalAccuracy}, + {"floorLevel", location.floor.level}, + {"heading", heading.trueHeading}, + {"speed", location.speed} + }} + }; + + callback("-1", json, Post {}); + }]; + + if (identifier != -1) { + const auto json = JSON::Object::Entries { + {"watch", JSON::Object::Entries { + {"identifier", identifier} + }} + }; + + return callback(seq, json, Post {}); + } + #endif + + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"type", "GeolocationPositionError"}, + {"message", "Failed to watch position"} + }} + }; + + callback(seq, json, Post {}); + } + + void Geolocation::clearWatch ( + const String& seq, + uint64_t id, + const Module::Callback callback + ) { + #if SSC_PLATFORM_APPLE + [this->locationObserver clearWatch: id]; + #endif + + callback(seq, JSON::Object {}, Post {}); + } + + template<> bool Module::template Observers>::add( + const Module::Observer&, + Module::Observer::Callback + ); + + template<> bool Geolocation::PermissionChangeObservers::remove( + const Geolocation::PermissionChangeObserver& + ); + + bool Geolocation::addPermissionChangeObserver ( + const PermissionChangeObserver& observer, + const PermissionChangeObserver::Callback callback + ) { + return this->permissionChangeObservers.add(observer, callback); + } + + bool Geolocation::removePermissionChangeObserver (const PermissionChangeObserver& observer) { + return this->permissionChangeObservers.remove(observer); + } +} diff --git a/src/core/geolocation.hh b/src/core/geolocation.hh new file mode 100644 index 0000000000..41e6187dfe --- /dev/null +++ b/src/core/geolocation.hh @@ -0,0 +1,96 @@ +#ifndef SSC_CORE_GEOLOCATION_H +#define SSC_CORE_GEOLOCATION_H + +#include "platform.hh" +#include "types.hh" +#include "module.hh" + +namespace SSC { + // forward + class Core; + class Geolocation; +} + +#if SSC_PLATFORM_APPLE +@class SSCLocationObserver; + +@interface SSCLocationManagerDelegate : NSObject +@property (nonatomic, strong) SSCLocationObserver* locationObserver; + +- (id) initWithLocationObserver: (SSCLocationObserver*) locationObserver; + +- (void) locationManager: (CLLocationManager*) locationManager + didFailWithError: (NSError*) error; + +- (void) locationManager: (CLLocationManager*) locationManager + didUpdateLocations: (NSArray*) locations; + +- (void) locationManager: (CLLocationManager*) locationManager + didFinishDeferredUpdatesWithError: (NSError*) error; + +- (void) locationManagerDidPauseLocationUpdates: (CLLocationManager*) locationManager; +- (void) locationManagerDidResumeLocationUpdates: (CLLocationManager*) locationManager; +- (void) locationManager: (CLLocationManager*) locationManager + didVisit: (CLVisit*) visit; + +- (void) locationManager: (CLLocationManager*) locationManager + didChangeAuthorizationStatus: (CLAuthorizationStatus) status; +- (void) locationManagerDidChangeAuthorization: (CLLocationManager*) locationManager; +@end + +@interface SSCLocationPositionWatcher : NSObject +@property (nonatomic, assign) NSInteger identifier; +@property (nonatomic, assign) void(^completion)(CLLocation*); ++ (SSCLocationPositionWatcher*) positionWatcherWithIdentifier: (NSInteger) identifier + completion: (void (^)(CLLocation*)) completion; +@end + +@interface SSCLocationObserver : NSObject +@property (nonatomic, retain) CLLocationManager* locationManager; +@property (nonatomic, retain) SSCLocationManagerDelegate* delegate; +@property (atomic, retain) NSMutableArray* activationCompletions; +@property (atomic, retain) NSMutableArray* locationRequestCompletions; +@property (atomic, retain) NSMutableArray* locationWatchers; +@property (nonatomic) SSC::Geolocation* geolocation; +@property (atomic, assign) BOOL isAuthorized; +- (BOOL) attemptActivation; +- (BOOL) attemptActivationWithCompletion: (void (^)(BOOL)) completion; +- (BOOL) getCurrentPositionWithCompletion: (void (^)(NSError*, CLLocation*)) completion; +- (int) watchPositionForIdentifier: (NSInteger) identifier + completion: (void (^)(NSError*, CLLocation*)) completion; +- (BOOL) clearWatch: (NSInteger) identifier; +@end +#endif + +namespace SSC { + class Geolocation : public Module { + public: + using PermissionChangeObserver = Module::Observer; + using PermissionChangeObservers = Module::Observers; + + #if SSC_PLATFORM_APPLE + SSCLocationObserver* locationObserver = nullptr; + SSCLocationPositionWatcher* locationPositionWatcher = nullptr; + SSCLocationManagerDelegate* locationManagerDelegate = nullptr; + #endif + + PermissionChangeObservers permissionChangeObservers; + + Geolocation (Core* core); + ~Geolocation (); + void getCurrentPosition (const String& seq, const Module::Callback callback); + void watchPosition (const String& seq, uint64_t id, const Module::Callback callback); + void clearWatch ( + const String& seq, + uint64_t id, + const Module::Callback callback + ); + + bool removePermissionChangeObserver (const PermissionChangeObserver& observer); + bool addPermissionChangeObserver ( + const PermissionChangeObserver& observer, + const PermissionChangeObserver::Callback callback + ); + }; +} +#endif diff --git a/src/core/headers.cc b/src/core/headers.cc index b43b1dde5a..6a696befd5 100644 --- a/src/core/headers.cc +++ b/src/core/headers.cc @@ -2,15 +2,31 @@ namespace SSC { Headers::Header::Header (const Header& header) { - this->key = header.key; + this->name = toLowerCase(header.name); this->value = header.value; } - Headers::Header::Header (const String& key, const Value& value) { - this->key = trim(key); + Headers::Header::Header (const String& name, const Value& value) { + this->name = toLowerCase(trim(name)); this->value = trim(value.str()); } + bool Headers::Header::operator == (const Header& header) const { + return this->value == header.value; + } + + bool Headers::Header::operator != (const Header& header) const { + return this->value != header.value; + } + + bool Headers::Header::operator == (const String& string) const { + return this->value.string == string; + } + + bool Headers::Header::operator != (const String& string) const { + return this->value.string != string; + } + Headers::Headers (const String& source) { for (const auto& entry : split(source, '\n')) { const auto tuple = split(entry, ':'); @@ -38,13 +54,13 @@ namespace SSC { } } - void Headers::set (const String& key, const String& value) { - set(Header{ key, value }); + void Headers::set (const String& name, const String& value) noexcept { + set(Header { name, value }); } - void Headers::set (const Header& header) { + void Headers::set (const Header& header) noexcept { for (auto& entry : entries) { - if (header.key == entry.key) { + if (header.name == entry.name) { entry.value = header.value; return; } @@ -53,9 +69,10 @@ namespace SSC { entries.push_back(header); } - bool Headers::has (const String& name) const { + bool Headers::has (const String& name) const noexcept { + const auto normalizedName = toLowerCase(name); for (const auto& header : entries) { - if (header.key == name) { + if (header.name == normalizedName) { return true; } } @@ -63,16 +80,26 @@ namespace SSC { return false; } - const Headers::Header& Headers::get (const String& name) const { - static const auto empty = Header(); - + const Headers::Header Headers::get (const String& name) const noexcept { + const auto normalizedName = toLowerCase(name); for (const auto& header : entries) { - if (header.key == name) { + if (header.name == normalizedName) { return header; } } - return empty; + return Header {}; + } + + Headers::Header& Headers::at (const String& name) { + const auto normalizedName = toLowerCase(name); + for (auto& header : entries) { + if (header.name == normalizedName) { + return header; + } + } + + throw std::out_of_range("Header does not exist"); } size_t Headers::size () const { @@ -83,7 +110,7 @@ namespace SSC { StringStream headers; auto count = this->size(); for (const auto& entry : this->entries) { - headers << entry.key << ": " << entry.value.str();; + headers << entry.name << ": " << entry.value.str();; if (--count > 0) { headers << "\n"; } @@ -91,6 +118,53 @@ namespace SSC { return headers.str(); } + const Headers::Iterator Headers::begin () const noexcept { + return this->entries.begin(); + } + + const Headers::Iterator Headers::end () const noexcept { + return this->entries.end(); + } + + bool Headers::erase (const String& name) noexcept { + for (int i = 0; i < this->entries.size(); ++i) { + const auto& entry = this->entries[i]; + if (entry.name == name) { + this->entries.erase(this->entries.begin() + i); + return true; + } + } + return false; + } + + const bool Headers::clear () noexcept { + if (this->entries.size() == 0) { + return false; + } + this->entries.clear(); + return true; + } + + String& Headers::operator [] (const String& name) { + if (!this->has(name)) { + this->set(name, ""); + } + + return this->at(name).value.string; + } + + const String Headers::operator [] (const String& name) const noexcept { + return this->get(name).value.string; + } + + JSON::Object Headers::json () const noexcept { + JSON::Object::Entries entries; + for (const auto& entry : this->entries) { + entries[entry.name] = entry.value.string; + } + return entries; + } + Headers::Value::Value (const String& value) { this->string = trim(value); } @@ -133,6 +207,22 @@ namespace SSC { } #endif + bool Headers::Value::operator == (const Value& value) const { + return this->string == value.string; + } + + bool Headers::Value::operator != (const Value& value) const { + return this->string != value.string; + } + + bool Headers::Value::operator == (const String& string) const { + return this->string == string; + } + + bool Headers::Value::operator != (const String& string) const { + return this->string != string; + } + const String& Headers::Value::str () const { return this->string; } diff --git a/src/core/headers.hh b/src/core/headers.hh new file mode 100644 index 0000000000..c07cbb2b54 --- /dev/null +++ b/src/core/headers.hh @@ -0,0 +1,81 @@ +#ifndef SSC_CORE_HEADERS_H +#define SSC_CORE_HEADERS_H + +#include "json.hh" +#include "platform.hh" +#include "types.hh" + +namespace SSC { + class Headers { + public: + class Value { + public: + String string; + Value () = default; + Value (const String& value); + Value (const char* value); + Value (const Value& value); + Value (bool value); + Value (int value); + Value (float value); + Value (int64_t value); + Value (uint64_t value); + Value (double_t value); + #if SSC_PLATFORM_APPLE + Value (ssize_t value); + #endif + bool operator == (const Value&) const; + bool operator != (const Value&) const; + bool operator == (const String&) const; + bool operator != (const String&) const; + + const String& str () const; + const char * c_str() const; + + template void set (T value) { + auto v = Value(value); + this->string = v.string; + } + }; + + class Header { + public: + String name; + Value value; + Header () = default; + Header (const Header& header); + Header (const String& name, const Value& value); + bool operator == (const Header&) const; + bool operator != (const Header&) const; + bool operator == (const String&) const; + bool operator != (const String&) const; + }; + + using Entries = Vector
; + using Iterator = Entries::const_iterator; + + Entries entries; + Headers () = default; + Headers (const Headers& headers); + Headers (const String& source); + Headers (const Vector>& entries); + Headers (const Entries& entries); + size_t size () const; + String str () const; + + void set (const String& name, const String& value) noexcept; + void set (const Header& header) noexcept; + bool has (const String& name) const noexcept; + const Header get (const String& name) const noexcept; + Header& at (const String& name); + const Iterator begin () const noexcept; + const Iterator end () const noexcept; + bool erase (const String& name) noexcept; + const bool clear () noexcept; + String& operator [] (const String& name); + const String operator [] (const String& name) const noexcept; + JSON::Object json () const noexcept; + }; +} + +#endif diff --git a/src/core/ip.hh b/src/core/ip.hh new file mode 100644 index 0000000000..35344693b3 --- /dev/null +++ b/src/core/ip.hh @@ -0,0 +1,26 @@ +#ifndef SSC_CORE_IP_H +#define SSC_CORE_IP_H + +#include "platform.hh" +#include "types.hh" + +namespace SSC { + static inline String addrToIPv4 (struct sockaddr_in* sin) { + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN); + return String(buf); + } + + static inline String addrToIPv6 (struct sockaddr_in6* sin) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &sin->sin6_addr, buf, INET6_ADDRSTRLEN); + return String(buf); + } + + static inline void parseAddress (struct sockaddr *name, int* port, char* address) { + struct sockaddr_in *name_in = (struct sockaddr_in *) name; + *port = ntohs(name_in->sin_port); + uv_ip4_name(name_in, address, 17); + } +} +#endif diff --git a/src/core/module.cc b/src/core/module.cc new file mode 100644 index 0000000000..f8cbe0e325 --- /dev/null +++ b/src/core/module.cc @@ -0,0 +1,9 @@ +#include "module.hh" +#include "core.hh" + +namespace SSC { + + Module::Module (Core* core) + : core(core) + {} +} diff --git a/src/core/module.hh b/src/core/module.hh new file mode 100644 index 0000000000..9db5158846 --- /dev/null +++ b/src/core/module.hh @@ -0,0 +1,131 @@ +#ifndef SSC_CORE_MODULE_H +#define SSC_CORE_MODULE_H + +#include "platform.hh" +#include "types.hh" +#include "json.hh" +#include "post.hh" + +namespace SSC { + uint64_t rand64 (); + // forward + class Core; + class Module { + public: + using Callback = Function; + + struct RequestContext { + String seq; + Module::Callback cb; + RequestContext () = default; + RequestContext (String seq, Module::Callback cb) { + this->seq = seq; + this->cb = cb; + } + }; + + template + class Observer { + public: + using Callback = Function; + uint64_t id = 0; + Callback callback; + + Observer () { + this->id = rand64(); + } + + Observer (const Callback& callback) + : callback(callback) + { + this->id = rand64(); + } + + Observer (uint64_t id, const Callback& callback) + : id(id), + callback(callback) + {} + }; + + template + class Observers { + public: + Vector observers; + Mutex mutex; + + bool add (const Observer& observer, const Observer::Callback callback = nullptr) { + Lock lock(this->mutex); + if (this->has(observer)) { + auto& existing = this->get(observer.id); + existing.callback = callback; + return true; + } else if (callback != nullptr) { + this->observers.push_back({ observer.id, callback }); + return true; + } else if (observer.callback != nullptr) { + this->observers.push_back(observer); + return true; + } + + return false; + } + + bool remove (const Observer& observer) { + Lock lock(this->mutex); + int cursor = -1; + while (cursor++ < this->observers.size()) { + if (this->observers.at(cursor).id == observer.id) { + break; + } + } + + if (cursor >= 0 && cursor < this->observers.size()) { + this->observers.erase(this->observers.begin() + cursor); + return true; + } + + return false; + } + + bool has (const Observer& observer) { + Lock lock(this->mutex); + for (const auto& existing : this->observers) { + if (existing.id == observer.id) { + return true; + } + } + + return false; + + } + + Observer& get (const uint64_t id) { + Lock lock(this->mutex); + for (auto& existing : this->observers) { + if (existing.id == id) { + return existing; + } + } + + throw std::out_of_range("Observer for ID does not exist"); + } + + template + bool dispatch (Types... arguments) { + Lock lock(this->mutex); + bool dispatched = false; + for (auto& observer : this->observers) { + if (observer.callback != nullptr) { + observer.callback(arguments...); + dispatched = true; + } + } + return dispatched; + } + }; + + Core *core = nullptr; + Module (Core* core); + }; +} +#endif diff --git a/src/core/network_status.cc b/src/core/network_status.cc new file mode 100644 index 0000000000..18cabb171c --- /dev/null +++ b/src/core/network_status.cc @@ -0,0 +1,89 @@ +#include "network_status.hh" +#include "platform.hh" +#include "core.hh" + +namespace SSC { + NetworkStatus::NetworkStatus (Core* core) : Module(core) { + #if SSC_PLATFORM_APPLE + dispatch_queue_attr_t attrs = dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_SERIAL, + QOS_CLASS_UTILITY, + DISPATCH_QUEUE_PRIORITY_DEFAULT + ); + + this->queue = dispatch_queue_create( + "socket.runtime.queue.ipc.network-status", + attrs + ); + + this->monitor = nw_path_monitor_create(); + + nw_path_monitor_set_queue(this->monitor, this->queue); + nw_path_monitor_set_update_handler(this->monitor, ^(nw_path_t path) { + if (path == nullptr) { + return; + } + + nw_path_status_t status = nw_path_get_status(path); + + String name; + String message; + + switch (status) { + case nw_path_status_invalid: { + name = "offline"; + message = "Network path is invalid"; + break; + } + case nw_path_status_satisfied: { + name = "online"; + message = "Network is usable"; + break; + } + case nw_path_status_satisfiable: { + name = "online"; + message = "Network may be usable"; + break; + } + case nw_path_status_unsatisfied: { + name = "offline"; + message = "Network is not usable"; + break; + } + } + + const auto json = JSON::Object::Entries { + {"name", name}, + {"message", message} + }; + + this->observers.dispatch(json); + }); + #endif + } + + NetworkStatus::~NetworkStatus () { + #if SSC_PLATFORM_APPLE + dispatch_release(this->queue); + nw_release(this->monitor); + this->monitor = nullptr; + this->queue = nullptr; + #endif + } + + bool NetworkStatus::start () { + #if SSC_PLATFORM_APPLE + nw_path_monitor_start(this->monitor); + return true; + #endif + return false; + } + + bool NetworkStatus::addObserver (const Observer& observer, const Observer::Callback callback) { + return this->observers.add(observer, callback); + } + + bool NetworkStatus::removeObserver (const Observer& observer) { + return this->observers.remove(observer); + } +} diff --git a/src/core/network_status.hh b/src/core/network_status.hh new file mode 100644 index 0000000000..c353aa2628 --- /dev/null +++ b/src/core/network_status.hh @@ -0,0 +1,36 @@ +#ifndef SSC_CORE_NETWORK_STATUS_H +#define SSC_CORE_NETWORK_STATUS_H + +#include "json.hh" +#include "platform.hh" +#include "types.hh" +#include "module.hh" + +namespace SSC { + // forward + class Core; + class NetworkStatus; +} + +namespace SSC { + class NetworkStatus : public Module { + public: + using Observer = Module::Observer; + using Observers = Module::Observers; + + #if SSC_PLATFORM_APPLE + dispatch_queue_t queue; + nw_path_monitor_t monitor; + #endif + + Observers observers; + + NetworkStatus (Core*); + ~NetworkStatus (); + bool start (); + bool addObserver (const Observer&, const Observer::Callback callback = nullptr); + bool removeObserver (const Observer&); + }; +} + +#endif diff --git a/src/core/notifications.cc b/src/core/notifications.cc new file mode 100644 index 0000000000..d7b577d3d6 --- /dev/null +++ b/src/core/notifications.cc @@ -0,0 +1,178 @@ +#include "notifications.hh" +#include "module.hh" + +#if SSC_PLATFORM_APPLE +@implementation SSCUserNotificationCenterDelegate +- (void) userNotificationCenter: (UNUserNotificationCenter*) center + didReceiveNotificationResponse: (UNNotificationResponse*) response + withCompletionHandler: (void (^)(void)) completionHandler +{ + using namespace SSC; + + const auto id = String(response.notification.request.identifier.UTF8String); + const auto action = ( + [response.actionIdentifier isEqualToString: UNNotificationDefaultActionIdentifier] + ? "default" + : "dismiss" + ); + + const auto json = JSON::Object::Entries { + {"id", id}, + {"action", action} + }; + + self.notifications->notificationResponseObservers.dispatch(json); + + completionHandler(); +} + +- (void) userNotificationCenter: (UNUserNotificationCenter*) center + willPresentNotification: (UNNotification*) notification + withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler +{ + using namespace SSC; + UNNotificationPresentationOptions options = UNNotificationPresentationOptionList; + const auto __block id = String(notification.request.identifier.UTF8String); + + if (notification.request.content.sound != nullptr) { + options |= UNNotificationPresentationOptionSound; + } + + if (notification.request.content.attachments != nullptr) { + if (notification.request.content.attachments.count > 0) { + options |= UNNotificationPresentationOptionBanner; + } + } + + self.notifications->notificationPresentedObservers.dispatch(JSON::Object::Entries { + {"id", id} + }); + + // look for dismissed notification + auto timer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + // if notification that was presented is not in the delivered notifications then + // then notify that the notification was "dismissed" + [notificationCenter getDeliveredNotificationsWithCompletionHandler: ^(NSArray *notifications) { + for (UNNotification* notification in notifications) { + if (String(notification.request.identifier.UTF8String) == id) { + return; + } + } + + [timer invalidate]; + + self.notifications->notificationResponseObservers.dispatch(JSON::Object::Entries { + {"id", id}, + {"action", "dismiss"} + }); + }]; + }]; + + [NSRunLoop.mainRunLoop + addTimer: timer + forMode: NSDefaultRunLoopMode + ]; +} +@end +#endif + +namespace SSC { + Notifications::Notifications (Core* core) + : Module(core), + permissionChangeObservers(), + notificationResponseObservers(), + notificationPresentedObservers() + { + #if SSC_PLATFORM_APPLE + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + + this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; + this->userNotificationCenterDelegate.notifications = this; + + if (!notificationCenter.delegate) { + notificationCenter.delegate = this->userNotificationCenterDelegate; + } + + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; + this->userNotificationCenterPollTimer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { + // look for authorization status changes + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + JSON::Object json; + if (this->currentUserNotificationAuthorizationStatus != settings.authorizationStatus) { + this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; + + if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + json = JSON::Object::Entries {{"state", "denied"}}; + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + json = JSON::Object::Entries {{"state", "prompt"}}; + } else { + json = JSON::Object::Entries {{"state", "granted"}}; + } + + this->permissionChangeObservers.dispatch(json); + } + }]; + }]; + + [NSRunLoop.mainRunLoop + addTimer: this->userNotificationCenterPollTimer + forMode: NSDefaultRunLoopMode + ]; + }]; + #endif + } + + Notifications::~Notifications () { + #if SSC_PLATFORM_APPLE + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + + if (notificationCenter.delegate == this->userNotificationCenterDelegate) { + notificationCenter.delegate = nullptr; + } + + [this->userNotificationCenterPollTimer invalidate]; + + #if !__has_feature(objc_arc) + [this->userNotificationCenterDelegate release]; + #endif + + this->userNotificationCenterDelegate = nullptr; + this->userNotificationCenterPollTimer = nullptr; + #endif + } + + bool Notifications::addPermissionChangeObserver ( + const PermissionChangeObserver& observer, + const PermissionChangeObserver::Callback callback + ) { + return this->permissionChangeObservers.add(observer, callback); + } + + bool Notifications::removePermissionChangeObserver (const PermissionChangeObserver& observer) { + return this->permissionChangeObservers.remove(observer); + } + + bool Notifications::addNotificationResponseObserver ( + const NotificationResponseObserver& observer, + const NotificationResponseObserver::Callback callback + ) { + return this->notificationResponseObservers.add(observer, callback); + } + + bool Notifications::removeNotificationResponseObserver (const NotificationResponseObserver& observer) { + return this->notificationResponseObservers.remove(observer); + } + + bool Notifications::addNotificationPresentedObserver ( + const NotificationPresentedObserver& observer, + const NotificationPresentedObserver::Callback callback + ) { + return this->notificationPresentedObservers.add(observer, callback); + } + + bool Notifications::removeNotificationPresentedObserver (const NotificationPresentedObserver& observer) { + return this->notificationPresentedObservers.remove(observer); + } +} diff --git a/src/core/notifications.hh b/src/core/notifications.hh new file mode 100644 index 0000000000..76d5476eb7 --- /dev/null +++ b/src/core/notifications.hh @@ -0,0 +1,66 @@ +#ifndef SSC_CORE_NOTIFICATIONS_H +#define SSC_CORE_NOTIFICATIONS_H + +#include "module.hh" +#include "platform.hh" + +namespace SSC { + class Notifications; +} + +#if SSC_PLATFORM_APPLE +@interface SSCUserNotificationCenterDelegate : NSObject +@property (nonatomic) SSC::Notifications* notifications; +- (void) userNotificationCenter: (UNUserNotificationCenter*) center + didReceiveNotificationResponse: (UNNotificationResponse*) response + withCompletionHandler: (void (^)(void)) completionHandler; + +- (void) userNotificationCenter: (UNUserNotificationCenter*) center + willPresentNotification: (UNNotification*) notification + withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler; +@end +#endif + +namespace SSC { + class Notifications : public Module { + public: + using PermissionChangeObserver = Module::Observer; + using PermissionChangeObservers = Module::Observers; + using NotificationResponseObserver = Module::Observer; + using NotificationResponseObservers = Module::Observers; + using NotificationPresentedObserver = Module::Observer; + using NotificationPresentedObservers = Module::Observers; + + #if SSC_PLATFORM_APPLE + SSCUserNotificationCenterDelegate* userNotificationCenterDelegate = nullptr; + NSTimer* userNotificationCenterPollTimer = nullptr; + UNAuthorizationStatus __block currentUserNotificationAuthorizationStatus; + #endif + + PermissionChangeObservers permissionChangeObservers; + NotificationResponseObservers notificationResponseObservers; + NotificationPresentedObservers notificationPresentedObservers; + + Notifications (Core* core); + ~Notifications (); + + bool removePermissionChangeObserver (const PermissionChangeObserver& observer); + bool addPermissionChangeObserver ( + const PermissionChangeObserver& observer, + const PermissionChangeObserver::Callback callback + ); + + bool removeNotificationResponseObserver (const NotificationResponseObserver& observer); + bool addNotificationResponseObserver ( + const NotificationResponseObserver& observer, + const NotificationResponseObserver::Callback callback + ); + + bool removeNotificationPresentedObserver (const NotificationPresentedObserver& observer); + bool addNotificationPresentedObserver ( + const NotificationPresentedObserver& observer, + const NotificationPresentedObserver::Callback callback + ); + }; +} +#endif diff --git a/src/core/os.cc b/src/core/os.cc index 02015ef220..783c377768 100644 --- a/src/core/os.cc +++ b/src/core/os.cc @@ -572,7 +572,7 @@ namespace SSC { auto post = Post {}; auto body = new char[size]{0}; auto json = JSON::Object {}; - post.body = body; + post.body = std::make_shared(body); post.length = size; memcpy(body, bytes.data(), size); cb(seq, json, post); @@ -588,7 +588,7 @@ namespace SSC { auto post = Post {}; auto body = new char[size]{0}; auto json = JSON::Object {}; - post.body = body; + post.body = std::make_shared(body); post.length = size; memcpy(body, bytes.data(), size); cb(seq, json, post); diff --git a/src/core/peer.hh b/src/core/peer.hh new file mode 100644 index 0000000000..38f17b52e0 --- /dev/null +++ b/src/core/peer.hh @@ -0,0 +1,155 @@ +#ifndef SSC_CORE_PEER_H +#define SSC_CORE_PEER_H +namespace SSC { + typedef enum { + PEER_TYPE_NONE = 0, + PEER_TYPE_TCP = 1 << 1, + PEER_TYPE_UDP = 1 << 2, + PEER_TYPE_MAX = 0xF + } peer_type_t; + + typedef enum { + PEER_FLAG_NONE = 0, + PEER_FLAG_EPHEMERAL = 1 << 1 + } peer_flag_t; + + typedef enum { + PEER_STATE_NONE = 0, + // general states + PEER_STATE_CLOSED = 1 << 1, + // udp states (10) + PEER_STATE_UDP_BOUND = 1 << 10, + PEER_STATE_UDP_CONNECTED = 1 << 11, + PEER_STATE_UDP_RECV_STARTED = 1 << 12, + PEER_STATE_UDP_PAUSED = 1 << 13, + // tcp states (20) + PEER_STATE_TCP_BOUND = 1 << 20, + PEER_STATE_TCP_CONNECTED = 1 << 21, + PEER_STATE_TCP_PAUSED = 1 << 13, + PEER_STATE_MAX = 1 << 0xF + } peer_state_t; + + struct LocalPeerInfo { + struct sockaddr_storage addr; + String address = ""; + String family = ""; + int port = 0; + int err = 0; + + int getsockname (uv_udp_t *socket, struct sockaddr *addr); + int getsockname (uv_tcp_t *socket, struct sockaddr *addr); + void init (uv_udp_t *socket); + void init (uv_tcp_t *socket); + void init (const struct sockaddr_storage *addr); + }; + + struct RemotePeerInfo { + struct sockaddr_storage addr; + String address = ""; + String family = ""; + int port = 0; + int err = 0; + + int getpeername (uv_udp_t *socket, struct sockaddr *addr); + int getpeername (uv_tcp_t *socket, struct sockaddr *addr); + void init (uv_udp_t *socket); + void init (uv_tcp_t *socket); + void init (const struct sockaddr_storage *addr); + }; + + /** + * A generic structure for a bound or connected peer. + */ + class Peer { + public: + struct RequestContext { + using Callback = Function; + Callback cb; + Peer *peer = nullptr; + RequestContext (Callback cb) { this->cb = cb; } + }; + + using UDPReceiveCallback = Function; + + // uv handles + union { + uv_udp_t udp; + uv_tcp_t tcp; // XXX: FIXME + } handle; + + // sockaddr + struct sockaddr_in addr; + + // callbacks + UDPReceiveCallback receiveCallback; + std::vector> onclose; + + // instance state + uint64_t id = 0; + std::recursive_mutex mutex; + Core *core; + + struct { + struct { + bool reuseAddr = false; + bool ipv6Only = false; // @TODO + } udp; + } options; + + // peer state + LocalPeerInfo local; + RemotePeerInfo remote; + peer_type_t type = PEER_TYPE_NONE; + peer_flag_t flags = PEER_FLAG_NONE; + peer_state_t state = PEER_STATE_NONE; + + /** + * Private `Peer` class constructor + */ + Peer (Core *core, peer_type_t peerType, uint64_t peerId, bool isEphemeral); + ~Peer (); + + int init (); + int initRemotePeerInfo (); + int initLocalPeerInfo (); + void addState (peer_state_t value); + void removeState (peer_state_t value); + bool hasState (peer_state_t value); + const RemotePeerInfo* getRemotePeerInfo (); + const LocalPeerInfo* getLocalPeerInfo (); + bool isUDP (); + bool isTCP (); + bool isEphemeral (); + bool isBound (); + bool isActive (); + bool isClosing (); + bool isClosed (); + bool isConnected (); + bool isPaused (); + int bind (); + int bind (String address, int port); + int bind (String address, int port, bool reuseAddr); + int rebind (); + int connect (String address, int port); + int disconnect (); + void send ( + char *buf, + size_t size, + int port, + const String address, + Peer::RequestContext::Callback cb + ); + int recvstart (); + int recvstart (UDPReceiveCallback onrecv); + int recvstop (); + int resume (); + int pause (); + void close (); + void close (Function onclose); + }; +} +#endif diff --git a/src/core/platform.hh b/src/core/platform.hh index 0f4b202aa3..d80bc54663 100644 --- a/src/core/platform.hh +++ b/src/core/platform.hh @@ -113,6 +113,7 @@ # define SSC_PLATFORM_NAME "win32" # define SSC_PLATFORM_OS "win32" # define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_APPLE 0 # define SSC_PLATFORM_IOS 0 # define SSC_PLATFORM_IOS_SIMULATOR 0 # define SSC_PLATFORM_LINUX 0 @@ -121,6 +122,7 @@ # define SSC_PLATFORM_WINDOWS 1 #elif defined(__APPLE__) # include +# define SSC_PLATFORM_APPLE 1 # define SSC_PLATFORM_NAME "darwin" # define SSC_PLATFORM_ANDROID 0 # define SSC_PLATFORM_IOS_SIMULATOR 0 @@ -145,7 +147,7 @@ #endif #if defined(__unix__) || defined(unix) || defined(__unix) -# define SSC_PLATFORM_UXIX 0 +# define SSC_PLATFORM_UXIX 1 #else # define SSC_PLATFORM_UXIX 0 #endif @@ -153,18 +155,20 @@ #elif defined(__linux__) # undef linux # define SSC_PLATFORM_NAME "linux" +# define SSC_PLATFORM_APPLE 0 # define SSC_PLATFORM_IOS 0 # define SSC_PLATFORM_IOS_SIMULATOR 0 -# define SSC_PLATFORM_LINUX 1 # define SSC_PLATFORM_MACOS 0 # define SSC_PLATFORM_WINDOWS 0 #ifdef __ANDROID__ # define SSC_PLATFORM_OS "android" # define SSC_PLATFORM_ANDROID 1 +# define SSC_PLATFORM_LINUX 0 #else # define SSC_PLATFORM_OS "linux" # define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_LINUX 1 #endif #if defined(__unix__) || defined(unix) || defined(__unix) @@ -193,6 +197,7 @@ # define SSC_PLATFORM_NAME "openbsd" # define SSC_PLATFORM_OS "openbsd" # define SSC_PLATFORM_ANDROID 0 +# define SSC_PLATFORM_APPLE 0 # define SSC_PLATFORM_IOS 0 # define SSC_PLATFORM_IOS_SIMULATOR 0 # define SSC_PLATFORM_LINUX 0 @@ -214,6 +219,10 @@ #define SSC_PLATFORM_DESKTOP 1 #endif +#if !defined(SSC_PLATFORM_SANDBOXED) +#define SSC_PLATFORM_SANDBOXED 0 +#endif + namespace SSC { struct RuntimePlatform { const String arch = ""; diff --git a/src/core/post.hh b/src/core/post.hh new file mode 100644 index 0000000000..4c303ff22b --- /dev/null +++ b/src/core/post.hh @@ -0,0 +1,32 @@ +#ifndef SSC_CORE_POST_H +#define SSC_CORE_POST_H + +#include "types.hh" + +namespace SSC { + struct Post { + using EventStreamCallback = Function; + + using ChunkStreamCallback = Function; + + uint64_t id = 0; + uint64_t ttl = 0; + SharedPointer body = nullptr; + size_t length = 0; + String headers = ""; + String workerId = ""; + SharedPointer eventStream; + SharedPointer chunkStream; + }; + + using Posts = std::map; +} +#endif diff --git a/src/core/preload.cc b/src/core/preload.cc index 0b1cec7f47..ad0c135c6a 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -3,6 +3,7 @@ #include "preload.hh" #include "string.hh" #include "config.hh" +#include "resource.hh" namespace SSC { String createPreload ( @@ -351,4 +352,81 @@ namespace SSC { return preload; } + + String injectHTMLPreload ( + const Core* core, + const Map userConfig, + String html, + String preload + ) { + if (html.size() == 0) { + return ""; + } + + if (userConfig.contains("webview_importmap") && userConfig.at("webview_importmap").size() > 0) { + auto resource = FileResource(Path(userConfig.at("webview_importmap"))); + + if (resource.exists()) { + const auto bytes = resource.read(); + + if (bytes != nullptr) { + const auto string = String(bytes, resource.size()); + + preload = ( + String("\n") + + preload + ); + } + } + } + + auto protocolHandlers = Vector { "npm:", "node:" }; + for (const auto& entry : core->protocolHandlers.mapping) { + if (entry.first != "npm" && entry.first != "node") { + protocolHandlers.push_back(String(entry.first) + ":"); + } + } + + html = tmpl(html, Map { + {"protocol_handlers", join(protocolHandlers, " ")} + }); + + if (html.find(""); + bool preloadWasInjected = false; + + if (existingImportMapCursor != String::npos) { + const auto closingScriptTag = html.find("", existingImportMapCursor); + if (closingScriptTag != String::npos) { + html = ( + html.substr(0, closingScriptTag + 9) + + preload + + html.substr(closingScriptTag + 9) + ); + + preloadWasInjected = true; + } + } + + if (!preloadWasInjected) { + if (html.find("") != String::npos) { + html = replace(html, "", String("" + preload)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + preload)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + preload)); + } else { + html = preload + html; + } + } + + return html; + } } diff --git a/src/core/preload.hh b/src/core/preload.hh index b9a4b70ab2..f4bef1e5fd 100644 --- a/src/core/preload.hh +++ b/src/core/preload.hh @@ -4,6 +4,7 @@ #include "../window/options.hh" namespace SSC { + class Core; struct PreloadOptions { bool module = false; bool wrap = false; @@ -18,5 +19,12 @@ namespace SSC { inline SSC::String createPreload (WindowOptions opts) { return createPreload(opts, PreloadOptions {}); } + + String injectHTMLPreload ( + const Core* core, + const Map userConfig, + String html, + String preload + ); } #endif diff --git a/src/core/resource.cc b/src/core/resource.cc new file mode 100644 index 0000000000..4298435493 --- /dev/null +++ b/src/core/resource.cc @@ -0,0 +1,433 @@ +#include "resource.hh" +#include "debug.hh" +#include "core.hh" + +namespace SSC { + std::map> FileResource::mimeTypes = { + {"application/font-woff", { ".woff" }}, + {"application/font-woff2", { ".woff2" }}, + {"application/x-font-opentype", { ".otf" }}, + {"application/x-font-ttf", { ".ttf" }}, + {"application/json", { ".json" }}, + {"application/ld+json", { ".jsonld" }}, + {"application/typescript", { ".ts", ".tsx" }}, + {"application/wasm", { ".wasm" }}, + {"audio/opus", { ".opux" }}, + {"audio/ogg", { ".oga" }}, + {"audio/mp3", { ".mp3" }}, + {"image/jpeg", { ".jpg", ".jpeg" }}, + {"image/png", { ".png" }}, + {"image/svg+xml", { ".svg" }}, + {"image/vnd.microsoft.icon", { ".ico" }}, + {"text/css", { ".css" }}, + {"text/html", { ".html" }}, + {"text/javascript", { ".js", ".cjs", ".mjs" }}, + {"text/plain", { ".txt", ".text" }}, + {"video/mp4", { ".mp4" }}, + {"video/mpeg", { ".mpeg" }}, + {"video/ogg", { ".ogv" }} + }; + + Path FileResource::getResourcesPath () { + static String value; + + if (value.size() > 0) { + return Path(value); + } + + #if SSC_PLATFORM_MACOS + static const auto currentDirectory = NSFileManager.defaultManager.currentDirectoryPath; + value = currentDirectory.UTF8String; + #elif SSC_PLATFORM_IOS || SSC_PLATFORM_IOS_SIMULATOR + static const auto resourcePath = NSBundle.mainBundle.resourcePath; + value = [[resourcePath stringByAppendingPathComponent: @"ui"] UTF8String]; + #elif SSC_PLATFORM_LINUX + static const auto self = fs::canonical("/proc/self/exe"); + value = self.parent_path().string(); + #elif SSC_PLATFORM_WINDOWS + static wchar_t filename[MAX_PATH]; + GetModuleFileNameW(NULL, filename, MAX_PATH); + const auto self = Path(filename).remove_filename(); + value = path.string(); + size_t offset = 0; + // escape + while ((offset = value.find('\\', offset)) != Sstring::npos) { + value.replace(offset, 1, "\\\\"); + offset += 2; + } + #else + value = getcwd_state_value(); + #endif + + if (value.size() > 0) { + #if !SSC_PLATFORM_WINDOWS + std::replace( + value.begin(), + value.end(), + '\\', + '/' + ); + #endif + } + + if (value.ends_with("/")) { + value = value.substr(0, value.size() - 1); + } + + return Path(value); + } + + FileResource::FileResource (const String& resourcePath) { + this->path = fs::absolute(Path(resourcePath)); + + this->startAccessing(); + } + + FileResource::~FileResource () { + this->stopAccessing(); + } + + FileResource::FileResource (const FileResource& resource) { + this->path = resource.path; + this->bytes = resource.bytes; + this->cache = resource.cache; + this->accessing = resource.accessing.load(); + + if (this->accessing) { + this->startAccessing(); + } + } + + FileResource::FileResource (FileResource&& resource) { + this->path = resource.path; + this->bytes = resource.bytes; + this->cache = resource.cache; + this->accessing = resource.accessing.load(); + + resource.bytes = nullptr; + resource.cache.size = 0; + resource.cache.bytes = nullptr; + + if (this->accessing) { + this->startAccessing(); + } + } + + FileResource& FileResource::operator= (const FileResource& resource) { + this->path = resource.path; + this->bytes = resource.bytes; + this->cache = resource.cache; + this->accessing = resource.accessing.load(); + + if (this->accessing) { + this->startAccessing(); + } + + return *this; + } + + FileResource& FileResource::operator= (FileResource&& resource) { + this->path = resource.path; + this->bytes = resource.bytes; + this->cache = resource.cache; + this->accessing = resource.accessing.load(); + + resource.bytes = nullptr; + resource.cache.size = 0; + resource.cache.bytes = nullptr; + + if (this->accessing) { + this->startAccessing(); + } + + return *this; + } + + bool FileResource::startAccessing () { + static const auto resourcesPath = FileResource::getResourcesPath(); + + if (this->accessing) { + return false; + } + + #if SSC_PLATFORM_APPLE + if (this->url == nullptr) { + #if SSC_PLATFORM_APPLE + this->url = [NSURL fileURLWithPath: @(this->path.string().c_str())]; + #endif + } + + if (!this->path.string().starts_with(resourcesPath.string())) { + if (![this->url startAccessingSecurityScopedResource]) { + debug("FAILED FOR %s", this->path.string().c_str()); + return false; + } + } + #endif + + this->accessing = true; + return true; + } + + bool FileResource::stopAccessing () { + if (!this->accessing) { + return false; + } + #if SSC_PLATFORM_APPLE + if (this->url != nullptr) { + [this->url stopAccessingSecurityScopedResource]; + } + #endif + this->accessing = false; + return true; + } + + bool FileResource::exists () const noexcept { + if (!this->accessing) { + return false; + } + + #if SSC_PLATFORM_APPLE + static auto fileManager = [[NSFileManager alloc] init]; + return [fileManager + fileExistsAtPath: @(this->path.string().c_str()) + isDirectory: NULL + ]; + #endif + + return false; + } + + bool FileResource::hasAccess () const noexcept { + return this->accessing; + } + + const String FileResource::mimeType () const noexcept { + if (!this->accessing) { + return ""; + } + + const auto extension = this->path.extension().string(); + if (extension.size() > 0) { + // try in memory simle mime db + for (const auto& entry : FileResource::mimeTypes) { + const auto& mimeType = entry.first; + const auto& extensions = entry.second; + if (extensions.contains(extension)) { + return mimeType; + } + } + } + + #if SSC_PLATFORM_APPLE + if (extension.size() > 0) { + @try { + const auto types = [UTType + typesWithTag: @(extension.c_str()) + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + if (types.count > 0 && types.firstObject.preferredMIMEType != nullptr) { + return types.firstObject.preferredMIMEType.UTF8String; + } + } @catch (::id) {} + } + #elif SSC_PLATFORM_LINUX + gboolean typeGuessResultUncertain = false; + gchar* type = nullptr; + + type = g_content_type_guess(path.c_str(), nullptr, 0, &typeGuessResultUncertain); + + if (!type || typeGuessResultUncertain) { + const auto bytes = this->read(true); + const auto size = this->size(true); + const auto nextType = g_content_type_guess( + path.c_str(), + data, + bytes, + &typeGuessResultUncertain + ); + + if (nextType) { + if (type) { + g_free(type) + } + + type = nextType; + } + } + + if (type == nullptr) { + return ""; + } + + const auto mimeType = String(type); + + if (type) { + g_free(type); + } + + return mimeType; + #elif SSC_PLATFORM_WINDOWS + LPWSTR mimeData; + const auto bytes = this->read(true) + const auto size = this->size(true); + const auto result = FindMimeFromData( + nullptr, // (LPBC) ignored (unsused) + convertStringToWString(path).c_str(), // filename + reinterpret_cast(bytes), // detected buffer data + (DWORD) size, // detected buffer size + nullptr, // mime suggestion + 0, // flags (unsused) + &mimeData, // output + 0 // reserved (unsused) + ); + + if (result == S_OK) { + return convertWStringToString(WString(mimeData)); + } + #endif + + return ""; + } + + size_t FileResource::size (bool cached) noexcept { + if (cached && this->cache.size > 0) { + return this->cache.size; + } + + if (!this->accessing) { + return -EPERM; + } + + #if SSC_PLATFORM_APPLE + if (this->url != nullptr) { + NSNumber* size = nullptr; + NSError* error = nullptr; + [this->url getResourceValue: &size + forKey: NSURLFileSizeKey + error: &error + ]; + + if (error) { + return -error.code; + } + + this->cache.size = size.longLongValue; + } + #elif SSC_PLATFORM_WINDOWS + LARGE_INTEGER fileSize; + auto handle = CreateFile( + this->path.c_str(), + GENERIC_READ, // access + FILE_SHARE_READ, // share mode + nullptr, // security attribues (unused) + OPEN_EXISTING, // creation disposition + nullptr, // flags and attribues (unused) + nullptr // templte file (unused) + ); + + auto result = GetFileSizeEx(handle, &fileSize); + + if (handle) { + CloseHandle(handle); + } + + this->cache.size = fileSize.QuadPart; + #else + this->cache.size = fs::file_size(this->path); + #endif + + return this->cache.size; + } + + // caller takes ownership of returned pointer + const char* FileResource::read (bool cached) { + Lock lock(this->mutex); + if (!this->accessing || !this->exists()) { + return nullptr; + } + + if (cached && this->cache.bytes != nullptr) { + return *this->cache.bytes; + } + + if (this->bytes != nullptr) { + this->bytes = nullptr; + } + + #if SSC_PLATFORM_APPLE + if (this->url == nullptr) { + return nullptr; + } + + const auto data = [NSData dataWithContentsOfURL: this->url]; + if (data.length > 0) { + this->bytes = std::make_shared(new char[data.length]{0}); + memcpy(*this->bytes, data.bytes, data.length); + } + #elif SSC_PLATFORM_LINUX + char* contents = nullptr; + gsize size = 0; + if (g_file_get_contents(this->path.string().c_str(), &contents, &size, &error)) { + if (size > 0) { + this->bytes = std::make_shared(new char[size]{0}); + memcpy(*this->bytes, contents, size); + } + } + + if (contents) { + g_free(contents); + } + #elif SSC_PLATFORM_WINDOWS + auto handle = CreateFile( + this->path.c_str(), + GENERIC_READ, // access + FILE_SHARE_READ, // share mode + nullptr, // security attribues (unused) + OPEN_EXISTING, // creation disposition + nullptr, // flags and attribues (unused) + nullptr // templte file (unused) + ); + + if (handle) { + const auto size = this->size(); + auto bytes = new char[size]{0}; + auto result = ReadFile( + handle, // File handle + reinterpret_cast(bytes), + (DWORD) size, // output buffer size + nullptr, // bytes read (unused) + nullptr // ignored (unused) + ); + + if (result) { + this->bytes = std::make_shared(bytes); + } else { + delete [] bytes; + } + + CloseHandle(handle); + } + #endif + + this->cache.bytes = this->bytes; + return *this->cache.bytes; + } + + const String FileResource::string (bool cached) { + if (!this->accessing || !this->exists()) { + return ""; + } + + const auto size = this->size(cached); + const auto bytes = this->read(cached); + + if (bytes != nullptr && size > 0) { + return String(bytes, size); + } + + return ""; + } +} diff --git a/src/core/resource.hh b/src/core/resource.hh new file mode 100644 index 0000000000..9a527f9336 --- /dev/null +++ b/src/core/resource.hh @@ -0,0 +1,52 @@ +#ifndef SSC_CORE_RESOURCE +#define SSC_CORE_RESOURCE + +#include "platform.hh" +#include "types.hh" + +namespace SSC { + class Resource { + public: + Atomic accessing = false; + Resource () = default; + virtual bool startAccessing () = 0; + virtual bool stopAccessing () = 0; + }; + + class FileResource : public Resource { + struct Cache { + std::shared_ptr bytes = nullptr; + size_t size = 0; + }; + + Cache cache; + std::shared_ptr bytes = nullptr; + public: + static std::map> mimeTypes; + static Path getResourcesPath (); + + Path path; + Mutex mutex; + #if SSC_PLATFORM_APPLE + NSURL* url = nullptr; + #endif + + FileResource (const String& resourcePath); + ~FileResource (); + FileResource (const FileResource&); + FileResource (FileResource&&); + FileResource& operator= (const FileResource&); + FileResource& operator= (FileResource&&); + + bool startAccessing (); + bool stopAccessing (); + bool exists () const noexcept; + bool hasAccess () const noexcept; + const String mimeType () const noexcept; + size_t size (bool cached = false) noexcept; + const char* read (bool cached = false); + const String string (bool cached = false); + }; +} + +#endif diff --git a/src/core/service_worker_container.cc b/src/core/service_worker_container.cc index dc4dd4827c..e0d5449ff8 100644 --- a/src/core/service_worker_container.cc +++ b/src/core/service_worker_container.cc @@ -3,11 +3,6 @@ #include "../ipc/ipc.hh" -// TODO(@jwerle): create a better platform macro to drop this garbage below -#if defined(_WIN32) || (defined(__linux__) && !defined(__ANDROID__)) || (defined(__APPLE__) && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) -#include "../app/app.hh" -#endif - namespace SSC { static IPC::Bridge* serviceWorkerBridge = nullptr; static const String normalizeScope (const String& scope) { @@ -25,7 +20,7 @@ namespace SSC { } const String ServiceWorkerContainer::FetchRequest::str () const { - auto string = this->scheme + "://" + this->host + this->pathname; + auto string = this->scheme + "://" + this->hostname + this->pathname; if (this->query.size() > 0) { string += String("?") + this->query; @@ -331,7 +326,13 @@ namespace SSC { request = this->fetchRequests.at(id); } while (0); - const auto post = Post { 0, 0, request.buffer.bytes, request.buffer.size }; + const auto post = Post { + 0, + 0, + request.buffer.bytes, + request.buffer.size + }; + reply(IPC::Result { message.seq, message, JSON::Object {}, post }); }); @@ -390,7 +391,7 @@ namespace SSC { }}); } - const auto headers = split(message.get("headers"), '\n'); + const auto headers = Headers(message.get("headers")); auto response = FetchResponse { id, statusCode, @@ -399,27 +400,11 @@ namespace SSC { { clientId } }; - String contentType = ""; - - // find content type - for (const auto& entry : headers) { - auto pair = split(trim(entry), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - - if (key == "content-type" || key == "Content-Type") { - contentType = value; - break; - } - } - - bool freeResponseBody = false; - - // XXX(@jwerle): we handle this in the android runtime + // XXX(@jwerle): we handle this in the android runtime #if !SSC_PLATFORM_ANDROID const auto extname = Path(request.pathname).extension().string(); auto html = (message.buffer.bytes != nullptr && message.buffer.size > 0) - ? String(response.buffer.bytes, response.buffer.size) + ? String(*response.buffer.bytes, response.buffer.size) : String(""); if ( @@ -427,7 +412,7 @@ namespace SSC { message.get("runtime-preload-injection") != "disabled" && ( message.get("runtime-preload-injection") == "always" || - (extname.ends_with("html") || contentType == "text/html") || + (extname.ends_with("html") || headers.get("content-type").value.string == "text/html") || (html.find("(new char[html.size()]{0}); response.buffer.size = html.size(); - freeResponseBody = true; - memcpy(response.buffer.bytes, html.c_str(), html.size()); + memcpy(*response.buffer.bytes, html.c_str(), html.size()); } #endif @@ -487,10 +471,6 @@ namespace SSC { reply(IPC::Result { message.seq, message }); - if (freeResponseBody) { - delete response.buffer.bytes; - } - do { Lock lock(this->mutex); this->fetchCallbacks.erase(id); @@ -670,26 +650,13 @@ namespace SSC { return false; } - for (const auto& entry : request.headers) { - const auto parts = split(trim(entry), ':'); - if (parts.size() == 2) { - const auto key = trim(parts[0]); - const auto value = trim(parts[1]); - - if ( - (key == "Runtime-ServiceWorker-Fetch-Mode" && value == "ignore") || - (key == "runtime-serviceworker-fetch-mode" && value == "ignore") - ) { - return false; - } + if (request.headers.get("runtime-serviceworker-fetch-mode") == "ignore") { + return false; + } - if ( - (key == "Runtime-Worker-Type" && value == "serviceworker") || - (key == "runtime-worker-type" && value == "serviceworker") - ) { - return false; - } - } + // TODO(@jwerle): this prevents nested service worker fetches - do we want to prevent this? + if (request.headers.get("runtime-worker-type") == "serviceworker") { + return false; } for (const auto& entry : this->registrations) { @@ -748,12 +715,6 @@ namespace SSC { return true; } - auto headers = JSON::Array {}; - - for (const auto& header : request.headers) { - headers.push(header); - } - const auto id = rand64(); const auto client = JSON::Object::Entries { {"id", std::to_string(request.client.id)} @@ -766,11 +727,11 @@ namespace SSC { const auto fetch = JSON::Object::Entries { {"id", std::to_string(id)}, {"method", request.method}, + {"host", request.hostname}, {"scheme", request.scheme}, - {"host", request.host}, {"pathname", pathname}, {"query", request.query}, - {"headers", headers}, + {"headers", request.headers.json()}, {"client", client} }; diff --git a/src/core/service_worker_container.hh b/src/core/service_worker_container.hh index 94d30449e0..cb06aad458 100644 --- a/src/core/service_worker_container.hh +++ b/src/core/service_worker_container.hh @@ -2,6 +2,7 @@ #define SSC_SERVICE_WORKER_CONTAINER_H #include "platform.hh" +#include "headers.hh" #include "types.hh" #include "json.hh" @@ -63,7 +64,6 @@ namespace SSC { const RegistrationOptions& options ); - // les 5 Registration (const Registration&); Registration (Registration&&); ~Registration () = default; @@ -79,16 +79,16 @@ namespace SSC { struct FetchBuffer { size_t size = 0; - char* bytes = nullptr; + SharedPointer bytes = nullptr; }; struct FetchRequest { String method; String scheme = "*"; - String host = ""; + String hostname = ""; String pathname; String query; - Vector headers; + Headers headers; FetchBuffer buffer; Client client; @@ -98,7 +98,7 @@ namespace SSC { struct FetchResponse { uint64_t id = 0; int statusCode = 200; - Vector headers; + Headers headers; FetchBuffer buffer; Client client; }; diff --git a/src/core/string.cc b/src/core/string.cc index 4cd553e340..a75625824f 100644 --- a/src/core/string.cc +++ b/src/core/string.cc @@ -17,9 +17,9 @@ namespace SSC { } String tmpl (const String& source, const Map& variables) { - String output = source; + String output(source); - for (const auto tuple : variables) { + for (const auto& tuple : variables) { auto key = String("[{]+(" + tuple.first + ")[}]+"); auto value = tuple.second; output = std::regex_replace(output, std::regex(key), value); @@ -93,6 +93,28 @@ namespace SSC { return source; } + String toLowerCase (const String& source) { + String output = source; + std::transform( + output.begin(), + output.end(), + output.begin(), + [](auto ch) { return std::tolower(ch); } + ); + return output; + } + + String toUpperCase (const String& source) { + String output = source; + std::transform( + output.begin(), + output.end(), + output.begin(), + [](auto ch) { return std::toupper(ch); } + ); + return output; + } + WString convertStringToWString (const String& source) { WString result(source.length(), L' '); std::copy(source.begin(), source.end(), result.begin()); @@ -151,4 +173,32 @@ namespace SSC { Vector parseStringList (const String& string) { return parseStringList(string, { ' ', ',' }); } + +#if SSC_PLATFORM_WINDOWS + String formatWindowsError (DWORD error, const String& source) { + StringStream message; + LPVOID errorMessage; + + // format + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &errorMessage, + 0, + nullptr + ); + + // create output string + message + << "Error " << error + << " in " << source + << ": " << (LPTSTR) errorMessage; + + LocalFree(lpMsgBuf); + + return message.str(); + } +#endif } diff --git a/src/core/string.hh b/src/core/string.hh index 40f26fb960..f7e236b49c 100644 --- a/src/core/string.hh +++ b/src/core/string.hh @@ -17,6 +17,8 @@ namespace SSC { String replace (const String& source, const std::regex& regex, const String& value); String tmpl (const String& source, const Map& variables); String trim (String source); + String toLowerCase (const String& source); + String toUpperCase (const String& source); // conversion WString convertStringToWString (const String& source); @@ -33,6 +35,10 @@ namespace SSC { Vector parseStringList (const String& string, const Vector& separators); Vector parseStringList (const String& string, const char separator); Vector parseStringList (const String& string); + +#if SSC_PLATFORM_WINDOWS + String formatWindowsError (DWORD error, const String& source); +#endif } #endif diff --git a/src/core/timers.cc b/src/core/timers.cc index 2765986b62..d825440ddf 100644 --- a/src/core/timers.cc +++ b/src/core/timers.cc @@ -1,6 +1,11 @@ #include "core.hh" namespace SSC { + void msleep (uint64_t ms) { + std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + } + const Core::Timers::ID Core::Timers::createTimer (uint64_t timeout, uint64_t interval, const Callback callback) { Lock lock(this->mutex); diff --git a/src/core/types.hh b/src/core/types.hh index c44d216592..04e20ef1e7 100644 --- a/src/core/types.hh +++ b/src/core/types.hh @@ -22,14 +22,15 @@ namespace SSC { namespace fs = std::filesystem; using AtomicBool = std::atomic; + using AtomicInt = std::atomic; using String = std::string; using StringStream = std::stringstream; using WString = std::wstring; using WStringStream = std::wstringstream; - using Map = std::map; using Mutex = std::recursive_mutex; - using Path = fs::path; using Lock = std::lock_guard; + using Map = std::map; + using Path = fs::path; using Thread = std::thread; using Exception = std::exception; @@ -39,7 +40,9 @@ namespace SSC { template using Vector = std::vector; template using Function = std::function; template using Tuple = std::tuple; - template using Set = std::set; + template using Set = std::set; + template using SharedPointer = std::shared_ptr; + template using UniquePointer = std::unique_ptr; using ExitCallback = Function; using MessageCallback = Function; diff --git a/src/core/udp.cc b/src/core/udp.cc index e6d4768332..9c67213f36 100644 --- a/src/core/udp.cc +++ b/src/core/udp.cc @@ -447,7 +447,7 @@ namespace SSC { }}; post.id = rand64(); - post.body = buf->base; + post.body = std::make_shared(buf->base); post.length = (int) nread; post.headers = headers.str(); diff --git a/src/extension/extension.cc b/src/extension/extension.cc index 3d1d01c499..a9b6f41496 100644 --- a/src/extension/extension.cc +++ b/src/extension/extension.cc @@ -5,40 +5,6 @@ namespace SSC { static Vector initializedExtensions; static Mutex mutex; - static String getcwd () { - String cwd; - #if defined(__linux__) && !defined(__ANDROID__) - try { - auto canonical = fs::canonical("/proc/self/exe"); - cwd = fs::path(canonical).parent_path().string(); - } catch (...) {} - #elif defined(__APPLE__) && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - auto fileManager = [NSFileManager defaultManager]; - auto currentDirectory = [fileManager currentDirectoryPath]; - cwd = String([currentDirectory UTF8String]); - #elif defined(__APPLE__) - NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; - cwd = String([[resourcePath stringByAppendingPathComponent: @"ui"] UTF8String]); - - #elif defined(_WIN32) - wchar_t filename[MAX_PATH]; - GetModuleFileNameW(NULL, filename, MAX_PATH); - auto path = fs::path { filename }.remove_filename(); - cwd = path.string(); - size_t last_pos = 0; - while ((last_pos = cwd.find('\\', last_pos)) != std::string::npos) { - cwd.replace(last_pos, 1, "\\\\"); - last_pos += 2; - } - #endif - - #ifndef _WIN32 - std::replace(cwd.begin(), cwd.end(), '\\', '/'); - #endif - - return cwd; - } - // explicit template instantiations template char* SSC::Extension::Context::Memory::alloc (size_t); template sapi_context_t* diff --git a/src/extension/ipc.cc b/src/extension/ipc.cc index f38321c087..801c393620 100644 --- a/src/extension/ipc.cc +++ b/src/extension/ipc.cc @@ -234,12 +234,15 @@ bool sapi_ipc_send_bytes ( auto post = SSC::Post { .id = 0, .ttl = 0, - .body = new char[size]{0}, + .body = nullptr, .length = size, .headers = headers ? headers : "" }; - memcpy(post.body, bytes, size); + if (bytes != nullptr && size > 0) { + post.body = std::make_shared(new char[size]{0}); + memcpy(*post.body, bytes, size); + } if (message) { auto result = SSC::IPC::Result( @@ -270,12 +273,15 @@ bool sapi_ipc_send_bytes_with_result ( auto post = SSC::Post { .id = 0, .ttl = 0, - .body = new char[size]{0}, + .body = nullptr, .length = size, - .headers =headers ? headers : "" + .headers = headers ? headers : "" }; - memcpy(post.body, bytes, size); + if (bytes != nullptr && size > 0) { + post.body = std::make_shared(new char[size]{0}); + memcpy(*post.body, bytes, size); + } return ctx->router->send(result->seq, result->str(), post); } @@ -487,7 +493,7 @@ const unsigned char* sapi_ipc_message_get_bytes ( const sapi_ipc_message_t* message ) { if (!message) return nullptr; - return reinterpret_cast(message->buffer.bytes); + return reinterpret_cast(*message->buffer.bytes); } unsigned int sapi_ipc_message_get_bytes_size ( @@ -652,9 +658,9 @@ void sapi_ipc_result_set_bytes ( unsigned char* bytes ) { if (result && size && bytes) { - auto pointer = const_cast(reinterpret_cast(bytes)); result->post.length = size; - result->post.body = pointer; + result->post.body = std::make_shared(new char[size]{0}); + memcpy(*result->post.body, bytes, size); } } @@ -662,7 +668,7 @@ unsigned char* sapi_ipc_result_get_bytes ( const sapi_ipc_result_t* result ) { return result - ? reinterpret_cast(result->post.body) + ? reinterpret_cast(*result->post.body) : nullptr; } diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 80bc9b4535..b1cd4c63f7 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -61,6047 +61,300 @@ static const Vector allowedNodeCoreModules = { "worker_threads" }; -#if defined(__APPLE__) -static std::map notificationRouterMap; -static Mutex notificationRouterMapMutex; - -static dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class( - DISPATCH_QUEUE_CONCURRENT, - QOS_CLASS_USER_INITIATED, - -1 -); - -static dispatch_queue_t queue = dispatch_queue_create( - "socket.runtime.ipc.bridge.queue", - qos -); -#endif +static void registerSchemeHandler (Router *router) { + static const auto MAX_BODY_BYTES = 4 * 1024 * 1024; + static const auto devHost = SSC::getDevHost(); + static Atomic isInitialized = false; -static JSON::Any validateMessageParameters ( - const Message& message, - const Vector names -) { - for (const auto& name : names) { - if (!message.has(name) || message.get(name).size() == 0) { - return JSON::Object::Entries { - {"message", "Expecting '" + name + "' in parameters"} - }; - } + if (isInitialized) { + return; } - return nullptr; -} - -static struct { Mutex mutex; String value = ""; } cwdstate; - -static void setcwd (String cwd) { - Lock lock(cwdstate.mutex); - cwdstate.value = cwd; -} + isInitialized = true; -static String getcwd () { - Lock lock(cwdstate.mutex); - String cwd = cwdstate.value; #if defined(__linux__) && !defined(__ANDROID__) - try { - auto canonical = fs::canonical("/proc/self/exe"); - cwd = fs::path(canonical).parent_path().string(); - } catch (...) {} -#elif defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) - NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; - cwd = String([[resourcePath stringByAppendingPathComponent: @"ui"] UTF8String]); -#elif defined(__APPLE__) - auto fileManager = [NSFileManager defaultManager]; - auto currentDirectory = [fileManager currentDirectoryPath]; - cwd = String([currentDirectory UTF8String]); -#elif defined(_WIN32) - wchar_t filename[MAX_PATH]; - GetModuleFileNameW(NULL, filename, MAX_PATH); - auto path = fs::path { filename }.remove_filename(); - cwd = path.string(); - size_t last_pos = 0; - while ((last_pos = cwd.find('\\', last_pos)) != std::string::npos) { - cwd.replace(last_pos, 1, "\\\\"); - last_pos += 2; - } -#endif - -#ifndef _WIN32 - std::replace(cwd.begin(), cwd.end(), '\\', '/'); -#endif + auto ctx = webkit_web_context_get_default(); + auto security = webkit_web_context_get_security_manager(ctx); - cwdstate.value = cwd; - return cwd; -} + webkit_web_context_register_uri_scheme(ctx, "ipc", [](auto request, auto ptr) { + IPC::Router* router = nullptr; -#define RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) \ - [message, reply](auto seq, auto json, auto post) { \ - reply(Result { seq, message, json, post }); \ - } + auto webview = webkit_uri_scheme_request_get_web_view(request); + auto windowManager = App::instance()->getWindowManager(); -#define REQUIRE_AND_GET_MESSAGE_VALUE(var, name, parse, ...) \ - try { \ - var = parse(message.get(name, ##__VA_ARGS__)); \ - } catch (...) { \ - return reply(Result::Err { message, JSON::Object::Entries { \ - {"message", "Invalid '" name "' given in parameters"} \ - }}); \ - } + for (auto& window : windowManager->windows) { + if ( + window != nullptr && + window->bridge != nullptr && + WEBKIT_WEB_VIEW(window->webview) == webview + ) { + router = &window->bridge->router; + break; + } + } -#define CLEANUP_AFTER_INVOKE_CALLBACK(router, message, result) { \ - if (!router->hasMappedBuffer(message.index, message.seq)) { \ - if (message.buffer.bytes != nullptr) { \ - delete [] message.buffer.bytes; \ - message.buffer.bytes = nullptr; \ - } \ - } \ - \ - if (!router->core->hasPostBody(result.post.body)) { \ - if (result.post.body != nullptr) { \ - delete [] result.post.body; \ - } \ - } \ -} + auto uri = String(webkit_uri_scheme_request_get_uri(request)); + auto method = String(webkit_uri_scheme_request_get_http_method(request)); + auto message = IPC::Message{ uri }; + char bytes[MAX_BODY_BYTES]{0}; -static void initRouterTable (Router *router) { - auto userConfig = router->bridge->userConfig; -#if defined(__APPLE__) - auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - auto SSC_OS_LOG_BUNDLE = os_log_create(bundleIdentifier.c_str(), - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - "socket.runtime.mobile" - #else - "socket.runtime.desktop" - #endif - ); -#endif + if ((method == "POST" || method == "PUT")) { + auto body = webkit_uri_scheme_request_get_http_body(request); + if (body) { + GError* error = nullptr; + message.buffer.bytes = new char[MAX_BODY_BYTES]{0}; - /** - * Starts a bluetooth service - * @param serviceId - */ - router->map("bluetooth.start", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"serviceId"}); + const auto success = g_input_stream_read_all( + body, + message.buffer.bytes, + MAX_BODY_BYTES, + &message.buffer.size, + nullptr, + &error + ); - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); + if (!success) { + delete message.buffer.bytes; + webkit_uri_scheme_request_finish_error( + request, + error + ); + return; + } + } } - if (router->bridge->userConfig["permissions_allow_bluetooth"] == "false") { - auto err =JSON::Object::Entries { - {"message", "Bluetooth is not allowed"} - }; + auto invoked = router->invoke(message, message.buffer.bytes, message.buffer.size, [=](auto result) { + auto json = result.str(); + auto size = result.post.body != nullptr ? result.post.length : json.size(); + auto body = result.post.body != nullptr ? result.post.body : json.c_str(); - return reply(Result::Err { message, err }); - } + char* data = nullptr; - router->bridge->bluetooth.startService( - message.seq, - message.get("serviceId"), - [reply, message](auto seq, auto json) { - reply(Result { seq, message, json }); + if (size > 0) { + data = new char[size]{0}; + memcpy(data, body, size); } - ); - }); - - /** - * Subscribes to a characteristic for a service. - * @param serviceId - * @param characteristicId - */ - router->map("bluetooth.subscribe", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, { - "characteristicId", - "serviceId" - }); - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - if (router->bridge->userConfig["permissions_allow_bluetooth"] == "false") { - auto err =JSON::Object::Entries { - {"message", "Bluetooth is not allowed"} - }; + auto stream = g_memory_input_stream_new_from_data(data, size, nullptr); + auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); + auto response = webkit_uri_scheme_response_new(stream, size); - return reply(Result::Err { message, err }); - } + soup_message_headers_append(headers, "cache-control", "no-cache"); + for (const auto& header : result.headers.entries) { + soup_message_headers_append(headers, header.key.c_str(), header.value.c_str()); + } - router->bridge->bluetooth.subscribeCharacteristic( - message.seq, - message.get("serviceId"), - message.get("characteristicId"), - [reply, message](auto seq, auto json) { - reply(Result { seq, message, json }); + if (result.post.body) { + webkit_uri_scheme_response_set_content_type(response, IPC_BINARY_CONTENT_TYPE); + } else { + webkit_uri_scheme_response_set_content_type(response, IPC_JSON_CONTENT_TYPE); } - ); - }); - - /** - * Publishes data to a characteristic for a service. - * @param serviceId - * @param characteristicId - */ - router->map("bluetooth.publish", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, { - "characteristicId", - "serviceId" - }); - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } + webkit_uri_scheme_request_finish_with_response(request, response); + g_input_stream_close_async(stream, 0, nullptr, +[]( + GObject* object, + GAsyncResult* asyncResult, + gpointer userData + ) { + auto stream = (GInputStream*) object; + g_input_stream_close_finish(stream, asyncResult, nullptr); + g_object_unref(stream); + g_idle_add_full( + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) [](gpointer userData) { + return G_SOURCE_REMOVE; + }, + userData, + [](gpointer userData) { + delete [] static_cast(userData); + } + ); + }, data); + }); - if (router->bridge->userConfig["permissions_allow_bluetooth"] == "false") { - auto err =JSON::Object::Entries { - {"message", "Bluetooth is not allowed"} + if (!invoked) { + auto err = JSON::Object::Entries { + {"source", uri}, + {"err", JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"}, + {"url", uri} + }} }; - return reply(Result::Err { message, err }); - } - - auto bytes = message.buffer.bytes; - auto size = message.buffer.size; + auto msg = JSON::Object(err).str(); + auto size = msg.size(); + auto bytes = msg.c_str(); + auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); + auto response = webkit_uri_scheme_response_new(stream, msg.size()); - if (bytes == nullptr) { - bytes = message.value.data(); - size = message.value.size(); + webkit_uri_scheme_response_set_status(response, 404, "Not found"); + webkit_uri_scheme_response_set_content_type(response, IPC_JSON_CONTENT_TYPE); + webkit_uri_scheme_request_finish_with_response(request, response); + g_object_unref(stream); } + }, + router, + 0); - router->bridge->bluetooth.publishCharacteristic( - message.seq, - bytes, - size, - message.get("serviceId"), - message.get("characteristicId"), - [reply, message](auto seq, auto json) { - reply(Result { seq, message, json }); - } - ); - }); - - /** - * Maps a message buffer bytes to an index + sequence. - * - * This setup allows us to push a byte array to the bridge and - * map it to an IPC call index and sequence pair, which is reused for an - * actual IPC call, subsequently. This is used for systems that do not support - * a POST/PUT body in XHR requests natively, so instead we decorate - * `message.buffer` with already an mapped buffer. - */ - router->map("buffer.map", false, [=](auto message, auto router, auto reply) { - router->setMappedBuffer(message.index, message.seq, message.buffer); - reply(Result { message.seq, message }); - }); - - /** - * Kills an already spawned child process. - * - * @param id - * @param signal - */ - router->map("child_process.kill", [=](auto message, auto router, auto reply) { - #if SSC_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; + webkit_web_context_register_uri_scheme(ctx, "socket", [](auto request, auto ptr) { + IPC::Router* router = nullptr; - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"id", "signal"}); + auto webview = webkit_uri_scheme_request_get_web_view(request); + auto windowManager = App::instance()->getWindowManager(); - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); + for (auto& window : windowManager->windows) { + if ( + window != nullptr && + window->bridge != nullptr && + WEBKIT_WEB_VIEW(window->webview) == webview + ) { + router = &window->bridge->router; + break; + } } - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - int signal; - REQUIRE_AND_GET_MESSAGE_VALUE(signal, "signal", std::stoi); - - router->core->childProcess.kill( - message.seq, - id, - signal, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - #endif - }); - - /** - * Spawns a child process - * - * @param id - * @param args (command, ...args) - */ - router->map("child_process.spawn", [=](auto message, auto router, auto reply) { - #if SSC_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; - - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"args", "id"}); + auto userConfig = router->bridge->userConfig; + bool isModule = false; + auto method = String(webkit_uri_scheme_request_get_http_method(request)); + auto uri = String(webkit_uri_scheme_request_get_uri(request)); + auto cwd = getcwd(); + uint64_t clientId = router->bridge->id; - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); + if (uri.starts_with("socket:///")) { + uri = uri.substr(10); + } else if (uri.starts_with("socket://")) { + uri = uri.substr(9); + } else if (uri.starts_with("socket:")) { + uri = uri.substr(7); } - auto args = split(message.get("args"), 0x0001); + const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + auto path = String( + uri.starts_with(bundleIdentifier) + ? uri.substr(bundleIdentifier.size()) + : "socket/" + uri + ); - if (args.size() == 0 || args.at(0).size() == 0) { - auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, - {"err", JSON::Object::Entries { - {"message", "Spawn requires at least one argument with a length greater than zero"}, - }} - }; + auto parsedPath = Router::parseURLComponents(path); + auto ext = fs::path(parsedPath.path).extension().string(); - return reply(Result { message.seq, message, json }); + if (ext.size() > 0 && !ext.starts_with(".")) { + ext = "." + ext; } - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + if (!uri.starts_with(bundleIdentifier)) { + path = parsedPath.path; + if (ext.size() == 0 && !path.ends_with(".js")) { + path += ".js"; + ext = ".js"; + } - const auto options = Core::ChildProcess::SpawnOptions { - .cwd = message.get("cwd", getcwd()), - .allowStdin = message.get("stdin") != "false", - .allowStdout = message.get("stdout") != "false", - .allowStderr = message.get("stderr") != "false" - }; + if (parsedPath.queryString.size() > 0) { + path += String("?") + parsedPath.queryString; + } - router->core->childProcess.spawn( - message.seq, - id, - args, - options, - [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); + if (parsedPath.fragment.size() > 0) { + path += String("#") + parsedPath.fragment; } - ); - #endif - }); - router->map("child_process.exec", [=](auto message, auto router, auto reply) { - #if SSC_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; + uri = "socket://" + bundleIdentifier + "/" + path; + auto moduleSource = trim(tmpl( + moduleTemplate, + Map { {"url", String(uri)} } + )); - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"args", "id"}); + auto size = moduleSource.size(); + auto bytes = moduleSource.data(); + auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); + if (stream) { + auto response = webkit_uri_scheme_response_new(stream, size); + webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); + webkit_uri_scheme_request_finish_with_response(request, response); + g_object_unref(stream); + } else { + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Failed to create response stream" + ) + ); + } + return; } - auto args = split(message.get("args"), 0x0001); + auto resolved = Router::resolveURLPathForWebView(parsedPath.path, cwd); + auto mount = Router::resolveNavigatorMountForWebView(parsedPath.path); + path = resolved.path; - if (args.size() == 0 || args.at(0).size() == 0) { - auto json = JSON::Object::Entries { - {"source", "child_process.exec"}, - {"err", JSON::Object::Entries { - {"message", "Spawn requires at least one argument with a length greater than zero"}, - }} - }; + if (mount.path.size() > 0) { + if (mount.resolution.redirect) { + auto redirectURL = resolved.path; + if (parsedPath.queryString.size() > 0) { + redirectURL += "?" + parsedPath.queryString; + } - return reply(Result { message.seq, message, json }); - } + if (parsedPath.fragment.size() > 0) { + redirectURL += "#" + parsedPath.fragment; + } - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + auto redirectSource = String( + "" + ); - uint64_t timeout = 0; - int killSignal = 0; + auto size = redirectSource.size(); + auto bytes = redirectSource.data(); + auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - if (message.has("timeout")) { - REQUIRE_AND_GET_MESSAGE_VALUE(timeout, "timeout", std::stoull); - } + if (stream) { + auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); + auto response = webkit_uri_scheme_response_new(stream, (gint64) size); + auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - if (message.has("killSignal")) { - REQUIRE_AND_GET_MESSAGE_VALUE(killSignal, "killSignal", std::stoi); - } + soup_message_headers_append(headers, "location", redirectURL.c_str()); + soup_message_headers_append(headers, "content-location", contentLocation.c_str()); - const auto options = Core::ChildProcess::ExecOptions { - .cwd = message.get("cwd", getcwd()), - .allowStdout = message.get("stdout") != "false", - .allowStderr = message.get("stderr") != "false", - .timeout = timeout, - .killSignal = killSignal - }; - - router->core->childProcess.exec( - message.seq, - id, - args, - options, - [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); - } - ); - #endif - }); - - /** - * Writes to an already spawned child process. - * - * @param id - */ - router->map("child_process.write", [=](auto message, auto router, auto reply) { - #if SSC_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; - - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->childProcess.write( - message.seq, - id, - message.buffer.bytes, - message.buffer.size, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - #endif - }); - - /** - * Look up an IP address by `hostname`. - * @param hostname Host name to lookup - * @param family IP address family to resolve [default = 0 (AF_UNSPEC)] - * @see getaddrinfo(3) - */ - router->map("dns.lookup", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"hostname"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int family = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(family, "family", std::stoi, "0"); - - router->core->dns.lookup( - message.seq, - Core::DNS::LookupOptions { message.get("hostname"), family }, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - router->map("extension.stats", [=](auto message, auto router, auto reply) { - auto extensions = Extension::all(); - auto name = message.get("name"); - - if (name.size() > 0) { - auto type = Extension::getExtensionType(name); - auto path = Extension::getExtensionPath(name); - auto json = JSON::Object::Entries { - {"source", "extension.stats"}, - {"data", JSON::Object::Entries { - {"abi", SOCKET_RUNTIME_EXTENSION_ABI_VERSION}, - {"name", name}, - {"type", type}, - // `path` is absolute to the location of the resources - {"path", String("/") + std::filesystem::relative(path, getcwd()).string()} - }} - }; - - reply(Result { message.seq, message, json }); - } else { - int loaded = 0; - - for (const auto& tuple : extensions) { - if (tuple.second != nullptr) { - loaded++; - } - } - - auto json = JSON::Object::Entries { - {"source", "extension.stats"}, - {"data", JSON::Object::Entries { - {"abi", SOCKET_RUNTIME_EXTENSION_ABI_VERSION}, - {"loaded", loaded} - }} - }; - - reply(Result { message.seq, message, json }); - } - }); - - /** - * Query for type of extension ('shared', 'wasm32', 'unknown') - * @param name - */ - router->map("extension.type", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"name"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto name = message.get("name"); - auto type = Extension::getExtensionType(name); - auto json = SSC::JSON::Object::Entries { - {"source", "extension.type"}, - {"data", JSON::Object::Entries { - {"name", name}, - {"type", type} - }} - }; - - reply(Result { message.seq, message, json }); - }); - - /** - * Load a named native extension. - * @param name - * @param allow - */ - router->map("extension.load", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"name"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto name = message.get("name"); - - if (!Extension::load(name)) { - #if defined(_WIN32) - auto error = FormatError(GetLastError(), "bridge"); - #else - auto err = dlerror(); - auto error = String(err ? err : "Unknown error"); - #endif - - std::cout << "Load extension error: " << error << std::endl; - - return reply(Result::Err { message, JSON::Object::Entries { - {"message", "Failed to load extension: '" + name + "': " + error} - }}); - } - - auto extension = Extension::get(name); - auto allowed = split(message.get("allow"), ','); - auto context = Extension::getContext(name); - auto ctx = context->memory.template alloc(context, router); - - for (const auto& value : allowed) { - auto policy = trim(value); - ctx->setPolicy(policy, true); - } - - Extension::setRouterContext(name, router, ctx); - - /// init context - if (!Extension::initialize(ctx, name, nullptr)) { - if (ctx->state == Extension::Context::State::Error) { - auto json = JSON::Object::Entries { - {"source", "extension.load"}, - {"extension", name}, - {"err", JSON::Object::Entries { - {"code", ctx->error.code}, - {"name", ctx->error.name}, - {"message", ctx->error.message}, - {"location", ctx->error.location}, - }} - }; - - reply(Result { message.seq, message, json }); - } else { - auto json = JSON::Object::Entries { - {"source", "extension.load"}, - {"extension", name}, - {"err", JSON::Object::Entries { - {"message", "Failed to initialize extension: '" + name + "'"}, - }} - }; - - reply(Result { message.seq, message, json }); - } - } else { - auto json = JSON::Object::Entries { - {"source", "extension.load"}, - {"data", JSON::Object::Entries { - {"abi", (uint64_t) extension->abi}, - {"name", extension->name}, - {"version", extension->version}, - {"description", extension->description} - }} - }; - - reply(Result { message.seq, message, json }); - } - }); - - /** - * Unload a named native extension. - * @param name - */ - router->map("extension.unload", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"name"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto name = message.get("name"); - - if (!Extension::isLoaded(name)) { - return reply(Result::Err { message, JSON::Object::Entries { - #if defined(_WIN32) - {"message", "Extension '" + name + "' is not loaded"} - #else - {"message", "Extension '" + name + "' is not loaded" + String(dlerror())} - #endif - }}); - } - - auto extension = Extension::get(name); - auto ctx = Extension::getRouterContext(name, router); - - if (Extension::unload(ctx, name, extension->contexts.size() == 1)) { - Extension::removeRouterContext(name, router); - auto json = JSON::Object::Entries { - {"source", "extension.unload"}, - {"extension", name}, - {"data", JSON::Object::Entries {}} - }; - return reply(Result { message.seq, message, json }); - } - - if (ctx->state == Extension::Context::State::Error) { - auto json = JSON::Object::Entries { - {"source", "extension.unload"}, - {"extension", name}, - {"err", JSON::Object::Entries { - {"code", ctx->error.code}, - {"name", ctx->error.name}, - {"message", ctx->error.message}, - {"location", ctx->error.location}, - }} - }; - - reply(Result { message.seq, message, json }); - } else { - auto json = JSON::Object::Entries { - {"source", "extension.unload"}, - {"extension", name}, - {"err", JSON::Object::Entries { - {"message", "Failed to unload extension: '" + name + "'"}, - }} - }; - - reply(Result { message.seq, message, json }); - } - }); - - /** - * Checks if current user can access file at `path` with `mode`. - * @param path - * @param mode - * @see access(2) - */ - router->map("fs.access", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path", "mode"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int mode = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); - - router->core->fs.access( - message.seq, - message.get("path"), - mode, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Returns a mapping of file system constants. - */ - router->map("fs.constants", [=](auto message, auto router, auto reply) { - router->core->fs.constants(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Changes `mode` of file at `path`. - * @param path - * @param mode - * @see chmod(2) - */ - router->map("fs.chmod", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path", "mode"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int mode = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); - - router->core->fs.chmod( - message.seq, - message.get("path"), - mode, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Changes uid and gid of file at `path`. - * @param path - * @param uid - * @param gid - * @see chown(2) - */ - router->map("fs.chown", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path", "uid", "gid"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int uid = 0; - int gid = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(uid, "uid", std::stoi); - REQUIRE_AND_GET_MESSAGE_VALUE(gid, "gid", std::stoi); - - router->core->fs.chown( - message.seq, - message.get("path"), - static_cast(uid), - static_cast(gid), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Changes uid and gid of symbolic link at `path`. - * @param path - * @param uid - * @param gid - * @see lchown(2) - */ - router->map("fs.lchown", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path", "uid", "gid"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int uid = 0; - int gid = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(uid, "uid", std::stoi); - REQUIRE_AND_GET_MESSAGE_VALUE(gid, "gid", std::stoi); - - router->core->fs.lchown( - message.seq, - message.get("path"), - static_cast(uid), - static_cast(gid), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Closes underlying file descriptor handle. - * @param id - * @see close(2) - */ - router->map("fs.close", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.close(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Closes underlying directory descriptor handle. - * @param id - * @see closedir(3) - */ - router->map("fs.closedir", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.closedir(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Closes an open file or directory descriptor handle. - * @param id - * @see close(2) - * @see closedir(3) - */ - router->map("fs.closeOpenDescriptor", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.closeOpenDescriptor( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Closes all open file and directory descriptors, optionally preserving - * explicitly retrained descriptors. - * @param preserveRetained (default: true) - * @see close(2) - * @see closedir(3) - */ - router->map("fs.closeOpenDescriptors", [=](auto message, auto router, auto reply) { - router->core->fs.closeOpenDescriptor( - message.seq, - message.get("preserveRetained") != "false", - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Copy file at path `src` to path `dest`. - * @param src - * @param dest - * @param flags - * @see copyfile(3) - */ - router->map("fs.copyFile", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"src", "dest", "flags"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int flags = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(flags, "flags", std::stoi); - - router->core->fs.copyFile( - message.seq, - message.get("src"), - message.get("dest"), - flags, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Creates a link at `dest` - * @param src - * @param dest - * @see link(2) - */ - router->map("fs.link", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"src", "dest"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.link( - message.seq, - message.get("src"), - message.get("dest"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Creates a symlink at `dest` - * @param src - * @param dest - * @param flags - * @see symlink(2) - */ - router->map("fs.symlink", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"src", "dest", "flags"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int flags = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(flags, "flags", std::stoi); - - router->core->fs.symlink( - message.seq, - message.get("src"), - message.get("dest"), - flags, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Computes stats for an open file descriptor. - * @param id - * @see stat(2) - * @see fstat(2) - */ - router->map("fs.fstat", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.fstat(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Synchronize a file's in-core state with storage device - * @param id - * @see fsync(2) - */ - router->map("fs.fsync", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.fsync( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Truncates opened file - * @param id - * @param offset - * @see ftruncate(2) - */ - router->map("fs.ftruncate", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "offset"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - int64_t offset; - REQUIRE_AND_GET_MESSAGE_VALUE(offset, "offset", std::stoll); - - router->core->fs.ftruncate( - message.seq, - id, - offset, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Returns all open file or directory descriptors. - */ - router->map("fs.getOpenDescriptors", [=](auto message, auto router, auto reply) { - router->core->fs.getOpenDescriptors( - message.seq, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Computes stats for a symbolic link at `path`. - * @param path - * @see stat(2) - * @see lstat(2) - */ - router->map("fs.lstat", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.lstat( - message.seq, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Creates a directory at `path` with an optional mode and an optional recursive flag. - * @param path - * @param mode - * @param recursive - * @see mkdir(2) - */ - router->map("fs.mkdir", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path", "mode"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int mode = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); - - router->core->fs.mkdir( - message.seq, - message.get("path"), - mode, - message.get("recursive") == "true", - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - - /** - * Opens a file descriptor at `path` for `id` with `flags` and `mode` - * @param id - * @param path - * @param flags - * @param mode - * @see open(2) - */ - router->map("fs.open", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, { - "id", - "path", - "flags", - "mode" - }); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - int mode = 0; - int flags = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); - REQUIRE_AND_GET_MESSAGE_VALUE(flags, "flags", std::stoi); - - router->core->fs.open( - message.seq, - id, - message.get("path"), - flags, - mode, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Opens a directory descriptor at `path` for `id` with `flags` and `mode` - * @param id - * @param path - * @see opendir(3) - */ - router->map("fs.opendir", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.opendir( - message.seq, - id, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Reads `size` bytes at `offset` from the underlying file descriptor. - * @param id - * @param size - * @param offset - * @see read(2) - */ - router->map("fs.read", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "size", "offset"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - int size = 0; - int offset = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(size, "size", std::stoi); - REQUIRE_AND_GET_MESSAGE_VALUE(offset, "offset", std::stoi); - - router->core->fs.read( - message.seq, - id, - size, - offset, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Reads next `entries` of from the underlying directory descriptor. - * @param id - * @param entries (default: 256) - */ - router->map("fs.readdir", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - int entries = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(entries, "entries", std::stoi); - - router->core->fs.readdir( - message.seq, - id, - entries, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Read value of a symbolic link at 'path' - * @param path - * @see readlink(2) - */ - router->map("fs.readlink", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.readlink( - message.seq, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Get the realpath at 'path' - * @param path - * @see realpath(2) - */ - router->map("fs.realpath", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.realpath( - message.seq, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Marks a file or directory descriptor as retained. - * @param id - */ - router->map("fs.retainOpenDescriptor", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.retainOpenDescriptor( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Renames file at path `src` to path `dest`. - * @param src - * @param dest - * @see rename(2) - */ - router->map("fs.rename", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"src", "dest"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.rename( - message.seq, - message.get("src"), - message.get("dest"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Removes file at `path`. - * @param path - * @see rmdir(2) - */ - router->map("fs.rmdir", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.rmdir( - message.seq, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Computes stats for a file at `path`. - * @param path - * @see stat(2) - */ - router->map("fs.stat", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.stat( - message.seq, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Stops a already started watcher - */ - router->map("fs.stopWatch", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.watch( - message.seq, - id, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Removes a file or empty directory at `path`. - * @param path - * @see unlink(2) - */ - router->map("fs.unlink", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - router->core->fs.unlink( - message.seq, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * TODO - */ - router->map("fs.watch", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "path"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->fs.watch( - message.seq, - id, - message.get("path"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Writes buffer at `message.buffer.bytes` of size `message.buffers.size` - * at `offset` for an opened file handle. - * @param id Handle ID for an open file descriptor - * @param offset The offset to start writing at - * @see write(2) - */ - router->map("fs.write", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "offset"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - if (message.buffer.bytes == nullptr || message.buffer.size == 0) { - auto err = JSON::Object::Entries {{ "message", "Missing buffer in message" }}; - return reply(Result::Err { message, err }); - } - - - uint64_t id; - int offset = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(offset, "offset", std::stoi); - - router->core->fs.write( - message.seq, - id, - message.buffer.bytes, - message.buffer.size, - offset, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - -#if defined(__APPLE__) - router->map("geolocation.getCurrentPosition", [=](auto message, auto router, auto reply) { - if (!router->locationObserver) { - auto err = JSON::Object::Entries {{ "message", "Location observer is not initialized", }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - auto performedActivation = [router->locationObserver getCurrentPositionWithCompletion: ^(NSError* error, CLLocation* location) { - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - auto err = JSON::Object::Entries {{ "message", message }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - auto heading = router->locationObserver.locationManager.heading; - auto json = JSON::Object::Entries { - {"coords", JSON::Object::Entries { - {"latitude", location.coordinate.latitude}, - {"longitude", location.coordinate.longitude}, - {"altitude", location.altitude}, - {"accuracy", location.horizontalAccuracy}, - {"altitudeAccuracy", location.verticalAccuracy}, - {"floorLevel", location.floor.level}, - {"heading", heading.trueHeading}, - {"speed", location.speed} - }} - }; - - reply(Result { message.seq, message, json }); - }]; - - if (!performedActivation) { - auto err = JSON::Object::Entries {{ "message", "Location observer could not be activated" }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - }); - - router->map("geolocation.watchPosition", [=](auto message, auto router, auto reply) { - if (!router->locationObserver) { - auto err = JSON::Object::Entries {{ "message", "Location observer is not initialized", }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int id = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoi); - - const int identifier = [router->locationObserver watchPositionForIdentifier: id completion: ^(NSError* error, CLLocation* location) { - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - auto err = JSON::Object::Entries {{ "message", message }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - auto heading = router->locationObserver.locationManager.heading; - auto json = JSON::Object::Entries { - {"watch", JSON::Object::Entries { - {"identifier", identifier}, - }}, - {"coords", JSON::Object::Entries { - {"latitude", location.coordinate.latitude}, - {"longitude", location.coordinate.longitude}, - {"altitude", location.altitude}, - {"accuracy", location.horizontalAccuracy}, - {"altitudeAccuracy", location.verticalAccuracy}, - {"floorLevel", location.floor.level}, - {"heading", heading.trueHeading}, - {"speed", location.speed} - }} - }; - - reply(Result { "-1", message, json }); - }]; - - if (identifier == -1) { - auto err = JSON::Object::Entries {{ "message", "Location observer could not be activated" }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - auto json = JSON::Object::Entries { - {"watch", JSON::Object::Entries { - {"identifier", identifier} - }} - }; - - reply(Result { message.seq, message, json }); - }); - - router->map("geolocation.clearWatch", [=](auto message, auto router, auto reply) { - if (!router->locationObserver) { - auto err = JSON::Object::Entries {{ "message", "Location observer is not initialized", }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - int id = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoi); - - [router->locationObserver clearWatch: id]; - - reply(Result { message.seq, message, JSON::Object{} }); - }); -#endif - - /** - * A private API for artifically setting the current cached CWD value. - * This is only useful on platforms that need to set this value from an - * external source, like Android or ChromeOS. - */ - router->map("internal.setcwd", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"value"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - setcwd(message.value); - reply(Result { message.seq, message, JSON::Object{} }); - }); - - /** - * Log `value to stdout` with platform dependent logger. - * @param value - */ - router->map("log", [=](auto message, auto router, auto reply) { - auto value = message.value.c_str(); - #if defined(__APPLE__) - NSLog(@"%s", value); - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", value); - #elif defined(__ANDROID__) - __android_log_print(ANDROID_LOG_DEBUG, "", "%s", value); - #else - printf("%s\n", value); - #endif - }); - -#if defined(__APPLE__) - router->map("notification.show", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, { - "id", - "title" - }); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - auto attachments = [NSMutableArray new]; - auto userInfo = [NSMutableDictionary new]; - auto content = [UNMutableNotificationContent new]; - auto __block id = message.get("id"); - - if (message.has("tag")) { - userInfo[@"tag"] = @(message.get("tag").c_str()); - content.threadIdentifier = @(message.get("tag").c_str()); - } - - if (message.has("lang")) { - userInfo[@"lang"] = @(message.get("lang").c_str()); - } - - if (!message.has("silent") && message.get("silent") == "false") { - content.sound = [UNNotificationSound defaultSound]; - } - - if (message.has("icon")) { - NSError* error = nullptr; - auto url = [NSURL URLWithString: @(message.get("icon").c_str())]; - - if (message.get("icon").starts_with("socket://")) { - url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", url.path.UTF8String - #else - stringWithFormat: @"/%s", url.path.UTF8String - #endif - ] - ]]; - - url = [NSURL fileURLWithPath: url.path]; - } - - auto types = [UTType - typesWithTag: url.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary new]; - - if (types.count > 0) { - options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; - }; - - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: url - options: options - error: &error - ]; - - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { { "message", message } }; - return reply(Result::Err { message, err }); - } - - [attachments addObject: attachment]; - } else { - // using an asset from the resources directory will require a code signed application - #if WAS_CODESIGNED - NSError* error = nullptr; - auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/icon.png" - #else - stringWithFormat: @"/icon.png" - #endif - ] - ]]; - - url = [NSURL fileURLWithPath: url.path]; - - auto types = [UTType - typesWithTag: url.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary new]; - - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: url - options: options - error: &error - ]; - - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { { "message", message } }; - - return reply(Result::Err { message, err }); - } - - [attachments addObject: attachment]; - #endif - } - - if (message.has("image")) { - NSError* error = nullptr; - auto url = [NSURL URLWithString: @(message.get("image").c_str())]; - - if (message.get("image").starts_with("socket://")) { - url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", url.path.UTF8String - #else - stringWithFormat: @"/%s", url.path.UTF8String - #endif - ] - ]]; - - url = [NSURL fileURLWithPath: url.path]; - } - - auto types = [UTType - typesWithTag: url.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary new]; - - if (types.count > 0) { - options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; - }; - - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: url - options: options - error: &error - ]; - - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - auto err = JSON::Object::Entries {{ "message", message }}; - - return reply(Result::Err { message, err }); - } - - [attachments addObject: attachment]; - } - - content.attachments = attachments; - content.userInfo = userInfo; - content.title = @(message.get("title").c_str()); - content.body = @(message.get("body", "").c_str()); - - auto request = [UNNotificationRequest - requestWithIdentifier: @(id.c_str()) - content: content - trigger: nil - ]; - - { - Lock lock(notificationRouterMapMutex); - notificationRouterMap.insert_or_assign(id, router); - } - - [notificationCenter addNotificationRequest: request withCompletionHandler: ^(NSError* error) { - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { - { "message", message } - }; - - reply(Result::Err { message, err }); - Lock lock(notificationRouterMapMutex); - notificationRouterMap.erase(id); - return; - } - - reply(Result { message.seq, message, JSON::Object::Entries { - {"id", request.identifier.UTF8String} - }}); - }]; - }); - - router->map("notification.close", [=](auto message, auto router, auto reply) { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - auto err = validateMessageParameters(message, { "id" }); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto id = message.get("id"); - auto identifiers = @[@(id.c_str())]; - - [notificationCenter removePendingNotificationRequestsWithIdentifiers: identifiers]; - [notificationCenter removeDeliveredNotificationsWithIdentifiers: identifiers]; - - reply(Result { message.seq, message, JSON::Object::Entries { - {"id", id} - }}); - - Lock lock(notificationRouterMapMutex); - if (notificationRouterMap.contains(id)) { - auto notificationRouter = notificationRouterMap.at(id); - JSON::Object json = JSON::Object::Entries { - {"id", id}, - {"action", "dismiss"} - }; - - notificationRouter->emit("notificationresponse", json.str()); - notificationRouterMap.erase(id); - } - }); - - router->map("notification.list", [=](auto message, auto router, auto reply) { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [notificationCenter getDeliveredNotificationsWithCompletionHandler: ^(NSArray *notifications) { - JSON::Array::Entries entries; - - Lock lock(notificationRouterMapMutex); - for (UNNotification* notification in notifications) { - auto id = String(notification.request.identifier.UTF8String); - - if ( - !notificationRouterMap.contains(id) || - notificationRouterMap.at(id) != router - ) { - continue; - } - - entries.push_back(JSON::Object::Entries { - {"id", id} - }); - } - - reply(Result { message.seq, message, entries }); - }]; - }); -#endif - - /** - * Read or modify the `SEND_BUFFER` or `RECV_BUFFER` for a peer socket. - * @param id Handle ID for the buffer to read/modify - * @param size If given, the size to set in the buffer [default = 0] - * @param buffer The buffer to read/modify (SEND_BUFFER, RECV_BUFFER) [default = 0 (SEND_BUFFER)] - */ - router->map("os.bufferSize", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - int buffer = 0; - int size = 0; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(buffer, "buffer", std::stoi, "0"); - REQUIRE_AND_GET_MESSAGE_VALUE(size, "size", std::stoi, "0"); - - router->core->os.bufferSize( - message.seq, - id, - size, - buffer, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Returns a mapping of operating system constants. - */ - router->map("os.constants", [=](auto message, auto router, auto reply) { - router->core->os.constants(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Returns a mapping of network interfaces. - */ - router->map("os.networkInterfaces", [=](auto message, auto router, auto reply) { - router->core->os.networkInterfaces(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Returns an array of CPUs available to the process. - */ - router->map("os.cpus", [=](auto message, auto router, auto reply) { - router->core->os.cpus(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - router->map("os.rusage", [=](auto message, auto router, auto reply) { - router->core->os.rusage(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - router->map("os.uptime", [=](auto message, auto router, auto reply) { - router->core->os.uptime(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - router->map("os.uname", [=](auto message, auto router, auto reply) { - router->core->os.uname(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - router->map("os.hrtime", [=](auto message, auto router, auto reply) { - router->core->os.hrtime(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - router->map("os.availableMemory", [=](auto message, auto router, auto reply) { - router->core->os.availableMemory(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - router->map("os.paths", [=](auto message, auto router, auto reply) { - const auto bundleIdentifier = router->bridge->userConfig["meta_bundle_identifier"]; - - JSON::Object json; - - // paths - String resources = getcwd(); - String downloads; - String documents; - String pictures; - String desktop; - String videos; - String config; - String music; - String home; - String data; - String log; - - #if defined(__APPLE__) - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - - static const auto fileManager = NSFileManager.defaultManager; - - #define DIRECTORY_PATH_FROM_FILE_MANAGER(type) ( \ - String([fileManager \ - URLForDirectory: type \ - inDomain: NSUserDomainMask \ - appropriateForURL: nil \ - create: NO \ - error: nil \ - ].path.UTF8String) \ - ) - - // overload with main bundle resources path for macos/ios - resources = String(NSBundle.mainBundle.resourcePath.UTF8String); - downloads = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDownloadsDirectory); - documents = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDocumentDirectory); - pictures = DIRECTORY_PATH_FROM_FILE_MANAGER(NSPicturesDirectory); - desktop = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDesktopDirectory); - videos = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory); - music = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory); - config = HOME + "/Library/Application Support/" + bundleIdentifier; - home = String(NSHomeDirectory().UTF8String); - data = HOME + "/Library/Application Support/" + bundleIdentifier; - log = HOME + "/Library/Logs/" + bundleIdentifier; - - #undef DIRECTORY_PATH_FROM_FILE_MANAGER - - #elif defined(__linux__) - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - - static const auto XDG_DOCUMENTS_DIR = Env::get("XDG_DOCUMENTS_DIR"); - static const auto XDG_DOWNLOAD_DIR = Env::get("XDG_DOWNLOAD_DIR"); - static const auto XDG_PICTURES_DIR = Env::get("XDG_PICTURES_DIR"); - static const auto XDG_DESKTOP_DIR = Env::get("XDG_DESKTOP_DIR"); - static const auto XDG_VIDEOS_DIR = Env::get("XDG_VIDEOS_DIR"); - static const auto XDG_MUSIC_DIR = Env::get("XDG_MUSIC_DIR"); - - static const auto XDG_CONFIG_HOME = Env::get("XDG_CONFIG_HOME", HOME + "/.config"); - static const auto XDG_DATA_HOME = Env::get("XDG_DATA_HOME", HOME + "/.local/share"); - - if (XDG_DOCUMENTS_DIR.size() > 0) { - documents = XDG_DOCUMENTS_DIR; - } else { - documents = (Path(HOME) / "Documents").string(); - } - - if (XDG_DOWNLOAD_DIR.size() > 0) { - downloads = XDG_DOWNLOAD_DIR; - } else { - downloads = (Path(HOME) / "Downloads").string(); - } - - if (XDG_DESKTOP_DIR.size() > 0) { - desktop = XDG_DESKTOP_DIR; - } else { - desktop = (Path(HOME) / "Desktop").string(); - } - - if (XDG_PICTURES_DIR.size() > 0) { - pictures = XDG_PICTURES_DIR; - } else if (fs::exists(Path(HOME) / "Images")) { - pictures = (Path(HOME) / "Images").string(); - } else if (fs::exists(Path(HOME) / "Photos")) { - pictures = (Path(HOME) / "Photos").string(); - } else { - pictures = (Path(HOME) / "Pictures").string(); - } - - if (XDG_VIDEOS_DIR.size() > 0) { - videos = XDG_VIDEOS_DIR; - } else { - videos = (Path(HOME) / "Videos").string(); - } - - if (XDG_MUSIC_DIR.size() > 0) { - music = XDG_MUSIC_DIR; - } else { - music = (Path(HOME) / "Music").string(); - } - - config = XDG_CONFIG_HOME + "/" + bundleIdentifier; - home = Path(HOME).string(); - data = XDG_DATA_HOME + "/" + bundleIdentifier; - log = config; - #elif defined(_WIN32) - static const auto HOME = Env::get("HOMEPATH", Env::get("HOME")); - static const auto USERPROFILE = Env::get("USERPROFILE", HOME); - downloads = (Path(USERPROFILE) / "Downloads").string(); - documents = (Path(USERPROFILE) / "Documents").string(); - desktop = (Path(USERPROFILE) / "Desktop").string(); - pictures = (Path(USERPROFILE) / "Pictures").string(); - videos = (Path(USERPROFILE) / "Videos").string(); - music = (Path(USERPROFILE) / "Music").string(); - config = (Path(Env::get("APPDATA")) / bundleIdentifier).string(); - home = Path(USERPROFILE).string(); - data = (Path(Env::get("APPDATA")) / bundleIdentifier).string(); - log = config; - #endif - - json["resources"] = resources; - json["downloads"] = downloads; - json["documents"] = documents; - json["pictures"] = pictures; - json["desktop"] = desktop; - json["videos"] = videos; - json["music"] = music; - json["config"] = config; - json["home"] = home; - json["data"] = data; - json["log"] = log; - - return reply(Result::Data { message, json }); - }); - - router->map("permissions.query", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"name"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto name = message.get("name"); - - #if defined(__APPLE__) - if (name == "geolocation") { - if (router->locationObserver.isAuthorized) { - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - } else if (router->locationObserver.locationManager) { - auto authorizationStatus = ( - router->locationObserver.locationManager.authorizationStatus - ); - - if (authorizationStatus == kCLAuthorizationStatusDenied) { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } else { - auto data = JSON::Object::Entries {{"state", "prompt"}}; - return reply(Result::Data { message, data }); - } - } - - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } - - if (name == "notifications") { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - if (!settings) { - auto err = JSON::Object::Entries {{ "message", "Failed to reach user notification settings" }}; - return reply(Result::Err { message, err }); - } - - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - auto data = JSON::Object::Entries {{"state", "prompt"}}; - return reply(Result::Data { message, data }); - } - - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - }]; - } - #endif - }); - - router->map("permissions.request", [=](auto message, auto router, auto reply) { - #if defined(__APPLE__) - __block auto userConfig = router->bridge->userConfig; - #else - auto userConfig = router->bridge->userConfig; - #endif - - auto err = validateMessageParameters(message, {"name"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - auto name = message.get("name"); - - if (name == "geolocation") { - #if defined(__APPLE__) - auto performedActivation = [router->locationObserver attemptActivationWithCompletion: ^(BOOL isAuthorized) { - if (!isAuthorized) { - auto reason = @("Location observer could not be activated"); - - if (!router->locationObserver.locationManager) { - reason = @("Location observer manager is not initialized"); - } else if (!router->locationObserver.locationManager.location) { - reason = @("Location observer manager could not provide location"); - } - - auto error = [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) - code: -1 - userInfo: @{ - NSLocalizedDescriptionKey: reason - } - ]; - } - - if (isAuthorized) { - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - } else if (router->locationObserver.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { - auto data = JSON::Object::Entries {{"state", "prompt"}}; - return reply(Result::Data { message, data }); - } else { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } - }]; - - if (!performedActivation) { - auto err = JSON::Object::Entries {{ "message", "Location observer could not be activated" }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - return; - #endif - } - - if (name == "notifications") { - #if defined(__APPLE__) - UNAuthorizationOptions options = UNAuthorizationOptionProvisional; - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - auto requestAlert = message.get("alert") == "true"; - auto requestBadge = message.get("badge") == "true"; - auto requestSound = message.get("sound") == "true"; - - if (requestAlert) { - options |= UNAuthorizationOptionAlert; - } - - if (requestBadge) { - options |= UNAuthorizationOptionBadge; - } - - if (requestSound) { - options |= UNAuthorizationOptionSound; - } - - if (requestAlert && requestSound) { - options |= UNAuthorizationOptionCriticalAlert; - } - - [notificationCenter - requestAuthorizationWithOptions: options - completionHandler: ^(BOOL granted, NSError *error) { - [notificationCenter - getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - if (error) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { - { "message", message } - }; - - return reply(Result::Err { message, err }); - } - - auto data = JSON::Object::Entries { - {"state", granted ? "granted" : "denied" } - }; - - return reply(Result::Data { message, data }); - } - - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - }]; - }]; - #endif - } - }); - - /** - * Simply returns `pong`. - */ - router->map("ping", [=](auto message, auto router, auto reply) { - auto result = Result { message.seq, message }; - result.data = "pong"; - reply(result); - }); - - /** - * Handles platform events. - * @param value The event name [domcontentloaded] - * @param data Optional data associated with the platform event. - */ - router->map("platform.event", [=](auto message, auto router, auto reply) { - const auto err = validateMessageParameters(message, {"value"}); - const auto frameType = message.get("runtime-frame-type"); - const auto frameSource = message.get("runtime-frame-source"); - auto userConfig = router->bridge->userConfig; - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - if (frameType == "top-level" && frameSource != "serviceworker") { - if (message.value == "load") { - const auto href = message.get("location.href"); - if (href.size() > 0) { - router->location.href = href; - router->location.workers.clear(); - auto tmp = href; - tmp = replace(tmp, "socket://", ""); - tmp = replace(tmp, "https://", ""); - tmp = replace(tmp, userConfig["meta_bundle_identifier"], ""); - auto parsed = Router::parseURL(tmp); - router->location.pathname = parsed.path; - router->location.query = parsed.queryString; - } - } - - if (router->bridge == router->core->serviceWorker.bridge) { - if (router->bridge->userConfig["webview_service_worker_mode"] == "hybrid" || platform.ios || platform.android) { - if (router->location.href.size() > 0 && message.value == "beforeruntimeinit") { - router->core->serviceWorker.reset(); - router->core->serviceWorker.isReady = false; - } else if (message.value == "runtimeinit") { - router->core->serviceWorker.isReady = true; - } - } - } - } - - if (message.value == "load" && frameType == "worker") { - const auto workerLocation = message.get("runtime-worker-location"); - const auto href = message.get("location.href"); - if (href.size() > 0 && workerLocation.size() > 0) { - router->location.workers[href] = workerLocation; - } - } - - router->core->platform.event( - message.seq, - message.value, - message.get("data"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Requests a notification with `title` and `body` to be shown. - * @param title - * @param body - */ - router->map("platform.notify", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"body", "title"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - router->core->platform.notify( - message.seq, - message.get("title"), - message.get("body"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Reveal a file in the native operating system file system explorer. - * @param value - */ - router->map("platform.revealFile", [=](auto message, auto router, auto reply) mutable { - auto err = validateMessageParameters(message, {"value"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - router->core->platform.revealFile( - message.seq, - message.get("value"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Requests a URL to be opened externally. - * @param value - */ - router->map("platform.openExternal", [=](auto message, auto router, auto reply) mutable { - const auto applicationProtocol = router->bridge->userConfig["meta_application_protocol"]; - auto err = validateMessageParameters(message, {"value"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - if (applicationProtocol.size() > 0 && message.value.starts_with(applicationProtocol + ":")) { - SSC::JSON::Object json = SSC::JSON::Object::Entries { - { "url", message.value } - }; - - router->bridge->router.emit("applicationurl", json.str()); - reply(Result { - message.seq, - message, - SSC::JSON::Object::Entries { - {"data", json} - } - }); - return; - } - - router->core->platform.openExternal( - message.seq, - message.value, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Return Socket Runtime primordials. - */ - router->map("platform.primordials", [=](auto message, auto router, auto reply) { - std::regex platform_pattern("^mac$", std::regex_constants::icase); - auto platformRes = std::regex_replace(platform.os, platform_pattern, "darwin"); - auto arch = std::regex_replace(platform.arch, std::regex("x86_64"), "x64"); - arch = std::regex_replace(arch, std::regex("x86"), "ia32"); - arch = std::regex_replace(arch, std::regex("arm(?!64).*"), "arm"); - auto json = JSON::Object::Entries { - {"source", "platform.primordials"}, - {"data", JSON::Object::Entries { - {"arch", arch}, - {"cwd", getcwd()}, - {"platform", platformRes}, - {"version", JSON::Object::Entries { - {"full", SSC::VERSION_FULL_STRING}, - {"short", SSC::VERSION_STRING}, - {"hash", SSC::VERSION_HASH_STRING}} - }, - {"host-operating-system", - #if defined(__APPLE__) - #if TARGET_IPHONE_SIMULATOR - "iphonesimulator" - #elif TARGET_OS_IPHONE - "iphoneos" - #else - "macosx" - #endif - #elif defined(__ANDROID__) - (router->bridge->isAndroidEmulator ? "android-emulator" : "android") - #elif defined(__WIN32) - "win32" - #elif defined(__linux__) - "linux" - #elif defined(__unix__) || defined(__unix) - "unix" - #else - "unknown" - #endif - } - }} - }; - reply(Result { message.seq, message, json }); - }); - - /** - * Returns pending post data typically returned in the response of an - * `ipc://post` IPC call intercepted by an XHR request. - * @param id The id of the post data. - */ - router->map("post", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - if (!router->core->hasPost(id)) { - return reply(Result::Err { message, JSON::Object::Entries { - {"id", std::to_string(id)}, - {"message", "Post not found for given 'id'"} - }}); - } - - auto result = Result { message.seq, message }; - result.post = router->core->getPost(id); - reply(result); - router->core->removePost(id); - }); - - /** - * Registers a custom protocol handler scheme. Custom protocols MUST be handled in service workers. - * @param scheme - * @param data - */ - router->map("protocol.register", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scheme"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - const auto scheme = message.get("scheme"); - const auto data = message.get("data"); - - if (data.size() > 0 && router->core->protocolHandlers.hasHandler(scheme)) { - router->core->protocolHandlers.setHandlerData(scheme, { data }); - } else { - router->core->protocolHandlers.registerHandler(scheme, { data }); - } - - reply(Result { message.seq, message }); - }); - - /** - * Unregister a custom protocol handler scheme. - * @param scheme - */ - router->map("protocol.unregister", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scheme"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - const auto scheme = message.get("scheme"); - - if (!router->core->protocolHandlers.hasHandler(scheme)) { - return reply(Result::Err { message, JSON::Object::Entries { - {"message", "Protocol handler scheme is not registered."} - }}); - } - - router->core->protocolHandlers.unregisterHandler(scheme); - - reply(Result { message.seq, message }); - }); - - /** - * Gets protocol handler data - * @param scheme - */ - router->map("protocol.getData", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scheme"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - const auto scheme = message.get("scheme"); - - if (!router->core->protocolHandlers.hasHandler(scheme)) { - return reply(Result::Err { message, JSON::Object::Entries { - {"message", "Protocol handler scheme is not registered."} - }}); - } - - const auto data = router->core->protocolHandlers.getHandlerData(scheme); - - reply(Result { message.seq, message, JSON::Raw(data.json) }); - }); - - /** - * Sets protocol handler data - * @param scheme - * @param data - */ - router->map("protocol.setData", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scheme", "data"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - const auto scheme = message.get("scheme"); - const auto data = message.get("data"); - - if (!router->core->protocolHandlers.hasHandler(scheme)) { - return reply(Result::Err { message, JSON::Object::Entries { - {"message", "Protocol handler scheme is not registered."} - }}); - } - - router->core->protocolHandlers.setHandlerData(scheme, { data }); - - reply(Result { message.seq, message }); - }); - - /** - * Prints incoming message value to stdout. - * @param value - */ - router->map("stdout", [=](auto message, auto router, auto reply) { - if (message.value.size() > 0) { - #if defined(__APPLE__) - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", message.value.c_str()); - #endif - IO::write(message.value, false); - } else if (message.buffer.size > 0) { - IO::write(String(message.buffer.bytes, message.buffer.size), false); - } - - reply(Result { message.seq, message }); - }); - - /** - * Prints incoming message value to stderr. - * @param value - */ - router->map("stderr", [=](auto message, auto router, auto reply) { - if (message.get("debug") == "true") { - if (message.value.size() > 0) { - debug("%s", message.value.c_str()); - } - } else if (message.value.size() > 0) { - #if defined(__APPLE__) - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", message.value.c_str()); - #endif - IO::write(message.value, true); - } else if (message.buffer.size > 0) { - IO::write(String(message.buffer.bytes, message.buffer.size), true); - } - - reply(Result { message.seq, message }); - }); - - /** - * Registers a service worker script for a given scope. - * @param scriptURL - * @param scope - */ - router->map("serviceWorker.register", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scriptURL", "scope"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - const auto options = ServiceWorkerContainer::RegistrationOptions { - .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, - .scope = message.get("scope"), - .scriptURL = message.get("scriptURL") - }; - - const auto registration = router->core->serviceWorker.registerServiceWorker(options); - auto json = JSON::Object { - JSON::Object::Entries { - {"registration", registration.json()} - } - }; - - reply(Result::Data { message, json }); - }); - - /** - * Resets the service worker container state. - */ - router->map("serviceWorker.reset", [=](auto message, auto router, auto reply) { - router->core->serviceWorker.reset(); - reply(Result::Data { message, JSON::Object {}}); - }); - - /** - * Unregisters a service worker for given scoep. - * @param scope - */ - router->map("serviceWorker.unregister", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scope"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - const auto scope = message.get("scope"); - router->core->serviceWorker.unregisterServiceWorker(scope); - - return reply(Result::Data { message, JSON::Object {} }); - }); - - /** - * Gets registration information for a service worker scope. - * @param scope - */ - router->map("serviceWorker.getRegistration", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"scope"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - const auto scope = message.get("scope"); - - for (const auto& entry : router->core->serviceWorker.registrations) { - const auto& registration = entry.second; - if (scope.starts_with(registration.options.scope)) { - auto json = JSON::Object { - JSON::Object::Entries { - {"registration", registration.json()}, - {"client", JSON::Object::Entries { - {"id", std::to_string(router->bridge->id)} - }} - } - }; - - return reply(Result::Data { message, json }); - } - } - - return reply(Result::Data { message, JSON::Object {} }); - }); - - /** - * Gets all service worker scope registrations. - */ - router->map("serviceWorker.getRegistrations", [=](auto message, auto router, auto reply) { - auto json = JSON::Array::Entries {}; - for (const auto& entry : router->core->serviceWorker.registrations) { - const auto& registration = entry.second; - json.push_back(registration.json()); - } - return reply(Result::Data { message, json }); - }); - - /** - * Informs container that a service worker will skip waiting. - * @param id - */ - router->map("serviceWorker.skipWaiting", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->serviceWorker.skipWaiting(id); - - reply(Result::Data { message, JSON::Object {}}); - }); - - /** - * Updates service worker controller state. - * @param id - * @param state - */ - router->map("serviceWorker.updateState", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "state"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - const auto workerURL = message.get("workerURL"); - const auto scriptURL = message.get("scriptURL"); - - if (workerURL.size() > 0 && scriptURL.size() > 0) { - router->location.workers[workerURL] = scriptURL; - } - - router->core->serviceWorker.updateState(id, message.get("state")); - reply(Result::Data { message, JSON::Object {}}); - }); - - /** - * Sets storage for a service worker. - * @param id - * @param key - * @param value - */ - router->map("serviceWorker.storage.set", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "key", "value"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - for (auto& entry : router->core->serviceWorker.registrations) { - if (entry.second.id == id) { - auto& registration = entry.second; - registration.storage.set(message.get("key"), message.get("value")); - return reply(Result::Data { message, JSON::Object {}}); - } - } - - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"} - } - }); - }); - - /** - * Gets a storage value for a service worker. - * @param id - * @param key - */ - router->map("serviceWorker.storage.get", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "key"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - for (auto& entry : router->core->serviceWorker.registrations) { - if (entry.second.id == id) { - auto& registration = entry.second; - return reply(Result::Data { - message, - JSON::Object::Entries { - {"value", registration.storage.get(message.get("key"))} - } - }); - } - } - - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"} - } - }); - }); - - /** - * Remoes a storage value for a service worker. - * @param id - * @param key - */ - router->map("serviceWorker.storage.remove", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "key"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - for (auto& entry : router->core->serviceWorker.registrations) { - if (entry.second.id == id) { - auto& registration = entry.second; - registration.storage.remove(message.get("key")); - return reply(Result::Data {message, JSON::Object {}}); - } - } - - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"} - } - }); - }); - - /** - * Clears all storage values for a service worker. - * @param id - */ - router->map("serviceWorker.storage.clear", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - for (auto& entry : router->core->serviceWorker.registrations) { - if (entry.second.id == id) { - auto& registration = entry.second; - registration.storage.clear(); - return reply(Result::Data { message, JSON::Object {} }); - } - } - - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"} - } - }); - }); - - /** - * Gets all storage values for a service worker. - * @param id - */ - router->map("serviceWorker.storage", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - for (auto& entry : router->core->serviceWorker.registrations) { - if (entry.second.id == id) { - auto& registration = entry.second; - return reply(Result::Data { message, registration.storage.json() }); - } - } - - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"} - } - }); - }); - - router->map("timers.setTimeout", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"timeout"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint32_t timeout; - REQUIRE_AND_GET_MESSAGE_VALUE(timeout, "timeout", std::stoul); - const auto wait = message.get("wait") == "true"; - const Core::Timers::ID id = router->core->timers.setTimeout(timeout, [=]() { - if (wait) { - reply(Result::Data { message, JSON::Object::Entries {{"id", std::to_string(id) }}}); - } - }); - - if (!wait) { - reply(Result::Data { message, JSON::Object::Entries {{"id", std::to_string(id) }}}); - } - }); - - router->map("timers.clearTimeout", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - router->core->timers.clearTimeout(id); - - reply(Result::Data { message, JSON::Object::Entries {{"id", std::to_string(id) }}}); - }); - - /** - * Binds an UDP socket to a specified port, and optionally a host - * address (default: 0.0.0.0). - * @param id Handle ID of underlying socket - * @param port Port to bind the UDP socket to - * @param address The address to bind the UDP socket to (default: 0.0.0.0) - * @param reuseAddr Reuse underlying UDP socket address (default: false) - */ - router->map("udp.bind", [=](auto message, auto router, auto reply) { - Core::UDP::BindOptions options; - auto err = validateMessageParameters(message, {"id", "port"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(options.port, "port", std::stoi); - - options.reuseAddr = message.get("reuseAddr") == "true"; - options.address = message.get("address", "0.0.0.0"); - - router->core->udp.bind( - message.seq, - id, - options, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Close socket handle and underlying UDP socket. - * @param id Handle ID of underlying socket - */ - router->map("udp.close", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.close(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); - }); - - /** - * Connects an UDP socket to a specified port, and optionally a host - * address (default: 0.0.0.0). - * @param id Handle ID of underlying socket - * @param port Port to connect the UDP socket to - * @param address The address to connect the UDP socket to (default: 0.0.0.0) - */ - router->map("udp.connect", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "port"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - Core::UDP::ConnectOptions options; - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(options.port, "port", std::stoi); - - options.address = message.get("address", "0.0.0.0"); - - router->core->udp.connect( - message.seq, - id, - options, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Disconnects a connected socket handle and underlying UDP socket. - * @param id Handle ID of underlying socket - */ - router->map("udp.disconnect", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.disconnect( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Returns connected peer socket address information. - * @param id Handle ID of underlying socket - */ - router->map("udp.getPeerName", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.getPeerName( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Returns local socket address information. - * @param id Handle ID of underlying socket - */ - router->map("udp.getSockName", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.getSockName( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Returns socket state information. - * @param id Handle ID of underlying socket - */ - router->map("udp.getState", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.getState( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Initializes socket handle to start receiving data from the underlying - * socket and route through the IPC bridge to the WebView. - * @param id Handle ID of underlying socket - */ - router->map("udp.readStart", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.readStart( - message.seq, - id, - [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); - } - ); - }); - - /** - * Stops socket handle from receiving data from the underlying - * socket and routing through the IPC bridge to the WebView. - * @param id Handle ID of underlying socket - */ - router->map("udp.readStop", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - - router->core->udp.readStop( - message.seq, - id, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - /** - * Broadcasts a datagram on the socket. For connectionless sockets, the - * destination port and address must be specified. Connected sockets, on the - * other hand, will use their associated remote endpoint, so the port and - * address arguments must not be set. - * @param id Handle ID of underlying socket - * @param port The port to send data to - * @param size The size of the bytes to send - * @param bytes A pointer to the bytes to send - * @param address The address to send to (default: 0.0.0.0) - * @param ephemeral Indicates that the socket handle, if created is ephemeral and should eventually be destroyed - */ - router->map("udp.send", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"id", "port"}); - - if (err.type != JSON::Type::Null) { - return reply(Result::Err { message, err }); - } - - Core::UDP::SendOptions options; - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - REQUIRE_AND_GET_MESSAGE_VALUE(options.port, "port", std::stoi); - - options.size = message.buffer.size; - options.bytes = message.buffer.bytes; - options.address = message.get("address", "0.0.0.0"); - options.ephemeral = message.get("ephemeral") == "true"; - - router->core->udp.send( - message.seq, - id, - options, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - - router->map("window.showFileSystemPicker", [=](auto message, auto router, auto reply) { - const auto allowMultiple = message.get("allowMultiple") == "true"; - const auto allowFiles = message.get("allowFiles") == "true"; - const auto allowDirs = message.get("allowDirs") == "true"; - const auto isSave = message.get("type") == "save"; - - const auto contentTypeSpecs = message.get("contentTypeSpecs"); - const auto defaultName = message.get("defaultName"); - const auto defaultPath = message.get("defaultPath"); - const auto title = message.get("title", isSave ? "Save" : "Open"); - - Dialog dialog; - auto options = Dialog::FileSystemPickerOptions { - .directories = allowDirs, - .multiple = allowMultiple, - .files = allowFiles, - .contentTypes = contentTypeSpecs, - .defaultName = defaultName, - .defaultPath = defaultPath, - .title = title - }; - - if (isSave) { - const auto result = dialog.showSaveFilePicker(options); - - if (result.size() == 0) { - auto err = JSON::Object::Entries {{"type", "AbortError"}}; - reply(Result::Err { message, err }); - } else { - auto data = JSON::Object::Entries { - {"paths", JSON::Array::Entries{result}} - }; - reply(Result::Data { message, data }); - } - } else { - JSON::Array paths; - const auto results = ( - allowFiles && !allowDirs - ? dialog.showOpenFilePicker(options) - : dialog.showDirectoryPicker(options) - ); - - for (const auto& result : results) { - paths.push(result); - } - - auto data = JSON::Object::Entries { - {"paths", paths} - }; - - reply(Result::Data { message, data }); - } - }); -} - -static void registerSchemeHandler (Router *router) { - static const auto MAX_BODY_BYTES = 4 * 1024 * 1024; - static const auto devHost = SSC::getDevHost(); - static Atomic isInitialized = false; - - if (isInitialized) { - return; - } - - isInitialized = true; - -#if defined(__linux__) && !defined(__ANDROID__) - auto ctx = webkit_web_context_get_default(); - auto security = webkit_web_context_get_security_manager(ctx); - - webkit_web_context_register_uri_scheme(ctx, "ipc", [](auto request, auto ptr) { - IPC::Router* router = nullptr; - - auto webview = webkit_uri_scheme_request_get_web_view(request); - auto windowManager = App::instance()->getWindowManager(); - - for (auto& window : windowManager->windows) { - if ( - window != nullptr && - window->bridge != nullptr && - WEBKIT_WEB_VIEW(window->webview) == webview - ) { - router = &window->bridge->router; - break; - } - } - - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto method = String(webkit_uri_scheme_request_get_http_method(request)); - auto message = IPC::Message{ uri }; - char bytes[MAX_BODY_BYTES]{0}; - - if ((method == "POST" || method == "PUT")) { - auto body = webkit_uri_scheme_request_get_http_body(request); - if (body) { - GError* error = nullptr; - message.buffer.bytes = new char[MAX_BODY_BYTES]{0}; - - const auto success = g_input_stream_read_all( - body, - message.buffer.bytes, - MAX_BODY_BYTES, - &message.buffer.size, - nullptr, - &error - ); - - if (!success) { - delete message.buffer.bytes; - webkit_uri_scheme_request_finish_error( - request, - error - ); - return; - } - } - } - - auto invoked = router->invoke(message, message.buffer.bytes, message.buffer.size, [=](auto result) { - auto json = result.str(); - auto size = result.post.body != nullptr ? result.post.length : json.size(); - auto body = result.post.body != nullptr ? result.post.body : json.c_str(); - - char* data = nullptr; - - if (size > 0) { - data = new char[size]{0}; - memcpy(data, body, size); - } - - auto stream = g_memory_input_stream_new_from_data(data, size, nullptr); - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, size); - - soup_message_headers_append(headers, "cache-control", "no-cache"); - for (const auto& header : result.headers.entries) { - soup_message_headers_append(headers, header.key.c_str(), header.value.c_str()); - } - - if (result.post.body) { - webkit_uri_scheme_response_set_content_type(response, IPC_BINARY_CONTENT_TYPE); - } else { - webkit_uri_scheme_response_set_content_type(response, IPC_JSON_CONTENT_TYPE); - } - - webkit_uri_scheme_request_finish_with_response(request, response); - g_input_stream_close_async(stream, 0, nullptr, +[]( - GObject* object, - GAsyncResult* asyncResult, - gpointer userData - ) { - auto stream = (GInputStream*) object; - g_input_stream_close_finish(stream, asyncResult, nullptr); - g_object_unref(stream); - g_idle_add_full( - G_PRIORITY_DEFAULT_IDLE, - (GSourceFunc) [](gpointer userData) { - return G_SOURCE_REMOVE; - }, - userData, - [](gpointer userData) { - delete [] static_cast(userData); - } - ); - }, data); - }); - - if (!invoked) { - auto err = JSON::Object::Entries { - {"source", uri}, - {"err", JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"}, - {"url", uri} - }} - }; - - auto msg = JSON::Object(err).str(); - auto size = msg.size(); - auto bytes = msg.c_str(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - auto response = webkit_uri_scheme_response_new(stream, msg.size()); - - webkit_uri_scheme_response_set_status(response, 404, "Not found"); - webkit_uri_scheme_response_set_content_type(response, IPC_JSON_CONTENT_TYPE); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - } - }, - router, - 0); - - webkit_web_context_register_uri_scheme(ctx, "socket", [](auto request, auto ptr) { - IPC::Router* router = nullptr; - - auto webview = webkit_uri_scheme_request_get_web_view(request); - auto windowManager = App::instance()->getWindowManager(); - - for (auto& window : windowManager->windows) { - if ( - window != nullptr && - window->bridge != nullptr && - WEBKIT_WEB_VIEW(window->webview) == webview - ) { - router = &window->bridge->router; - break; - } - } - - auto userConfig = router->bridge->userConfig; - bool isModule = false; - auto method = String(webkit_uri_scheme_request_get_http_method(request)); - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto cwd = getcwd(); - uint64_t clientId = router->bridge->id; - - if (uri.starts_with("socket:///")) { - uri = uri.substr(10); - } else if (uri.starts_with("socket://")) { - uri = uri.substr(9); - } else if (uri.starts_with("socket:")) { - uri = uri.substr(7); - } - - const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - auto path = String( - uri.starts_with(bundleIdentifier) - ? uri.substr(bundleIdentifier.size()) - : "socket/" + uri - ); - - auto parsedPath = Router::parseURL(path); - auto ext = fs::path(parsedPath.path).extension().string(); - - if (ext.size() > 0 && !ext.starts_with(".")) { - ext = "." + ext; - } - - if (!uri.starts_with(bundleIdentifier)) { - path = parsedPath.path; - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - ext = ".js"; - } - - if (parsedPath.queryString.size() > 0) { - path += String("?") + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - path += String("#") + parsedPath.fragment; - } - - uri = "socket://" + bundleIdentifier + "/" + path; - auto moduleSource = trim(tmpl( - moduleTemplate, - Map { {"url", String(uri)} } - )); - - auto size = moduleSource.size(); - auto bytes = moduleSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - - if (stream) { - auto response = webkit_uri_scheme_response_new(stream, size); - webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - } else { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - } - return; - } - - auto resolved = Router::resolveURLPathForWebView(parsedPath.path, cwd); - auto mount = Router::resolveNavigatorMountForWebView(parsedPath.path); - path = resolved.path; - - if (mount.path.size() > 0) { - if (mount.resolution.redirect) { - auto redirectURL = resolved.path; - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto redirectSource = String( - "" - ); - - auto size = redirectSource.size(); - auto bytes = redirectSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - - if (stream) { - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, (gint64) size); - auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - - soup_message_headers_append(headers, "location", redirectURL.c_str()); - soup_message_headers_append(headers, "content-location", contentLocation.c_str()); - - webkit_uri_scheme_response_set_http_headers(response, headers); - webkit_uri_scheme_response_set_content_type(response, "text/html"); - webkit_uri_scheme_request_finish_with_response(request, response); - - g_object_unref(stream); - } else { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - } - - return; - } - } else if (path.size() == 0) { - if (userConfig.contains("webview_default_index")) { - path = userConfig["webview_default_index"]; - } else { - if (router->core->serviceWorker.registrations.size() > 0) { - auto requestHeaders = webkit_uri_scheme_request_get_http_headers(request); - auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; - - fetchRequest.client.id = clientId; - fetchRequest.client.preload = router->bridge->preload; - - fetchRequest.method = method; - fetchRequest.scheme = "socket"; - fetchRequest.host = userConfig["meta_bundle_identifier"]; - fetchRequest.pathname = parsedPath.path; - fetchRequest.query = parsedPath.queryString; - - soup_message_headers_foreach( - requestHeaders, - [](auto name, auto value, auto userData) { - auto fetchRequest = reinterpret_cast(userData); - const auto entry = String(name) + ": " + String(value); - fetchRequest->headers.push_back(entry); - }, - &fetchRequest - ); - - if ((method == "POST" || method == "PUT")) { - auto body = webkit_uri_scheme_request_get_http_body(request); - if (body) { - GError* error = nullptr; - fetchRequest.buffer.bytes = new char[MAX_BODY_BYTES]{0}; - - const auto success = g_input_stream_read_all( - body, - fetchRequest.buffer.bytes, - MAX_BODY_BYTES, - &fetchRequest.buffer.size, - nullptr, - &error - ); - - if (!success) { - delete fetchRequest.buffer.bytes; - webkit_uri_scheme_request_finish_error( - request, - error - ); - return; - } - } - } - - const auto fetched = router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { - if (res.statusCode == 0) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "%.*s", - (int) res.buffer.size, - res.buffer.bytes - ) - ); - return; - } - - const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); - auto stream = g_memory_input_stream_new_from_data(res.buffer.bytes, res.buffer.size, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } - - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, (gint64) res.buffer.size); - - for (const auto& line : webviewHeaders) { - auto pair = split(trim(line), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - soup_message_headers_append(headers, key.c_str(), value.c_str()); - } - - for (const auto& line : res.headers) { - auto pair = split(trim(line), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - - if (key == "content-type" || key == "Content-Type") { - webkit_uri_scheme_response_set_content_type(response, value.c_str()); - } - - soup_message_headers_append(headers, key.c_str(), value.c_str()); - } - - webkit_uri_scheme_response_set_http_headers(response, headers); - webkit_uri_scheme_request_finish_with_response(request, response); - - g_object_unref(stream); - }); - - if (fetched) { - return; - } else { - if (fetchRequest.buffer.bytes != nullptr) { - delete fetchRequest.buffer.bytes; - } - } - } - } - } else if (resolved.redirect) { - auto redirectURL = resolved.path; - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto redirectSource = String( - "" - ); - - auto size = redirectSource.size(); - auto bytes = redirectSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } - - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, (gint64) size); - auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - - soup_message_headers_append(headers, "location", redirectURL.c_str()); - soup_message_headers_append(headers, "content-location", contentLocation.c_str()); - - webkit_uri_scheme_response_set_http_headers(response, headers); - webkit_uri_scheme_response_set_content_type(response, "text/html"); - webkit_uri_scheme_request_finish_with_response(request, response); - - g_object_unref(stream); - return; - } - - if (mount.path.size() > 0) { - path = mount.path; - } else if (path.size() > 0) { - path = fs::absolute(fs::path(cwd) / path.substr(1)).string(); - } - - if (path.size() == 0 || !fs::exists(path)) { - auto stream = g_memory_input_stream_new_from_data(nullptr, 0, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } - - auto response = webkit_uri_scheme_response_new(stream, 0); - - webkit_uri_scheme_response_set_status(response, 404, "Not found"); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - return; - } - - WebKitURISchemeResponse* response = nullptr; - GInputStream* stream = nullptr; - gchar* mimeType = nullptr; - GError* error = nullptr; - char* data = nullptr; - - auto webviewHeaders = split(userConfig["webview_headers"], '\n'); - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - - if (path.ends_with(".html")) { - auto script = router->bridge->preload; - - if (userConfig["webview_importmap"].size() > 0) { - const auto file = Path(userConfig["webview_importmap"]); - - if (fs::exists(file)) { - String string; - std::ifstream stream(file.string().c_str()); - auto buffer = std::istreambuf_iterator(stream); - auto end = std::istreambuf_iterator(); - string.assign(buffer, end); - stream.close(); - - script = ( - String("\n") + - script - ); - } - } - - const auto file = Path(path); - - if (fs::exists(file)) { - char* contents = nullptr; - gsize size = 0; - if (g_file_get_contents(file.c_str(), &contents, &size, &error)) { - String html = contents; - Vector protocolHandlers = { "npm:", "node:" }; - for (const auto& entry : router->core->protocolHandlers.mapping) { - protocolHandlers.push_back(String(entry.first) + ":"); - } - - html = tmpl(html, Map { - {"protocol_handlers", join(protocolHandlers, " ")} - }); - - if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else { - html = script + html; - } - - data = new char[html.size()]{0}; - memcpy(data, html.data(), html.size()); - g_free(contents); - - stream = g_memory_input_stream_new_from_data(data, (gint64) html.size(), nullptr); - - if (stream) { - response = webkit_uri_scheme_response_new(stream, -1); - } else { - delete [] data; - data = nullptr; - } - } - } - } else { - auto file = g_file_new_for_path(path.c_str()); - auto size = fs::file_size(path); - - if (file) { - stream = (GInputStream*) g_file_read(file, nullptr, &error); - g_object_unref(file); - } - - if (stream) { - response = webkit_uri_scheme_response_new(stream, (gint64) size); - g_object_unref(stream); - } - } - - if (!stream) { - webkit_uri_scheme_request_finish_error(request, error); - g_error_free(error); - return; - } - - soup_message_headers_append(headers, "cache-control", "no-cache"); - soup_message_headers_append(headers, "access-control-allow-origin", "*"); - soup_message_headers_append(headers, "access-control-allow-methods", "*"); - soup_message_headers_append(headers, "access-control-allow-headers", "*"); - soup_message_headers_append(headers, "access-control-allow-credentials", "true"); - - for (const auto& line : webviewHeaders) { - auto pair = split(trim(line), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - soup_message_headers_append(headers, key.c_str(), value.c_str()); - } - - webkit_uri_scheme_response_set_http_headers(response, headers); - - if (path.ends_with(".wasm")) { - webkit_uri_scheme_response_set_content_type(response, "application/wasm"); - } else if (path.ends_with(".cjs") || path.ends_with(".mjs")) { - webkit_uri_scheme_response_set_content_type(response, "text/javascript"); - } else if (path.ends_with(".ts")) { - webkit_uri_scheme_response_set_content_type(response, "application/typescript"); - } else { - mimeType = g_content_type_guess(path.c_str(), nullptr, 0, nullptr); - if (mimeType) { - webkit_uri_scheme_response_set_content_type(response, mimeType); - } else { - webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); - } - } - - webkit_uri_scheme_request_finish_with_response(request, response); - - if (data) { - g_input_stream_close_async(stream, 0, nullptr, +[]( - GObject* object, - GAsyncResult* asyncResult, - gpointer userData - ) { - auto stream = (GInputStream*) object; - g_input_stream_close_finish(stream, asyncResult, nullptr); - g_object_unref(stream); - g_idle_add_full( - G_PRIORITY_DEFAULT_IDLE, - (GSourceFunc) [](gpointer userData) { - return G_SOURCE_REMOVE; - }, - userData, - [](gpointer userData) { - delete [] static_cast(userData); - } - ); - }, data); - } else { - g_object_unref(stream); - } - - if (mimeType) { - g_free(mimeType); - } - }, - router, - 0); - - webkit_web_context_register_uri_scheme(ctx, "node", [](auto request, auto ptr) { - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto router = reinterpret_cast(ptr); - auto userConfig = router->bridge->userConfig; - - const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - - if (uri.starts_with("node:///")) { - uri = uri.substr(10); - } else if (uri.starts_with("node://")) { - uri = uri.substr(9); - } else if (uri.starts_with("node:")) { - uri = uri.substr(7); - } - - auto path = String("socket/" + uri); - auto ext = fs::path(path).extension().string(); - - if (ext.size() > 0 && !ext.starts_with(".")) { - ext = "." + ext; - } - - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - ext = ".js"; - } - - uri = "socket://" + bundleIdentifier + "/" + path; - - auto moduleSource = trim(tmpl( - moduleTemplate, - Map { {"url", String(uri)} } - )); - - auto size = moduleSource.size(); - auto bytes = moduleSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - auto response = webkit_uri_scheme_response_new(stream, size); - - webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - }, - router, - 0); - - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "ipc"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "ipc"); - webkit_security_manager_register_uri_scheme_as_secure(security, "ipc"); - webkit_security_manager_register_uri_scheme_as_local(security, "ipc"); - - if (devHost.starts_with("http:")) { - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "http"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "http"); - webkit_security_manager_register_uri_scheme_as_secure(security, "http"); - webkit_security_manager_register_uri_scheme_as_local(security, "http"); - } - - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "socket"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "socket"); - webkit_security_manager_register_uri_scheme_as_secure(security, "socket"); - webkit_security_manager_register_uri_scheme_as_local(security, "socket"); - - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "node"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "node"); - webkit_security_manager_register_uri_scheme_as_secure(security, "node"); - webkit_security_manager_register_uri_scheme_as_local(security, "node"); -#endif -} - -#if defined(__APPLE__) -@implementation SSCIPCSchemeHandler -{ - SSC::Mutex mutex; - std::unordered_map tasks; -} - -- (void) enqueueTask: (Task) task withMessage: (IPC::Message) message { - Lock lock(mutex); - if (task != nullptr && !tasks.contains(task)) { - tasks.emplace(task, message); - } -} - -- (void) finalizeTask: (Task) task { - Lock lock(mutex); - if (task != nullptr && tasks.contains(task)) { - tasks.erase(task); - } -} - -- (bool) waitingForTask: (Task) task { - Lock lock(mutex); - return task != nullptr && tasks.contains(task); -} - -- (void) webView: (SSCBridgedWebView*) webview stopURLSchemeTask: (Task) task { - Lock lock(mutex); - if (tasks.contains(task)) { - auto message = tasks[task]; - if (message.cancel->handler != nullptr) { - message.cancel->handler(message.cancel->data); - } - } - [self finalizeTask: task]; -} - -- (void) webView: (SSCBridgedWebView*) webview startURLSchemeTask: (Task) task { - static auto fileManager = [[NSFileManager alloc] init]; - - if (!self.router) return; - if (!self.router->core) return; - if (!self.router->bridge) return; - - auto userConfig = self.router->bridge->userConfig; - - if (!userConfig.count("meta_bundle_identifier")) return; - - const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - - auto rawHeaders = userConfig.count("webview_headers") ? userConfig["webview_headers"] : ""; - const auto webviewHeaders = split(rawHeaders, '\n'); - const auto request = task.request; - const auto scheme = String(request.URL.scheme.UTF8String); - const auto url = String(request.URL.absoluteString.UTF8String); - - uint64_t clientId = self.router->bridge->id; - - if (request.allHTTPHeaderFields != nullptr) { - if (request.allHTTPHeaderFields[@"runtime-client-id"] != nullptr) { - try { - clientId = std::stoull(request.allHTTPHeaderFields[@"runtime-client-id"].UTF8String); - } catch (...) {} - } - } - - auto message = Message(url, true); - message.isHTTP = true; - message.cancel = std::make_shared(); - - auto headers = [NSMutableDictionary dictionary]; - - for (const auto& line : webviewHeaders) { - const auto pair = split(trim(line), ':'); - const auto key = @(trim(pair[0]).c_str()); - const auto value = @(trim(pair[1]).c_str()); - headers[key] = value; - } - - if (SSC::isDebugEnabled()) { - headers[@"cache-control"] = @"no-cache"; - } - - headers[@"access-control-allow-origin"] = @"*"; - headers[@"access-control-allow-methods"] = @"*"; - headers[@"access-control-allow-headers"] = @"*"; - headers[@"access-control-allow-credentials"] = @"true"; - - if (String(request.HTTPMethod.UTF8String) == "OPTIONS") { - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didFinish]; - #if !__has_feature(objc_arc) - [response release]; - #endif - - return; - } - - const bool hasHandler = self.router->core->protocolHandlers.hasHandler(scheme); - - // handle 'npm:' and custom protocol schemes - if (scheme == "npm" || hasHandler) { - auto absoluteURL = String(request.URL.absoluteString.UTF8String); - auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; - - fetchRequest.client.id = clientId; - fetchRequest.client.preload = self.router->bridge->preload; - - fetchRequest.method = String(request.HTTPMethod.UTF8String); - fetchRequest.scheme = scheme; - - if (request.URL.path != nullptr) { - fetchRequest.pathname = String(request.URL.path.UTF8String); - fetchRequest.host = bundleIdentifier; - } else if (request.URL.host != nullptr) { - fetchRequest.pathname = String("/") + String(request.URL.host.UTF8String); - fetchRequest.host = bundleIdentifier; - } else { - fetchRequest.host = bundleIdentifier; - if (absoluteURL.starts_with(scheme + "://")) { - fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + "://", ""); - } else if (absoluteURL.starts_with(scheme + ":/")) { - fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + ":/", ""); - } else { - fetchRequest.pathname = String("/") + replace(absoluteURL, scheme + ":", ""); - } - } - - if (request.URL.host != nullptr && request.URL.path != nullptr) { - fetchRequest.host = String(request.URL.host.UTF8String); - } else { - fetchRequest.host = bundleIdentifier; - } - - if (scheme == "npm") { - fetchRequest.pathname = String("/socket/npm") + fetchRequest.pathname; - } - - if (request.URL.query != nullptr) { - fetchRequest.query = String(request.URL.query.UTF8String); - } else { - auto cursor = absoluteURL.find_first_of("?"); - if (cursor != String::npos && cursor < absoluteURL.size()) { - Vectorcomponents; - - // re-encode all URI components as this part of the URL may be sitting - // in the "auth" position of the URI with non-encoded characters - for (const auto entry : split(absoluteURL.substr(cursor + 1, absoluteURL.size()), "&")) { - const auto parts = split(entry, "="); - if (parts.size() == 2) { - const auto component = encodeURIComponent(parts[0]) + "=" + encodeURIComponent(parts[1]); - components.push_back(component); - } - } - - fetchRequest.query = join(components, "&"); - auto cursor = fetchRequest.pathname.find_first_of("?"); - if (cursor != String::npos) { - fetchRequest.pathname = fetchRequest.pathname.substr(0, cursor); - } - } - } - - if (request.allHTTPHeaderFields != nullptr) { - for (NSString* key in request.allHTTPHeaderFields) { - const auto value = [request.allHTTPHeaderFields objectForKey: key]; - if (value != nullptr) { - if (String(key.UTF8String) == "referer" || String(key.UTF8String) == "Referer") { - if (self.router->location.workers.contains(value.UTF8String)) { - const auto workerLocation = self.router->location.workers[value.UTF8String]; - const auto entry = String(key.UTF8String) + ": " + workerLocation; - fetchRequest.headers.push_back(entry); - continue; - } else if (self.router->core->serviceWorker.bridge->router.location.workers.contains(value.UTF8String)) { - const auto workerLocation = self.router->core->serviceWorker.bridge->router.location.workers[value.UTF8String]; - const auto entry = String(key.UTF8String) + ": " + workerLocation; - fetchRequest.headers.push_back(entry); - continue; - } - } - - const auto entry = String(key.UTF8String) + ": " + String(value.UTF8String); - fetchRequest.headers.push_back(entry); - } - } - } - - if (request.HTTPBody && request.HTTPBody.bytes && request.HTTPBody.length > 0) { - fetchRequest.buffer.size = request.HTTPBody.length; - fetchRequest.buffer.bytes = new char[fetchRequest.buffer.size]{0}; - memcpy( - fetchRequest.buffer.bytes, - request.HTTPBody.bytes, - fetchRequest.buffer.size - ); - } - - const auto scope = self.router->core->protocolHandlers.getServiceWorkerScope(fetchRequest.scheme); - - if (scope.size() > 0) { - fetchRequest.pathname = scope + fetchRequest.pathname; - } - - const auto requestURL = scheme == "npm" - ? replace(fetchRequest.str(), "npm://", "socket://") - : fetchRequest.str(); - - const auto fetched = self.router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { - if (![self waitingForTask: task]) { - return; - } - - if (res.statusCode == 0) { - @try { - [task didFailWithError: [NSError - errorWithDomain: @(bundleIdentifier.c_str()) - code: 1 - userInfo: @{NSLocalizedDescriptionKey: @(res.buffer.bytes)} - ]]; - } @catch (id e) { - // ignore possible 'NSInternalInconsistencyException' - } - return; - } - - auto headers = [NSMutableDictionary dictionary]; - - for (const auto& entry : res.headers) { - auto pair = split(trim(entry), ':'); - auto key = @(trim(pair[0]).c_str()); - auto value = @(trim(pair[1]).c_str()); - headers[key] = value; - } - - @try { - if (![self waitingForTask: task]) { - return; - } - - const auto response = [[NSHTTPURLResponse alloc] - initWithURL: [NSURL URLWithString: @(requestURL.c_str())] - statusCode: res.statusCode - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - [task didReceiveResponse: response]; - - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - if (fetchRequest.method != "HEAD" && fetchRequest.method != "OPTIONS") { - const auto data = [NSData - dataWithBytes: res.buffer.bytes - length: res.buffer.size - ]; - - if (res.buffer.size && data.length > 0) { - [task didReceiveData: data]; - } - } - - [task didFinish]; - [self finalizeTask: task]; - #if !__has_feature(objc_arc) - [response release]; - #endif - } @catch (id e) { - // ignore possible 'NSInternalInconsistencyException' - } - }); - - if (fetched) { - [self enqueueTask: task withMessage: message]; - self.router->bridge->core->setTimeout(32000, [=] () mutable { - if ([self waitingForTask: task]) { - @try { - [self finalizeTask: task]; - [task didFailWithError: [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) - code: 1 - userInfo: @{NSLocalizedDescriptionKey: @"ServiceWorker request timed out."} - ]]; - } @catch (id e) { - } - } - }); - return; - } - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 404 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - if (scheme == "socket" || scheme == "node") { - auto host = request.URL.host; - auto components = [NSURLComponents - componentsWithURL: request.URL - resolvingAgainstBaseURL: YES - ]; - - components.scheme = @"file"; - components.host = request.URL.host; - - NSData* data = nullptr; - bool isModule = false; - - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - const auto basePath = String(NSBundle.mainBundle.resourcePath.UTF8String) + "/ui"; - #else - const auto basePath = String(NSBundle.mainBundle.resourcePath.UTF8String); - #endif - auto path = String(components.path.UTF8String); - - auto ext = String( - components.URL.pathExtension.length > 0 - ? components.URL.pathExtension.UTF8String - : "" - ); - - if (ext.size() > 0 && !ext.starts_with(".")) { - ext = "." + ext; - } - - // assumes `import 'socket:/module'` syntax - if (host == nullptr && path.starts_with(bundleIdentifier)) { - host = @(path.substr(0, bundleIdentifier.size()).c_str()); - path = path.substr(bundleIdentifier.size()); - - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - } - - components.path = @(path.c_str()); - } - - if ( - scheme != "node" && - host.UTF8String != nullptr && - String(host.UTF8String) == bundleIdentifier - ) { - auto parsedPath = Router::parseURL(path); - auto resolved = Router::resolveURLPathForWebView(path, basePath); - auto mount = Router::resolveNavigatorMountForWebView(path); - path = resolved.path; - - if (mount.path.size() > 0) { - if (mount.resolution.redirect) { - auto redirectURL = mount.resolution.path; - - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto redirectSource = String( - "" - ); - - headers[@"content-length"] = @(redirectSource.size()); - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: [NSURL URLWithString: @(redirectURL.c_str())] - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - - if (String(request.HTTPMethod.UTF8String) != "HEAD") { - data = [@(redirectSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; - [task didReceiveData: data]; - } - - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } else { - auto url = [NSURL fileURLWithPath: @(mount.path.c_str())]; - - if (path.ends_with(".wasm")) { - headers[@"content-type"] = @("application/wasm"); - } else if (path.ends_with(".ts")) { - headers[@"content-type"] = @("application/typescript"); - } else if (path.ends_with(".cjs") || path.ends_with(".mjs")) { - headers[@"content-type"] = @("text/javascript"); - } else if (components.URL.pathExtension != nullptr) { - auto types = [UTType - typesWithTag: components.URL.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - if (types.count > 0 && types.firstObject.preferredMIMEType) { - headers[@"content-type"] = types.firstObject.preferredMIMEType; - } - } - - if (String(request.HTTPMethod.UTF8String) == "HEAD") { - NSNumber* size = nullptr; - NSError* error = nullptr; - [url startAccessingSecurityScopedResource]; - [url getResourceValue: &size - forKey: NSURLFileSizeKey - error: &error - ]; - [url stopAccessingSecurityScopedResource]; - - if (size != nullptr) { - headers[@"content-length"] = size.stringValue; - } - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - } - - [url startAccessingSecurityScopedResource]; - auto data = [NSData dataWithContentsOfURL: url]; - headers[@"content-length"] = [@(data.length) stringValue]; - [url stopAccessingSecurityScopedResource]; - - - if (mount.path.ends_with("html")) { - const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; - auto script = self.router->bridge->preload; - - if (userConfig.count("webview_importmap") > 0 && userConfig["webview_importmap"].size() > 0) { - const auto filename = userConfig["webview_importmap"]; - const auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", filename.c_str() - #else - stringWithFormat: @"/%s", filename.c_str() - #endif - ] - ]]; - - const auto data = [NSData - dataWithContentsOfURL: [NSURL fileURLWithPath: url.path] - ]; - - if (data && data.length > 0) { - const auto string = [NSString.alloc - initWithData: data - encoding: NSUTF8StringEncoding - ]; - - script = ( - String("\n") + - script - ); - } - } - - Vector protocolHandlers = { "npm:", "node:" }; - for (const auto& entry : self.router->core->protocolHandlers.mapping) { - protocolHandlers.push_back(String(entry.first) + ":"); - } - - auto html = tmpl(String(string.UTF8String), Map { - {"protocol_handlers", join(protocolHandlers, " ")} - }); - - if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else { - html = script + html; - } - - data = [@(html.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; - } - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didReceiveData: data]; - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - } else if (path.size() == 0) { - if (userConfig.contains("webview_default_index")) { - path = userConfig["webview_default_index"]; - } else if (!path.starts_with("/socket/service-worker")) { - if (self.router->core->serviceWorker.registrations.size() > 0) { - auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; - - fetchRequest.client.id = clientId; - fetchRequest.client.preload = self.router->bridge->preload; - - fetchRequest.method = String(request.HTTPMethod.UTF8String); - fetchRequest.scheme = scheme; - - if (request.URL.host != nullptr) { - fetchRequest.host = String(request.URL.host.UTF8String); - } else { - fetchRequest.host = userConfig["meta_bundle_identifier"]; - } - - fetchRequest.pathname = String(request.URL.path.UTF8String); - - if (request.URL.query != nullptr) { - fetchRequest.query = String(request.URL.query.UTF8String); - } - - if (request.allHTTPHeaderFields != nullptr) { - for (NSString* key in request.allHTTPHeaderFields) { - const auto value = [request.allHTTPHeaderFields objectForKey: key]; - if (value != nullptr) { - if (String(key.UTF8String) == "referer" || String(key.UTF8String) == "Referer") { - if (self.router->location.workers.contains(value.UTF8String)) { - const auto workerLocation = self.router->location.workers[value.UTF8String]; - const auto entry = String(key.UTF8String) + ": " + workerLocation; - fetchRequest.headers.push_back(entry); - continue; - } else if (self.router->core->serviceWorker.bridge->router.location.workers.contains(value.UTF8String)) { - const auto workerLocation = self.router->core->serviceWorker.bridge->router.location.workers[value.UTF8String]; - const auto entry = String(key.UTF8String) + ": " + workerLocation; - fetchRequest.headers.push_back(entry); - continue; - } - } - - const auto entry = String(key.UTF8String) + ": " + String(value.UTF8String); - fetchRequest.headers.push_back(entry); - } - } - } - - if (request.HTTPBody && request.HTTPBody.bytes && request.HTTPBody.length > 0) { - fetchRequest.buffer.size = request.HTTPBody.length; - fetchRequest.buffer.bytes = new char[fetchRequest.buffer.size]{0}; - memcpy( - fetchRequest.buffer.bytes, - request.HTTPBody.bytes, - fetchRequest.buffer.size - ); - } - - const auto requestURL = String(request.URL.absoluteString.UTF8String); - const auto fetched = self.router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { - if (![self waitingForTask: task]) { - return; - } - - if (res.statusCode == 0) { - @try { - [task didFailWithError: [NSError - errorWithDomain: @(bundleIdentifier.c_str()) - code: 1 - userInfo: @{NSLocalizedDescriptionKey: @(res.buffer.bytes)} - ]]; - } @catch (id e) { - // ignore possible 'NSInternalInconsistencyException' - } - return; - } - - const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); - auto headers = [NSMutableDictionary dictionary]; - - for (const auto& line : webviewHeaders) { - const auto pair = split(trim(line), ':'); - const auto key = @(trim(pair[0]).c_str()); - const auto value = @(trim(pair[1]).c_str()); - headers[key] = value; - } - - for (const auto& entry : res.headers) { - auto pair = split(trim(entry), ':'); - auto key = @(trim(pair[0]).c_str()); - auto value = @(trim(pair[1]).c_str()); - headers[key] = value; - } - - @try { - if (![self waitingForTask: task]) { - return; - } - - const auto response = [[NSHTTPURLResponse alloc] - initWithURL: [NSURL URLWithString: @(requestURL.c_str())] - statusCode: res.statusCode - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - [task didReceiveResponse: response]; - - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - if (fetchRequest.method != "HEAD" && fetchRequest.method != "OPTIONS") { - const auto data = [NSData - dataWithBytes: res.buffer.bytes - length: res.buffer.size - ]; - - if (res.buffer.size && data.length > 0) { - [task didReceiveData: data]; - } - } - - [task didFinish]; - [self finalizeTask: task]; - #if !__has_feature(objc_arc) - [response release]; - #endif - } @catch (id e) { - // ignore possible 'NSInternalInconsistencyException' - } - }); - - if (fetched) { - [self enqueueTask: task withMessage: message]; - self.router->bridge->core->setTimeout(32000, [=] () mutable { - if ([self waitingForTask: task]) { - @try { - [self finalizeTask: task]; - [task didFailWithError: [NSError - errorWithDomain: @(bundleIdentifier.c_str()) - code: 1 - userInfo: @{NSLocalizedDescriptionKey: @"ServiceWorker request timed out."} - ]]; - } @catch (id e) { - } - } - }); - return; - } - } - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 404 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - } else if (resolved.redirect) { - auto redirectURL = path; - - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto redirectSource = String( - "" - ); - - headers[@"content-length"] = @(redirectSource.size()); - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: [NSURL URLWithString: @(redirectURL.c_str())] - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - - if (String(request.HTTPMethod.UTF8String) != "HEAD") { - data = [@(redirectSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; - [task didReceiveData: data]; - } - - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - components.host = @(""); - - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - components.path = [[[NSBundle mainBundle] resourcePath] - stringByAppendingPathComponent: [NSString - stringWithFormat: @"/ui/%s", path.c_str() - ] - ]; - #else - components.path = [[[NSBundle mainBundle] resourcePath] - stringByAppendingPathComponent: [NSString - stringWithFormat: @"/%s", path.c_str() - ] - ]; - #endif - - if (String(request.HTTPMethod.UTF8String) == "HEAD") { - NSNumber* size = nullptr; - NSError* error = nullptr; - [components.URL - getResourceValue: &size - forKey: NSURLFileSizeKey - error: &error - ]; - - if (size != nullptr) { - headers[@"content-length"] = size.stringValue; - } - } else if (String(request.HTTPMethod.UTF8String) == "GET") { - data = [NSData dataWithContentsOfURL: components.URL]; - } - - components.host = request.URL.host; - } else { - isModule = true; - - auto prefix = String( - path.starts_with(bundleIdentifier) - ? "" - : "socket/" - ); - - path = replace(path, bundleIdentifier + "/", ""); - - if (scheme == "node") { - const auto specifier = replace(path, ".js", ""); - if ( - std::find( - allowedNodeCoreModules.begin(), - allowedNodeCoreModules.end(), - specifier - ) == allowedNodeCoreModules.end() - ) { - const auto response = [NSHTTPURLResponse.alloc - initWithURL: request.URL - statusCode: 404 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didFinish]; - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - } - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - } - - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - components.path = [[[NSBundle mainBundle] resourcePath] - stringByAppendingPathComponent: [NSString - stringWithFormat: @"/ui/%s%s", prefix.c_str(), path.c_str() - ] - ]; - #else - components.path = [[[NSBundle mainBundle] resourcePath] - stringByAppendingPathComponent: [NSString - stringWithFormat: @"/%s%s", prefix.c_str(), path.c_str() - ] - ]; - #endif - auto moduleUri = "socket://" + bundleIdentifier + "/" + prefix + path; - auto moduleSource = trim(tmpl( - moduleTemplate, - Map { {"url", String(moduleUri)} } - )); - - if (String(request.HTTPMethod.UTF8String) == "GET") { - data = [@(moduleSource.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; - } - } - - auto exists = [fileManager - fileExistsAtPath: components.path - isDirectory: NULL]; - - components.path = @(path.c_str()); - - if (exists && data) { - headers[@"content-length"] = [@(data.length) stringValue]; - if (isModule && data.length > 0) { - headers[@"content-type"] = @"text/javascript"; - } else if (path.ends_with(".ts")) { - headers[@"content-type"] = @("application/typescript"); - } else if (path.ends_with(".cjs") || path.ends_with(".mjs")) { - headers[@"content-type"] = @("text/javascript"); - } else if (path.ends_with(".wasm")) { - headers[@"content-type"] = @("application/wasm"); - } else if (components.URL.pathExtension != nullptr) { - auto types = [UTType - typesWithTag: components.URL.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - if (types.count > 0 && types.firstObject.preferredMIMEType) { - headers[@"content-type"] = types.firstObject.preferredMIMEType; - } - } - } - - components.scheme = @("socket"); - headers[@"content-location"] = components.URL.path; - const auto socketModulePrefix = "socket://" + bundleIdentifier + "/socket/"; - - const auto absoluteURL = String(components.URL.absoluteString.UTF8String); - const auto absoluteURLPathExtension = components.URL.pathExtension != nullptr - ? String(components.URL.pathExtension.UTF8String) - : String(""); - - - if (!isModule && absoluteURL.starts_with(socketModulePrefix)) { - isModule = true; - } - - if (absoluteURLPathExtension.ends_with("html")) { - const auto string = [NSString.alloc initWithData: data encoding: NSUTF8StringEncoding]; - auto script = self.router->bridge->preload; - - if (userConfig.count("webview_importmap") > 0 && userConfig["webview_importmap"].size() > 0) { - const auto filename = Path(userConfig["webview_importmap"]).filename(); - const auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", filename.c_str() - #else - stringWithFormat: @"/%s", filename.c_str() - #endif - ] - ]]; - - const auto data = [NSData - dataWithContentsOfURL: [NSURL fileURLWithPath: url.path] - ]; - - if (data && data.length > 0) { - const auto string = [NSString.alloc - initWithData: data - encoding: NSUTF8StringEncoding - ]; - - script = ( - String("\n") + - script - ); - } - } - - Vector protocolHandlers = { "npm:", "node:" }; - for (const auto& entry : self.router->core->protocolHandlers.mapping) { - protocolHandlers.push_back(String(entry.first) + ":"); - } - - auto html = tmpl(String(string.UTF8String), Map { - {"protocol_handlers", join(protocolHandlers, " ")} - }); - - if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else { - html = script + html; - } - - data = [@(html.c_str()) dataUsingEncoding: NSUTF8StringEncoding]; - } - - const auto statusCode = exists ? 200 : 404; - const auto response = [NSHTTPURLResponse.alloc - initWithURL: components.URL - statusCode: statusCode - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - - if (data && data.length > 0) { - [task didReceiveData: data]; - } - - [task didFinish]; - - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - if (message.name == "post") { - auto id = std::stoull(message.get("id")); - auto post = self.router->core->getPost(id); - - headers[@"content-length"] = [@(post.length) stringValue]; - - if (post.headers.size() > 0) { - const auto lines = SSC::split(SSC::trim(post.headers), '\n'); - - for (const auto& line : lines) { - const auto pair = split(trim(line), ':'); - const auto key = @(trim(pair[0]).c_str()); - const auto value = @(trim(pair[1]).c_str()); - headers[key] = value; - } - } - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - - if (post.body) { - const auto data = [NSData dataWithBytes: post.body length: post.length]; - [task didReceiveData: data]; - } else { - const auto string = @(""); - const auto data = [string dataUsingEncoding: NSUTF8StringEncoding]; - [task didReceiveData: data]; - } - - [task didFinish]; - #if !__has_feature(objc_arc) - [response release]; - #endif - - self.router->core->removePost(id); - return; - } - - size_t bufsize = 0; - char *body = NULL; - - // if there is a body on the reuqest, pass it into the method router. - auto rawBody = request.HTTPBody; - - if (rawBody) { - const void* data = [rawBody bytes]; - bufsize = [rawBody length]; - body = (char *) data; - } - - [self enqueueTask: task withMessage: message]; - - auto invoked = self.router->invoke(message, body, bufsize, [=](Result result) { - // @TODO Communicate task cancellation to the route, so it can cancel its work. - if (![self waitingForTask: task]) { - return; - } - - auto id = result.id; - auto headers = [NSMutableDictionary dictionary]; - - headers[@"cache-control"] = @"no-cache"; - headers[@"access-control-allow-origin"] = @"*"; - headers[@"access-control-allow-methods"] = @"*"; - headers[@"access-control-allow-headers"] = @"*"; - headers[@"access-control-allow-credentials"] = @"true"; - - for (const auto& header : result.headers.entries) { - const auto key = @(trim(header.key).c_str()); - const auto value = @(trim(header.value.str()).c_str()); - headers[key] = value; - } - - NSData* data = nullptr; - if (result.post.eventStream != nullptr) { - *result.post.eventStream = [=]( - const char* name, - const char* data, - bool finished - ) { - if (![self waitingForTask: task]) { - return false; - } - - const auto eventName = @(name); - const auto eventData = @(data); - - if (eventName.length > 0 || eventData.length > 0) { - auto event = eventName.length > 0 && eventData.length > 0 - ? [NSString stringWithFormat: - @"event: %@\ndata: %@\n\n", eventName, eventData - ] - : eventData.length > 0 - ? [NSString stringWithFormat: @"data: %@\n\n", eventData] - : [NSString stringWithFormat: @"event: %@\n\n", eventName]; - - [task didReceiveData: [event dataUsingEncoding:NSUTF8StringEncoding]]; - } - - if (finished) { - if (![self waitingForTask: task]) { - return false; - } - [task didFinish]; - [self finalizeTask: task]; - } - - return true; - }; - - headers[@"content-type"] = @"text/event-stream"; - headers[@"cache-control"] = @"no-store"; - } else if (result.post.chunkStream != nullptr) { - *result.post.chunkStream = [=]( - const char* chunk, - size_t chunk_size, - bool finished - ) { - if (![self waitingForTask: task]) { - return false; - } - - [task didReceiveData: [NSData dataWithBytes:chunk length:chunk_size]]; - - if (finished) { - if (![self waitingForTask: task]) { - return false; - } - [task didFinish]; - [self finalizeTask: task]; - } - return true; - }; - headers[@"transfer-encoding"] = @"chunked"; - } else { - std::string json; - const char* body; - size_t size; - if (result.post.body != nullptr) { - body = result.post.body; - size = result.post.length; - } else { - json = result.str(); - body = json.c_str(); - size = json.size(); - headers[@"content-type"] = @"application/json"; - } - headers[@"content-length"] = @(size).stringValue; - data = [NSData dataWithBytes: body length: size]; - } - - @try { - if (![self waitingForTask: task]) { - return; - } - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: task.request.URL - statusCode: 200 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - [task didReceiveResponse: response]; - - if (data != nullptr) { - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - - [task didReceiveData: data]; - if (![self waitingForTask: task]) { - #if !__has_feature(objc_arc) - [response release]; - #endif - return; - } - [task didFinish]; - [self finalizeTask: task]; - } - - #if !__has_feature(objc_arc) - if (response != nullptr) [response release]; - #endif - } @catch (::id e) {} - }); - - if (!invoked) { - auto headers = [NSMutableDictionary dictionary]; - auto json = JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"}, - {"url", url} - }} - }; - - auto msg = JSON::Object(json).str(); - auto str = @(msg.c_str()); - auto data = [str dataUsingEncoding: NSUTF8StringEncoding]; - - headers[@"access-control-allow-credentials"] = @"true"; - headers[@"access-control-allow-origin"] = @"*"; - headers[@"access-control-allow-headers"] = @"*"; - headers[@"content-length"] = [@(msg.size()) stringValue]; - - auto response = [[NSHTTPURLResponse alloc] - initWithURL: request.URL - statusCode: 404 - HTTPVersion: @"HTTP/1.1" - headerFields: headers - ]; - - [task didReceiveResponse: response]; - [task didReceiveData: data]; - [task didFinish]; - #if !__has_feature(objc_arc) - [response release]; - #endif - } -} -@end - -@implementation SSCLocationPositionWatcher -+ (SSCLocationPositionWatcher*) positionWatcherWithIdentifier: (NSInteger) identifier - completion: (void (^)(CLLocation*)) completion { - auto watcher= [SSCLocationPositionWatcher new]; - watcher.identifier = identifier; - watcher.completion = [completion copy]; - return watcher; -} -@end - -@implementation SSCLocationObserver -- (id) init { - self = [super init]; - self.delegate = [[SSCLocationManagerDelegate alloc] initWithLocationObserver: self]; - self.isAuthorized = NO; - self.locationWatchers = [NSMutableArray new]; - self.activationCompletions = [NSMutableArray new]; - self.locationRequestCompletions = [NSMutableArray new]; - - self.locationManager = [CLLocationManager new]; - self.locationManager.delegate = self.delegate; - self.locationManager.desiredAccuracy = CLAccuracyAuthorizationFullAccuracy; - self.locationManager.pausesLocationUpdatesAutomatically = NO; - -#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - self.locationManager.allowsBackgroundLocationUpdates = YES; - self.locationManager.showsBackgroundLocationIndicator = YES; -#endif - - if ([CLLocationManager locationServicesEnabled]) { - if ( - #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorized || - #else - self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse || - #endif - self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways - ) { - self.isAuthorized = YES; - } - } - - return self; -} - -- (BOOL) attemptActivation { - if ([CLLocationManager locationServicesEnabled] == NO) { - return NO; - } - - if (self.isAuthorized) { - [self.locationManager requestLocation]; - return YES; - } - -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - [self.locationManager requestWhenInUseAuthorization]; -#else - [self.locationManager requestAlwaysAuthorization]; -#endif - - return YES; -} - -- (BOOL) attemptActivationWithCompletion: (void (^)(BOOL)) completion { - if (self.isAuthorized) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(YES); - }); - return YES; - } - - if ([self attemptActivation]) { - [self.activationCompletions addObject: [completion copy]]; - return YES; - } - - return NO; -} - -- (BOOL) getCurrentPositionWithCompletion: (void (^)(NSError*, CLLocation*)) completion { - return [self attemptActivationWithCompletion: ^(BOOL isAuthorized) { - auto userConfig = self.router->bridge->userConfig; - if (!isAuthorized) { - auto reason = @("Location observer could not be activated"); - - if (!self.locationManager) { - reason = @("Location observer manager is not initialized"); - } else if (!self.locationManager.location) { - reason = @("Location observer manager could not provide location"); - } - - auto error = [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) - code: -1 - userInfo: @{ - NSLocalizedDescriptionKey: reason - } - ]; - - return completion(error, nullptr); - } - - auto location = self.locationManager.location; - if (location.timestamp.timeIntervalSince1970 > 0) { - completion(nullptr, self.locationManager.location); - } else { - [self.locationRequestCompletions addObject: [completion copy]]; - } - - [self.locationManager requestLocation]; - }]; -} - -- (int) watchPositionForIdentifier: (NSInteger) identifier - completion: (void (^)(NSError*, CLLocation*)) completion { - SSCLocationPositionWatcher* watcher = nullptr; - BOOL exists = NO; - - for (SSCLocationPositionWatcher* existing in self.locationWatchers) { - if (existing.identifier == identifier) { - watcher = existing; - exists = YES; - break; - } - } - - if (!watcher) { - watcher = [SSCLocationPositionWatcher - positionWatcherWithIdentifier: identifier - completion: ^(CLLocation* location) { - completion(nullptr, location); - }]; - } - - auto performedActivation = [self attemptActivationWithCompletion: ^(BOOL isAuthorized) { - auto userConfig = self.router->bridge->userConfig; - if (!isAuthorized) { - auto error = [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) - code: -1 - userInfo: @{ - @"Error reason": @("Location observer could not be activated") - } - ]; - - return completion(error, nullptr); - } - - [self.locationManager startUpdatingLocation]; - - if (CLLocationManager.headingAvailable) { - [self.locationManager startUpdatingHeading]; - } - - [self.locationManager startMonitoringSignificantLocationChanges]; - }]; - - if (!performedActivation) { - #if !__has_feature(objc_arc) - [watcher release]; - #endif - return -1; - } - - if (!exists) { - [self.locationWatchers addObject: watcher]; - } - - return identifier; -} - -- (BOOL) clearWatch: (NSInteger) identifier { - for (SSCLocationPositionWatcher* watcher in self.locationWatchers) { - if (watcher.identifier == identifier) { - [self.locationWatchers removeObject: watcher]; - #if !__has_feature(objc_arc) - [watcher release]; - #endif - return YES; - } - } - - return NO; -} -@end - -@implementation SSCLocationManagerDelegate -- (id) initWithLocationObserver: (SSCLocationObserver*) locationObserver { - self = [super init]; - self.locationObserver = locationObserver; - locationObserver.delegate = self; - return self; -} - -- (void) locationManager: (CLLocationManager*) locationManager - didUpdateLocations: (NSArray*) locations { - auto locationRequestCompletions = [NSArray arrayWithArray: self.locationObserver.locationRequestCompletions]; - for (id item in locationRequestCompletions) { - auto completion = (void (^)(CLLocation*)) item; - completion(locations.firstObject); - [self.locationObserver.locationRequestCompletions removeObject: item]; - #if !__has_feature(objc_arc) - [completion release]; - #endif - } - - for (SSCLocationPositionWatcher* watcher in self.locationObserver.locationWatchers) { - watcher.completion(locations.firstObject); - } -} - -- (void) locationManager: (CLLocationManager*) locationManager - didFailWithError: (NSError*) error { - // TODO(@jwerle): handle location manager error - debug("locationManager:didFailWithError: %@", error); -} - -- (void) locationManager: (CLLocationManager*) locationManager - didFinishDeferredUpdatesWithError: (NSError*) error { - // TODO(@jwerle): handle deferred error - debug("locationManager:didFinishDeferredUpdatesWithError: %@", error); -} - -- (void) locationManagerDidPauseLocationUpdates: (CLLocationManager*) locationManager { - // TODO(@jwerle): handle pause for updates - debug("locationManagerDidPauseLocationUpdates"); -} - -- (void) locationManagerDidResumeLocationUpdates: (CLLocationManager*) locationManager { - // TODO(@jwerle): handle resume for updates - debug("locationManagerDidResumeLocationUpdates"); -} - -- (void) locationManager: (CLLocationManager*) locationManager - didVisit: (CLVisit*) visit { - auto locations = [NSArray arrayWithObject: locationManager.location]; - [self locationManager: locationManager didUpdateLocations: locations]; -} - -- (void) locationManager: (CLLocationManager*) locationManager - didChangeAuthorizationStatus: (CLAuthorizationStatus) status { - // XXX(@jwerle): this is a legacy callback - [self locationManagerDidChangeAuthorization: locationManager]; -} - -- (void) locationManagerDidChangeAuthorization: (CLLocationManager*) locationManager { - auto activationCompletions = [NSArray arrayWithArray: self.locationObserver.activationCompletions]; - if ( - #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - locationManager.authorizationStatus == kCLAuthorizationStatusAuthorized || - #else - locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse || - #endif - locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways - ) { - JSON::Object json = JSON::Object::Entries { - {"name", "geolocation"}, - {"state", "granted"} - }; - - self.locationObserver.router->emit("permissionchange", json.str()); - self.locationObserver.isAuthorized = YES; - for (id item in activationCompletions) { - auto completion = (void (^)(BOOL)) item; - completion(YES); - [self.locationObserver.activationCompletions removeObject: item]; - #if !__has_feature(objc_arc) - [completion release]; - #endif - } - } else { - JSON::Object json = JSON::Object::Entries { - {"name", "geolocation"}, - {"state", locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined - ? "prompt" - : "denied" - } - }; - - self.locationObserver.router->emit("permissionchange", json.str()); - self.locationObserver.isAuthorized = NO; - for (id item in activationCompletions) { - auto completion = (void (^)(BOOL)) item; - completion(NO); - [self.locationObserver.activationCompletions removeObject: item]; - #if !__has_feature(objc_arc) - [completion release]; - #endif - } - } -} -@end - -@implementation SSCUserNotificationCenterDelegate -- (void) userNotificationCenter: (UNUserNotificationCenter*) center - didReceiveNotificationResponse: (UNNotificationResponse*) response - withCompletionHandler: (void (^)(void)) completionHandler { - completionHandler(); - Lock lock(notificationRouterMapMutex); - auto id = String(response.notification.request.identifier.UTF8String); - Router* router = notificationRouterMap.find(id) != notificationRouterMap.end() - ? notificationRouterMap.at(id) - : nullptr; - - if (router) { - JSON::Object json = JSON::Object::Entries { - {"id", id}, - {"action", - [response.actionIdentifier isEqualToString: UNNotificationDefaultActionIdentifier] - ? "default" - : "dismiss" - } - }; - - notificationRouterMap.erase(id); - router->emit("notificationresponse", json.str()); - } -} - -- (void) userNotificationCenter: (UNUserNotificationCenter*) center - willPresentNotification: (UNNotification*) notification - withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler { - UNNotificationPresentationOptions options = UNNotificationPresentationOptionList; - - if (notification.request.content.sound != nullptr) { - options |= UNNotificationPresentationOptionSound; - } - - if (notification.request.content.attachments != nullptr) { - if (notification.request.content.attachments.count > 0) { - options |= UNNotificationPresentationOptionBanner; - } - } - - completionHandler(options); - - Lock lock(notificationRouterMapMutex); - auto __block id = String(notification.request.identifier.UTF8String); - Router* __block router = notificationRouterMap.find(id) != notificationRouterMap.end() - ? notificationRouterMap.at(id) - : nullptr; - - if (router) { - JSON::Object json = JSON::Object::Entries { - {"id", id} - }; - - router->emit("notificationpresented", json.str()); - // look for dismissed notification - auto timer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [notificationCenter getDeliveredNotificationsWithCompletionHandler: ^(NSArray *notifications) { - BOOL found = NO; - - for (UNNotification* notification in notifications) { - if (String(notification.request.identifier.UTF8String) == id) { - return; - } - } - - [timer invalidate]; - JSON::Object json = JSON::Object::Entries { - {"id", id}, - {"action", "dismiss"} - }; - - router->emit("notificationresponse", json.str()); - - Lock lock(notificationRouterMapMutex); - if (notificationRouterMap.contains(id)) { - notificationRouterMap.erase(id); - } - }]; - }]; - - [NSRunLoop.mainRunLoop - addTimer: timer - forMode: NSDefaultRunLoopMode - ]; - } -} -@end -#endif - -namespace SSC::IPC { - static Vector instances; - static Mutex mutex; - -#if SSC_PLATFORM_DESKTOP - static FileSystemWatcher* fileSystemWatcher = nullptr; -#endif - - Bridge::Bridge (Core *core, Map userConfig) - : userConfig(userConfig), - router() - { - Lock lock(SSC::IPC::mutex); - instances.push_back(this); - - this->id = rand64(); - this->core = core; - this->router.core = core; - - this->bluetooth.sendFunction = [this]( - const String& seq, - const JSON::Any value, - const SSC::Post post - ) { - this->router.send(seq, value.str(), post); - }; - - this->bluetooth.emitFunction = [this]( - const String& seq, - const JSON::Any value - ) { - this->router.emit(seq, value.str()); - }; - - #if SSC_PLATFORM_DESKTOP - auto defaultUserConfig = SSC::getUserConfig(); - if ( - fileSystemWatcher == nullptr && - isDebugEnabled() && - defaultUserConfig["webview_watch"] == "true" - ) { - fileSystemWatcher = new FileSystemWatcher(getcwd()); - fileSystemWatcher->core = this->core; - fileSystemWatcher->start([=]( - const auto& path, - const auto& events, - const auto& context - ) mutable { - Lock lock(SSC::IPC::mutex); - - static const auto cwd = getcwd(); - const auto relativePath = std::filesystem::relative(path, cwd).string(); - const auto json = JSON::Object::Entries {{"path", relativePath}}; - const auto result = SSC::IPC::Result(json); - - for (auto& bridge : instances) { - auto userConfig = bridge->userConfig; - if ( - !platform.ios && - !platform.android && - userConfig["webview_watch"] == "true" && - bridge->userConfig["webview_service_worker_mode"] != "hybrid" && - (!userConfig.contains("webview_watch_reload") || userConfig.at("webview_watch_reload") != "false") - ) { - // check if changed path was a service worker, if so unregister it so it can be reloaded - for (const auto& entry : fileSystemWatcher->core->serviceWorker.registrations) { - const auto& registration = entry.second; - #if defined(__ANDROID__) - auto scriptURL = String("https://"); - #else - auto scriptURL = String("socket://"); - #endif - - scriptURL += userConfig["meta_bundle_identifier"]; - - if (!relativePath.starts_with("/")) { - scriptURL += "/"; - } - - scriptURL += relativePath; - if (registration.scriptURL == scriptURL) { - // 1. unregister service worker - // 2. re-register service worker - // 3. wait for it to be registered - // 4. emit 'filedidchange' event - bridge->core->serviceWorker.unregisterServiceWorker(entry.first); - bridge->core->setTimeout(8, [bridge, result, ®istration] () { - bridge->core->setInterval(8, [bridge, result, ®istration] (auto cancel) { - if (registration.state == ServiceWorkerContainer::Registration::State::Activated) { - cancel(); - - uint64_t timeout = 500; - if (bridge->userConfig["webview_watch_service_worker_reload_timeout"].size() > 0) { - try { - timeout = std::stoull(bridge->userConfig["webview_watch_service_worker_reload_timeout"]); - } catch (...) {} - } - - bridge->core->setTimeout(timeout, [bridge, result] () { - bridge->router.emit("filedidchange", result.json().str()); - }); - } - }); - - bridge->core->serviceWorker.registerServiceWorker(registration.options); - }); - return; - } - } - } - - bridge->router.emit("filedidchange", result.json().str()); - } - }); - } - #endif - - this->router.init(this); - } - - Bridge::~Bridge () { - Lock lock(SSC::IPC::mutex); - const auto cursor = std::find(instances.begin(), instances.end(), this); - if (cursor != instances.end()) { - instances.erase(cursor); - } - - #if SSC_PLATFORM_DESKTOP - if (instances.size() == 0) { - if (fileSystemWatcher) { - fileSystemWatcher->stop(); - delete fileSystemWatcher; - } - } - #endif - } - - bool Router::hasMappedBuffer (int index, const Message::Seq seq) { - Lock lock(this->mutex); - auto key = std::to_string(index) + seq; - return this->buffers.find(key) != this->buffers.end(); - } - - MessageBuffer Router::getMappedBuffer (int index, const Message::Seq seq) { - if (this->hasMappedBuffer(index, seq)) { - Lock lock(this->mutex); - auto key = std::to_string(index) + seq; - return this->buffers.at(key); - } - - return MessageBuffer {}; - } - - void Router::setMappedBuffer ( - int index, - const Message::Seq seq, - MessageBuffer buffer - ) { - Lock lock(this->mutex); - auto key = std::to_string(index) + seq; - this->buffers.insert_or_assign(key, buffer); - } - - void Router::removeMappedBuffer (int index, const Message::Seq seq) { - Lock lock(this->mutex); - if (this->hasMappedBuffer(index, seq)) { - auto key = std::to_string(index) + seq; - this->buffers.erase(key); - } - } - - bool Bridge::route (const String& uri, const char *bytes, size_t size) { - return this->route(uri, bytes, size, nullptr); - } - - bool Bridge::route ( - const String& uri, - const char* bytes, - size_t size, - Router::ResultCallback callback - ) { - if (callback != nullptr) { - return this->router.invoke(uri, bytes, size, callback); - } else { - return this->router.invoke(uri, bytes, size); - } - } - - const Vector& Bridge::getAllowedNodeCoreModules () const { - return allowedNodeCoreModules; - } - - /* - . - ├── a-conflict-index - │ └── index.html - ├── a-conflict-index.html - ├── an-index-file - │ ├── a-html-file.html - │ └── index.html - ├── another-file.html - └── index.html - - Subtleties: - Direct file navigation always wins - /foo/index.html have precedent over foo.html - /foo redirects to /foo/ when there is a /foo/index.html - - '/' -> '/index.html' - '/index.html' -> '/index.html' - '/a-conflict-index' -> redirect to '/a-conflict-index/' - '/another-file' -> '/another-file.html' - '/another-file.html' -> '/another-file.html' - '/an-index-file/' -> '/an-index-file/index.html' - '/an-index-file' -> redirect to '/an-index-file/' - '/an-index-file/a-html-file' -> '/an-index-file/a-html-file.html' - */ - Router::WebViewURLPathResolution Router::resolveURLPathForWebView (String inputPath, const String& basePath) { - namespace fs = std::filesystem; - - if (inputPath.starts_with("/")) { - inputPath = inputPath.substr(1); - } - - // Resolve the full path - const auto fullPath = (fs::path(basePath) / fs::path(inputPath)).make_preferred(); - - // 1. Try the given path if it's a file - if (fs::is_regular_file(fullPath)) { - return Router::WebViewURLPathResolution{"/" + replace(fs::relative(fullPath, basePath).string(), "\\\\", "/")}; - } - - // 2. Try appending a `/` to the path and checking for an index.html - const auto indexPath = fullPath / fs::path("index.html"); - if (fs::is_regular_file(indexPath)) { - if (fullPath.string().ends_with("\\") || fullPath.string().ends_with("/")) { - return Router::WebViewURLPathResolution{ - .path = "/" + replace(fs::relative(indexPath, basePath).string(), "\\\\", "/"), - .redirect = false - }; - } else { - return Router::WebViewURLPathResolution{ - .path = "/" + replace(fs::relative(fullPath, basePath).string(), "\\\\", "/") + "/", - .redirect = true - }; - } - } - - // 3. Check if appending a .html file extension gives a valid file - auto htmlPath = fullPath; - htmlPath.replace_extension(".html"); - if (fs::is_regular_file(htmlPath)) { - return Router::WebViewURLPathResolution{"/" + replace(fs::relative(htmlPath, basePath).string(), "\\\\", "/")}; - } - - // If no valid path is found, return empty string - return Router::WebViewURLPathResolution{}; - }; - - Router::WebViewURLComponents Router::parseURL (const SSC::String& url) { - Router::WebViewURLComponents components; - components.originalUrl = url; - - size_t queryPos = url.find('?'); - size_t fragmentPos = url.find('#'); - - if (queryPos != SSC::String::npos) { - components.path = url.substr(0, queryPos); - } else if (fragmentPos != SSC::String::npos) { - components.path = url.substr(0, fragmentPos); - } else { - components.path = url; - } - - if (queryPos != SSC::String::npos) { // Extract the query string - components.queryString = url.substr( - queryPos + 1, - fragmentPos != SSC::String::npos - ? fragmentPos - queryPos - 1 - : SSC::String::npos - ); - } - - if (fragmentPos != SSC::String::npos) { // Extract the fragment - components.fragment = url.substr(fragmentPos + 1); - } - - return components; - } - - static const Map getWebViewNavigatorMounts () { - static const auto userConfig = getUserConfig(); - #if defined(_WIN32) - static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); - #elif defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) - static const auto HOME = String(NSHomeDirectory().UTF8String); - #else - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - #endif - - static Map mounts; - - if (mounts.size() > 0) { - return mounts; - } - - Map mappings = { - {"\\$HOST_HOME", HOME}, - {"~", HOME}, - - {"\\$HOST_CONTAINER", - #if defined(__APPLE__) - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) objectAtIndex: 0].UTF8String - #else - // `homeDirectoryForCurrentUser` resolves to sandboxed container - // directory when in "sandbox" mode, otherwise the user's HOME directory - NSFileManager.defaultManager.homeDirectoryForCurrentUser.absoluteString.UTF8String - #endif - #elif defined(__linux__) - // TODO(@jwerle): figure out `$HOST_CONTAINER` for Linux - getcwd(), - #elif defined(_WIN32) - // TODO(@jwerle): figure out `$HOST_CONTAINER` for Windows - getcwd(), - #endif - }, - - {"\\$HOST_PROCESS_WORKING_DIRECTORY", - #if defined(__APPLE__) - NSBundle.mainBundle.resourcePath.UTF8String - #else - getcwd(), - #endif - } - }; - - for (const auto& tuple : userConfig) { - if (tuple.first.starts_with("webview_navigator_mounts_")) { - auto key = replace(tuple.first, "webview_navigator_mounts_", ""); - - if (key.starts_with("android") && !platform.android) continue; - if (key.starts_with("ios") && !platform.ios) continue; - if (key.starts_with("linux") && !platform.linux) continue; - if (key.starts_with("mac") && !platform.mac) continue; - if (key.starts_with("win") && !platform.win) continue; - - key = replace(key, "android_", ""); - key = replace(key, "ios_", ""); - key = replace(key, "linux_", ""); - key = replace(key, "mac_", ""); - key = replace(key, "win_", ""); - - String path = key; - - for (const auto& map : mappings) { - path = replace(path, map.first, map.second); - } - - const auto& value = tuple.second; - mounts.insert_or_assign(path, value); - } - } - - return mounts; - } - - Router::WebViewNavigatorMount Router::resolveNavigatorMountForWebView (const String& path) { - static const auto mounts = getWebViewNavigatorMounts(); - - for (const auto& tuple : mounts) { - if (path.starts_with(tuple.second)) { - const auto relative = replace(path, tuple.second, ""); - const auto resolution = resolveURLPathForWebView(relative, tuple.first); - if (resolution.path.size() > 0) { - const auto resolved = Path(tuple.first) / resolution.path.substr(1); - return WebViewNavigatorMount { - resolution, - resolved.string(), - path - }; - } else { - const auto resolved = relative.starts_with("/") - ? Path(tuple.first) / relative.substr(1) - : Path(tuple.first) / relative; - - return WebViewNavigatorMount { - resolution, - resolved.string(), - path - }; - } - } - } - - return WebViewNavigatorMount {}; - } - - Router::Router () {} - - void Router::init (Bridge* bridge) { - this->bridge = bridge; - - if (bridge->userConfig["permissions_allow_service_worker"] != "false") { - bridge->userConfig["webview_service-workers_/socket/npm/"] = "/socket/npm/service-worker.js"; - } - - #if defined(__APPLE__) - this->networkStatusObserver = [SSCIPCNetworkStatusObserver new]; - this->locationObserver = [SSCLocationObserver new]; - this->schemeHandler = [SSCIPCSchemeHandler new]; - - [this->schemeHandler setRouter: this]; - [this->locationObserver setRouter: this]; - [this->networkStatusObserver setRouter: this]; - #endif - - initRouterTable(this); - registerSchemeHandler(this); - - #if SSC_PLATFORM_LINUX && !SSC_PLATFORM_ANDROID - ProtocolHandlers::Mapping protocolHandlerMappings = { - {"npm", ProtocolHandlers::Protocol { "npm" }} - }; - - static Set registeredProtocolHandlerMappings; - - for (const auto& entry : split(bridge->userConfig["webview_protocol-handlers"], " ")) { - const auto scheme = replace(trim(entry), ":", ""); - protocolHandlerMappings.insert_or_assign(scheme, ProtocolHandlers::Protocol { scheme }); - } + webkit_uri_scheme_response_set_http_headers(response, headers); + webkit_uri_scheme_response_set_content_type(response, "text/html"); + webkit_uri_scheme_request_finish_with_response(request, response); - for (const auto& entry : bridge->userConfig) { - const auto& key = entry.first; - if (key.starts_with("webview_protocol-handlers_")) { - const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; - const auto data = entry.second; - if (data.starts_with(".") || data.starts_with("/")) { - protocolHandlerMappings.insert_or_assign(scheme, ProtocolHandlers::Protocol { scheme }); + g_object_unref(stream); } else { - protocolHandlerMappings.insert_or_assign(scheme, ProtocolHandlers::Protocol { scheme, { data } }); + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Failed to create response stream" + ) + ); } - } - } - for (const auto& entry : protocolHandlerMappings) { - const auto& scheme = entry.first; - const auto& data = entry.second.data; - - if (registeredProtocolHandlerMappings.contains(scheme)) { - continue; + return; } - - // manually handle NPM here - if (scheme == "npm" || bridge->core->protocolHandlers.registerHandler(scheme, data)) { - auto ctx = webkit_web_context_get_default(); - auto security = webkit_web_context_get_security_manager(ctx); - registeredProtocolHandlerMappings.insert(scheme); - webkit_security_manager_register_uri_scheme_as_display_isolated(security, scheme.c_str()); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, scheme.c_str()); - webkit_security_manager_register_uri_scheme_as_secure(security, scheme.c_str()); - webkit_security_manager_register_uri_scheme_as_local(security, scheme.c_str()); - webkit_web_context_register_uri_scheme(ctx, scheme.c_str(), [](auto request, auto ptr) { - static const auto MAX_BODY_BYTES = 4 * 1024 * 1024; - IPC::Router* router = nullptr; - String protocol; - String pathname; - - auto webview = webkit_uri_scheme_request_get_web_view(request); - - for (auto& window : App::instance()->getWindowManager()->windows) { - if ( - window != nullptr && - window->bridge != nullptr && - WEBKIT_WEB_VIEW(window->webview) == webview - ) { - router = &window->bridge->router; - break; - } - } - - if (!router) { - auto userConfig = SSC::getUserConfig(); - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Missing router in request" - ) - ); - } - + } else if (path.size() == 0) { + if (userConfig.contains("webview_default_index")) { + path = userConfig["webview_default_index"]; + } else { + if (router->core->serviceWorker.registrations.size() > 0) { + auto requestHeaders = webkit_uri_scheme_request_get_http_headers(request); auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; - auto userConfig = router->bridge->userConfig; - auto headers = webkit_uri_scheme_request_get_http_headers(request); - auto method = String(webkit_uri_scheme_request_get_http_method(request)); - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto cwd = getcwd(); - - if (uri.find_first_of(":") != String::npos) { - protocol = uri.substr(0, uri.find_first_of(":")); - } - fetchRequest.client.id = router->bridge->id; + fetchRequest.client.id = clientId; fetchRequest.client.preload = router->bridge->preload; - fetchRequest.method = method; - fetchRequest.scheme = protocol; - - pathname = uri.substr(protocol.size() + 1, uri.size()); - - if (pathname.starts_with("//")) { - pathname = pathname.substr(2, pathname.size()); - if (pathname.find_first_of("/") != String::npos) { - fetchRequest.host = pathname.substr(0, pathname.find_first_of("/")); - pathname = pathname.substr(pathname.find_first_of("/"), pathname.size()); - } else { - fetchRequest.host = pathname; - pathname = "/"; - } - } else { - fetchRequest.host = userConfig["meta_bundle_identifier"]; - } - - if (!pathname.starts_with("/")) { - pathname = String("/") + pathname; - } - - // handle internal `npm:` protocol scheme - if (protocol == "npm") { - pathname = String("/socket/npm") + pathname; - } - const auto scope = router->core->protocolHandlers.getServiceWorkerScope(protocol); - const auto parsed = Router::parseURL(pathname); - - fetchRequest.query = parsed.queryString; - - if (scope.size() > 0) { - fetchRequest.pathname = scope + parsed.path; - } else { - fetchRequest.pathname = parsed.path; - } + fetchRequest.method = method; + fetchRequest.scheme = "socket"; + fetchRequest.host = userConfig["meta_bundle_identifier"]; + fetchRequest.pathname = parsedPath.path; + fetchRequest.query = parsedPath.queryString; soup_message_headers_foreach( - headers, + requestHeaders, [](auto name, auto value, auto userData) { auto fetchRequest = reinterpret_cast(userData); const auto entry = String(name) + ": " + String(value); @@ -6110,7 +363,7 @@ namespace SSC::IPC { &fetchRequest ); - if (method == "POST" || method == "PUT") { + if ((method == "POST" || method == "PUT")) { auto body = webkit_uri_scheme_request_get_http_body(request); if (body) { GError* error = nullptr; @@ -6201,478 +454,510 @@ namespace SSC::IPC { delete fetchRequest.buffer.bytes; } } + } + } + } else if (resolved.redirect) { + auto redirectURL = resolved.path; + if (parsedPath.queryString.size() > 0) { + redirectURL += "?" + parsedPath.queryString; + } - auto stream = g_memory_input_stream_new_from_data(nullptr, 0, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } + if (parsedPath.fragment.size() > 0) { + redirectURL += "#" + parsedPath.fragment; + } + + auto redirectSource = String( + "" + ); - auto response = webkit_uri_scheme_response_new(stream, 0); + auto size = redirectSource.size(); + auto bytes = redirectSource.data(); + auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - webkit_uri_scheme_response_set_status(response, 404, "Not found"); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - }, - nullptr, - 0); + if (!stream) { + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Failed to create response stream" + ) + ); + return; } - } - #endif - this->preserveCurrentTable(); + auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); + auto response = webkit_uri_scheme_response_new(stream, (gint64) size); + auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); + + soup_message_headers_append(headers, "location", redirectURL.c_str()); + soup_message_headers_append(headers, "content-location", contentLocation.c_str()); + + webkit_uri_scheme_response_set_http_headers(response, headers); + webkit_uri_scheme_response_set_content_type(response, "text/html"); + webkit_uri_scheme_request_finish_with_response(request, response); + + g_object_unref(stream); + return; + } - #if defined(__APPLE__) - [this->networkStatusObserver start]; + if (mount.path.size() > 0) { + path = mount.path; + } else if (path.size() > 0) { + path = fs::absolute(fs::path(cwd) / path.substr(1)).string(); + } - if (bridge->userConfig["permissions_allow_notifications"] != "false") { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + if (path.size() == 0 || !fs::exists(path)) { + auto stream = g_memory_input_stream_new_from_data(nullptr, 0, 0); - if (!notificationCenter.delegate) { - notificationCenter.delegate = [SSCUserNotificationCenterDelegate new]; + if (!stream) { + webkit_uri_scheme_request_finish_error( + request, + g_error_new( + g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), + 1, + "Failed to create response stream" + ) + ); + return; } - UNAuthorizationStatus __block currentAuthorizationStatus; - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - currentAuthorizationStatus = settings.authorizationStatus; - this->notificationPollTimer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { - // look for authorization status changes - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - if (currentAuthorizationStatus != settings.authorizationStatus) { - JSON::Object json; - currentAuthorizationStatus = settings.authorizationStatus; - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - json = JSON::Object::Entries { - {"name", "notifications"}, - {"state", "denied"} - }; - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - json = JSON::Object::Entries { - {"name", "notifications"}, - {"state", "prompt"} - }; - } else { - json = JSON::Object::Entries { - {"name", "notifications"}, - {"state", "granted"} - }; - } + auto response = webkit_uri_scheme_response_new(stream, 0); - this->emit("permissionchange", json.str()); - } - }]; - }]; - - [NSRunLoop.mainRunLoop - addTimer: this->notificationPollTimer - forMode: NSDefaultRunLoopMode - ]; - }]; + webkit_uri_scheme_response_set_status(response, 404, "Not found"); + webkit_uri_scheme_request_finish_with_response(request, response); + g_object_unref(stream); + return; } - #endif - } - Router::~Router () { - #if defined(__APPLE__) - if (this->networkStatusObserver != nullptr) { - #if !__has_feature(objc_arc) - [this->networkStatusObserver release]; - #endif - } + WebKitURISchemeResponse* response = nullptr; + GInputStream* stream = nullptr; + gchar* mimeType = nullptr; + GError* error = nullptr; + char* data = nullptr; - if (this->locationObserver != nullptr) { - #if !__has_feature(objc_arc) - [this->locationObserver release]; - #endif - } + auto webviewHeaders = split(userConfig["webview_headers"], '\n'); + auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - if (this->schemeHandler != nullptr) { - #if !__has_feature(objc_arc) - [this->schemeHandler release]; - #endif - } + if (path.ends_with(".html")) { + auto script = router->bridge->preload; - if (this->notificationPollTimer != nullptr) { - [this->notificationPollTimer invalidate]; + if (userConfig["webview_importmap"].size() > 0) { + const auto file = Path(userConfig["webview_importmap"]); + + if (fs::exists(file)) { + String string; + std::ifstream stream(file.string().c_str()); + auto buffer = std::istreambuf_iterator(stream); + auto end = std::istreambuf_iterator(); + string.assign(buffer, end); + stream.close(); + + script = ( + String("\n") + + script + ); + } } - this->notificationPollTimer = nullptr; - this->networkStatusObserver = nullptr; - this->locationObserver = nullptr; - this->schemeHandler = nullptr; - #endif - } + const auto file = Path(path); - void Router::preserveCurrentTable () { - Lock lock(mutex); - this->preserved = this->table; - } + if (fs::exists(file)) { + char* contents = nullptr; + gsize size = 0; + if (g_file_get_contents(file.c_str(), &contents, &size, &error)) { + String html = contents; + Vector protocolHandlers = { "npm:", "node:" }; + for (const auto& entry : router->core->protocolHandlers.mapping) { + protocolHandlers.push_back(String(entry.first) + ":"); + } + + html = tmpl(html, Map { + {"protocol_handlers", join(protocolHandlers, " ")} + }); + + if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else if (html.find("") != String::npos) { + html = replace(html, "", String("" + script)); + } else { + html = script + html; + } + + data = new char[html.size()]{0}; + memcpy(data, html.data(), html.size()); + g_free(contents); + + stream = g_memory_input_stream_new_from_data(data, (gint64) html.size(), nullptr); + + if (stream) { + response = webkit_uri_scheme_response_new(stream, -1); + } else { + delete [] data; + data = nullptr; + } + } + } + } else { + auto file = g_file_new_for_path(path.c_str()); + auto size = fs::file_size(path); - uint64_t Router::listen (const String& name, MessageCallback callback) { - Lock lock(mutex); + if (file) { + stream = (GInputStream*) g_file_read(file, nullptr, &error); + g_object_unref(file); + } - if (!this->listeners.contains(name)) { - this->listeners[name] = Vector(); + if (stream) { + response = webkit_uri_scheme_response_new(stream, (gint64) size); + g_object_unref(stream); + } } - auto& listeners = this->listeners.at(name); - auto token = rand64(); - listeners.push_back(MessageCallbackListenerContext { token , callback }); - return token; - } + if (!stream) { + webkit_uri_scheme_request_finish_error(request, error); + g_error_free(error); + return; + } - bool Router::unlisten (const String& name, uint64_t token) { - Lock lock(mutex); + soup_message_headers_append(headers, "cache-control", "no-cache"); + soup_message_headers_append(headers, "access-control-allow-origin", "*"); + soup_message_headers_append(headers, "access-control-allow-methods", "*"); + soup_message_headers_append(headers, "access-control-allow-headers", "*"); + soup_message_headers_append(headers, "access-control-allow-credentials", "true"); - if (!this->listeners.contains(name)) { - return false; + for (const auto& line : webviewHeaders) { + auto pair = split(trim(line), ':'); + auto key = trim(pair[0]); + auto value = trim(pair[1]); + soup_message_headers_append(headers, key.c_str(), value.c_str()); } - auto& listeners = this->listeners.at(name); - for (int i = 0; i < listeners.size(); ++i) { - const auto& listener = listeners[i]; - if (listener.token == token) { - listeners.erase(listeners.begin() + i); - return true; + webkit_uri_scheme_response_set_http_headers(response, headers); + + if (path.ends_with(".wasm")) { + webkit_uri_scheme_response_set_content_type(response, "application/wasm"); + } else if (path.ends_with(".cjs") || path.ends_with(".mjs")) { + webkit_uri_scheme_response_set_content_type(response, "text/javascript"); + } else if (path.ends_with(".ts")) { + webkit_uri_scheme_response_set_content_type(response, "application/typescript"); + } else { + mimeType = g_content_type_guess(path.c_str(), nullptr, 0, nullptr); + if (mimeType) { + webkit_uri_scheme_response_set_content_type(response, mimeType); + } else { + webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); } } - return false; - } + webkit_uri_scheme_request_finish_with_response(request, response); - void Router::map (const String& name, MessageCallback callback) { - return this->map(name, true, callback); - } + if (data) { + g_input_stream_close_async(stream, 0, nullptr, +[]( + GObject* object, + GAsyncResult* asyncResult, + gpointer userData + ) { + auto stream = (GInputStream*) object; + g_input_stream_close_finish(stream, asyncResult, nullptr); + g_object_unref(stream); + g_idle_add_full( + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) [](gpointer userData) { + return G_SOURCE_REMOVE; + }, + userData, + [](gpointer userData) { + delete [] static_cast(userData); + } + ); + }, data); + } else { + g_object_unref(stream); + } + + if (mimeType) { + g_free(mimeType); + } + }, + router, + 0); + + webkit_web_context_register_uri_scheme(ctx, "node", [](auto request, auto ptr) { + auto uri = String(webkit_uri_scheme_request_get_uri(request)); + auto router = reinterpret_cast(ptr); + auto userConfig = router->bridge->userConfig; - void Router::map (const String& name, bool async, MessageCallback callback) { - Lock lock(mutex); + const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - String data = name; - // URI hostnames are not case sensitive. Convert to lowercase. - std::transform(data.begin(), data.end(), data.begin(), - [](unsigned char c) { return std::tolower(c); }); - if (callback != nullptr) { - table.insert_or_assign(data, MessageCallbackContext { async, callback }); + if (uri.starts_with("node:///")) { + uri = uri.substr(10); + } else if (uri.starts_with("node://")) { + uri = uri.substr(9); + } else if (uri.starts_with("node:")) { + uri = uri.substr(7); } - } - void Router::unmap (const String& name) { - Lock lock(mutex); + auto path = String("socket/" + uri); + auto ext = fs::path(path).extension().string(); - String data = name; - // URI hostnames are not case sensitive. Convert to lowercase. - std::transform(data.begin(), data.end(), data.begin(), - [](unsigned char c) { return std::tolower(c); }); - if (table.find(data) != table.end()) { - table.erase(data); + if (ext.size() > 0 && !ext.starts_with(".")) { + ext = "." + ext; } - } - - bool Router::invoke (const String& uri, const char *bytes, size_t size) { - return this->invoke(uri, bytes, size, [this](auto result) { - this->send(result.seq, result.str(), result.post); - }); - } - - bool Router::invoke (const String& uri, ResultCallback callback) { - return this->invoke(uri, nullptr, 0, callback); - } - bool Router::invoke ( - const String& uri, - const char *bytes, - size_t size, - ResultCallback callback - ) { - if (this->core->shuttingDown) { - return false; + if (ext.size() == 0 && !path.ends_with(".js")) { + path += ".js"; + ext = ".js"; } - auto message = Message(uri, true); - return this->invoke(message, bytes, size, callback); - } + uri = "socket://" + bundleIdentifier + "/" + path; - bool Router::invoke ( - const Message& message, - const char *bytes, - size_t size, - ResultCallback callback - ) { - if (this->core->shuttingDown) { - return false; - } + auto moduleSource = trim(tmpl( + moduleTemplate, + Map { {"url", String(uri)} } + )); - auto name = message.name; - MessageCallbackContext ctx; + auto size = moduleSource.size(); + auto bytes = moduleSource.data(); + auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); + auto response = webkit_uri_scheme_response_new(stream, size); - // URI hostnames are not case sensitive. Convert to lowercase. - std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { - return std::tolower(c); - }); + webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); + webkit_uri_scheme_request_finish_with_response(request, response); + g_object_unref(stream); + }, + router, + 0); - do { - // lookup router function in the preserved table, - // then the public table, return if unable to determine a context - if (this->preserved.find(name) != this->preserved.end()) { - ctx = this->preserved.at(name); - } else if (this->table.find(name) != this->table.end()) { - ctx = this->table.at(name); - } else { - return false; - } - } while (0); - - if (ctx.callback != nullptr) { - Message msg(message); - // decorate message with buffer if buffer was previously - // mapped with `ipc://buffer.map`, which we do on Linux - if (this->hasMappedBuffer(msg.index, msg.seq)) { - msg.buffer = this->getMappedBuffer(msg.index, msg.seq); - this->removeMappedBuffer(msg.index, msg.seq); - } else if (bytes != nullptr && size > 0) { - // alloc and copy `bytes` into `msg.buffer.bytes - caller owns `bytes` - // `msg.buffer.bytes` is free'd in CLEANUP_AFTER_INVOKE_CALLBACK - msg.buffer.bytes = new char[size]{0}; - msg.buffer.size = size; - memcpy(msg.buffer.bytes, bytes, size); - } + webkit_security_manager_register_uri_scheme_as_display_isolated(security, "ipc"); + webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "ipc"); + webkit_security_manager_register_uri_scheme_as_secure(security, "ipc"); + webkit_security_manager_register_uri_scheme_as_local(security, "ipc"); - // named listeners - do { - auto listeners = this->listeners[name]; - for (const auto& listener : listeners) { - listener.callback(msg, this, [](const auto& _) {}); - } - } while (0); + if (devHost.starts_with("http:")) { + webkit_security_manager_register_uri_scheme_as_display_isolated(security, "http"); + webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "http"); + webkit_security_manager_register_uri_scheme_as_secure(security, "http"); + webkit_security_manager_register_uri_scheme_as_local(security, "http"); + } - // wild card (*) listeners - do { - auto listeners = this->listeners["*"]; - for (const auto& listener : listeners) { - listener.callback(msg, this, [](const auto& _) {}); - } - } while (0); - - if (ctx.async) { - auto dispatched = this->dispatch([ctx, msg, callback, this]() mutable { - ctx.callback(msg, this, [msg, callback, this](const auto result) mutable { - if (result.seq == "-1") { - this->send(result.seq, result.str(), result.post); - } else { - callback(result); - } + webkit_security_manager_register_uri_scheme_as_display_isolated(security, "socket"); + webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "socket"); + webkit_security_manager_register_uri_scheme_as_secure(security, "socket"); + webkit_security_manager_register_uri_scheme_as_local(security, "socket"); - CLEANUP_AFTER_INVOKE_CALLBACK(this, msg, result); - }); - }); + webkit_security_manager_register_uri_scheme_as_display_isolated(security, "node"); + webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "node"); + webkit_security_manager_register_uri_scheme_as_secure(security, "node"); + webkit_security_manager_register_uri_scheme_as_local(security, "node"); +#endif +} - if (!dispatched) { - CLEANUP_AFTER_INVOKE_CALLBACK(this, msg, Result{}); - } +namespace SSC::IPC { + Mutex Router::notificationMapMutex; + std::map Router::notificationMap; - return dispatched; - } else { - ctx.callback(msg, this, [msg, callback, this](const auto result) mutable { - if (result.seq == "-1") { - this->send(result.seq, result.str(), result.post); - } else { - callback(result); - } - CLEANUP_AFTER_INVOKE_CALLBACK(this, msg, result); - }); + static Vector instances; + static Mutex mutex; - return true; - } - } +#if SSC_PLATFORM_DESKTOP + static FileSystemWatcher* fileSystemWatcher = nullptr; +#endif - return false; - } + Bridge::Bridge (Core *core, Map userConfig) + : userConfig(userConfig), + router(), + navigator(this) + { + Lock lock(SSC::IPC::mutex); + instances.push_back(this); - bool Router::send ( - const Message::Seq& seq, - const String data, - const Post post - ) { - if (this->core->shuttingDown) { - return false; - } + this->id = rand64(); + this->core = core; + this->router.core = core; - if (post.body || seq == "-1") { - auto script = this->core->createPost(seq, data, post); - return this->evaluateJavaScript(script); - } + this->bluetooth.sendFunction = [this]( + const String& seq, + const JSON::Any value, + const SSC::Post post + ) { + this->router.send(seq, value.str(), post); + }; - auto value = encodeURIComponent(data); - auto script = getResolveToRenderProcessJavaScript( - seq.size() == 0 ? "-1" : seq, - "0", - value - ); + this->bluetooth.emitFunction = [this]( + const String& seq, + const JSON::Any value + ) { + this->router.emit(seq, value.str()); + }; - return this->evaluateJavaScript(script); - } + core->networkStatus.addObserver(this->networkStatusObserver, [this](auto json) { + if (json.has("name")) { + this->router.emit(json["name"].str(), json.str()); + } + }); - bool Router::emit ( - const String& name, - const String data - ) { - if (this->core->shuttingDown) { - return false; - } + core->geolocation.addPermissionChangeObserver(this->geolocationPermissionChangeObserver, [this] (auto json) { + JSON::Object event = JSON::Object::Entries { + {"name", "geolocation"}, + {"state", json["state"]} + }; + this->router.emit("permissionchange", event.str()); + }); - const auto value = encodeURIComponent(data); - const auto script = getEmitToRenderProcessJavaScript(name, value); - return this->evaluateJavaScript(script); - } + core->notifications.addPermissionChangeObserver(this->notificationsPermissionChangeObserver, [this](auto json) { + JSON::Object event = JSON::Object::Entries { + {"name", "notifications"}, + {"state", json["state"]} + }; + this->router.emit("permissionchange", event.str()); + }); - bool Router::evaluateJavaScript (const String js) { - if (this->core->shuttingDown) { - return false; - } + if (userConfig["permissions_allow_notifications"] != "false") { + core->notifications.addNotificationResponseObserver(this->notificationResponseObserver, [this](auto json) { + this->router.emit("notificationresponse", json.str()); + }); - if (this->evaluateJavaScriptFunction != nullptr) { - this->evaluateJavaScriptFunction(js); - return true; + core->notifications.addNotificationPresentedObserver(this->notificationPresentedObserver, [this](auto json) { + this->router.emit("notificationpresented", json.str()); + }); } - return false; - } + #if SSC_PLATFORM_DESKTOP + auto defaultUserConfig = SSC::getUserConfig(); + if ( + fileSystemWatcher == nullptr && + isDebugEnabled() && + defaultUserConfig["webview_watch"] == "true" + ) { + fileSystemWatcher = new FileSystemWatcher(getcwd()); + fileSystemWatcher->core = this->core; + fileSystemWatcher->start([=]( + const auto& path, + const auto& events, + const auto& context + ) mutable { + Lock lock(SSC::IPC::mutex); - bool Router::dispatch (DispatchCallback callback) { - if (!this->core || this->core->shuttingDown) { - return false; - } + static const auto cwd = getcwd(); + const auto relativePath = std::filesystem::relative(path, cwd).string(); + const auto json = JSON::Object::Entries {{"path", relativePath}}; + const auto result = SSC::IPC::Result(json); - if (this->dispatchFunction != nullptr) { - this->dispatchFunction(callback); - return true; - } + for (auto& bridge : instances) { + auto userConfig = bridge->userConfig; + if ( + !platform.ios && + !platform.android && + userConfig["webview_watch"] == "true" && + bridge->userConfig["webview_service_worker_mode"] != "hybrid" && + (!userConfig.contains("webview_watch_reload") || userConfig.at("webview_watch_reload") != "false") + ) { + // check if changed path was a service worker, if so unregister it so it can be reloaded + for (const auto& entry : fileSystemWatcher->core->serviceWorker.registrations) { + const auto& registration = entry.second; + #if defined(__ANDROID__) + auto scriptURL = String("https://"); + #else + auto scriptURL = String("socket://"); + #endif - return false; - } + scriptURL += userConfig["meta_bundle_identifier"]; - bool Router::isNavigationAllowed (const String& url) const { - static const auto devHost = SSC::getDevHost(); - auto userConfig = this->bridge->userConfig; - const auto allowed = SSC::split(SSC::trim(userConfig["webview_navigator_policies_allowed"]), ' '); + if (!relativePath.starts_with("/")) { + scriptURL += "/"; + } - for (const auto& entry : split(userConfig["webview_protocol-handlers"], " ")) { - const auto scheme = replace(trim(entry), ":", ""); - if (url.starts_with(scheme + ":")) { - return true; - } - } + scriptURL += relativePath; + if (registration.scriptURL == scriptURL) { + // 1. unregister service worker + // 2. re-register service worker + // 3. wait for it to be registered + // 4. emit 'filedidchange' event + bridge->core->serviceWorker.unregisterServiceWorker(entry.first); + bridge->core->setTimeout(8, [bridge, result, ®istration] () { + bridge->core->setInterval(8, [bridge, result, ®istration] (auto cancel) { + if (registration.state == ServiceWorkerContainer::Registration::State::Activated) { + cancel(); - for (const auto& entry : userConfig) { - const auto& key = entry.first; - if (key.starts_with("webview_protocol-handlers_")) { - const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; - if (url.starts_with(scheme + ":")) { - return true; - } - } - } + uint64_t timeout = 500; + if (bridge->userConfig["webview_watch_service_worker_reload_timeout"].size() > 0) { + try { + timeout = std::stoull(bridge->userConfig["webview_watch_service_worker_reload_timeout"]); + } catch (...) {} + } - for (const auto& entry : allowed) { - SSC::String pattern = entry; - pattern = SSC::replace(pattern, "\\.", "\\."); - pattern = SSC::replace(pattern, "\\*", "(.*)"); - pattern = SSC::replace(pattern, "\\.\\.\\*", "(.*)"); - pattern = SSC::replace(pattern, "\\/", "\\/"); + bridge->core->setTimeout(timeout, [bridge, result] () { + bridge->router.emit("filedidchange", result.json().str()); + }); + } + }); - try { - std::regex regex(pattern); - std::smatch match; + bridge->core->serviceWorker.registerServiceWorker(registration.options); + }); + return; + } + } + } - if (std::regex_match(url, match, regex, std::regex_constants::match_any)) { - return true; + bridge->router.emit("filedidchange", result.json().str()); } - } catch (...) {} - } - - if (url.starts_with("socket:") || url.starts_with("npm:") || url.starts_with(devHost)) { - return true; + }); } + #endif - return false; + this->router.init(this); } -} -#if defined(__APPLE__) -@implementation SSCIPCNetworkStatusObserver -- (id) init { - self = [super init]; - dispatch_queue_attr_t attrs = dispatch_queue_attr_make_with_qos_class( - DISPATCH_QUEUE_SERIAL, - QOS_CLASS_UTILITY, - DISPATCH_QUEUE_PRIORITY_DEFAULT - ); - - _monitorQueue = dispatch_queue_create( - "co.socketsupply.queue.ipc.network-status-observer", - attrs - ); - - // self.monitor = nw_path_monitor_create_with_type(nw_interface_type_wifi); - _monitor = nw_path_monitor_create(); - nw_path_monitor_set_queue(_monitor, _monitorQueue); - nw_path_monitor_set_update_handler(_monitor, ^(nw_path_t path) { - if (path == nullptr) { - return; - } - - nw_path_status_t status = nw_path_get_status(path); + Bridge::~Bridge () { + Lock lock(SSC::IPC::mutex); - String name; - String message; + // remove observers + core->networkStatus.removeObserver(this->networkStatusObserver); + core->geolocation.removePermissionChangeObserver(this->geolocationPermissionChangeObserver); - switch (status) { - case nw_path_status_invalid: { - name = "offline"; - message = "Network path is invalid"; - break; - } - case nw_path_status_satisfied: { - name = "online"; - message = "Network is usable"; - break; - } - case nw_path_status_satisfiable: { - name = "online"; - message = "Network may be usable"; - break; - } - case nw_path_status_unsatisfied: { - name = "offline"; - message = "Network is not usable"; - break; - } + const auto cursor = std::find(instances.begin(), instances.end(), this); + if (cursor != instances.end()) { + instances.erase(cursor); } - auto json = JSON::Object::Entries { - {"message", message} - }; + #if SSC_PLATFORM_DESKTOP + if (instances.size() == 0) { + if (fileSystemWatcher) { + fileSystemWatcher->stop(); + delete fileSystemWatcher; + } + } + #endif + } - auto router = self.router; - self.router->dispatch([router, json, name] () { - auto data = JSON::Object(json); - router->emit(name, data.str()); - }); - }); + bool Bridge::route (const String& uri, const char *bytes, size_t size) { + return this->route(uri, bytes, size, nullptr); + } - return self; -} + bool Bridge::route ( + const String& uri, + const char* bytes, + size_t size, + Router::ResultCallback callback + ) { + if (callback != nullptr) { + return this->router.invoke(uri, bytes, size, callback); + } else { + return this->router.invoke(uri, bytes, size); + } + } -- (void) start { - nw_path_monitor_start(_monitor); + const Vector& Bridge::getAllowedNodeCoreModules () const { + return allowedNodeCoreModules; + } } -@end -#endif diff --git a/src/ipc/bridge.hh b/src/ipc/bridge.hh new file mode 100644 index 0000000000..f66490603a --- /dev/null +++ b/src/ipc/bridge.hh @@ -0,0 +1,45 @@ +#ifndef SSC_IPC_BRIDGE_H +#define SSC_IPC_BRIDGE_H + +#include "../core/core.hh" +#include "navigator.hh" +#include "router.hh" + +namespace SSC::IPC { + class Bridge { + public: + static Vector getInstances(); + + const NetworkStatus::Observer networkStatusObserver; + const Geolocation::PermissionChangeObserver geolocationPermissionChangeObserver; + const Notifications::PermissionChangeObserver notificationsPermissionChangeObserver; + const Notifications::NotificationResponseObserver notificationResponseObserver; + const Notifications::NotificationPresentedObserver notificationPresentedObserver; + + Bluetooth bluetooth; + Navigator navigator; + Router router; + Map userConfig = getUserConfig(); + + Core *core = nullptr; + String preload = ""; + uint64_t id = 0; + + #if SSC_PLATFORM_ANDROID + bool isAndroidEmulator = false; + #endif + + Bridge (Core *core, Map userConfig = getUserConfig()); + ~Bridge (); + bool route (const String& msg, const char *bytes, size_t size); + bool route ( + const String& msg, + const char *bytes, + size_t size, + Router::ResultCallback + ); + + const Vector& getAllowedNodeCoreModules () const; + }; +} +#endif diff --git a/src/ipc/client.hh b/src/ipc/client.hh new file mode 100644 index 0000000000..4c4ec157c5 --- /dev/null +++ b/src/ipc/client.hh @@ -0,0 +1,16 @@ +#ifndef SSC_IPC_CLIENT_H +#define SSC_IPC_CLIENT_H + +#include "../core/core.hh" + +namespace SSC::IPC { + struct Client { + struct CurrentRequest { + String url; + }; + + String id; + CurrentRequest currentRequest; + }; +} +#endif diff --git a/src/ipc/ipc.cc b/src/ipc/ipc.cc deleted file mode 100644 index 67713ed6eb..0000000000 --- a/src/ipc/ipc.cc +++ /dev/null @@ -1,249 +0,0 @@ -#include "../core/core.hh" -#include "ipc.hh" -namespace SSC { - #if defined(_WIN32) - SSC::String FormatError(DWORD error, SSC::String source) { - SSC::StringStream message; - LPVOID lpMsgBuf; - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, NULL ); - - message << "Error " << error << " in " << source << ": " << (LPTSTR)lpMsgBuf; - LocalFree(lpMsgBuf); - - return message.str(); - } - #endif -} -namespace SSC::IPC { - Message::Message (const Message& message) { - this->buffer.bytes = message.buffer.bytes; - this->buffer.size = message.buffer.size; - this->value = message.value; - this->index = message.index; - this->name = message.name; - this->seq = message.seq; - this->uri = message.uri; - this->args = message.args; - this->isHTTP = message.isHTTP; - this->cancel = message.cancel; - } - - Message::Message (const String& source, char *bytes, size_t size) - : Message(source, false, bytes, size) - {} - - Message::Message ( - const String& source, - bool decodeValues, - char *bytes, - size_t size - ) : Message(source, decodeValues) - { - this->buffer.bytes = bytes; - this->buffer.size = size; - } - - Message::Message (const String& source) - : Message(source, false) - {} - - Message::Message (const String& source, bool decodeValues) { - String str = source; - uri = str; - - // bail if missing protocol prefix - if (str.find("ipc://") == -1) return; - - // bail if malformed - if (str.compare("ipc://") == 0) return; - if (str.compare("ipc://?") == 0) return; - - String query; - String path; - - auto raw = split(str, '?'); - path = raw[0]; - if (raw.size() > 1) query = raw[1]; - - auto parts = split(path, '/'); - if (parts.size() >= 1) name = parts[1]; - - if (raw.size() != 2) return; - auto pairs = split(raw[1], '&'); - - for (auto& rawPair : pairs) { - auto pair = split(rawPair, '='); - if (pair.size() <= 1) continue; - - if (pair[0].compare("index") == 0) { - try { - this->index = std::stoi(pair[1].size() > 0 ? pair[1] : "0"); - } catch (...) { - debug("Warning: received non-integer index"); - } - } - - if (pair[0].compare("value") == 0) { - this->value = decodeURIComponent(pair[1]); - } - - if (pair[0].compare("seq") == 0) { - this->seq = decodeURIComponent(pair[1]); - } - - if (decodeValues) { - this->args[pair[0]] = decodeURIComponent(pair[1]); - } else { - this->args[pair[0]] = pair[1]; - } - } - } - - bool Message::has (const String& key) const { - return ( - this->args.find(key) != this->args.end() && - this->args.at(key).size() > 0 - ); - } - - String Message::get (const String& key) const { - return this->get(key, ""); - } - - String Message::get (const String& key, const String &fallback) const { - if (key == "value") return this->value; - return args.count(key) ? decodeURIComponent(args.at(key)) : fallback; - } - - Result::Result ( - const Message::Seq& seq, - const Message& message - ) { - this->id = rand64(); - this->seq = seq; - this->message = message; - this->source = message.name; - this->post.workerId = this->message.get("runtime-worker-id"); - } - - Result::Result ( - const Message::Seq& seq, - const Message& message, - JSON::Any value - ) : Result(seq, message, value, Post{}) { - } - - Result::Result ( - const Message::Seq& seq, - const Message& message, - JSON::Any value, - Post post - ) : Result(seq, message) { - this->post = post; - this->headers = Headers(post.headers); - - if (this->post.workerId.size() == 0) { - this->post.workerId = this->message.get("runtime-worker-id"); - } - - if (value.type != JSON::Type::Any) { - this->value = value; - } - } - - Result::Result (const JSON::Any value) { - this->id = rand64(); - this->value = value; - } - - Result::Result (const Err error): Result(error.message.seq, error.message) { - this->err = error.value; - this->source = error.message.name; - } - - Result::Result (const Data data): Result(data.message.seq, data.message) { - this->data = data.value; - this->post = data.post; - this->source = data.message.name; - this->headers = Headers(data.post.headers); - } - - JSON::Any Result::json () const { - if (!this->value.isNull()) { - if (this->value.isObject()) { - auto object = this->value.as(); - - if (object.has("data") || object.has("err")) { - object["source"] = this->source; - object["id"] = std::to_string(this->id); - } - - return object; - } - - return this->value; - } - - auto entries = JSON::Object::Entries { - {"source", this->source}, - {"id", std::to_string(this->id)} - }; - - if (!this->err.isNull()) { - entries["err"] = this->err; - if (this->err.isObject()) { - if (this->err.as().has("id")) { - entries["id"] = this->err.as().get("id"); - } - } - } else if (!this->data.isNull()) { - entries["data"] = this->data; - if (this->data.isObject()) { - if (this->data.as().has("id")) { - entries["id"] = this->data.as().get("id"); - } - } - } - - return JSON::Object(entries); - } - - String Result::str () const { - auto json = this->json(); - return json.str(); - } - - Result::Err::Err ( - const Message& message, - JSON::Any value - ) { - this->seq = message.seq; - this->message = message; - this->value = value; - } - - Result::Data::Data ( - const Message& message, - JSON::Any value - ) : Data(message, value, Post{}) { - } - - Result::Data::Data ( - const Message& message, - JSON::Any value, - Post post - ) { - this->seq = message.seq; - this->message = message; - this->value = value; - this->post = post; - } -} diff --git a/src/ipc/ipc.hh b/src/ipc/ipc.hh index 0f5bfc4301..ec467911e3 100644 --- a/src/ipc/ipc.hh +++ b/src/ipc/ipc.hh @@ -1,364 +1,15 @@ #ifndef SSC_IPC_H #define SSC_IPC_H -#include "../core/core.hh" +#include "bridge.hh" +#include "client.hh" +#include "message.hh" +#include "navigator.hh" +#include "result.hh" +#include "router.hh" +#include "scheme_handlers.hh" namespace SSC::IPC { - class Router; - class Bridge; -} - -// create a proxy module so imports of the module of concern are imported -// exactly once at the canonical URL (file:///...) in contrast to module -// URLs (socket:...) - -static constexpr auto moduleTemplate = -R"S( -import module from '{{url}}' -export * from '{{url}}' -export default module -)S"; - -#if defined(__APPLE__) -namespace SSC::IPC { - using Task = id; - using Tasks = std::map; -} - -@class SSCBridgedWebView; -@interface SSCIPCSchemeHandler : NSObject -@property (nonatomic) SSC::IPC::Router* router; -- (void) webView: (SSCBridgedWebView*) webview - startURLSchemeTask: (SSC::IPC::Task) task; -- (void) webView: (SSCBridgedWebView*) webview - stopURLSchemeTask: (SSC::IPC::Task) task; -@end - -@interface SSCIPCSchemeTasks : NSObject { - std::unique_ptr tasks; - SSC::Mutex mutex; -} -- (SSC::IPC::Task) get: (SSC::String) id; -- (void) remove: (SSC::String) id; -- (bool) has: (SSC::String) id; -- (void) put: (SSC::String) id task: (SSC::IPC::Task) task; -@end - -@interface SSCIPCNetworkStatusObserver : NSObject -@property (nonatomic, assign) dispatch_queue_t monitorQueue; -@property (nonatomic, assign) SSC::IPC::Router* router; -@property (nonatomic, assign) nw_path_monitor_t monitor; -- (id) init; -- (void) start; -@end - -@class SSCLocationObserver; -@interface SSCLocationManagerDelegate : NSObject -@property (nonatomic, strong) SSCLocationObserver* locationObserver; - -- (id) initWithLocationObserver: (SSCLocationObserver*) locationObserver; - -- (void) locationManager: (CLLocationManager*) locationManager - didFailWithError: (NSError*) error; - -- (void) locationManager: (CLLocationManager*) locationManager - didUpdateLocations: (NSArray*) locations; - -- (void) locationManager: (CLLocationManager*) locationManager - didFinishDeferredUpdatesWithError: (NSError*) error; - -- (void) locationManagerDidPauseLocationUpdates: (CLLocationManager*) locationManager; -- (void) locationManagerDidResumeLocationUpdates: (CLLocationManager*) locationManager; -- (void) locationManager: (CLLocationManager*) locationManager - didVisit: (CLVisit*) visit; - -- (void) locationManager: (CLLocationManager*) locationManager - didChangeAuthorizationStatus: (CLAuthorizationStatus) status; -- (void) locationManagerDidChangeAuthorization: (CLLocationManager*) locationManager; -@end - -@interface SSCLocationPositionWatcher : NSObject -@property (nonatomic, assign) NSInteger identifier; -@property (nonatomic, assign) void(^completion)(CLLocation*); -+ (SSCLocationPositionWatcher*) positionWatcherWithIdentifier: (NSInteger) identifier - completion: (void (^)(CLLocation*)) completion; -@end - -@interface SSCLocationObserver : NSObject -@property (nonatomic, retain) CLLocationManager* locationManager; -@property (nonatomic, retain) SSCLocationManagerDelegate* delegate; -@property (atomic, retain) NSMutableArray* activationCompletions; -@property (atomic, retain) NSMutableArray* locationRequestCompletions; -@property (atomic, retain) NSMutableArray* locationWatchers; -@property (nonatomic) SSC::IPC::Router* router; -@property (atomic, assign) BOOL isAuthorized; -- (BOOL) attemptActivation; -- (BOOL) attemptActivationWithCompletion: (void (^)(BOOL)) completion; -- (BOOL) getCurrentPositionWithCompletion: (void (^)(NSError*, CLLocation*)) completion; -- (int) watchPositionForIdentifier: (NSInteger) identifier - completion: (void (^)(NSError*, CLLocation*)) completion; -- (BOOL) clearWatch: (NSInteger) identifier; -@end - -@interface SSCUserNotificationCenterDelegate : NSObject -- (void) userNotificationCenter: (UNUserNotificationCenter*) center - didReceiveNotificationResponse: (UNNotificationResponse*) response - withCompletionHandler: (void (^)(void)) completionHandler; - -- (void) userNotificationCenter: (UNUserNotificationCenter*) center - willPresentNotification: (UNNotification*) notification - withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler; -@end -#endif - -namespace SSC::IPC { - struct Client { - struct CurrentRequest { - String url; - }; - - String id; - CurrentRequest currentRequest; - }; - - struct MessageBuffer { - size_t size = 0; - char* bytes = nullptr; - MessageBuffer(char* bytes, size_t size) - : size(size), bytes(bytes) { } - #ifdef _WIN32 - ICoreWebView2SharedBuffer* shared_buf = nullptr; - MessageBuffer(ICoreWebView2SharedBuffer* buf, size_t size) - : size(size), shared_buf(buf) { - BYTE* b = reinterpret_cast(bytes); - HRESULT r = buf->get_Buffer(&b); - if (r != S_OK) { - // TODO(trevnorris): Handle this - } - } - #endif - MessageBuffer() = default; - }; - - struct MessageCancellation { - void (*handler)(void*) = nullptr; - void *data = nullptr; - }; - - class Message { - public: - using Seq = String; - MessageBuffer buffer; - Client client; - - String value = ""; - String name = ""; - String uri = ""; - int index = -1; - Seq seq = ""; - Map args; - bool isHTTP = false; - std::shared_ptr cancel; - - Message () = default; - Message (const Message& message); - Message (const String& source, bool decodeValues); - Message (const String& source); - Message (const String& source, bool decodeValues, char *bytes, size_t size); - Message (const String& source, char *bytes, size_t size); - bool has (const String& key) const; - String get (const String& key) const; - String get (const String& key, const String& fallback) const; - String str () const { return this->uri; } - const char * c_str () const { return this->uri.c_str(); } - }; - - class Result { - public: - class Err { - public: - Message message; - Message::Seq seq; - JSON::Any value; - Err () = default; - Err (const Message&, JSON::Any); - }; - - class Data { - public: - Message message; - Message::Seq seq; - JSON::Any value; - Post post; - - Data () = default; - Data (const Message&, JSON::Any); - Data (const Message&, JSON::Any, Post); - }; - - Message message; - Message::Seq seq = "-1"; - uint64_t id = 0; - String source = ""; - JSON::Any value = nullptr; - JSON::Any data = nullptr; - JSON::Any err = nullptr; - Headers headers; - Post post; - - Result () = default; - Result (const Result&) = default; - Result (const JSON::Any); - Result (const Err error); - Result (const Data data); - Result (const Message::Seq&, const Message&); - Result (const Message::Seq&, const Message&, JSON::Any); - Result (const Message::Seq&, const Message&, JSON::Any, Post); - String str () const; - JSON::Any json () const; - }; - - class Router { - public: - using EvaluateJavaScriptCallback = std::function; - using DispatchCallback = std::function; - using ReplyCallback = std::function; - using ResultCallback = std::function; - using MessageCallback = std::function; - using BufferMap = std::map; - - struct Location { - String href; - String pathname; - String query; - Map workers; - }; - - struct MessageCallbackContext { - bool async = true; - MessageCallback callback; - }; - - struct MessageCallbackListenerContext { - uint64_t token; - MessageCallback callback; - }; - - using Table = std::map; - using Listeners = std::map>; - - struct WebViewURLPathResolution { - String path = ""; - bool redirect = false; - }; - - struct WebViewNavigatorMount { - WebViewURLPathResolution resolution; // absolute URL resolution - String path; // root path on host file system - String route; // root path in webview navigator - }; - - struct WebViewURLComponents { - String originalUrl; - String queryString; - String fragment; - String path; - }; - - static WebViewURLComponents parseURL(const String& url); - static WebViewURLPathResolution resolveURLPathForWebView (String inputPath, const String& basePath); - static WebViewNavigatorMount resolveNavigatorMountForWebView (const String& path); - - private: - Table preserved; - - public: - EvaluateJavaScriptCallback evaluateJavaScriptFunction = nullptr; - std::function dispatchFunction = nullptr; - BufferMap buffers; - Mutex mutex; - Table table; - Listeners listeners; - Core *core = nullptr; - Bridge *bridge = nullptr; - Location location; - #if defined(__APPLE__) - SSCIPCNetworkStatusObserver* networkStatusObserver = nullptr; - SSCLocationObserver* locationObserver = nullptr; - SSCIPCSchemeHandler* schemeHandler = nullptr; - SSCIPCSchemeTasks* schemeTasks = nullptr; - NSTimer* notificationPollTimer = nullptr; - #elif defined(__linux__) && !defined(__ANDROID__) - WebKitWebContext* webkitWebContext = nullptr; - #endif - - Router (); - Router (const Router &) = delete; - ~Router (); - - void init (Bridge* bridge); - - MessageBuffer getMappedBuffer (int index, const Message::Seq seq); - bool hasMappedBuffer (int index, const Message::Seq seq); - void removeMappedBuffer (int index, const Message::Seq seq); - void setMappedBuffer(int index, const Message::Seq seq, MessageBuffer msg_buf); - - void preserveCurrentTable (); - - uint64_t listen (const String& name, MessageCallback callback); - bool unlisten (const String& name, uint64_t token); - void map (const String& name, MessageCallback callback); - void map (const String& name, bool async, MessageCallback callback); - void unmap (const String& name); - bool dispatch (DispatchCallback callback); - bool emit (const String& name, const String data); - bool evaluateJavaScript (const String javaScript); - bool send (const Message::Seq& seq, const String data, const Post post); - bool invoke (const String& msg, ResultCallback callback); - bool invoke (const String& msg, const char *bytes, size_t size); - bool invoke ( - const String& msg, - const char *bytes, - size_t size, - ResultCallback callback - ); - bool invoke ( - const Message& msg, - const char *bytes, - size_t size, - ResultCallback callback - ); - - bool isNavigationAllowed (const String& url) const; - }; - - class Bridge { - public: - static Vector getInstances(); - Router router; - Bluetooth bluetooth; - Core *core = nullptr; - Map userConfig = getUserConfig(); - String preload = ""; - uint64_t id = 0; - - #if SSC_PLATFORM_ANDROID - bool isAndroidEmulator = false; - #endif - - Bridge (Core *core, Map userConfig = getUserConfig()); - ~Bridge (); - bool route (const String& msg, const char *bytes, size_t size); - bool route ( - const String& msg, - const char *bytes, - size_t size, - Router::ResultCallback - ); - - const Vector& getAllowedNodeCoreModules () const; - }; - inline String getResolveToMainProcessMessage ( const String& seq, const String& state, @@ -366,5 +17,5 @@ namespace SSC::IPC { ) { return String("ipc://resolve?seq=" + seq + "&state=" + state + "&value=" + value); } -} // SSC::IPC +} #endif diff --git a/src/ipc/message.cc b/src/ipc/message.cc new file mode 100644 index 0000000000..346a272f54 --- /dev/null +++ b/src/ipc/message.cc @@ -0,0 +1,87 @@ +#include "message.hh" + +namespace SSC::IPC { + Message::Message (const Message& message) { + this->buffer.bytes = message.buffer.bytes; + this->buffer.size = message.buffer.size; + this->value = message.value; + this->index = message.index; + this->name = message.name; + this->seq = message.seq; + this->uri = message.uri; + this->args = message.args; + this->isHTTP = message.isHTTP; + this->cancel = message.cancel; + } + + Message::Message (const String& source) : Message(source, false) { + } + + Message::Message (const String& source, bool decodeValues) { + String str = source; + uri = str; + + // bail if missing protocol prefix + if (str.find("ipc://") == -1) return; + + // bail if malformed + if (str.compare("ipc://") == 0) return; + if (str.compare("ipc://?") == 0) return; + + String query; + String path; + + auto raw = split(str, '?'); + path = raw[0]; + if (raw.size() > 1) query = raw[1]; + + auto parts = split(path, '/'); + if (parts.size() >= 1) name = parts[1]; + + if (raw.size() != 2) return; + auto pairs = split(raw[1], '&'); + + for (auto& rawPair : pairs) { + auto pair = split(rawPair, '='); + if (pair.size() <= 1) continue; + + if (pair[0].compare("index") == 0) { + try { + this->index = std::stoi(pair[1].size() > 0 ? pair[1] : "0"); + } catch (...) { + debug("SSC:IPC::Message: Warning: received non-integer index"); + } + } + + if (pair[0].compare("value") == 0) { + this->value = decodeURIComponent(pair[1]); + } + + if (pair[0].compare("seq") == 0) { + this->seq = decodeURIComponent(pair[1]); + } + + if (decodeValues) { + this->args[pair[0]] = decodeURIComponent(pair[1]); + } else { + this->args[pair[0]] = pair[1]; + } + } + } + + bool Message::has (const String& key) const { + return ( + this->args.find(key) != this->args.end() && + this->args.at(key).size() > 0 + ); + } + + String Message::get (const String& key) const { + return this->get(key, ""); + } + + String Message::get (const String& key, const String &fallback) const { + if (key == "value") return this->value; + return args.count(key) ? decodeURIComponent(args.at(key)) : fallback; + } +} diff --git a/src/ipc/message.hh b/src/ipc/message.hh new file mode 100644 index 0000000000..0a6285c870 --- /dev/null +++ b/src/ipc/message.hh @@ -0,0 +1,58 @@ +#ifndef SSC_IPC_MESSAGE_H +#define SSC_IPC_MESSAGE_H + +#include "../core/core.hh" +#include "client.hh" + +namespace SSC::IPC { + struct MessageBuffer { + size_t size = 0; + SharedPointer bytes = nullptr; + MessageBuffer(SharedPointer bytes, size_t size) + : size(size), bytes(bytes) { } + #ifdef _WIN32 + ICoreWebView2SharedBuffer* shared_buf = nullptr; + MessageBuffer(ICoreWebView2SharedBuffer* buf, size_t size) + : size(size), shared_buf(buf) { + BYTE* b = reinterpret_cast(bytes); + HRESULT r = buf->get_Buffer(&b); + if (r != S_OK) { + // TODO(trevnorris): Handle this + } + } + #endif + MessageBuffer() = default; + }; + + struct MessageCancellation { + void (*handler)(void*) = nullptr; + void *data = nullptr; + }; + + class Message { + public: + using Seq = String; + MessageBuffer buffer; + Client client; + + String value = ""; + String name = ""; + String uri = ""; + int index = -1; + Seq seq = ""; + Map args; + bool isHTTP = false; + std::shared_ptr cancel; + + Message () = default; + Message (const Message& message); + Message (const String& source, bool decodeValues); + Message (const String& source); + bool has (const String& key) const; + String get (const String& key) const; + String get (const String& key, const String& fallback) const; + String str () const { return this->uri; } + const char * c_str () const { return this->uri.c_str(); } + }; +} +#endif diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc new file mode 100644 index 0000000000..98c2403473 --- /dev/null +++ b/src/ipc/navigator.cc @@ -0,0 +1,175 @@ +#include "navigator.hh" +#include "router.hh" +#include "bridge.hh" + +#if SSC_PLATFORM_APPLE +@implementation SSCNavigationDelegate +- (void) webView: (WKWebView*) webView + didFailNavigation: (WKNavigation*) navigation + withError: (NSError*) error +{ + // TODO(@jwerle) +} + +- (void) webView: (WKWebView*) webView + didFailProvisionalNavigation: (WKNavigation*) navigation + withError: (NSError*) error { + // TODO(@jwerle) +} + +- (void) webView: (WKWebView*) webview + decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction + decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler +{ + using namespace SSC; + if ( + webview != nullptr && + webview.URL != nullptr && + webview.URL.absoluteString.UTF8String != nullptr && + navigationAction != nullptr && + navigationAction.request.URL.absoluteString.UTF8String != nullptr + ) { + const auto currentURL = String(webview.URL.absoluteString.UTF8String); + const auto requestURL = String(navigationAction.request.URL.absoluteString.UTF8String); + + if (!self.navigator->handleNavigationRequest(currentURL, requestURL)) { + return decisionHandler(WKNavigationActionPolicyCancel); + } + } + + decisionHandler(WKNavigationActionPolicyAllow); +} + +- (void) webView: (WKWebView*) webView + decidePolicyForNavigationResponse: (WKNavigationResponse*) navigationResponse + decisionHandler: (void (^)(WKNavigationResponsePolicy)) decisionHandler { + decisionHandler(WKNavigationResponsePolicyAllow); +} +@end + +#endif + +namespace SSC::IPC { + Navigator::Navigator (Bridge* bridge) + : bridge(bridge) + { + #if SSC_PLATFORM_APPLE + this->navigationDelegate = [SSCNavigationDelegate new]; + this->navigationDelegate.navigator = this; + #endif + } + + Navigator::~Navigator () { + #if SSC_PLATFORM_APPLE + #if !__has_feature(objc_arc) + [this->navigationDelegate release]; + #endif + this->navigationDelegate = nullptr; + #endif + } + + bool Navigator::handleNavigationRequest ( + const String& currentURL, + const String& requestedURL + ) { + auto userConfig = this->bridge->userConfig; + const auto links = parseStringList(userConfig["meta_application_links"], ' '); + const auto applinks = parseStringList(userConfig["meta_application_links"], ' '); + const auto currentURLComponents = Router::parseURLComponents(currentURL); + + bool hasAppLink = false; + if (applinks.size() > 0 && currentURLComponents.authority.size() > 0) { + const auto host = currentURLComponents.authority; + for (const auto& applink : applinks) { + const auto parts = split(applink, '?'); + if (host == parts[0]) { + hasAppLink = true; + break; + } + } + } + + if (hasAppLink) { + SSC::JSON::Object json = SSC::JSON::Object::Entries {{ + "url", requestedURL + }}; + + this->bridge->router.emit("applicationurl", json.str()); + return false; + } + + if ( + userConfig["meta_application_protocol"].size() > 0 && + requestedURL.starts_with(userConfig["meta_application_protocol"]) && + !requestedURL.starts_with("socket://" + userConfig["meta_bundle_identifier"]) + ) { + + SSC::JSON::Object json = SSC::JSON::Object::Entries {{ + "url", requestedURL + }}; + + this->bridge->router.emit("applicationurl", json.str()); + return false; + } + + if (!this->isNavigationRequestAllowed(currentURL, requestedURL)) { + debug("Navigation was ignored for: %s", requestedURL.c_str()); + return false; + } + + return true; + } + + bool Navigator::isNavigationRequestAllowed ( + const String& currentURL, + const String& requestedURL + ) { + static const auto devHost = getDevHost(); + auto userConfig = this->bridge->userConfig; + const auto allowed = split(trim(userConfig["webview_navigator_policies_allowed"]), ' '); + + for (const auto& entry : split(userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (requestedURL.starts_with(scheme + ":")) { + return true; + } + } + + for (const auto& entry : userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + if (requestedURL.starts_with(scheme + ":")) { + return true; + } + } + } + + for (const auto& entry : allowed) { + String pattern = entry; + pattern = replace(pattern, "\\.", "\\."); + pattern = replace(pattern, "\\*", "(.*)"); + pattern = replace(pattern, "\\.\\.\\*", "(.*)"); + pattern = replace(pattern, "\\/", "\\/"); + + try { + std::regex regex(pattern); + std::smatch match; + + if (std::regex_match(requestedURL, match, regex, std::regex_constants::match_any)) { + return true; + } + } catch (...) {} + } + + if ( + requestedURL.starts_with("socket:") || + requestedURL.starts_with("npm:") || + requestedURL.starts_with(devHost) + ) { + return true; + } + + return false; + } +} diff --git a/src/ipc/navigator.hh b/src/ipc/navigator.hh new file mode 100644 index 0000000000..5c6a3d8efe --- /dev/null +++ b/src/ipc/navigator.hh @@ -0,0 +1,51 @@ +#ifndef SSC_IPC_NAVIGATOR_H +#define SSC_IPC_NAVIGATOR_H + +#include "../core/types.hh" +#include "../core/config.hh" +#include "../core/platform.hh" + +namespace SSC::IPC { + // forward + class Bridge; + class Navigator; +} + +#if SSC_PLATFORM_APPLE +@interface SSCNavigationDelegate : NSObject +@property (nonatomic) SSC::IPC::Navigator* navigator; +- (void) webView: (WKWebView*) webView + didFailNavigation: (WKNavigation*) navigation + withError: (NSError*) error; + +- (void) webView: (WKWebView*) webView + didFailProvisionalNavigation: (WKNavigation*) navigation + withError: (NSError*) error; + +- (void) webView: (WKWebView*) webview + decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction + decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler; + +- (void) webView: (WKWebView*) webView + decidePolicyForNavigationResponse: (WKNavigationResponse*) navigationResponse + decisionHandler: (void (^)(WKNavigationResponsePolicy)) decisionHandler; +@end +#endif + +namespace SSC::IPC { + class Navigator { + public: + Bridge* bridge = nullptr; + #if SSC_PLATFORM_APPLE + SSCNavigationDelegate* navigationDelegate = nullptr; + #endif + + Navigator (Bridge* bridge); + ~Navigator (); + + bool handleNavigationRequest (const String& currentURL, const String& requestedURL); + bool isNavigationRequestAllowed (const String& location, const String& requestURL); + }; +} + +#endif diff --git a/src/ipc/result.cc b/src/ipc/result.cc new file mode 100644 index 0000000000..7eed64bb27 --- /dev/null +++ b/src/ipc/result.cc @@ -0,0 +1,127 @@ +#include "result.hh" + +namespace SSC::IPC { + Result::Result ( + const Message::Seq& seq, + const Message& message + ) { + this->id = rand64(); + this->seq = seq; + this->message = message; + this->source = message.name; + this->post.workerId = this->message.get("runtime-worker-id"); + } + + Result::Result ( + const Message::Seq& seq, + const Message& message, + JSON::Any value + ) : Result(seq, message, value, Post{}) { + } + + Result::Result ( + const Message::Seq& seq, + const Message& message, + JSON::Any value, + Post post + ) : Result(seq, message) { + this->post = post; + this->headers = Headers(post.headers); + + if (this->post.workerId.size() == 0) { + this->post.workerId = this->message.get("runtime-worker-id"); + } + + if (value.type != JSON::Type::Any) { + this->value = value; + } + } + + Result::Result (const JSON::Any value) { + this->id = rand64(); + this->value = value; + } + + Result::Result (const Err error): Result(error.message.seq, error.message) { + this->err = error.value; + this->source = error.message.name; + } + + Result::Result (const Data data): Result(data.message.seq, data.message) { + this->data = data.value; + this->post = data.post; + this->source = data.message.name; + this->headers = Headers(data.post.headers); + } + + JSON::Any Result::json () const { + if (!this->value.isNull()) { + if (this->value.isObject()) { + auto object = this->value.as(); + + if (object.has("data") || object.has("err")) { + object["source"] = this->source; + object["id"] = std::to_string(this->id); + } + + return object; + } + + return this->value; + } + + auto entries = JSON::Object::Entries { + {"source", this->source}, + {"id", std::to_string(this->id)} + }; + + if (!this->err.isNull()) { + entries["err"] = this->err; + if (this->err.isObject()) { + if (this->err.as().has("id")) { + entries["id"] = this->err.as().get("id"); + } + } + } else if (!this->data.isNull()) { + entries["data"] = this->data; + if (this->data.isObject()) { + if (this->data.as().has("id")) { + entries["id"] = this->data.as().get("id"); + } + } + } + + return JSON::Object(entries); + } + + String Result::str () const { + auto json = this->json(); + return json.str(); + } + + Result::Err::Err ( + const Message& message, + JSON::Any value + ) { + this->seq = message.seq; + this->message = message; + this->value = value; + } + + Result::Data::Data ( + const Message& message, + JSON::Any value + ) : Data(message, value, Post{}) { + } + + Result::Data::Data ( + const Message& message, + JSON::Any value, + Post post + ) { + this->seq = message.seq; + this->message = message; + this->value = value; + this->post = post; + } +} diff --git a/src/ipc/result.hh b/src/ipc/result.hh new file mode 100644 index 0000000000..b5abaf2f08 --- /dev/null +++ b/src/ipc/result.hh @@ -0,0 +1,53 @@ +#ifndef SSC_IPC_RESULT_H +#define SSC_IPC_RESULT_H + +#include "../core/core.hh" +#include "message.hh" + +namespace SSC::IPC { + class Result { + public: + class Err { + public: + Message message; + Message::Seq seq; + JSON::Any value; + Err () = default; + Err (const Message&, JSON::Any); + }; + + class Data { + public: + Message message; + Message::Seq seq; + JSON::Any value; + Post post; + + Data () = default; + Data (const Message&, JSON::Any); + Data (const Message&, JSON::Any, Post); + }; + + Message message; + Message::Seq seq = "-1"; + uint64_t id = 0; + String source = ""; + JSON::Any value = nullptr; + JSON::Any data = nullptr; + JSON::Any err = nullptr; + Headers headers; + Post post; + + Result () = default; + Result (const Result&) = default; + Result (const JSON::Any); + Result (const Err error); + Result (const Data data); + Result (const Message::Seq&, const Message&); + Result (const Message::Seq&, const Message&, JSON::Any); + Result (const Message::Seq&, const Message&, JSON::Any, Post); + String str () const; + JSON::Any json () const; + }; +} +#endif diff --git a/src/ipc/router.cc b/src/ipc/router.cc new file mode 100644 index 0000000000..ca0751844f --- /dev/null +++ b/src/ipc/router.cc @@ -0,0 +1,1046 @@ +#include "bridge.hh" +#include "router.hh" + +// create a proxy module so imports of the module of concern are imported +// exactly once at the canonical URL (file:///...) in contrast to module +// URLs (socket:...) + +static constexpr auto moduleTemplate = +R"S( +import module from '{{url}}' +export * from '{{url}}' +export default module +)S"; + +namespace SSC::IPC { + /* + . + ├── a-conflict-index + │ └── index.html + ├── a-conflict-index.html + ├── an-index-file + │ ├── a-html-file.html + │ └── index.html + ├── another-file.html + └── index.html + + Subtleties: + Direct file navigation always wins + /foo/index.html have precedent over foo.html + /foo redirects to /foo/ when there is a /foo/index.html + + '/' -> '/index.html' + '/index.html' -> '/index.html' + '/a-conflict-index' -> redirect to '/a-conflict-index/' + '/another-file' -> '/another-file.html' + '/another-file.html' -> '/another-file.html' + '/an-index-file/' -> '/an-index-file/index.html' + '/an-index-file' -> redirect to '/an-index-file/' + '/an-index-file/a-html-file' -> '/an-index-file/a-html-file.html' + */ + Router::WebViewURLPathResolution Router::resolveURLPathForWebView (String inputPath, const String& basePath) { + namespace fs = std::filesystem; + + if (inputPath.starts_with("/")) { + inputPath = inputPath.substr(1); + } + + // Resolve the full path + const auto fullPath = (fs::path(basePath) / fs::path(inputPath)).make_preferred(); + + // 1. Try the given path if it's a file + if (fs::is_regular_file(fullPath)) { + return Router::WebViewURLPathResolution{"/" + replace(fs::relative(fullPath, basePath).string(), "\\\\", "/")}; + } + + // 2. Try appending a `/` to the path and checking for an index.html + const auto indexPath = fullPath / fs::path("index.html"); + if (fs::is_regular_file(indexPath)) { + if (fullPath.string().ends_with("\\") || fullPath.string().ends_with("/")) { + return Router::WebViewURLPathResolution{ + .pathname = "/" + replace(fs::relative(indexPath, basePath).string(), "\\\\", "/"), + .redirect = false + }; + } else { + return Router::WebViewURLPathResolution{ + .pathname = "/" + replace(fs::relative(fullPath, basePath).string(), "\\\\", "/") + "/", + .redirect = true + }; + } + } + + // 3. Check if appending a .html file extension gives a valid file + auto htmlPath = fullPath; + htmlPath.replace_extension(".html"); + if (fs::is_regular_file(htmlPath)) { + return Router::WebViewURLPathResolution{"/" + replace(fs::relative(htmlPath, basePath).string(), "\\\\", "/")}; + } + + // If no valid path is found, return empty string + return Router::WebViewURLPathResolution{}; + }; + + Router::WebViewURLComponents Router::parseURLComponents (const SSC::String& url) { + Router::WebViewURLComponents components; + components.originalURL = url; + auto input = url; + + if (input.starts_with("./")) { + input = input.substr(1); + } + + if (!input.starts_with("/")) { + const auto colon = input.find(':'); + + if (colon != String::npos) { + components.scheme = input.substr(0, colon); + input = input.substr(colon + 1, input.size()); + + if (input.starts_with("//")) { + input = input.substr(2, input.size()); + + const auto slash = input.find("/"); + if (slash != String::npos) { + components.authority = input.substr(0, slash); + input = input.substr(slash, input.size()); + } else { + const auto questionMark = input.find("?"); + const auto fragment = input.find("#"); + if (questionMark != String::npos & fragment != String::npos) { + if (questionMark < fragment) { + components.authority = input.substr(0, questionMark); + input = input.substr(questionMark, input.size()); + } else { + components.authority = input.substr(0, fragment); + input = input.substr(fragment, input.size()); + } + } else if (questionMark != String::npos) { + components.authority = input.substr(0, questionMark); + input = input.substr(questionMark, input.size()); + } else if (fragment != String::npos) { + components.authority = input.substr(0, fragment); + input = input.substr(fragment, input.size()); + } + } + } + } + } + + input = decodeURIComponent(input); + + const auto questionMark = input.find("?"); + const auto fragment = input.find("#"); + + if (questionMark != String::npos && fragment != String::npos) { + if (questionMark < fragment) { + components.pathname = input.substr(0, questionMark); + components.query = input.substr(questionMark + 1, fragment - questionMark - 1); + components.fragment = input.substr(fragment + 1, input.size()); + } else { + components.pathname = input.substr(0, fragment); + components.fragment = input.substr(fragment + 1, input.size()); + } + } else if (questionMark != String::npos) { + components.pathname = input.substr(0, questionMark); + components.query = input.substr(questionMark + 1, input.size()); + } else if (fragment != String::npos) { + components.pathname = input.substr(0, fragment); + components.fragment = input.substr(fragment + 1, input.size()); + } else { + components.pathname = input; + } + + if (!components.pathname.starts_with("/")) { + components.pathname = "/" + components.pathname; + } + + return components; + } + + static const Map getWebViewNavigatorMounts () { + static const auto userConfig = getUserConfig(); + #if defined(_WIN32) + static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); + #elif defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) + static const auto HOME = String(NSHomeDirectory().UTF8String); + #else + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + #endif + + static Map mounts; + + if (mounts.size() > 0) { + return mounts; + } + + Map mappings = { + {"\\$HOST_HOME", HOME}, + {"~", HOME}, + + {"\\$HOST_CONTAINER", + #if defined(__APPLE__) + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) objectAtIndex: 0].UTF8String + #else + // `homeDirectoryForCurrentUser` resolves to sandboxed container + // directory when in "sandbox" mode, otherwise the user's HOME directory + NSFileManager.defaultManager.homeDirectoryForCurrentUser.absoluteString.UTF8String + #endif + #elif defined(__linux__) + // TODO(@jwerle): figure out `$HOST_CONTAINER` for Linux + getcwd(), + #elif defined(_WIN32) + // TODO(@jwerle): figure out `$HOST_CONTAINER` for Windows + getcwd(), + #endif + }, + + {"\\$HOST_PROCESS_WORKING_DIRECTORY", + #if defined(__APPLE__) + NSBundle.mainBundle.resourcePath.UTF8String + #else + getcwd(), + #endif + } + }; + + for (const auto& tuple : userConfig) { + if (tuple.first.starts_with("webview_navigator_mounts_")) { + auto key = replace(tuple.first, "webview_navigator_mounts_", ""); + + if (key.starts_with("android") && !platform.android) continue; + if (key.starts_with("ios") && !platform.ios) continue; + if (key.starts_with("linux") && !platform.linux) continue; + if (key.starts_with("mac") && !platform.mac) continue; + if (key.starts_with("win") && !platform.win) continue; + + key = replace(key, "android_", ""); + key = replace(key, "ios_", ""); + key = replace(key, "linux_", ""); + key = replace(key, "mac_", ""); + key = replace(key, "win_", ""); + + String path = key; + + for (const auto& map : mappings) { + path = replace(path, map.first, map.second); + } + + const auto& value = tuple.second; + mounts.insert_or_assign(path, value); + } + } + + return mounts; + } + + Router::WebViewNavigatorMount Router::resolveNavigatorMountForWebView (const String& path) { + static const auto mounts = getWebViewNavigatorMounts(); + + for (const auto& tuple : mounts) { + if (path.starts_with(tuple.second)) { + const auto relative = replace(path, tuple.second, ""); + const auto resolution = resolveURLPathForWebView(relative, tuple.first); + if (resolution.pathname.size() > 0) { + const auto resolved = Path(tuple.first) / resolution.pathname.substr(1); + return WebViewNavigatorMount { + resolution, + resolved.string(), + path + }; + } else { + const auto resolved = relative.starts_with("/") + ? Path(tuple.first) / relative.substr(1) + : Path(tuple.first) / relative; + + return WebViewNavigatorMount { + resolution, + resolved.string(), + path + }; + } + } + } + + return WebViewNavigatorMount {}; + } + + Router::Router () : schemeHandlers(this) {} + + void Router::init (Bridge* bridge) { + this->bridge = bridge; + + this->init(); + this->preserveCurrentTable(); + } + + Router::~Router () { + } + + void Router::preserveCurrentTable () { + Lock lock(mutex); + this->preserved = this->table; + } + + uint64_t Router::listen (const String& name, MessageCallback callback) { + Lock lock(mutex); + + if (!this->listeners.contains(name)) { + this->listeners[name] = Vector(); + } + + auto& listeners = this->listeners.at(name); + auto token = rand64(); + listeners.push_back(MessageCallbackListenerContext { token , callback }); + return token; + } + + bool Router::unlisten (const String& name, uint64_t token) { + Lock lock(mutex); + + if (!this->listeners.contains(name)) { + return false; + } + + auto& listeners = this->listeners.at(name); + for (int i = 0; i < listeners.size(); ++i) { + const auto& listener = listeners[i]; + if (listener.token == token) { + listeners.erase(listeners.begin() + i); + return true; + } + } + + return false; + } + + void Router::map (const String& name, MessageCallback callback) { + return this->map(name, true, callback); + } + + void Router::map (const String& name, bool async, MessageCallback callback) { + Lock lock(mutex); + + String data = name; + // URI hostnames are not case sensitive. Convert to lowercase. + std::transform(data.begin(), data.end(), data.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (callback != nullptr) { + table.insert_or_assign(data, MessageCallbackContext { async, callback }); + } + } + + void Router::unmap (const String& name) { + Lock lock(mutex); + + String data = name; + // URI hostnames are not case sensitive. Convert to lowercase. + std::transform(data.begin(), data.end(), data.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (table.find(data) != table.end()) { + table.erase(data); + } + } + + bool Router::invoke (const String& uri, const char *bytes, size_t size) { + return this->invoke(uri, bytes, size, [this](auto result) { + this->send(result.seq, result.str(), result.post); + }); + } + + bool Router::invoke (const String& uri, ResultCallback callback) { + return this->invoke(uri, nullptr, 0, callback); + } + + bool Router::invoke ( + const String& uri, + const char *bytes, + size_t size, + ResultCallback callback + ) { + if (this->core->shuttingDown) { + return false; + } + + auto message = Message(uri, true); + return this->invoke(message, bytes, size, callback); + } + + bool Router::invoke ( + const Message& message, + const char *bytes, + size_t size, + ResultCallback callback + ) { + if (this->core->shuttingDown) { + return false; + } + + auto name = message.name; + MessageCallbackContext ctx; + + // URI hostnames are not case sensitive. Convert to lowercase. + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { + return std::tolower(c); + }); + + do { + // lookup router function in the preserved table, + // then the public table, return if unable to determine a context + if (this->preserved.find(name) != this->preserved.end()) { + ctx = this->preserved.at(name); + } else if (this->table.find(name) != this->table.end()) { + ctx = this->table.at(name); + } else { + return false; + } + } while (0); + + if (ctx.callback != nullptr) { + Message msg(message); + // decorate message with buffer if buffer was previously + // mapped with `ipc://buffer.map`, which we do on Linux + if (this->hasMappedBuffer(msg.index, msg.seq)) { + msg.buffer = this->getMappedBuffer(msg.index, msg.seq); + this->removeMappedBuffer(msg.index, msg.seq); + } else if (bytes != nullptr && size > 0) { + msg.buffer.bytes = std::make_shared(new char[size]{0}); + msg.buffer.size = size; + memcpy(*msg.buffer.bytes, bytes, size); + } + + // named listeners + do { + auto listeners = this->listeners[name]; + for (const auto& listener : listeners) { + listener.callback(msg, this, [](const auto& _) {}); + } + } while (0); + + // wild card (*) listeners + do { + auto listeners = this->listeners["*"]; + for (const auto& listener : listeners) { + listener.callback(msg, this, [](const auto& _) {}); + } + } while (0); + + if (ctx.async) { + auto dispatched = this->dispatch([ctx, msg, callback, this]() mutable { + ctx.callback(msg, this, [msg, callback, this](const auto result) mutable { + if (result.seq == "-1") { + this->send(result.seq, result.str(), result.post); + } else { + callback(result); + } + }); + }); + + return dispatched; + } else { + ctx.callback(msg, this, [msg, callback, this](const auto result) mutable { + if (result.seq == "-1") { + this->send(result.seq, result.str(), result.post); + } else { + callback(result); + } + }); + + return true; + } + } + + return false; + } + + bool Router::send ( + const Message::Seq& seq, + const String data, + const Post post + ) { + if (this->core->shuttingDown) { + return false; + } + + if (post.body != nullptr || seq == "-1") { + auto script = this->core->createPost(seq, data, post); + return this->evaluateJavaScript(script); + } + + auto value = encodeURIComponent(data); + auto script = getResolveToRenderProcessJavaScript( + seq.size() == 0 ? "-1" : seq, + "0", + value + ); + + return this->evaluateJavaScript(script); + } + + bool Router::emit ( + const String& name, + const String data + ) { + if (this->core->shuttingDown) { + return false; + } + + const auto value = encodeURIComponent(data); + const auto script = getEmitToRenderProcessJavaScript(name, value); + return this->evaluateJavaScript(script); + } + + bool Router::evaluateJavaScript (const String js) { + if (this->core->shuttingDown) { + return false; + } + + if (this->evaluateJavaScriptFunction != nullptr) { + this->evaluateJavaScriptFunction(js); + return true; + } + + return false; + } + + bool Router::dispatch (DispatchCallback callback) { + if (!this->core || this->core->shuttingDown) { + return false; + } + + if (this->dispatchFunction != nullptr) { + this->dispatchFunction(callback); + return true; + } + + return false; + } + + bool Router::hasMappedBuffer (int index, const Message::Seq seq) { + Lock lock(this->mutex); + auto key = std::to_string(index) + seq; + return this->buffers.find(key) != this->buffers.end(); + } + + MessageBuffer Router::getMappedBuffer (int index, const Message::Seq seq) { + if (this->hasMappedBuffer(index, seq)) { + Lock lock(this->mutex); + auto key = std::to_string(index) + seq; + return this->buffers.at(key); + } + + return MessageBuffer {}; + } + + void Router::setMappedBuffer ( + int index, + const Message::Seq seq, + MessageBuffer buffer + ) { + Lock lock(this->mutex); + auto key = std::to_string(index) + seq; + this->buffers.insert_or_assign(key, buffer); + } + + void Router::removeMappedBuffer (int index, const Message::Seq seq) { + Lock lock(this->mutex); + if (this->hasMappedBuffer(index, seq)) { + auto key = std::to_string(index) + seq; + this->buffers.erase(key); + } + } + + void Router::configureHandlers (const SchemeHandlers::Configuration& configuration) { + this->schemeHandlers.configure(configuration); + this->schemeHandlers.registerSchemeHandler("ipc", [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + auto message = Message(request.url(), true); + + // handle special 'ipc://post' case + if (message.name == "post") { + uint64_t id = 0; + + try { + id = std::stoull(message.get("id")); + } catch (...) { + auto response = SchemeHandlers::Response(request, 400); + response.send(JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "Invalid 'id' given in parameters"} + }} + }); + + callback(response); + return; + } + + if (!this->core->hasPost(id)) { + auto response = SchemeHandlers::Response(request, 404); + response.send(JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "A 'Post' was not found for the given 'id' in parameters"}, + {"type", "NotFoundError"} + }} + }); + + callback(response); + return; + } + + auto response = SchemeHandlers::Response(request, 200); + const auto post = this->core->getPost(id); + + // handle raw headers in 'Post' object + if (post.headers.size() > 0) { + const auto lines = split(trim(post.headers), '\n'); + for (const auto& line : lines) { + const auto pair = split(trim(line), ':'); + const auto key = trim(pair[0]); + const auto value = trim(pair[1]); + response.setHeader(key, value); + } + } + + response.write(post.length, post.body); + callback(response); + this->core->removePost(id); + return; + } + + message.isHTTP = true; + message.cancel = std::make_shared(); + + callbacks.cancel = [message] () { + if (message.cancel->handler != nullptr) { + message.cancel->handler(message.cancel->data); + } + }; + + const auto size = request.body.size; + const auto bytes = request.body.bytes != nullptr ? *request.body.bytes : nullptr; + const auto invoked = this->invoke(message, bytes, size, [request, message, callback](Result result) { + if (!request.isActive()) { + return; + } + + auto response = SchemeHandlers::Response(request); + + response.setHeaders(result.headers); + + // handle event source streams + if (result.post.eventStream != nullptr) { + response.setHeader("content-type", "text/event-stream"); + response.setHeader("cache-control", "no-store"); + *result.post.eventStream = [request, response, message, callback]( + const char* name, + const char* data, + bool finished + ) mutable { + if (request.isCancelled()) { + if (message.cancel->handler != nullptr) { + message.cancel->handler(message.cancel->data); + } + return false; + } + + response.writeHead(200); + + const auto event = SchemeHandlers::Response::Event { name, data }; + + if (event.count() > 0) { + response.write(event.str()); + } + + if (finished) { + callback(response); + } + + return true; + }; + return; + } + + // handle chunk streams + if (result.post.chunkStream != nullptr) { + response.setHeader("transfer-encoding", "chunked"); + *result.post.chunkStream = [request, response, message, callback]( + const char* chunk, + size_t size, + bool finished + ) mutable { + if (request.isCancelled()) { + if (message.cancel->handler != nullptr) { + message.cancel->handler(message.cancel->data); + } + return false; + } + + response.writeHead(200); + response.write(size, chunk); + + if (finished) { + callback(response); + } + + return true; + }; + return; + } + + if (result.post.body != nullptr) { + response.write(result.post.length, result.post.body); + } else { + response.write(result.json()); + } + + callback(response); + }); + + if (!invoked) { + auto response = SchemeHandlers::Response(request, 404); + response.send(JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"}, + {"url", request.url()} + }} + }); + + callback(response); + } + }); + + this->schemeHandlers.registerSchemeHandler("socket", [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + auto userConfig = this->bridge->userConfig; + auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + // the location of static application resources + const auto applicationResources = FileResource::getResourcesPath().string(); + // default response is 404 + auto response = SchemeHandlers::Response(request, 404); + + // the resouce path that may be request + String resourcePath; + + // the content location relative to the request origin + String contentLocation; + + // application resource or service worker request at `socket:///*` + if (request.hostname == bundleIdentifier) { + const auto resolved = Router::resolveURLPathForWebView(request.pathname, applicationResources); + const auto mount = Router::resolveNavigatorMountForWebView(request.pathname); + + if (mount.resolution.redirect || resolved.redirect) { + auto pathname = mount.resolution.redirect + ? mount.resolution.pathname + : resolved.pathname; + + if (request.method == "GET") { + auto location = mount.resolution.pathname; + if (request.query.size() > 0) { + location += "?" + request.query; + } + + if (request.fragment.size() > 0) { + location += "#" + request.fragment; + } + + response.redirect(location); + return callback(response); + } + } else if (mount.path.size() > 0) { + resourcePath = mount.path; + } else if (request.pathname == "" || request.pathname == "/") { + if (userConfig.contains("webview_default_index")) { + resourcePath = userConfig["webview_default_index"]; + if (resourcePath.starts_with("./")) { + resourcePath = applicationResources + resourcePath.substr(1); + } else if (resourcePath.starts_with("/")) { + resourcePath = applicationResources + resourcePath; + } else { + resourcePath = applicationResources + + "/" + resourcePath; + } + } + } + + if (resourcePath.size() == 0 && resolved.pathname.size() > 0) { + resourcePath = applicationResources + resolved.pathname; + } + + // handle HEAD and GET requests for a file resource + if (resourcePath.size() > 0) { + contentLocation = replace(resourcePath, applicationResources, ""); + + auto resource = FileResource(resourcePath); + + if (!resource.exists()) { + response.writeHead(404); + } else { + if (contentLocation.size() > 0) { + response.setHeader("content-location", contentLocation); + } + + if (request.method == "OPTIONS") { + response.setHeader("access-control-allow-origin", "*"); + response.setHeader("access-control-allow-methods", "GET, HEAD"); + response.setHeader("access-control-allow-headers", "*"); + response.setHeader("access-control-allow-credentials", "true"); + response.writeHead(200); + } + + if (request.method == "HEAD") { + const auto contentType = resource.mimeType(); + const auto contentLength = resource.size(); + + if (contentType.size() > 0) { + response.setHeader("content-type", contentType); + } + + if (contentLength > 0) { + response.setHeader("content-length", contentLength); + } + + response.writeHead(200); + } + + if (request.method == "GET") { + if (resource.mimeType() != "text/html") { + response.send(resource); + } else { + const auto html = injectHTMLPreload( + this->core, + userConfig, + resource.string(), + this->bridge->preload + ); + + response.setHeader("content-type", "text/html"); + response.setHeader("content-length", html.size()); + response.writeHead(200); + response.write(html); + } + } + } + + return callback(response); + } + + if (router->core->serviceWorker.registrations.size() > 0) { + const auto fetch = ServiceWorkerContainer::FetchRequest { + request.method, + request.scheme, + request.hostname, + request.pathname, + request.query, + request.headers, + ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, + ServiceWorkerContainer::Client { request.client.id } + }; + + const auto fetched = router->core->serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { + if (!request.isActive()) { + return; + } + + response.writeHead(res.statusCode, res.headers); + response.write(res.buffer.size, res.buffer.bytes); + callback(response); + }); + + if (fetched) { + router->bridge->core->setTimeout(32000, [request] () mutable { + if (request.isActive()) { + auto response = SchemeHandlers::Response(request, 408); + response.fail("ServiceWorker request timed out."); + } + }); + return; + } + } + + response.writeHead(404); + return callback(response); + } + + // module or stdlib import/fetch `socket:/` which will just + // proxy an import into a normal resource request above + if (request.hostname.size() == 0) { + auto pathname = request.pathname; + + if (!pathname.ends_with(".js")) { + pathname += ".js"; + } + + if (!pathname.starts_with("/")) { + pathname = "/" + pathname; + } + + resourcePath = applicationResources + "/socket" + pathname; + contentLocation = "/socket" + pathname; + + auto resource = FileResource(resourcePath); + + if (resource.exists()) { + const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; + const auto module = tmpl(moduleTemplate, Map {{"url", url}}); + const auto contentType = resource.mimeType(); + + if (contentType.size() > 0) { + response.setHeader("content-type", contentType); + } + + response.setHeader("content-length", module.size()); + + if (contentLocation.size() > 0) { + response.setHeader("content-location", contentLocation); + } + + response.writeHead(200); + response.write(trim(module)); + } + + return callback(response); + } + + response.writeHead(404); + callback(response); + }); + + Map protocolHandlers = { + {"npm", "/socket/npm/service-worker.js"} + }; + + for (const auto& entry : split(this->bridge->userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (this->bridge->core->protocolHandlers.registerHandler(scheme)) { + protocolHandlers.insert_or_assign(scheme, ""); + } + } + + for (const auto& entry : this->bridge->userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + const auto data = entry.second; + if (this->bridge->core->protocolHandlers.registerHandler(scheme, { data })) { + protocolHandlers.insert_or_assign(scheme, data); + } + } + } + + for (const auto& entry : protocolHandlers) { + const auto& scheme = entry.first; + const auto id = rand64(); + + auto scriptURL = trim(entry.second); + + if (scriptURL.size() == 0) { + continue; + } + + if (!scriptURL.starts_with(".") && !scriptURL.starts_with("/")) { + continue; + } + + if (scriptURL.starts_with(".")) { + scriptURL = scriptURL.substr(1, scriptURL.size()); + } + + String scope = "/"; + + auto scopeParts = split(scriptURL, "/"); + if (scopeParts.size() > 0) { + scopeParts = Vector(scopeParts.begin(), scopeParts.end() - 1); + scope = join(scopeParts, "/"); + } + + scriptURL = ( + #if SSC_PLATFORM_ANDROID + "https://" + + #else + "socket://" + + #endif + bridge->userConfig["meta_bundle_identifier"] + + scriptURL + ); + + this->bridge->core->serviceWorker.registerServiceWorker({ + .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, + .scope = scope, + .scriptURL = scriptURL, + .scheme = scheme, + .id = id + }); + + this->schemeHandlers.registerSchemeHandler(scheme, [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + if (this->core->serviceWorker.registrations.size() > 0) { + auto hostname = request.hostname; + auto pathname = request.pathname; + + if (request.scheme == "npm") { + hostname = this->bridge->userConfig["meta_bundle_identifier"]; + } + + const auto scope = this->core->protocolHandlers.getServiceWorkerScope(request.scheme); + + if (scope.size() > 0) { + pathname = scope + pathname; + } + + const auto fetch = ServiceWorkerContainer::FetchRequest { + request.method, + request.scheme, + hostname, + pathname, + request.query, + request.headers, + ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, + ServiceWorkerContainer::Client { request.client.id } + }; + + const auto fetched = this->core->serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { + if (!request.isActive()) { + return; + } + + auto response = SchemeHandlers::Response(request); + response.writeHead(res.statusCode, res.headers); + response.write(res.buffer.size, res.buffer.bytes); + callback(response); + }); + + if (fetched) { + this->bridge->core->setTimeout(32000, [request] () mutable { + if (request.isActive()) { + auto response = SchemeHandlers::Response(request, 408); + response.fail("Protocol handler ServiceWorker request timed out."); + } + }); + return; + } + } + + auto response = SchemeHandlers::Response(request); + response.writeHead(404); + callback(response); + }); + } + } +} diff --git a/src/ipc/router.hh b/src/ipc/router.hh new file mode 100644 index 0000000000..75a7e99c89 --- /dev/null +++ b/src/ipc/router.hh @@ -0,0 +1,127 @@ +#ifndef SSC_IPC_ROUTER_H +#define SSC_IPC_ROUTER_H + +#include "../core/core.hh" +#include "scheme_handlers.hh" +#include "message.hh" +#include "result.hh" + +namespace SSC::IPC { + class Bridge; + class Router { + public: + using EvaluateJavaScriptCallback = std::function; + using DispatchCallback = std::function; + using ReplyCallback = std::function; + using ResultCallback = std::function; + using MessageCallback = std::function; + using BufferMap = std::map; + + struct Location { + String href; + String pathname; + String query; + Map workers; + }; + + struct MessageCallbackContext { + bool async = true; + MessageCallback callback; + }; + + struct MessageCallbackListenerContext { + uint64_t token; + MessageCallback callback; + }; + + using Table = std::map; + using Listeners = std::map>; + + struct WebViewURLPathResolution { + String pathname = ""; + bool redirect = false; + }; + + struct WebViewNavigatorMount { + WebViewURLPathResolution resolution; // absolute URL resolution + String path; // root path on host file system + String route; // root path in webview navigator + }; + + struct WebViewURLComponents { + String originalURL; + String scheme = ""; + String authority = ""; + String pathname; + String query; + String fragment; + }; + + static WebViewURLComponents parseURLComponents (const String& url); + static WebViewURLPathResolution resolveURLPathForWebView (String inputPath, const String& basePath); + static WebViewNavigatorMount resolveNavigatorMountForWebView (const String& path); + + #if defined(__APPLE__) + static Mutex notificationMapMutex; + static std::map notificationMap; + #endif + + private: + Table preserved; + + public: + EvaluateJavaScriptCallback evaluateJavaScriptFunction = nullptr; + Function dispatchFunction = nullptr; + + Listeners listeners; + BufferMap buffers; + Mutex mutex; + Table table; + + Location location; + SchemeHandlers schemeHandlers; + + Core *core = nullptr; + Bridge *bridge = nullptr; + + Router (); + Router (const Router &) = delete; + ~Router (); + + void init (); + void init (Bridge* bridge); + void configureHandlers (const SchemeHandlers::Configuration& configuration); + + MessageBuffer getMappedBuffer (int index, const Message::Seq seq); + bool hasMappedBuffer (int index, const Message::Seq seq); + void removeMappedBuffer (int index, const Message::Seq seq); + void setMappedBuffer(int index, const Message::Seq seq, MessageBuffer msg_buf); + + void preserveCurrentTable (); + + uint64_t listen (const String& name, MessageCallback callback); + bool unlisten (const String& name, uint64_t token); + void map (const String& name, MessageCallback callback); + void map (const String& name, bool async, MessageCallback callback); + void unmap (const String& name); + bool dispatch (DispatchCallback callback); + bool emit (const String& name, const String data); + bool evaluateJavaScript (const String javaScript); + bool send (const Message::Seq& seq, const String data, const Post post); + bool invoke (const String& msg, ResultCallback callback); + bool invoke (const String& msg, const char *bytes, size_t size); + bool invoke ( + const String& msg, + const char *bytes, + size_t size, + ResultCallback callback + ); + bool invoke ( + const Message& msg, + const char *bytes, + size_t size, + ResultCallback callback + ); + }; +} +#endif diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc new file mode 100644 index 0000000000..2602f7af95 --- /dev/null +++ b/src/ipc/routes.cc @@ -0,0 +1,3032 @@ +#include "../core/types.hh" +#include "../core/json.hh" +#include "../extension/extension.hh" +#include "../window/window.hh" +#include "ipc.hh" + +#define REQUIRE_AND_GET_MESSAGE_VALUE(var, name, parse, ...) \ + try { \ + var = parse(message.get(name, ##__VA_ARGS__)); \ + } catch (...) { \ + return reply(Result::Err { message, JSON::Object::Entries { \ + {"message", "Invalid '" name "' given in parameters"} \ + }}); \ + } + +#define RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) \ + [message, reply](auto seq, auto json, auto post) { \ + reply(Result { seq, message, json, post }); \ + } + +using namespace SSC; +using namespace SSC::IPC; + +static JSON::Any validateMessageParameters ( + const Message& message, + const Vector names +) { + for (const auto& name : names) { + if (!message.has(name) || message.get(name).size() == 0) { + return JSON::Object::Entries { + {"message", "Expecting '" + name + "' in parameters"} + }; + } + } + + return nullptr; +} + +static void mapIPCRoutes (Router *router) { + auto userConfig = router->bridge->userConfig; +#if defined(__APPLE__) + auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + auto SSC_OS_LOG_BUNDLE = os_log_create(bundleIdentifier.c_str(), + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + "socket.runtime.mobile" + #else + "socket.runtime.desktop" + #endif + ); +#endif + + /** + * Starts a bluetooth service + * @param serviceId + */ + router->map("bluetooth.start", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"serviceId"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + if (router->bridge->userConfig["permissions_allow_bluetooth"] == "false") { + auto err =JSON::Object::Entries { + {"message", "Bluetooth is not allowed"} + }; + + return reply(Result::Err { message, err }); + } + + router->bridge->bluetooth.startService( + message.seq, + message.get("serviceId"), + [reply, message](auto seq, auto json) { + reply(Result { seq, message, json }); + } + ); + }); + + /** + * Subscribes to a characteristic for a service. + * @param serviceId + * @param characteristicId + */ + router->map("bluetooth.subscribe", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, { + "characteristicId", + "serviceId" + }); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + if (router->bridge->userConfig["permissions_allow_bluetooth"] == "false") { + auto err =JSON::Object::Entries { + {"message", "Bluetooth is not allowed"} + }; + + return reply(Result::Err { message, err }); + } + + router->bridge->bluetooth.subscribeCharacteristic( + message.seq, + message.get("serviceId"), + message.get("characteristicId"), + [reply, message](auto seq, auto json) { + reply(Result { seq, message, json }); + } + ); + }); + + /** + * Publishes data to a characteristic for a service. + * @param serviceId + * @param characteristicId + */ + router->map("bluetooth.publish", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, { + "characteristicId", + "serviceId" + }); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + if (router->bridge->userConfig["permissions_allow_bluetooth"] == "false") { + auto err =JSON::Object::Entries { + {"message", "Bluetooth is not allowed"} + }; + + return reply(Result::Err { message, err }); + } + + auto bytes = *message.buffer.bytes; + auto size = message.buffer.size; + + if (bytes == nullptr) { + bytes = message.value.data(); + size = message.value.size(); + } + + router->bridge->bluetooth.publishCharacteristic( + message.seq, + bytes, + size, + message.get("serviceId"), + message.get("characteristicId"), + [reply, message](auto seq, auto json) { + reply(Result { seq, message, json }); + } + ); + }); + + /** + * Maps a message buffer bytes to an index + sequence. + * + * This setup allows us to push a byte array to the bridge and + * map it to an IPC call index and sequence pair, which is reused for an + * actual IPC call, subsequently. This is used for systems that do not support + * a POST/PUT body in XHR requests natively, so instead we decorate + * `message.buffer` with already an mapped buffer. + */ + router->map("buffer.map", false, [=](auto message, auto router, auto reply) { + router->setMappedBuffer(message.index, message.seq, message.buffer); + reply(Result { message.seq, message }); + }); + + /** + * Kills an already spawned child process. + * + * @param id + * @param signal + */ + router->map("child_process.kill", [=](auto message, auto router, auto reply) { + #if SSC_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else + auto err = validateMessageParameters(message, {"id", "signal"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + int signal; + REQUIRE_AND_GET_MESSAGE_VALUE(signal, "signal", std::stoi); + + router->core->childProcess.kill( + message.seq, + id, + signal, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + #endif + }); + + /** + * Spawns a child process + * + * @param id + * @param args (command, ...args) + */ + router->map("child_process.spawn", [=](auto message, auto router, auto reply) { + #if SSC_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else + auto err = validateMessageParameters(message, {"args", "id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto args = split(message.get("args"), 0x0001); + + if (args.size() == 0 || args.at(0).size() == 0) { + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"err", JSON::Object::Entries { + {"message", "Spawn requires at least one argument with a length greater than zero"}, + }} + }; + + return reply(Result { message.seq, message, json }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + const auto options = Core::ChildProcess::SpawnOptions { + .cwd = message.get("cwd", getcwd()), + .allowStdin = message.get("stdin") != "false", + .allowStdout = message.get("stdout") != "false", + .allowStderr = message.get("stderr") != "false" + }; + + router->core->childProcess.spawn( + message.seq, + id, + args, + options, + [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + } + ); + #endif + }); + + router->map("child_process.exec", [=](auto message, auto router, auto reply) { + #if SSC_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else + auto err = validateMessageParameters(message, {"args", "id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto args = split(message.get("args"), 0x0001); + + if (args.size() == 0 || args.at(0).size() == 0) { + auto json = JSON::Object::Entries { + {"source", "child_process.exec"}, + {"err", JSON::Object::Entries { + {"message", "Spawn requires at least one argument with a length greater than zero"}, + }} + }; + + return reply(Result { message.seq, message, json }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + uint64_t timeout = 0; + int killSignal = 0; + + if (message.has("timeout")) { + REQUIRE_AND_GET_MESSAGE_VALUE(timeout, "timeout", std::stoull); + } + + if (message.has("killSignal")) { + REQUIRE_AND_GET_MESSAGE_VALUE(killSignal, "killSignal", std::stoi); + } + + const auto options = Core::ChildProcess::ExecOptions { + .cwd = message.get("cwd", getcwd()), + .allowStdout = message.get("stdout") != "false", + .allowStderr = message.get("stderr") != "false", + .timeout = timeout, + .killSignal = killSignal + }; + + router->core->childProcess.exec( + message.seq, + id, + args, + options, + [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + } + ); + #endif + }); + + /** + * Writes to an already spawned child process. + * + * @param id + */ + router->map("child_process.write", [=](auto message, auto router, auto reply) { + #if SSC_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; + + return reply(Result::Err { message, err }); + #else + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->childProcess.write( + message.seq, + id, + *message.buffer.bytes, + message.buffer.size, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + #endif + }); + + /** + * Look up an IP address by `hostname`. + * @param hostname Host name to lookup + * @param family IP address family to resolve [default = 0 (AF_UNSPEC)] + * @see getaddrinfo(3) + */ + router->map("dns.lookup", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"hostname"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int family = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(family, "family", std::stoi, "0"); + + router->core->dns.lookup( + message.seq, + Core::DNS::LookupOptions { message.get("hostname"), family }, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + router->map("extension.stats", [=](auto message, auto router, auto reply) { + auto extensions = Extension::all(); + auto name = message.get("name"); + + if (name.size() > 0) { + auto type = Extension::getExtensionType(name); + auto path = Extension::getExtensionPath(name); + auto json = JSON::Object::Entries { + {"source", "extension.stats"}, + {"data", JSON::Object::Entries { + {"abi", SOCKET_RUNTIME_EXTENSION_ABI_VERSION}, + {"name", name}, + {"type", type}, + // `path` is absolute to the location of the resources + {"path", String("/") + std::filesystem::relative(path, getcwd()).string()} + }} + }; + + reply(Result { message.seq, message, json }); + } else { + int loaded = 0; + + for (const auto& tuple : extensions) { + if (tuple.second != nullptr) { + loaded++; + } + } + + auto json = JSON::Object::Entries { + {"source", "extension.stats"}, + {"data", JSON::Object::Entries { + {"abi", SOCKET_RUNTIME_EXTENSION_ABI_VERSION}, + {"loaded", loaded} + }} + }; + + reply(Result { message.seq, message, json }); + } + }); + + /** + * Query for type of extension ('shared', 'wasm32', 'unknown') + * @param name + */ + router->map("extension.type", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"name"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto name = message.get("name"); + auto type = Extension::getExtensionType(name); + auto json = SSC::JSON::Object::Entries { + {"source", "extension.type"}, + {"data", JSON::Object::Entries { + {"name", name}, + {"type", type} + }} + }; + + reply(Result { message.seq, message, json }); + }); + + /** + * Load a named native extension. + * @param name + * @param allow + */ + router->map("extension.load", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"name"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto name = message.get("name"); + + if (!Extension::load(name)) { + #if defined(_WIN32) + auto error = formatWindowsError(GetLastError(), "bridge"); + #else + auto err = dlerror(); + auto error = String(err ? err : "Unknown error"); + #endif + + std::cout << "Load extension error: " << error << std::endl; + + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Failed to load extension: '" + name + "': " + error} + }}); + } + + auto extension = Extension::get(name); + auto allowed = split(message.get("allow"), ','); + auto context = Extension::getContext(name); + auto ctx = context->memory.template alloc(context, router); + + for (const auto& value : allowed) { + auto policy = trim(value); + ctx->setPolicy(policy, true); + } + + Extension::setRouterContext(name, router, ctx); + + /// init context + if (!Extension::initialize(ctx, name, nullptr)) { + if (ctx->state == Extension::Context::State::Error) { + auto json = JSON::Object::Entries { + {"source", "extension.load"}, + {"extension", name}, + {"err", JSON::Object::Entries { + {"code", ctx->error.code}, + {"name", ctx->error.name}, + {"message", ctx->error.message}, + {"location", ctx->error.location}, + }} + }; + + reply(Result { message.seq, message, json }); + } else { + auto json = JSON::Object::Entries { + {"source", "extension.load"}, + {"extension", name}, + {"err", JSON::Object::Entries { + {"message", "Failed to initialize extension: '" + name + "'"}, + }} + }; + + reply(Result { message.seq, message, json }); + } + } else { + auto json = JSON::Object::Entries { + {"source", "extension.load"}, + {"data", JSON::Object::Entries { + {"abi", (uint64_t) extension->abi}, + {"name", extension->name}, + {"version", extension->version}, + {"description", extension->description} + }} + }; + + reply(Result { message.seq, message, json }); + } + }); + + /** + * Unload a named native extension. + * @param name + */ + router->map("extension.unload", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"name"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto name = message.get("name"); + + if (!Extension::isLoaded(name)) { + return reply(Result::Err { message, JSON::Object::Entries { + #if defined(_WIN32) + {"message", "Extension '" + name + "' is not loaded"} + #else + {"message", "Extension '" + name + "' is not loaded" + String(dlerror())} + #endif + }}); + } + + auto extension = Extension::get(name); + auto ctx = Extension::getRouterContext(name, router); + + if (Extension::unload(ctx, name, extension->contexts.size() == 1)) { + Extension::removeRouterContext(name, router); + auto json = JSON::Object::Entries { + {"source", "extension.unload"}, + {"extension", name}, + {"data", JSON::Object::Entries {}} + }; + return reply(Result { message.seq, message, json }); + } + + if (ctx->state == Extension::Context::State::Error) { + auto json = JSON::Object::Entries { + {"source", "extension.unload"}, + {"extension", name}, + {"err", JSON::Object::Entries { + {"code", ctx->error.code}, + {"name", ctx->error.name}, + {"message", ctx->error.message}, + {"location", ctx->error.location}, + }} + }; + + reply(Result { message.seq, message, json }); + } else { + auto json = JSON::Object::Entries { + {"source", "extension.unload"}, + {"extension", name}, + {"err", JSON::Object::Entries { + {"message", "Failed to unload extension: '" + name + "'"}, + }} + }; + + reply(Result { message.seq, message, json }); + } + }); + + /** + * Checks if current user can access file at `path` with `mode`. + * @param path + * @param mode + * @see access(2) + */ + router->map("fs.access", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path", "mode"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int mode = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); + + router->core->fs.access( + message.seq, + message.get("path"), + mode, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Returns a mapping of file system constants. + */ + router->map("fs.constants", [=](auto message, auto router, auto reply) { + router->core->fs.constants(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Changes `mode` of file at `path`. + * @param path + * @param mode + * @see chmod(2) + */ + router->map("fs.chmod", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path", "mode"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int mode = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); + + router->core->fs.chmod( + message.seq, + message.get("path"), + mode, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Changes uid and gid of file at `path`. + * @param path + * @param uid + * @param gid + * @see chown(2) + */ + router->map("fs.chown", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path", "uid", "gid"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int uid = 0; + int gid = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(uid, "uid", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(gid, "gid", std::stoi); + + router->core->fs.chown( + message.seq, + message.get("path"), + static_cast(uid), + static_cast(gid), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Changes uid and gid of symbolic link at `path`. + * @param path + * @param uid + * @param gid + * @see lchown(2) + */ + router->map("fs.lchown", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path", "uid", "gid"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int uid = 0; + int gid = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(uid, "uid", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(gid, "gid", std::stoi); + + router->core->fs.lchown( + message.seq, + message.get("path"), + static_cast(uid), + static_cast(gid), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Closes underlying file descriptor handle. + * @param id + * @see close(2) + */ + router->map("fs.close", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.close(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Closes underlying directory descriptor handle. + * @param id + * @see closedir(3) + */ + router->map("fs.closedir", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.closedir(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Closes an open file or directory descriptor handle. + * @param id + * @see close(2) + * @see closedir(3) + */ + router->map("fs.closeOpenDescriptor", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.closeOpenDescriptor( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Closes all open file and directory descriptors, optionally preserving + * explicitly retrained descriptors. + * @param preserveRetained (default: true) + * @see close(2) + * @see closedir(3) + */ + router->map("fs.closeOpenDescriptors", [=](auto message, auto router, auto reply) { + router->core->fs.closeOpenDescriptor( + message.seq, + message.get("preserveRetained") != "false", + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Copy file at path `src` to path `dest`. + * @param src + * @param dest + * @param flags + * @see copyfile(3) + */ + router->map("fs.copyFile", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"src", "dest", "flags"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int flags = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(flags, "flags", std::stoi); + + router->core->fs.copyFile( + message.seq, + message.get("src"), + message.get("dest"), + flags, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Creates a link at `dest` + * @param src + * @param dest + * @see link(2) + */ + router->map("fs.link", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"src", "dest"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.link( + message.seq, + message.get("src"), + message.get("dest"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Creates a symlink at `dest` + * @param src + * @param dest + * @param flags + * @see symlink(2) + */ + router->map("fs.symlink", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"src", "dest", "flags"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int flags = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(flags, "flags", std::stoi); + + router->core->fs.symlink( + message.seq, + message.get("src"), + message.get("dest"), + flags, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Computes stats for an open file descriptor. + * @param id + * @see stat(2) + * @see fstat(2) + */ + router->map("fs.fstat", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.fstat(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Synchronize a file's in-core state with storage device + * @param id + * @see fsync(2) + */ + router->map("fs.fsync", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.fsync( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Truncates opened file + * @param id + * @param offset + * @see ftruncate(2) + */ + router->map("fs.ftruncate", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "offset"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + int64_t offset; + REQUIRE_AND_GET_MESSAGE_VALUE(offset, "offset", std::stoll); + + router->core->fs.ftruncate( + message.seq, + id, + offset, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Returns all open file or directory descriptors. + */ + router->map("fs.getOpenDescriptors", [=](auto message, auto router, auto reply) { + router->core->fs.getOpenDescriptors( + message.seq, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Computes stats for a symbolic link at `path`. + * @param path + * @see stat(2) + * @see lstat(2) + */ + router->map("fs.lstat", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.lstat( + message.seq, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Creates a directory at `path` with an optional mode and an optional recursive flag. + * @param path + * @param mode + * @param recursive + * @see mkdir(2) + */ + router->map("fs.mkdir", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path", "mode"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int mode = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); + + router->core->fs.mkdir( + message.seq, + message.get("path"), + mode, + message.get("recursive") == "true", + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + + /** + * Opens a file descriptor at `path` for `id` with `flags` and `mode` + * @param id + * @param path + * @param flags + * @param mode + * @see open(2) + */ + router->map("fs.open", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, { + "id", + "path", + "flags", + "mode" + }); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + int mode = 0; + int flags = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(mode, "mode", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(flags, "flags", std::stoi); + + router->core->fs.open( + message.seq, + id, + message.get("path"), + flags, + mode, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Opens a directory descriptor at `path` for `id` with `flags` and `mode` + * @param id + * @param path + * @see opendir(3) + */ + router->map("fs.opendir", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.opendir( + message.seq, + id, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Reads `size` bytes at `offset` from the underlying file descriptor. + * @param id + * @param size + * @param offset + * @see read(2) + */ + router->map("fs.read", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "size", "offset"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + int size = 0; + int offset = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(size, "size", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(offset, "offset", std::stoi); + + router->core->fs.read( + message.seq, + id, + size, + offset, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Reads next `entries` of from the underlying directory descriptor. + * @param id + * @param entries (default: 256) + */ + router->map("fs.readdir", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + int entries = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(entries, "entries", std::stoi); + + router->core->fs.readdir( + message.seq, + id, + entries, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Read value of a symbolic link at 'path' + * @param path + * @see readlink(2) + */ + router->map("fs.readlink", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.readlink( + message.seq, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Get the realpath at 'path' + * @param path + * @see realpath(2) + */ + router->map("fs.realpath", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.realpath( + message.seq, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Marks a file or directory descriptor as retained. + * @param id + */ + router->map("fs.retainOpenDescriptor", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.retainOpenDescriptor( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Renames file at path `src` to path `dest`. + * @param src + * @param dest + * @see rename(2) + */ + router->map("fs.rename", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"src", "dest"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.rename( + message.seq, + message.get("src"), + message.get("dest"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Removes file at `path`. + * @param path + * @see rmdir(2) + */ + router->map("fs.rmdir", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.rmdir( + message.seq, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Computes stats for a file at `path`. + * @param path + * @see stat(2) + */ + router->map("fs.stat", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.stat( + message.seq, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Stops a already started watcher + */ + router->map("fs.stopWatch", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.watch( + message.seq, + id, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Removes a file or empty directory at `path`. + * @param path + * @see unlink(2) + */ + router->map("fs.unlink", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + router->core->fs.unlink( + message.seq, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * TODO + */ + router->map("fs.watch", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "path"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->fs.watch( + message.seq, + id, + message.get("path"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Writes buffer at `message.buffer.bytes` of size `message.buffers.size` + * at `offset` for an opened file handle. + * @param id Handle ID for an open file descriptor + * @param offset The offset to start writing at + * @see write(2) + */ + router->map("fs.write", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "offset"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + if (message.buffer.bytes == nullptr || message.buffer.size == 0) { + auto err = JSON::Object::Entries {{ "message", "Missing buffer in message" }}; + return reply(Result::Err { message, err }); + } + + + uint64_t id; + int offset = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(offset, "offset", std::stoi); + + router->core->fs.write( + message.seq, + id, + *message.buffer.bytes, + message.buffer.size, + offset, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + +#if defined(__APPLE__) + router->map("geolocation.getCurrentPosition", [=](auto message, auto router, auto reply) { + router->core->geolocation.getCurrentPosition( + message.seq, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + router->map("geolocation.watchPosition", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int id = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoi); + + router->core->geolocation.watchPosition( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + router->map("geolocation.clearWatch", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + int id = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoi); + router->core->geolocation.clearWatch( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + + reply(Result { message.seq, message, JSON::Object{} }); + }); +#endif + + /** + * A private API for artifically setting the current cached CWD value. + * This is only useful on platforms that need to set this value from an + * external source, like Android or ChromeOS. + */ + router->map("internal.setcwd", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"value"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + setcwd(message.value); + reply(Result { message.seq, message, JSON::Object{} }); + }); + + /** + * Log `value to stdout` with platform dependent logger. + * @param value + */ + router->map("log", [=](auto message, auto router, auto reply) { + auto value = message.value.c_str(); + #if defined(__APPLE__) + NSLog(@"%s", value); + os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", value); + #elif defined(__ANDROID__) + __android_log_print(ANDROID_LOG_DEBUG, "", "%s", value); + #else + printf("%s\n", value); + #endif + }); + +#if defined(__APPLE__) + router->map("notification.show", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, { + "id", + "title" + }); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + auto attachments = [NSMutableArray new]; + auto userInfo = [NSMutableDictionary new]; + auto content = [UNMutableNotificationContent new]; + auto __block id = message.get("id"); + + if (message.has("tag")) { + userInfo[@"tag"] = @(message.get("tag").c_str()); + content.threadIdentifier = @(message.get("tag").c_str()); + } + + if (message.has("lang")) { + userInfo[@"lang"] = @(message.get("lang").c_str()); + } + + if (!message.has("silent") && message.get("silent") == "false") { + content.sound = [UNNotificationSound defaultSound]; + } + + if (message.has("icon")) { + NSError* error = nullptr; + auto url = [NSURL URLWithString: @(message.get("icon").c_str())]; + + if (message.get("icon").starts_with("socket://")) { + url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath + stringByAppendingPathComponent: [NSString + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + stringWithFormat: @"/ui/%s", url.path.UTF8String + #else + stringWithFormat: @"/%s", url.path.UTF8String + #endif + ] + ]]; + + url = [NSURL fileURLWithPath: url.path]; + } + + auto types = [UTType + typesWithTag: url.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + auto options = [NSMutableDictionary new]; + + if (types.count > 0) { + options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; + }; + + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: url + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + auto err = JSON::Object::Entries { { "message", message } }; + return reply(Result::Err { message, err }); + } + + [attachments addObject: attachment]; + } else { + // using an asset from the resources directory will require a code signed application + #if SSC_PLATFORM_SANDBOXED + NSError* error = nullptr; + auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath + stringByAppendingPathComponent: [NSString + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + stringWithFormat: @"/ui/icon.png" + #else + stringWithFormat: @"/icon.png" + #endif + ] + ]]; + + url = [NSURL fileURLWithPath: url.path]; + + auto types = [UTType + typesWithTag: url.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + auto options = [NSMutableDictionary new]; + + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: url + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + auto err = JSON::Object::Entries { { "message", message } }; + + return reply(Result::Err { message, err }); + } + + [attachments addObject: attachment]; + #endif + } + + if (message.has("image")) { + NSError* error = nullptr; + auto url = [NSURL URLWithString: @(message.get("image").c_str())]; + + if (message.get("image").starts_with("socket://")) { + url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath + stringByAppendingPathComponent: [NSString + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + stringWithFormat: @"/ui/%s", url.path.UTF8String + #else + stringWithFormat: @"/%s", url.path.UTF8String + #endif + ] + ]]; + + url = [NSURL fileURLWithPath: url.path]; + } + + auto types = [UTType + typesWithTag: url.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + auto options = [NSMutableDictionary new]; + + if (types.count > 0) { + options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; + }; + + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: url + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + auto err = JSON::Object::Entries {{ "message", message }}; + + return reply(Result::Err { message, err }); + } + + [attachments addObject: attachment]; + } + + content.attachments = attachments; + content.userInfo = userInfo; + content.title = @(message.get("title").c_str()); + content.body = @(message.get("body", "").c_str()); + + auto request = [UNNotificationRequest + requestWithIdentifier: @(id.c_str()) + content: content + trigger: nil + ]; + + { + Lock lock(Router::notificationMapMutex); + Router::notificationMap.insert_or_assign(id, router); + } + + [notificationCenter addNotificationRequest: request withCompletionHandler: ^(NSError* error) { + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + auto err = JSON::Object::Entries { + { "message", message } + }; + + reply(Result::Err { message, err }); + Lock lock(Router::notificationMapMutex); + Router::notificationMap.erase(id); + return; + } + + reply(Result { message.seq, message, JSON::Object::Entries { + {"id", request.identifier.UTF8String} + }}); + }]; + }); + + router->map("notification.close", [=](auto message, auto router, auto reply) { + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + auto err = validateMessageParameters(message, { "id" }); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto id = message.get("id"); + auto identifiers = @[@(id.c_str())]; + + [notificationCenter removePendingNotificationRequestsWithIdentifiers: identifiers]; + [notificationCenter removeDeliveredNotificationsWithIdentifiers: identifiers]; + + reply(Result { message.seq, message, JSON::Object::Entries { + {"id", id} + }}); + + Lock lock(Router::notificationMapMutex); + if (Router::notificationMap.contains(id)) { + auto notificationRouter = Router::notificationMap.at(id); + JSON::Object json = JSON::Object::Entries { + {"id", id}, + {"action", "dismiss"} + }; + + notificationRouter->emit("notificationresponse", json.str()); + Router::notificationMap.erase(id); + } + }); + + router->map("notification.list", [=](auto message, auto router, auto reply) { + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + [notificationCenter getDeliveredNotificationsWithCompletionHandler: ^(NSArray *notifications) { + JSON::Array::Entries entries; + + Lock lock(Router::notificationMapMutex); + for (UNNotification* notification in notifications) { + auto id = String(notification.request.identifier.UTF8String); + + if ( + !Router::notificationMap.contains(id) || + Router::notificationMap.at(id) != router + ) { + continue; + } + + entries.push_back(JSON::Object::Entries { + {"id", id} + }); + } + + reply(Result { message.seq, message, entries }); + }]; + }); +#endif + + /** + * Read or modify the `SEND_BUFFER` or `RECV_BUFFER` for a peer socket. + * @param id Handle ID for the buffer to read/modify + * @param size If given, the size to set in the buffer [default = 0] + * @param buffer The buffer to read/modify (SEND_BUFFER, RECV_BUFFER) [default = 0 (SEND_BUFFER)] + */ + router->map("os.bufferSize", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + int buffer = 0; + int size = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(buffer, "buffer", std::stoi, "0"); + REQUIRE_AND_GET_MESSAGE_VALUE(size, "size", std::stoi, "0"); + + router->core->os.bufferSize( + message.seq, + id, + size, + buffer, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Returns a mapping of operating system constants. + */ + router->map("os.constants", [=](auto message, auto router, auto reply) { + router->core->os.constants(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Returns a mapping of network interfaces. + */ + router->map("os.networkInterfaces", [=](auto message, auto router, auto reply) { + router->core->os.networkInterfaces(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Returns an array of CPUs available to the process. + */ + router->map("os.cpus", [=](auto message, auto router, auto reply) { + router->core->os.cpus(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("os.rusage", [=](auto message, auto router, auto reply) { + router->core->os.rusage(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("os.uptime", [=](auto message, auto router, auto reply) { + router->core->os.uptime(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("os.uname", [=](auto message, auto router, auto reply) { + router->core->os.uname(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("os.hrtime", [=](auto message, auto router, auto reply) { + router->core->os.hrtime(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("os.availableMemory", [=](auto message, auto router, auto reply) { + router->core->os.availableMemory(message.seq, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("os.paths", [=](auto message, auto router, auto reply) { + const auto bundleIdentifier = router->bridge->userConfig["meta_bundle_identifier"]; + + JSON::Object json; + + // paths + String resources = getcwd(); + String downloads; + String documents; + String pictures; + String desktop; + String videos; + String config; + String music; + String home; + String data; + String log; + + #if defined(__APPLE__) + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + + static const auto fileManager = NSFileManager.defaultManager; + + #define DIRECTORY_PATH_FROM_FILE_MANAGER(type) ( \ + String([fileManager \ + URLForDirectory: type \ + inDomain: NSUserDomainMask \ + appropriateForURL: nil \ + create: NO \ + error: nil \ + ].path.UTF8String) \ + ) + + // overload with main bundle resources path for macos/ios + resources = String(NSBundle.mainBundle.resourcePath.UTF8String); + downloads = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDownloadsDirectory); + documents = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDocumentDirectory); + pictures = DIRECTORY_PATH_FROM_FILE_MANAGER(NSPicturesDirectory); + desktop = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDesktopDirectory); + videos = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory); + music = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory); + config = HOME + "/Library/Application Support/" + bundleIdentifier; + home = String(NSHomeDirectory().UTF8String); + data = HOME + "/Library/Application Support/" + bundleIdentifier; + log = HOME + "/Library/Logs/" + bundleIdentifier; + + #undef DIRECTORY_PATH_FROM_FILE_MANAGER + + #elif defined(__linux__) + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + + static const auto XDG_DOCUMENTS_DIR = Env::get("XDG_DOCUMENTS_DIR"); + static const auto XDG_DOWNLOAD_DIR = Env::get("XDG_DOWNLOAD_DIR"); + static const auto XDG_PICTURES_DIR = Env::get("XDG_PICTURES_DIR"); + static const auto XDG_DESKTOP_DIR = Env::get("XDG_DESKTOP_DIR"); + static const auto XDG_VIDEOS_DIR = Env::get("XDG_VIDEOS_DIR"); + static const auto XDG_MUSIC_DIR = Env::get("XDG_MUSIC_DIR"); + + static const auto XDG_CONFIG_HOME = Env::get("XDG_CONFIG_HOME", HOME + "/.config"); + static const auto XDG_DATA_HOME = Env::get("XDG_DATA_HOME", HOME + "/.local/share"); + + if (XDG_DOCUMENTS_DIR.size() > 0) { + documents = XDG_DOCUMENTS_DIR; + } else { + documents = (Path(HOME) / "Documents").string(); + } + + if (XDG_DOWNLOAD_DIR.size() > 0) { + downloads = XDG_DOWNLOAD_DIR; + } else { + downloads = (Path(HOME) / "Downloads").string(); + } + + if (XDG_DESKTOP_DIR.size() > 0) { + desktop = XDG_DESKTOP_DIR; + } else { + desktop = (Path(HOME) / "Desktop").string(); + } + + if (XDG_PICTURES_DIR.size() > 0) { + pictures = XDG_PICTURES_DIR; + } else if (fs::exists(Path(HOME) / "Images")) { + pictures = (Path(HOME) / "Images").string(); + } else if (fs::exists(Path(HOME) / "Photos")) { + pictures = (Path(HOME) / "Photos").string(); + } else { + pictures = (Path(HOME) / "Pictures").string(); + } + + if (XDG_VIDEOS_DIR.size() > 0) { + videos = XDG_VIDEOS_DIR; + } else { + videos = (Path(HOME) / "Videos").string(); + } + + if (XDG_MUSIC_DIR.size() > 0) { + music = XDG_MUSIC_DIR; + } else { + music = (Path(HOME) / "Music").string(); + } + + config = XDG_CONFIG_HOME + "/" + bundleIdentifier; + home = Path(HOME).string(); + data = XDG_DATA_HOME + "/" + bundleIdentifier; + log = config; + #elif defined(_WIN32) + static const auto HOME = Env::get("HOMEPATH", Env::get("HOME")); + static const auto USERPROFILE = Env::get("USERPROFILE", HOME); + downloads = (Path(USERPROFILE) / "Downloads").string(); + documents = (Path(USERPROFILE) / "Documents").string(); + desktop = (Path(USERPROFILE) / "Desktop").string(); + pictures = (Path(USERPROFILE) / "Pictures").string(); + videos = (Path(USERPROFILE) / "Videos").string(); + music = (Path(USERPROFILE) / "Music").string(); + config = (Path(Env::get("APPDATA")) / bundleIdentifier).string(); + home = Path(USERPROFILE).string(); + data = (Path(Env::get("APPDATA")) / bundleIdentifier).string(); + log = config; + #endif + + json["resources"] = resources; + json["downloads"] = downloads; + json["documents"] = documents; + json["pictures"] = pictures; + json["desktop"] = desktop; + json["videos"] = videos; + json["music"] = music; + json["config"] = config; + json["home"] = home; + json["data"] = data; + json["log"] = log; + + return reply(Result::Data { message, json }); + }); + + router->map("permissions.query", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"name"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto name = message.get("name"); + + #if defined(__APPLE__) + if (name == "geolocation") { + if (router->core->geolocation.locationObserver.isAuthorized) { + auto data = JSON::Object::Entries {{"state", "granted"}}; + return reply(Result::Data { message, data }); + } else if (router->core->geolocation.locationObserver.locationManager) { + auto authorizationStatus = ( + router->core->geolocation.locationObserver.locationManager.authorizationStatus + ); + + if (authorizationStatus == kCLAuthorizationStatusDenied) { + auto data = JSON::Object::Entries {{"state", "denied"}}; + return reply(Result::Data { message, data }); + } else { + auto data = JSON::Object::Entries {{"state", "prompt"}}; + return reply(Result::Data { message, data }); + } + } + + auto data = JSON::Object::Entries {{"state", "denied"}}; + return reply(Result::Data { message, data }); + } + + if (name == "notifications") { + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + if (!settings) { + auto err = JSON::Object::Entries {{ "message", "Failed to reach user notification settings" }}; + return reply(Result::Err { message, err }); + } + + if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + auto data = JSON::Object::Entries {{"state", "denied"}}; + return reply(Result::Data { message, data }); + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + auto data = JSON::Object::Entries {{"state", "prompt"}}; + return reply(Result::Data { message, data }); + } + + auto data = JSON::Object::Entries {{"state", "granted"}}; + return reply(Result::Data { message, data }); + }]; + } + #endif + }); + + router->map("permissions.request", [=](auto message, auto router, auto reply) { + #if defined(__APPLE__) + __block auto userConfig = router->bridge->userConfig; + #else + auto userConfig = router->bridge->userConfig; + #endif + + auto err = validateMessageParameters(message, {"name"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto name = message.get("name"); + + if (name == "geolocation") { + #if defined(__APPLE__) + auto performedActivation = [router->core->geolocation.locationObserver attemptActivationWithCompletion: ^(BOOL isAuthorized) { + if (!isAuthorized) { + auto reason = @("Location observer could not be activated"); + + if (!router->core->geolocation.locationObserver.locationManager) { + reason = @("Location observer manager is not initialized"); + } else if (!router->core->geolocation.locationObserver.locationManager.location) { + reason = @("Location observer manager could not provide location"); + } + + auto error = [NSError + errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) + code: -1 + userInfo: @{ + NSLocalizedDescriptionKey: reason + } + ]; + } + + if (isAuthorized) { + auto data = JSON::Object::Entries {{"state", "granted"}}; + return reply(Result::Data { message, data }); + } else if (router->core->geolocation.locationObserver.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { + auto data = JSON::Object::Entries {{"state", "prompt"}}; + return reply(Result::Data { message, data }); + } else { + auto data = JSON::Object::Entries {{"state", "denied"}}; + return reply(Result::Data { message, data }); + } + }]; + + if (!performedActivation) { + auto err = JSON::Object::Entries {{ "message", "Location observer could not be activated" }}; + err["type"] = "GeolocationPositionError"; + return reply(Result::Err { message, err }); + } + + return; + #endif + } + + if (name == "notifications") { + #if defined(__APPLE__) + UNAuthorizationOptions options = UNAuthorizationOptionProvisional; + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + auto requestAlert = message.get("alert") == "true"; + auto requestBadge = message.get("badge") == "true"; + auto requestSound = message.get("sound") == "true"; + + if (requestAlert) { + options |= UNAuthorizationOptionAlert; + } + + if (requestBadge) { + options |= UNAuthorizationOptionBadge; + } + + if (requestSound) { + options |= UNAuthorizationOptionSound; + } + + if (requestAlert && requestSound) { + options |= UNAuthorizationOptionCriticalAlert; + } + + [notificationCenter + requestAuthorizationWithOptions: options + completionHandler: ^(BOOL granted, NSError *error) { + [notificationCenter + getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + auto data = JSON::Object::Entries {{"state", "denied"}}; + return reply(Result::Data { message, data }); + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + if (error) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + auto err = JSON::Object::Entries { + { "message", message } + }; + + return reply(Result::Err { message, err }); + } + + auto data = JSON::Object::Entries { + {"state", granted ? "granted" : "denied" } + }; + + return reply(Result::Data { message, data }); + } + + auto data = JSON::Object::Entries {{"state", "granted"}}; + return reply(Result::Data { message, data }); + }]; + }]; + #endif + } + }); + + /** + * Simply returns `pong`. + */ + router->map("ping", [=](auto message, auto router, auto reply) { + auto result = Result { message.seq, message }; + result.data = "pong"; + reply(result); + }); + + /** + * Handles platform events. + * @param value The event name [domcontentloaded] + * @param data Optional data associated with the platform event. + */ + router->map("platform.event", [=](auto message, auto router, auto reply) { + const auto err = validateMessageParameters(message, {"value"}); + const auto frameType = message.get("runtime-frame-type"); + const auto frameSource = message.get("runtime-frame-source"); + auto userConfig = router->bridge->userConfig; + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + if (frameType == "top-level" && frameSource != "serviceworker") { + if (message.value == "load") { + const auto href = message.get("location.href"); + if (href.size() > 0) { + router->location.href = href; + router->location.workers.clear(); + auto tmp = href; + tmp = replace(tmp, "socket://", ""); + tmp = replace(tmp, "https://", ""); + tmp = replace(tmp, userConfig["meta_bundle_identifier"], ""); + auto parsed = Router::parseURLComponents(tmp); + router->location.pathname = parsed.pathname; + router->location.query = parsed.query; + } + } + + if (router->bridge == router->core->serviceWorker.bridge) { + if (router->bridge->userConfig["webview_service_worker_mode"] == "hybrid" || platform.ios || platform.android) { + if (router->location.href.size() > 0 && message.value == "beforeruntimeinit") { + router->core->serviceWorker.reset(); + router->core->serviceWorker.isReady = false; + } else if (message.value == "runtimeinit") { + router->core->serviceWorker.isReady = true; + } + } + } + } + + if (message.value == "load" && frameType == "worker") { + const auto workerLocation = message.get("runtime-worker-location"); + const auto href = message.get("location.href"); + if (href.size() > 0 && workerLocation.size() > 0) { + router->location.workers[href] = workerLocation; + } + } + + router->core->platform.event( + message.seq, + message.value, + message.get("data"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Requests a notification with `title` and `body` to be shown. + * @param title + * @param body + */ + router->map("platform.notify", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"body", "title"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + router->core->platform.notify( + message.seq, + message.get("title"), + message.get("body"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Reveal a file in the native operating system file system explorer. + * @param value + */ + router->map("platform.revealFile", [=](auto message, auto router, auto reply) mutable { + auto err = validateMessageParameters(message, {"value"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + router->core->platform.revealFile( + message.seq, + message.get("value"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Requests a URL to be opened externally. + * @param value + */ + router->map("platform.openExternal", [=](auto message, auto router, auto reply) mutable { + const auto applicationProtocol = router->bridge->userConfig["meta_application_protocol"]; + auto err = validateMessageParameters(message, {"value"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + if (applicationProtocol.size() > 0 && message.value.starts_with(applicationProtocol + ":")) { + SSC::JSON::Object json = SSC::JSON::Object::Entries { + { "url", message.value } + }; + + router->bridge->router.emit("applicationurl", json.str()); + reply(Result { + message.seq, + message, + SSC::JSON::Object::Entries { + {"data", json} + } + }); + return; + } + + router->core->platform.openExternal( + message.seq, + message.value, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Return Socket Runtime primordials. + */ + router->map("platform.primordials", [=](auto message, auto router, auto reply) { + std::regex platform_pattern("^mac$", std::regex_constants::icase); + auto platformRes = std::regex_replace(platform.os, platform_pattern, "darwin"); + auto arch = std::regex_replace(platform.arch, std::regex("x86_64"), "x64"); + arch = std::regex_replace(arch, std::regex("x86"), "ia32"); + arch = std::regex_replace(arch, std::regex("arm(?!64).*"), "arm"); + auto json = JSON::Object::Entries { + {"source", "platform.primordials"}, + {"data", JSON::Object::Entries { + {"arch", arch}, + {"cwd", getcwd()}, + {"platform", platformRes}, + {"version", JSON::Object::Entries { + {"full", SSC::VERSION_FULL_STRING}, + {"short", SSC::VERSION_STRING}, + {"hash", SSC::VERSION_HASH_STRING}} + }, + {"host-operating-system", + #if defined(__APPLE__) + #if TARGET_IPHONE_SIMULATOR + "iphonesimulator" + #elif TARGET_OS_IPHONE + "iphoneos" + #else + "macosx" + #endif + #elif defined(__ANDROID__) + (router->bridge->isAndroidEmulator ? "android-emulator" : "android") + #elif defined(__WIN32) + "win32" + #elif defined(__linux__) + "linux" + #elif defined(__unix__) || defined(__unix) + "unix" + #else + "unknown" + #endif + } + }} + }; + reply(Result { message.seq, message, json }); + }); + + /** + * Returns pending post data typically returned in the response of an + * `ipc://post` IPC call intercepted by an XHR request. + * @param id The id of the post data. + */ + router->map("post", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + if (!router->core->hasPost(id)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"id", std::to_string(id)}, + {"message", "Post not found for given 'id'"} + }}); + } + + auto result = Result { message.seq, message }; + result.post = router->core->getPost(id); + reply(result); + router->core->removePost(id); + }); + + /** + * Registers a custom protocol handler scheme. Custom protocols MUST be handled in service workers. + * @param scheme + * @param data + */ + router->map("protocol.register", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + const auto data = message.get("data"); + + if (data.size() > 0 && router->core->protocolHandlers.hasHandler(scheme)) { + router->core->protocolHandlers.setHandlerData(scheme, { data }); + } else { + router->core->protocolHandlers.registerHandler(scheme, { data }); + } + + reply(Result { message.seq, message }); + }); + + /** + * Unregister a custom protocol handler scheme. + * @param scheme + */ + router->map("protocol.unregister", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + + if (!router->core->protocolHandlers.hasHandler(scheme)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Protocol handler scheme is not registered."} + }}); + } + + router->core->protocolHandlers.unregisterHandler(scheme); + + reply(Result { message.seq, message }); + }); + + /** + * Gets protocol handler data + * @param scheme + */ + router->map("protocol.getData", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + + if (!router->core->protocolHandlers.hasHandler(scheme)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Protocol handler scheme is not registered."} + }}); + } + + const auto data = router->core->protocolHandlers.getHandlerData(scheme); + + reply(Result { message.seq, message, JSON::Raw(data.json) }); + }); + + /** + * Sets protocol handler data + * @param scheme + * @param data + */ + router->map("protocol.setData", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scheme", "data"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + const auto scheme = message.get("scheme"); + const auto data = message.get("data"); + + if (!router->core->protocolHandlers.hasHandler(scheme)) { + return reply(Result::Err { message, JSON::Object::Entries { + {"message", "Protocol handler scheme is not registered."} + }}); + } + + router->core->protocolHandlers.setHandlerData(scheme, { data }); + + reply(Result { message.seq, message }); + }); + + /** + * Prints incoming message value to stdout. + * @param value + */ + router->map("stdout", [=](auto message, auto router, auto reply) { + if (message.value.size() > 0) { + #if defined(__APPLE__) + os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", message.value.c_str()); + #endif + IO::write(message.value, false); + } else if (message.buffer.bytes != nullptr && message.buffer.size > 0) { + IO::write(String(*message.buffer.bytes, message.buffer.size), false); + } + + reply(Result { message.seq, message }); + }); + + /** + * Prints incoming message value to stderr. + * @param value + */ + router->map("stderr", [=](auto message, auto router, auto reply) { + if (message.get("debug") == "true") { + if (message.value.size() > 0) { + debug("%s", message.value.c_str()); + } + } else if (message.value.size() > 0) { + #if defined(__APPLE__) + os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", message.value.c_str()); + #endif + IO::write(message.value, true); + } else if (message.buffer.bytes != nullptr && message.buffer.size > 0) { + IO::write(String(*message.buffer.bytes, message.buffer.size), true); + } + + reply(Result { message.seq, message }); + }); + + /** + * Registers a service worker script for a given scope. + * @param scriptURL + * @param scope + */ + router->map("serviceWorker.register", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scriptURL", "scope"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto options = ServiceWorkerContainer::RegistrationOptions { + .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, + .scope = message.get("scope"), + .scriptURL = message.get("scriptURL") + }; + + const auto registration = router->core->serviceWorker.registerServiceWorker(options); + auto json = JSON::Object { + JSON::Object::Entries { + {"registration", registration.json()} + } + }; + + reply(Result::Data { message, json }); + }); + + /** + * Resets the service worker container state. + */ + router->map("serviceWorker.reset", [=](auto message, auto router, auto reply) { + router->core->serviceWorker.reset(); + reply(Result::Data { message, JSON::Object {}}); + }); + + /** + * Unregisters a service worker for given scoep. + * @param scope + */ + router->map("serviceWorker.unregister", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scope"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto scope = message.get("scope"); + router->core->serviceWorker.unregisterServiceWorker(scope); + + return reply(Result::Data { message, JSON::Object {} }); + }); + + /** + * Gets registration information for a service worker scope. + * @param scope + */ + router->map("serviceWorker.getRegistration", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"scope"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto scope = message.get("scope"); + + for (const auto& entry : router->core->serviceWorker.registrations) { + const auto& registration = entry.second; + if (scope.starts_with(registration.options.scope)) { + auto json = JSON::Object { + JSON::Object::Entries { + {"registration", registration.json()}, + {"client", JSON::Object::Entries { + {"id", std::to_string(router->bridge->id)} + }} + } + }; + + return reply(Result::Data { message, json }); + } + } + + return reply(Result::Data { message, JSON::Object {} }); + }); + + /** + * Gets all service worker scope registrations. + */ + router->map("serviceWorker.getRegistrations", [=](auto message, auto router, auto reply) { + auto json = JSON::Array::Entries {}; + for (const auto& entry : router->core->serviceWorker.registrations) { + const auto& registration = entry.second; + json.push_back(registration.json()); + } + return reply(Result::Data { message, json }); + }); + + /** + * Informs container that a service worker will skip waiting. + * @param id + */ + router->map("serviceWorker.skipWaiting", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->serviceWorker.skipWaiting(id); + + reply(Result::Data { message, JSON::Object {}}); + }); + + /** + * Updates service worker controller state. + * @param id + * @param state + */ + router->map("serviceWorker.updateState", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "state"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + const auto workerURL = message.get("workerURL"); + const auto scriptURL = message.get("scriptURL"); + + if (workerURL.size() > 0 && scriptURL.size() > 0) { + router->location.workers[workerURL] = scriptURL; + } + + router->core->serviceWorker.updateState(id, message.get("state")); + reply(Result::Data { message, JSON::Object {}}); + }); + + /** + * Sets storage for a service worker. + * @param id + * @param key + * @param value + */ + router->map("serviceWorker.storage.set", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "key", "value"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + registration.storage.set(message.get("key"), message.get("value")); + return reply(Result::Data { message, JSON::Object {}}); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Gets a storage value for a service worker. + * @param id + * @param key + */ + router->map("serviceWorker.storage.get", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "key"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + return reply(Result::Data { + message, + JSON::Object::Entries { + {"value", registration.storage.get(message.get("key"))} + } + }); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Remoes a storage value for a service worker. + * @param id + * @param key + */ + router->map("serviceWorker.storage.remove", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "key"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + registration.storage.remove(message.get("key")); + return reply(Result::Data {message, JSON::Object {}}); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Clears all storage values for a service worker. + * @param id + */ + router->map("serviceWorker.storage.clear", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + registration.storage.clear(); + return reply(Result::Data { message, JSON::Object {} }); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + /** + * Gets all storage values for a service worker. + * @param id + */ + router->map("serviceWorker.storage", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + for (auto& entry : router->core->serviceWorker.registrations) { + if (entry.second.id == id) { + auto& registration = entry.second; + return reply(Result::Data { message, registration.storage.json() }); + } + } + + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"} + } + }); + }); + + router->map("timers.setTimeout", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"timeout"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint32_t timeout; + REQUIRE_AND_GET_MESSAGE_VALUE(timeout, "timeout", std::stoul); + const auto wait = message.get("wait") == "true"; + const Core::Timers::ID id = router->core->timers.setTimeout(timeout, [=]() { + if (wait) { + reply(Result::Data { message, JSON::Object::Entries {{"id", std::to_string(id) }}}); + } + }); + + if (!wait) { + reply(Result::Data { message, JSON::Object::Entries {{"id", std::to_string(id) }}}); + } + }); + + router->map("timers.clearTimeout", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + router->core->timers.clearTimeout(id); + + reply(Result::Data { message, JSON::Object::Entries {{"id", std::to_string(id) }}}); + }); + + /** + * Binds an UDP socket to a specified port, and optionally a host + * address (default: 0.0.0.0). + * @param id Handle ID of underlying socket + * @param port Port to bind the UDP socket to + * @param address The address to bind the UDP socket to (default: 0.0.0.0) + * @param reuseAddr Reuse underlying UDP socket address (default: false) + */ + router->map("udp.bind", [=](auto message, auto router, auto reply) { + Core::UDP::BindOptions options; + auto err = validateMessageParameters(message, {"id", "port"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(options.port, "port", std::stoi); + + options.reuseAddr = message.get("reuseAddr") == "true"; + options.address = message.get("address", "0.0.0.0"); + + router->core->udp.bind( + message.seq, + id, + options, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Close socket handle and underlying UDP socket. + * @param id Handle ID of underlying socket + */ + router->map("udp.close", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.close(message.seq, id, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + /** + * Connects an UDP socket to a specified port, and optionally a host + * address (default: 0.0.0.0). + * @param id Handle ID of underlying socket + * @param port Port to connect the UDP socket to + * @param address The address to connect the UDP socket to (default: 0.0.0.0) + */ + router->map("udp.connect", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "port"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + Core::UDP::ConnectOptions options; + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(options.port, "port", std::stoi); + + options.address = message.get("address", "0.0.0.0"); + + router->core->udp.connect( + message.seq, + id, + options, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Disconnects a connected socket handle and underlying UDP socket. + * @param id Handle ID of underlying socket + */ + router->map("udp.disconnect", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.disconnect( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Returns connected peer socket address information. + * @param id Handle ID of underlying socket + */ + router->map("udp.getPeerName", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.getPeerName( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Returns local socket address information. + * @param id Handle ID of underlying socket + */ + router->map("udp.getSockName", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.getSockName( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Returns socket state information. + * @param id Handle ID of underlying socket + */ + router->map("udp.getState", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.getState( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Initializes socket handle to start receiving data from the underlying + * socket and route through the IPC bridge to the WebView. + * @param id Handle ID of underlying socket + */ + router->map("udp.readStart", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.readStart( + message.seq, + id, + [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + } + ); + }); + + /** + * Stops socket handle from receiving data from the underlying + * socket and routing through the IPC bridge to the WebView. + * @param id Handle ID of underlying socket + */ + router->map("udp.readStop", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->core->udp.readStop( + message.seq, + id, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + /** + * Broadcasts a datagram on the socket. For connectionless sockets, the + * destination port and address must be specified. Connected sockets, on the + * other hand, will use their associated remote endpoint, so the port and + * address arguments must not be set. + * @param id Handle ID of underlying socket + * @param port The port to send data to + * @param size The size of the bytes to send + * @param bytes A pointer to the bytes to send + * @param address The address to send to (default: 0.0.0.0) + * @param ephemeral Indicates that the socket handle, if created is ephemeral and should eventually be destroyed + */ + router->map("udp.send", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "port"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + Core::UDP::SendOptions options; + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + REQUIRE_AND_GET_MESSAGE_VALUE(options.port, "port", std::stoi); + + options.size = message.buffer.size; + options.bytes = *message.buffer.bytes; + options.address = message.get("address", "0.0.0.0"); + options.ephemeral = message.get("ephemeral") == "true"; + + router->core->udp.send( + message.seq, + id, + options, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + + router->map("window.showFileSystemPicker", [=](auto message, auto router, auto reply) { + const auto allowMultiple = message.get("allowMultiple") == "true"; + const auto allowFiles = message.get("allowFiles") == "true"; + const auto allowDirs = message.get("allowDirs") == "true"; + const auto isSave = message.get("type") == "save"; + + const auto contentTypeSpecs = message.get("contentTypeSpecs"); + const auto defaultName = message.get("defaultName"); + const auto defaultPath = message.get("defaultPath"); + const auto title = message.get("title", isSave ? "Save" : "Open"); + + Dialog dialog; + auto options = Dialog::FileSystemPickerOptions { + .directories = allowDirs, + .multiple = allowMultiple, + .files = allowFiles, + .contentTypes = contentTypeSpecs, + .defaultName = defaultName, + .defaultPath = defaultPath, + .title = title + }; + + if (isSave) { + const auto result = dialog.showSaveFilePicker(options); + + if (result.size() == 0) { + auto err = JSON::Object::Entries {{"type", "AbortError"}}; + reply(Result::Err { message, err }); + } else { + auto data = JSON::Object::Entries { + {"paths", JSON::Array::Entries{result}} + }; + reply(Result::Data { message, data }); + } + } else { + JSON::Array paths; + const auto results = ( + allowFiles && !allowDirs + ? dialog.showOpenFilePicker(options) + : dialog.showDirectoryPicker(options) + ); + + for (const auto& result : results) { + paths.push(result); + } + + auto data = JSON::Object::Entries { + {"paths", paths} + }; + + reply(Result::Data { message, data }); + } + }); +} + +namespace SSC::IPC { + void Router::init () { + mapIPCRoutes(this); + } +} diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc new file mode 100644 index 0000000000..2334376868 --- /dev/null +++ b/src/ipc/scheme_handlers.cc @@ -0,0 +1,1227 @@ +#include "scheme_handlers.hh" +#include "ipc.hh" + +using namespace SSC; + +#if SSC_PLATFORM_APPLE +using Task = id; + +@class SSCBridgedWebView; +@interface SSCInternalWKURLSchemeHandler : NSObject +@property (nonatomic) SSC::IPC::SchemeHandlers* handlers; + +- (void) webView: (SSCBridgedWebView*) webview + startURLSchemeTask: (id) task; + +- (void) webView: (SSCBridgedWebView*) webview + stopURLSchemeTask: (id) task; +@end + +@implementation SSCInternalWKURLSchemeHandler +{ + Mutex mutex; + std::unordered_map tasks; +} + +- (void) enqueueTask: (Task) task + withRequestID: (uint64_t) id +{ + Lock lock(mutex); + if (task != nullptr && !tasks.contains(task)) { + tasks.emplace(task, id); + } +} + +- (void) finalizeTask: (Task) task { + Lock lock(mutex); + if (task != nullptr && tasks.contains(task)) { + tasks.erase(task); + } +} + +- (bool) waitingForTask: (Task) task { + Lock lock(mutex); + return task != nullptr && tasks.contains(task); +} + +- (void) webView: (SSCBridgedWebView*) webview + stopURLSchemeTask: (Task) task +{ + Lock lock(mutex); + + if (tasks.contains(task)) { + const auto id = tasks[task]; + if (self.handlers->requests.contains(id)) { + auto& request = self.handlers->requests.at(id); + request.cancelled = true; + if (request.callbacks.cancel != nullptr) { + request.callbacks.cancel(); + } + } + } + + [self finalizeTask: task]; +} + +- (void) webView: (SSCBridgedWebView*) webview + startURLSchemeTask: (Task) task +{ + auto request = IPC::SchemeHandlers::Request::Builder(self.handlers, task) + .setMethod(toUpperCase(task.request.HTTPMethod.UTF8String)) + // copies all headers + .setHeaders(task.request.allHTTPHeaderFields) + // copies request body + .setBody(task.request.HTTPBody) + .build(); + + [self enqueueTask: task withRequestID: request.id]; + const auto handled = self.handlers->handleRequest(request, [=](const auto& response) { + [self finalizeTask: task]; + }); + + if (!handled) { + auto response = IPC::SchemeHandlers::Response(request, 404); + response.finish(); + [self finalizeTask: task]; + return; + } +} +@end +#elif SSC_PLATFORM_LINUX +static const auto MAX_URI_SCHEME_REQUEST_BODY_BYTES = 4 * 1024 * 1024; +static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer userData) { + static auto windowManager = App::instance()->getWindowManager(); + static auto userConfig = SSC::getUserConfig(); + auto webview = webkit_uri_scheme_request_get_web_view(schemeRequest); + + if (!windowManager) { + const auto quark = g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()); + const auto error = g_error_new(quark, 1, "Missing WindowManager in request"); + webkit_uri_scheme_request_finish_error(schemeRequest, error); + return; + } + + IPC::Router* router = nullptr; + + for (auto& window : windowManager->windows) { + if ( + window != nullptr && + window->bridge != nullptr && + WEBKIT_WEB_VIEW(window->webview) == webview + ) { + router = &window->bridge->router; + break; + } + } + + if (!router) { + const auto quark = g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()); + const auto error = g_error_new(quark, 1, "Missing IPC bridge router in request"); + webkit_uri_scheme_request_finish_error(schemeRequest, error); + return; + } + + auto request = IPC::SchemeHandlers::Request::Builder(router->schemeHandlers, schemeRequest) + .setMethod(String(webkit_uri_scheme_request_get_http_method(platformRequest))) + // copies all request soup headers + .setHeaders(webkit_uri_scheme_request_get_http_headers(request)) + // reads and copies request stream body + .setBody(webkit_uri_scheme_request_get_http_body(schemeRequest)) + .build(); + + const auto handled = router->schemeHandlers.handleRequest(request, [=](const auto response) { + // TODO(@jwerle): handle uri scheme response + }); + + if (!handled) { + auto response = IPC::SchemeHandlers::Response(request, 404); + callback(response); + return; + } +} + +#endif + +namespace SSC::IPC { + class SchemeHandlersInternals { + public: + SchemeHandlers* handlers = nullptr; + #if SSC_PLATFORM_APPLE + SSCInternalWKURLSchemeHandler* schemeHandler = nullptr; + #endif + + SchemeHandlersInternals (SchemeHandlers* handlers) { + this->handlers = handlers; + #if SSC_PLATFORM_APPLE + this->schemeHandler = [SSCInternalWKURLSchemeHandler new]; + this->schemeHandler.handlers = handlers; + #endif + } + + ~SchemeHandlersInternals () { + #if SSC_PLATFORM_APPLE + if (this->schemeHandler != nullptr) { + #if !__has_feature(objc_arc) + [this->schemeHandler release]; + #endif + this->schemeHandler = nullptr; + } + #endif + } + }; +} + +namespace SSC::IPC { +#if SSC_PLATFORM_LINUX + static Set globallyRegisteredSchemesForLinux; +#endif + static const std::map STATUS_CODES = { + {100, "Continue"}, + {101, "Switching Protocols"}, + {102, "Processing"}, + {103, "Early Hints"}, + {200, "OK"}, + {201, "Created"}, + {202, "Accepted"}, + {203, "Non-Authoritative Information"}, + {204, "No Content"}, + {205, "Reset Content"}, + {206, "Partial Content"}, + {207, "Multi-Status"}, + {208, "Already Reported"}, + {226, "IM Used"}, + {300, "Multiple Choices"}, + {301, "Moved Permanently"}, + {302, "Found"}, + {303, "See Other"}, + {304, "Not Modified"}, + {305, "Use Proxy"}, + {307, "Temporary Redirect"}, + {308, "Permanent Redirect"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {402, "Payment Required"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {405, "Method Not Allowed"}, + {406, "Not Acceptable"}, + {407, "Proxy Authentication Required"}, + {408, "Request Timeout"}, + {409, "Conflict"}, + {410, "Gone"}, + {411, "Length Required"}, + {412, "Precondition Failed"}, + {413, "Payload Too Large"}, + {414, "URI Too Long"}, + {415, "Unsupported Media Type"}, + {416, "Range Not Satisfiable"}, + {417, "Expectation Failed"}, + {418, "I'm a Teapot"}, + {421, "Misdirected Request"}, + {422, "Unprocessable Entity"}, + {423, "Locked"}, + {424, "Failed Dependency"}, + {425, "Too Early"}, + {426, "Upgrade Required"}, + {428, "Precondition Required"}, + {429, "Too Many Requests"}, + {431, "Request Header Fields Too Large"}, + {451, "Unavailable For Legal Reasons"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {504, "Gateway Timeout"}, + {505, "HTTP Version Not Supported"}, + {506, "Variant Also Negotiates"}, + {507, "Insufficient Storage"}, + {508, "Loop Detected"}, + {509, "Bandwidth Limit Exceeded"}, + {510, "Not Extended"}, +{511, "Network Authentication Required"} + }; + + SchemeHandlers::SchemeHandlers (Router* router) { + this->router = router; + this->internals = new SchemeHandlersInternals(this); + } + + SchemeHandlers::~SchemeHandlers () { + if (this->internals != nullptr) { + delete this->internals; + this->internals = nullptr; + } + } + + void SchemeHandlers::configure (const Configuration& configuration) { + this->configuration = configuration; + } + + bool SchemeHandlers::hasHandlerForScheme (const String& scheme) { + Lock lock(this->mutex); + return this->handlers.contains(scheme); + } + + bool SchemeHandlers::registerSchemeHandler (const String& scheme, const Handler& handler) { + Lock lock(this->mutex); + + if (scheme.size() == 0 || this->hasHandlerForScheme(scheme)) { + return false; + } + + #if SSC_PLATFORM_APPLE + if (this->configuration.webview == nullptr) { + return false; + } + + [this->configuration.webview + setURLSchemeHandler: this->internals->schemeHandler + forURLScheme: @(scheme.c_str()) + ]; + + #elif SSC_PLATFORM_LINUX + // schemes are registered for the globally shared defaut context, + // despite the `SchemeHandlers` instance bound to a `Router` + // we'll select the correct `Router` in the callback which will give + // access to the `SchemeHandlers` that should handle the + // request and provide a response + if ( + std::find( + globallyRegisteredSchemesForLinux.begin(), + globallyRegisteredSchemesForLinux.end(), + scheme + ) != globallyRegisteredSchemesForLinux.end() + ) { + globallyRegisteredSchemesForLinux.insert(scheme); + auto context = webkit_web_context_get_default(); + auto security = webkit_web_context_get_security_manager(context); + webkit_web_context_register_uri_scheme( + context, + scheme.c_str(), + onURISchemeRequest + nullptr, + nullptr + ); + } + #elif SSC_PLATFORM_WINDOWS + #endif + + this->handlers.insert_or_assign(scheme, handler); + return true; + } + + bool SchemeHandlers::handleRequest ( + const Request& request, + const HandlerCallback callback + ) { + // request was not finalized, likely not from a `Request::Builder` + if (!request.finalized) { + return false; + } + + // already an active request + if (this->requests.contains(request.id)) { + return false; + } + + // respond with a 404 if somehow we are trying to respond to a + // request scheme we do not know about, we do not need to call + // `request.finalize()` as we'll just respond to the request right away + if (!this->handlers.contains(request.scheme)) { + auto response = IPC::SchemeHandlers::Response(request, 404); + // make sure the response was finished first + response.finish(); + + // notify finished, even for 404 + if (response.request.callbacks.finish != nullptr) { + response.request.callbacks.finish(); + } + + if (callback != nullptr) { + callback(response); + } + + return true; + } + + const auto handler = this->handlers.at(request.scheme); + + // fail if there is somehow not a handler for this request scheme + if (handler == nullptr) { + return false; + } + + if (request.error != nullptr) { + auto response = IPC::SchemeHandlers::Response(request, 500); + response.fail(request.error); + return true; + } + + const auto result = this->requests.insert_or_assign(request.id, std::move(request)); + const auto id = request.id; + + // stored request reference + auto& req = result.first->second; + + handler(req, this->router, req.callbacks, [this, id, callback](auto& response) { + // make sure the response was finished before + // calling the `callback` function below + response.finish(); + + // notify finished + if (response.request.callbacks.finish != nullptr) { + response.request.callbacks.finish(); + } + + if (callback != nullptr) { + callback(response); + } + + this->requests.erase(id); + }); + + return true; + } + + bool SchemeHandlers::isRequestActive (uint64_t id) { + Lock lock(this->mutex); + return this->requests.contains(id); + } + + bool SchemeHandlers::isRequestCancelled (uint64_t id) { + Lock lock(this->mutex); + return ( + id > 0 && + this->requests.contains(id) && + this->requests.at(id).cancelled + ); + } + + SchemeHandlers::Request::Builder::Builder ( + SchemeHandlers* handlers, + PlatformRequest platformRequest + ) { + #if SSC_PLATFORM_APPLE + this->absoluteURL = platformRequest.request.URL.absoluteString.UTF8String; + #elif SSC_PLATFORM_LINUX + this->absoluteURL = webkit_uri_scheme_request_get_uri(schemeRequest); + #elif SSC_PLATFORM_WINDOWS + LPWSTR requestURI; + platformRequest->get_Uri(&requestURI); + this->absoluteURL = convertWStringToString(requestURI); + CoTaskMemFree(requestURI); + #endif + + const auto userConfig = handlers->router->bridge->userConfig; + const auto components = IPC::Router::parseURLComponents(this->absoluteURL); + const auto bundleIdentifier = userConfig.contains("meta_bundle_identifier") + ? userConfig.at("meta_bundle_identifier") + : ""; + + this->request.reset(new Request( + handlers, + platformRequest, + Request::Options { + .scheme = components.scheme + } + )); + + // default client id, can be overloaded with 'runtime-client-id' header + this->request->client.id = handlers->router->bridge->id; + + // build request URL components from parsed URL components + this->request->originalURL = components.originalURL; + this->request->hostname = components.authority; + this->request->pathname = components.pathname; + this->request->query = components.query; + this->request->fragment = components.fragment; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setScheme (const String& scheme) { + this->request->scheme = scheme; + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setMethod (const String& method) { + this->request->method = method; + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setHostname (const String& hostname) { + this->request->hostname = hostname; + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setPathname (const String& pathname) { + this->request->pathname = pathname; + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setQuery (const String& query) { + this->request->query = query; + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setFragment (const String& fragment) { + this->request->fragment = fragment; + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setHeader ( + const String& name, + const Headers::Value& value + ) { + this->request->headers.set(name, value.string); + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setHeaders (const Headers& headers) { + for (const auto& entry : headers) { + this->request->headers.set(entry); + } + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setHeaders (const Map& headers) { + for (const auto& entry : headers) { + this->request->headers.set(entry.first, entry.second); + } + return *this; + } + +#if SSC_PLATFORM_APPLE + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setHeaders ( + const NSDictionary* headers + ) { + if (headers == nullptr) { + return *this; + } + + for (NSString* key in headers) { + const auto value = [headers objectForKey: key]; + if (value != nullptr) { + this->request->headers.set(key.UTF8String, value.UTF8String); + } + } + + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setBody (const NSData* data) { + if (data != nullptr && data.length > 0 && data.bytes != nullptr) { + return this->setBody(data.length, reinterpret_cast(data.bytes)); + } + return *this; + } +#elif SSC_PLATFORM_LINUX + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setHeaders ( + const SoupMessageHeaders* headers + ) { + if (headers == nullptr) { + return *this; + } + + soup_message_headers_foreach( + headers, + [](auto name, auto value, auto userData) { + auto request = reinterpret_cast(userData); + request->headers.set(name, value); + }, + this->request + ); + + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setBody ( + const GInputStream* stream + ) { + if (stream == nullptr) { + return *this; + } + + if (this->request->method == "POST" || this->request->method == "PUT" || this->request->method == "PATCH") { + GError* error = nullptr; + this->request->body->bytes = std::make_shared(new char[MAX_URI_SCHEME_REQUEST_BODY_BYTES]{0}); + const auto success = g_input_stream_read_all( + stream, + *this->request.body.bytes + MAX_URI_SCHEME_REQUEST_BODY_BYTES, + &this->request->body.size, + nullptr, + &this->error + ); + } + return *this; + } +#endif + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setBody (const Body& body) { + if (this->request->method == "POST" || this->request->method == "PUT" || this->request->method == "PATCH") { + this->request->body = body; + } + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setBody (size_t size, const char* bytes) { + if (this->request->method == "POST" || this->request->method == "PUT" || this->request->method == "PATCH") { + if (size > 0 && bytes != nullptr) { + this->request->body.size = size; + this->request->body.bytes = std::make_shared(new char[size]{0}); + memcpy( + *this->request->body.bytes, + bytes, + this->request->body.size + ); + } + } + return *this; + } + + SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setCallbacks (const RequestCallbacks& callbacks) { + this->request->callbacks = callbacks; + return *this; + } + + SchemeHandlers::Request& SchemeHandlers::Request::Builder::build () { + this->request->error = this->error; + this->request->finalize(); + return *this->request; + } + + SchemeHandlers::Request::Request ( + SchemeHandlers* handlers, + PlatformRequest platformRequest, + const Options& options + ) + : handlers(handlers), + router(handlers->router), + scheme(options.scheme), + method(options.method), + hostname(options.hostname), + pathname(options.pathname), + query(options.query), + fragment(options.fragment), + headers(options.headers) + { + this->platformRequest = platformRequest; + } + + SchemeHandlers::Request::~Request () { + } + + static void copyRequest ( + SchemeHandlers::Request* destination, + const SchemeHandlers::Request& source + ) noexcept { + destination->id = source.id; + destination->scheme = source.scheme; + destination->method = source.method; + destination->hostname = source.hostname; + destination->pathname = source.pathname; + destination->query = source.query; + destination->fragment = source.fragment; + + destination->headers = source.headers; + destination->body = source.body; + + destination->client = source.client; + destination->callbacks = source.callbacks; + destination->originalURL = source.originalURL; + + if (destination->finalized) { + destination->origin = source.origin; + destination->params = source.params; + } + + destination->finalized = source.finalized.load(); + destination->cancelled = source.cancelled.load(); + + destination->router = source.router; + destination->handlers = source.handlers; + destination->platformRequest = source.platformRequest; + } + + SchemeHandlers::Request::Request (const Request& request) noexcept { + copyRequest(this, request); + } + + SchemeHandlers::Request::Request (Request&& request) noexcept { + copyRequest(this, request); + } + + SchemeHandlers::Request& SchemeHandlers::Request::operator= (const Request& request) noexcept { + copyRequest(this, request); + return *this; + } + + SchemeHandlers::Request& SchemeHandlers::Request::operator= (Request&& request) noexcept { + copyRequest(this, request); + return *this; + } + + bool SchemeHandlers::Request::hasHeader (const String& name) const { + if (this->headers.has(name)) { + return true; + } + + return false; + } + + const String SchemeHandlers::Request::getHeader (const String& name) const { + return this->headers.get(name).value.string; + } + + const String SchemeHandlers::Request::url () const { + return this->str(); + } + + const String SchemeHandlers::Request::str () const { + if (this->hostname.size() > 0) { + return ( + this->scheme + + "://" + + this->hostname + + this->pathname + + (this->query.size() ? "?" + this->query : "") + + (this->fragment.size() ? "#" + this->fragment : "") + ); + } + + return ( + this->scheme + + ":" + + this->pathname.substr(1) + + (this->query.size() ? "?" + this->query : "") + + (this->fragment.size() ? "#" + this->fragment : "") + ); + } + + bool SchemeHandlers::Request::finalize () { + if (this->finalized) { + return false; + } + + if (this->hasHeader("runtime-client-id")) { + try { + this->client.id = std::stoull(this->getHeader("runtime-client-id")); + } catch (...) {} + } + + for (const auto& entry : split(this->query, '&')) { + const auto parts = split(entry, '='); + if (parts.size() == 2) { + const auto key = decodeURIComponent(trim(parts[0])); + const auto value = decodeURIComponent(trim(parts[1])); + this->params.insert_or_assign(key, value); + } + } + + this->finalized = true; + this->origin = this->scheme + "://" + this->hostname; + return true; + } + + bool SchemeHandlers::Request::isActive () const { + return this->handlers != nullptr && this->handlers->isRequestActive(this->id); + } + + bool SchemeHandlers::Request::isCancelled () const { + return this->handlers != nullptr && this->handlers->isRequestCancelled(this->id); + } + + JSON::Object SchemeHandlers::Request::json () const { + return JSON::Object::Entries { + {"scheme", this->scheme}, + {"method", this->method}, + {"hostname", this->hostname}, + {"pathname", this->pathname}, + {"query", this->query}, + {"fragment", this->fragment}, + {"headers", this->headers.json()}, + {"client", JSON::Object::Entries { + {"id", this->client.id} + }} + }; + } + + SchemeHandlers::Response::Response ( + const Request& request, + int statusCode, + const Headers headers + ) : request(std::move(request)), + handlers(request.handlers), + client(request.client), + id(request.id) + { + const auto defaultHeaders = split( + this->request.router->bridge->userConfig["webview_headers"], + '\n' + ); + + if (SSC::isDebugEnabled()) { + this->setHeader("cache-control", "no-cache"); + } + + this->setHeader("access-control-allow-origin", "*"); + this->setHeader("access-control-allow-methods", "*"); + this->setHeader("access-control-allow-headers", "*"); + this->setHeader("access-control-allow-credentials", "true"); + + for (const auto& entry : defaultHeaders) { + const auto parts = split(trim(entry), ':'); + this->setHeader(parts[0], parts[1]); + } + } + + static void copyResponse ( + SchemeHandlers::Response* destination, + const SchemeHandlers::Response& source + ) noexcept { + destination->id = source.id; + destination->client = source.client; + destination->headers = source.headers; + destination->finished = source.finished.load(); + destination->handlers = source.handlers; + destination->statusCode = source.statusCode; + destination->platformResponse = source.platformResponse; + } + + SchemeHandlers::Response::Response (const Response& response) noexcept + : request(response.request) + { + copyResponse(this, response); + } + + SchemeHandlers::Response::Response (Response&& response) noexcept + : request(response.request) + { + copyResponse(this, response); + } + + SchemeHandlers::Response::~Response () {} + + SchemeHandlers::Response& SchemeHandlers::Response::operator= (const Response& response) noexcept { + copyResponse(this, response); + return *this; + } + + SchemeHandlers::Response& SchemeHandlers::Response::operator= (Response&& response) noexcept { + copyResponse(this, response); + return *this; + } + + bool SchemeHandlers::Response::writeHead (int statusCode, const Headers headers) { + // fail if already finished + if (this->finished) { + return false; + } + + if ( + !this->handlers->isRequestActive(this->id) || + this->handlers->isRequestCancelled(this->id) + ) { + return false; + } + + // fail if head of response is already created + if (this->platformResponse != nullptr) { + return false; + } + + if (this->request.platformRequest == nullptr) { + return false; + } + + if (statusCode >= 100 && statusCode < 600) { + this->statusCode = statusCode; + } + + for (const auto& header : headers) { + this->setHeader(header); + } + + #if SSC_PLATFORM_APPLE || SSC_PLATFORM_LINUX + // webkit status codes cannot be in the range of 300 >= statusCode < 400 + if (this->statusCode >= 300 && this->statusCode < 400) { + this->statusCode = 200; + } + #endif + + #if SSC_PLATFORM_APPLE + auto headerFields= [NSMutableDictionary dictionary]; + for (const auto& entry : this->headers) { + headerFields[@(entry.name.c_str())] = @(entry.value.c_str()); + } + + auto platformRequest = this->request.platformRequest; + if (platformRequest != nullptr && platformRequest.request != nullptr) { + const auto url = platformRequest.request.URL; + if (url != nullptr) { + @try { + this->platformResponse = [[NSHTTPURLResponse alloc] + initWithURL: platformRequest.request.URL + statusCode: this->statusCode + HTTPVersion: @"HTTP/1.1" + headerFields: headerFields + ]; + } @catch (::id) { + return false; + } + + [platformRequest didReceiveResponse: this->platformResponse]; + return true; + } + } + #elif SSC_PLATFORM_LINUX + const auto contentLength = this->headers.has("content-length") + ? this->headers.get("content-length") + : String(""); + + gint64 size = -1; + if (contentLength.size() > 0) { + try { + size = std::stol(contentLength); + } catch (...) { + } + } + + this->platformResponseStream = g_memory_input_stream_new(); + this->platformResponse = webkit_uri_scheme_response_new(stream, size); + + auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); + for (const auto& entry : this->headers) { + soup_message_headers_append(headers, entry.name.c_str(), entry.value.c_str()); + } + + webkit_uri_scheme_response_set_http_headers(this->platformResponse, headers); + + if (this->headers.has("content-type")) { + const auto contentType = this->headers.get("content-type"); + if (contentType.size() > 0) { + webkit_uri_scheme_response_set_content_type(this->platformResponse, contentType.c_str()); + } + } + + const auto statusText = String( + STATUS_CODES.contains(this->statusCode) + ? STATUS_CODES.at(this->statusCode) + : "" + ); + + webkit_uri_scheme_response_set_status( + this->platformResponse, + this->statusCode, + statusText.size() > 0 ? statusText.c_str() : nullptr + ); + + #elif SSC_PLATFORM_WINDOWS + #endif + return false; + } + + bool SchemeHandlers::Response::write (size_t size, const char* bytes) { + if ( + !this->handlers->isRequestActive(this->id) || + this->handlers->isRequestCancelled(this->id) + ) { + return false; + } + + if (!this->platformResponse) { + // set 'content-length' header if response was not created + this->setHeader("content-length", size); + if (!this->writeHead()) { + return false; + } + } + + if (size > 0 && bytes != nullptr) { + #if SSC_PLATFORM_APPLE + const auto data = [NSData dataWithBytes: bytes length: size]; + @try { + [this->request.platformRequest didReceiveData: data]; + } @catch (::id) { + return false; + } + return true; + #elif SSC_PLATFORM_LINUX + g_memory_input_stream_add_data( + this->platformResponseStream, + const_cast(reinterpret_cast(bytes)), + size, + nullptr + ); + return true; + #elif SSC_PLATFORM_WINDOWS + #endif + } + + return false; + } + + bool SchemeHandlers::Response::write (size_t size, SharedPointer bytes) { + if (bytes != nullptr && size > 0) { + return this->write(size, *bytes); + } + + return false; + } + + bool SchemeHandlers::Response::write (const String& source) { + if (!this->hasHeader("content-type")) { + this->setHeader("content-type", "application/octet-stream"); + } + return this->write(source.size(), source.data()); + } + + bool SchemeHandlers::Response::write (const JSON::Any& json) { + this->setHeader("content-type", "application/json"); + return this->write(json.str()); + } + + bool SchemeHandlers::Response::write (const FileResource& resource) { + auto responseResource = FileResource(resource.path.string()); + const auto contentLength = responseResource.size(); + const auto contentType = responseResource.mimeType(); + + if (contentType.size() > 0) { + this->setHeader("content-type", contentType); + } + + if (contentLength > 0) { + this->setHeader("content-length", contentLength); + } + + if (contentLength > 0) { + const auto data = responseResource.read(); + return this->write(responseResource.size(), data); + } + + return false; + } + + bool SchemeHandlers::Response::send (const String& source) { + return this->write(source) && this->finish(); + } + + bool SchemeHandlers::Response::send (const JSON::Any& json) { + return this->write(json) && this->finish(); + } + + bool SchemeHandlers::Response::send (const FileResource& resource) { + return this->write(resource) && this->finish(); + } + + bool SchemeHandlers::Response::finish () { + // fail if already finished + if (this->finished) { + return false; + } + + if ( + !this->handlers->isRequestActive(this->id) || + this->handlers->isRequestCancelled(this->id) + ) { + return false; + } + + if (!this->platformResponse) { + if (!this->writeHead()) { + return false; + } + } + + #if SSC_PLATFORM_APPLE + @try { + [this->request.platformRequest didFinish]; + } @catch (::id) {} + #if !__has_feature(objc_arc) + [this->platformResponse release]; + #endif + #elif SSC_PLATFORM_LINUX + webkit_uri_scheme_request_finish_with_response( + this->request.platformRequest, + this->platformResponse + ); + g_object_unref(this->platformResponseStream); + this->platformResponseStream = nullptr; + #elif SSC_PLATFORM_WINDOWS + #else + #endif + + this->platformResponse = nullptr; + this->finished = true; + return true; + } + + void SchemeHandlers::Response::setHeader (const String& name, const Headers::Value& value) { + const auto router = this->request.handlers->router; + if (toLowerCase(name) == "referer") { + if (router->location.workers.contains(value.string)) { + const auto workerLocation = router->location.workers[value.string]; + this->headers[name] = workerLocation; + return; + } else if (router->core->serviceWorker.bridge->router.location.workers.contains(value.string)) { + const auto workerLocation = router->core->serviceWorker.bridge->router.location.workers[value.string]; + this->headers[name] = workerLocation; + return; + } + } + + this->headers[name] = value.string; + } + + void SchemeHandlers::Response::setHeader (const Headers::Header& header) { + this->setHeader(header.name, header.value.string); + } + + void SchemeHandlers::Response::setHeader (const String& name, size_t value) { + this->setHeader(name, std::to_string(value)); + } + + void SchemeHandlers::Response::setHeader (const String& name, int64_t value) { + this->setHeader(name, std::to_string(value)); + } + + void SchemeHandlers::Response::setHeader (const String& name, uint64_t value) { + this->setHeader(name, std::to_string(value)); + } + + void SchemeHandlers::Response::setHeaders (const Headers& headers) { + for (const auto& header : headers) { + this->setHeader(header); + } + } + + const String SchemeHandlers::Response::getHeader (const String& name) const { + return this->headers.get(name).value.string; + } + + bool SchemeHandlers::Response::hasHeader (const String& name) const { + return this->headers.has(name); + } + + bool SchemeHandlers::Response::fail (const Error* error) { + #if SSC_PLATFORM_APPLE + if (error.localizedDescription != nullptr) { + return this->fail(error.localizedDescription.UTF8String); + } else if (error.localizedFailureReason != nullptr) { + return this->fail(error.localizedFailureReason.UTF8String); + } else { + return this->fail("Request failed for an unknown reason"); + } + #elif SSC_PLATFORM_LINUX + if (error != nullptr && error->message != nullptr) { + return this->fail(error->message); + } + #elif SSC_PLATFORM_WINDOWS + #else + return this->fail(String(error)); + #endif + } + + bool SchemeHandlers::Response::fail (const String& reason) { + const auto bundleIdentifier = this->request.router->bridge->userConfig["meta_bundle_identifier"]; + if (this->platformResponse != nullptr) { + return false; + } + + #if SSC_PLATFORM_APPLE + @try { + [this->request.platformRequest + didFailWithError: [NSError + errorWithDomain: @(bundleIdentifier.c_str()) + code: 1 + userInfo: @{NSLocalizedDescriptionKey: @(reason.c_str())} + ]]; + } @catch (::id e) { + // ignore possible 'NSInternalInconsistencyException' + return false; + } + #elif SSC_PLATFORM_LINUX + const auto quark = g_quark_from_string(bundleIdentifier.c_str()); + if (quark == nullptr) { + return false; + } + + const auto error = g_error_new(quark 1, reason.c_str()); + + if (error == nullptr) { + return false; + } + + webkit_uri_scheme_request_finish_error(this->platformRequest, error); + #elif SSC_PLATFORM_WINDOWS + #else + #endif + this->finished = true; + return true; + } + + bool SchemeHandlers::Response::redirect (const String& location, int statusCode) { + static constexpr auto redirectSourceTemplate = R"S( + + )S"; + + // if head was already written, then we cannot perform a redirect + if (this->platformResponse) { + return false; + } + + if (location.starts_with("/")) { + this->setHeader("location", this->request.origin + location); + } else if (location.starts_with(".")) { + this->setHeader("location", this->request.origin + location.substr(1)); + } else { + this->setHeader("location", location); + } + + if (!this->writeHead(statusCode)) { + return false; + } + + if (this->request.method != "HEAD" && this->request.method != "OPTIONS") { + const auto content = tmpl( + redirectSourceTemplate, + {{"url", location}} + ); + + if (!this->write(content)) { + return false; + } + } + + return this->finish(); + } + + const String SchemeHandlers::Response::Event::str () const noexcept { + if (this->name.size() > 0 && this->data.size() > 0) { + return ( + String("event: ") + this->name + "\n" + + String("data: ") + this->data + "\n" + "\n" + ); + } + + if (this->name.size() > 0) { + return String("event: ") + this->name + "\n\n"; + } + + if (this->data.size() > 0) { + return String("data: ") + this->data + "\n\n"; + } + + return ""; + } + + size_t SchemeHandlers::Response::Event::count () const noexcept { + if (this->name.size() > 0 && this->data.size() > 0) { + return 2; + } else if (this->name.size() > 0 || this->data.size() > 0) { + return 1; + } else { + return 0; + } + } +} diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh new file mode 100644 index 0000000000..1d32eb7acf --- /dev/null +++ b/src/ipc/scheme_handlers.hh @@ -0,0 +1,240 @@ +#ifndef SSC_IPC_SCHEME_HANDLERS_H +#define SSC_IPC_SCHEME_HANDLERS_H + +#include "../core/core.hh" +#include "../core/platform.hh" + +namespace SSC::IPC { + class Router; + class SchemeHandlers; +} + +namespace SSC::IPC { + class SchemeHandlersInternals; + class SchemeHandlers { + private: + SchemeHandlersInternals* internals = nullptr; + + public: + #if SSC_PLATFORM_APPLE + using Error = NSError; + #elif SSC_PLATFORM_LINUX + using Error = GError; + #else + using Error = char; + #endif + + struct Client { + uint64_t id = 0; + }; + + struct Body { + size_t size = 0; + SharedPointer bytes = nullptr; + }; + + struct RequestCallbacks { + Function cancel; + Function finish; + }; + + #if SSC_PLATFORM_APPLE + using PlatformRequest = id; + using PlatformResponse = NSHTTPURLResponse*; + #elif SSC_PLATFORM_LINUX + using PlatformRequest = WebKitURISchemeRequest*; + using PlatformResponse = WebKitURISchemeResponse*; + #elif SSC_PLATFORM_WINDOWS + using PlatformRequest = ICoreWebView2WebResourceRequest*; + using PlatformResponse = ICoreWebView2WebResourceResponse*; + #else + // TODO + #endif + + struct Request { + struct Options { + String scheme = ""; + String method = "GET"; + String hostname = ""; + String pathname = "/"; + String query = ""; + String fragment = ""; + Headers headers; + Function isCancelled; + }; + + struct Builder { + String absoluteURL; + Error* error = nullptr; + UniquePointer request = nullptr; + + Builder ( + SchemeHandlers* handlers, + PlatformRequest platformRequest + ); + + Builder& setScheme (const String& scheme); + Builder& setMethod (const String& method); + Builder& setHostname (const String& hostname); + Builder& setPathname (const String& pathname); + Builder& setQuery (const String& query); + Builder& setFragment (const String& fragment); + Builder& setHeader (const String& name, const Headers::Value& value); + Builder& setHeaders (const Headers& headers); + Builder& setHeaders (const Map& headers); + + #if SSC_PLATFORM_APPLE + Builder& setHeaders (const NSDictionary* headers); + Builder& setBody (const NSData* data); + #elif SSC_PLATFORM_LINUX + Builder& setHeaders (const SoupMessageHeaders* headers); + Builder& setBody (const GInputStream* stream); + #endif + + Builder& setBody (const Body& body); + Builder& setBody (size_t size, const char* bytes); + Builder& setCallbacks (const RequestCallbacks& callbacks); + Request& build (); + }; + + uint64_t id = rand64(); + String scheme = ""; + String method = "GET"; + String hostname = ""; + String pathname = "/"; + String query = ""; + String fragment = ""; + Headers headers; + + String origin = ""; + Map params; + Body body; + Client client; + String originalURL; + RequestCallbacks callbacks; + + Atomic finalized = false; + Atomic cancelled = false; + + Error* error = nullptr; + const Router* router = nullptr; + SchemeHandlers* handlers = nullptr; + PlatformRequest platformRequest; + + Request ( + SchemeHandlers* handlers, + PlatformRequest platformRequest, + const Options& options + ); + + Request (const Request&) noexcept; + Request (Request&&) noexcept; + ~Request (); + Request& operator= (const Request&) noexcept; + Request& operator= (Request&&) noexcept; + + const String getHeader (const String& name) const; + bool hasHeader (const String& name) const; + const String str () const; + const String url () const; + bool finalize (); + JSON::Object json () const; + bool isActive () const; + bool isCancelled () const; + }; + + struct Response { + struct Event { + String name = ""; + String data = ""; + const String str () const noexcept; + size_t count () const noexcept; + }; + + const Request request; + uint64_t id = rand64(); + int statusCode = 200; + Headers headers; + Client client; + Atomic finished = false; + SchemeHandlers* handlers = nullptr; + PlatformResponse platformResponse = nullptr; + + Response ( + const Request& request, + int statusCode = 200, + const Headers headers = {} + ); + + Response (const Response&) noexcept; + Response (Response&&) noexcept; + ~Response (); + Response& operator= (const Response&) noexcept; + Response& operator= (Response&&) noexcept; + + bool write (size_t size, const char* bytes); + bool write (size_t size, SharedPointer); + bool write (const String& source); + bool write (const JSON::Any& json); + bool write (const FileResource& resource); + bool send (const String& source); + bool send (const JSON::Any& json); + bool send (const FileResource& resource); + bool writeHead (int statusCode = 0, const Headers headers = {}); + bool finish (); + void setHeader (const String& name, const Headers::Value& value); + void setHeader (const String& name, size_t value); + void setHeader (const String& name, int64_t value); + void setHeader (const String& name, uint64_t value); + void setHeader (const Headers::Header& header); + void setHeaders (const Headers& headers); + void setHeaders (const Map& headers); + #if SSC_PLATFORM_APPLE + void setHeaders (const NSDictionary* headers); + #endif + const String getHeader (const String& name) const; + bool hasHeader (const String& name) const; + bool fail (const String& reason); + bool fail (const Error* error); + bool redirect (const String& location, int statusCode = 302); + }; + + using HandlerCallback = Function; + using Handler = Function; + using HandlerMap = std::map; + using RequestMap = std::map; + + struct Configuration { + #if SSC_PLATFORM_APPLE + WKWebViewConfiguration* webview = nullptr; + #endif + }; + + Configuration configuration; + Router* router = nullptr; + HandlerMap handlers; + RequestMap requests; + Mutex mutex; + + #if SSC_PLATFORM_LINUX + GInputStream* platformResponseStream = nullptr + #endif + + SchemeHandlers (Router* router); + ~SchemeHandlers (); + + void configure (const Configuration& configuration); + bool hasHandlerForScheme (const String& scheme); + bool registerSchemeHandler (const String& scheme, const Handler& handler); + bool handleRequest (const Request& request, const HandlerCallback calllback = nullptr); + bool isRequestActive (uint64_t id); + bool isRequestCancelled (uint64_t id); + }; +} + +#endif diff --git a/src/process/win.cc b/src/process/win.cc index 353d46fe50..fa1cc09462 100644 --- a/src/process/win.cc +++ b/src/process/win.cc @@ -9,27 +9,6 @@ namespace SSC { -#if defined(SSC_CLI) -SSC::String FormatError(DWORD error, SSC::String source) { - SSC::StringStream message; - LPVOID lpMsgBuf; - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, NULL ); - - message << "Error " << error << " in " << source << ": " << (LPTSTR)lpMsgBuf; - LocalFree(lpMsgBuf); - - return message.str(); -} -#endif - const static SSC::StringStream initial; Process::Data::Data() noexcept : id(0) {} @@ -229,7 +208,7 @@ Process::id_type Process::open(const SSC::String &command, const SSC::String &pa WaitForSingleObject(_processHandle, INFINITE); if (GetExitCodeProcess(_processHandle, &exitCode) == 0) { - std::cerr << FormatError(GetLastError(), "SSC::Process::open() GetExitCodeProcess()") << std::endl; + std::cerr << formatWindowsError(GetLastError(), "SSC::Process::open() GetExitCodeProcess()") << std::endl; exitCode = -1; } diff --git a/src/window/apple.mm b/src/window/apple.mm index a51a9ac459..fd3b9d7185 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -1,140 +1,50 @@ #include "window.hh" #include "../ipc/ipc.hh" -@implementation SSCNavigationDelegate -- (void) webView: (WKWebView*) webView - didFailNavigation: (WKNavigation*) navigation - withError: (NSError*) error -{ - // TODO(@jwerle) -} - -- (void) webView: (WKWebView*) webView - didFailProvisionalNavigation: (WKNavigation*) navigation - withError: (NSError*) error { - // TODO(@jwerle) -} - -- (void) webView: (WKWebView*) webview - decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction - decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler -{ - if ( - webview != nullptr && - webview.URL != nullptr && - webview.URL.absoluteString.UTF8String != nullptr && - navigationAction != nullptr && - navigationAction.request.URL.absoluteString.UTF8String != nullptr - ) { - auto userConfig = self.bridge->userConfig; - static const auto devHost = SSC::getDevHost(); - static const auto links = SSC::parseStringList(userConfig["meta_application_links"], ' '); - - auto base = SSC::String(webview.URL.absoluteString.UTF8String); - auto request = SSC::String(navigationAction.request.URL.absoluteString.UTF8String); - - const auto applinks = SSC::parseStringList(userConfig["meta_application_links"], ' '); - bool hasAppLink = false; - - if (applinks.size() > 0 && navigationAction.request.URL.host != nullptr) { - auto host = SSC::String(navigationAction.request.URL.host.UTF8String); - for (const auto& applink : applinks) { - const auto parts = SSC::split(applink, '?'); - if (host == parts[0]) { - hasAppLink = true; - break; - } - } - } - - if (hasAppLink) { - if (self.bridge != nullptr) { - decisionHandler(WKNavigationActionPolicyCancel); - SSC::JSON::Object json = SSC::JSON::Object::Entries {{ - "url", request - }}; +#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR +@implementation SSCWindowDelegate +@end +@implementation SSCWindow : NSObject +@end +#else +@implementation SSCWindow +- (void) layoutIfNeeded { + [super layoutIfNeeded]; - self.bridge->router.emit("applicationurl", json.str()); - return; - } - } + if (self.titleBarView == nullptr || self.titleBarView.subviews.count > 0) return; - if ( - userConfig["meta_application_protocol"].size() > 0 && - request.starts_with(userConfig["meta_application_protocol"]) && - !request.starts_with("socket://" + userConfig["meta_bundle_identifier"]) - ) { - if (self.bridge != nullptr) { - decisionHandler(WKNavigationActionPolicyCancel); + NSButton *closeButton = [self standardWindowButton:NSWindowCloseButton]; + NSButton *minimizeButton = [self standardWindowButton:NSWindowMiniaturizeButton]; + NSButton *zoomButton = [self standardWindowButton:NSWindowZoomButton]; - SSC::JSON::Object json = SSC::JSON::Object::Entries {{ - "url", request - }}; + if (closeButton && minimizeButton && zoomButton) { + [self.titleBarView addSubview: closeButton]; + [self.titleBarView addSubview: minimizeButton]; + [self.titleBarView addSubview: zoomButton]; + } +} - self.bridge->router.emit("applicationurl", json.str()); - return; - } +- (void)sendEvent:(NSEvent *)event { + if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeLeftMouseDragged) { + if (event.type == NSEventTypeLeftMouseDown) { + [self.webview mouseDown:event]; } - if (!self.bridge->router.isNavigationAllowed(request)) { - debug("Navigation was ignored for: %s", request.c_str()); - decisionHandler(WKNavigationActionPolicyCancel); + if (event.type == NSEventTypeLeftMouseDragged) { + [self.webview mouseDragged:event]; return; } } - decisionHandler(WKNavigationActionPolicyAllow); + [super sendEvent:event]; } - -- (void) webView: (WKWebView*) webView - decidePolicyForNavigationResponse: (WKNavigationResponse*) navigationResponse - decisionHandler: (void (^)(WKNavigationResponsePolicy)) decisionHandler { - decisionHandler(WKNavigationResponsePolicyAllow); +@end +@implementation SSCWindowDelegate +- (void) userContentController: (WKUserContentController*) userContentController + didReceiveScriptMessage: (WKScriptMessage*) scriptMessage +{ } @end - -#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - @implementation SSCWindowDelegate - @end - @implementation SSCWindow : NSObject - @end -#else - @implementation SSCWindow - - (void)layoutIfNeeded { - [super layoutIfNeeded]; - - if (self.titleBarView == nullptr || self.titleBarView.subviews.count > 0) return; - - NSButton *closeButton = [self standardWindowButton:NSWindowCloseButton]; - NSButton *minimizeButton = [self standardWindowButton:NSWindowMiniaturizeButton]; - NSButton *zoomButton = [self standardWindowButton:NSWindowZoomButton]; - - if (closeButton && minimizeButton && zoomButton) { - [self.titleBarView addSubview: closeButton]; - [self.titleBarView addSubview: minimizeButton]; - [self.titleBarView addSubview: zoomButton]; - } - } - - - (void)sendEvent:(NSEvent *)event { - if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeLeftMouseDragged) { - if (event.type == NSEventTypeLeftMouseDown) { - [self.webview mouseDown:event]; - } - - if (event.type == NSEventTypeLeftMouseDragged) { - [self.webview mouseDragged:event]; - return; - } - } - - [super sendEvent:event]; - } - @end - @implementation SSCWindowDelegate - - (void) userContentController: (WKUserContentController*) userContentController didReceiveScriptMessage: (WKScriptMessage*) scriptMessage { - } - @end #endif @implementation SSCBridgedWebView @@ -144,21 +54,21 @@ @implementation SSCBridgedWebView int lastX = 0; int lastY = 0; -- (void)viewDidChangeEffectiveAppearance { +- (void) viewDidChangeEffectiveAppearance { [super viewDidChangeEffectiveAppearance]; SSCWindow *window = (SSCWindow*) self.window; if (@available(macOS 10.14, *)) { if ([window.effectiveAppearance.name containsString:@"Dark"]) { - [window setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:1.0]]; // Dark mode color + [window setBackgroundColor: [NSColor colorWithCalibratedWhite:0.1 alpha:1.0]]; // Dark mode color } else { - [window setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]]; // Light mode color + [window setBackgroundColor: [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]]; // Light mode color } } } -- (void)resizeSubviewsWithOldSize:(NSSize)oldSize { +- (void) resizeSubviewsWithOldSize:(NSSize)oldSize { [super resizeSubviewsWithOldSize:oldSize]; SSCWindow *w = (SSCWindow*) self.window; @@ -182,7 +92,11 @@ - (void)resizeSubviewsWithOldSize:(NSSize)oldSize { w.titleBarView.frame = NSMakeRect(newX, newY, viewWidth, viewHeight); } -- (instancetype)initWithFrame:(NSRect)frameRect configuration:(WKWebViewConfiguration *)configuration radius:(CGFloat)radius margin:(CGFloat)margin { +- (instancetype) initWithFrame: (NSRect) frameRect + configuration: (WKWebViewConfiguration*) configuration + radius: (CGFloat) radius + margin: (CGFloat) margin +{ self = [super initWithFrame:frameRect configuration: configuration]; if (self && radius > 0.0) { @@ -195,9 +109,9 @@ - (instancetype)initWithFrame:(NSRect)frameRect configuration:(WKWebViewConfigur return self; } -- (void)layout { +- (void) layout { [super layout]; - + NSRect bounds = self.superview.bounds; if (self.radius > 0.0) { @@ -878,8 +792,11 @@ - (void) webView: (WKWebView*) webView }); }); - // Initialize WKWebView WKWebViewConfiguration* config = [WKWebViewConfiguration new]; + this->bridge->router.configureHandlers({ + .webview = config + }); + // https://webkit.org/blog/10882/app-bound-domains/ // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/3585117-limitsnavigationstoappbounddomai config.limitsNavigationsToAppBoundDomains = YES; @@ -1008,48 +925,6 @@ - (void) webView: (WKWebView*) webView completionHandler: ^(){} ]; - [config - setValue: @YES - forKey: @"allowUniversalAccessFromFileURLs" - ]; - - [config.preferences - setValue: @YES - forKey: @"allowFileAccessFromFileURLs" - ]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"npm"]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"ipc"]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"socket"]; - - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @"node"]; - - for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { - const auto scheme = replace(trim(entry), ":", ""); - if (app.core->protocolHandlers.registerHandler(scheme)) { - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @(replace(trim(scheme), ":", "").c_str())]; - } - } - - for (const auto& entry : opts.userConfig) { - const auto& key = entry.first; - if (key.starts_with("webview_protocol-handlers_")) { - const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; - const auto data = entry.second; - if (app.core->protocolHandlers.registerHandler(scheme, { data })) { - [config setURLSchemeHandler: bridge->router.schemeHandler - forURLScheme: @(scheme.c_str())]; - } - } - } - static const auto devHost = SSC::getDevHost(); if (devHost.starts_with("http:")) { [config.processPool @@ -1126,14 +1001,12 @@ - (void) webView: (WKWebView*) webView // windowDelegate = [SSCWindowDelegate new]; - navigationDelegate = [SSCNavigationDelegate new]; - navigationDelegate.bridge = this->bridge; [controller addScriptMessageHandler: windowDelegate name: @"external"]; // set delegates window.delegate = windowDelegate; webview.UIDelegate = webview; - webview.navigationDelegate = navigationDelegate; + webview.navigationDelegate = this->bridge->navigator.navigationDelegate; if (!isDelegateSet) { isDelegateSet = true; @@ -1438,10 +1311,6 @@ - (void) webView: (WKWebView*) webView objc_removeAssociatedObjects(this->windowDelegate); this->windowDelegate = nullptr; } - - if (this->navigationDelegate != nullptr) { - this->navigationDelegate = nullptr; - } } void Window::maximize () { diff --git a/src/window/manager.cc b/src/window/manager.cc new file mode 100644 index 0000000000..6f861239ee --- /dev/null +++ b/src/window/manager.cc @@ -0,0 +1,389 @@ +#include "window.hh" + +namespace SSC { + WindowManager::WindowManager (App &app) + : app(app), + inits(SSC_MAX_WINDOWS + SSC_MAX_WINDOWS_RESERVED), + windows(SSC_MAX_WINDOWS + SSC_MAX_WINDOWS_RESERVED) + { + if (isDebugEnabled()) { + this->lastDebugLogLine = std::chrono::system_clock::now(); + } + } + + WindowManager::~WindowManager () { + this->destroy(); + } + + void WindowManager::WindowManager::destroy () { + if (this->destroyed) { + return; + } + + for (auto window : windows) { + destroyWindow(window); + } + + this->destroyed = true; + this->windows.clear(); + this->inits.clear(); + } + + void WindowManager::WindowManager::configure (WindowManagerOptions configuration) { + if (this->destroyed) { + return; + } + + this->options.defaultHeight = configuration.defaultHeight; + this->options.defaultWidth = configuration.defaultWidth; + this->options.defaultMinWidth = configuration.defaultMinWidth; + this->options.defaultMinHeight = configuration.defaultMinHeight; + this->options.defaultMaxWidth = configuration.defaultMaxWidth; + this->options.defaultMaxHeight = configuration.defaultMaxHeight; + this->options.onMessage = configuration.onMessage; + this->options.userConfig = configuration.userConfig; + this->options.onExit = configuration.onExit; + this->options.aspectRatio = configuration.aspectRatio; + this->options.isTest = configuration.isTest; + this->options.argv = configuration.argv; + this->options.cwd = configuration.cwd; + this->options.userConfig = getUserConfig(); + } + + WindowManager::ManagedWindow* WindowManager::WindowManager::getWindow (int index, WindowStatus status) { + Lock lock(this->mutex); + if (this->destroyed) { + return nullptr; + } + + if ( + this->getWindowStatus(index) > WindowStatus::WINDOW_NONE && + this->getWindowStatus(index) < status + ) { + return this->windows[index]; + } + + return nullptr; + } + + WindowManager::ManagedWindow* WindowManager::WindowManager::getWindow (int index) { + return this->getWindow(index, WindowStatus::WINDOW_EXITING); + } + + WindowManager::ManagedWindow* WindowManager::WindowManager::getOrCreateWindow (int index) { + return this->getOrCreateWindow(index, WindowOptions {}); + } + + WindowManager::ManagedWindow* WindowManager::WindowManager::getOrCreateWindow ( + int index, + WindowOptions opts + ) { + if (this->destroyed || index < 0) { + return nullptr; + } + + if (this->getWindowStatus(index) == WindowStatus::WINDOW_NONE) { + opts.index = index; + return this->createWindow(opts); + } + + return this->getWindow(index); + } + + WindowManager::WindowStatus WindowManager::getWindowStatus (int index) { + Lock lock(this->mutex); + if (this->destroyed) { + return WindowStatus::WINDOW_NONE; + } + + if (index >= 0 && this->inits[index] && this->windows[index] != nullptr) { + return this->windows[index]->status; + } + + return WindowStatus::WINDOW_NONE; + } + + void WindowManager::destroyWindow (int index) { + Lock lock(this->mutex); + + if (!this->destroyed && index >= 0 && this->inits[index] && this->windows[index] != nullptr) { + return this->destroyWindow(windows[index]); + } + } + + void WindowManager::destroyWindow (ManagedWindow* window) { + if (!this->destroyed && window != nullptr) { + return this->destroyWindow(reinterpret_cast(window)); + } + } + + void WindowManager::destroyWindow (Window* window) { + Lock lock(this->mutex); + + if (!this->destroyed && window != nullptr && this->windows[window->index] != nullptr) { + auto metadata = reinterpret_cast(window); + this->inits[window->index] = false; + this->windows[window->index] = nullptr; + + if (metadata->status < WINDOW_CLOSING) { + window->close(0); + } + + if (metadata->status < WINDOW_KILLING) { + window->kill(); + } + + if (!window->opts.canExit) { + delete window; + } + } + } + + WindowManager::ManagedWindow* WindowManager::createWindow (WindowOptions opts) { + Lock lock(this->mutex); + + if (this->destroyed) { + return nullptr; + } + + StringStream env; + + if (this->inits[opts.index] && this->windows[opts.index] != nullptr) { + return this->windows[opts.index]; + } + + if (opts.userConfig.size() > 0) { + for (auto const &envKey : parseStringList(opts.userConfig["build_env"])) { + auto cleanKey = trim(envKey); + + if (!Env::has(cleanKey)) { + continue; + } + + auto envValue = Env::get(cleanKey.c_str()); + + env << String( + cleanKey + "=" + encodeURIComponent(envValue) + "&" + ); + } + } else { + for (auto const &envKey : parseStringList(this->options.userConfig["build_env"])) { + auto cleanKey = trim(envKey); + + if (!Env::has(cleanKey)) { + continue; + } + + auto envValue = Env::get(cleanKey); + + env << String( + cleanKey + "=" + encodeURIComponent(envValue) + "&" + ); + } + } + + auto screen = Window::getScreenSize(); + + float width = opts.width <= 0 + ? Window::getSizeInPixels(this->options.defaultWidth, screen.width) + : opts.width; + float height = opts.height <= 0 + ? Window::getSizeInPixels(this->options.defaultHeight, screen.height) + : opts.height; + float minWidth = opts.minWidth <= 0 + ? Window::getSizeInPixels(this->options.defaultMinWidth, screen.width) + : opts.minWidth; + float minHeight = opts.minHeight <= 0 + ? Window::getSizeInPixels(this->options.defaultMinHeight, screen.height) + : opts.minHeight; + float maxWidth = opts.maxWidth <= 0 + ? Window::getSizeInPixels(this->options.defaultMaxWidth, screen.width) + : opts.maxWidth; + float maxHeight = opts.maxHeight <= 0 + ? Window::getSizeInPixels(this->options.defaultMaxHeight, screen.height) + : opts.maxHeight; + + WindowOptions windowOptions = { + .resizable = opts.resizable, + .minimizable = opts.minimizable, + .maximizable = opts.maximizable, + .closable = opts.closable, + .frameless = opts.frameless, + .utility = opts.utility, + .canExit = opts.canExit, + .width = width, + .height = height, + .minWidth = minWidth, + .minHeight = minHeight, + .maxWidth = maxWidth, + .maxHeight = maxHeight, + .radius = opts.radius, + .margin = opts.margin, + .index = opts.index, + .debug = isDebugEnabled() || opts.debug, + .isTest = this->options.isTest, + .headless = opts.headless, + .aspectRatio = opts.aspectRatio, + .titlebarStyle = opts.titlebarStyle, + .windowControlOffsets = opts.windowControlOffsets, + .backgroundColorLight = opts.backgroundColorLight, + .backgroundColorDark = opts.backgroundColorDark, + .cwd = this->options.cwd, + .title = opts.title.size() > 0 ? opts.title : "", + .url = opts.url.size() > 0 ? opts.url : "data:text/html,", + .argv = this->options.argv, + .preload = opts.preload.size() > 0 ? opts.preload : "", + .env = env.str(), + .userConfig = this->options.userConfig, + .userScript = opts.userScript, + .runtimePrimordialOverrides = opts.runtimePrimordialOverrides, + .preloadCommonJS = opts.preloadCommonJS != false + }; + + for (const auto& tuple : opts.userConfig) { + windowOptions.userConfig[tuple.first] = tuple.second; + } + + if (isDebugEnabled()) { + this->log("Creating Window#" + std::to_string(opts.index)); + } + + auto window = new ManagedWindow(*this, app, windowOptions); + + window->status = WindowStatus::WINDOW_CREATED; + window->onExit = this->options.onExit; + window->onMessage = this->options.onMessage; + + this->windows[opts.index] = window; + this->inits[opts.index] = true; + + return window; + } + + WindowManager::ManagedWindow* WindowManager::createDefaultWindow (WindowOptions opts) { + return this->createWindow(WindowOptions { + .resizable = opts.resizable, + .minimizable = opts.minimizable, + .maximizable = opts.maximizable, + .closable = opts.closable, + .frameless = opts.frameless, + .utility = opts.utility, + .canExit = true, + .width = opts.width, + .height = opts.height, + .index = 0, + #ifdef PORT + .port = PORT, + #endif + .headless = opts.userConfig["build_headless"] == "true", + .titlebarStyle = opts.titlebarStyle, + .windowControlOffsets = opts.windowControlOffsets, + .backgroundColorLight = opts.backgroundColorLight, + .backgroundColorDark = opts.backgroundColorDark, + .userConfig = opts.userConfig + }); + } + + JSON::Array WindowManager::json (Vector indices) { + auto i = 0; + JSON::Array result; + for (auto index : indices) { + auto window = this->getWindow(index); + if (window != nullptr) { + result[i++] = window->json(); + } + } + return result; + } + + WindowManager::ManagedWindow::ManagedWindow ( + WindowManager &manager, + App &app, + WindowOptions options + ) : Window(app, options), + manager(manager) + {} + + WindowManager::ManagedWindow::~ManagedWindow () {} + + void WindowManager::ManagedWindow::show (const String &seq) { + auto index = std::to_string(this->opts.index); + manager.log("Showing Window#" + index + " (seq=" + seq + ")"); + status = WindowStatus::WINDOW_SHOWING; + Window::show(); + status = WindowStatus::WINDOW_SHOWN; + } + + void WindowManager::ManagedWindow::hide (const String &seq) { + if ( + status > WindowStatus::WINDOW_HIDDEN && + status < WindowStatus::WINDOW_EXITING + ) { + auto index = std::to_string(this->opts.index); + manager.log("Hiding Window#" + index + " (seq=" + seq + ")"); + status = WindowStatus::WINDOW_HIDING; + Window::hide(); + status = WindowStatus::WINDOW_HIDDEN; + } + } + + void WindowManager::ManagedWindow::close (int code) { + if (status < WindowStatus::WINDOW_CLOSING) { + auto index = std::to_string(this->opts.index); + manager.log("Closing Window#" + index + " (code=" + std::to_string(code) + ")"); + status = WindowStatus::WINDOW_CLOSING; + Window::close(code); + if (this->opts.canExit) { + status = WindowStatus::WINDOW_EXITED; + } else { + status = WindowStatus::WINDOW_CLOSED; + } + } + } + + void WindowManager::ManagedWindow::exit (int code) { + if (status < WindowStatus::WINDOW_EXITING) { + auto index = std::to_string(this->opts.index); + manager.log("Exiting Window#" + index + " (code=" + std::to_string(code) + ")"); + status = WindowStatus::WINDOW_EXITING; + Window::exit(code); + status = WindowStatus::WINDOW_EXITED; + } + } + + void WindowManager::ManagedWindow::kill () { + if (status < WindowStatus::WINDOW_KILLING) { + auto index = std::to_string(this->opts.index); + manager.log("Killing Window#" + index); + status = WindowStatus::WINDOW_KILLING; + Window::kill(); + status = WindowStatus::WINDOW_KILLED; + gc(); + } + } + + void WindowManager::ManagedWindow::gc () { + if (App::instance() != nullptr) { + manager.destroyWindow(reinterpret_cast(this)); + } + } + + JSON::Object WindowManager::ManagedWindow::json () { + auto index = this->opts.index; + auto size = this->getSize(); + uint64_t id = 0; + + if (this->bridge != nullptr) { + id = this->bridge->id; + } + + return JSON::Object::Entries { + {"id", std::to_string(id)}, + {"index", index}, + {"title", this->getTitle()}, + {"width", size.width}, + {"height", size.height}, + {"status", this->status} + }; + } +} diff --git a/src/window/win.cc b/src/window/win.cc index 3e1c12d116..37dda5d4fe 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1201,7 +1201,7 @@ namespace SSC { path = path.substr(0, path.size() - 2); } - auto parsedPath = IPC::Router::parseURL(path); + auto parsedPath = IPC::Router::parseURLComponents(path); auto rootPath = this->modulePath.parent_path(); auto resolved = IPC::Router::resolveURLPathForWebView(parsedPath.path, rootPath.string()); auto mount = IPC::Router::resolveNavigatorMountForWebView(parsedPath.path); diff --git a/src/window/window.hh b/src/window/window.hh index 6d0c80c613..733c52724d 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -33,6 +33,7 @@ namespace SSC { } #if defined(__APPLE__) +@class SSCBridgedWebView; #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR @interface SSCWindowDelegate : NSObject @property (nonatomic) SSC::Window* window; @@ -126,25 +127,6 @@ sourceOperationMaskForDraggingContext: (NSDraggingContext) context; initiatedByFrame: (WKFrameInfo*) frame completionHandler: (void (^)(BOOL result)) completionHandler; @end - -@interface SSCNavigationDelegate : NSObject -@property (nonatomic) SSC::IPC::Bridge* bridge; -- (void) webView: (WKWebView*) webView - didFailNavigation: (WKNavigation*) navigation - withError: (NSError*) error; - -- (void) webView: (WKWebView*) webView - didFailProvisionalNavigation: (WKNavigation*) navigation - withError: (NSError*) error; - -- (void) webView: (WKWebView*) webview - decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction - decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler; - -- (void) webView: (WKWebView*) webView - decidePolicyForNavigationResponse: (WKNavigationResponse*) navigationResponse - decisionHandler: (void (^)(WKNavigationResponsePolicy)) decisionHandler; -@end #endif namespace SSC { @@ -189,7 +171,6 @@ namespace SSC { #endif SSCBridgedWebView* webview; SSCWindowDelegate* windowDelegate = nullptr; - SSCNavigationDelegate *navigationDelegate = nullptr; #elif defined(__linux__) && !defined(__ANDROID__) GtkSelectionData *selectionData = nullptr; GtkAccelGroup *accelGroup = nullptr; @@ -348,90 +329,17 @@ namespace SSC { WindowManager &manager, App &app, WindowOptions opts - ) : Window(app, opts) , manager(manager) { } - - ~ManagedWindow () {} + ); - void show (const String &seq) { - auto index = std::to_string(this->opts.index); - manager.log("Showing Window#" + index + " (seq=" + seq + ")"); - status = WindowStatus::WINDOW_SHOWING; - Window::show(); - status = WindowStatus::WINDOW_SHOWN; - } - - void hide (const String &seq) { - if ( - status > WindowStatus::WINDOW_HIDDEN && - status < WindowStatus::WINDOW_EXITING - ) { - auto index = std::to_string(this->opts.index); - manager.log("Hiding Window#" + index + " (seq=" + seq + ")"); - status = WindowStatus::WINDOW_HIDING; - Window::hide(); - status = WindowStatus::WINDOW_HIDDEN; - } - } - - void close (int code) { - if (status < WindowStatus::WINDOW_CLOSING) { - auto index = std::to_string(this->opts.index); - manager.log("Closing Window#" + index + " (code=" + std::to_string(code) + ")"); - status = WindowStatus::WINDOW_CLOSING; - Window::close(code); - if (this->opts.canExit) { - status = WindowStatus::WINDOW_EXITED; - } else { - status = WindowStatus::WINDOW_CLOSED; - } - } - } + ~ManagedWindow (); - void exit (int code) { - if (status < WindowStatus::WINDOW_EXITING) { - auto index = std::to_string(this->opts.index); - manager.log("Exiting Window#" + index + " (code=" + std::to_string(code) + ")"); - status = WindowStatus::WINDOW_EXITING; - Window::exit(code); - status = WindowStatus::WINDOW_EXITED; - } - } - - void kill () { - if (status < WindowStatus::WINDOW_KILLING) { - auto index = std::to_string(this->opts.index); - manager.log("Killing Window#" + index); - status = WindowStatus::WINDOW_KILLING; - Window::kill(); - status = WindowStatus::WINDOW_KILLED; - gc(); - } - } - - void gc () { - if (App::instance() != nullptr) { - manager.destroyWindow(reinterpret_cast(this)); - } - } - - JSON::Object json () { - auto index = this->opts.index; - auto size = this->getSize(); - uint64_t id = 0; - - if (this->bridge != nullptr) { - id = this->bridge->id; - } - - return JSON::Object::Entries { - { "id", std::to_string(id) }, - { "index", index }, - { "title", this->getTitle() }, - { "width", size.width }, - { "height", size.height }, - { "status", this->status } - }; - } + void show (const String &seq); + void hide (const String &seq); + void close (int code); + void exit (int code); + void kill (); + void gc (); + JSON::Object json (); }; std::chrono::system_clock::time_point lastDebugLogLine; @@ -443,49 +351,8 @@ namespace SSC { Mutex mutex; WindowManagerOptions options; - WindowManager (App &app) : - app(app), - inits(SSC_MAX_WINDOWS + SSC_MAX_WINDOWS_RESERVED), - windows(SSC_MAX_WINDOWS + SSC_MAX_WINDOWS_RESERVED) - { - if (isDebugEnabled()) { - lastDebugLogLine = std::chrono::system_clock::now(); - } - } - - ~WindowManager () { - destroy(); - } - - void destroy () { - if (this->destroyed) return; - for (auto window : windows) { - destroyWindow(window); - } - - this->destroyed = true; - - windows.clear(); - inits.clear(); - } - - void configure (WindowManagerOptions configuration) { - if (destroyed) return; - this->options.defaultHeight = configuration.defaultHeight; - this->options.defaultWidth = configuration.defaultWidth; - this->options.defaultMinWidth = configuration.defaultMinWidth; - this->options.defaultMinHeight = configuration.defaultMinHeight; - this->options.defaultMaxWidth = configuration.defaultMaxWidth; - this->options.defaultMaxHeight = configuration.defaultMaxHeight; - this->options.onMessage = configuration.onMessage; - this->options.userConfig = configuration.userConfig; - this->options.onExit = configuration.onExit; - this->options.aspectRatio = configuration.aspectRatio; - this->options.isTest = configuration.isTest; - this->options.argv = configuration.argv; - this->options.cwd = configuration.cwd; - this->options.userConfig = getUserConfig(); - } + WindowManager (App &app); + ~WindowManager (); void inline log (const String line) { if (destroyed || !isDebugEnabled()) return; @@ -501,238 +368,23 @@ namespace SSC { lastDebugLogLine = now; } - ManagedWindow* getWindow (int index, WindowStatus status) { - Lock lock(this->mutex); - if (this->destroyed) return nullptr; - if ( - getWindowStatus(index) > WindowStatus::WINDOW_NONE && - getWindowStatus(index) < status - ) { - return windows[index]; - } - - return nullptr; - } - - ManagedWindow* getWindow (int index) { - return getWindow(index, WindowStatus::WINDOW_EXITING); - } - - ManagedWindow* getOrCreateWindow (int index) { - return getOrCreateWindow(index, WindowOptions {}); - } - - ManagedWindow* getOrCreateWindow (int index, WindowOptions opts) { - if (this->destroyed) return nullptr; - if (index < 0) return nullptr; - if (getWindowStatus(index) == WindowStatus::WINDOW_NONE) { - opts.index = index; - return createWindow(opts); - } - - return getWindow(index); - } - - WindowStatus getWindowStatus (int index) { - Lock lock(this->mutex); - if (this->destroyed) return WindowStatus::WINDOW_NONE; - if (index >= 0 && inits[index] && windows[index] != nullptr) { - return windows[index]->status; - } - - return WindowStatus::WINDOW_NONE; - } - - void destroyWindow (int index) { - Lock lock(this->mutex); - if (destroyed) return; - if (index >= 0 && inits[index] && windows[index] != nullptr) { - return destroyWindow(windows[index]); - } - } - - void destroyWindow (ManagedWindow* window) { - if (destroyed) return; - if (window != nullptr) { - return destroyWindow(reinterpret_cast(window)); - } - } - - void destroyWindow (Window* window) { - Lock lock(this->mutex); - if (destroyed) return; - - if (window != nullptr && windows[window->index] != nullptr) { - auto metadata = reinterpret_cast(window); - inits[window->index] = false; - windows[window->index] = nullptr; - - if (metadata->status < WINDOW_CLOSING) { - window->close(0); - } - - if (metadata->status < WINDOW_KILLING) { - window->kill(); - } - - if (!window->opts.canExit) { - delete window; - } - } - } - - ManagedWindow* createWindow (WindowOptions opts) { - Lock lock(this->mutex); - if (destroyed) return nullptr; - StringStream env; - - if (inits[opts.index] && windows[opts.index] != nullptr) { - return windows[opts.index]; - } - - if (opts.userConfig.size() > 0) { - for (auto const &envKey : parseStringList(opts.userConfig["build_env"])) { - auto cleanKey = trim(envKey); + void destroy (); + void configure (WindowManagerOptions configuration); - if (!Env::has(cleanKey)) { - continue; - } + ManagedWindow* getWindow (int index, WindowStatus status); + ManagedWindow* getWindow (int index); + ManagedWindow* getOrCreateWindow (int index); + ManagedWindow* getOrCreateWindow (int index, WindowOptions opts); + WindowStatus getWindowStatus (int index); - auto envValue = Env::get(cleanKey.c_str()); + void destroyWindow (int index); + void destroyWindow (ManagedWindow* window); + void destroyWindow (Window* window); - env << String( - cleanKey + "=" + encodeURIComponent(envValue) + "&" - ); - } - } else { - for (auto const &envKey : parseStringList(this->options.userConfig["build_env"])) { - auto cleanKey = trim(envKey); - - if (!Env::has(cleanKey)) { - continue; - } - - auto envValue = Env::get(cleanKey); - - env << String( - cleanKey + "=" + encodeURIComponent(envValue) + "&" - ); - } - } - - auto screen = Window::getScreenSize(); - - float width = opts.width <= 0 - ? Window::getSizeInPixels(this->options.defaultWidth, screen.width) - : opts.width; - float height = opts.height <= 0 - ? Window::getSizeInPixels(this->options.defaultHeight, screen.height) - : opts.height; - float minWidth = opts.minWidth <= 0 - ? Window::getSizeInPixels(this->options.defaultMinWidth, screen.width) - : opts.minWidth; - float minHeight = opts.minHeight <= 0 - ? Window::getSizeInPixels(this->options.defaultMinHeight, screen.height) - : opts.minHeight; - float maxWidth = opts.maxWidth <= 0 - ? Window::getSizeInPixels(this->options.defaultMaxWidth, screen.width) - : opts.maxWidth; - float maxHeight = opts.maxHeight <= 0 - ? Window::getSizeInPixels(this->options.defaultMaxHeight, screen.height) - : opts.maxHeight; - - WindowOptions windowOptions = { - .resizable = opts.resizable, - .minimizable = opts.minimizable, - .maximizable = opts.maximizable, - .closable = opts.closable, - .frameless = opts.frameless, - .utility = opts.utility, - .canExit = opts.canExit, - .width = width, - .height = height, - .minWidth = minWidth, - .minHeight = minHeight, - .maxWidth = maxWidth, - .maxHeight = maxHeight, - .radius = opts.radius, - .margin = opts.margin, - .index = opts.index, - .debug = isDebugEnabled() || opts.debug, - .isTest = this->options.isTest, - .headless = opts.headless, - .aspectRatio = opts.aspectRatio, - .titlebarStyle = opts.titlebarStyle, - .windowControlOffsets = opts.windowControlOffsets, - .backgroundColorLight = opts.backgroundColorLight, - .backgroundColorDark = opts.backgroundColorDark, - .cwd = this->options.cwd, - .title = opts.title.size() > 0 ? opts.title : "", - .url = opts.url.size() > 0 ? opts.url : "data:text/html,", - .argv = this->options.argv, - .preload = opts.preload.size() > 0 ? opts.preload : "", - .env = env.str(), - .userConfig = this->options.userConfig, - .userScript = opts.userScript, - .runtimePrimordialOverrides = opts.runtimePrimordialOverrides, - .preloadCommonJS = opts.preloadCommonJS != false - }; - - for (const auto& tuple : opts.userConfig) { - windowOptions.userConfig[tuple.first] = tuple.second; - } - - if (isDebugEnabled()) { - this->log("Creating Window#" + std::to_string(opts.index)); - } - - auto window = new ManagedWindow(*this, app, windowOptions); + ManagedWindow* createWindow (WindowOptions opts); + ManagedWindow* createDefaultWindow (WindowOptions opts); - window->status = WindowStatus::WINDOW_CREATED; - window->onExit = this->options.onExit; - window->onMessage = this->options.onMessage; - - windows[opts.index] = window; - inits[opts.index] = true; - - return window; - } - - ManagedWindow* createDefaultWindow (WindowOptions opts) { - return createWindow(WindowOptions { - .resizable = opts.resizable, - .minimizable = opts.minimizable, - .maximizable = opts.maximizable, - .closable = opts.closable, - .frameless = opts.frameless, - .utility = opts.utility, - .canExit = true, - .width = opts.width, - .height = opts.height, - .index = 0, - #ifdef PORT - .port = PORT, - #endif - .headless = opts.userConfig["build_headless"] == "true", - .titlebarStyle = opts.titlebarStyle, - .windowControlOffsets = opts.windowControlOffsets, - .backgroundColorLight = opts.backgroundColorLight, - .backgroundColorDark = opts.backgroundColorDark, - .userConfig = opts.userConfig - }); - } - - JSON::Array json (std::vector indices) { - auto i = 0; - JSON::Array result; - for (auto index : indices) { - auto window = getWindow(index); - if (window != nullptr) { - result[i++] = window->json(); - } - } - return result; - } + JSON::Array json (Vector indices); }; class Dialog { @@ -749,7 +401,6 @@ namespace SSC { String title; }; - #if defined(__APPLE__) && TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR SSCUIPickerDelegate* uiPickerDelegate = nullptr; Vector delegatedResults; From 7a13355d04a87a2e25887a1a4a8aa972c56281ee Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:06:30 -0400 Subject: [PATCH 0636/1178] feat(core/url): introduce URL parser/struct --- src/core/url.cc | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ src/core/url.hh | 44 +++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 src/core/url.cc create mode 100644 src/core/url.hh diff --git a/src/core/url.cc b/src/core/url.cc new file mode 100644 index 0000000000..55e0bae886 --- /dev/null +++ b/src/core/url.cc @@ -0,0 +1,168 @@ +#include "codec.hh" +#include "string.hh" +#include "url.hh" + +namespace SSC { + const URL::Components URL::Components::parse (const String& url) { + URL::Components components; + components.originalURL = url; + auto input = url; + + if (input.starts_with("./")) { + input = input.substr(1); + } + + if (!input.starts_with("/")) { + const auto colon = input.find(':'); + + if (colon != String::npos) { + components.scheme = input.substr(0, colon); + input = input.substr(colon + 1, input.size()); + + if (input.starts_with("//")) { + input = input.substr(2, input.size()); + + const auto slash = input.find("/"); + if (slash != String::npos) { + components.authority = input.substr(0, slash); + input = input.substr(slash, input.size()); + } else { + const auto questionMark = input.find("?"); + const auto fragment = input.find("#"); + if (questionMark != String::npos & fragment != String::npos) { + if (questionMark < fragment) { + components.authority = input.substr(0, questionMark); + input = input.substr(questionMark, input.size()); + } else { + components.authority = input.substr(0, fragment); + input = input.substr(fragment, input.size()); + } + } else if (questionMark != String::npos) { + components.authority = input.substr(0, questionMark); + input = input.substr(questionMark, input.size()); + } else if (fragment != String::npos) { + components.authority = input.substr(0, fragment); + input = input.substr(fragment, input.size()); + } + } + } + } + } + + input = decodeURIComponent(input); + + const auto questionMark = input.find("?"); + const auto fragment = input.find("#"); + + if (questionMark != String::npos && fragment != String::npos) { + if (questionMark < fragment) { + components.pathname = input.substr(0, questionMark); + components.query = input.substr(questionMark + 1, fragment - questionMark - 1); + components.fragment = input.substr(fragment + 1, input.size()); + } else { + components.pathname = input.substr(0, fragment); + components.fragment = input.substr(fragment + 1, input.size()); + } + } else if (questionMark != String::npos) { + components.pathname = input.substr(0, questionMark); + components.query = input.substr(questionMark + 1, input.size()); + } else if (fragment != String::npos) { + components.pathname = input.substr(0, fragment); + components.fragment = input.substr(fragment + 1, input.size()); + } else { + components.pathname = input; + } + + if (!components.pathname.starts_with("/")) { + components.pathname = "/" + components.pathname; + } + + return components; + } + + URL::URL (const String& href) { + const auto components = URL::Components::parse(href); + + this->scheme = components.scheme; + this->pathname = components.pathname; + this->query = components.query; + this->fragment = components.fragment; + this->search = this->query.size() > 0 ? "?" + this->query : ""; + this->hash = this->fragment.size() > 0 ? "#" + this->fragment : ""; + + if (components.scheme.size() > 0) { + this->protocol = components.scheme + ":"; + } + + const auto authorityParts = split(components.authority, '@'); + if (authorityParts.size() == 2) { + const auto userParts = split(authorityParts[0], ':'); + + if (userParts.size() == 2) { + this->username = userParts[0]; + this->password = userParts[1]; + } else if (userParts.size() == 1) { + this->username = userParts[0]; + } + + const auto hostParts = split(authorityParts[1], ':'); + if (hostParts.size() > 1) { + this->port = hostParts[1]; + } + + if (hostParts.size() > 0) { + this->hostname = hostParts[0]; + } + } else if (authorityParts.size() == 1) { + const auto hostParts = split(authorityParts[1], ':'); + if (hostParts.size() > 1) { + this->port = hostParts[1]; + } + + if (hostParts.size() > 0) { + this->hostname = hostParts[0]; + } + } + + if (this->protocol.size() > 0) { + if (this->hostname.size() > 0) { + this->origin = this->protocol + "//" + this->hostname; + } else { + this->origin = this->protocol + this->pathname; + } + + this->href = this->origin + this->pathname + this->search + this->hash; + } + + for (const auto& entry : split(this->query, '&')) { + const auto parts = split(entry, '='); + if (parts.size() == 2) { + const auto key = decodeURIComponent(trim(parts[0])); + const auto value = decodeURIComponent(trim(parts[1])); + this->searchParams.insert_or_assign(key, value); + } + } + } + + URL::URL (const JSON::Object& json) + : URL(json["href"].str()) + {} + + const String URL::str () const { + return this->href; + } + + const JSON::Object URL::json () const { + return JSON::Object::Entries { + {"href", this->href}, + {"origin", this->origin}, + {"protocol", this->protocol}, + {"username", this->username}, + {"password", this->password}, + {"hostname", this->hostname}, + {"pathname", this->pathname}, + {"search", this->search}, + {"hash", this->hash} + }; + } +} diff --git a/src/core/url.hh b/src/core/url.hh new file mode 100644 index 0000000000..38b7e58ced --- /dev/null +++ b/src/core/url.hh @@ -0,0 +1,44 @@ +#ifndef SSC_CORE_URL_H +#define SSC_CORE_URL_H + +#include "types.hh" +#include "json.hh" + +namespace SSC { + struct URL { + struct Components { + String originalURL = ""; + String scheme = ""; + String authority = ""; + String pathname = ""; + String query = ""; + String fragment = ""; + + static const Components parse (const String& url); + }; + + // core properties + String href = ""; + String origin = ""; + String protocol = ""; + String username = ""; + String password = ""; + String hostname = ""; + String port = ""; + String pathname = ""; + String search = ""; // includes '?' and 'query' if 'query' is not empty + String hash = ""; // include '#' and 'fragment' if 'fragment' is not empty + + // extra properties + String scheme; + String fragment; + String query; + Map searchParams; + + URL (const String& href); + URL (const JSON::Object& json); + const String str () const; + const JSON::Object json () const; + }; +} +#endif From 373740fc31a811f0e3e819065ae07131e0ccc0e0 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:07:35 -0400 Subject: [PATCH 0637/1178] refactor(window): use shared pointer for managed windows owned by window manager --- src/window/apple.mm | 99 +++++++---------- src/window/linux.cc | 240 +++--------------------------------------- src/window/manager.cc | 213 +++++++++++++++++-------------------- src/window/win.cc | 201 +++++++++++------------------------ src/window/window.hh | 57 +++++----- 5 files changed, 242 insertions(+), 568 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index fd3b9d7185..ea08e75107 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -743,6 +743,7 @@ - (void) webView: (WKWebView*) webView window.titlebarAppearsTransparent = true; auto userConfig = opts.userConfig; + WKWebViewConfiguration* config = [WKWebViewConfiguration new]; this->index = opts.index; this->bridge = new IPC::Bridge(app.core, userConfig); @@ -758,45 +759,6 @@ - (void) webView: (WKWebView*) webView }); }; - this->bridge->router.map("window.eval", [=, this](auto message, auto router, auto reply) { - auto value = message.value; - auto seq = message.seq; - auto script = [NSString stringWithUTF8String: value.c_str()]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [webview evaluateJavaScript: script completionHandler: ^(id result, NSError *error) { - if (result) { - auto msg = String([[NSString stringWithFormat:@"%@", result] UTF8String]); - this->bridge->router.send(seq, msg, Post{}); - } else if (error) { - auto exception = (NSString *) error.userInfo[@"WKJavaScriptExceptionMessage"]; - auto message = [[NSString stringWithFormat:@"%@", exception] UTF8String]; - auto err = encodeURIComponent(String(message)); - - if (err == "(null)") { - this->bridge->router.send(seq, "null", Post{}); - return; - } - - auto json = JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", String("Error: ") + err} - }} - }; - - this->bridge->router.send(seq, JSON::Object(json).str(), Post{}); - } else { - this->bridge->router.send(seq, "undefined", Post{}); - } - }]; - }); - }); - - WKWebViewConfiguration* config = [WKWebViewConfiguration new]; - this->bridge->router.configureHandlers({ - .webview = config - }); - // https://webkit.org/blog/10882/app-bound-domains/ // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/3585117-limitsnavigationstoappbounddomai config.limitsNavigationsToAppBoundDomains = YES; @@ -925,14 +887,6 @@ - (void) webView: (WKWebView*) webView completionHandler: ^(){} ]; - static const auto devHost = SSC::getDevHost(); - if (devHost.starts_with("http:")) { - [config.processPool - performSelector: @selector(_registerURLSchemeAsSecure:) - withObject: @"http" - ]; - } - @try { [prefs setValue: @YES forKey: @"offlineApplicationCacheIsEnabled"]; } @catch (NSException *error) { @@ -949,6 +903,10 @@ - (void) webView: (WKWebView*) webView .userScript = opts.userScript }); + this->bridge->configureHandlers({ + .webview = config + }); + webview = [SSCBridgedWebView.alloc initWithFrame: NSZeroRect configuration: config @@ -1264,7 +1222,18 @@ - (void) webView: (WKWebView*) webView Window::~Window () { this->close(0); + debug("DELETE BRIDGE"); delete this->bridge; + + if (this->webview) { + #if !__has_feature(objc_arc) + [this->webview.configuration.processPool release]; + [this->webview release]; + #endif + } + + this->bridge = nullptr; + this->webview = nullptr; } ScreenSize Window::getScreenSize () { @@ -1296,17 +1265,16 @@ - (void) webView: (WKWebView*) webView } void Window::close (int code) { + this->webview.navigationDelegate = nullptr; + this->webview.UIDelegate = nullptr; if (this->window != nullptr) { [this->window performClose: nil]; auto app = App::instance(); app->windowManager->destroyWindow(this->index); + this->window.contentView = nullptr; this->window = nullptr; } - if (this->webview) { - this->webview = nullptr; - } - if (this->windowDelegate != nullptr) { objc_removeAssociatedObjects(this->windowDelegate); this->windowDelegate = nullptr; @@ -1385,9 +1353,9 @@ - (void) webView: (WKWebView*) webView } } - SSC::String Window::getTitle () { - if (this->window) { - return SSC::String([this->window.title UTF8String]); + const String Window::getTitle () const { + if (this->window && this->window.title.UTF8String != nullptr) { + return this->window.title.UTF8String; } return ""; @@ -1405,14 +1373,27 @@ - (void) webView: (WKWebView*) webView return ScreenSize {0, 0}; } - NSRect e = this->window.frame; + const auto frame = this->window.frame; - this->height = e.size.height; - this->width = e.size.width; + this->height = frame.size.height; + this->width = frame.size.width; return ScreenSize { - .height = (int) e.size.height, - .width = (int) e.size.width + .height = (int) frame.size.height, + .width = (int) frame.size.width + }; + } + + const ScreenSize Window::getSize () const { + if (this->window == nullptr) { + return ScreenSize {0, 0}; + } + + const auto frame = this->window.frame; + + return ScreenSize { + .height = (int) frame.size.height, + .width = (int) frame.size.width }; } diff --git a/src/window/linux.cc b/src/window/linux.cc index 58369136c5..6c27f0c381 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -70,6 +70,7 @@ namespace SSC { this->index = this->opts.index; this->bridge = new IPC::Bridge(app.core, opts.userConfig); + this->bridge->router.configureHandlers({}); this->hotkey.init(this->bridge); @@ -81,97 +82,6 @@ namespace SSC { this->eval(js); }; - this->bridge->router.map("window.eval", [=, &app](auto message, auto router, auto reply) { - WindowManager* windowManager = app.getWindowManager(); - if (windowManager == nullptr) { - // @TODO(jwerle): print warning - return; - } - - auto window = windowManager->getWindow(message.index); - static auto userConfig = SSC::getUserConfig(); - - if (userConfig["application_agent"] == "true") { - gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window), TRUE); - } - - if (window == nullptr) { - return reply(IPC::Result::Err { message, JSON::Object::Entries { - {"message", "Invalid window index given"} - }}); - } - - auto value = message.get("value"); - auto ctx = new WebViewJavaScriptAsyncContext { reply, message, window }; - - webkit_web_view_evaluate_javascript( - WEBKIT_WEB_VIEW(window->webview), - value.c_str(), - -1, - nullptr, - nullptr, - nullptr, - [](GObject *object, GAsyncResult *res, gpointer data) { - GError *error = nullptr; - auto ctx = reinterpret_cast(data); - auto value = webkit_web_view_evaluate_javascript_finish( - WEBKIT_WEB_VIEW(ctx->window->webview), - res, - &error - ); - - if (!value) { - ctx->reply(IPC::Result::Err { ctx->message, JSON::Object::Entries { - {"code", error->code}, - {"message", String(error->message)} - }}); - - g_error_free(error); - return; - } else { - if ( - jsc_value_is_null(value) || - jsc_value_is_array(value) || - jsc_value_is_object(value) || - jsc_value_is_number(value) || - jsc_value_is_string(value) || - jsc_value_is_function(value) || - jsc_value_is_undefined(value) || - jsc_value_is_constructor(value) - ) { - auto context = jsc_value_get_context(value); - auto string = jsc_value_to_string(value); - auto exception = jsc_context_get_exception(context); - auto json = JSON::Any {}; - - if (exception) { - auto message = jsc_exception_get_message(exception); - - if (message == nullptr) { - message = "An unknown error occured"; - } - - ctx->reply(IPC::Result::Err { ctx->message, JSON::Object::Entries { - {"message", String(message)} - }}); - } else if (string) { - ctx->reply(IPC::Result { ctx->message.seq, ctx->message, String(string) }); - } - - if (string) { - g_free(string); - } - } else { - ctx->reply(IPC::Result::Err { ctx->message, JSON::Object::Entries { - {"message", "Error: An unknown JavaScript evaluation error has occurred"} - }}); - } - } - }, - ctx - ); - }); - if (opts.resizable) { gtk_window_set_default_size(GTK_WINDOW(window), opts.width, opts.height); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); @@ -225,72 +135,13 @@ namespace SSC { NULL ))); + this->bridge->navigator.configureWebView(webview); + this->bridge->core->notifications.configureWebView(webview); + gtk_widget_set_can_focus(GTK_WIDGET(webview), true); webkit_cookie_manager_set_accept_policy(cookieManager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); - g_signal_connect( - G_OBJECT(webContext), - "initialize-notification-permissions", - G_CALLBACK(+[]( - WebKitWebContext* webContext, - gpointer userData - ) { - static auto userConfig = SSC::getUserConfig(); - static const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - - auto uri = "socket://" + bundleIdentifier; - auto origin = webkit_security_origin_new_for_uri(uri.c_str()); - GList* allowed = nullptr; - GList* disallowed = nullptr; - - webkit_security_origin_ref(origin); - - if (origin && allowed && disallowed) { - if (userConfig["permissions_allow_notifications"] == "false") { - disallowed = g_list_append(disallowed, (gpointer) origin); - } else { - allowed = g_list_append(allowed, (gpointer) origin); - } - - if (allowed && disallowed) { - webkit_web_context_initialize_notification_permissions( - webContext, - allowed, - disallowed - ); - } - } - - if (allowed) { - g_list_free(allowed); - } - - if (disallowed) { - g_list_free(disallowed); - } - - if (origin) { - webkit_security_origin_unref(origin); - } - }), - this - ); - - g_signal_connect( - G_OBJECT(webview), - "show-notification", - G_CALLBACK(+[]( - WebKitWebView* webview, - WebKitNotification* notification, - gpointer userData - ) -> bool { - static auto userConfig = SSC::getUserConfig(); - return userConfig["permissions_allow_notifications"] == "false"; - }), - this - ); - // handle `navigator.permissions.query()` g_signal_connect( G_OBJECT(webview), @@ -414,73 +265,6 @@ namespace SSC { this ); - g_signal_connect( - G_OBJECT(webview), - "decide-policy", - G_CALLBACK((+[]( - WebKitWebView* webview, - WebKitPolicyDecision* decision, - WebKitPolicyDecisionType decisionType, - gpointer userData - ) { - static const auto devHost = SSC::getDevHost(); - auto window = static_cast(userData); - - if (decisionType != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { - webkit_policy_decision_use(decision); - return true; - } - - const auto nav = WEBKIT_NAVIGATION_POLICY_DECISION (decision); - const auto action = webkit_navigation_policy_decision_get_navigation_action(nav); - const auto req = webkit_navigation_action_get_request(action); - const auto uri = String(webkit_uri_request_get_uri(req)); - const auto applicationProtocol = window->opts.userConfig["meta_application_protocol"]; - - if ( - applicationProtocol.size() > 0 && - uri.starts_with(window->opts.userConfig["meta_application_protocol"]) - ) { - webkit_policy_decision_ignore(decision); - - if (window != nullptr && window->bridge != nullptr) { - SSC::JSON::Object json = SSC::JSON::Object::Entries { - {"url", uri } - }; - - window->bridge->router.emit("applicationurl", json.str()); - } - - return false; - } - - if (!window->bridge->router.isNavigationAllowed(uri)) { - debug("Navigation was ignored for: %s", uri.c_str()); - webkit_policy_decision_ignore(decision); - return false; - } - - return true; - })), - this - ); - - g_signal_connect( - G_OBJECT(webview), - "load-changed", - G_CALLBACK(+[](WebKitWebView* wv, WebKitLoadEvent event, gpointer arg) { - auto *window = static_cast(arg); - if (event == WEBKIT_LOAD_STARTED) { - window->app.isReady = false; - } - - if (event == WEBKIT_LOAD_FINISHED) { - window->app.isReady = true; - } - }), - this - ); - // Calling gtk_drag_source_set interferes with text selection /* gtk_drag_source_set( webview, @@ -1198,9 +982,15 @@ namespace SSC { } } - SSC::String Window::getTitle () { - auto title = gtk_window_get_title(GTK_WINDOW(window)); - return String(title != nullptr ? title : ""); + const String Window::getTitle () const { + if (this->window != nullptr) { + const auto title = gtk_window_get_title(GTK_WINDOW(this->window)); + if (title != nullptr) { + return title; + } + } + + return ""; } void Window::setTitle (const String &s) { @@ -1266,6 +1056,10 @@ namespace SSC { return ScreenSize { this->height, this->width }; } + const ScreenSize Window::getSize () const { + return ScreenSize { this->height, this->width }; + } + void Window::setSize (int width, int height, int hints) { gtk_widget_realize(window); gtk_window_set_resizable(GTK_WINDOW(window), hints != WINDOW_HINT_FIXED); diff --git a/src/window/manager.cc b/src/window/manager.cc index 6f861239ee..967ff7cf0b 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -3,7 +3,6 @@ namespace SSC { WindowManager::WindowManager (App &app) : app(app), - inits(SSC_MAX_WINDOWS + SSC_MAX_WINDOWS_RESERVED), windows(SSC_MAX_WINDOWS + SSC_MAX_WINDOWS_RESERVED) { if (isDebugEnabled()) { @@ -20,16 +19,11 @@ namespace SSC { return; } - for (auto window : windows) { - destroyWindow(window); - } - - this->destroyed = true; this->windows.clear(); - this->inits.clear(); + this->destroyed = true; } - void WindowManager::WindowManager::configure (WindowManagerOptions configuration) { + void WindowManager::WindowManager::configure (const WindowManagerOptions& configuration) { if (this->destroyed) { return; } @@ -50,7 +44,10 @@ namespace SSC { this->options.userConfig = getUserConfig(); } - WindowManager::ManagedWindow* WindowManager::WindowManager::getWindow (int index, WindowStatus status) { + SharedPointer WindowManager::WindowManager::getWindow ( + int index, + WindowStatus status + ) { Lock lock(this->mutex); if (this->destroyed) { return nullptr; @@ -66,23 +63,24 @@ namespace SSC { return nullptr; } - WindowManager::ManagedWindow* WindowManager::WindowManager::getWindow (int index) { + SharedPointer WindowManager::WindowManager::getWindow (int index) { return this->getWindow(index, WindowStatus::WINDOW_EXITING); } - WindowManager::ManagedWindow* WindowManager::WindowManager::getOrCreateWindow (int index) { + SharedPointer WindowManager::WindowManager::getOrCreateWindow (int index) { return this->getOrCreateWindow(index, WindowOptions {}); } - WindowManager::ManagedWindow* WindowManager::WindowManager::getOrCreateWindow ( + SharedPointer WindowManager::WindowManager::getOrCreateWindow ( int index, - WindowOptions opts + const WindowOptions& options ) { if (this->destroyed || index < 0) { return nullptr; } if (this->getWindowStatus(index) == WindowStatus::WINDOW_NONE) { + WindowOptions opts = options; opts.index = index; return this->createWindow(opts); } @@ -92,12 +90,14 @@ namespace SSC { WindowManager::WindowStatus WindowManager::getWindowStatus (int index) { Lock lock(this->mutex); + if (this->destroyed) { return WindowStatus::WINDOW_NONE; } - if (index >= 0 && this->inits[index] && this->windows[index] != nullptr) { - return this->windows[index]->status; + const auto window = this->windows[index]; + if (window != nullptr) { + return window->status; } return WindowStatus::WINDOW_NONE; @@ -106,65 +106,50 @@ namespace SSC { void WindowManager::destroyWindow (int index) { Lock lock(this->mutex); - if (!this->destroyed && index >= 0 && this->inits[index] && this->windows[index] != nullptr) { - return this->destroyWindow(windows[index]); - } - } - - void WindowManager::destroyWindow (ManagedWindow* window) { - if (!this->destroyed && window != nullptr) { - return this->destroyWindow(reinterpret_cast(window)); - } - } - - void WindowManager::destroyWindow (Window* window) { - Lock lock(this->mutex); - - if (!this->destroyed && window != nullptr && this->windows[window->index] != nullptr) { - auto metadata = reinterpret_cast(window); - this->inits[window->index] = false; - this->windows[window->index] = nullptr; - - if (metadata->status < WINDOW_CLOSING) { + auto window = this->windows[index]; + if (window != nullptr) { + if (window->status < WINDOW_CLOSING) { window->close(0); } - if (metadata->status < WINDOW_KILLING) { + if (window->status < WINDOW_KILLING) { window->kill(); } if (!window->opts.canExit) { - delete window; + this->windows[index] = nullptr; } } } - WindowManager::ManagedWindow* WindowManager::createWindow (WindowOptions opts) { + SharedPointer WindowManager::createWindow (const WindowOptions& options) { Lock lock(this->mutex); if (this->destroyed) { return nullptr; } - StringStream env; - - if (this->inits[opts.index] && this->windows[opts.index] != nullptr) { - return this->windows[opts.index]; + if (this->windows.size() > options.index && this->windows[options.index] != nullptr) { + return this->windows[options.index]; } - if (opts.userConfig.size() > 0) { - for (auto const &envKey : parseStringList(opts.userConfig["build_env"])) { - auto cleanKey = trim(envKey); + StringStream env; - if (!Env::has(cleanKey)) { - continue; - } + if (options.userConfig.size() > 0) { + if (options.userConfig.contains("build_env")) { + for (auto const &envKey : parseStringList(options.userConfig.at("build_env"))) { + auto cleanKey = trim(envKey); - auto envValue = Env::get(cleanKey.c_str()); + if (!Env::has(cleanKey)) { + continue; + } - env << String( - cleanKey + "=" + encodeURIComponent(envValue) + "&" - ); + auto envValue = Env::get(cleanKey.c_str()); + + env << String( + cleanKey + "=" + encodeURIComponent(envValue) + "&" + ); + } } } else { for (auto const &envKey : parseStringList(this->options.userConfig["build_env"])) { @@ -184,68 +169,68 @@ namespace SSC { auto screen = Window::getScreenSize(); - float width = opts.width <= 0 + float width = options.width <= 0 ? Window::getSizeInPixels(this->options.defaultWidth, screen.width) - : opts.width; - float height = opts.height <= 0 + : options.width; + float height = options.height <= 0 ? Window::getSizeInPixels(this->options.defaultHeight, screen.height) - : opts.height; - float minWidth = opts.minWidth <= 0 + : options.height; + float minWidth = options.minWidth <= 0 ? Window::getSizeInPixels(this->options.defaultMinWidth, screen.width) - : opts.minWidth; - float minHeight = opts.minHeight <= 0 + : options.minWidth; + float minHeight = options.minHeight <= 0 ? Window::getSizeInPixels(this->options.defaultMinHeight, screen.height) - : opts.minHeight; - float maxWidth = opts.maxWidth <= 0 + : options.minHeight; + float maxWidth = options.maxWidth <= 0 ? Window::getSizeInPixels(this->options.defaultMaxWidth, screen.width) - : opts.maxWidth; - float maxHeight = opts.maxHeight <= 0 + : options.maxWidth; + float maxHeight = options.maxHeight <= 0 ? Window::getSizeInPixels(this->options.defaultMaxHeight, screen.height) - : opts.maxHeight; + : options.maxHeight; WindowOptions windowOptions = { - .resizable = opts.resizable, - .minimizable = opts.minimizable, - .maximizable = opts.maximizable, - .closable = opts.closable, - .frameless = opts.frameless, - .utility = opts.utility, - .canExit = opts.canExit, + .resizable = options.resizable, + .minimizable = options.minimizable, + .maximizable = options.maximizable, + .closable = options.closable, + .frameless = options.frameless, + .utility = options.utility, + .canExit = options.canExit, .width = width, .height = height, .minWidth = minWidth, .minHeight = minHeight, .maxWidth = maxWidth, .maxHeight = maxHeight, - .radius = opts.radius, - .margin = opts.margin, - .index = opts.index, - .debug = isDebugEnabled() || opts.debug, + .radius = options.radius, + .margin = options.margin, + .index = options.index, + .debug = isDebugEnabled() || options.debug, .isTest = this->options.isTest, - .headless = opts.headless, - .aspectRatio = opts.aspectRatio, - .titlebarStyle = opts.titlebarStyle, - .windowControlOffsets = opts.windowControlOffsets, - .backgroundColorLight = opts.backgroundColorLight, - .backgroundColorDark = opts.backgroundColorDark, + .headless = options.headless, + .aspectRatio = options.aspectRatio, + .titlebarStyle = options.titlebarStyle, + .windowControlOffsets = options.windowControlOffsets, + .backgroundColorLight = options.backgroundColorLight, + .backgroundColorDark = options.backgroundColorDark, .cwd = this->options.cwd, - .title = opts.title.size() > 0 ? opts.title : "", - .url = opts.url.size() > 0 ? opts.url : "data:text/html,", + .title = options.title.size() > 0 ? options.title : "", + .url = options.url.size() > 0 ? options.url : "data:text/html,", .argv = this->options.argv, - .preload = opts.preload.size() > 0 ? opts.preload : "", + .preload = options.preload.size() > 0 ? options.preload : "", .env = env.str(), .userConfig = this->options.userConfig, - .userScript = opts.userScript, - .runtimePrimordialOverrides = opts.runtimePrimordialOverrides, - .preloadCommonJS = opts.preloadCommonJS != false + .userScript = options.userScript, + .runtimePrimordialOverrides = options.runtimePrimordialOverrides, + .preloadCommonJS = options.preloadCommonJS != false }; - for (const auto& tuple : opts.userConfig) { + for (const auto& tuple : options.userConfig) { windowOptions.userConfig[tuple.first] = tuple.second; } if (isDebugEnabled()) { - this->log("Creating Window#" + std::to_string(opts.index)); + this->log("Creating Window#" + std::to_string(options.index)); } auto window = new ManagedWindow(*this, app, windowOptions); @@ -254,37 +239,39 @@ namespace SSC { window->onExit = this->options.onExit; window->onMessage = this->options.onMessage; - this->windows[opts.index] = window; - this->inits[opts.index] = true; + this->windows[options.index].reset(window); - return window; + return this->windows.at(options.index); } - WindowManager::ManagedWindow* WindowManager::createDefaultWindow (WindowOptions opts) { + SharedPointer WindowManager::createDefaultWindow (const WindowOptions& options) { return this->createWindow(WindowOptions { - .resizable = opts.resizable, - .minimizable = opts.minimizable, - .maximizable = opts.maximizable, - .closable = opts.closable, - .frameless = opts.frameless, - .utility = opts.utility, + .resizable = options.resizable, + .minimizable = options.minimizable, + .maximizable = options.maximizable, + .closable = options.closable, + .frameless = options.frameless, + .utility = options.utility, .canExit = true, - .width = opts.width, - .height = opts.height, + .width = options.width, + .height = options.height, .index = 0, #ifdef PORT .port = PORT, #endif - .headless = opts.userConfig["build_headless"] == "true", - .titlebarStyle = opts.titlebarStyle, - .windowControlOffsets = opts.windowControlOffsets, - .backgroundColorLight = opts.backgroundColorLight, - .backgroundColorDark = opts.backgroundColorDark, - .userConfig = opts.userConfig + .headless = ( + options.userConfig.contains("build_headless") && + options.userConfig.at("build_headless") == "true" + ), + .titlebarStyle = options.titlebarStyle, + .windowControlOffsets = options.windowControlOffsets, + .backgroundColorLight = options.backgroundColorLight, + .backgroundColorDark = options.backgroundColorDark, + .userConfig = options.userConfig }); } - JSON::Array WindowManager::json (Vector indices) { + JSON::Array WindowManager::json (const Vector& indices) { auto i = 0; JSON::Array result; for (auto index : indices) { @@ -358,17 +345,11 @@ namespace SSC { status = WindowStatus::WINDOW_KILLING; Window::kill(); status = WindowStatus::WINDOW_KILLED; - gc(); - } - } - - void WindowManager::ManagedWindow::gc () { - if (App::instance() != nullptr) { - manager.destroyWindow(reinterpret_cast(this)); + manager.destroyWindow(this->opts.index); } } - JSON::Object WindowManager::ManagedWindow::json () { + JSON::Object WindowManager::ManagedWindow::json () const { auto index = this->opts.index; auto size = this->getSize(); uint64_t id = 0; diff --git a/src/window/win.cc b/src/window/win.cc index 37dda5d4fe..6483b9d53b 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -751,92 +751,9 @@ namespace SSC { // UNREACHABLE - cannot continue } - static const int MAX_ALLOWED_SCHEME_ORIGINS = 64; - static const int MAX_CUSTOM_SCHEME_REGISTRATIONS = 64; - - struct SchemeRegistration { - String scheme; - }; - - ICoreWebView2CustomSchemeRegistration* registrations[MAX_CUSTOM_SCHEME_REGISTRATIONS] = {}; - Vector schemeRegistrations; - - schemeRegistrations.push_back({ "ipc" }); - schemeRegistrations.push_back({ "socket" }); - schemeRegistrations.push_back({ "node" }); - schemeRegistrations.push_back({ "npm" }); - - for (const auto& entry : split(opts.userConfig["webview_protocol-handlers"], " ")) { - const auto scheme = replace(trim(entry), ":", ""); - if (app.core->protocolHandlers.registerHandler(scheme)) { - schemeRegistrations.push_back({ scheme }); - } - } - - for (const auto& entry : opts.userConfig) { - const auto& key = entry.first; - if (key.starts_with("webview_protocol-handlers_")) { - const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; - const auto data = entry.second; - if (app.core->protocolHandlers.registerHandler(scheme, { data })) { - schemeRegistrations.push_back({ scheme }); - } - } - } - - Set origins; - Set protocols = { - "about", - "https", - "socket", - "npm", - "node" - }; - - static const auto devHost = SSC::getDevHost(); - const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {}; - int allowedOriginsCount = 0; - int registrationsCount = 0; - - if (devHost.starts_with("http:")) { - allowedOrigins[allowedOriginsCount] = convertStringToWString(devHost).c_str(); - } - - for (const auto& schemeRegistration : schemeRegistrations) { - protocols.insert(schemeRegistration.scheme); - } - - for (const auto& protocol : protocols) { - if (origins.size() == MAX_ALLOWED_SCHEME_ORIGINS) { - break; - } - - const auto origin = protocol + "://*"; - origins.insert(origin); - allowedOrigins[allowedOriginsCount++] = convertStringToWString(origin).c_str(); - } - - Set> customSchemeRegistrations; - for (const auto& schemeRegistration : schemeRegistrations) { - auto registration = Microsoft::WRL::Make( - convertStringToWString(schemeRegistration.scheme).c_str() - ); - - registration->put_HasAuthorityComponent(TRUE); - registration->put_TreatAsSecure(TRUE); - registration->SetAllowedOrigins(origins.size(), allowedOrigins); - - customSchemeRegistrations.insert(registration); - } - - for (const auto& registration : customSchemeRegistrations) { - registrations[registrationsCount++] = registration.Get(); - } - - options4->SetCustomSchemeRegistrations( - registrationsCount, - static_cast(registrations) - ); + this->bridge->router.configureHandlers({ + options + }); auto init = [&, opts]() -> HRESULT { return CreateCoreWebView2EnvironmentWithOptions( @@ -911,38 +828,6 @@ namespace SSC { COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW ); - EventRegistrationToken tokenNavigation; - - webview->add_NavigationStarting( - Microsoft::WRL::Callback( - [&](ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs *e) { - static const auto devHost = SSC::getDevHost(); - - PWSTR uri; - e->get_Uri(&uri); - SSC::String url(SSC::convertWStringToString(uri)); - Window* w = reinterpret_cast(GetWindowLongPtr((HWND)window, GWLP_USERDATA)); - - if (url.starts_with(userConfig["meta_application_protocol"])) { - e->put_Cancel(true); - if (w != nullptr) { - SSC::JSON::Object json = SSC::JSON::Object::Entries {{ - "url", url - }}; - w->bridge->router.emit("applicationurl", json.str()); - } - } else if (!w->bridge->router.isNavigationAllowed(url)) { - debug("Navigation was ignored for: %s", url.c_str()); - e->put_Cancel(true); - } - - CoTaskMemFree(uri); - return S_OK; - } - ).Get(), - &tokenNavigation - ); - EventRegistrationToken tokenSchemaFilter; webview->AddWebResourceRequestedFilter(L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL); @@ -961,18 +846,42 @@ namespace SSC { webview->add_WebResourceRequested( Microsoft::WRL::Callback( - [&, opts](ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) { - static auto userConfig = SSC::getUserConfig(); - static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - - Window* w = reinterpret_cast(GetWindowLongPtr((HWND)window, GWLP_USERDATA)); - - ICoreWebView2WebResourceRequest* req = nullptr; + [&, opts](ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* event) { + ICoreWebView2WebResourceRequest* platformRequest = nullptr; + ICoreWebView2HttpRequestHeaders* headers = nullptr; ICoreWebView2Environment* env = nullptr; ICoreWebView2_2* webview2 = nullptr; - LPWSTR req_uri; - LPWSTR req_method; + + LPWSTR method; + LPWSTR uri; + + webview->QueryInterface(IID_PPV_ARGS(&webview2)); + webview2->get_Environment(&env); + event->get_Request(&platformRequest); + + platformRequest->get_Headers(&headers); + platformRequest->get_Method(&method); + platformRequest->get_Uri(&uri); + + auto request = IPC::SchemeHandlers::Request::Builder(this->bridge->router->schemeHandlers, platformRequest); + + request.setMethod(convertWStringToString(method)); + + do { + ComPtr iterator; + BOOL hasCurrent = FALSE; + CHECK_FAILURE(headers->GetIterator(&iterator)); + while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { + LPWSTR name; + LPWSTR value; + + if (iterator->GetCurrentHeader(&name, &value) == S_OK) { + request.setHeader(convertWStringToString(name), convertWStringToString(value)); + } + } + } while (0); + String method; String uri; @@ -1738,13 +1647,19 @@ namespace SSC { }); } - SSC::String Window::getTitle () { - int len = GetWindowTextLength(window) + 1; - LPTSTR title = new TCHAR[len]; - GetWindowText(window, title, len); - String title_s = convertWStringToString(title); - delete[] title; - return title_s; + const String Window::getTitle () const { + if (window != nullptr) { + const auto size = GetWindowTextLength(window) + 1; + LPTSTR text = new TCHAR[size]{0}; + if (text != nullptr) { + GetWindowText(window, text, size); + const auto title = convertWStringToString(text); + delete [] text; + return title; + } + } + + return ""; } void Window::setTitle (const SSC::String& title) { @@ -1760,11 +1675,15 @@ namespace SSC { // Make sure controller exists, and the call to get window bounds succeeds. if (controller != nullptr && controller->get_Bounds(&rect) >= 0) { - height = rect.bottom - rect.top; - width = rect.right - rect.left; + this->height = rect.bottom - rect.top; + this->width = rect.right - rect.left; } - return { static_cast(height), static_cast(width) }; + return { static_cast(this->height), static_cast(this->width) }; + } + + ScreenSize Window::getSize () const { + return { static_cast(this->height), static_cast(this->width) }; } void Window::setSize (int width, int height, int hints) { @@ -1779,11 +1698,11 @@ namespace SSC { SetWindowLong(window, GWL_STYLE, style); if (hints == WINDOW_HINT_MAX) { - m_maxsz.x = width; - m_maxsz.y = height; + maximumSize.x = width; + maximumSize.y = height; } else if (hints == WINDOW_HINT_MIN) { - m_minsz.x = width; - m_minsz.y = height; + minimumSize.x = width; + minimumSize.y = height; } else { RECT r; r.left = r.top = 0; diff --git a/src/window/window.hh b/src/window/window.hh index 733c52724d..cd81710ade 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -159,19 +159,14 @@ namespace SSC { int width = 0; int height = 0; bool exiting = false; - Mutex mutex; - - #if !defined(__APPLE__) || (defined(__APPLE__) && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) - fs::path modulePath; - #endif - #if defined(__APPLE__) - #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - SSCWindow* window; - #endif + #if SSC_PLATFORM_APPLE SSCBridgedWebView* webview; SSCWindowDelegate* windowDelegate = nullptr; - #elif defined(__linux__) && !defined(__ANDROID__) + #if SSC_PLATFORM_MACOS + SSCWindow* window; + #endif + #elif SSC_PLATFORM_LINUX GtkSelectionData *selectionData = nullptr; GtkAccelGroup *accelGroup = nullptr; GtkWidget *webview = nullptr; @@ -187,7 +182,7 @@ namespace SSC { std::vector draggablePayload; bool isDragInvokedInsideWindow; GdkPoint initialLocation; - #elif defined(_WIN32) + #elif SSC_PLATFORM_WINDOWS static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); bool usingCustomEdgeRuntimeDirectory = false; ICoreWebView2Controller *controller = nullptr; @@ -195,17 +190,23 @@ namespace SSC { HMENU menubar; HMENU menutray; DWORD mainThread = GetCurrentThreadId(); + double dragLastX = 0; double dragLastY = 0; bool shouldDrag; DragDrop* drop; - POINT m_minsz = POINT {0, 0}; - POINT m_maxsz = POINT {0, 0}; + + POINT minimumSize = POINT {0, 0}; + POINT maximumSize = POINT {0, 0}; + POINT initialCursorPos = POINT {0, 0}; RECT initialWindowPos = RECT {0, 0, 0, 0}; + HWND window; - std::map menuMap; - std::map menuTrayMap; + std::map menuMap; + std::map menuTrayMap; + fs::path modulePath; + void resize (HWND window); #endif @@ -225,9 +226,10 @@ namespace SSC { void maximize(); void restore(); void navigate (const String&, const String&); - String getTitle (); + const String getTitle () const; void setTitle (const String&); ScreenSize getSize (); + const ScreenSize getSize () const; void setSize (int, int, int); void setContextMenu (const String&, const String&); void closeContextMenu (const String&); @@ -339,15 +341,14 @@ namespace SSC { void exit (int code); void kill (); void gc (); - JSON::Object json (); + JSON::Object json () const; }; std::chrono::system_clock::time_point lastDebugLogLine; App &app; bool destroyed = false; - Vector inits; - Vector windows; + Vector> windows; Mutex mutex; WindowManagerOptions options; @@ -369,22 +370,20 @@ namespace SSC { } void destroy (); - void configure (WindowManagerOptions configuration); + void configure (const WindowManagerOptions& configuration); - ManagedWindow* getWindow (int index, WindowStatus status); - ManagedWindow* getWindow (int index); - ManagedWindow* getOrCreateWindow (int index); - ManagedWindow* getOrCreateWindow (int index, WindowOptions opts); + SharedPointer getWindow (int index, const WindowStatus status); + SharedPointer getWindow (int index); + SharedPointer getOrCreateWindow (int index); + SharedPointer getOrCreateWindow (int index, const WindowOptions& options); WindowStatus getWindowStatus (int index); void destroyWindow (int index); - void destroyWindow (ManagedWindow* window); - void destroyWindow (Window* window); - ManagedWindow* createWindow (WindowOptions opts); - ManagedWindow* createDefaultWindow (WindowOptions opts); + SharedPointer createWindow (const WindowOptions& options); + SharedPointer createDefaultWindow (const WindowOptions& options); - JSON::Array json (Vector indices); + JSON::Array json (const Vector& indices); }; class Dialog { From ade439be65889986013db673f89b51b28474e345 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:49:44 -0400 Subject: [PATCH 0638/1178] refactor(api/fs): introduce 'writeFileSync' --- api/fs/index.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/api/fs/index.js b/api/fs/index.js index c48a4fa8d8..456ea4512f 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -1251,6 +1251,45 @@ export function writeFile (path, data, options, callback) { }) } +/** + * Writes data to a file synchronously. + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} + */ +export function writeFileSync (path, data, options) { + const id = String(options?.id || rand64()) + const stats = statSync(path) + + result = ipc.sendSync('fs.open', { + id, + mode: options?.mode || 0o666, + path, + flags: options?.flags ? normalizeFlags(options.flags) : 'w' + }, options) + + if (result.err) { + throw result.err + } + + result = ipc.sendSync('fs.write', { id, offset: 0 }, null, data) + + if (result.err) { + throw result.err + } + + result = ipc.sendSync('fs.close', { id }, options) + + if (result.err) { + throw result.err + } +} + /** * Watch for changes at `path` calling `callback` * @param {string} From cda8ac64832cc548477e074e5f2f84c62c5b6446 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:50:15 -0400 Subject: [PATCH 0639/1178] refactor(api/path): introduce 'TMP' well known path --- api/path.js | 6 ++++-- api/path/index.js | 6 ++++-- api/path/posix.js | 6 ++++-- api/path/well-known.js | 9 ++++++++- api/path/win32.js | 6 ++++-- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/api/path.js b/api/path.js index baf7b96f51..cf3be66729 100644 --- a/api/path.js +++ b/api/path.js @@ -16,7 +16,8 @@ import { MUSIC, HOME, DATA, - LOG + LOG, + TMP } from './path/index.js' const isWin32 = primordials.platform === 'win32' @@ -52,7 +53,8 @@ export { MUSIC, HOME, DATA, - LOG + LOG, + TMP } export default isWin32 ? win32 : posix diff --git a/api/path/index.js b/api/path/index.js index 6fae2ffb2a..002e01a6b9 100644 --- a/api/path/index.js +++ b/api/path/index.js @@ -14,7 +14,8 @@ import { MUSIC, HOME, DATA, - LOG + LOG, + TMP } from './well-known.js' export { @@ -34,7 +35,8 @@ export { MUSIC, HOME, DATA, - LOG + LOG, + TMP } export default exports diff --git a/api/path/posix.js b/api/path/posix.js index 671c43d7fa..216a8c20df 100644 --- a/api/path/posix.js +++ b/api/path/posix.js @@ -14,7 +14,8 @@ import { MUSIC, HOME, DATA, - LOG + LOG, + TMP } from './well-known.js' import * as exports from './posix.js' @@ -37,7 +38,8 @@ export { MUSIC, HOME, DATA, - LOG + LOG, + TMP } export default exports diff --git a/api/path/well-known.js b/api/path/well-known.js index 1e3e8ef014..a316c70e56 100644 --- a/api/path/well-known.js +++ b/api/path/well-known.js @@ -62,6 +62,12 @@ export const DATA = paths.data || null */ export const LOG = paths.log || null +/** + * Well known path to the application's "tmp" folder. + * @type {?string} + */ +export const TMP = paths.tmp || null + /** * Well known path to the application's "home" folder. * This may be the user's HOME directory or the application container sandbox. @@ -80,5 +86,6 @@ export default { MUSIC, HOME, DATA, - LOG + LOG, + TMP } diff --git a/api/path/win32.js b/api/path/win32.js index c7be443b0c..fce09290e4 100644 --- a/api/path/win32.js +++ b/api/path/win32.js @@ -14,7 +14,8 @@ import { MUSIC, HOME, DATA, - LOG + LOG, + TMP } from './well-known.js' import * as exports from './win32.js' @@ -37,7 +38,8 @@ export { MUSIC, HOME, DATA, - LOG + LOG, + TMP } export default exports From c30bef0901f2dff95d24e0c8985eb7eb55a56b6a Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:50:55 -0400 Subject: [PATCH 0640/1178] refactor(api/process): hande 'getOwnPropertyDescriptor' in 'env' proxy --- api/process.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/api/process.js b/api/process.js index 25793266fc..b817f717b4 100644 --- a/api/process.js +++ b/api/process.js @@ -16,6 +16,9 @@ let didEmitExitEvent = false let cwd = primordials.cwd export class ProcessEnvironmentEvent extends Event { + key + value + constructor (type, key, value) { super(type) this.key = key @@ -29,7 +32,7 @@ export const env = Object.defineProperties(new EventTarget(), { enumerable: false, writable: false, value: new Proxy({}, { - get (_, property, receiver) { + get (_, property) { if (Reflect.has(env, property)) { return Reflect.get(env, property) } @@ -46,11 +49,23 @@ export const env = Object.defineProperties(new EventTarget(), { deleteProperty (_, property) { if (Reflect.has(env, property)) { + // @ts-ignore env.dispatchEvent(new ProcessEnvironmentEvent('delete', property)) } return Reflect.deleteProperty(env, property) }, + getOwnPropertyDescriptor (_, property) { + if (Reflect.has(globalThis.__args.env, property)) { + return { + configurable: true, + enumerable: true, + writable: true, + value: globalThis.__args.env[property] + } + } + }, + has (_, property) { return ( Reflect.has(env, property) || @@ -69,8 +84,11 @@ export const env = Object.defineProperties(new EventTarget(), { }) class Process extends EventEmitter { + // @ts-ignore stdin = new tty.ReadStream(0) + // @ts-ignore stdout = new tty.WriteStream(1) + // @ts-ignore stderr = new tty.WriteStream(2) get version () { @@ -152,7 +170,9 @@ if (!isNode) { }) globalThis.addEventListener('signal', (event) => { + // @ts-ignore if (event.detail.signal) { + // @ts-ignore const code = event.detail.signal const name = signal.getName(code) const message = signal.getMessage(code) From 1c3a96793cb6acad9a9d22c6bd867684be2090e6 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:51:53 -0400 Subject: [PATCH 0641/1178] refactor(api/service-worker): time out on container resolution, prevent early fetch --- api/service-worker/container.js | 2 +- api/service-worker/worker.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 52f7663d76..0da355ee4b 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -320,7 +320,7 @@ export class ServiceWorkerContainer extends EventTarget { await preloadExistingRegistration(this) } - internal.get(this).init.resolve() + setTimeout(() => internal.get(this).init.resolve(), 250) } async getRegistration (clientURL) { diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index e160984cc8..38aa513d51 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -343,6 +343,10 @@ async function onMessage (event) { return } + if (!/activated|activating/.test(state.serviceWorker.state)) { + return + } + if (data?.fetch?.request) { event.stopImmediatePropagation() if (/post|put/i.test(data.fetch.request.method)) { From 828604084602d248b3643b68cef695fe47d7754f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:53:37 -0400 Subject: [PATCH 0642/1178] refactor(ipc): implement bridge scheme handlers, more clean up --- src/ipc/bridge.cc | 1326 +++++++++++++++++------------------- src/ipc/bridge.hh | 3 + src/ipc/navigator.cc | 71 +- src/ipc/navigator.hh | 4 + src/ipc/router.cc | 605 +--------------- src/ipc/router.hh | 25 +- src/ipc/routes.cc | 270 +------- src/ipc/scheme_handlers.cc | 134 +++- src/ipc/scheme_handlers.hh | 29 +- 9 files changed, 867 insertions(+), 1600 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index b1cd4c63f7..30c1870e19 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -22,6 +22,18 @@ extern const SSC::Map SSC::getUserConfig (); extern bool SSC::isDebugEnabled (); +// create a proxy module so imports of the module of concern are imported +// exactly once at the canonical URL (file:///...) in contrast to module +// URLs (socket:...) + +static constexpr auto moduleTemplate = +R"S( +import module from '{{url}}' +export * from '{{url}}' +export default module +)S"; + + using namespace SSC; using namespace SSC::IPC; @@ -38,12 +50,18 @@ static const Vector allowedNodeCoreModules = { "dns/promises", "events", "fs", + "fs/constants", "fs/promises", "http", "https", + "ip", + "module", "net", "os", + "os/constants", "path", + "path/posix", + "path/win32", "perf_hooks", "process", "querystring", @@ -55,717 +73,13 @@ static const Vector allowedNodeCoreModules = { "timers", "timers/promises", "tty", - "util", "url", + "util", "vm", "worker_threads" }; -static void registerSchemeHandler (Router *router) { - static const auto MAX_BODY_BYTES = 4 * 1024 * 1024; - static const auto devHost = SSC::getDevHost(); - static Atomic isInitialized = false; - - if (isInitialized) { - return; - } - - isInitialized = true; - -#if defined(__linux__) && !defined(__ANDROID__) - auto ctx = webkit_web_context_get_default(); - auto security = webkit_web_context_get_security_manager(ctx); - - webkit_web_context_register_uri_scheme(ctx, "ipc", [](auto request, auto ptr) { - IPC::Router* router = nullptr; - - auto webview = webkit_uri_scheme_request_get_web_view(request); - auto windowManager = App::instance()->getWindowManager(); - - for (auto& window : windowManager->windows) { - if ( - window != nullptr && - window->bridge != nullptr && - WEBKIT_WEB_VIEW(window->webview) == webview - ) { - router = &window->bridge->router; - break; - } - } - - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto method = String(webkit_uri_scheme_request_get_http_method(request)); - auto message = IPC::Message{ uri }; - char bytes[MAX_BODY_BYTES]{0}; - - if ((method == "POST" || method == "PUT")) { - auto body = webkit_uri_scheme_request_get_http_body(request); - if (body) { - GError* error = nullptr; - message.buffer.bytes = new char[MAX_BODY_BYTES]{0}; - - const auto success = g_input_stream_read_all( - body, - message.buffer.bytes, - MAX_BODY_BYTES, - &message.buffer.size, - nullptr, - &error - ); - - if (!success) { - delete message.buffer.bytes; - webkit_uri_scheme_request_finish_error( - request, - error - ); - return; - } - } - } - - auto invoked = router->invoke(message, message.buffer.bytes, message.buffer.size, [=](auto result) { - auto json = result.str(); - auto size = result.post.body != nullptr ? result.post.length : json.size(); - auto body = result.post.body != nullptr ? result.post.body : json.c_str(); - - char* data = nullptr; - - if (size > 0) { - data = new char[size]{0}; - memcpy(data, body, size); - } - - auto stream = g_memory_input_stream_new_from_data(data, size, nullptr); - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, size); - - soup_message_headers_append(headers, "cache-control", "no-cache"); - for (const auto& header : result.headers.entries) { - soup_message_headers_append(headers, header.key.c_str(), header.value.c_str()); - } - - if (result.post.body) { - webkit_uri_scheme_response_set_content_type(response, IPC_BINARY_CONTENT_TYPE); - } else { - webkit_uri_scheme_response_set_content_type(response, IPC_JSON_CONTENT_TYPE); - } - - webkit_uri_scheme_request_finish_with_response(request, response); - g_input_stream_close_async(stream, 0, nullptr, +[]( - GObject* object, - GAsyncResult* asyncResult, - gpointer userData - ) { - auto stream = (GInputStream*) object; - g_input_stream_close_finish(stream, asyncResult, nullptr); - g_object_unref(stream); - g_idle_add_full( - G_PRIORITY_DEFAULT_IDLE, - (GSourceFunc) [](gpointer userData) { - return G_SOURCE_REMOVE; - }, - userData, - [](gpointer userData) { - delete [] static_cast(userData); - } - ); - }, data); - }); - - if (!invoked) { - auto err = JSON::Object::Entries { - {"source", uri}, - {"err", JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"}, - {"url", uri} - }} - }; - - auto msg = JSON::Object(err).str(); - auto size = msg.size(); - auto bytes = msg.c_str(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - auto response = webkit_uri_scheme_response_new(stream, msg.size()); - - webkit_uri_scheme_response_set_status(response, 404, "Not found"); - webkit_uri_scheme_response_set_content_type(response, IPC_JSON_CONTENT_TYPE); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - } - }, - router, - 0); - - webkit_web_context_register_uri_scheme(ctx, "socket", [](auto request, auto ptr) { - IPC::Router* router = nullptr; - - auto webview = webkit_uri_scheme_request_get_web_view(request); - auto windowManager = App::instance()->getWindowManager(); - - for (auto& window : windowManager->windows) { - if ( - window != nullptr && - window->bridge != nullptr && - WEBKIT_WEB_VIEW(window->webview) == webview - ) { - router = &window->bridge->router; - break; - } - } - - auto userConfig = router->bridge->userConfig; - bool isModule = false; - auto method = String(webkit_uri_scheme_request_get_http_method(request)); - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto cwd = getcwd(); - uint64_t clientId = router->bridge->id; - - if (uri.starts_with("socket:///")) { - uri = uri.substr(10); - } else if (uri.starts_with("socket://")) { - uri = uri.substr(9); - } else if (uri.starts_with("socket:")) { - uri = uri.substr(7); - } - - const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - auto path = String( - uri.starts_with(bundleIdentifier) - ? uri.substr(bundleIdentifier.size()) - : "socket/" + uri - ); - - auto parsedPath = Router::parseURLComponents(path); - auto ext = fs::path(parsedPath.path).extension().string(); - - if (ext.size() > 0 && !ext.starts_with(".")) { - ext = "." + ext; - } - - if (!uri.starts_with(bundleIdentifier)) { - path = parsedPath.path; - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - ext = ".js"; - } - - if (parsedPath.queryString.size() > 0) { - path += String("?") + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - path += String("#") + parsedPath.fragment; - } - - uri = "socket://" + bundleIdentifier + "/" + path; - auto moduleSource = trim(tmpl( - moduleTemplate, - Map { {"url", String(uri)} } - )); - - auto size = moduleSource.size(); - auto bytes = moduleSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - - if (stream) { - auto response = webkit_uri_scheme_response_new(stream, size); - webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - } else { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - } - return; - } - - auto resolved = Router::resolveURLPathForWebView(parsedPath.path, cwd); - auto mount = Router::resolveNavigatorMountForWebView(parsedPath.path); - path = resolved.path; - - if (mount.path.size() > 0) { - if (mount.resolution.redirect) { - auto redirectURL = resolved.path; - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto redirectSource = String( - "" - ); - - auto size = redirectSource.size(); - auto bytes = redirectSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - - if (stream) { - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, (gint64) size); - auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - - soup_message_headers_append(headers, "location", redirectURL.c_str()); - soup_message_headers_append(headers, "content-location", contentLocation.c_str()); - - webkit_uri_scheme_response_set_http_headers(response, headers); - webkit_uri_scheme_response_set_content_type(response, "text/html"); - webkit_uri_scheme_request_finish_with_response(request, response); - - g_object_unref(stream); - } else { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - } - - return; - } - } else if (path.size() == 0) { - if (userConfig.contains("webview_default_index")) { - path = userConfig["webview_default_index"]; - } else { - if (router->core->serviceWorker.registrations.size() > 0) { - auto requestHeaders = webkit_uri_scheme_request_get_http_headers(request); - auto fetchRequest = ServiceWorkerContainer::FetchRequest {}; - - fetchRequest.client.id = clientId; - fetchRequest.client.preload = router->bridge->preload; - - fetchRequest.method = method; - fetchRequest.scheme = "socket"; - fetchRequest.host = userConfig["meta_bundle_identifier"]; - fetchRequest.pathname = parsedPath.path; - fetchRequest.query = parsedPath.queryString; - - soup_message_headers_foreach( - requestHeaders, - [](auto name, auto value, auto userData) { - auto fetchRequest = reinterpret_cast(userData); - const auto entry = String(name) + ": " + String(value); - fetchRequest->headers.push_back(entry); - }, - &fetchRequest - ); - - if ((method == "POST" || method == "PUT")) { - auto body = webkit_uri_scheme_request_get_http_body(request); - if (body) { - GError* error = nullptr; - fetchRequest.buffer.bytes = new char[MAX_BODY_BYTES]{0}; - - const auto success = g_input_stream_read_all( - body, - fetchRequest.buffer.bytes, - MAX_BODY_BYTES, - &fetchRequest.buffer.size, - nullptr, - &error - ); - - if (!success) { - delete fetchRequest.buffer.bytes; - webkit_uri_scheme_request_finish_error( - request, - error - ); - return; - } - } - } - - const auto fetched = router->core->serviceWorker.fetch(fetchRequest, [=] (auto res) mutable { - if (res.statusCode == 0) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "%.*s", - (int) res.buffer.size, - res.buffer.bytes - ) - ); - return; - } - - const auto webviewHeaders = split(userConfig["webview_headers"], '\n'); - auto stream = g_memory_input_stream_new_from_data(res.buffer.bytes, res.buffer.size, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } - - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, (gint64) res.buffer.size); - - for (const auto& line : webviewHeaders) { - auto pair = split(trim(line), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - soup_message_headers_append(headers, key.c_str(), value.c_str()); - } - - for (const auto& line : res.headers) { - auto pair = split(trim(line), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - - if (key == "content-type" || key == "Content-Type") { - webkit_uri_scheme_response_set_content_type(response, value.c_str()); - } - - soup_message_headers_append(headers, key.c_str(), value.c_str()); - } - - webkit_uri_scheme_response_set_http_headers(response, headers); - webkit_uri_scheme_request_finish_with_response(request, response); - - g_object_unref(stream); - }); - - if (fetched) { - return; - } else { - if (fetchRequest.buffer.bytes != nullptr) { - delete fetchRequest.buffer.bytes; - } - } - } - } - } else if (resolved.redirect) { - auto redirectURL = resolved.path; - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto redirectSource = String( - "" - ); - - auto size = redirectSource.size(); - auto bytes = redirectSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } - - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto response = webkit_uri_scheme_response_new(stream, (gint64) size); - auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - - soup_message_headers_append(headers, "location", redirectURL.c_str()); - soup_message_headers_append(headers, "content-location", contentLocation.c_str()); - - webkit_uri_scheme_response_set_http_headers(response, headers); - webkit_uri_scheme_response_set_content_type(response, "text/html"); - webkit_uri_scheme_request_finish_with_response(request, response); - - g_object_unref(stream); - return; - } - - if (mount.path.size() > 0) { - path = mount.path; - } else if (path.size() > 0) { - path = fs::absolute(fs::path(cwd) / path.substr(1)).string(); - } - - if (path.size() == 0 || !fs::exists(path)) { - auto stream = g_memory_input_stream_new_from_data(nullptr, 0, 0); - - if (!stream) { - webkit_uri_scheme_request_finish_error( - request, - g_error_new( - g_quark_from_string(userConfig["meta_bundle_identifier"].c_str()), - 1, - "Failed to create response stream" - ) - ); - return; - } - - auto response = webkit_uri_scheme_response_new(stream, 0); - - webkit_uri_scheme_response_set_status(response, 404, "Not found"); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - return; - } - - WebKitURISchemeResponse* response = nullptr; - GInputStream* stream = nullptr; - gchar* mimeType = nullptr; - GError* error = nullptr; - char* data = nullptr; - - auto webviewHeaders = split(userConfig["webview_headers"], '\n'); - auto headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - - if (path.ends_with(".html")) { - auto script = router->bridge->preload; - - if (userConfig["webview_importmap"].size() > 0) { - const auto file = Path(userConfig["webview_importmap"]); - - if (fs::exists(file)) { - String string; - std::ifstream stream(file.string().c_str()); - auto buffer = std::istreambuf_iterator(stream); - auto end = std::istreambuf_iterator(); - string.assign(buffer, end); - stream.close(); - - script = ( - String("\n") + - script - ); - } - } - - const auto file = Path(path); - - if (fs::exists(file)) { - char* contents = nullptr; - gsize size = 0; - if (g_file_get_contents(file.c_str(), &contents, &size, &error)) { - String html = contents; - Vector protocolHandlers = { "npm:", "node:" }; - for (const auto& entry : router->core->protocolHandlers.mapping) { - protocolHandlers.push_back(String(entry.first) + ":"); - } - - html = tmpl(html, Map { - {"protocol_handlers", join(protocolHandlers, " ")} - }); - - if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + script)); - } else { - html = script + html; - } - - data = new char[html.size()]{0}; - memcpy(data, html.data(), html.size()); - g_free(contents); - - stream = g_memory_input_stream_new_from_data(data, (gint64) html.size(), nullptr); - - if (stream) { - response = webkit_uri_scheme_response_new(stream, -1); - } else { - delete [] data; - data = nullptr; - } - } - } - } else { - auto file = g_file_new_for_path(path.c_str()); - auto size = fs::file_size(path); - - if (file) { - stream = (GInputStream*) g_file_read(file, nullptr, &error); - g_object_unref(file); - } - - if (stream) { - response = webkit_uri_scheme_response_new(stream, (gint64) size); - g_object_unref(stream); - } - } - - if (!stream) { - webkit_uri_scheme_request_finish_error(request, error); - g_error_free(error); - return; - } - - soup_message_headers_append(headers, "cache-control", "no-cache"); - soup_message_headers_append(headers, "access-control-allow-origin", "*"); - soup_message_headers_append(headers, "access-control-allow-methods", "*"); - soup_message_headers_append(headers, "access-control-allow-headers", "*"); - soup_message_headers_append(headers, "access-control-allow-credentials", "true"); - - for (const auto& line : webviewHeaders) { - auto pair = split(trim(line), ':'); - auto key = trim(pair[0]); - auto value = trim(pair[1]); - soup_message_headers_append(headers, key.c_str(), value.c_str()); - } - - webkit_uri_scheme_response_set_http_headers(response, headers); - - if (path.ends_with(".wasm")) { - webkit_uri_scheme_response_set_content_type(response, "application/wasm"); - } else if (path.ends_with(".cjs") || path.ends_with(".mjs")) { - webkit_uri_scheme_response_set_content_type(response, "text/javascript"); - } else if (path.ends_with(".ts")) { - webkit_uri_scheme_response_set_content_type(response, "application/typescript"); - } else { - mimeType = g_content_type_guess(path.c_str(), nullptr, 0, nullptr); - if (mimeType) { - webkit_uri_scheme_response_set_content_type(response, mimeType); - } else { - webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); - } - } - - webkit_uri_scheme_request_finish_with_response(request, response); - - if (data) { - g_input_stream_close_async(stream, 0, nullptr, +[]( - GObject* object, - GAsyncResult* asyncResult, - gpointer userData - ) { - auto stream = (GInputStream*) object; - g_input_stream_close_finish(stream, asyncResult, nullptr); - g_object_unref(stream); - g_idle_add_full( - G_PRIORITY_DEFAULT_IDLE, - (GSourceFunc) [](gpointer userData) { - return G_SOURCE_REMOVE; - }, - userData, - [](gpointer userData) { - delete [] static_cast(userData); - } - ); - }, data); - } else { - g_object_unref(stream); - } - - if (mimeType) { - g_free(mimeType); - } - }, - router, - 0); - - webkit_web_context_register_uri_scheme(ctx, "node", [](auto request, auto ptr) { - auto uri = String(webkit_uri_scheme_request_get_uri(request)); - auto router = reinterpret_cast(ptr); - auto userConfig = router->bridge->userConfig; - - const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - - if (uri.starts_with("node:///")) { - uri = uri.substr(10); - } else if (uri.starts_with("node://")) { - uri = uri.substr(9); - } else if (uri.starts_with("node:")) { - uri = uri.substr(7); - } - - auto path = String("socket/" + uri); - auto ext = fs::path(path).extension().string(); - - if (ext.size() > 0 && !ext.starts_with(".")) { - ext = "." + ext; - } - - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - ext = ".js"; - } - - uri = "socket://" + bundleIdentifier + "/" + path; - - auto moduleSource = trim(tmpl( - moduleTemplate, - Map { {"url", String(uri)} } - )); - - auto size = moduleSource.size(); - auto bytes = moduleSource.data(); - auto stream = g_memory_input_stream_new_from_data(bytes, size, 0); - auto response = webkit_uri_scheme_response_new(stream, size); - - webkit_uri_scheme_response_set_content_type(response, SOCKET_MODULE_CONTENT_TYPE); - webkit_uri_scheme_request_finish_with_response(request, response); - g_object_unref(stream); - }, - router, - 0); - - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "ipc"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "ipc"); - webkit_security_manager_register_uri_scheme_as_secure(security, "ipc"); - webkit_security_manager_register_uri_scheme_as_local(security, "ipc"); - - if (devHost.starts_with("http:")) { - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "http"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "http"); - webkit_security_manager_register_uri_scheme_as_secure(security, "http"); - webkit_security_manager_register_uri_scheme_as_local(security, "http"); - } - - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "socket"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "socket"); - webkit_security_manager_register_uri_scheme_as_secure(security, "socket"); - webkit_security_manager_register_uri_scheme_as_local(security, "socket"); - - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "node"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "node"); - webkit_security_manager_register_uri_scheme_as_secure(security, "node"); - webkit_security_manager_register_uri_scheme_as_local(security, "node"); -#endif -} - namespace SSC::IPC { - Mutex Router::notificationMapMutex; - std::map Router::notificationMap; - static Vector instances; static Mutex mutex; @@ -775,8 +89,8 @@ namespace SSC::IPC { Bridge::Bridge (Core *core, Map userConfig) : userConfig(userConfig), - router(), - navigator(this) + navigator(this), + schemeHandlers(&this->router) { Lock lock(SSC::IPC::mutex); instances.push_back(this); @@ -814,6 +128,10 @@ namespace SSC::IPC { this->router.emit("permissionchange", event.str()); }); + // on Linux, much of the Notification API is supported so these observers + // below are not needed as those events already occur in the webview + // we are patching for the other platforms + #if !SSC_PLATFORM_LINUX core->notifications.addPermissionChangeObserver(this->notificationsPermissionChangeObserver, [this](auto json) { JSON::Object event = JSON::Object::Entries { {"name", "notifications"}, @@ -831,6 +149,7 @@ namespace SSC::IPC { this->router.emit("notificationpresented", json.str()); }); } + #endif #if SSC_PLATFORM_DESKTOP auto defaultUserConfig = SSC::getUserConfig(); @@ -922,8 +241,11 @@ namespace SSC::IPC { Lock lock(SSC::IPC::mutex); // remove observers - core->networkStatus.removeObserver(this->networkStatusObserver); core->geolocation.removePermissionChangeObserver(this->geolocationPermissionChangeObserver); + core->networkStatus.removeObserver(this->networkStatusObserver); + core->notifications.removePermissionChangeObserver(this->notificationsPermissionChangeObserver); + core->notifications.removeNotificationResponseObserver(this->notificationResponseObserver); + core->notifications.removeNotificationPresentedObserver(this->notificationPresentedObserver); const auto cursor = std::find(instances.begin(), instances.end(), this); if (cursor != instances.end()) { @@ -960,4 +282,592 @@ namespace SSC::IPC { const Vector& Bridge::getAllowedNodeCoreModules () const { return allowedNodeCoreModules; } + + void Bridge::configureHandlers (const SchemeHandlers::Configuration& configuration) { + this->schemeHandlers.configure(configuration); + this->schemeHandlers.registerSchemeHandler("ipc", [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + auto message = Message(request.url(), true); + + // handle special 'ipc://post' case + if (message.name == "post") { + uint64_t id = 0; + + try { + id = std::stoull(message.get("id")); + } catch (...) { + auto response = SchemeHandlers::Response(request, 400); + response.send(JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "Invalid 'id' given in parameters"} + }} + }); + + callback(response); + return; + } + + if (!this->core->hasPost(id)) { + auto response = SchemeHandlers::Response(request, 404); + response.send(JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "A 'Post' was not found for the given 'id' in parameters"}, + {"type", "NotFoundError"} + }} + }); + + callback(response); + return; + } + + auto response = SchemeHandlers::Response(request, 200); + const auto post = this->core->getPost(id); + + // handle raw headers in 'Post' object + if (post.headers.size() > 0) { + const auto lines = split(trim(post.headers), '\n'); + for (const auto& line : lines) { + const auto pair = split(trim(line), ':'); + const auto key = trim(pair[0]); + const auto value = trim(pair[1]); + response.setHeader(key, value); + } + } + + response.write(post.length, post.body); + callback(response); + this->core->removePost(id); + return; + } + + message.isHTTP = true; + message.cancel = std::make_shared(); + + callbacks.cancel = [message] () { + if (message.cancel->handler != nullptr) { + message.cancel->handler(message.cancel->data); + } + }; + + const auto size = request.body.size; + const auto bytes = request.body.bytes != nullptr ? *request.body.bytes : nullptr; + const auto invoked = this->router.invoke(message, bytes, size, [request, message, callback](Result result) { + if (!request.isActive()) { + return; + } + + auto response = SchemeHandlers::Response(request); + + response.setHeaders(result.headers); + response.setHeader("access-control-allow-origin", "*"); + response.setHeader("access-control-allow-methods", "GET, POST, PUT, DELETE"); + response.setHeader("access-control-allow-headers", "*"); + response.setHeader("access-control-allow-credentials", "true"); + + // handle event source streams + if (result.post.eventStream != nullptr) { + response.setHeader("content-type", "text/event-stream"); + response.setHeader("cache-control", "no-store"); + *result.post.eventStream = [request, response, message, callback]( + const char* name, + const char* data, + bool finished + ) mutable { + if (request.isCancelled()) { + if (message.cancel->handler != nullptr) { + message.cancel->handler(message.cancel->data); + } + return false; + } + + response.writeHead(200); + + const auto event = SchemeHandlers::Response::Event { name, data }; + + if (event.count() > 0) { + response.write(event.str()); + } + + if (finished) { + callback(response); + } + + return true; + }; + return; + } + + // handle chunk streams + if (result.post.chunkStream != nullptr) { + response.setHeader("transfer-encoding", "chunked"); + *result.post.chunkStream = [request, response, message, callback]( + const char* chunk, + size_t size, + bool finished + ) mutable { + if (request.isCancelled()) { + if (message.cancel->handler != nullptr) { + message.cancel->handler(message.cancel->data); + } + return false; + } + + response.writeHead(200); + response.write(size, chunk); + + if (finished) { + callback(response); + } + + return true; + }; + return; + } + + if (result.post.body != nullptr) { + response.write(result.post.length, result.post.body); + } else { + response.write(result.json()); + } + + callback(response); + }); + + if (!invoked) { + auto response = SchemeHandlers::Response(request, 404); + response.send(JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"}, + {"url", request.url()} + }} + }); + + callback(response); + } + }); + + this->schemeHandlers.registerSchemeHandler("socket", [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + auto userConfig = this->userConfig; + auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + // the location of static application resources + const auto applicationResources = FileResource::getResourcesPath().string(); + // default response is 404 + auto response = SchemeHandlers::Response(request, 404); + + // the resouce path that may be request + String resourcePath; + + // the content location relative to the request origin + String contentLocation; + + // application resource or service worker request at `socket:///*` + if (request.hostname == bundleIdentifier) { + const auto resolved = Router::resolveURLPathForWebView(request.pathname, applicationResources); + const auto mount = Router::resolveNavigatorMountForWebView(request.pathname); + + if (mount.resolution.redirect || resolved.redirect) { + auto pathname = mount.resolution.redirect + ? mount.resolution.pathname + : resolved.pathname; + + if (request.method == "GET") { + auto location = mount.resolution.pathname; + if (request.query.size() > 0) { + location += "?" + request.query; + } + + if (request.fragment.size() > 0) { + location += "#" + request.fragment; + } + + response.redirect(location); + return callback(response); + } + } else if (mount.path.size() > 0) { + resourcePath = mount.path; + } else if (request.pathname == "" || request.pathname == "/") { + if (userConfig.contains("webview_default_index")) { + resourcePath = userConfig["webview_default_index"]; + if (resourcePath.starts_with("./")) { + resourcePath = applicationResources + resourcePath.substr(1); + } else if (resourcePath.starts_with("/")) { + resourcePath = applicationResources + resourcePath; + } else { + resourcePath = applicationResources + + "/" + resourcePath; + } + } + } + + if (resourcePath.size() == 0 && resolved.pathname.size() > 0) { + resourcePath = applicationResources + resolved.pathname; + } + + // handle HEAD and GET requests for a file resource + if (resourcePath.size() > 0) { + contentLocation = replace(resourcePath, applicationResources, ""); + + auto resource = FileResource(resourcePath); + + if (!resource.exists()) { + response.writeHead(404); + } else { + if (contentLocation.size() > 0) { + response.setHeader("content-location", contentLocation); + } + + if (request.method == "OPTIONS") { + response.setHeader("access-control-allow-origin", "*"); + response.setHeader("access-control-allow-methods", "GET, HEAD"); + response.setHeader("access-control-allow-headers", "*"); + response.setHeader("access-control-allow-credentials", "true"); + response.writeHead(200); + } + + if (request.method == "HEAD") { + const auto contentType = resource.mimeType(); + const auto contentLength = resource.size(); + + if (contentType.size() > 0) { + response.setHeader("content-type", contentType); + } + + if (contentLength > 0) { + response.setHeader("content-length", contentLength); + } + + response.writeHead(200); + } + + if (request.method == "GET") { + if (resource.mimeType() != "text/html") { + response.send(resource); + } else { + const auto html = injectHTMLPreload( + this->core, + userConfig, + resource.str(), + this->preload + ); + + response.setHeader("content-type", "text/html"); + response.setHeader("content-length", html.size()); + response.writeHead(200); + response.write(html); + } + } + } + + return callback(response); + } + + if (router->core->serviceWorker.registrations.size() > 0) { + const auto fetch = ServiceWorkerContainer::FetchRequest { + request.method, + request.scheme, + request.hostname, + request.pathname, + request.query, + request.headers, + ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, + ServiceWorkerContainer::Client { request.client.id, router->bridge->preload } + }; + + const auto fetched = router->core->serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { + if (!request.isActive()) { + return; + } + + response.writeHead(res.statusCode, res.headers); + response.write(res.buffer.size, res.buffer.bytes); + callback(response); + }); + + if (fetched) { + router->bridge->core->setTimeout(32000, [request] () mutable { + if (request.isActive()) { + auto response = SchemeHandlers::Response(request, 408); + response.fail("ServiceWorker request timed out."); + } + }); + return; + } + } + + response.writeHead(404); + return callback(response); + } + + // module or stdlib import/fetch `socket:/` which will just + // proxy an import into a normal resource request above + if (request.hostname.size() == 0) { + auto pathname = request.pathname; + + if (!pathname.ends_with(".js")) { + pathname += ".js"; + } + + if (!pathname.starts_with("/")) { + pathname = "/" + pathname; + } + + resourcePath = applicationResources + "/socket" + pathname; + contentLocation = "/socket" + pathname; + + auto resource = FileResource(resourcePath); + + if (resource.exists()) { + const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; + const auto module = tmpl(moduleTemplate, Map {{"url", url}}); + const auto contentType = resource.mimeType(); + + if (contentType.size() > 0) { + response.setHeader("content-type", contentType); + } + + response.setHeader("content-length", module.size()); + + if (contentLocation.size() > 0) { + response.setHeader("content-location", contentLocation); + } + + response.writeHead(200); + response.write(trim(module)); + } + + return callback(response); + } + + response.writeHead(404); + callback(response); + }); + + this->schemeHandlers.registerSchemeHandler("node", [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + auto userConfig = this->userConfig; + auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + // the location of static application resources + const auto applicationResources = FileResource::getResourcesPath().string(); + // default response is 404 + auto response = SchemeHandlers::Response(request, 404); + + // the resouce path that may be request + String resourcePath; + + // the content location relative to the request origin + String contentLocation; + + // module or stdlib import/fetch `socket:/` which will just + // proxy an import into a normal resource request above + if (request.hostname.size() == 0) { + static const auto allowedNodeCoreModules = this->getAllowedNodeCoreModules(); + const auto isAllowedNodeCoreModule = allowedNodeCoreModules.end() != std::find( + allowedNodeCoreModules.begin(), + allowedNodeCoreModules.end(), + request.pathname.substr(1) + ); + + if (!isAllowedNodeCoreModule) { + response.writeHead(404); + return callback(response); + } + + auto pathname = request.pathname; + + if (!pathname.ends_with(".js")) { + pathname += ".js"; + } + + if (!pathname.starts_with("/")) { + pathname = "/" + pathname; + } + + contentLocation = "/socket" + pathname; + resourcePath = applicationResources + contentLocation; + + auto resource = FileResource(resourcePath); + + if (!resource.exists()) { + if (!pathname.ends_with(".js")) { + pathname = request.pathname; + + if (!pathname.starts_with("/")) { + pathname = "/" + pathname; + } + + if (pathname.ends_with("/")) { + pathname = pathname.substr(0, pathname.size() - 1); + } + + contentLocation = "/socket" + pathname + "/index.js"; + resourcePath = applicationResources + contentLocation; + } + + resource = FileResource(resourcePath); + } + + if (resource.exists()) { + const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; + const auto module = tmpl(moduleTemplate, Map {{"url", url}}); + const auto contentType = resource.mimeType(); + + if (contentType.size() > 0) { + response.setHeader("content-type", contentType); + } + + response.setHeader("content-length", module.size()); + + if (contentLocation.size() > 0) { + response.setHeader("content-location", contentLocation); + } + + response.writeHead(200); + response.write(trim(module)); + } + + return callback(response); + } + + response.writeHead(404); + callback(response); + }); + + Map protocolHandlers = { + {"npm", "/socket/npm/service-worker.js"} + }; + + for (const auto& entry : split(this->userConfig["webview_protocol-handlers"], " ")) { + const auto scheme = replace(trim(entry), ":", ""); + if (this->core->protocolHandlers.registerHandler(scheme)) { + protocolHandlers.insert_or_assign(scheme, ""); + } + } + + for (const auto& entry : this->userConfig) { + const auto& key = entry.first; + if (key.starts_with("webview_protocol-handlers_")) { + const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; + const auto data = entry.second; + if (this->core->protocolHandlers.registerHandler(scheme, { data })) { + protocolHandlers.insert_or_assign(scheme, data); + } + } + } + + for (const auto& entry : protocolHandlers) { + const auto& scheme = entry.first; + const auto id = rand64(); + + auto scriptURL = trim(entry.second); + + if (scriptURL.size() == 0) { + continue; + } + + if (!scriptURL.starts_with(".") && !scriptURL.starts_with("/")) { + continue; + } + + if (scriptURL.starts_with(".")) { + scriptURL = scriptURL.substr(1, scriptURL.size()); + } + + String scope = "/"; + + auto scopeParts = split(scriptURL, "/"); + if (scopeParts.size() > 0) { + scopeParts = Vector(scopeParts.begin(), scopeParts.end() - 1); + scope = join(scopeParts, "/"); + } + + scriptURL = ( + #if SSC_PLATFORM_ANDROID + "https://" + + #else + "socket://" + + #endif + this->userConfig["meta_bundle_identifier"] + + scriptURL + ); + + this->core->serviceWorker.registerServiceWorker({ + .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, + .scope = scope, + .scriptURL = scriptURL, + .scheme = scheme, + .id = id + }); + + this->schemeHandlers.registerSchemeHandler(scheme, [this]( + const auto& request, + const auto router, + auto& callbacks, + auto callback + ) { + if (this->core->serviceWorker.registrations.size() > 0) { + auto hostname = request.hostname; + auto pathname = request.pathname; + + if (request.scheme == "npm") { + hostname = this->userConfig["meta_bundle_identifier"]; + } + + const auto scope = this->core->protocolHandlers.getServiceWorkerScope(request.scheme); + + if (scope.size() > 0) { + pathname = scope + pathname; + } + + const auto fetch = ServiceWorkerContainer::FetchRequest { + request.method, + request.scheme, + hostname, + pathname, + request.query, + request.headers, + ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, + ServiceWorkerContainer::Client { request.client.id, router->bridge->preload } + }; + + const auto fetched = this->core->serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { + if (!request.isActive()) { + return; + } + + auto response = SchemeHandlers::Response(request); + response.writeHead(res.statusCode, res.headers); + response.write(res.buffer.size, res.buffer.bytes); + callback(response); + }); + + if (fetched) { + this->core->setTimeout(32000, [request] () mutable { + if (request.isActive()) { + auto response = SchemeHandlers::Response(request, 408); + response.fail("Protocol handler ServiceWorker request timed out."); + } + }); + return; + } + } + + auto response = SchemeHandlers::Response(request); + response.writeHead(404); + callback(response); + }); + } + } } diff --git a/src/ipc/bridge.hh b/src/ipc/bridge.hh index f66490603a..35aeaea8ee 100644 --- a/src/ipc/bridge.hh +++ b/src/ipc/bridge.hh @@ -4,6 +4,7 @@ #include "../core/core.hh" #include "navigator.hh" #include "router.hh" +#include "scheme_handlers.hh" namespace SSC::IPC { class Bridge { @@ -18,6 +19,7 @@ namespace SSC::IPC { Bluetooth bluetooth; Navigator navigator; + SchemeHandlers schemeHandlers; Router router; Map userConfig = getUserConfig(); @@ -40,6 +42,7 @@ namespace SSC::IPC { ); const Vector& getAllowedNodeCoreModules () const; + void configureHandlers (const SchemeHandlers::Configuration& configuration); }; } #endif diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index 98c2403473..beed648222 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -68,6 +68,75 @@ namespace SSC::IPC { #endif } + #if SSC_PLATFORM_LINUX + void Navigator::configureWebView (WebKitWebView* webview) { + g_signal_connect( + G_OBJECT(webview), + "decide-policy", + G_CALLBACK((+[]( + WebKitWebView* webview, + WebKitPolicyDecision* decision, + WebKitPolicyDecisionType decisionType, + gpointer userData + ) { + auto navigator = reinterpret_cast(userData); + + if (decisionType != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { + webkit_policy_decision_use(decision); + return true; + } + + const auto navigation = WEBKIT_NAVIGATION_POLICY_DECISION(decision); + const auto action = webkit_navigation_policy_decision_get_navigation_action(navigation); + const auto request = webkit_navigation_action_get_request(action); + const auto currentURL = String(webkit_web_view_get_uri(webview)); + const auto requestedURL = String(webkit_uri_request_get_uri(request) + + if (!navigator->handleNavigationRequest(currentURL, requestedURL)) { + webkit_policy_decision_ignore(decision); + return false; + } + + return true; + })), + this + ); + } + #elif SSC_PLATFORM_WINDOWS + void Navigator::configureWebView (ICoreWebView2* webview) { + EventRegistrationToken tokenNavigation; + webview->add_NavigationStarting( + Microsoft::WRL::Callback( + [this, &](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs *event) { + PWSTR source; + PWSTR uri; + + event->get_Uri(&uri); + webview->get_Source(&source); + + if (uri == nullptr || source == nullptr) { + if (uri) CoTaskMemFree(uri); + if (source) CoTaskMemFree(source); + return E_POIINTER; + } + + const auto requestedURL = convertWStringToString(uri); + const auto currentURL = convertWStringToString(source); + + if (!this->handleNavigationRequest(currentURL, requestedURL)) { + event->put_Cancel(true); + } + + CoTaskMemFree(uri); + CoTaskMemFree(source); + return S_OK; + } + ).Get(), + &tokenNavigation + ); + } +#endif + bool Navigator::handleNavigationRequest ( const String& currentURL, const String& requestedURL @@ -75,7 +144,7 @@ namespace SSC::IPC { auto userConfig = this->bridge->userConfig; const auto links = parseStringList(userConfig["meta_application_links"], ' '); const auto applinks = parseStringList(userConfig["meta_application_links"], ' '); - const auto currentURLComponents = Router::parseURLComponents(currentURL); + const auto currentURLComponents = URL::Components::parse(currentURL); bool hasAppLink = false; if (applinks.size() > 0 && currentURLComponents.authority.size() > 0) { diff --git a/src/ipc/navigator.hh b/src/ipc/navigator.hh index 5c6a3d8efe..3c44bdf53f 100644 --- a/src/ipc/navigator.hh +++ b/src/ipc/navigator.hh @@ -45,6 +45,10 @@ namespace SSC::IPC { bool handleNavigationRequest (const String& currentURL, const String& requestedURL); bool isNavigationRequestAllowed (const String& location, const String& requestURL); + + #if SSC_PLATFORM_LINUX + void configureWebView (WebKitWebView* object); + #endif }; } diff --git a/src/ipc/router.cc b/src/ipc/router.cc index ca0751844f..3bfe94d0e7 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -1,17 +1,6 @@ #include "bridge.hh" #include "router.hh" -// create a proxy module so imports of the module of concern are imported -// exactly once at the canonical URL (file:///...) in contrast to module -// URLs (socket:...) - -static constexpr auto moduleTemplate = -R"S( -import module from '{{url}}' -export * from '{{url}}' -export default module -)S"; - namespace SSC::IPC { /* . @@ -80,88 +69,14 @@ namespace SSC::IPC { return Router::WebViewURLPathResolution{}; }; - Router::WebViewURLComponents Router::parseURLComponents (const SSC::String& url) { - Router::WebViewURLComponents components; - components.originalURL = url; - auto input = url; - - if (input.starts_with("./")) { - input = input.substr(1); - } - - if (!input.starts_with("/")) { - const auto colon = input.find(':'); - - if (colon != String::npos) { - components.scheme = input.substr(0, colon); - input = input.substr(colon + 1, input.size()); - - if (input.starts_with("//")) { - input = input.substr(2, input.size()); - - const auto slash = input.find("/"); - if (slash != String::npos) { - components.authority = input.substr(0, slash); - input = input.substr(slash, input.size()); - } else { - const auto questionMark = input.find("?"); - const auto fragment = input.find("#"); - if (questionMark != String::npos & fragment != String::npos) { - if (questionMark < fragment) { - components.authority = input.substr(0, questionMark); - input = input.substr(questionMark, input.size()); - } else { - components.authority = input.substr(0, fragment); - input = input.substr(fragment, input.size()); - } - } else if (questionMark != String::npos) { - components.authority = input.substr(0, questionMark); - input = input.substr(questionMark, input.size()); - } else if (fragment != String::npos) { - components.authority = input.substr(0, fragment); - input = input.substr(fragment, input.size()); - } - } - } - } - } - - input = decodeURIComponent(input); - - const auto questionMark = input.find("?"); - const auto fragment = input.find("#"); - - if (questionMark != String::npos && fragment != String::npos) { - if (questionMark < fragment) { - components.pathname = input.substr(0, questionMark); - components.query = input.substr(questionMark + 1, fragment - questionMark - 1); - components.fragment = input.substr(fragment + 1, input.size()); - } else { - components.pathname = input.substr(0, fragment); - components.fragment = input.substr(fragment + 1, input.size()); - } - } else if (questionMark != String::npos) { - components.pathname = input.substr(0, questionMark); - components.query = input.substr(questionMark + 1, input.size()); - } else if (fragment != String::npos) { - components.pathname = input.substr(0, fragment); - components.fragment = input.substr(fragment + 1, input.size()); - } else { - components.pathname = input; - } - - if (!components.pathname.starts_with("/")) { - components.pathname = "/" + components.pathname; - } - - return components; - } - static const Map getWebViewNavigatorMounts () { static const auto userConfig = getUserConfig(); - #if defined(_WIN32) + static Map mounts; + + // determine HOME + #if SSC_PLATFORM_WINDOWS static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); - #elif defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) + #elif SSC_PLATFORM_IOS || SSC_PLATFORM_IOS_SIMULATOR static const auto HOME = String(NSHomeDirectory().UTF8String); #else static const auto uid = getuid(); @@ -171,8 +86,6 @@ namespace SSC::IPC { : Env::get("HOME", getcwd()); #endif - static Map mounts; - if (mounts.size() > 0) { return mounts; } @@ -269,8 +182,6 @@ namespace SSC::IPC { return WebViewNavigatorMount {}; } - Router::Router () : schemeHandlers(this) {} - void Router::init (Bridge* bridge) { this->bridge = bridge; @@ -278,17 +189,11 @@ namespace SSC::IPC { this->preserveCurrentTable(); } - Router::~Router () { - } - void Router::preserveCurrentTable () { - Lock lock(mutex); this->preserved = this->table; } uint64_t Router::listen (const String& name, MessageCallback callback) { - Lock lock(mutex); - if (!this->listeners.contains(name)) { this->listeners[name] = Vector(); } @@ -300,8 +205,6 @@ namespace SSC::IPC { } bool Router::unlisten (const String& name, uint64_t token) { - Lock lock(mutex); - if (!this->listeners.contains(name)) { return false; } @@ -323,8 +226,6 @@ namespace SSC::IPC { } void Router::map (const String& name, bool async, MessageCallback callback) { - Lock lock(mutex); - String data = name; // URI hostnames are not case sensitive. Convert to lowercase. std::transform(data.begin(), data.end(), data.begin(), @@ -335,8 +236,6 @@ namespace SSC::IPC { } void Router::unmap (const String& name) { - Lock lock(mutex); - String data = name; // URI hostnames are not case sensitive. Convert to lowercase. std::transform(data.begin(), data.end(), data.begin(), @@ -521,14 +420,12 @@ namespace SSC::IPC { } bool Router::hasMappedBuffer (int index, const Message::Seq seq) { - Lock lock(this->mutex); auto key = std::to_string(index) + seq; return this->buffers.find(key) != this->buffers.end(); } MessageBuffer Router::getMappedBuffer (int index, const Message::Seq seq) { if (this->hasMappedBuffer(index, seq)) { - Lock lock(this->mutex); auto key = std::to_string(index) + seq; return this->buffers.at(key); } @@ -541,506 +438,14 @@ namespace SSC::IPC { const Message::Seq seq, MessageBuffer buffer ) { - Lock lock(this->mutex); auto key = std::to_string(index) + seq; this->buffers.insert_or_assign(key, buffer); } void Router::removeMappedBuffer (int index, const Message::Seq seq) { - Lock lock(this->mutex); if (this->hasMappedBuffer(index, seq)) { auto key = std::to_string(index) + seq; this->buffers.erase(key); } } - - void Router::configureHandlers (const SchemeHandlers::Configuration& configuration) { - this->schemeHandlers.configure(configuration); - this->schemeHandlers.registerSchemeHandler("ipc", [this]( - const auto& request, - const auto router, - auto& callbacks, - auto callback - ) { - auto message = Message(request.url(), true); - - // handle special 'ipc://post' case - if (message.name == "post") { - uint64_t id = 0; - - try { - id = std::stoull(message.get("id")); - } catch (...) { - auto response = SchemeHandlers::Response(request, 400); - response.send(JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", "Invalid 'id' given in parameters"} - }} - }); - - callback(response); - return; - } - - if (!this->core->hasPost(id)) { - auto response = SchemeHandlers::Response(request, 404); - response.send(JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", "A 'Post' was not found for the given 'id' in parameters"}, - {"type", "NotFoundError"} - }} - }); - - callback(response); - return; - } - - auto response = SchemeHandlers::Response(request, 200); - const auto post = this->core->getPost(id); - - // handle raw headers in 'Post' object - if (post.headers.size() > 0) { - const auto lines = split(trim(post.headers), '\n'); - for (const auto& line : lines) { - const auto pair = split(trim(line), ':'); - const auto key = trim(pair[0]); - const auto value = trim(pair[1]); - response.setHeader(key, value); - } - } - - response.write(post.length, post.body); - callback(response); - this->core->removePost(id); - return; - } - - message.isHTTP = true; - message.cancel = std::make_shared(); - - callbacks.cancel = [message] () { - if (message.cancel->handler != nullptr) { - message.cancel->handler(message.cancel->data); - } - }; - - const auto size = request.body.size; - const auto bytes = request.body.bytes != nullptr ? *request.body.bytes : nullptr; - const auto invoked = this->invoke(message, bytes, size, [request, message, callback](Result result) { - if (!request.isActive()) { - return; - } - - auto response = SchemeHandlers::Response(request); - - response.setHeaders(result.headers); - - // handle event source streams - if (result.post.eventStream != nullptr) { - response.setHeader("content-type", "text/event-stream"); - response.setHeader("cache-control", "no-store"); - *result.post.eventStream = [request, response, message, callback]( - const char* name, - const char* data, - bool finished - ) mutable { - if (request.isCancelled()) { - if (message.cancel->handler != nullptr) { - message.cancel->handler(message.cancel->data); - } - return false; - } - - response.writeHead(200); - - const auto event = SchemeHandlers::Response::Event { name, data }; - - if (event.count() > 0) { - response.write(event.str()); - } - - if (finished) { - callback(response); - } - - return true; - }; - return; - } - - // handle chunk streams - if (result.post.chunkStream != nullptr) { - response.setHeader("transfer-encoding", "chunked"); - *result.post.chunkStream = [request, response, message, callback]( - const char* chunk, - size_t size, - bool finished - ) mutable { - if (request.isCancelled()) { - if (message.cancel->handler != nullptr) { - message.cancel->handler(message.cancel->data); - } - return false; - } - - response.writeHead(200); - response.write(size, chunk); - - if (finished) { - callback(response); - } - - return true; - }; - return; - } - - if (result.post.body != nullptr) { - response.write(result.post.length, result.post.body); - } else { - response.write(result.json()); - } - - callback(response); - }); - - if (!invoked) { - auto response = SchemeHandlers::Response(request, 404); - response.send(JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"}, - {"url", request.url()} - }} - }); - - callback(response); - } - }); - - this->schemeHandlers.registerSchemeHandler("socket", [this]( - const auto& request, - const auto router, - auto& callbacks, - auto callback - ) { - auto userConfig = this->bridge->userConfig; - auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - // the location of static application resources - const auto applicationResources = FileResource::getResourcesPath().string(); - // default response is 404 - auto response = SchemeHandlers::Response(request, 404); - - // the resouce path that may be request - String resourcePath; - - // the content location relative to the request origin - String contentLocation; - - // application resource or service worker request at `socket:///*` - if (request.hostname == bundleIdentifier) { - const auto resolved = Router::resolveURLPathForWebView(request.pathname, applicationResources); - const auto mount = Router::resolveNavigatorMountForWebView(request.pathname); - - if (mount.resolution.redirect || resolved.redirect) { - auto pathname = mount.resolution.redirect - ? mount.resolution.pathname - : resolved.pathname; - - if (request.method == "GET") { - auto location = mount.resolution.pathname; - if (request.query.size() > 0) { - location += "?" + request.query; - } - - if (request.fragment.size() > 0) { - location += "#" + request.fragment; - } - - response.redirect(location); - return callback(response); - } - } else if (mount.path.size() > 0) { - resourcePath = mount.path; - } else if (request.pathname == "" || request.pathname == "/") { - if (userConfig.contains("webview_default_index")) { - resourcePath = userConfig["webview_default_index"]; - if (resourcePath.starts_with("./")) { - resourcePath = applicationResources + resourcePath.substr(1); - } else if (resourcePath.starts_with("/")) { - resourcePath = applicationResources + resourcePath; - } else { - resourcePath = applicationResources + + "/" + resourcePath; - } - } - } - - if (resourcePath.size() == 0 && resolved.pathname.size() > 0) { - resourcePath = applicationResources + resolved.pathname; - } - - // handle HEAD and GET requests for a file resource - if (resourcePath.size() > 0) { - contentLocation = replace(resourcePath, applicationResources, ""); - - auto resource = FileResource(resourcePath); - - if (!resource.exists()) { - response.writeHead(404); - } else { - if (contentLocation.size() > 0) { - response.setHeader("content-location", contentLocation); - } - - if (request.method == "OPTIONS") { - response.setHeader("access-control-allow-origin", "*"); - response.setHeader("access-control-allow-methods", "GET, HEAD"); - response.setHeader("access-control-allow-headers", "*"); - response.setHeader("access-control-allow-credentials", "true"); - response.writeHead(200); - } - - if (request.method == "HEAD") { - const auto contentType = resource.mimeType(); - const auto contentLength = resource.size(); - - if (contentType.size() > 0) { - response.setHeader("content-type", contentType); - } - - if (contentLength > 0) { - response.setHeader("content-length", contentLength); - } - - response.writeHead(200); - } - - if (request.method == "GET") { - if (resource.mimeType() != "text/html") { - response.send(resource); - } else { - const auto html = injectHTMLPreload( - this->core, - userConfig, - resource.string(), - this->bridge->preload - ); - - response.setHeader("content-type", "text/html"); - response.setHeader("content-length", html.size()); - response.writeHead(200); - response.write(html); - } - } - } - - return callback(response); - } - - if (router->core->serviceWorker.registrations.size() > 0) { - const auto fetch = ServiceWorkerContainer::FetchRequest { - request.method, - request.scheme, - request.hostname, - request.pathname, - request.query, - request.headers, - ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, - ServiceWorkerContainer::Client { request.client.id } - }; - - const auto fetched = router->core->serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { - if (!request.isActive()) { - return; - } - - response.writeHead(res.statusCode, res.headers); - response.write(res.buffer.size, res.buffer.bytes); - callback(response); - }); - - if (fetched) { - router->bridge->core->setTimeout(32000, [request] () mutable { - if (request.isActive()) { - auto response = SchemeHandlers::Response(request, 408); - response.fail("ServiceWorker request timed out."); - } - }); - return; - } - } - - response.writeHead(404); - return callback(response); - } - - // module or stdlib import/fetch `socket:/` which will just - // proxy an import into a normal resource request above - if (request.hostname.size() == 0) { - auto pathname = request.pathname; - - if (!pathname.ends_with(".js")) { - pathname += ".js"; - } - - if (!pathname.starts_with("/")) { - pathname = "/" + pathname; - } - - resourcePath = applicationResources + "/socket" + pathname; - contentLocation = "/socket" + pathname; - - auto resource = FileResource(resourcePath); - - if (resource.exists()) { - const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; - const auto module = tmpl(moduleTemplate, Map {{"url", url}}); - const auto contentType = resource.mimeType(); - - if (contentType.size() > 0) { - response.setHeader("content-type", contentType); - } - - response.setHeader("content-length", module.size()); - - if (contentLocation.size() > 0) { - response.setHeader("content-location", contentLocation); - } - - response.writeHead(200); - response.write(trim(module)); - } - - return callback(response); - } - - response.writeHead(404); - callback(response); - }); - - Map protocolHandlers = { - {"npm", "/socket/npm/service-worker.js"} - }; - - for (const auto& entry : split(this->bridge->userConfig["webview_protocol-handlers"], " ")) { - const auto scheme = replace(trim(entry), ":", ""); - if (this->bridge->core->protocolHandlers.registerHandler(scheme)) { - protocolHandlers.insert_or_assign(scheme, ""); - } - } - - for (const auto& entry : this->bridge->userConfig) { - const auto& key = entry.first; - if (key.starts_with("webview_protocol-handlers_")) { - const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; - const auto data = entry.second; - if (this->bridge->core->protocolHandlers.registerHandler(scheme, { data })) { - protocolHandlers.insert_or_assign(scheme, data); - } - } - } - - for (const auto& entry : protocolHandlers) { - const auto& scheme = entry.first; - const auto id = rand64(); - - auto scriptURL = trim(entry.second); - - if (scriptURL.size() == 0) { - continue; - } - - if (!scriptURL.starts_with(".") && !scriptURL.starts_with("/")) { - continue; - } - - if (scriptURL.starts_with(".")) { - scriptURL = scriptURL.substr(1, scriptURL.size()); - } - - String scope = "/"; - - auto scopeParts = split(scriptURL, "/"); - if (scopeParts.size() > 0) { - scopeParts = Vector(scopeParts.begin(), scopeParts.end() - 1); - scope = join(scopeParts, "/"); - } - - scriptURL = ( - #if SSC_PLATFORM_ANDROID - "https://" + - #else - "socket://" + - #endif - bridge->userConfig["meta_bundle_identifier"] + - scriptURL - ); - - this->bridge->core->serviceWorker.registerServiceWorker({ - .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, - .scope = scope, - .scriptURL = scriptURL, - .scheme = scheme, - .id = id - }); - - this->schemeHandlers.registerSchemeHandler(scheme, [this]( - const auto& request, - const auto router, - auto& callbacks, - auto callback - ) { - if (this->core->serviceWorker.registrations.size() > 0) { - auto hostname = request.hostname; - auto pathname = request.pathname; - - if (request.scheme == "npm") { - hostname = this->bridge->userConfig["meta_bundle_identifier"]; - } - - const auto scope = this->core->protocolHandlers.getServiceWorkerScope(request.scheme); - - if (scope.size() > 0) { - pathname = scope + pathname; - } - - const auto fetch = ServiceWorkerContainer::FetchRequest { - request.method, - request.scheme, - hostname, - pathname, - request.query, - request.headers, - ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, - ServiceWorkerContainer::Client { request.client.id } - }; - - const auto fetched = this->core->serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { - if (!request.isActive()) { - return; - } - - auto response = SchemeHandlers::Response(request); - response.writeHead(res.statusCode, res.headers); - response.write(res.buffer.size, res.buffer.bytes); - callback(response); - }); - - if (fetched) { - this->bridge->core->setTimeout(32000, [request] () mutable { - if (request.isActive()) { - auto response = SchemeHandlers::Response(request, 408); - response.fail("Protocol handler ServiceWorker request timed out."); - } - }); - return; - } - } - - auto response = SchemeHandlers::Response(request); - response.writeHead(404); - callback(response); - }); - } - } } diff --git a/src/ipc/router.hh b/src/ipc/router.hh index 75a7e99c89..a2e6ac5c78 100644 --- a/src/ipc/router.hh +++ b/src/ipc/router.hh @@ -2,7 +2,6 @@ #define SSC_IPC_ROUTER_H #include "../core/core.hh" -#include "scheme_handlers.hh" #include "message.hh" #include "result.hh" @@ -48,24 +47,9 @@ namespace SSC::IPC { String route; // root path in webview navigator }; - struct WebViewURLComponents { - String originalURL; - String scheme = ""; - String authority = ""; - String pathname; - String query; - String fragment; - }; - - static WebViewURLComponents parseURLComponents (const String& url); static WebViewURLPathResolution resolveURLPathForWebView (String inputPath, const String& basePath); static WebViewNavigatorMount resolveNavigatorMountForWebView (const String& path); - #if defined(__APPLE__) - static Mutex notificationMapMutex; - static std::map notificationMap; - #endif - private: Table preserved; @@ -79,18 +63,17 @@ namespace SSC::IPC { Table table; Location location; - SchemeHandlers schemeHandlers; Core *core = nullptr; Bridge *bridge = nullptr; - Router (); - Router (const Router &) = delete; - ~Router (); + Router () = default; + Router (const Router&) = delete; + Router (const Router&&) = delete; + Router (Router&&) = delete; void init (); void init (Bridge* bridge); - void configureHandlers (const SchemeHandlers::Configuration& configuration); MessageBuffer getMappedBuffer (int index, const Message::Seq seq); bool hasMappedBuffer (int index, const Message::Seq seq); diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 2602f7af95..5e0d506707 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1374,7 +1374,6 @@ static void mapIPCRoutes (Router *router) { #endif }); -#if defined(__APPLE__) router->map("notification.show", [=](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, { "id", @@ -1385,268 +1384,54 @@ static void mapIPCRoutes (Router *router) { return reply(Result::Err { message, err }); } - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - auto attachments = [NSMutableArray new]; - auto userInfo = [NSMutableDictionary new]; - auto content = [UNMutableNotificationContent new]; - auto __block id = message.get("id"); - - if (message.has("tag")) { - userInfo[@"tag"] = @(message.get("tag").c_str()); - content.threadIdentifier = @(message.get("tag").c_str()); - } - - if (message.has("lang")) { - userInfo[@"lang"] = @(message.get("lang").c_str()); - } - - if (!message.has("silent") && message.get("silent") == "false") { - content.sound = [UNNotificationSound defaultSound]; - } - - if (message.has("icon")) { - NSError* error = nullptr; - auto url = [NSURL URLWithString: @(message.get("icon").c_str())]; - - if (message.get("icon").starts_with("socket://")) { - url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", url.path.UTF8String - #else - stringWithFormat: @"/%s", url.path.UTF8String - #endif - ] - ]]; - - url = [NSURL fileURLWithPath: url.path]; - } - - auto types = [UTType - typesWithTag: url.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary new]; - - if (types.count > 0) { - options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; - }; - - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: url - options: options - error: &error - ]; - - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { { "message", message } }; - return reply(Result::Err { message, err }); - } - - [attachments addObject: attachment]; - } else { - // using an asset from the resources directory will require a code signed application - #if SSC_PLATFORM_SANDBOXED - NSError* error = nullptr; - auto url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/icon.png" - #else - stringWithFormat: @"/icon.png" - #endif - ] - ]]; - - url = [NSURL fileURLWithPath: url.path]; - - auto types = [UTType - typesWithTag: url.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary new]; - - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: url - options: options - error: &error - ]; - - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { { "message", message } }; - - return reply(Result::Err { message, err }); - } - - [attachments addObject: attachment]; - #endif - } - - if (message.has("image")) { - NSError* error = nullptr; - auto url = [NSURL URLWithString: @(message.get("image").c_str())]; - - if (message.get("image").starts_with("socket://")) { - url = [NSURL URLWithString: [NSBundle.mainBundle.resourcePath - stringByAppendingPathComponent: [NSString - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - stringWithFormat: @"/ui/%s", url.path.UTF8String - #else - stringWithFormat: @"/%s", url.path.UTF8String - #endif - ] - ]]; - - url = [NSURL fileURLWithPath: url.path]; - } - - auto types = [UTType - typesWithTag: url.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary new]; - - if (types.count > 0) { - options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; - }; - - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: url - options: options - error: &error - ]; - - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - auto err = JSON::Object::Entries {{ "message", message }}; + const auto options = Notifications::ShowOptions { + message.get("id"), + message.get("title"), + message.get("tag"), + message.get("lang"), + message.get("silent") == "true", + message.get("icon"), + message.get("image"), + message.get("body") + }; + router->bridge->core->notifications.show(options, [=] (const auto result) { + if (result.error.size() > 0) { + const auto err = JSON::Object::Entries {{ "message", result.error }}; return reply(Result::Err { message, err }); } - [attachments addObject: attachment]; - } - - content.attachments = attachments; - content.userInfo = userInfo; - content.title = @(message.get("title").c_str()); - content.body = @(message.get("body", "").c_str()); - - auto request = [UNNotificationRequest - requestWithIdentifier: @(id.c_str()) - content: content - trigger: nil - ]; - - { - Lock lock(Router::notificationMapMutex); - Router::notificationMap.insert_or_assign(id, router); - } - - [notificationCenter addNotificationRequest: request withCompletionHandler: ^(NSError* error) { - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { - { "message", message } - }; - - reply(Result::Err { message, err }); - Lock lock(Router::notificationMapMutex); - Router::notificationMap.erase(id); - return; - } - - reply(Result { message.seq, message, JSON::Object::Entries { - {"id", request.identifier.UTF8String} - }}); - }]; + const auto data = JSON::Object::Entries {{"id", result.notification.identifier}}; + reply(Result::Data { message, data }); + }); }); router->map("notification.close", [=](auto message, auto router, auto reply) { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; auto err = validateMessageParameters(message, { "id" }); if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); } - auto id = message.get("id"); - auto identifiers = @[@(id.c_str())]; - [notificationCenter removePendingNotificationRequestsWithIdentifiers: identifiers]; - [notificationCenter removeDeliveredNotificationsWithIdentifiers: identifiers]; + const auto notification = Notifications::Notification(message.get("id")); + router->core->notifications.close(notification); reply(Result { message.seq, message, JSON::Object::Entries { - {"id", id} + {"id", notification.identifier} }}); - - Lock lock(Router::notificationMapMutex); - if (Router::notificationMap.contains(id)) { - auto notificationRouter = Router::notificationMap.at(id); - JSON::Object json = JSON::Object::Entries { - {"id", id}, - {"action", "dismiss"} - }; - - notificationRouter->emit("notificationresponse", json.str()); - Router::notificationMap.erase(id); - } }); router->map("notification.list", [=](auto message, auto router, auto reply) { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [notificationCenter getDeliveredNotificationsWithCompletionHandler: ^(NSArray *notifications) { - JSON::Array::Entries entries; - - Lock lock(Router::notificationMapMutex); - for (UNNotification* notification in notifications) { - auto id = String(notification.request.identifier.UTF8String); - - if ( - !Router::notificationMap.contains(id) || - Router::notificationMap.at(id) != router - ) { - continue; - } - - entries.push_back(JSON::Object::Entries { - {"id", id} - }); + router->core->notifications.list([=](const auto notifications) { + JSON::Array entries; + for (const auto& notification : notifications) { + entries.push(notification.json()); } - reply(Result { message.seq, message, entries }); - }]; + reply(Result::Data { message.seq, entries, }); + }); }); -#endif /** * Read or modify the `SEND_BUFFER` or `RECV_BUFFER` for a peer socket. @@ -1735,6 +1520,7 @@ static void mapIPCRoutes (Router *router) { String home; String data; String log; + String tmp = fs::temp_directory_path().string(); #if defined(__APPLE__) static const auto uid = getuid(); @@ -1767,6 +1553,7 @@ static void mapIPCRoutes (Router *router) { home = String(NSHomeDirectory().UTF8String); data = HOME + "/Library/Application Support/" + bundleIdentifier; log = HOME + "/Library/Logs/" + bundleIdentifier; + tmp = String(NSTemporaryDirectory().UTF8String); #undef DIRECTORY_PATH_FROM_FILE_MANAGER @@ -1857,6 +1644,7 @@ static void mapIPCRoutes (Router *router) { json["home"] = home; json["data"] = data; json["log"] = log; + json["tmp"] = tmp; return reply(Result::Data { message, json }); }); @@ -2070,7 +1858,7 @@ static void mapIPCRoutes (Router *router) { tmp = replace(tmp, "socket://", ""); tmp = replace(tmp, "https://", ""); tmp = replace(tmp, userConfig["meta_bundle_identifier"], ""); - auto parsed = Router::parseURLComponents(tmp); + const auto parsed = URL::Components::parse(tmp); router->location.pathname = parsed.pathname; router->location.query = parsed.query; } diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 2334376868..c04d78e6d5 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -2,6 +2,14 @@ #include "ipc.hh" using namespace SSC; +using namespace SSC::IPC; + +namespace SSC::IPC { + static struct { + SchemeHandlers::RequestMap map; + Mutex mutex; + } requests; +} #if SSC_PLATFORM_APPLE using Task = id; @@ -47,12 +55,11 @@ using Task = id; - (void) webView: (SSCBridgedWebView*) webview stopURLSchemeTask: (Task) task { - Lock lock(mutex); - + Lock lock(requests.mutex); if (tasks.contains(task)) { const auto id = tasks[task]; - if (self.handlers->requests.contains(id)) { - auto& request = self.handlers->requests.at(id); + if (requests.map.contains(id)) { + auto& request = requests.map.at(id); request.cancelled = true; if (request.callbacks.cancel != nullptr) { request.callbacks.cancel(); @@ -66,6 +73,20 @@ using Task = id; - (void) webView: (SSCBridgedWebView*) webview startURLSchemeTask: (Task) task { + if (self.handlers == nullptr) { + static auto userConfig = SSC::getUserConfig(); + const auto bundleIdentifier = userConfig.contains("meta_bundle_identifier") + ? userConfig.at("meta_bundle_identifier") + : ""; + + [task didFailWithError: [NSError + errorWithDomain: @(bundleIdentifier.c_str()) + code: 1 + userInfo: @{NSLocalizedDescriptionKey: @("Request is invalid state")} + ]]; + return; + } + auto request = IPC::SchemeHandlers::Request::Builder(self.handlers, task) .setMethod(toUpperCase(task.request.HTTPMethod.UTF8String)) // copies all headers @@ -161,6 +182,7 @@ namespace SSC::IPC { ~SchemeHandlersInternals () { #if SSC_PLATFORM_APPLE if (this->schemeHandler != nullptr) { + this->schemeHandler.handlers = nullptr; #if !__has_feature(objc_arc) [this->schemeHandler release]; #endif @@ -175,6 +197,7 @@ namespace SSC::IPC { #if SSC_PLATFORM_LINUX static Set globallyRegisteredSchemesForLinux; #endif + static const std::map STATUS_CODES = { {100, "Continue"}, {101, "Switching Protocols"}, @@ -254,17 +277,33 @@ namespace SSC::IPC { } void SchemeHandlers::configure (const Configuration& configuration) { + static const auto devHost = SSC::getDevHost(); this->configuration = configuration; + #if SSC_PLATFORM_APPLE + if (SSC::isDebugEnabled() && devHost.starts_with("http:")) { + [configuration.webview.processPool + performSelector: @selector(_registerURLSchemeAsSecure:) + withObject: @"http" + ]; + } + #elif SSC_PLATFORM_LINUX + if (SSC::isDebugEnabled() && devHost.starts_with("http:")) { + auto webContext = webkit_web_context_get_default(); + auto security = webkit_web_context_get_security_manager(webContext); + + webkit_security_manager_register_uri_scheme_as_display_isolated(security, "http"); + webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "http"); + webkit_security_manager_register_uri_scheme_as_secure(security, "http"); + webkit_security_manager_register_uri_scheme_as_local(security, "http"); + } + #endif } bool SchemeHandlers::hasHandlerForScheme (const String& scheme) { - Lock lock(this->mutex); return this->handlers.contains(scheme); } bool SchemeHandlers::registerSchemeHandler (const String& scheme, const Handler& handler) { - Lock lock(this->mutex); - if (scheme.size() == 0 || this->hasHandlerForScheme(scheme)) { return false; } @@ -304,6 +343,40 @@ namespace SSC::IPC { ); } #elif SSC_PLATFORM_WINDOWS + static const int MAX_ALLOWED_SCHEME_ORIGINS = 64; + static const int MAX_CUSTOM_SCHEME_REGISTRATIONS = 64; + + auto registration = Microsoft::WRL::Make( + convertStringToWString(scheme).c_str() + ); + + Microsoft::WRL::ComPtr options; + ICoreWebView2CustomSchemeRegistration* registrations[MAX_CUSTOM_SCHEME_REGISTRATIONS] = {}; + const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {}; + int i = 0; + + for (const auto& entry : this->handlers) { + allowedOrigins[i++] = convertStringToWString(entry.first + "://*").c_str(); + } + + registration->put_HasAuthorityComponent(TRUE); + registration->SetAllowedOrigins(this->handlers.size(), allowedOrigins); + + this->coreWebView2CustomSchemeRegistrations.insert(registration); + + if (this->configuration.webview.As(&options) != S_OK) { + return false; + } + + for (const auto& registration : this->coreWebView2CustomSchemeRegistrations) { + registrations[registrationsCount++] = registration.Get(); + } + + options->SetCustomSchemeRegistrations( + registrationsCount, + static_cast(registrations) + ); + #endif this->handlers.insert_or_assign(scheme, handler); @@ -314,13 +387,13 @@ namespace SSC::IPC { const Request& request, const HandlerCallback callback ) { + Lock lock(requests.mutex); // request was not finalized, likely not from a `Request::Builder` if (!request.finalized) { return false; } - // already an active request - if (this->requests.contains(request.id)) { + if (requests.map.contains(request.id)) { return false; } @@ -357,7 +430,7 @@ namespace SSC::IPC { return true; } - const auto result = this->requests.insert_or_assign(request.id, std::move(request)); + const auto result = requests.map.insert_or_assign(request.id, std::move(request)); const auto id = request.id; // stored request reference @@ -377,26 +450,38 @@ namespace SSC::IPC { callback(response); } - this->requests.erase(id); + Lock lock(requests.mutex); + requests.map.erase(id); }); return true; } bool SchemeHandlers::isRequestActive (uint64_t id) { - Lock lock(this->mutex); - return this->requests.contains(id); + Lock lock(requests.mutex); + return requests.map.contains(id); } bool SchemeHandlers::isRequestCancelled (uint64_t id) { - Lock lock(this->mutex); + Lock lock(requests.mutex); return ( id > 0 && - this->requests.contains(id) && - this->requests.at(id).cancelled + requests.map.contains(id) && + requests.map.at(id).cancelled ); } + #if SSC_PLATFORM_APPLE + void SchemeHandlers::configureWebView (SSCBridgeWebView* webview) { + } + #elif SSC_PLATFORM_LINUX + void SchemeHandlers::configureWebView (WebKitWebView* webview) { + } + #elif SSC_PLATFORM_WINDOWS + void SchemeHandlers::configureWebView (ICoreWebView2* webview) { + } + #endif + SchemeHandlers::Request::Builder::Builder ( SchemeHandlers* handlers, PlatformRequest platformRequest @@ -413,7 +498,7 @@ namespace SSC::IPC { #endif const auto userConfig = handlers->router->bridge->userConfig; - const auto components = IPC::Router::parseURLComponents(this->absoluteURL); + const auto url = URL::Components::parse(this->absoluteURL); const auto bundleIdentifier = userConfig.contains("meta_bundle_identifier") ? userConfig.at("meta_bundle_identifier") : ""; @@ -422,7 +507,7 @@ namespace SSC::IPC { handlers, platformRequest, Request::Options { - .scheme = components.scheme + .scheme = url.scheme } )); @@ -430,11 +515,11 @@ namespace SSC::IPC { this->request->client.id = handlers->router->bridge->id; // build request URL components from parsed URL components - this->request->originalURL = components.originalURL; - this->request->hostname = components.authority; - this->request->pathname = components.pathname; - this->request->query = components.query; - this->request->fragment = components.fragment; + this->request->originalURL = this->absoluteURL; + this->request->hostname = url.authority; + this->request->pathname = url.pathname; + this->request->query = url.query; + this->request->fragment = url.fragment; } SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setScheme (const String& scheme) { @@ -607,8 +692,7 @@ namespace SSC::IPC { this->platformRequest = platformRequest; } - SchemeHandlers::Request::~Request () { - } + SchemeHandlers::Request::~Request () {} static void copyRequest ( SchemeHandlers::Request* destination, diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 1d32eb7acf..c26b198570 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -4,6 +4,15 @@ #include "../core/core.hh" #include "../core/platform.hh" +#if SSC_PLATFORM_WINDOWS +#include "WebView2.h" +#include "WebView2EnvironmentOptions.h" +#endif + +#if SSC_PLATFORM_APPLE +@class SSCBridgeWebView; +#endif + namespace SSC::IPC { class Router; class SchemeHandlers; @@ -159,6 +168,9 @@ namespace SSC::IPC { Atomic finished = false; SchemeHandlers* handlers = nullptr; PlatformResponse platformResponse = nullptr; + #if SSC_PLATFORM_LINUX + GInputStream* platformResponseStream = nullptr + #endif Response ( const Request& request, @@ -206,23 +218,24 @@ namespace SSC::IPC { RequestCallbacks& callbacks, HandlerCallback )>; + using HandlerMap = std::map; using RequestMap = std::map; struct Configuration { #if SSC_PLATFORM_APPLE WKWebViewConfiguration* webview = nullptr; + #elif + ComPtr webview = nullptr; #endif }; Configuration configuration; Router* router = nullptr; HandlerMap handlers; - RequestMap requests; - Mutex mutex; - #if SSC_PLATFORM_LINUX - GInputStream* platformResponseStream = nullptr + #if SSC_PLATFORM_WINDOWS + Set> coreWebView2CustomSchemeRegistrations; #endif SchemeHandlers (Router* router); @@ -234,6 +247,14 @@ namespace SSC::IPC { bool handleRequest (const Request& request, const HandlerCallback calllback = nullptr); bool isRequestActive (uint64_t id); bool isRequestCancelled (uint64_t id); + + #if SSC_PLATFORM_APPLE + void configureWebView (SSCBridgeWebView* webview); + #elif SSC_PLATFORM_LINUX + void configureWebView (WebKitWebView* webview); + #elif SSC_PLATFORM_WINDOWS + void configureWebView (ICoreWebView2* webview); + #endif }; } From 2f00c3fc5546e743e1f595136d72679ea49df589 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 25 Apr 2024 23:55:00 -0400 Subject: [PATCH 0643/1178] refactor(core/notifications): more core notifications --- src/core/core.hh | 1 + src/core/file_system_watcher.cc | 2 - src/core/file_system_watcher.hh | 3 - src/core/headers.cc | 18 +- src/core/notifications.cc | 323 +++++++++++++++++++++++++++ src/core/notifications.hh | 36 +++ src/core/peer.hh | 10 +- src/core/preload.cc | 34 +-- src/core/protocol_handlers.cc | 2 +- src/core/protocol_handlers.hh | 4 +- src/core/resource.cc | 35 ++- src/core/resource.hh | 10 +- src/core/service_worker_container.cc | 30 +-- src/core/string.cc | 13 ++ src/core/string.hh | 1 + 15 files changed, 444 insertions(+), 78 deletions(-) diff --git a/src/core/core.hh b/src/core/core.hh index 20625c4538..509cd41c2f 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -25,6 +25,7 @@ #include "service_worker_container.hh" #include "string.hh" #include "types.hh" +#include "url.hh" #include "version.hh" namespace SSC { diff --git a/src/core/file_system_watcher.cc b/src/core/file_system_watcher.cc index a3ad341958..306d7589e8 100644 --- a/src/core/file_system_watcher.cc +++ b/src/core/file_system_watcher.cc @@ -121,7 +121,6 @@ namespace SSC { return false; } - Lock lock(this->mutex); this->callback = callback; // a loop may be configured for the instance already, perhaps here or @@ -232,7 +231,6 @@ namespace SSC { } bool FileSystemWatcher::stop () { - Lock lock(this->mutex); if (!this->isRunning) { return false; } diff --git a/src/core/file_system_watcher.hh b/src/core/file_system_watcher.hh index b320ac090a..94bdbf5d91 100644 --- a/src/core/file_system_watcher.hh +++ b/src/core/file_system_watcher.hh @@ -66,9 +66,6 @@ namespace SSC { AtomicBool isRunning = false; Core* core = nullptr; - // thread state - Mutex mutex; - static void handleEventCallback ( EventHandle* handle, const char* filename, diff --git a/src/core/headers.cc b/src/core/headers.cc index 6a696befd5..239ed55e61 100644 --- a/src/core/headers.cc +++ b/src/core/headers.cc @@ -108,13 +108,25 @@ namespace SSC { String Headers::str () const { StringStream headers; - auto count = this->size(); + auto remaining = this->size(); for (const auto& entry : this->entries) { - headers << entry.name << ": " << entry.value.str();; - if (--count > 0) { + auto parts = split(entry.name, '-'); + + std::transform( + parts.begin(), + parts.end(), + parts.begin(), + toProperCase + ); + + const auto name = join(parts, '-'); + + headers << name << ": " << entry.value.str(); + if (--remaining > 0) { headers << "\n"; } } + return headers.str(); } diff --git a/src/core/notifications.cc b/src/core/notifications.cc index d7b577d3d6..d0a5ec7164 100644 --- a/src/core/notifications.cc +++ b/src/core/notifications.cc @@ -1,5 +1,8 @@ #include "notifications.hh" +#include "resource.hh" #include "module.hh" +#include "debug.hh" +#include "url.hh" #if SSC_PLATFORM_APPLE @implementation SSCUserNotificationCenterDelegate @@ -78,6 +81,12 @@ #endif namespace SSC { + const JSON::Object Notifications::Notification::json () const { + return JSON::Object::Entries { + {"id", this->identifier} + }; + } + Notifications::Notifications (Core* core) : Module(core), permissionChangeObservers(), @@ -175,4 +184,318 @@ namespace SSC { bool Notifications::removeNotificationPresentedObserver (const NotificationPresentedObserver& observer) { return this->notificationPresentedObservers.remove(observer); } + + bool Notifications::show (const ShowOptions& options, const ShowCallback callback) { + #if SSC_PLATFORM_APPLE + if (options.id.size() == 0) { + callback(ShowResult { "Missing 'id' in Notifications::ShowOptions" }); + return false; + } + + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + // XXX(@jwerle): release this? + auto attachments = [NSMutableArray new]; + // XXX(@jwerle): release this? + auto userInfo = [NSMutableDictionary new]; + // XXX(@jwerle): release this? + auto content = [UNMutableNotificationContent new]; + auto __block id = options.id; + + if (options.tag.size() > 0) { + userInfo[@"tag"] = @(options.tag.c_str()); + content.threadIdentifier = @(options.tag.c_str()); + } + + if (options.lang.size() > 0) { + userInfo[@"lang"] = @(options.lang.c_str()); + } + + if (options.silent == false) { + content.sound = [UNNotificationSound defaultSound]; + } + + if (options.icon.size() > 0) { + NSError* error = nullptr; + NSURL* iconURL = nullptr; + + const auto url = URL(options.icon); + + if (options.icon.starts_with("socket://")) { + const auto path = FileResource::getResourcePath(url.pathname); + iconURL = [NSURL fileURLWithPath: @(path.string().c_str())]; + } else { + iconURL = [NSURL fileURLWithPath: @(url.href.c_str())]; + } + + const auto types = [UTType + typesWithTag: iconURL.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + // XXX(@jwerle): release this? + auto options = [NSMutableDictionary new]; + + if (types.count > 0) { + options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; + }; + + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: iconURL + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + callback(ShowResult { message }); + return false; + } + + [attachments addObject: attachment]; + } else { + // FIXME(): this define never is true + #if SSC_PLATFORM_SANDBOXED + // using an asset from the resources directory will require a code signed application + NSError* error = nullptr; + const auto path = FileResource::getResourcePath(String("icon.png")); + const auto iconURL = [NSURL fileURLWithPath: @(path.string().c_str())]; + const auto types = [UTType + typesWithTag: iconURL.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + // XXX(@jwerle): release this? + auto options = [NSMutableDictionary new]; + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: iconURL + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + callback(ShowResult { message }); + return false; + } + + [attachments addObject: attachment]; + #endif + } + + if (options.image.size() > 0) { + NSError* error = nullptr; + NSURL* imageURL = nullptr; + + const auto url = URL(options.image); + + if (options.image.starts_with("socket://")) { + const auto path = FileResource::getResourcePath(url.pathname); + imageURL = [NSURL fileURLWithPath: @(path.string().c_str())]; + } else { + imageURL = [NSURL fileURLWithPath: @(url.href.c_str())]; + } + + auto types = [UTType + typesWithTag: imageURL.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + auto options = [NSMutableDictionary new]; + + if (types.count > 0) { + options[UNNotificationAttachmentOptionsTypeHintKey] = types.firstObject.preferredMIMEType; + }; + + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: imageURL + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + callback(ShowResult { message }); + return false; + } + + [attachments addObject: attachment]; + } + + content.attachments = attachments; + content.userInfo = userInfo; + content.title = @(options.title.c_str()); + content.body = @(options.body.c_str()); + + auto request = [UNNotificationRequest + requestWithIdentifier: @(id.c_str()) + content: content + trigger: nil + ]; + + [notificationCenter addNotificationRequest: request withCompletionHandler: ^(NSError* error) { + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + callback(ShowResult { message }); + return; + } + + callback(ShowResult { "", id }); + }]; + #endif + return false; + } + + bool Notifications::close (const Notification& notification) { + #if SSC_PLATFORM_APPLE + const auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + const auto identifiers = @[@(notification.identifier.c_str())]; + const auto json = JSON::Object::Entries { + {"id", notification.identifier}, + {"action", "dismiss"} + }; + + [notificationCenter removePendingNotificationRequestsWithIdentifiers: identifiers]; + [notificationCenter removeDeliveredNotificationsWithIdentifiers: identifiers]; + + this->notificationResponseObservers.dispatch(json); + return true; + #endif + + return false; + } + + void Notifications::list (const ListCallback callback) const { + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + [notificationCenter getDeliveredNotificationsWithCompletionHandler: ^(NSArray *notifications) { + Vector entries; + + for (UNNotification* notification in notifications) { + entries.push_back(Notification { notification.request.identifier.UTF8String }); + } + + callback(entries); + }]; + } + +#if SSC_PLATFORM_LINUX + void Notifications::configureWebView (WebKitWebView* webview) { + Lock lock(this->mutex); + + static bool areWebContextSignalsConnected = false; + + g_signal_connect( + G_OBJECT(webview), + "show-notification", + G_CALLBACK(+[]( + WebKitWebView* webview, + WebKitNotification* notification, + gpointer userData + ) -> bool { + static auto windowManager = App::instance()->getWindowManager(); + + if (windowManager == nullptr) { + return false; + } + + for (auto& window : windowManager->windows) { + if ( + window != nullptr && + window->bridge != nullptr && + WEBKIT_WEB_VIEW(window->webview) == webview + ) { + auto userConfig = window->bridge->userConfig; + return userConfig["permissions_allow_notifications"] != "false"; + } + } + + return false; + }), + this + ); + + if (!areWebContextSignalsConnected) { + auto webContext = webkit_web_context_get_default(); + + areWebContextSignalsConnected = true; + + g_signal_connect( + G_OBJECT(webContext), + "initialize-notification-permissions", + G_CALLBACK(+[]( + WebKitWebContext* webContext, + gpointer userData + ) { + static auto userConfig = SSC::getUserConfig(); + static const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + + const auto uri = "socket://" + bundleIdentifier; + const auto origin = webkit_security_origin_new_for_uri(uri.c_str()); + + GList* allowed = nullptr; + GList* disallowed = nullptr; + + webkit_security_origin_ref(origin); + + if (origin && allowed && disallowed) { + if (userConfig["permissions_allow_notifications"] == "false") { + disallowed = g_list_append(disallowed, (gpointer) origin); + } else { + allowed = g_list_append(allowed, (gpointer) origin); + } + + if (allowed && disallowed) { + webkit_web_context_initialize_notification_permissions( + webContext, + allowed, + disallowed + ); + } + } + + if (allowed) { + g_list_free(allowed); + } + + if (disallowed) { + g_list_free(disallowed); + } + + if (origin) { + webkit_security_origin_unref(origin); + } + }), + this + ); + } + } +#elif SSC_PLATFORM_WINDOWS + void Notifications::configureWebView (ICoreWebView2* webview) { + } +#endif } diff --git a/src/core/notifications.hh b/src/core/notifications.hh index 76d5476eb7..90160d0e8c 100644 --- a/src/core/notifications.hh +++ b/src/core/notifications.hh @@ -31,12 +31,38 @@ namespace SSC { using NotificationPresentedObserver = Module::Observer; using NotificationPresentedObservers = Module::Observers; + struct Notification { + String identifier; + const JSON::Object json () const; + }; + + struct ShowOptions { + String id; + String title; + String tag; + String lang; + bool silent = false; + String icon; + String image; + String body; + }; + + struct ShowResult { + String error = ""; + Notification notification; + }; + + using ShowCallback = Function; + using ListCallback = Function&)>; + #if SSC_PLATFORM_APPLE SSCUserNotificationCenterDelegate* userNotificationCenterDelegate = nullptr; NSTimer* userNotificationCenterPollTimer = nullptr; UNAuthorizationStatus __block currentUserNotificationAuthorizationStatus; #endif + Mutex mutex; + PermissionChangeObservers permissionChangeObservers; NotificationResponseObservers notificationResponseObservers; NotificationPresentedObservers notificationPresentedObservers; @@ -61,6 +87,16 @@ namespace SSC { const NotificationPresentedObserver& observer, const NotificationPresentedObserver::Callback callback ); + + bool show (const ShowOptions& options, const ShowCallback callback); + bool close (const Notification& notification); + void list (const ListCallback callback) const; + + #if SSC_PLATFORM_LINUX + void configureWebView (WebKitWebView* webview); + #elif SSC_PLATFORM_WINDOWS + void configureWebView (ICoreWebView2 *webviev); + #endif }; } #endif diff --git a/src/core/peer.hh b/src/core/peer.hh index 38f17b52e0..0b4c42f775 100644 --- a/src/core/peer.hh +++ b/src/core/peer.hh @@ -1,6 +1,12 @@ #ifndef SSC_CORE_PEER_H #define SSC_CORE_PEER_H +#include + +#include "types.hh" +#include "post.hh" + namespace SSC { + class Core; typedef enum { PEER_TYPE_NONE = 0, PEER_TYPE_TCP = 1 << 1, @@ -86,11 +92,11 @@ namespace SSC { // callbacks UDPReceiveCallback receiveCallback; - std::vector> onclose; + Vector> onclose; // instance state uint64_t id = 0; - std::recursive_mutex mutex; + Mutex mutex; Core *core; struct { diff --git a/src/core/preload.cc b/src/core/preload.cc index ad0c135c6a..7e8f4dc2aa 100644 --- a/src/core/preload.cc +++ b/src/core/preload.cc @@ -209,34 +209,6 @@ namespace SSC { " Object.freeze(globalThis.__args.config); \n" " Object.freeze(globalThis.__args.argv); \n" " Object.freeze(globalThis.__args.env); \n" - " \n" - " const { addEventListener } = globalThis; \n" - " globalThis.addEventListener = function (eventName, ...args) { \n" - " eventName = eventName.replace('load', '__runtime_init__'); \n" - " return addEventListener.call(this, eventName, ...args); \n" - " }; \n" - " \n" - " try { \n" - " const event = '__runtime_init__'; \n" - " let onload = null \n" - " Object.defineProperty(globalThis, 'onload', { \n" - " get: () => onload, \n" - " set (value) { \n" - " const opts = { once: true }; \n" - " if (onload) { \n" - " globalThis.removeEventListener(event, onload, opts); \n" - " onload = null; \n" - " } \n" - " \n" - " if (typeof value === 'function') { \n" - " onload = value; \n" - " globalThis.addEventListener(event, onload, opts, { \n" - " once: true \n" - " }); \n" - " } \n" - " } \n" - " }); \n" - " } catch {} \n" "})(); \n" ); @@ -363,7 +335,11 @@ namespace SSC { return ""; } - if (userConfig.contains("webview_importmap") && userConfig.at("webview_importmap").size() > 0) { + if ( + html.find("\n"; - preload += "\n"; - } - } else { - preload += ( - "if (document.readyState === 'complete') { \n" - " import('socket:internal/init') \n" - " .then(async () => { \n" - " " + preloadOptions.userScript + " \n" - " }) \n" - " .catch(console.error); \n" - "} else { \n" - " document.addEventListener('readystatechange', () => { \n" - " if (/interactive|complete/.test(document.readyState)) { \n" - " import('socket:internal/init') \n" - " .then(async () => { \n" - " " + preloadOptions.userScript + " \n" - " }) \n" - " .catch(console.error); \n" - " } \n" - " }, { once: true }); \n" - "} \n" - ); - - if (preloadOptions.wrap) { - preload += "\n"; - } - } - - if (preloadOptions.wrap) { - preload += "\n"; - preload += "\n"; - } - - return preload; - } - - String injectHTMLPreload ( - const Core* core, - const Map userConfig, - String html, - String preload - ) { - if (html.size() == 0) { - return ""; - } - - if ( - html.find("\n") + - preload - ); - } - } - } - - auto protocolHandlers = Vector { "npm:", "node:" }; - for (const auto& entry : core->protocolHandlers.mapping) { - if (entry.first != "npm" && entry.first != "node") { - protocolHandlers.push_back(String(entry.first) + ":"); - } - } - - html = tmpl(html, Map { - {"protocol_handlers", join(protocolHandlers, " ")} - }); - - if (html.find(""); - bool preloadWasInjected = false; - - if (existingImportMapCursor != String::npos) { - const auto closingScriptTag = html.find("", existingImportMapCursor); - if (closingScriptTag != String::npos) { - html = ( - html.substr(0, closingScriptTag + 9) + - preload + - html.substr(closingScriptTag + 9) - ); - - preloadWasInjected = true; - } - } - - if (!preloadWasInjected) { - if (html.find("") != String::npos) { - html = replace(html, "", String("" + preload)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + preload)); - } else if (html.find("") != String::npos) { - html = replace(html, "", String("" + preload)); - } else { - html = preload + html; - } - } - - return html; - } -} diff --git a/src/core/preload.hh b/src/core/preload.hh deleted file mode 100644 index d87c945e51..0000000000 --- a/src/core/preload.hh +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CORE_PRELOAD_HH -#define CORE_PRELOAD_HH - -#include "../window/options.hh" - -namespace SSC { - class Core; - struct PreloadOptions { - bool module = false; - bool wrap = false; - uint64_t clientId = 0; - String userScript = ""; - }; - - String createPreload ( - const WindowOptions opts, - const PreloadOptions preloadOptions - ); - - inline SSC::String createPreload (WindowOptions opts) { - return createPreload(opts, PreloadOptions {}); - } - - String injectHTMLPreload ( - const Core* core, - const Map userConfig, - String html, - String preload - ); -} -#endif diff --git a/src/process/process.hh b/src/core/process.hh similarity index 71% rename from src/process/process.hh rename to src/core/process.hh index 9480da4d56..b03aa4934f 100644 --- a/src/process/process.hh +++ b/src/core/process.hh @@ -1,11 +1,11 @@ -#ifndef SSC_PROCESS_PROCESS_H -#define SSC_PROCESS_PROCESS_H +#ifndef SOCKET_RUNTIME_CORE_PROCESS_H +#define SOCKET_RUNTIME_CORE_PROCESS_H -#include +#include "../platform/platform.hh" -#include "../core/types.hh" -#include "../core/string.hh" -#include "../core/platform.hh" +#if !SOCKET_RUNTIME_PLATFORM_IOS +#include +#endif #ifndef WIFEXITED #define WIFEXITED(w) ((w) & 0x7f) @@ -26,7 +26,7 @@ namespace SSC { // Buffer size for reading stdout and stderr. Default is 131072 (128 kB). size_t bufferSize = 131072; // Set to true to inherit file descriptors from parent process. Default is false. - // On Windows: has no effect unless read_stdout==nullptr, read_stderr==nullptr and open_stdin==false. + // On Windows: has no effect unless readStdout==nullptr, readStderr==nullptr and openStdin==false. bool inheritFDs = false; // On Windows only: controls how the process is started, mimics STARTUPINFO's wShowWindow. @@ -51,6 +51,7 @@ namespace SSC { ShowWindow show_window{ShowWindow::show_default}; }; +#if !SOCKET_RUNTIME_PLATFORM_IOS inline ExecOutput exec (String command) { command = command + " 2>&1"; @@ -61,7 +62,7 @@ namespace SSC { const int bufsize = 128; Array buffer; - #ifdef _WIN32 + #if SOCKET_RUNTIME_PLATFORM_WINDOWS // // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/popen-wpopen?view=msvc-160 // _popen works fine in a console application... ok fine that's all we need it for... thanks. @@ -82,7 +83,7 @@ namespace SSC { } } while (count > 0); - #ifdef _WIN32 + #if SOCKET_RUNTIME_PLATFORM_WINDOWS exitCode = _pclose(pipe); #else exitCode = pclose(pipe); @@ -99,22 +100,25 @@ namespace SSC { return eo; } +#endif // Platform independent class for creating processes. // Note on Windows: it seems not possible to specify which pipes to redirect. - // Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, + // Thus, at the moment, if readStdout==nullptr, readStderr==nullptr and openStdin==false, // the stdout, stderr and stdin are sent to the parent process instead. class Process { public: static constexpr auto PROCESS_WAIT_TIMEOUT = 256; - #if SSC_PLATFORM_WINDOWS + #if SOCKET_RUNTIME_PLATFORM_WINDOWS typedef unsigned long PID; // process id (pid) type typedef void *FD; // file descriptor type + #elif SOCKET_RUNTIME_PLATFORM_IOS + typedef int PID; + typedef int FD; #else typedef pid_t PID; typedef int FD; - typedef String string_type; #endif String command; @@ -123,10 +127,10 @@ namespace SSC { Atomic closed = true; Atomic status = -1; Atomic lastWriteStatus = 0; - bool open_stdin; + bool openStdin; PID id = 0; - #if SSC_PLATFORM_WINDOWS + #if SOCKET_RUNTIME_PLATFORM_WINDOWS String shell = ""; #else String shell = "/bin/sh"; @@ -135,13 +139,14 @@ namespace SSC { private: class Data { - public: - Data() noexcept; - PID id; - #if SSC_PLATFORM_WINDOWS - void *handle {nullptr}; - #endif - int exit_status{-1}; + public: + PID id; + int exitStatus = -1; + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + void* handle = nullptr; + #endif + + Data() noexcept; }; public: @@ -149,26 +154,28 @@ namespace SSC { const String &command, const String &argv, const String &path = String(""), - MessageCallback read_stdout = nullptr, - MessageCallback read_stderr = nullptr, - MessageCallback on_exit = nullptr, - bool open_stdin = true, - const ProcessConfig &config = {}) noexcept; - - #if !SSC_PLATFORM_WINDOWS + MessageCallback readStdout = nullptr, + MessageCallback readStderr = nullptr, + MessageCallback onExit = nullptr, + bool openStdin = true, + const ProcessConfig &config = {} + ) noexcept; + + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS // Starts a process with the environment of the calling process. // Supported on Unix-like systems only. - Process( + Process ( const std::function &function, - MessageCallback read_stdout = nullptr, - MessageCallback read_stderr = nullptr, - MessageCallback on_exit = nullptr, - bool open_stdin = true, - const ProcessConfig &config = {}) noexcept; + MessageCallback readStdout = nullptr, + MessageCallback readStderr = nullptr, + MessageCallback onExit = nullptr, + bool openStdin = true, + const ProcessConfig &config = {} + ) noexcept; #endif ~Process () noexcept { - close_fds(); + closeFDs(); }; // Get the process id of the started process. @@ -178,8 +185,8 @@ namespace SSC { // Write to stdin. bool write (const char *bytes, size_t size); - bool write (const SharedPointer bytes, size_t size) { - return write(*bytes, size); + bool write (const SharedPointer bytes, size_t size) { + return write(bytes.get(), size); } // Write to stdin. Convenience function using write(const char *, size_t). @@ -189,7 +196,7 @@ namespace SSC { // Close stdin. If the process takes parameters from stdin, use this to // notify that all parameters have been sent. - void close_stdin () noexcept; + void closeStdin () noexcept; PID open () noexcept { if (this->command.size() == 0) return 0; auto str = trim(this->command + " " + this->argv); @@ -198,8 +205,6 @@ namespace SSC { return pid; } - // Kill a given process id. Use kill(bool force) instead if possible. - // force=true is only supported on Unix-like systems. void kill (PID id) noexcept; void kill () noexcept { this->kill(this->getPID()); @@ -209,27 +214,27 @@ namespace SSC { private: Data data; - std::mutex close_mutex; - MessageCallback read_stdout; - MessageCallback read_stderr; - MessageCallback on_exit; - Mutex stdin_mutex; - Mutex stdout_mutex; - Mutex stderr_mutex; + std::mutex closeMutex; + MessageCallback readStdout; + MessageCallback readStderr; + MessageCallback onExit; + Mutex stdinMutex; + Mutex stdoutMutex; + Mutex stderrMutex; ProcessConfig config; - #if !SSC_PLATFORM_WINDOWS - Thread stdout_stderr_thread; + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS + Thread stdoutAndStderrThread; #else - Thread stdout_thread, stderr_thread; + Thread stdoutThread, stderrThread; #endif - UniquePointer stdout_fd, stderr_fd, stdin_fd; + UniquePointer stdoutFD, stderrFD, stdinFD; void read () noexcept; - void close_fds () noexcept; + void closeFDs () noexcept; PID open (const String &command, const String &path) noexcept; - #if !SSC_PLATFORM_WINDOWS + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS PID open (const Function &function) noexcept; #endif }; diff --git a/src/core/process/unix.cc b/src/core/process/unix.cc new file mode 100644 index 0000000000..a86a3339c4 --- /dev/null +++ b/src/core/process/unix.cc @@ -0,0 +1,447 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../process.hh" +#include "../debug.hh" +#include "../modules/timers.hh" + +namespace SSC { + static StringStream initial; + + Process::Data::Data () noexcept + : id(-1) + {} + + Process::Process ( + const String &command, + const String &argv, + const String &path, + MessageCallback readStdout, + MessageCallback readStderr, + MessageCallback onExit, + bool openStdin, + const ProcessConfig &config + ) noexcept + : openStdin(true), + readStdout(std::move(readStdout)), + readStderr(std::move(readStderr)), + onExit(std::move(onExit)), + command(command), + argv(argv), + path(path) + {} + + Process::Process ( + const Function &function, + MessageCallback readStdout, + MessageCallback readStderr, + MessageCallback onExit, + bool openStdin, + const ProcessConfig &config + ) noexcept + : readStdout(std::move(readStdout)), + readStderr(std::move(readStderr)), + onExit(std::move(onExit)), + openStdin(openStdin), + config(config) + { + #if !SOCKET_RUNTIME_PLATFORM_IOS + open(function); + read(); + #endif + } + + Process::PID Process::open (const Function &function) noexcept { + #if SOCKET_RUNTIME_PLATFORM_IOS + return -1; // -EPERM + #else + if (openStdin) { + stdinFD = UniquePointer(new FD); + } + + if (readStdout) { + stdoutFD = UniquePointer(new FD); + } + + if (readStderr) { + stderrFD = UniquePointer(new FD); + } + + int stdin_p[2]; + int stdout_p[2]; + int stderr_p[2]; + + if (stdinFD && pipe(stdin_p) != 0) { + return -1; + } + + if (stdoutFD && pipe(stdout_p) != 0) { + if (stdinFD) { + close(stdin_p[0]); + close(stdin_p[1]); + } + return -1; + } + + if (stderrFD && pipe(stderr_p) != 0) { + if (stdinFD) { + close(stdin_p[0]); + close(stdin_p[1]); + } + + if (stdoutFD) { + close(stdout_p[0]); + close(stdout_p[1]); + } + + return -1; + } + + PID pid = fork(); + + if (pid < 0) { + if (stdinFD) { + close(stdin_p[0]); + close(stdin_p[1]); + } + + if (stdoutFD) { + close(stdout_p[0]); + close(stdout_p[1]); + } + + if (stderrFD) { + close(stderr_p[0]); + close(stderr_p[1]); + } + + return pid; + } + + closed = false; + id = pid; + + if (pid > 0) { + #if SOCKET_RUNTIME_PLATFORM_APPLE + setpgid(pid, getpgid(0)); + #endif + + auto thread = Thread([this] { + int code = 0; + waitpid(this->id, &code, 0); + + this->status = WEXITSTATUS(code); + this->closed = true; + + if (this->onExit != nullptr) { + this->onExit(std::to_string(status)); + } + }); + + thread.detach(); + } else if (pid == 0) { + if (stdinFD) { + dup2(stdin_p[0], 0); + } + + if (stdoutFD) { + dup2(stdout_p[1], 1); + } + + if (stderrFD) { + dup2(stderr_p[1], 2); + } + + if (stdinFD) { + close(stdin_p[0]); + close(stdin_p[1]); + } + + if (stdoutFD) { + close(stdout_p[0]); + close(stdout_p[1]); + } + + if (stderrFD) { + close(stderr_p[0]); + close(stderr_p[1]); + } + + #if SOCKET_RUNTIME_PLATFORM_APPLE + setpgid(0, 0); + #endif + + if (function) { + function(); + } + + _exit(EXIT_FAILURE); + } + + if (stdinFD) { + close(stdin_p[0]); + } + + if (stdoutFD) { + close(stdout_p[1]); + } + + if (stderrFD) { + close(stderr_p[1]); + } + + if (stdinFD) { + *stdinFD = stdin_p[1]; + } + + if (stdoutFD) { + *stdoutFD = stdout_p[0]; + } + + if (stderrFD) { + *stderrFD = stderr_p[0]; + } + + data.id = pid; + return pid; + #endif + } + + Process::PID Process::open (const String &command, const String &path) noexcept { + #if SOCKET_RUNTIME_PLATFORM_IOS + return -1; // -EPERM + #else + return open([&command, &path, this] { + auto command_c_str = command.c_str(); + String cd_path_and_command; + + if (!path.empty()) { + auto path_escaped = path; + size_t pos = 0; + + // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 + while ((pos = path_escaped.find('\'', pos)) != String::npos) { + path_escaped.replace(pos, 1, "'\\''"); + pos += 4; + } + + cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links + command_c_str = cd_path_and_command.c_str(); + } + + #if SOCKET_RUNTIME_PLATFORM_APPLE + setpgid(0, 0); + #endif + if (this->shell.size() > 0) { + return execl(this->shell.c_str(), this->shell.c_str(), "-c", command_c_str, nullptr); + } else { + return execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + } + }); + #endif + } + + int Process::wait () { + #if SOCKET_RUNTIME_PLATFORM_IOS + return -1; // -EPERM + #else + do { + msleep(Process::PROCESS_WAIT_TIMEOUT); + } while (this->closed == false); + + return this->status; + #endif + } + + void Process::read() noexcept { + #if !SOCKET_RUNTIME_PLATFORM_IOS + if (data.id <= 0 || (!stdoutFD && !stderrFD)) { + return; + } + + stdoutAndStderrThread = Thread([this] { + Vector pollfds; + std::bitset<2> fd_is_stdout; + + if (stdoutFD) { + fd_is_stdout.set(pollfds.size()); + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stdoutFD, F_SETFL, fcntl(*stdoutFD, F_GETFL) | O_NONBLOCK) == 0 ? *stdoutFD : -1; + pollfds.back().events = POLLIN; + } + + if (stderrFD) { + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stderrFD, F_SETFL, fcntl(*stderrFD, F_GETFL) | O_NONBLOCK) == 0 ? *stderrFD : -1; + pollfds.back().events = POLLIN; + } + + auto buffer = UniquePointer(new char[config.bufferSize]); + bool any_open = !pollfds.empty(); + StringStream ss; + + while (any_open && (poll(pollfds.data(), static_cast(pollfds.size()), -1) > 0 || errno == EINTR)) { + any_open = false; + + for (size_t i = 0; i < pollfds.size(); ++i) { + if (!(pollfds[i].fd >= 0)) continue; + if (pollfds[i].revents & POLLIN) { + memset(buffer.get(), 0, config.bufferSize); + const ssize_t n = ::read(pollfds[i].fd, buffer.get(), config.bufferSize); + + if (n > 0) { + if (fd_is_stdout[i]) { + Lock lock(stdoutMutex); + auto b = String(buffer.get()); + auto parts = splitc(b, '\n'); + + if (parts.size() > 1) { + for (int i = 0; i < parts.size() - 1; i++) { + ss << parts[i]; + + String s(ss.str()); + readStdout(s); + + ss.str(String()); + ss.clear(); + ss.copyfmt(initial); + } + ss << parts[parts.size() - 1]; + } else { + ss << b; + } + } else { + Lock lock(stderrMutex); + readStderr(String(buffer.get())); + } + } else if (n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + pollfds[i].fd = -1; + continue; + } + } + + if (pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + pollfds[i].fd = -1; + continue; + } + + any_open = true; + } + } + }); + #endif + } + + void Process::closeFDs () noexcept { + #if !SOCKET_RUNTIME_PLATFORM_IOS + if (stdoutAndStderrThread.joinable()) { + stdoutAndStderrThread.join(); + } + + if (stdinFD) { + closeStdin(); + } + + if (stdoutFD) { + if (data.id > 0) { + close(*stdoutFD); + } + + stdoutFD.reset(); + } + + if (stderrFD) { + if (data.id > 0) { + close(*stderrFD); + } + + stderrFD.reset(); + } + #endif + } + + bool Process::write (const char *bytes, size_t n) { + #if SOCKET_RUNTIME_PLATFORM_IOS + return false; + #else + Lock lock(stdinMutex); + + this->lastWriteStatus = 0; + + if (stdinFD) { + String b(bytes); + + while (true && (b.size() > 0)) { + int bytesWritten = ::write(*stdinFD, b.c_str(), b.size()); + + if (bytesWritten == -1) { + this->lastWriteStatus = errno; + return false; + } + + if (bytesWritten >= b.size()) { + break; + } + + b = b.substr(bytesWritten, b.size()); + } + + int bytesWritten = ::write(*stdinFD, "\n", 1); + if (bytesWritten == -1) { + this->lastWriteStatus = errno; + return false; + } + + return true; + } + + return false; + #endif + } + + void Process::closeStdin () noexcept { + #if !SOCKET_RUNTIME_PLATFORM_IOS + Lock lock(stdinMutex); + + if (stdinFD) { + if (data.id > 0) { + close(*stdinFD); + } + + stdinFD.reset(); + } + #endif + } + + void Process::kill (PID id) noexcept { + #if !SOCKET_RUNTIME_PLATFORM_IOS + if (id <= 0 || ::kill(-id, 0) != 0) { + return; + } + + auto r = ::kill(-id, SIGTERM); + + if (r != 0 && ::kill(-id, 0) == 0) { + r = ::kill(-id, SIGINT); + + if (r != 0 && ::kill(-id, 0) == 0 ) { + r = ::kill(-id, SIGKILL); + + if (r != 0 && ::kill(-id, 0) == 0) { + debug("Process: Failed to kill process %d", id); + } + } + } + #endif + } +} diff --git a/src/process/win.cc b/src/core/process/win.cc similarity index 71% rename from src/process/win.cc rename to src/core/process/win.cc index 9aa2d6731d..f4d026c8fe 100644 --- a/src/process/win.cc +++ b/src/core/process/win.cc @@ -1,12 +1,11 @@ -#include "process.hh" -#include "../core/core.hh" - #include #include #include #include #include +#include "../process.hh" + namespace SSC { const static StringStream initial; @@ -17,16 +16,16 @@ Process::Process( const String &command, const String &argv, const String &path, - MessageCallback read_stdout, - MessageCallback read_stderr, - MessageCallback on_exit, - bool open_stdin, + MessageCallback readStdout, + MessageCallback readStderr, + MessageCallback onExit, + bool openStdin, const ProcessConfig &config ) noexcept : - open_stdin(true), - read_stdout(std::move(read_stdout)), - read_stderr(std::move(read_stderr)), - on_exit(std::move(on_exit)) + openStdin(true), + readStdout(std::move(readStdout)), + readStderr(std::move(readStderr)), + onExit(std::move(onExit)) { this->command = command; this->argv = argv; @@ -70,20 +69,20 @@ class Handle { }; //Based on the discussion thread: https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxq1wsj -std::mutex create_process_mutex; +std::mutex create_processMutex; //Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx. Process::PID Process::open (const String &command, const String &path) noexcept { - if (open_stdin) { - stdin_fd = UniquePointer(new Process::FD(nullptr)); + if (openStdin) { + stdinFD = UniquePointer(new Process::FD(nullptr)); } - if (read_stdout) { - stdout_fd = UniquePointer(new Process::FD(nullptr)); + if (readStdout) { + stdoutFD = UniquePointer(new Process::FD(nullptr)); } - if (read_stderr) { - stderr_fd = UniquePointer(new Process::FD(nullptr)); + if (readStderr) { + stderrFD = UniquePointer(new Process::FD(nullptr)); } Handle stdin_rd_p; @@ -99,22 +98,22 @@ Process::PID Process::open (const String &command, const String &path) noexcept security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = nullptr; - Lock lock(create_process_mutex); + Lock lock(create_processMutex); - if (stdin_fd) { + if (stdinFD) { if (!CreatePipe(&stdin_rd_p, &stdin_wr_p, &security_attributes, 0) || !SetHandleInformation(stdin_wr_p, HANDLE_FLAG_INHERIT, 0)) return 0; } - if (stdout_fd) { + if (stdoutFD) { if (!CreatePipe(&stdout_rd_p, &stdout_wr_p, &security_attributes, 0) || !SetHandleInformation(stdout_rd_p, HANDLE_FLAG_INHERIT, 0)) { return 0; } } - if (stderr_fd) { + if (stderrFD) { if (!CreatePipe(&stderr_rd_p, &stderr_wr_p, &security_attributes, 0) || !SetHandleInformation(stderr_rd_p, HANDLE_FLAG_INHERIT, 0)) { return 0; @@ -125,14 +124,14 @@ Process::PID Process::open (const String &command, const String &path) noexcept STARTUPINFO startup_info; ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); - ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); startup_info.hStdInput = stdin_rd_p; startup_info.hStdOutput = stdout_wr_p; startup_info.hStdError = stderr_wr_p; - if (stdin_fd || stdout_fd || stderr_fd) + if (stdinFD || stdoutFD || stderrFD) startup_info.dwFlags |= STARTF_USESTDHANDLES; if (config.show_window != ProcessConfig::ShowWindow::show_default) { @@ -143,12 +142,12 @@ Process::PID Process::open (const String &command, const String &path) noexcept auto process_command = command; #ifdef MSYS_PROCESS_USE_SH size_t pos = 0; - while((pos = process_command.find('\\', pos)) != string_type::npos) { + while((pos = process_command.find('\\', pos)) != String::npos) { process_command.replace(pos, 1, "\\\\\\\\"); pos += 4; } pos = 0; - while((pos = process_command.find('\"', pos)) != string_type::npos) { + while((pos = process_command.find('\"', pos)) != String::npos) { process_command.replace(pos, 1, "\\\""); pos += 2; } @@ -173,8 +172,8 @@ Process::PID Process::open (const String &command, const String &path) noexcept const_cast(cmd), nullptr, nullptr, - stdin_fd || stdout_fd || stderr_fd || config.inheritFDs, // Cannot be false when stdout, stderr or stdin is used - stdin_fd || stdout_fd || stderr_fd ? CREATE_NO_WINDOW : 0, // CREATE_NO_WINDOW cannot be used when stdout or stderr is redirected to parent process + stdinFD || stdoutFD || stderrFD || config.inheritFDs, // Cannot be false when stdout, stderr or stdin is used + stdinFD || stdoutFD || stderrFD ? CREATE_NO_WINDOW : 0, // CREATE_NO_WINDOW cannot be used when stdout or stderr is redirected to parent process nullptr, path.empty() ? nullptr : path.c_str(), &startup_info, @@ -189,20 +188,20 @@ Process::PID Process::open (const String &command, const String &path) noexcept CloseHandle(process_info.hThread); } - if (stdin_fd) { - *stdin_fd = stdin_wr_p.detach(); + if (stdinFD) { + *stdinFD = stdin_wr_p.detach(); } - if (stdout_fd) { - *stdout_fd = stdout_rd_p.detach(); + if (stdoutFD) { + *stdoutFD = stdout_rd_p.detach(); } - if (stderr_fd) { - *stderr_fd = stderr_rd_p.detach(); + if (stderrFD) { + *stderrFD = stderr_rd_p.detach(); } auto processHandle = process_info.hProcess; - auto t = std::thread([&](HANDLE _processHandle) { + auto t = Thread([&](HANDLE _processHandle) { DWORD exitCode = 0; try { WaitForSingleObject(_processHandle, INFINITE); @@ -220,8 +219,8 @@ Process::PID Process::open (const String &command, const String &path) noexcept this->status = (exitCode <= UINT_MAX ? exitCode : WEXITSTATUS(exitCode)); this->closed = true; - if (this->on_exit != nullptr) - this->on_exit(std::to_string(this->status)); + if (this->onExit != nullptr) + this->onExit(std::to_string(this->status)); } catch (std::exception e) { std::cerr << "Process thread exception: " << e.what() << std::endl; this->closed = true; @@ -244,8 +243,8 @@ void Process::read() noexcept { return; } - if (stdout_fd) { - stdout_thread = std::thread([this]() { + if (stdoutFD) { + stdoutThread = Thread([this]() { DWORD n; UniquePointer buffer(new char[config.bufferSize]); @@ -253,7 +252,7 @@ void Process::read() noexcept { for (;;) { memset(buffer.get(), 0, config.bufferSize); - BOOL bSuccess = ReadFile(*stdout_fd, static_cast(buffer.get()), static_cast(config.bufferSize), &n, nullptr); + BOOL bSuccess = ReadFile(*stdoutFD, static_cast(buffer.get()), static_cast(config.bufferSize), &n, nullptr); if (!bSuccess || n == 0) { break; @@ -263,12 +262,12 @@ void Process::read() noexcept { auto parts = splitc(b, '\n'); if (parts.size() > 1) { - Lock lock(stdout_mutex); + Lock lock(stdoutMutex); for (int i = 0; i < parts.size() - 1; i++) { ss << parts[i]; String s(ss.str()); - read_stdout(s); + readStdout(s); ss.str(String()); ss.clear(); ss.copyfmt(initial); @@ -281,64 +280,64 @@ void Process::read() noexcept { }); } - if (stderr_fd) { - stderr_thread = std::thread([this]() { + if (stderrFD) { + stderrThread = Thread([this]() { DWORD n; UniquePointer buffer(new char[config.bufferSize]); for (;;) { - BOOL bSuccess = ReadFile(*stderr_fd, static_cast(buffer.get()), static_cast(config.bufferSize), &n, nullptr); + BOOL bSuccess = ReadFile(*stderrFD, static_cast(buffer.get()), static_cast(config.bufferSize), &n, nullptr); if (!bSuccess || n == 0) break; - Lock lock(stderr_mutex); - read_stderr(String(buffer.get())); + Lock lock(stderrMutex); + readStderr(String(buffer.get())); } }); } } -void Process::close_fds() noexcept { - if (stdout_thread.joinable()) { - stdout_thread.join(); +void Process::closeFDs() noexcept { + if (stdoutThread.joinable()) { + stdoutThread.join(); } - if (stderr_thread.joinable()) { - stderr_thread.join(); + if (stderrThread.joinable()) { + stderrThread.join(); } - if (stdin_fd) { - close_stdin(); + if (stdinFD) { + closeStdin(); } - if (stdout_fd) { - if (*stdout_fd != nullptr) { - CloseHandle(*stdout_fd); + if (stdoutFD) { + if (*stdoutFD != nullptr) { + CloseHandle(*stdoutFD); } - stdout_fd.reset(); + stdoutFD.reset(); } - if (stderr_fd) { - if (*stderr_fd != nullptr) { - CloseHandle(*stderr_fd); + if (stderrFD) { + if (*stderrFD != nullptr) { + CloseHandle(*stderrFD); } - stderr_fd.reset(); + stderrFD.reset(); } } bool Process::write(const char *bytes, size_t n) { - if (!open_stdin) { - throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); + if (!openStdin) { + throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set openStdin=true when constructing the process."); } - Lock lock(stdin_mutex); - if (stdin_fd) { + Lock lock(stdinMutex); + if (stdinFD) { String b(bytes); while (true && (b.size() > 0)) { DWORD bytesWritten; DWORD size = static_cast(b.size()); - BOOL bSuccess = WriteFile(*stdin_fd, b.c_str(), size, &bytesWritten, nullptr); + BOOL bSuccess = WriteFile(*stdinFD, b.c_str(), size, &bytesWritten, nullptr); if (bytesWritten >= size || bSuccess) { break; @@ -348,7 +347,7 @@ bool Process::write(const char *bytes, size_t n) { } DWORD bytesWritten; - BOOL bSuccess = WriteFile(*stdin_fd, L"\n", static_cast(2), &bytesWritten, nullptr); + BOOL bSuccess = WriteFile(*stdinFD, L"\n", static_cast(2), &bytesWritten, nullptr); if (!bSuccess || bytesWritten == 0) { return false; @@ -360,15 +359,15 @@ bool Process::write(const char *bytes, size_t n) { return false; } -void Process::close_stdin () noexcept { - Lock lock(stdin_mutex); +void Process::closeStdin () noexcept { + Lock lock(stdinMutex); - if (stdin_fd) { - if (*stdin_fd != nullptr) { - CloseHandle(*stdin_fd); + if (stdinFD) { + if (*stdinFD != nullptr) { + CloseHandle(*stdinFD); } - stdin_fd.reset(); + stdinFD.reset(); } } @@ -378,8 +377,6 @@ void Process::kill (PID id) noexcept { return; } - this->closed = true; - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot) { @@ -401,6 +398,8 @@ void Process::kill (PID id) noexcept { } CloseHandle(snapshot); + + this->closed = true; } HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id); diff --git a/src/core/protocol_handlers.cc b/src/core/protocol_handlers.cc deleted file mode 100644 index a3f532724c..0000000000 --- a/src/core/protocol_handlers.cc +++ /dev/null @@ -1,81 +0,0 @@ -#include "protocol_handlers.hh" -#include "core.hh" - -namespace SSC { - static Vector reserved = { - "socket", - "ipc", - "node", - "npm" - }; - - ProtocolHandlers::ProtocolHandlers (Core* core) - : Module(core) - {} - - ProtocolHandlers::~ProtocolHandlers () {} - - bool ProtocolHandlers::registerHandler (const String& scheme, const Data data) { - Lock lock(this->mutex); - - if (std::find(reserved.begin(), reserved.end(), scheme) != reserved.end()) { - return false; - } - - if (this->mapping.contains(scheme)) { - return false; - } - - this->mapping.insert_or_assign(scheme, Protocol { scheme, data }); - return true; - } - - bool ProtocolHandlers::unregisterHandler (const String& scheme) { - Lock lock(this->mutex); - - if (!this->mapping.contains(scheme)) { - return false; - } - - this->mapping.erase(scheme); - return true; - } - - const ProtocolHandlers::Data ProtocolHandlers::getHandlerData (const String& scheme) { - Lock lock(this->mutex); - - if (!this->mapping.contains(scheme)) { - return Data {}; - } - - return this->mapping.at(scheme).data; - } - - bool ProtocolHandlers::setHandlerData (const String& scheme, const Data data) { - Lock lock(this->mutex); - - if (!this->mapping.contains(scheme)) { - return false; - } - - this->mapping.at(scheme).data = data; - return true; - } - - bool ProtocolHandlers::hasHandler (const String& scheme) { - Lock lock(this->mutex); - return this->mapping.contains(scheme); - } - - const String ProtocolHandlers::getServiceWorkerScope (const String& scheme) { - for (const auto& entry : this->core->serviceWorker.registrations) { - const auto& scope = entry.first; - const auto& registration = entry.second; - if (registration.options.scheme == scheme) { - return scope; - } - } - - return ""; - } -} diff --git a/src/core/resource.cc b/src/core/resource.cc index 08902b20e2..20d2951243 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -2,7 +2,19 @@ #include "debug.hh" #include "core.hh" +#if SOCKET_RUNTIME_PLATFORM_ANDROID +#include "../platform/android.hh" +#include +#endif + namespace SSC { + static std::map caches; + static Mutex mutex; + +#if SOCKET_RUNTIME_PLATFORM_ANDROID + Android::AssetManager* sharedAndroidAssetManager = nullptr; +#endif + std::map> FileResource::mimeTypes = { {"application/font-woff", { ".woff" }}, {"application/font-woff2", { ".woff2" }}, @@ -28,6 +40,94 @@ namespace SSC { {"video/ogg", { ".ogv" }} }; +#if SOCKET_RUNTIME_PLATFORM_ANDROID + static Path getRelativeAndroidAssetManagerPath (const Path& resourcePath) { + auto resourcesPath = FileResource::getResourcesPath(); + auto assetPath = replace(resourcePath.string(), resourcesPath.string(), ""); + if (assetPath.starts_with("/")) { + assetPath = assetPath.substr(1); + } + + return Path(assetPath); + } +#endif + +#if SOCKET_RUNTIME_PLATFORM_ANDROID + void FileResource::setSharedAndroidAssetManager (Android::AssetManager* assetManager) { + Lock lock(mutex); + sharedAndroidAssetManager = assetManager; + } + + Android::AssetManager* FileResource::getSharedAndroidAssetManager () { + return sharedAndroidAssetManager; + } +#endif + + bool FileResource::isFile (const String& resourcePath) { + return FileResource::isFile(Path(resourcePath)); + } + + bool FileResource::isFile (const Path& resourcePath) { + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (sharedAndroidAssetManager) { + const auto assetPath = getRelativeAndroidAssetManagerPath(resourcePath); + const auto asset = AAssetManager_open( + sharedAndroidAssetManager, + assetPath.c_str(), + AASSET_MODE_BUFFER + ); + + if (asset) { + AAsset_close(asset); + return true; + } + } + #elif SOCKET_RUNTIME_PLATFORM_APPLE + static auto fileManager = [[NSFileManager alloc] init]; + bool isDirectory = false; + const auto fileExistsAtPath = [fileManager + fileExistsAtPath: @(this->path.string().c_str()) + isDirectory: &isDirectory + ]; + + return fileExistsAtPath && !isDirectory + #endif + + return fs::is_regular_file(resourcePath); + } + + bool FileResource::isDirectory (const String& resourcePath) { + return FileResource::isDirectory(Path(resourcePath)); + } + + bool FileResource::isDirectory (const Path& resourcePath) { + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (sharedAndroidAssetManager) { + const auto assetPath = getRelativeAndroidAssetManagerPath(resourcePath); + const auto assetDir = AAssetManager_openDir( + sharedAndroidAssetManager, + assetPath.c_str() + ); + + if (assetDir) { + AAssetDir_close(assetDir); + return true; + } + } + #elif SOCKET_RUNTIME_PLATFORM_APPLE + static auto fileManager = [[NSFileManager alloc] init]; + bool isDirectory = false; + const auto fileExistsAtPath = [fileManager + fileExistsAtPath: @(this->path.string().c_str()) + isDirectory: &isDirectory + ]; + + return fileExistsAtPath && isDirectory + #endif + + return fs::is_directory(resourcePath); + } + Path FileResource::getResourcesPath () { static String value; @@ -35,16 +135,16 @@ namespace SSC { return Path(value); } - #if SSC_PLATFORM_MACOS + #if SOCKET_RUNTIME_PLATFORM_MACOS static const auto resourcePath = NSBundle.mainBundle.resourcePath; value = resourcePath.UTF8String; - #elif SSC_PLATFORM_IOS || SSC_PLATFORM_IOS_SIMULATOR + #elif SOCKET_RUNTIME_PLATFORM_IOS || SOCKET_RUNTIME_PLATFORM_IOS_SIMULATOR static const auto resourcePath = NSBundle.mainBundle.resourcePath; value = [resourcePath stringByAppendingPathComponent: @"ui"].UTF8String; - #elif SSC_PLATFORM_LINUX + #elif SOCKET_RUNTIME_PLATFORM_LINUX static const auto self = fs::canonical("/proc/self/exe"); value = self.parent_path().string(); - #elif SSC_PLATFORM_WINDOWS + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS static wchar_t filename[MAX_PATH]; GetModuleFileNameW(NULL, filename, MAX_PATH); const auto self = Path(filename).remove_filename(); @@ -60,7 +160,7 @@ namespace SSC { #endif if (value.size() > 0) { - #if !SSC_PLATFORM_WINDOWS + #if !PLATFORM_WINDOWS std::replace( value.begin(), value.end(), @@ -83,7 +183,7 @@ namespace SSC { Path FileResource::getResourcePath (const String& resourcePath) { const auto resourcesPath = FileResource::getResourcesPath(); - #if SSC_PLATFORM_WINDOWS + #if SOCKET_RUNTIME_PLATFORM_WINDOWS if (resourcePath.starts_with("\\")) { return Path(resourcesPath.string() + resourcePath); } @@ -98,12 +198,18 @@ namespace SSC { #endif } - FileResource::FileResource (const String& resourcePath) { - this->path = fs::absolute(Path(resourcePath)); - + FileResource::FileResource ( + const Path& resourcePath, + const Options& options + ) : options(options) { + this->path = fs::absolute(resourcePath); this->startAccessing(); } + FileResource::FileResource (const String& resourcePath, const Options& options) + : FileResource(Path(resourcePath), options) + {} + FileResource::~FileResource () { this->stopAccessing(); } @@ -112,6 +218,7 @@ namespace SSC { this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; + this->options = resource.options; this->accessing = resource.accessing.load(); if (this->accessing) { @@ -123,6 +230,7 @@ namespace SSC { this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; + this->options = resource.options; this->accessing = resource.accessing.load(); resource.bytes = nullptr; @@ -138,6 +246,7 @@ namespace SSC { this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; + this->options = resource.options; this->accessing = resource.accessing.load(); if (this->accessing) { @@ -151,6 +260,7 @@ namespace SSC { this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; + this->options = resource.options; this->accessing = resource.accessing.load(); resource.bytes = nullptr; @@ -171,9 +281,13 @@ namespace SSC { return false; } - #if SSC_PLATFORM_APPLE + if (caches.contains(this->path.string())) { + this->cache = caches.at(this->path.string()); + } + + #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->url == nullptr) { - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE this->url = [NSURL fileURLWithPath: @(this->path.string().c_str())]; #endif } @@ -193,7 +307,7 @@ namespace SSC { if (!this->accessing) { return false; } - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->url != nullptr) { [this->url stopAccessingSecurityScopedResource]; } @@ -207,15 +321,31 @@ namespace SSC { return false; } - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE static auto fileManager = [[NSFileManager alloc] init]; return [fileManager fileExistsAtPath: @(this->path.string().c_str()) isDirectory: NULL ]; - #endif + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + if (sharedAndroidAssetManager) { + const auto assetPath = getRelativeAndroidAssetManagerPath(this->path); + const auto asset = AAssetManager_open( + sharedAndroidAssetManager, + assetPath.c_str(), + AASSET_MODE_BUFFER + ); - return false; + if (asset) { + AAsset_close(asset); + return true; + } + } + + return fs::exists(this->path); + #else + return fs::exists(this->path); + #endif } bool FileResource::hasAccess () const noexcept { @@ -239,7 +369,7 @@ namespace SSC { } } - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE if (extension.size() > 0) { @try { const auto types = [UTType @@ -253,25 +383,25 @@ namespace SSC { } } @catch (::id) {} } - #elif SSC_PLATFORM_LINUX + #elif SOCKET_RUNTIME_PLATFORM_LINUX gboolean typeGuessResultUncertain = false; gchar* type = nullptr; - type = g_content_type_guess(path.c_str(), nullptr, 0, &typeGuessResultUncertain); + type = g_content_type_guess(this->path.string().c_str(), nullptr, 0, &typeGuessResultUncertain); if (!type || typeGuessResultUncertain) { - const auto bytes = this->read(true); - const auto size = this->size(true); + const auto bytes = this->read(); + const auto size = this->size(); const auto nextType = g_content_type_guess( - path.c_str(), - data, - bytes, + reinterpret_cast(this->path.string().c_str()), + reinterpret_cast(bytes), + (gsize) size, &typeGuessResultUncertain ); if (nextType) { if (type) { - g_free(type) + g_free(type); } type = nextType; @@ -289,7 +419,7 @@ namespace SSC { } return mimeType; - #elif SSC_PLATFORM_WINDOWS + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS LPWSTR mimeData; const auto bytes = this->read(true) const auto size = this->size(true); @@ -307,11 +437,21 @@ namespace SSC { if (result == S_OK) { return convertWStringToString(WString(mimeData)); } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + if (extension.size() > 1) { + return Android::MimeTypeMap::sharedMimeTypeMap()->getMimeTypeFromExtension( + extension.starts_with(".") ? extension.substr(1) : extension + ); + } #endif return ""; } + size_t FileResource::size () const noexcept { + return this->cache.size; + } + size_t FileResource::size (bool cached) noexcept { if (cached && this->cache.size > 0) { return this->cache.size; @@ -321,7 +461,7 @@ namespace SSC { return -1; } - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->url != nullptr) { NSNumber* size = nullptr; NSError* error = nullptr; @@ -336,7 +476,7 @@ namespace SSC { this->cache.size = size.longLongValue; } - #elif SSC_PLATFORM_WINDOWS + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS LARGE_INTEGER fileSize; auto handle = CreateFile( this->path.c_str(), @@ -355,13 +495,40 @@ namespace SSC { } else { return -2; } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + bool success = false; + if (sharedAndroidAssetManager) { + const auto assetPath = getRelativeAndroidAssetManagerPath(this->path); + const auto asset = AAssetManager_open( + sharedAndroidAssetManager, + assetPath.c_str(), + AASSET_MODE_BUFFER + ); + + if (asset) { + this->cache.size = AAsset_getLength(asset); + AAsset_close(asset); + } + } + + if (!success) { + if (fs::exists(this->path)) { + this->cache.size = fs::file_size(this->path); + } + } #else - this->cache.size = fs::file_size(this->path); + if (fs::exists(this->path)) { + this->cache.size = fs::file_size(this->path); + } #endif return this->cache.size; } + const char* FileResource::read () const { + return this->cache.bytes.get(); + } + // caller takes ownership of returned pointer const char* FileResource::read (bool cached) { if (!this->accessing || !this->exists()) { @@ -369,37 +536,38 @@ namespace SSC { } if (cached && this->cache.bytes != nullptr) { - return *this->cache.bytes; + return this->cache.bytes.get(); } if (this->bytes != nullptr) { this->bytes = nullptr; } - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->url == nullptr) { return nullptr; } const auto data = [NSData dataWithContentsOfURL: this->url]; if (data.length > 0) { - this->bytes = std::make_shared(new char[data.length]{0}); - memcpy(*this->bytes, data.bytes, data.length); + this->bytes.reset(new char[data.length]{0}); + memcpy(this->bytes.get(), data.bytes, data.length); } - #elif SSC_PLATFORM_LINUX + #elif SOCKET_RUNTIME_PLATFORM_LINUX + GError* error = nullptr; char* contents = nullptr; gsize size = 0; if (g_file_get_contents(this->path.string().c_str(), &contents, &size, &error)) { if (size > 0) { - this->bytes = std::make_shared(new char[size]{0}); - memcpy(*this->bytes, contents, size); + this->bytes.reset(new char[size]{0}); + memcpy(this->bytes.get(), contents, size); } } if (contents) { g_free(contents); } - #elif SSC_PLATFORM_WINDOWS + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS auto handle = CreateFile( this->path.c_str(), GENERIC_READ, // access @@ -422,17 +590,62 @@ namespace SSC { ); if (result) { - this->bytes = std::make_shared(bytes); + this->bytes.reset(bytes); } else { delete [] bytes; } CloseHandle(handle); } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + bool success = false; + if (sharedAndroidAssetManager) { + const auto assetPath = getRelativeAndroidAssetManagerPath(this->path); + const auto asset = AAssetManager_open( + sharedAndroidAssetManager, + assetPath.c_str(), + AASSET_MODE_BUFFER + ); + + if (asset) { + auto size = AAsset_getLength(asset); + if (size) { + const auto buffer = AAsset_getBuffer(asset); + if (buffer) { + auto bytes = new char[size]{0}; + memcpy(bytes, buffer, size); + this->bytes.reset(bytes); + this->cache.size = size; + success = true; + } + } + + AAsset_close(asset); + } + } + + if (!success) { + auto stream = std::ifstream(this->path); + auto buffer = std::istreambuf_iterator(stream); + auto size = fs::file_size(this->path); + auto end = std::istreambuf_iterator(); + + auto bytes = new char[size]{0}; + String content; + + content.assign(buffer, end); + memcpy(bytes, content.data(), size); + + this->bytes.reset(bytes); + this->cache.size = size; + } #endif this->cache.bytes = this->bytes; - return *this->cache.bytes; + if (this->options.cache) { + caches.insert_or_assign(this->path.string(), this->cache); + } + return this->cache.bytes.get(); } const String FileResource::str (bool cached) { diff --git a/src/core/resource.hh b/src/core/resource.hh index c15e5ac1ca..d93fc621e6 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -1,8 +1,10 @@ -#ifndef SSC_CORE_RESOURCE -#define SSC_CORE_RESOURCE +#ifndef SOCKET_RUNTIME_CORE_RESOURCE_H +#define SOCKET_RUNTIME_CORE_RESOURCE_H -#include "platform.hh" -#include "types.hh" +#include "../platform/platform.hh" +#if SOCKET_RUNTIME_PLATFORM_ANDROID +#include "../platform/android.hh" +#endif namespace SSC { class Resource { @@ -14,14 +16,30 @@ namespace SSC { }; class FileResource : public Resource { - struct Cache { - SharedPointer bytes = nullptr; - size_t size = 0; - }; - - Cache cache; - SharedPointer bytes = nullptr; public: + #if SOCKET_RUNTIME_PLATFORM_ANDROID + static void setSharedAndroidAssetManager (Android::AssetManager*); + static Android::AssetManager* getSharedAndroidAssetManager (); + #endif + + static bool isFile (const String& resourcePath); + static bool isFile (const Path& resourcePath); + static bool isDirectory (const String& resourcePath); + static bool isDirectory (const Path& resourcePath); + + struct Cache { + SharedPointer bytes = nullptr; + size_t size = 0; + }; + + struct Options { + bool cache; + }; + + Cache cache; + Options options; + SharedPointer bytes = nullptr; + static std::map> mimeTypes; static Path getResourcesPath (); static Path getResourcePath (const Path& resourcePath); @@ -29,11 +47,13 @@ namespace SSC { Path path; - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE NSURL* url = nullptr; + #elif SOCKET_RUNTIME_PLATFORM_APPLE #endif - FileResource (const String& resourcePath); + FileResource (const Path& resourcePath, const Options& options = {}); + FileResource (const String& resourcePath, const Options& options = {}); ~FileResource (); FileResource (const FileResource&); FileResource (FileResource&&); @@ -46,9 +66,10 @@ namespace SSC { bool hasAccess () const noexcept; const String mimeType () const noexcept; size_t size (bool cached = false) noexcept; + size_t size () const noexcept; + const char* read () const; const char* read (bool cached = false); const String str (bool cached = false); }; } - #endif diff --git a/src/core/peer.cc b/src/core/socket.cc similarity index 60% rename from src/core/peer.cc rename to src/core/socket.cc index 194f9fc84b..0970b1ed66 100644 --- a/src/core/peer.cc +++ b/src/core/socket.cc @@ -1,84 +1,9 @@ +#include "modules/udp.hh" +#include "socket.hh" #include "core.hh" +#include "ip.hh" namespace SSC { - void Core::resumeAllPeers () { - dispatchEventLoop([=, this]() { - for (auto const &tuple : this->peers) { - auto peer = tuple.second; - if (peer != nullptr && (peer->isBound() || peer->isConnected())) { - peer->resume(); - } - } - }); - } - - void Core::pauseAllPeers () { - dispatchEventLoop([=, this]() { - for (auto const &tuple : this->peers) { - auto peer = tuple.second; - if (peer != nullptr && (peer->isBound() || peer->isConnected())) { - peer->pause(); - } - } - }); - } - - bool Core::hasPeer (uint64_t peerId) { - Lock lock(this->peersMutex); - return this->peers.find(peerId) != this->peers.end(); - } - - void Core::removePeer (uint64_t peerId) { - return this->removePeer(peerId, false); - } - - void Core::removePeer (uint64_t peerId, bool autoClose) { - if (this->hasPeer(peerId)) { - if (autoClose) { - auto peer = this->getPeer(peerId); - if (peer != nullptr) { - peer->close(); - } - } - - Lock lock(this->peersMutex); - this->peers.erase(peerId); - } - } - - Peer* Core::getPeer (uint64_t peerId) { - if (!this->hasPeer(peerId)) return nullptr; - Lock lock(this->peersMutex); - return this->peers.at(peerId); - } - - Peer* Core::createPeer (peer_type_t peerType, uint64_t peerId) { - return this->createPeer(peerType, peerId, false); - } - - Peer* Core::createPeer ( - peer_type_t peerType, - uint64_t peerId, - bool isEphemeral - ) { - if (this->hasPeer(peerId)) { - auto peer = this->getPeer(peerId); - if (peer != nullptr) { - if (isEphemeral) { - Lock lock(peer->mutex); - peer->flags = (peer_flag_t) (peer->flags | PEER_FLAG_EPHEMERAL); - } - } - - return peer; - } - - auto peer = new Peer(this, peerType, peerId, isEphemeral); - Lock lock(this->peersMutex); - this->peers[peer->id] = peer; - return peer; - } - int LocalPeerInfo::getsockname (uv_udp_t *socket, struct sockaddr *addr) { int namelen = sizeof(struct sockaddr_storage); return uv_udp_getsockname(socket, addr, &namelen); @@ -116,11 +41,11 @@ namespace SSC { void LocalPeerInfo::init (const struct sockaddr_storage *addr) { if (addr->ss_family == AF_INET) { this->family = "IPv4"; - this->address = addrToIPv4((struct sockaddr_in*) addr); + this->address = IP::addrToIPv4((struct sockaddr_in*) addr); this->port = (int) htons(((struct sockaddr_in*) addr)->sin_port); } else if (addr->ss_family == AF_INET6) { this->family = "IPv6"; - this->address = addrToIPv6((struct sockaddr_in6*) addr); + this->address = IP::addrToIPv6((struct sockaddr_in6*) addr); this->port = (int) htons(((struct sockaddr_in6*) addr)->sin6_port); } } @@ -162,18 +87,18 @@ namespace SSC { void RemotePeerInfo::init (const struct sockaddr_storage *addr) { if (addr->ss_family == AF_INET) { this->family = "IPv4"; - this->address = addrToIPv4((struct sockaddr_in*) addr); + this->address = IP::addrToIPv4((struct sockaddr_in*) addr); this->port = (int) htons(((struct sockaddr_in*) addr)->sin_port); } else if (addr->ss_family == AF_INET6) { this->family = "IPv6"; - this->address = addrToIPv6((struct sockaddr_in6*) addr); + this->address = IP::addrToIPv6((struct sockaddr_in6*) addr); this->port = (int) htons(((struct sockaddr_in6*) addr)->sin6_port); } } - Peer::Peer ( + Socket::Socket ( Core *core, - peer_type_t peerType, + socket_type_t peerType, uint64_t peerId, bool isEphemeral ) { @@ -182,29 +107,27 @@ namespace SSC { this->core = core; if (isEphemeral) { - this->flags = (peer_flag_t) (this->flags | PEER_FLAG_EPHEMERAL); + this->flags = (socket_flag_t) (this->flags | SOCKET_FLAG_EPHEMERAL); } this->init(); } - Peer::~Peer () { - this->core->removePeer(this->id, true); // auto close - } + Socket::~Socket () {} - int Peer::init () { + int Socket::init () { Lock lock(this->mutex); auto loop = this->core->getEventLoop(); int err = 0; memset(&this->handle, 0, sizeof(this->handle)); - if (this->type == PEER_TYPE_UDP) { + if (this->type == SOCKET_TYPE_UDP) { if ((err = uv_udp_init(loop, (uv_udp_t *) &this->handle))) { return err; } this->handle.udp.data = (void *) this; - } else if (this->type == PEER_TYPE_TCP) { + } else if (this->type == SOCKET_TYPE_TCP) { if ((err = uv_tcp_init(loop, (uv_tcp_t *) &this->handle))) { return err; } @@ -214,102 +137,102 @@ namespace SSC { return err; } - int Peer::initRemotePeerInfo () { + int Socket::initRemotePeerInfo () { Lock lock(this->mutex); - if (this->type == PEER_TYPE_UDP) { + if (this->type == SOCKET_TYPE_UDP) { this->remote.init((uv_udp_t *) &this->handle); - } else if (this->type == PEER_TYPE_TCP) { + } else if (this->type == SOCKET_TYPE_TCP) { this->remote.init((uv_tcp_t *) &this->handle); } return this->remote.err; } - int Peer::initLocalPeerInfo () { + int Socket::initLocalPeerInfo () { Lock lock(this->mutex); - if (this->type == PEER_TYPE_UDP) { + if (this->type == SOCKET_TYPE_UDP) { this->local.init((uv_udp_t *) &this->handle); - } else if (this->type == PEER_TYPE_TCP) { + } else if (this->type == SOCKET_TYPE_TCP) { this->local.init((uv_tcp_t *) &this->handle); } return this->local.err; } - void Peer::addState (peer_state_t value) { + void Socket::addState (socket_state_t value) { Lock lock(this->mutex); - this->state = (peer_state_t) (this->state | value); + this->state = (socket_state_t) (this->state | value); } - void Peer::removeState (peer_state_t value) { + void Socket::removeState (socket_state_t value) { Lock lock(this->mutex); - this->state = (peer_state_t) (this->state & ~value); + this->state = (socket_state_t) (this->state & ~value); } - bool Peer::hasState (peer_state_t value) { + bool Socket::hasState (socket_state_t value) { Lock lock(this->mutex); return (value & this->state) == value; } - const RemotePeerInfo* Peer::getRemotePeerInfo () { + const RemotePeerInfo* Socket::getRemotePeerInfo () { Lock lock(this->mutex); return &this->remote; } - const LocalPeerInfo* Peer::getLocalPeerInfo () { + const LocalPeerInfo* Socket::getLocalPeerInfo () { Lock lock(this->mutex); return &this->local; } - bool Peer::isUDP () { + bool Socket::isUDP () { Lock lock(this->mutex); - return this->type == PEER_TYPE_UDP; + return this->type == SOCKET_TYPE_UDP; } - bool Peer::isTCP () { + bool Socket::isTCP () { Lock lock(this->mutex); - return this->type == PEER_TYPE_TCP; + return this->type == SOCKET_TYPE_TCP; } - bool Peer::isEphemeral () { + bool Socket::isEphemeral () { Lock lock(this->mutex); - return (PEER_FLAG_EPHEMERAL & this->flags) == PEER_FLAG_EPHEMERAL; + return (SOCKET_FLAG_EPHEMERAL & this->flags) == SOCKET_FLAG_EPHEMERAL; } - bool Peer::isBound () { + bool Socket::isBound () { return ( - (this->isUDP() && this->hasState(PEER_STATE_UDP_BOUND)) || - (this->isTCP() && this->hasState(PEER_STATE_TCP_BOUND)) + (this->isUDP() && this->hasState(SOCKET_STATE_UDP_BOUND)) || + (this->isTCP() && this->hasState(SOCKET_STATE_TCP_BOUND)) ); } - bool Peer::isActive () { + bool Socket::isActive () { Lock lock(this->mutex); return uv_is_active((const uv_handle_t *) &this->handle); } - bool Peer::isClosing () { + bool Socket::isClosing () { Lock lock(this->mutex); return uv_is_closing((const uv_handle_t *) &this->handle); } - bool Peer::isClosed () { - return this->hasState(PEER_STATE_CLOSED); + bool Socket::isClosed () { + return this->hasState(SOCKET_STATE_CLOSED); } - bool Peer::isConnected () { + bool Socket::isConnected () { return ( - (this->isUDP() && this->hasState(PEER_STATE_UDP_CONNECTED)) || - (this->isTCP() && this->hasState(PEER_STATE_TCP_CONNECTED)) + (this->isUDP() && this->hasState(SOCKET_STATE_UDP_CONNECTED)) || + (this->isTCP() && this->hasState(SOCKET_STATE_TCP_CONNECTED)) ); } - bool Peer::isPaused () { + bool Socket::isPaused () { return ( - (this->isUDP() && this->hasState(PEER_STATE_UDP_PAUSED)) || - (this->isTCP() && this->hasState(PEER_STATE_TCP_PAUSED)) + (this->isUDP() && this->hasState(SOCKET_STATE_UDP_PAUSED)) || + (this->isTCP() && this->hasState(SOCKET_STATE_TCP_PAUSED)) ); } - int Peer::bind () { + int Socket::bind () { auto info = this->getLocalPeerInfo(); if (info->err) { @@ -319,11 +242,11 @@ namespace SSC { return this->bind(info->address, info->port, this->options.udp.reuseAddr); } - int Peer::bind (const String& address, int port) { + int Socket::bind (const String& address, int port) { return this->bind(address, port, false); } - int Peer::bind (const String& address, int port, bool reuseAddr) { + int Socket::bind (const String& address, int port, bool reuseAddr) { Lock lock(this->mutex); auto sockaddr = (struct sockaddr*) &this->addr; int flags = 0; @@ -345,7 +268,7 @@ namespace SSC { return err; } - this->addState(PEER_STATE_UDP_BOUND); + this->addState(SOCKET_STATE_UDP_BOUND); } if (this->isTCP()) { @@ -355,7 +278,7 @@ namespace SSC { return this->initLocalPeerInfo(); } - int Peer::rebind () { + int Socket::rebind () { int err = 0; if (this->isUDP()) { @@ -380,7 +303,7 @@ namespace SSC { return err; } - int Peer::connect (const String& address, int port) { + int Socket::connect (const String& address, int port) { Lock lock(this->mutex); auto sockaddr = (struct sockaddr*) &this->addr; int err = 0; @@ -394,13 +317,13 @@ namespace SSC { return err; } - this->addState(PEER_STATE_UDP_CONNECTED); + this->addState(SOCKET_STATE_UDP_CONNECTED); } return this->initRemotePeerInfo(); } - int Peer::disconnect () { + int Socket::disconnect () { int err = 0; if (this->isUDP()) { @@ -412,19 +335,19 @@ namespace SSC { return err; } - this->removeState(PEER_STATE_UDP_CONNECTED); + this->removeState(SOCKET_STATE_UDP_CONNECTED); } } return err; } - void Peer::send ( - SharedPointer bytes, + void Socket::send ( + SharedPointer bytes, size_t size, int port, const String& address, - Peer::RequestContext::Callback cb + const Socket::RequestContext::Callback& callback ) { Lock lock(this->mutex); int err = 0; @@ -436,24 +359,24 @@ namespace SSC { err = uv_ip4_addr((char *) address.c_str(), port, &this->addr); if (err) { - return cb(err, Post{}); + return callback(err, Post{}); } } - auto ctx = new Peer::RequestContext(size, bytes, cb); + auto ctx = new Socket::RequestContext(size, bytes, callback); auto req = new uv_udp_send_t; req->data = (void *) ctx; - ctx->peer = this; + ctx->socket = this; err = uv_udp_send(req, (uv_udp_t *) &this->handle, &ctx->buffer, 1, sockaddr, [](uv_udp_send_t *req, int status) { - auto ctx = reinterpret_cast(req->data); - auto peer = ctx->peer; + auto ctx = reinterpret_cast(req->data); + auto socket = ctx->socket; - ctx->cb(status, Post{}); + ctx->callback(status, Post{}); - if (peer->isEphemeral()) { - peer->close(); + if (socket->isEphemeral()) { + socket->close(); } delete ctx; @@ -461,7 +384,7 @@ namespace SSC { }); if (err < 0) { - ctx->cb(err, Post{}); + ctx->callback(err, Post{}); if (this->isEphemeral()) { this->close(); @@ -472,7 +395,7 @@ namespace SSC { } } - int Peer::recvstart () { + int Socket::recvstart () { if (this->receiveCallback != nullptr) { return this->recvstart(this->receiveCallback); } @@ -480,14 +403,14 @@ namespace SSC { return UV_EINVAL; } - int Peer::recvstart (Peer::UDPReceiveCallback receiveCallback) { + int Socket::recvstart (Socket::UDPReceiveCallback receiveCallback) { Lock lock(this->mutex); - if (this->hasState(PEER_STATE_UDP_RECV_STARTED)) { + if (this->hasState(SOCKET_STATE_UDP_RECV_STARTED)) { return UV_EALREADY; } - this->addState(PEER_STATE_UDP_RECV_STARTED); + this->addState(SOCKET_STATE_UDP_RECV_STARTED); this->receiveCallback = receiveCallback; auto allocate = [](uv_handle_t *handle, size_t size, uv_buf_t *buf) { @@ -504,24 +427,24 @@ namespace SSC { const struct sockaddr *addr, unsigned flags ) { - auto peer = (Peer *) handle->data; + auto socket = (Socket *) handle->data; if (nread == UV_ENOTCONN) { - peer->recvstop(); + socket->recvstop(); return; } - peer->receiveCallback(nread, buf, addr); + socket->receiveCallback(nread, buf, addr); }; return uv_udp_recv_start((uv_udp_t *) &this->handle, allocate, receive); } - int Peer::recvstop () { + int Socket::recvstop () { int err = 0; - if (this->hasState(PEER_STATE_UDP_RECV_STARTED)) { - this->removeState(PEER_STATE_UDP_RECV_STARTED); + if (this->hasState(SOCKET_STATE_UDP_RECV_STARTED)) { + this->removeState(SOCKET_STATE_UDP_RECV_STARTED); Lock lock(this->core->loopMutex); err = uv_udp_recv_stop((uv_udp_t *) &this->handle); } @@ -529,7 +452,7 @@ namespace SSC { return err; } - int Peer::resume () { + int Socket::resume () { int err = 0; if (this->isPaused()) { @@ -545,13 +468,13 @@ namespace SSC { // @TODO } - this->removeState(PEER_STATE_UDP_PAUSED); + this->removeState(SOCKET_STATE_UDP_PAUSED); } return err; } - int Peer::pause () { + int Socket::pause () { int err = 0; if ((err = this->recvstop())) { @@ -559,7 +482,7 @@ namespace SSC { } if (!this->isPaused() && !this->isClosing()) { - this->addState(PEER_STATE_UDP_PAUSED); + this->addState(SOCKET_STATE_UDP_PAUSED); if (this->isBound()) { Lock lock(this->mutex); uv_close((uv_handle_t *) &this->handle, nullptr); @@ -571,13 +494,13 @@ namespace SSC { return err; } - void Peer::close () { + void Socket::close () { return this->close(nullptr); } - void Peer::close (std::function onclose) { + void Socket::close (std::function onclose) { if (this->isClosed()) { - this->core->removePeer(this->id); + this->core->udp.removeSocket(this->id); if (onclose != nullptr) { onclose(); } @@ -596,23 +519,24 @@ namespace SSC { this->onclose.push_back(onclose); } - if (this->type == PEER_TYPE_UDP) { + if (this->type == SOCKET_TYPE_UDP) { Lock lock(this->mutex); // reset state and set to CLOSED uv_close((uv_handle_t*) &this->handle, [](uv_handle_t *handle) { - auto peer = (Peer *) handle->data; - if (peer != nullptr) { - peer->removeState((peer_state_t) ( - PEER_STATE_UDP_BOUND | - PEER_STATE_UDP_CONNECTED | - PEER_STATE_UDP_RECV_STARTED + auto socket = (Socket *) handle->data; + if (socket != nullptr) { + socket->removeState((socket_state_t) ( + SOCKET_STATE_UDP_BOUND | + SOCKET_STATE_UDP_CONNECTED | + SOCKET_STATE_UDP_RECV_STARTED )); - for (const auto &onclose : peer->onclose) { + for (const auto &onclose : socket->onclose) { onclose(); } - delete peer; + socket->core->udp.removeSocket(socket->id); + socket = nullptr; } }); } diff --git a/src/core/peer.hh b/src/core/socket.hh similarity index 63% rename from src/core/peer.hh rename to src/core/socket.hh index 2d5fd044c6..19475024a3 100644 --- a/src/core/peer.hh +++ b/src/core/socket.hh @@ -1,39 +1,38 @@ -#ifndef SSC_CORE_PEER_H -#define SSC_CORE_PEER_H -#include +#ifndef SOCKET_RUNTIME_CORE_SOCKET_H +#define SOCKET_RUNTIME_CORE_SOCKET_H -#include "types.hh" +#include "../platform/platform.hh" #include "post.hh" namespace SSC { class Core; typedef enum { - PEER_TYPE_NONE = 0, - PEER_TYPE_TCP = 1 << 1, - PEER_TYPE_UDP = 1 << 2, - PEER_TYPE_MAX = 0xF - } peer_type_t; + SOCKET_TYPE_NONE = 0, + SOCKET_TYPE_TCP = 1 << 1, + SOCKET_TYPE_UDP = 1 << 2, + SOCKET_TYPE_MAX = 0xF + } socket_type_t; typedef enum { - PEER_FLAG_NONE = 0, - PEER_FLAG_EPHEMERAL = 1 << 1 - } peer_flag_t; + SOCKET_FLAG_NONE = 0, + SOCKET_FLAG_EPHEMERAL = 1 << 1 + } socket_flag_t; typedef enum { - PEER_STATE_NONE = 0, + SOCKET_STATE_NONE = 0, // general states - PEER_STATE_CLOSED = 1 << 1, + SOCKET_STATE_CLOSED = 1 << 1, // udp states (10) - PEER_STATE_UDP_BOUND = 1 << 10, - PEER_STATE_UDP_CONNECTED = 1 << 11, - PEER_STATE_UDP_RECV_STARTED = 1 << 12, - PEER_STATE_UDP_PAUSED = 1 << 13, + SOCKET_STATE_UDP_BOUND = 1 << 10, + SOCKET_STATE_UDP_CONNECTED = 1 << 11, + SOCKET_STATE_UDP_RECV_STARTED = 1 << 12, + SOCKET_STATE_UDP_PAUSED = 1 << 13, // tcp states (20) - PEER_STATE_TCP_BOUND = 1 << 20, - PEER_STATE_TCP_CONNECTED = 1 << 21, - PEER_STATE_TCP_PAUSED = 1 << 13, - PEER_STATE_MAX = 1 << 0xF - } peer_state_t; + SOCKET_STATE_TCP_BOUND = 1 << 20, + SOCKET_STATE_TCP_CONNECTED = 1 << 21, + SOCKET_STATE_TCP_PAUSED = 1 << 13, + SOCKET_STATE_MAX = 1 << 0xF + } socket_state_t; struct LocalPeerInfo { struct sockaddr_storage addr; @@ -64,25 +63,25 @@ namespace SSC { }; /** - * A generic structure for a bound or connected peer. + * A generic structure for a bound or connected socket. */ - class Peer { + class Socket { public: struct RequestContext { using Callback = Function; - SharedPointer bytes = nullptr; + SharedPointer bytes = nullptr; size_t size = 0; uv_buf_t buffer; - Callback cb; - Peer *peer = nullptr; - RequestContext (Callback cb) { this->cb = cb; } - RequestContext (size_t size, SharedPointer bytes, Callback cb) { + Callback callback; + Socket* socket = nullptr; + RequestContext (Callback callback) { this->callback = callback; } + RequestContext (size_t size, SharedPointer bytes, Callback callback) { if (bytes != nullptr) { - this->buffer = uv_buf_init(*bytes, size); + this->buffer = uv_buf_init(bytes.get(), size); this->bytes = bytes; } this->size = size; - this->cb = cb; + this->callback = callback; } }; @@ -120,22 +119,22 @@ namespace SSC { // peer state LocalPeerInfo local; RemotePeerInfo remote; - peer_type_t type = PEER_TYPE_NONE; - peer_flag_t flags = PEER_FLAG_NONE; - peer_state_t state = PEER_STATE_NONE; + socket_type_t type = SOCKET_TYPE_NONE; + socket_flag_t flags = SOCKET_FLAG_NONE; + socket_state_t state = SOCKET_STATE_NONE; /** - * Private `Peer` class constructor + * Private `Socket` class constructor */ - Peer (Core *core, peer_type_t peerType, uint64_t peerId, bool isEphemeral); - ~Peer (); + Socket (Core *core, socket_type_t peerType, uint64_t peerId, bool isEphemeral); + ~Socket (); int init (); int initRemotePeerInfo (); int initLocalPeerInfo (); - void addState (peer_state_t value); - void removeState (peer_state_t value); - bool hasState (peer_state_t value); + void addState (socket_state_t value); + void removeState (socket_state_t value); + bool hasState (socket_state_t value); const RemotePeerInfo* getRemotePeerInfo (); const LocalPeerInfo* getLocalPeerInfo (); bool isUDP (); @@ -154,11 +153,11 @@ namespace SSC { int connect (const String& address, int port); int disconnect (); void send ( - SharedPointer bytes, + SharedPointer bytes, size_t size, int port, const String& address, - Peer::RequestContext::Callback cb + const Socket::RequestContext::Callback& callback ); int recvstart (); int recvstart (UDPReceiveCallback onrecv); diff --git a/src/core/udp.cc b/src/core/udp.cc deleted file mode 100644 index 9c67213f36..0000000000 --- a/src/core/udp.cc +++ /dev/null @@ -1,592 +0,0 @@ -#include "core.hh" - -namespace SSC { - static JSON::Object::Entries ERR_SOCKET_ALREADY_BOUND ( - const String& source, - uint64_t id - ) { - return JSON::Object::Entries { - {"source", source}, - {"err", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"type", "InternalError"}, - {"code", "ERR_SOCKET_ALREADY_BOUND"}, - {"message", "Socket is already bound"} - }} - }; - } - - static JSON::Object::Entries ERR_SOCKET_DGRAM_IS_CONNECTED ( - const String &source, - uint64_t id - ) { - return JSON::Object::Entries { - {"source", source}, - {"err", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"type", "InternalError"}, - {"code", "ERR_SOCKET_DGRAM_IS_CONNECTED"}, - {"message", "Already connected"} - }} - }; - } - - static JSON::Object::Entries ERR_SOCKET_DGRAM_NOT_CONNECTED ( - const String &source, - uint64_t id - ) { - return JSON::Object::Entries { - {"source", source}, - {"err", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"type", "InternalError"}, - {"code", "ERR_SOCKET_DGRAM_NOT_CONNECTED"}, - {"message", "Not connected"} - }} - }; - } - - static JSON::Object::Entries ERR_SOCKET_DGRAM_CLOSED ( - const String& source, - uint64_t id - ) { - return JSON::Object::Entries { - {"source", source}, - {"err", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"type", "InternalError"}, - {"code", "ERR_SOCKET_DGRAM_CLOSED"}, - {"message", "Socket is closed"} - }} - }; - } - - static JSON::Object::Entries ERR_SOCKET_DGRAM_CLOSING ( - const String& source, - uint64_t id - ) { - return JSON::Object::Entries { - {"source", source}, - {"err", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"type", "NotFoundError"}, - {"code", "ERR_SOCKET_DGRAM_CLOSING"}, - {"message", "Socket is closing"} - }} - }; - } - - static JSON::Object::Entries ERR_SOCKET_DGRAM_NOT_RUNNING ( - const String& source, - uint64_t id - ) { - return JSON::Object::Entries { - {"source", source}, - {"err", JSON::Object::Entries { - {"id", std::to_string(id)}, - {"type", "NotFoundError"}, - {"code", "ERR_SOCKET_DGRAM_NOT_RUNNING"}, - {"message", "Not running"} - }} - }; - } - - void Core::UDP::bind ( - const String seq, - uint64_t peerId, - UDP::BindOptions options, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - if (this->core->hasPeer(peerId)) { - if (this->core->getPeer(peerId)->isBound()) { - auto json = ERR_SOCKET_ALREADY_BOUND("udp.bind", peerId); - return cb(seq, json, Post{}); - } - } - - auto peer = this->core->createPeer(PEER_TYPE_UDP, peerId); - auto err = peer->bind(options.address, options.port, options.reuseAddr); - - if (err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.bind"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto info = peer->getLocalPeerInfo(); - - if (info->err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.bind"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(info->err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.bind"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"port", (int) info->port}, - {"event" , "listening"}, - {"family", info->family}, - {"address", info->address} - }} - }; - - cb(seq, json, Post{}); - }); - } - - void Core::UDP::connect ( - const String seq, - uint64_t peerId, - UDP::ConnectOptions options, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - auto peer = this->core->createPeer(PEER_TYPE_UDP, peerId); - - if (peer->isConnected()) { - auto json = ERR_SOCKET_DGRAM_IS_CONNECTED("udp.connect", peerId); - return cb(seq, json, Post{}); - } - - auto err = peer->connect(options.address, options.port); - - if (err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.connect"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto info = peer->getRemotePeerInfo(); - - if (info->err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.connect"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(info->err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.connect"}, - {"data", JSON::Object::Entries { - {"address", info->address}, - {"family", info->family}, - {"port", (int) info->port}, - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post{}); - }); - } - - void Core::UDP::disconnect ( - const String seq, - uint64_t peerId, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_CONNECTED("udp.disconnect", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - auto err = peer->disconnect(); - - if (err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.disconnect"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.disconnect"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post{}); - }); - } - - void Core::UDP::getPeerName (String seq, uint64_t peerId, Module::Callback cb) { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_CONNECTED("udp.getPeerName", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - auto info = peer->getRemotePeerInfo(); - - if (info->err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.getPeerName"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(info->err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.getPeerName"}, - {"data", JSON::Object::Entries { - {"address", info->address}, - {"family", info->family}, - {"port", (int) info->port}, - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post{}); - } - - void Core::UDP::getSockName (String seq, uint64_t peerId, Callback cb) { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.getSockName", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - auto info = peer->getLocalPeerInfo(); - - if (info->err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.getSockName"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(info->err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.getSockName"}, - {"data", JSON::Object::Entries { - {"address", info->address}, - {"family", info->family}, - {"port", (int) info->port}, - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post{}); - } - - void Core::UDP::getState ( - const String seq, - uint64_t peerId, - Module::Callback cb - ) { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.getState", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - - if (!peer->isUDP()) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.getState", peerId); - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.getState"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"type", "udp"}, - {"bound", peer->isBound()}, - {"active", peer->isActive()}, - {"closed", peer->isClosed()}, - {"closing", peer->isClosing()}, - {"connected", peer->isConnected()}, - {"ephemeral", peer->isEphemeral()} - }} - }; - - cb(seq, json, Post{}); - } - - void Core::UDP::send ( - String seq, - uint64_t peerId, - UDP::SendOptions options, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this] { - auto peer = this->core->createPeer(PEER_TYPE_UDP, peerId, options.ephemeral); - auto size = options.size; // @TODO(jwerle): validate MTU - auto port = options.port; - auto bytes = options.bytes; - auto address = options.address; - peer->send(bytes, size, port, address, [=](auto status, auto post) { - if (status < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.send"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(status))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.send"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"status", status} - }} - }; - - cb(seq, json, Post{}); - }); - }); - } - - void Core::UDP::readStart (String seq, uint64_t peerId, Module::Callback cb) { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.readStart", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - - if (peer->isClosed()) { - auto json = ERR_SOCKET_DGRAM_CLOSED("udp.readStart", peerId); - return cb(seq, json, Post{}); - } - - if (peer->isClosing()) { - auto json = ERR_SOCKET_DGRAM_CLOSING("udp.readStart", peerId); - return cb(seq, json, Post{}); - } - - if (peer->hasState(PEER_STATE_UDP_RECV_STARTED)) { - auto json = JSON::Object::Entries { - {"source", "udp.readStart"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", "Socket is already receiving"} - }} - }; - - return cb(seq, json, Post{}); - } - - if (peer->isActive()) { - auto json = JSON::Object::Entries { - {"source", "udp.readStart"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)} - }} - }; - - return cb(seq, json, Post{}); - } - - auto err = peer->recvstart([=](auto nread, auto buf, auto addr) { - if (nread == UV_EOF) { - auto json = JSON::Object::Entries { - {"source", "udp.readStart"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"EOF", true} - }} - }; - - cb("-1", json, Post{}); - } else if (nread > 0) { - char address[17] = {0}; - Post post; - int port; - - parseAddress((struct sockaddr *) addr, &port, address); - - auto headers = Headers {{ - {"content-type" ,"application/octet-stream"}, - {"content-length", nread} - }}; - - post.id = rand64(); - post.body = std::make_shared(buf->base); - post.length = (int) nread; - post.headers = headers.str(); - - auto json = JSON::Object::Entries { - {"source", "udp.readStart"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"port", port}, - {"bytes", std::to_string(post.length)}, - {"address", address} - }} - }; - - cb("-1", json, post); - } - }); - - // `UV_EALREADY || UV_EBUSY` could mean there might be - // active IO on the underlying handle - if (err < 0 && err != UV_EALREADY && err != UV_EBUSY) { - auto json = JSON::Object::Entries { - {"source", "udp.readStart"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.readStart"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post {}); - } - - void Core::UDP::readStop ( - const String seq, - uint64_t peerId, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this] { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.readStop", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - - if (peer->isClosed()) { - auto json = ERR_SOCKET_DGRAM_CLOSED("udp.readStop", peerId); - return cb(seq, json, Post{}); - } - - if (peer->isClosing()) { - auto json = ERR_SOCKET_DGRAM_CLOSING("udp.readStop", peerId); - return cb(seq, json, Post{}); - } - - if (!peer->hasState(PEER_STATE_UDP_RECV_STARTED)) { - auto json = JSON::Object::Entries { - {"source", "udp.readStop"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", "Socket is not receiving"} - }} - }; - - return cb(seq, json, Post{}); - } - - auto err = peer->recvstop(); - - if (err < 0) { - auto json = JSON::Object::Entries { - {"source", "udp.readStop"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(peerId)}, - {"message", String(uv_strerror(err))} - }} - }; - - return cb(seq, json, Post{}); - } - - auto json = JSON::Object::Entries { - {"source", "udp.readStop"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post {}); - }); - } - - void Core::UDP::close ( - const String seq, - uint64_t peerId, - Module::Callback cb - ) { - this->core->dispatchEventLoop([=, this]() { - if (!this->core->hasPeer(peerId)) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.close", peerId); - return cb(seq, json, Post{}); - } - - auto peer = this->core->getPeer(peerId); - - if (!peer->isUDP()) { - auto json = ERR_SOCKET_DGRAM_NOT_RUNNING("udp.close", peerId); - return cb(seq, json, Post{}); - } - - if (peer->isClosed()) { - auto json = ERR_SOCKET_DGRAM_CLOSED("udp.close", peerId); - return cb(seq, json, Post{}); - } - - if (peer->isClosing()) { - auto json = ERR_SOCKET_DGRAM_CLOSING("udp.close", peerId); - return cb(seq, json, Post{}); - } - - peer->close([=, this]() { - auto json = JSON::Object::Entries { - {"source", "udp.close"}, - {"data", JSON::Object::Entries { - {"id", std::to_string(peerId)} - }} - }; - - cb(seq, json, Post{}); - }); - }); - } -} diff --git a/src/core/url.cc b/src/core/url.cc index 7cc7d8bf7d..a0b1425eeb 100644 --- a/src/core/url.cc +++ b/src/core/url.cc @@ -1,5 +1,4 @@ #include "codec.hh" -#include "string.hh" #include "url.hh" namespace SSC { @@ -125,7 +124,7 @@ namespace SSC { this->hostname = hostParts[0]; } } else if (authorityParts.size() == 1) { - const auto hostParts = split(authorityParts[1], ':'); + const auto hostParts = split(authorityParts[0], ':'); if (hostParts.size() > 1) { this->port = hostParts[1]; } diff --git a/src/core/url.hh b/src/core/url.hh index af59e3bedc..6fe2cf6319 100644 --- a/src/core/url.hh +++ b/src/core/url.hh @@ -1,7 +1,6 @@ -#ifndef SSC_CORE_URL_H -#define SSC_CORE_URL_H +#ifndef SOCKET_RUNTIME_CORE_URL_H +#define SOCKET_RUNTIME_CORE_URL_H -#include "types.hh" #include "json.hh" namespace SSC { diff --git a/src/core/version.hh b/src/core/version.hh index 287636700d..46a64e5f9c 100644 --- a/src/core/version.hh +++ b/src/core/version.hh @@ -1,14 +1,13 @@ -#ifndef SSC_CORE_VERSION -#define SSC_CORE_VERSION +#ifndef SOCKET_RUNTIME_CORE_VERSION_H +#define SOCKET_RUNTIME_CORE_VERSION_H +#include "../platform/string.hh" #include "config.hh" -#include "string.hh" -#include "types.hh" namespace SSC { - inline const auto VERSION_FULL_STRING = String(CONVERT_TO_STRING(SSC_VERSION) " (" CONVERT_TO_STRING(SSC_VERSION_HASH) ")"); - inline const auto VERSION_HASH_STRING = String(CONVERT_TO_STRING(SSC_VERSION_HASH)); - inline const auto VERSION_STRING = String(CONVERT_TO_STRING(SSC_VERSION)); + inline const auto VERSION_FULL_STRING = String(CONVERT_TO_STRING(SOCKET_RUNTIME_VERSION) " (" CONVERT_TO_STRING(SOCKET_RUNTIME_VERSION_HASH) ")"); + inline const auto VERSION_HASH_STRING = String(CONVERT_TO_STRING(SOCKET_RUNTIME_VERSION_HASH)); + inline const auto VERSION_STRING = String(CONVERT_TO_STRING(SOCKET_RUNTIME_VERSION)); } #endif diff --git a/src/core/webview.cc b/src/core/webview.cc new file mode 100644 index 0000000000..dc9eb85d87 --- /dev/null +++ b/src/core/webview.cc @@ -0,0 +1,694 @@ +#include "webview.hh" + +#if SOCKET_RUNTIME_PLATFORM_APPLE +#if SOCKET_RUNTIME_PLATFORM_MACOS +@interface WKOpenPanelParameters (WKPrivate) +- (NSArray*) _acceptedMIMETypes; +- (NSArray*) _acceptedFileExtensions; +- (NSArray*) _allowedFileExtensions; +@end +#endif + +@implementation SSCBridgedWebView +#if SOCKET_RUNTIME_PLATFORM_MACOS +Vector draggablePayload; +CGFloat MACOS_TRAFFIC_LIGHT_BUTTON_SIZE = 16; +int lastX = 0; +int lastY = 0; + +- (void) viewDidAppear: (BOOL) animated { +} + +- (void) viewDidDisappear: (BOOL) animated { +} + +- (void) viewDidChangeEffectiveAppearance { + [super viewDidChangeEffectiveAppearance]; + + if ([self.window.effectiveAppearance.name containsString: @"Dark"]) { + [self.window setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.1 alpha: 1.0]]; // Dark mode color + } else { + [self.window setBackgroundColor: [NSColor colorWithCalibratedWhite: 1.0 alpha: 1.0]]; // Light mode color + } +} + +- (void) resizeSubviewsWithOldSize: (NSSize) oldSize { + [super resizeSubviewsWithOldSize: oldSize]; + + const auto w = reinterpret_cast(self.window); + const auto viewWidth = w.titleBarView.frame.size.width; + const auto viewHeight = w.titleBarView.frame.size.height; + const auto newX = w.windowControlOffsets.x; + const auto newY = 0.f; + + const auto closeButton = [w standardWindowButton: NSWindowCloseButton]; + const auto minimizeButton = [w standardWindowButton: NSWindowMiniaturizeButton]; + const auto zoomButton = [w standardWindowButton: NSWindowZoomButton]; + + if (closeButton && minimizeButton && zoomButton) { + [w.titleBarView addSubview: closeButton]; + [w.titleBarView addSubview: minimizeButton]; + [w.titleBarView addSubview: zoomButton]; + } + + w.titleBarView.frame = NSMakeRect(newX, newY, viewWidth, viewHeight); +} + +- (instancetype) initWithFrame: (NSRect) frameRect + configuration: (WKWebViewConfiguration*) configuration + radius: (CGFloat) radius + margin: (CGFloat) margin +{ + self = [super initWithFrame: frameRect configuration: configuration]; + + if (self && radius > 0.0) { + self.radius = radius; + self.margin = margin; + self.layer.cornerRadius = radius; + self.layer.masksToBounds = YES; + } + + return self; +} + +- (void) layout { + [super layout]; + + NSRect bounds = self.superview.bounds; + + if (self.radius > 0.0) { + if (self.contentHeight == 0.0) { + self.contentHeight = self.superview.bounds.size.height - self.bounds.size.height; + } + + bounds.size.height = bounds.size.height - self.contentHeight; + } + + if (self.margin > 0.0) { + CGFloat borderWidth = self.margin; + self.frame = NSInsetRect(bounds, borderWidth, borderWidth); + } +} + +- (BOOL) wantsPeriodicDraggingUpdates { + return YES; +} + +- (BOOL) prepareForDragOperation: (id) info { + [info setDraggingFormation: NSDraggingFormationNone]; + return YES; +} + +- (BOOL) performDragOperation: (id) info { + return YES; +} + +- (void) concludeDragOperation: (id) info { +} + +- (void) updateDraggingItemsForDrag: (id) info { +} + +- (NSDragOperation) draggingEntered: (id) info { + const auto json = JSON::Object {}; + const auto payload = getEmitToRenderProcessJavaScript("dragenter", json.str()); + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; + [self draggingUpdated: info]; + return NSDragOperationGeneric; +} + +- (NSDragOperation) draggingUpdated: (id) info { + const auto position = info.draggingLocation; + const auto x = std::to_string(position.x); + const auto y = std::to_string(self.frame.size.height - position.y); + + auto count = draggablePayload.size(); + auto inbound = false; + + if (count == 0) { + inbound = true; + count = [info numberOfValidItemsForDrop]; + } + + const auto data = JSON::Object::Entries { + {"count", (unsigned int) count}, + {"inbound", inbound}, + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object {data}; + const auto payload = getEmitToRenderProcessJavaScript("drag", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; + return [super draggingUpdated: info]; +} + +- (void) draggingExited: (id) info { + const auto position = info.draggingLocation; + const auto x = std::to_string(position.x); + const auto y = std::to_string(self.frame.size.height - position.y); + + const auto data = JSON::Object::Entries { + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object {data}; + const auto payload = getEmitToRenderProcessJavaScript("dragend", json.str()); + + draggablePayload.clear(); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; +} + +- (void) draggingEnded: (id) info { + const auto pasteboard = info.draggingPasteboard; + const auto position = info.draggingLocation; + const auto x = position.x; + const auto y = self.frame.size.height - position.y; + + const auto pasteboardFiles = [pasteboard + readObjectsForClasses: @[NSURL.class] + options: @{} + ]; + + auto files = JSON::Array::Entries {}; + + for (NSURL* file in pasteboardFiles) { + files.push_back(file.path.UTF8String); + } + + const auto data = JSON::Object::Entries { + {"files", files}, + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object { data }; + const auto payload = getEmitToRenderProcessJavaScript("dropin", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; +} + +- (void) updateEvent: (NSEvent*) event { + const auto location = [self convertPoint: event.locationInWindow fromView :nil]; + const auto x = std::to_string(location.x); + const auto y = std::to_string(location.y); + const auto count = draggablePayload.size(); + + if (((int) location.x) == lastX || ((int) location.y) == lastY) { + return [super mouseDown: event]; + } + + const auto data = JSON::Object::Entries { + {"count", (unsigned int) count}, + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object { data }; + const auto payload = getEmitToRenderProcessJavaScript("drag", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; +} + +- (void) mouseUp: (NSEvent*) event { + [super mouseUp: event]; + + const auto location = [self convertPoint: event.locationInWindow fromView: nil]; + const auto x = location.x; + const auto y = location.y; + + const auto significantMoveX = (lastX - x) > 6 || (x - lastX) > 6; + const auto significantMoveY = (lastY - y) > 6 || (y - lastY) > 6; + + if (significantMoveX || significantMoveY) { + for (const auto& path : draggablePayload) { + const auto data = JSON::Object::Entries { + {"src", path}, + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object { data }; + const auto payload = getEmitToRenderProcessJavaScript("drop", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; + } + } + + const auto data = JSON::Object::Entries { + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object { data }; + auto payload = getEmitToRenderProcessJavaScript("dragend", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; +} + +- (void) mouseDown: (NSEvent*) event { + self.shouldDrag = false; + draggablePayload.clear(); + + const auto location = [self convertPoint: event.locationInWindow fromView: nil]; + const auto x = std::to_string(location.x); + const auto y = std::to_string(location.y); + + self.initialWindowPos = location; + + lastX = (int) location.x; + lastY = (int) location.y; + + String js( + "(() => { " + " const v = '--app-region'; " + " let el = document.elementFromPoint(" + x + "," + y + "); " + " " + " while (el) { " + " if (getComputedStyle(el).getPropertyValue(v) == 'drag') return 'movable'; " + " el = el.parentElement; " + " } " + " return '' " + "})() " + ); + + [self + evaluateJavaScript: @(js.c_str()) + completionHandler: ^(id result, NSError *error) + { + if (error) { + NSLog(@"%@", error); + [super mouseDown: event]; + return; + } + + if (![result isKindOfClass: NSString.class]) { + [super mouseDown: event]; + return; + } + + const auto match = String([result UTF8String]); + + if (match.compare("movable") != 0) { + [super mouseDown: event]; + return; + } + + self.shouldDrag = true; + [self updateEvent: event]; + }]; +} + +- (void) mouseDragged: (NSEvent*) event { + NSPoint currentLocation = [self convertPoint:event.locationInWindow fromView:nil]; + + if (self.shouldDrag) { + CGFloat deltaX = currentLocation.x - self.initialWindowPos.x; + CGFloat deltaY = currentLocation.y - self.initialWindowPos.y; + + NSRect frame = self.window.frame; + frame.origin.x += deltaX; + frame.origin.y -= deltaY; + + [self.window setFrame:frame display:YES]; + } + + [super mouseDragged:event]; + + if (!NSPointInRect(currentLocation, self.frame)) { + auto payload = getEmitToRenderProcessJavaScript("dragexit", "{}"); + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; + } + + /* + + // TODO(@heapwolf): refactor the legacy native multi-file drag-drop stuff + + if (draggablePayload.size() == 0) { + return; + } + + const auto x = location.x; + const auto y = location.y; + const auto significantMoveX = (lastX - x) > 6 || (x - lastX) > 6; + const auto significantMoveY = (lastY - y) > 6 || (y - lastY) > 6; + + if (significantMoveX || significantMoveY) { + const auto data = JSON::Object::Entries { + {"count", (unsigned int) draggablePayload.size()}, + {"x", x}, + {"y", y} + }; + + const auto json = JSON::Object { data }; + const auto payload = getEmitToRenderProcessJavaScript("drag", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; + } + + if (NSPointInRect(location, self.frame)) { + return; + } + + const auto pasteboard = [NSPasteboard pasteboardWithName: NSPasteboardNameDrag]; + const auto dragItems = [NSMutableArray new]; + const auto iconSize = NSMakeSize(32, 32); // according to documentation + + [pasteboard declareTypes: @[(NSString*) kPasteboardTypeFileURLPromise] owner:self]; + + auto dragPosition = [self convertPoint: event.locationInWindow fromView: nil]; + dragPosition.x -= 16; + dragPosition.y -= 16; + + NSRect imageLocation; + imageLocation.origin = dragPosition; + imageLocation.size = iconSize; + + for (const auto& file : draggablePayload) { + const auto url = [NSURL fileURLWithPath: @(file.c_str())]; + const auto icon = [NSWorkspace.sharedWorkspace iconForContentType: UTTypeURL]; + + NSArray* (^providerBlock)() = ^NSArray* () { + const auto component = [ + [NSDraggingImageComponent.alloc initWithKey: NSDraggingImageComponentIconKey + ] retain]; + + component.frame = NSMakeRect(0, 0, iconSize.width, iconSize.height); + component.contents = icon; + return @[component]; + }; + + auto provider = [NSFilePromiseProvider.alloc initWithFileType: @"public.url" delegate: self]; + + [provider setUserInfo: @(file.c_str())]; + + auto dragItem = [NSDraggingItem.alloc initWithPasteboardWriter: provider]; + + dragItem.draggingFrame = NSMakeRect( + dragPosition.x, + dragPosition.y, + iconSize.width, + iconSize.height + ); + + dragItem.imageComponentsProvider = providerBlock; + [dragItems addObject: dragItem]; + } + + auto session = [self + beginDraggingSessionWithItems: dragItems + event: event + source: self + ]; + + session.draggingFormation = NSDraggingFormationPile; + draggablePayload.clear(); + */ +} + +- (NSDragOperation) draggingSession: (NSDraggingSession*) session + sourceOperationMaskForDraggingContext: (NSDraggingContext) context +{ + return NSDragOperationGeneric; +} + +- (void) filePromiseProvider: (NSFilePromiseProvider*) filePromiseProvider + writePromiseToURL: (NSURL*) url + completionHandler: (void (^)(NSError *errorOrNil)) completionHandler +{ + const auto dest = String(url.path.UTF8String); + const auto src = String([filePromiseProvider.userInfo UTF8String]); + const auto data = [@"" dataUsingEncoding: NSUTF8StringEncoding]; + + [data writeToURL: url atomically: YES]; + + const auto json = JSON::Object { + JSON::Object::Entries { + {"src", src}, + {"dest", dest} + } + }; + + const auto payload = getEmitToRenderProcessJavaScript("dropout", json.str()); + + [self evaluateJavaScript: @(payload.c_str()) completionHandler: nil]; + + completionHandler(nil); +} + +- (NSString*) filePromiseProvider: (NSFilePromiseProvider*) filePromiseProvider + fileNameForType: (NSString*) fileType +{ + const auto id = rand64(); + const auto filename = std::to_string(id) + ".download"; + return @(filename.c_str()); +} + +- (void) webView: (WKWebView*) webView + runOpenPanelWithParameters: (WKOpenPanelParameters*) parameters + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(NSArray*URLs)) completionHandler +{ + const auto acceptedFileExtensions = parameters._acceptedFileExtensions; + const auto acceptedMIMETypes = parameters._acceptedMIMETypes; + StringStream contentTypesSpec; + + for (NSString* acceptedMIMEType in acceptedMIMETypes) { + contentTypesSpec << acceptedMIMEType.UTF8String << "|"; + } + + if (acceptedFileExtensions.count > 0) { + contentTypesSpec << "*/*:"; + const auto count = acceptedFileExtensions.count; + int seen = 0; + for (NSString* acceptedFileExtension in acceptedFileExtensions) { + const auto string = String(acceptedFileExtension.UTF8String); + + if (!string.starts_with(".")) { + contentTypesSpec << "."; + } + + contentTypesSpec << string; + if (++seen < count) { + contentTypesSpec << ","; + } + } + } + + auto contentTypes = trim(contentTypesSpec.str()); + + if (contentTypes.size() == 0) { + contentTypes = "*/*"; + } + + if (contentTypes.ends_with("|")) { + contentTypes = contentTypes.substr(0, contentTypes.size() - 1); + } + + const auto options = Dialog::FileSystemPickerOptions { + .directories = false, + .multiple = parameters.allowsMultipleSelection ? true : false, + .contentTypes = contentTypes, + .defaultName = "", + .defaultPath = "", + .title = "Choose a File" + }; + + Dialog dialog; + const auto results = dialog.showOpenFilePicker(options); + + if (results.size() == 0) { + completionHandler(nullptr); + return; + } + + auto urls = [NSMutableArray array]; + + for (const auto& result : results) { + [urls addObject: [NSURL URLWithString: @(result.c_str())]]; + } + + completionHandler(urls); +} +#endif + +#if (!SOCKET_RUNTIME_PLATFORM_IOS_SIMULATOR) || (SOCKET_RUNTIME_PLATFORM_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_15) +- (void) webView: (WKWebView*) webView + requestDeviceOrientationAndMotionPermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler { + static auto userConfig = getUserConfig(); + + if (userConfig["permissions_allow_device_orientation"] == "false") { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + decisionHandler(WKPermissionDecisionGrant); +} + +- (void) webView: (WKWebView*) webView + requestMediaCapturePermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + type: (WKMediaCaptureType) type + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler { + static auto userConfig = getUserConfig(); + + if (userConfig["permissions_allow_user_media"] == "false") { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + if (type == WKMediaCaptureTypeCameraAndMicrophone) { + if ( + userConfig["permissions_allow_camera"] == "false" || + userConfig["permissions_allow_microphone"] == "false" + ) { + decisionHandler(WKPermissionDecisionDeny); + return; + } + } + + if ( + type == WKMediaCaptureTypeCamera && + userConfig["permissions_allow_camera"] == "false" + ) { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + if ( + type == WKMediaCaptureTypeMicrophone && + userConfig["permissions_allow_microphone"] == "false" + ) { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + decisionHandler(WKPermissionDecisionGrant); +} +#endif + +- (void) webView: (SSCBridgedWebView*) webview + runJavaScriptAlertPanelWithMessage: (NSString*) message + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(void)) completionHandler +{ + static auto userConfig = getUserConfig(); + const auto title = userConfig.contains("window_alert_title") + ? userConfig["window_alert_title"] + : userConfig["meta_title"] + ":"; + +#if SOCKET_RUNTIME_PLATFORM_IOS + const auto alert = [UIAlertController + alertControllerWithTitle: @(title.c_str()) + message: message + preferredStyle: UIAlertControllerStyleAlert + ]; + + const auto ok = [UIAlertAction + actionWithTitle: @"OK" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + completionHandler(); + }]; + + [alert addAction: ok]; + + [webview.window.rootViewController + presentViewController: alert + animated: YES + completion: nil + ]; +#else + const auto alert = [NSAlert new]; + [alert setMessageText: @(title.c_str())]; + [alert setInformativeText: message]; + [alert addButtonWithTitle: @"OK"]; + [alert runModal]; + completionHandler(); +#if !__has_feature(objc_arc) + [alert release]; +#endif +#endif +} + +#if SOCKET_RUNTIME_PLATFORM_IOS +- (void)traitCollectionDidChange:(UITraitCollection *) previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + static auto userConfig = getUserConfig(); + const auto window = (Window*) objc_getAssociatedObject(self, "window"); + + UIUserInterfaceStyle interfaceStyle = window->window.traitCollection.userInterfaceStyle; + + auto hasBackgroundDark = userConfig.count("window_background_color_dark") > 0; + auto hasBackgroundLight = userConfig.count("window_background_color_light") > 0; + + if (interfaceStyle == UIUserInterfaceStyleDark && hasBackgroundDark) { + window->setBackgroundColor(userConfig["window_background_color_dark"]); + } else if (hasBackgroundLight) { + window->setBackgroundColor(userConfig["window_background_color_light"]); + } +} +#endif + +- (void) webView: (WKWebView*) webview + runJavaScriptConfirmPanelWithMessage: (NSString*) message + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(BOOL result)) completionHandler +{ + static auto userConfig = getUserConfig(); + const auto title = userConfig.contains("window_alert_title") + ? userConfig["window_alert_title"] + : userConfig["meta_title"] + ":"; + +#if SOCKET_RUNTIME_PLATFORM_IOS + const auto alert = [UIAlertController + alertControllerWithTitle: @(title.c_str()) + message: message + preferredStyle: UIAlertControllerStyleAlert + ]; + + const auto ok = [UIAlertAction + actionWithTitle: @"OK" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + completionHandler(YES); + }]; + + const auto cancel = [UIAlertAction + actionWithTitle: @"Cancel" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + completionHandler(NO); + }]; + + [alert addAction: ok]; + [alert addAction: cancel]; + + [webview.window.rootViewController + presentViewController: alert + animated: YES + completion: nil + ]; +#else + const auto alert = [NSAlert new]; + [alert setMessageText: @(title.c_str())]; + [alert setInformativeText: message]; + [alert addButtonWithTitle: @"OK"]; + [alert addButtonWithTitle: @"Cancel"]; + completionHandler([alert runModal] == NSAlertFirstButtonReturn); +#if !__has_feature(objc_arc) + [alert release]; +#endif +#endif +} +@end + +@implementation SSCWebViewController +@end +#endif diff --git a/src/window/webview.hh b/src/core/webview.hh similarity index 68% rename from src/window/webview.hh rename to src/core/webview.hh index 6fb7853320..cdeb40115d 100644 --- a/src/window/webview.hh +++ b/src/core/webview.hh @@ -1,17 +1,11 @@ -#ifndef SSC_WINDOW_WEBVIEW_H -#define SSC_WINDOW_WEBVIEW_H +#ifndef SOCKET_RUNTIME_WINDOW_WEBVIEW_H +#define SOCKET_RUNTIME_WINDOW_WEBVIEW_H -#include "../core/types.hh" -#include "../core/platform.hh" +#include "../platform/platform.hh" -namespace SSC { - // forward - class Window; -} - -#if SSC_PLATFORM_APPLE -@interface SSCBridgedWebView : -#if SSC_PLATFORM_IOS +#if SOCKET_RUNTIME_PLATFORM_APPLE +@interface SSCWebView : +#if SOCKET_RUNTIME_PLATFORM_IOS WKWebView @property (strong, nonatomic) NSLayoutConstraint *keyboardHeightConstraint; #else @@ -29,7 +23,7 @@ namespace SSC { @property (nonatomic) BOOL shouldDrag; #endif -#if SSC_PLATFORM_MACOS +#if SOCKET_RUNTIME_PLATFORM_MACOS - (NSDragOperation) draggingSession: (NSDraggingSession *) session sourceOperationMaskForDraggingContext: (NSDraggingContext) context; @@ -39,7 +33,7 @@ namespace SSC { completionHandler: (void (^)(NSArray*)) completionHandler; #endif -#if SSC_PLATFORM_MACOS || (SSC_PLATFORM_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_15) +#if SOCKET_RUNTIME_PLATFORM_MACOS || (SOCKET_RUNTIME_PLATFORM_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_15) - (void) webView: (WKWebView*) webView requestDeviceOrientationAndMotionPermissionForOrigin: (WKSecurityOrigin*) origin @@ -63,22 +57,42 @@ namespace SSC { initiatedByFrame: (WKFrameInfo*) frame completionHandler: (void (^)(BOOL result)) completionHandler; @end + +#if SOCKET_RUNTIME_PLATFORM_MACOS +@interface WKOpenPanelParameters (WKPrivate) + - (NSArray*) _acceptedMIMETypes; + - (NSArray*) _acceptedFileExtensions; + - (NSArray*) _allowedFileExtensions; +@end +#endif +#if SOCKET_RUNTIME_PLATFORM_IOS +@interface SSCWebViewController : UIViewController + @property (nonatomic, strong) SSCWebView* webview; +@end +#endif #endif namespace SSC { -#if SSC_PLATFORM_APPLE - using WebView = SSCBridgedWebView; +#if SOCKET_RUNTIME_PLATFORM_ANDROID + class CoreAndroidWebView; + class CoreAndroidWebViewSettings; +#endif + +#if SOCKET_RUNTIME_PLATFORM_APPLE + using WebView = SSCWebView; using WebViewSettings = WKWebViewConfiguration; -#elif SSC_PLATFORM_LINUX +#elif SOCKET_RUNTIME_PLATFORM_LINUX using WebView = WebKitWebView; using WebViewSettings = WebKitSettings; -#elif SSC_PLATFORM_WINDOWS +#elif SOCKET_RUNTIME_PLATFORM_WINDOWS using WebView = ICoreWebView2; using WebViewSettings = ComPtr; +#elif SOCKET_RUNTIME_PLATFORM_ANDROID + using WebView = CoreAndroidWebView; + using WebViewSettings = CoreAndroidWebViewSettings; #else struct WebView; struct WebViewSettings; #endif } - #endif diff --git a/src/core/webview.kt b/src/core/webview.kt new file mode 100644 index 0000000000..a54dc78cd5 --- /dev/null +++ b/src/core/webview.kt @@ -0,0 +1,18 @@ +// vim: set sw=2: +package socket.runtime.core + +import java.lang.ref.WeakReference + +import android.content.Context +import android.util.AttributeSet +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse + +/** + * @see https://developer.android.com/reference/kotlin/android/webkit/WebView + */ +open class WebView (context: android.content.Context) : android.webkit.WebView(context) + +open class WebChromeClient : android.webkit.WebChromeClient() {} +open class WebViewClient : android.webkit.WebViewClient() { +} diff --git a/src/desktop/main.cc b/src/desktop/main.cc index ae8ed4a1d7..3c0dcb591f 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -2,11 +2,9 @@ #include "../cli/cli.hh" #include "../ipc/ipc.hh" #include "../core/core.hh" -#include "../core/ini.hh" -#include "../process/process.hh" #include "../window/window.hh" -#if SSC_PLATFORM_LINUX +#if SOCKET_RUNTIME_PLATFORM_LINUX #include #include #endif @@ -21,7 +19,7 @@ // A cross platform MAIN macro that // magically gives us argc and argv. // -#if SSC_PLATFORM_WINDOWS +#if SOCKET_RUNTIME_PLATFORM_WINDOWS #define MAIN \ static const int argc = __argc; \ static char** argv = __argv; \ @@ -36,7 +34,7 @@ int main (int argc, char** argv) #endif -#if SSC_PLATFORM_APPLE +#if SOCKET_RUNTIME_PLATFORM_APPLE #include #endif @@ -44,7 +42,7 @@ String("Invalid index given for window: ") + std::to_string(index) static void installSignalHandler (int signum, void (*handler)(int)) { -#if SSC_PLATFORM_LINUX +#if SOCKET_RUNTIME_PLATFORM_LINUX struct sigaction action; sigemptyset(&action.sa_mask); action.sa_handler = handler; @@ -90,9 +88,9 @@ void signalHandler (int signal) { static const auto signals = parseStringList(userConfig["application_signals"]); String name; - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE name = String(sys_signame[signal]); - #elif SSC_PLATFORM_LINUX + #elif SOCKET_RUNTIME_PLATFORM_LINUX name = strsignal(signal); #endif @@ -107,32 +105,7 @@ void signalHandler (int signal) { } } -String getNavigationError (const String &cwd, const String &value) { - auto url = value.substr(7); - - if (!value.starts_with("socket://") && !value.starts_with("socket://")) { - return String("only socket:// protocol is allowed for the file navigation. Got url ") + value; - } - - if (url.empty()) { - return String("empty url"); - } - - return String(""); -} - -inline const Vector splitToInts (const String& s, const char& c) { - Vector result; - String token; - std::istringstream ss(s); - - while (std::getline(ss, token, c)) { - result.push_back(std::stoi(token)); - } - return result; -} - -#if SSC_PLATFORM_LINUX +#if SOCKET_RUNTIME_PLATFORM_LINUX static void handleApplicationURLEvent (const String url) { JSON::Object json = JSON::Object::Entries {{ "url", url @@ -187,7 +160,7 @@ static DBusHandlerResult onDBusMessage ( return DBUS_HANDLER_RESULT_HANDLED; } -#elif SSC_PLATFORM_WINDOWS +#elif SOCKET_RUNTIME_PLATFORM_WINDOWS BOOL registerWindowsURISchemeInRegistry () { static auto userConfig = getUserConfig(); HKEY shellKey; @@ -260,7 +233,7 @@ BOOL registerWindowsURISchemeInRegistry () { // which on windows is hInstance, on mac and linux this is just an int. // MAIN { -#if SSC_PLATFORM_LINUX +#if SOCKET_RUNTIME_PLATFORM_LINUX // use 'SIGPWR' instead of the default 'SIGUSR1' handler // see https://github.com/WebKit/WebKit/blob/2fd8f81aac4e867ffe107c0e1b3e34b1628c0953/Source/WTF/wtf/posix/ThreadingPOSIX.cpp#L185 Env::set("JSC_SIGNAL_FOR_GC", "30"); @@ -279,8 +252,8 @@ MAIN { // windowManager instance. app_ptr = &app; - const String _host = getDevHost(); - const auto _port = getDevPort(); + const String devHost = getDevHost(); + const auto devPort = getDevPort(); const String OK_STATE = "0"; const String ERROR_STATE = "1"; @@ -290,7 +263,7 @@ MAIN { String suffix = ""; - StringStream argvArray; + Vector argvArray; StringStream argvForward; bool isCommandMode = false; @@ -306,7 +279,7 @@ MAIN { auto bundleIdentifier = userConfig["meta_bundle_identifier"]; -#if SSC_PLATFORM_LINUX +#if SOCKET_RUNTIME_PLATFORM_LINUX static const auto TMPDIR = Env::get("TMPDIR", "/tmp"); static const auto appInstanceLock = fs::path(TMPDIR) / (bundleIdentifier + ".lock"); auto appInstanceLockFd = open(appInstanceLock.c_str(), O_CREAT | O_EXCL, 0600); @@ -442,7 +415,7 @@ MAIN { g_signal_connect(gtkApp, "activate", G_CALLBACK(onGTKApplicationActivation), NULL); } -#elif SSC_PLATFORM_WINDOWS +#elif SOCKET_RUNTIME_PLATFORM_WINDOWS HANDLE hMutex = CreateMutex(NULL, TRUE, bundleIdentifier.c_str()); auto lastWindowsError = GetLastError(); auto appProtocol = userConfig["meta_application_protocol"]; @@ -492,10 +465,7 @@ MAIN { for (auto const arg : std::span(argv, argc)) { auto s = String(arg); - argvArray - << "'" - << replace(s, "'", "\'") - << (c++ < argc ? "', " : "'"); + argvArray.push_back("'" + replace(s, "'", "\'") + "'"); bool helpRequested = ( (s.find("--help") == 0) || @@ -530,7 +500,7 @@ MAIN { // launched from the `ssc` cli app.wasLaunchedFromCli = s.find("--from-ssc") == 0 ? true : false; - #ifdef _WIN32 + #if SOCKET_RUNTIME_PLATFORM_WINDOWS if (!app.w32ShowConsole && s.find("--w32-console") == 0) { app.w32ShowConsole = true; app.ShowConsole(); @@ -654,7 +624,7 @@ MAIN { createProcess(true); shutdownHandler = [&](int signum) { - #if SSC_PLATFORM_LINUX + #if SOCKET_RUNTIME_PLATFORM_LINUX unlink(appInstanceLock.c_str()); #endif if (process != nullptr) { @@ -663,7 +633,7 @@ MAIN { exit(signum); }; - #ifndef _WIN32 + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS installSignalHandler(SIGHUP, signalHandler); #endif @@ -673,10 +643,10 @@ MAIN { return exitCode; } - #if SSC_PLATFORM_APPLE - static auto SSC_OS_LOG_BUNDLE = os_log_create( + #if SOCKET_RUNTIME_PLATFORM_APPLE + static auto SOCKET_RUNTIME_OS_LOG_BUNDLE = os_log_create( bundleIdentifier.c_str(), - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #if SOCKET_RUNTIME_PLATFORM_MOBILE "socket.runtime.mobile" #else "socket.runtime.desktop" @@ -684,56 +654,55 @@ MAIN { ); #endif - auto onStdErr = [&](auto err) { - #if SSC_PLATFORM_APPLE - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", err.c_str()); + const auto onStdErr = [&](const auto& output) { + #if SOCKET_RUNTIME_PLATFORM_APPLE + os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", output.c_str()); #endif - std::cerr << "\033[31m" + err + "\033[0m"; + std::cerr << "\033[31m" + output + "\033[0m"; - for (auto w : app.windowManager.windows) { - if (w != nullptr) { - auto window = app.windowManager.getWindow(w->opts.index); - window->eval(getEmitToRenderProcessJavaScript("process-error", err)); + for (const auto& window : app.windowManager.windows) { + if (window != nullptr) { + window->bridge.emit("process-error", output); } } }; // - // # Backend -> Main + // # "Backend" -> Main // Launch the backend process and connect callbacks to the stdio and stderr pipes. // - auto onStdOut = [&](String const &out) { - IPC::Message message(out); + const auto onStdOut = [&](const auto& output) { + const auto message = IPC::Message(output); if (message.index > 0 && message.name.size() == 0) { // @TODO: print warning return; } - if (message.index > SSC_MAX_WINDOWS) { + if (message.index > SOCKET_RUNTIME_MAX_WINDOWS) { // @TODO: print warning return; } - auto value = message.get("value"); + const auto value = message.value; if (message.name == "stdout") { - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE dispatch_async(dispatch_get_main_queue(), ^{ - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_DEFAULT, "%{public}s", value.c_str()); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_DEFAULT, "%{public}s", value.c_str()); }); #endif - std::cout << value; + IO::write(value); return; } if (message.name == "stderr") { - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE dispatch_async(dispatch_get_main_queue(), ^{ - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", value.c_str()); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", value.c_str()); }); #endif - std::cerr << "\033[31m" + value + "\033[0m"; + IO::write(value, true); return; } @@ -743,24 +712,18 @@ MAIN { // are parsable commands, try to do something with them, otherwise they are // just stdout and we can write the data to the pipe. // - app.dispatch([&, message, value] { - auto seq = message.get("seq"); - + app.dispatch([&, message, value]() { if (message.name == "send") { - String script = getEmitToRenderProcessJavaScript( - message.get("event"), - value - ); + const auto event = message.get("event"); if (message.index >= 0) { - auto window = app.windowManager.getWindow(message.index); + const auto window = app.windowManager.getWindow(message.index); if (window) { - window->eval(script); + window->bridge.emit(event, value); } } else { - for (auto w : app.windowManager.windows) { - if (w != nullptr) { - auto window = app.windowManager.getWindow(w->opts.index); - window->eval(script); + for (const auto& window : app.windowManager.windows) { + if (window) { + window->bridge.emit(event, value); } } } @@ -780,30 +743,22 @@ MAIN { } if (message.name == "heartbeat") { - if (seq.size() > 0) { - auto result = IPC::Result(message.seq, message, "heartbeat"); - window->resolvePromise(seq, OK_STATE, result.str()); + if (message.seq.size() > 0) { + const auto result = IPC::Result(message.seq, message, "heartbeat"); + window->bridge.send(message.seq, result.json()); } - return; } if (message.name == "resolve") { - window->resolvePromise(seq, message.get("state"), encodeURIComponent(value)); - return; - } - - if (message.name == "config") { - auto key = message.get("key"); - window->resolvePromise(seq, OK_STATE, app.userConfig[key]); + window->resolvePromise(message.seq, message.get("state"), encodeURIComponent(value)); return; } if (message.name == "process.exit") { - for (auto w : app.windowManager.windows) { - if (w != nullptr) { - auto window = app.windowManager.getWindow(w->opts.index); - window->resolvePromise(message.seq, OK_STATE, value); + for (const auto& window : app.windowManager.windows) { + if (window) { + window->bridge.emit("process-exit", message.value); } } return; @@ -820,7 +775,7 @@ MAIN { [&](String const &code) { for (auto w : app.windowManager.windows) { if (w != nullptr) { - auto window = app.windowManager.getWindow(w->opts.index); + auto window = app.windowManager.getWindow(w->options.index); window->eval(getEmitToRenderProcessJavaScript("backend-exit", code)); } } @@ -835,12 +790,12 @@ MAIN { // callback doesnt need to dispatch because it's already in the // main thread. // - auto onMessage = [&](auto out) { + const auto onMessage = [&](const auto& output) { // debug("onMessage %s", out.c_str()); - IPC::Message message(out, true); + const auto message = IPC::Message(output, true); auto window = app.windowManager.getWindow(message.index); - auto value = message.get("value"); + auto value = message.value; // the window must exist if (!window && message.index >= 0) { @@ -852,14 +807,13 @@ MAIN { } if (message.name == "process.open") { - auto seq = message.get("seq"); auto force = message.get("force") == "true" ? true : false; if (cmd.size() > 0) { if (process == nullptr || force) { createProcess(force); process->open(); } - #ifdef _WIN32 + #ifdef SOCKET_RUNTIME_PLATFORM_WINDOWS size_t last_pos = 0; while ((last_pos = process->path.find('\\', last_pos)) != String::npos) { process->path.replace(last_pos, 1, "\\\\\\\\"); @@ -871,30 +825,27 @@ MAIN { { "argv", process->argv }, { "path", process->path } }; - window->resolvePromise(seq, OK_STATE, json); + window->resolvePromise(message.seq, OK_STATE, json); return; } - window->resolvePromise(seq, ERROR_STATE, JSON::null); + window->resolvePromise(message.seq, ERROR_STATE, JSON::null); return; } if (message.name == "process.kill") { - auto seq = message.get("seq"); - if (cmd.size() > 0 && process != nullptr) { killProcess(process); } - window->resolvePromise(seq, OK_STATE, JSON::null); + window->resolvePromise(message.seq, OK_STATE, JSON::null); return; } if (message.name == "process.write") { - auto seq = message.get("seq"); if (cmd.size() > 0 && process != nullptr) { - process->write(out); + process->write(output); } - window->resolvePromise(seq, OK_STATE, JSON::null); + window->resolvePromise(message.seq, OK_STATE, JSON::null); return; } @@ -911,7 +862,7 @@ MAIN { {"err", JSON::Object::Entries { {"message", "Not found"}, {"type", "NotFoundError"}, - {"url", out} + {"url", output} }} }; @@ -930,16 +881,17 @@ MAIN { // we clean up the windows and the backend process. // shutdownHandler = [&](int code) { - #if SSC_PLATFORM_LINUX + #if SOCKET_RUNTIME_PLATFORM_LINUX unlink(appInstanceLock.c_str()); #endif if (process != nullptr) { process->kill(); + process = nullptr; } app.windowManager.destroy(); - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE if (app_ptr->wasLaunchedFromCli) { debug("__EXIT_SIGNAL__=%d", 0); CLI::notify(); @@ -952,8 +904,114 @@ MAIN { app.onExit = shutdownHandler; -#if SSC_PLATFORM_LINUX - Thread mainThread([&]() { + // + // If this is being run in a terminal/multiplexer + // +#if !SOCKET_RUNTIME_PLATFORM_WINDOWS + installSignalHandler(SIGHUP, signalHandler); +#endif + +#if defined(SIGUSR1) + installSignalHandler(SIGUSR1, signalHandler); +#endif + + installSignalHandler(SIGINT, signalHandler); + installSignalHandler(SIGTERM, signalHandler); + + const auto signalsDisabled = userConfig["application_signals"] == "false"; + const auto signals = parseStringList(userConfig["application_signals"]); + +#define SET_DEFAULT_WINDOW_SIGNAL_HANDLER(sig) { \ + const auto name = String(CONVERT_TO_STRING(sig)); \ + if ( \ + !signalsDisabled || \ + std::find(signals.begin(), signals.end(), name) != signals.end() \ + ) { \ + installSignalHandler(sig, defaultWindowSignalHandler); \ + } \ +} + +#if defined(SIGQUIT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGQUIT) +#endif +#if defined(SIGILL) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGILL) +#endif +#if defined(SIGTRAP) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTRAP) +#endif +#if defined(SIGABRT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGABRT) +#endif +#if defined(SIGIOT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGIOT) +#endif +#if defined(SIGBUS) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGBUS) +#endif +#if defined(SIGFPE) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGFPE) +#endif +#if defined(SIGKILL) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGKILL) +#endif +#if defined(SIGUSR2) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGUSR2) +#endif +#if defined(SIGPIPE) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGPIPE) +#endif +#if defined(SIGALRM) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGALRM) +#endif +#if defined(SIGCHLD) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGCHLD) +#endif +#if defined(SIGCONT) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGCONT) +#endif +#if defined(SIGSTOP) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSTOP) +#endif +#if defined(SIGTSTP) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTSTP) +#endif +#if defined(SIGTTIN) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTTIN) +#endif +#if defined(SIGTTOU) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTTOU) +#endif +#if defined(SIGURG) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGURG) +#endif +#if defined(SIGXCPU) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGXCPU) +#endif +#if defined(SIGXFSZ) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGXFSZ) +#endif +#if defined(SIGVTALRM) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGVTALRM) +#endif +#if defined(SIGPROF) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGPROF) +#endif +#if defined(SIGWINCH) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGWINCH) +#endif +#if defined(SIGIO) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGIO) +#endif +#if defined(SIGINFO) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGINFO) +#endif +#if defined(SIGSYS) + SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSYS) +#endif + +#if SOCKET_RUNTIME_PLATFORM_LINUX + //Thread mainThread([&]() { #endif Vector properties = { "window_width", "window_height", @@ -995,73 +1053,75 @@ MAIN { return String(""); }; - app.windowManager.configure(WindowManagerOptions { + auto windowManagerOptions = WindowManagerOptions { .defaultHeight = getProperty("window_height"), .defaultWidth = getProperty("window_width"), .defaultMinWidth = getProperty("window_min_width"), .defaultMinHeight = getProperty("window_min_height"), .defaultMaxWidth = getProperty("window_max_width"), - .defaultMaxHeight = getProperty("window_max_height"), - .isTest = isTest, - .argv = argvArray.str(), - .cwd = cwd, - .userConfig = app.userConfig, - .onMessage = onMessage, - .onExit = shutdownHandler - }); + .defaultMaxHeight = getProperty("window_max_height") + }; + + windowManagerOptions.features.useTestScript = isTest; + windowManagerOptions.userConfig = app.userConfig; + windowManagerOptions.argv = argvArray; + windowManagerOptions.onMessage = onMessage; + windowManagerOptions.onExit = shutdownHandler; + + app.windowManager.configure(windowManagerOptions); auto isMaximizable = getProperty("window_maximizable"); auto isMinimizable = getProperty("window_minimizable"); auto isClosable = getProperty("window_closable"); auto defaultWindow = app.windowManager.createDefaultWindow(WindowOptions { - .resizable = getProperty("window_resizable") == "false" ? false : true, .minimizable = (isMinimizable == "" || isMinimizable == "true") ? true : false, .maximizable = (isMaximizable == "" || isMaximizable == "true") ? true : false, + .resizable = getProperty("window_resizable") == "false" ? false : true, .closable = (isClosable == "" || isClosable == "true") ? true : false, .frameless = getProperty("window_frameless") == "true" ? true : false, .utility = getProperty("window_utility") == "true" ? true : false, - .canExit = true, + .shouldExitApplicationOnClose = true, .titlebarStyle = getProperty("window_titlebar_style"), .windowControlOffsets = getProperty("mac_window_control_offsets"), .backgroundColorLight = getProperty("window_background_color_light"), - .backgroundColorDark = getProperty("window_background_color_dark"), - .userConfig = userConfig, - .onExit = shutdownHandler + .backgroundColorDark = getProperty("window_background_color_dark") }); if ( userConfig["webview_service_worker_mode"] != "hybrid" && userConfig["permissions_allow_service_worker"] != "false" ) { + auto serviceWorkerWindowOptions = WindowOptions {}; auto serviceWorkerUserConfig = userConfig; auto screen = defaultWindow->getScreenSize(); - serviceWorkerUserConfig["webview_watch_reload"] = "false"; - auto serviceWorkerWindow = app.windowManager.createWindow({ - .canExit = false, - .width = defaultWindow->getSizeInPixels("80%", screen.width), - .height = defaultWindow->getSizeInPixels("80%", screen.height), - .minWidth = defaultWindow->getSizeInPixels("40%", screen.width), - .minHeight = defaultWindow->getSizeInPixels("30%", screen.height), - .index = SSC_SERVICE_WORKER_CONTAINER_WINDOW_INDEX, - .headless = Env::get("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG").size() == 0, - .userConfig = serviceWorkerUserConfig, - .preloadCommonJS = false - }); - app.core->serviceWorker.init(&serviceWorkerWindow->bridge); - serviceWorkerWindow->show(); + serviceWorkerUserConfig["webview_watch_reload"] = "false"; + serviceWorkerWindowOptions.shouldExitApplicationOnClose = false; + serviceWorkerWindowOptions.minHeight = defaultWindow->getSizeInPixels("30%", screen.height); + serviceWorkerWindowOptions.height = defaultWindow->getSizeInPixels("80%", screen.height); + serviceWorkerWindowOptions.minWidth = defaultWindow->getSizeInPixels("40%", screen.width); + serviceWorkerWindowOptions.width = defaultWindow->getSizeInPixels("80%", screen.width); + serviceWorkerWindowOptions.index = SOCKET_RUNTIME_SERVICE_WORKER_CONTAINER_WINDOW_INDEX; + serviceWorkerWindowOptions.headless = Env::get("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG").size() == 0; + serviceWorkerWindowOptions.userConfig = serviceWorkerUserConfig; + serviceWorkerWindowOptions.features.useGlobalCommonJS = false; + serviceWorkerWindowOptions.features.useGlobalNodeJS = false; + + auto serviceWorkerWindow = app.windowManager.createWindow(serviceWorkerWindowOptions); + + app.serviceWorkerContainer.init(&serviceWorkerWindow->bridge); serviceWorkerWindow->navigate( "socket://" + userConfig["meta_bundle_identifier"] + "/socket/service-worker/index.html" ); } else if (userConfig["webview_service_worker_mode"] == "hybrid") { - app.core->serviceWorker.init(&defaultWindow->bridge); + app.serviceWorkerContainer.init(&defaultWindow->bridge); } defaultWindow->show(); - if (_port > 0) { - defaultWindow->navigate(_host + ":" + std::to_string(_port)); + if (devPort > 0) { + defaultWindow->navigate(devHost + ":" + std::to_string(devPort)); defaultWindow->setSystemMenu(String( "Developer Mode: \n" " Reload: r + CommandOrControl\n" @@ -1084,10 +1144,10 @@ MAIN { String value; std::getline(std::cin, value); - Thread t([&](String value) { + auto t = Thread([&](String value) { auto app = App::sharedApplication(); - while (!app->core->domReady) { + while (!app->core->platform.wasFirstDOMContentLoadedEventDispatched) { std::this_thread::sleep_for(std::chrono::milliseconds(128)); } @@ -1112,120 +1172,14 @@ MAIN { // thread and run it until it returns a non-zero int. // while (app.run(argc, argv) == 0); -#if SSC_PLATFORM_LINUX - }); -#endif - - // - // If this is being run in a terminal/multiplexer - // -#ifndef _WIN32 - installSignalHandler(SIGHUP, signalHandler); +#if SOCKET_RUNTIME_PLATFORM_LINUX + //}); #endif -#if defined(SIGUSR1) - installSignalHandler(SIGUSR1, signalHandler); -#endif - - installSignalHandler(SIGINT, signalHandler); - installSignalHandler(SIGTERM, signalHandler); - - const auto signalsDisabled = userConfig["application_signals"] == "false"; - const auto signals = parseStringList(userConfig["application_signals"]); - -#define SET_DEFAULT_WINDOW_SIGNAL_HANDLER(sig) { \ - const auto name = String(CONVERT_TO_STRING(sig)); \ - if ( \ - !signalsDisabled || \ - std::find(signals.begin(), signals.end(), name) != signals.end() \ - ) { \ - installSignalHandler(sig, defaultWindowSignalHandler); \ - } \ -} - -#if defined(SIGQUIT) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGQUIT) -#endif -#if defined(SIGILL) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGILL) -#endif -#if defined(SIGTRAP) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTRAP) -#endif -#if defined(SIGABRT) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGABRT) -#endif -#if defined(SIGIOT) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGIOT) -#endif -#if defined(SIGBUS) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGBUS) -#endif -#if defined(SIGFPE) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGFPE) -#endif -#if defined(SIGKILL) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGKILL) -#endif -#if defined(SIGUSR2) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGUSR2) -#endif -#if defined(SIGPIPE) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGPIPE) -#endif -#if defined(SIGALRM) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGALRM) -#endif -#if defined(SIGCHLD) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGCHLD) -#endif -#if defined(SIGCONT) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGCONT) -#endif -#if defined(SIGSTOP) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSTOP) -#endif -#if defined(SIGTSTP) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTSTP) -#endif -#if defined(SIGTTIN) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTTIN) -#endif -#if defined(SIGTTOU) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGTTOU) -#endif -#if defined(SIGURG) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGURG) -#endif -#if defined(SIGXCPU) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGXCPU) -#endif -#if defined(SIGXFSZ) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGXFSZ) -#endif -#if defined(SIGVTALRM) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGVTALRM) -#endif -#if defined(SIGPROF) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGPROF) -#endif -#if defined(SIGWINCH) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGWINCH) -#endif -#if defined(SIGIO) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGIO) -#endif -#if defined(SIGINFO) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGINFO) -#endif -#if defined(SIGSYS) - SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSYS) -#endif - -#if SSC_PLATFORM_LINUX - if (mainThread.joinable()) { - mainThread.join(); - } +#if SOCKET_RUNTIME_PLATFORM_LINUX + //if (mainThread.joinable()) { + //mainThread.join(); + //} dbus_connection_unref(connection); #endif diff --git a/src/extension/extension.cc b/src/extension/extension.cc index a9b6f41496..7562e2334a 100644 --- a/src/extension/extension.cc +++ b/src/extension/extension.cc @@ -112,7 +112,7 @@ namespace SSC { bool Extension::Context::release () { if (this->retain_count == 0) { - debug("WARN - Double release of SSC extension context"); + debug("WARN - Double release of runtime extension context"); return false; } if (--this->retain_count == 0) { @@ -186,7 +186,7 @@ namespace SSC { String Extension::getExtensionsDirectory (const String& name) { auto cwd = getcwd(); - #if defined(_WIN32) + #if SOCKET_RUNTIME_PLATFORM_WINDOWS return cwd + "\\socket\\extensions\\" + name + "\\"; #else return cwd + "/socket/extensions/" + name + "/"; @@ -272,7 +272,7 @@ namespace SSC { } String Extension::getExtensionType (const String& name) { - const auto libraryPath = getExtensionsDirectory(name) + (name + RUNTIME_EXTENSION_FILE_EXT); + const auto libraryPath = getExtensionsDirectory(name) + (name + SOCKET_RUNTIME_EXTENSION_FILENAME_EXTNAME); const auto wasmPath = getExtensionsDirectory(name) + (name + ".wasm"); if (fs::exists(wasmPath)) { return "wasm32"; @@ -292,7 +292,7 @@ namespace SSC { } if (type == "shared") { - return getExtensionsDirectory(name) + (name + RUNTIME_EXTENSION_FILE_EXT); + return getExtensionsDirectory(name) + (name + SOCKET_RUNTIME_EXTENSION_FILENAME_EXTNAME); } return ""; @@ -305,16 +305,16 @@ namespace SSC { // check if extension is already known if (isLoaded(name)) return true; - auto path = getExtensionsDirectory(name) + (name + RUNTIME_EXTENSION_FILE_EXT); + auto path = getExtensionsDirectory(name) + (name + SOCKET_RUNTIME_EXTENSION_FILENAME_EXTNAME); - #if defined(_WIN32) + #if SOCKET_RUNTIME_PLATFORM_WINDOWS auto handle = LoadLibrary(path.c_str()); if (handle == nullptr) return false; auto __sapi_extension_init = (sapi_extension_registration_entry) GetProcAddress(handle, "__sapi_extension_init"); if (!__sapi_extension_init) return false; #else - #if defined(__ANDROID__) - auto handle = dlopen(String("libextension-" + name + RUNTIME_EXTENSION_FILE_EXT).c_str(), RTLD_NOW | RTLD_LOCAL); + #if SOCKET_RUNTIME_PLATFORM_ANDROID + auto handle = dlopen(String("libextension-" + name + SOCKET_RUNTIME_EXTENSION_FILENAME_EXTNAME).c_str(), RTLD_NOW | RTLD_LOCAL); #else auto handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); #endif @@ -386,7 +386,7 @@ namespace SSC { return true; } - #if defined(_WIN32) + #if SOCKET_RUNTIME_PLATFORM_WINDOWS if (!FreeLibrary(reinterpret_cast(extension->handle))) { return false; } @@ -570,23 +570,23 @@ void sapi_log (const sapi_context_t* ctx, const char* message) { output = message; } -#if defined(__linux__) && defined(__ANDROID__) +#if SOCKET_RUNTIME_PLATFORM_ANDROID __android_log_print(ANDROID_LOG_INFO, "Console", "%s", message); #else SSC::IO::write(output, false); #endif -#if defined(__APPLE__) +#if SOCKET_RUNTIME_PLATFORM_APPLE static auto userConfig = SSC::getUserConfig(); static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - static auto SSC_OS_LOG_BUNDLE = os_log_create(bundleIdentifier.c_str(), -#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - "socket.runtime.mobile" -#else - "socket.runtime.desktop" -#endif - ); - os_log_with_type(SSC_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", output.c_str()); + static auto SOCKET_RUNTIME_OS_LOG_INFO = os_log_create(bundleIdentifier.c_str(), + #if SOCKET_RUNTIME_PLATFORM_MOBILE + "socket.runtime.mobile" + #else + "socket.runtime.desktop" + #endif + ); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_INFO, OS_LOG_TYPE_INFO, "%{public}s", output.c_str()); #endif } diff --git a/src/extension/extension.hh b/src/extension/extension.hh index 0cc55f7adf..7a1fd26e0f 100644 --- a/src/extension/extension.hh +++ b/src/extension/extension.hh @@ -1,19 +1,15 @@ -#ifndef SSC_EXTENSION_H -#define SSC_EXTENSION_H - -#if !defined(_WIN32) -# include -#endif +#ifndef SOCKET_RUNTIME_EXTENSION_EXTENSION_H +#define SOCKET_RUNTIME_EXTENSION_EXTENSION_H #include "../../include/socket/extension.h" -#include "../process/process.hh" +#include "../core/process.hh" #include "../core/core.hh" #include "../ipc/ipc.hh" -#if defined(_WIN32) -#define RUNTIME_EXTENSION_FILE_EXT ".dll" +#if SOCKET_RUNTIME_PLATFORM_WINDOWS +#define SOCKET_RUNTIME_EXTENSION_FILENAME_EXTNAME ".dll" #else -#define RUNTIME_EXTENSION_FILE_EXT ".so" +#define SOCKET_RUNTIME_EXTENSION_FILENAME_EXTNAME ".so" #endif namespace SSC { @@ -278,5 +274,4 @@ extern "C" { {} }; }; - #endif diff --git a/src/extension/ipc.cc b/src/extension/ipc.cc index 8924f88ace..d3b45e9f3d 100644 --- a/src/extension/ipc.cc +++ b/src/extension/ipc.cc @@ -240,8 +240,8 @@ bool sapi_ipc_send_bytes ( }; if (bytes != nullptr && size > 0) { - post.body = std::make_shared(new char[size]{0}); - memcpy(*post.body, bytes, size); + post.body = std::make_shared(size); + memcpy(post.body.get(), bytes, size); } if (message) { @@ -279,8 +279,8 @@ bool sapi_ipc_send_bytes_with_result ( }; if (bytes != nullptr && size > 0) { - post.body = std::make_shared(new char[size]{0}); - memcpy(*post.body, bytes, size); + post.body = std::make_shared(size); + memcpy(post.body.get(), bytes, size); } return ctx->router->bridge->send(result->seq, result->str(), post); @@ -396,11 +396,11 @@ bool sapi_ipc_invoke ( uri = "ipc://" + uri; } - SSC::SharedPointer data = nullptr; + SSC::SharedPointer data = nullptr; if (bytes != nullptr && size > 0) { - data = std::make_shared(new char[size]{0}); - memcpy(*data, bytes, size); + data.reset(new char[size]{0}); + memcpy(data.get(), bytes, size); } return ctx->router->invoke(uri, data, size, [ctx, callback](auto result) { @@ -500,7 +500,7 @@ const unsigned char* sapi_ipc_message_get_bytes ( const sapi_ipc_message_t* message ) { if (!message) return nullptr; - return reinterpret_cast(*message->buffer.bytes); + return reinterpret_cast(message->buffer.bytes.get()); } unsigned int sapi_ipc_message_get_bytes_size ( @@ -666,8 +666,8 @@ void sapi_ipc_result_set_bytes ( ) { if (result && size && bytes) { result->post.length = size; - result->post.body = std::make_shared(new char[size]{0}); - memcpy(*result->post.body, bytes, size); + result->post.body = std::make_shared(size); + memcpy(result->post.body.get(), bytes, size); } } @@ -675,7 +675,7 @@ unsigned char* sapi_ipc_result_get_bytes ( const sapi_ipc_result_t* result ) { return result - ? reinterpret_cast(*result->post.body) + ? reinterpret_cast(result->post.body.get()) : nullptr; } diff --git a/src/extension/json.cc b/src/extension/json.cc index 26487df383..7386ac87a2 100644 --- a/src/extension/json.cc +++ b/src/extension/json.cc @@ -1,5 +1,5 @@ -#include "extension.hh" #include +#include "extension.hh" const sapi_json_type_t sapi_json_typeof (const sapi_json_any_t* json) { if (json->isNull()) return SAPI_JSON_TYPE_NULL; diff --git a/src/extension/process.cc b/src/extension/process.cc index e3d01e2629..8297ac30af 100644 --- a/src/extension/process.cc +++ b/src/extension/process.cc @@ -4,7 +4,7 @@ const sapi_process_exec_t* sapi_process_exec ( sapi_context_t* ctx, const char* command ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_exec is not supported on this platform"); return nullptr; #endif @@ -23,7 +23,7 @@ const sapi_process_exec_t* sapi_process_exec ( int sapi_process_exec_get_exit_code ( const sapi_process_exec_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_exec_get_exit_code is not supported on this platform"); return -1; #endif @@ -34,7 +34,7 @@ int sapi_process_exec_get_exit_code ( const char* sapi_process_exec_get_output ( const sapi_process_exec_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_exec_get_output is not supported on this platform"); return nullptr; #endif @@ -51,7 +51,7 @@ sapi_process_spawn_t* sapi_process_spawn ( sapi_process_spawn_stderr_callback_t onstderr, sapi_process_spawn_exit_callback_t onexit ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn is not supported on this platform"); return nullptr; #endif @@ -72,7 +72,7 @@ sapi_process_spawn_t* sapi_process_spawn ( int sapi_process_spawn_get_exit_code ( const sapi_process_spawn_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_get_exit_code is not supported on this platform"); return -1; #endif @@ -82,7 +82,7 @@ int sapi_process_spawn_get_exit_code ( unsigned long sapi_process_spawn_get_pid ( const sapi_process_spawn_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_get_pid is not supported on this platform"); return 0; #endif @@ -92,7 +92,7 @@ unsigned long sapi_process_spawn_get_pid ( sapi_context_t* sapi_process_spawn_get_context ( const sapi_process_spawn_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_get_context is not supported on this platform"); return nullptr; #endif @@ -102,7 +102,7 @@ sapi_context_t* sapi_process_spawn_get_context ( int sapi_process_spawn_wait ( sapi_process_spawn_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_wait is not supported on this platform"); return -1; #endif @@ -114,7 +114,7 @@ bool sapi_process_spawn_write ( const char* data, const size_t size ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_write is not supported on this platform"); return false; #endif @@ -126,12 +126,12 @@ bool sapi_process_spawn_write ( bool sapi_process_spawn_close_stdin ( sapi_process_spawn_t* process ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_close_stdin is not supported on this platform"); return false; #endif if (!process || process->closed) return false; - process->close_stdin(); + process->closeStdin(); return true; } @@ -139,7 +139,7 @@ bool sapi_process_spawn_kill ( sapi_process_spawn_t* process, int code ) { -#if defined(__APPLE__) && (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) +#if SOCKET_RUNTIME_PLATFORM_IOS debug("sapi_process_spawn_kill is not supported on this platform"); return false; #endif diff --git a/src/init.cc b/src/init.cc index 95ddde33a2..abcb4c71b2 100644 --- a/src/init.cc +++ b/src/init.cc @@ -1,30 +1,36 @@ -#include "core/config.hh" -#include "core/string.hh" -#include "core/types.hh" -#include "core/ini.hh" - #if defined(__cplusplus) -// These rely on project-specific, compile-time variables. -namespace SSC { - bool isDebugEnabled () { - return DEBUG == 1; +#include + +extern "C" { + + // implemented in `init.cc` + const unsigned char* socket_runtime_init_get_user_config_bytes () { + return __socket_runtime_user_config_bytes; + } + + unsigned int socket_runtime_init_get_user_config_bytes_size () { + return sizeof(__socket_runtime_user_config_bytes); } - const Map getUserConfig () { - #include "user-config-bytes.hh" // NOLINT - return INI::parse(std::string( - (const char*) __ssc_config_bytes, - sizeof(__ssc_config_bytes) - )); + bool socket_runtime_init_is_debug_enabled () { + #if DEBUG + return true; + #endif + return false; } - const String getDevHost () { - static const auto host = String(HOST); - return host; + const char* socket_runtime_init_get_dev_host () { + #if defined(HOST) + return HOST; + #endif + return ""; } - int getDevPort () { + int socket_runtime_init_get_dev_port () { + #if defined(PORT) return PORT; + #endif + return 0; } } #endif diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 57077a70e2..eb362b3613 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -1,6 +1,8 @@ +#include "../serviceworker/protocols.hh" #include "../extension/extension.hh" #include "../window/window.hh" -#include "../core/protocol_handlers.hh" +#include "../core/version.hh" +#include "../app/app.hh" #include "ipc.hh" extern const SSC::Map SSC::getUserConfig (); @@ -10,12 +12,18 @@ namespace SSC::IPC { static Vector instances; static Mutex mutex; - // create a proxy module so imports of the module of concern are imported - // exactly once at the canonical URL (file:///...) in contrast to module - // URLs (socket:...) - - static constexpr auto moduleTemplate = + // The `ESM_IMPORT_PROXY_TEMPLATE` is used to provide an ESM module as + // a proxy to a canonical URL for a module so `socket:` and + // `socket:///socket/.js` resolve to the exact + // same module + static constexpr auto ESM_IMPORT_PROXY_TEMPLATE = R"S( + /** + * This module exists to provide a proxy to a canonical URL for a module + * so `socket:` and `socket:///socket/.js` + * resolve to the exact same module instance. + * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} + */ import module from '{{url}}' export * from '{{url}}' export default module @@ -63,7 +71,7 @@ namespace SSC::IPC { "worker_threads" }; -#if SSC_PLATFORM_DESKTOP +#if SOCKET_RUNTIME_PLATFORM_DESKTOP static FileSystemWatcher* developerResourcesFileSystemWatcher = nullptr; static void initializeDeveloperResourcesFileSystemWatcher (SharedPointer core) { auto defaultUserConfig = SSC::getUserConfig(); @@ -96,9 +104,9 @@ namespace SSC::IPC { (!userConfig.contains("webview_watch_reload") || userConfig.at("webview_watch_reload") != "false") ) { // check if changed path was a service worker, if so unregister it so it can be reloaded - for (const auto& entry : developerResourcesFileSystemWatcher->core->serviceWorker.registrations) { + for (const auto& entry : App::sharedApplication()->serviceWorkerContainer.registrations) { const auto& registration = entry.second; - #if defined(__ANDROID__) + #if SOCKET_RUNTIME_PLATFORM_ANDROID auto scriptURL = String("https://"); #else auto scriptURL = String("socket://"); @@ -116,7 +124,7 @@ namespace SSC::IPC { // 2. re-register service worker // 3. wait for it to be registered // 4. emit 'filedidchange' event - bridge->core->serviceWorker.unregisterServiceWorker(entry.first); + bridge->navigator.serviceWorker.unregisterServiceWorker(entry.first); bridge->core->setTimeout(8, [bridge, result, ®istration] () { bridge->core->setInterval(8, [bridge, result, ®istration] (auto cancel) { if (registration.state == ServiceWorkerContainer::Registration::State::Activated) { @@ -135,7 +143,7 @@ namespace SSC::IPC { } }); - bridge->core->serviceWorker.registerServiceWorker(registration.options); + bridge->navigator.serviceWorker.registerServiceWorker(registration.options); }); return; } @@ -156,10 +164,10 @@ namespace SSC::IPC { navigator(this), schemeHandlers(this) { - Lock lock(SSC::IPC::mutex); - instances.push_back(this); - this->id = rand64(); + #if SOCKET_RUNTIME_PLATFORM_ANDROID + this->isAndroidEmulator = App::sharedApplication()->isAndroidEmulator; + #endif this->bluetooth.sendFunction = [this]( const String& seq, @@ -176,6 +184,10 @@ namespace SSC::IPC { this->emit(seq, value.str()); }; + this->dispatchFunction = [] (auto callback) { + App::sharedApplication()->dispatch(callback); + }; + core->networkStatus.addObserver(this->networkStatusObserver, [this](auto json) { if (json.has("name")) { this->emit(json["name"].str(), json.str()); @@ -193,7 +205,7 @@ namespace SSC::IPC { // on Linux, much of the Notification API is supported so these observers // below are not needed as those events already occur in the webview // we are patching for the other platforms - #if !SSC_PLATFORM_LINUX + #if !SOCKET_RUNTIME_PLATFORM_LINUX core->notifications.addPermissionChangeObserver(this->notificationsPermissionChangeObserver, [this](auto json) { JSON::Object event = JSON::Object::Entries { {"name", "notifications"}, @@ -213,7 +225,9 @@ namespace SSC::IPC { } #endif - #if SSC_PLATFORM_DESKTOP + Lock lock(SSC::IPC::mutex); + instances.push_back(this); + #if SOCKET_RUNTIME_PLATFORM_DESKTOP initializeDeveloperResourcesFileSystemWatcher(core); #endif } @@ -233,7 +247,7 @@ namespace SSC::IPC { instances.erase(cursor); } - #if SSC_PLATFORM_DESKTOP + #if SOCKET_RUNTIME_PLATFORM_DESKTOP if (instances.size() == 0) { if (developerResourcesFileSystemWatcher) { developerResourcesFileSystemWatcher->stop(); @@ -251,7 +265,6 @@ namespace SSC::IPC { } void Bridge::configureWebView (WebView* webview) { - this->core->notifications.configureWebView(webview); this->schemeHandlers.configureWebView(webview); this->navigator.configureWebView(webview); } @@ -295,13 +308,13 @@ namespace SSC::IPC { return false; } - bool Bridge::route (const String& uri, SharedPointer bytes, size_t size) { + bool Bridge::route (const String& uri, SharedPointer bytes, size_t size) { return this->route(uri, bytes, size, nullptr); } bool Bridge::route ( const String& uri, - SharedPointer bytes, + SharedPointer bytes, size_t size, Router::ResultCallback callback ) { @@ -524,11 +537,6 @@ namespace SSC::IPC { return callback(response); } - - if (message.get("resolve") == "false") { - auto response = SchemeHandlers::Response(request, 200); - return callback(response); - } }); this->schemeHandlers.registerSchemeHandler("socket", [this]( @@ -629,12 +637,9 @@ namespace SSC::IPC { if (resource.mimeType() != "text/html") { response.send(resource); } else { - const auto html = injectHTMLPreload( - this->core.get(), - userConfig, - resource.str(), - this->preload - ); + const auto html = this->preload.insertIntoHTML(resource.str(), { + .protocolHandlerSchemes = this->navigator.serviceWorker.protocols.getSchemes() + }); response.setHeader("content-type", "text/html"); response.setHeader("content-length", html.size()); @@ -647,7 +652,7 @@ namespace SSC::IPC { return callback(response); } - if (this->core->serviceWorker.registrations.size() > 0) { + if (this->navigator.serviceWorker.registrations.size() > 0) { const auto fetch = ServiceWorkerContainer::FetchRequest { request.method, request.scheme, @@ -655,11 +660,11 @@ namespace SSC::IPC { request.pathname, request.query, request.headers, - ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, + ServiceWorkerContainer::FetchBody { request.body.size, request.body.bytes }, ServiceWorkerContainer::Client { request.client.id, this->preload } }; - const auto fetched = this->core->serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { + const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { if (!request.isActive()) { return; } @@ -668,7 +673,7 @@ namespace SSC::IPC { response.fail("ServiceWorker request failed"); } else { response.writeHead(res.statusCode, res.headers); - response.write(res.buffer.size, res.buffer.bytes); + response.write(res.body.size, res.body.bytes); } callback(response); @@ -705,25 +710,37 @@ namespace SSC::IPC { resourcePath = applicationResources + "/socket" + pathname; contentLocation = "/socket" + pathname; - auto resource = FileResource(resourcePath); + auto resource = FileResource(resourcePath, { .cache = true }); if (resource.exists()) { - const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; - const auto module = tmpl(moduleTemplate, Map {{"url", url}}); + const auto url = ( + "socket://" + + bundleIdentifier + + "/socket" + + pathname + + (request.query.size() > 0 ? "?" + request.query : "") + ); + + const auto moduleImportProxy = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map { + {"url", url}, + {"commit", VERSION_HASH_STRING}, + {"pathname", pathname} + }); + const auto contentType = resource.mimeType(); if (contentType.size() > 0) { response.setHeader("content-type", contentType); } - response.setHeader("content-length", module.size()); + response.setHeader("content-length", moduleImportProxy.size()); if (contentLocation.size() > 0) { response.setHeader("content-location", contentLocation); } response.writeHead(200); - response.write(trim(module)); + response.write(moduleImportProxy); } return callback(response); @@ -780,7 +797,7 @@ namespace SSC::IPC { contentLocation = "/socket" + pathname; resourcePath = applicationResources + contentLocation; - auto resource = FileResource(resourcePath); + auto resource = FileResource(resourcePath, { .cache = true }); if (!resource.exists()) { if (!pathname.ends_with(".js")) { @@ -798,12 +815,12 @@ namespace SSC::IPC { resourcePath = applicationResources + contentLocation; } - resource = FileResource(resourcePath); + resource = FileResource(resourcePath, { .cache = true }); } if (resource.exists()) { const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; - const auto module = tmpl(moduleTemplate, Map {{"url", url}}); + const auto module = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map {{"url", url}}); const auto contentType = resource.mimeType(); if (contentType.size() > 0) { @@ -833,7 +850,7 @@ namespace SSC::IPC { for (const auto& entry : split(this->userConfig["webview_protocol-handlers"], " ")) { const auto scheme = replace(trim(entry), ":", ""); - if (this->core->protocolHandlers.registerHandler(scheme)) { + if (this->navigator.serviceWorker.protocols.registerHandler(scheme)) { protocolHandlers.insert_or_assign(scheme, ""); } } @@ -843,7 +860,7 @@ namespace SSC::IPC { if (key.starts_with("webview_protocol-handlers_")) { const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; const auto data = entry.second; - if (this->core->protocolHandlers.registerHandler(scheme, { data })) { + if (this->navigator.serviceWorker.protocols.registerHandler(scheme, { data })) { protocolHandlers.insert_or_assign(scheme, data); } } @@ -876,7 +893,7 @@ namespace SSC::IPC { } scriptURL = ( - #if SSC_PLATFORM_ANDROID + #if SOCKET_RUNTIME_PLATFORM_ANDROID "https://" + #else "socket://" + @@ -885,7 +902,7 @@ namespace SSC::IPC { scriptURL ); - this->core->serviceWorker.registerServiceWorker({ + this->navigator.serviceWorker.registerServiceWorker({ .type = ServiceWorkerContainer::RegistrationOptions::Type::Module, .scope = scope, .scriptURL = scriptURL, @@ -899,7 +916,7 @@ namespace SSC::IPC { auto& callbacks, auto callback ) { - if (this->core->serviceWorker.registrations.size() > 0) { + if (this->navigator.serviceWorker.registrations.size() > 0) { auto hostname = request.hostname; auto pathname = request.pathname; @@ -907,7 +924,7 @@ namespace SSC::IPC { hostname = this->userConfig["meta_bundle_identifier"]; } - const auto scope = this->core->protocolHandlers.getServiceWorkerScope(request.scheme); + const auto scope = this->navigator.serviceWorker.protocols.getServiceWorkerScope(request.scheme); if (scope.size() > 0) { pathname = scope + pathname; @@ -920,11 +937,11 @@ namespace SSC::IPC { pathname, request.query, request.headers, - ServiceWorkerContainer::FetchBuffer { request.body.size, request.body.bytes }, + ServiceWorkerContainer::FetchBody { request.body.size, request.body.bytes }, ServiceWorkerContainer::Client { request.client.id, this->preload } }; - const auto fetched = this->core->serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { + const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { if (!request.isActive()) { return; } @@ -935,7 +952,7 @@ namespace SSC::IPC { response.fail("ServiceWorker request failed"); } else { response.writeHead(res.statusCode, res.headers); - response.write(res.buffer.size, res.buffer.bytes); + response.write(res.body.size, res.body.bytes); } callback(response); @@ -958,4 +975,8 @@ namespace SSC::IPC { }); } } + + void Bridge::configureNavigatorMounts () { + this->navigator.configureMounts(); + } } diff --git a/src/ipc/bridge.hh b/src/ipc/bridge.hh index d12db2882c..d9613cefc2 100644 --- a/src/ipc/bridge.hh +++ b/src/ipc/bridge.hh @@ -1,8 +1,10 @@ -#ifndef SSC_IPC_BRIDGE_H -#define SSC_IPC_BRIDGE_H +#ifndef SOCKET_RUNTIME_IPC_BRIDGE_H +#define SOCKET_RUNTIME_IPC_BRIDGE_H #include "../core/core.hh" -#include "../window/webview.hh" +#include "../core/webview.hh" + +#include "preload.hh" #include "navigator.hh" #include "router.hh" #include "scheme_handlers.hh" @@ -17,11 +19,11 @@ namespace SSC::IPC { static Vector getInstances(); - const NetworkStatus::Observer networkStatusObserver; - const Geolocation::PermissionChangeObserver geolocationPermissionChangeObserver; - const Notifications::PermissionChangeObserver notificationsPermissionChangeObserver; - const Notifications::NotificationResponseObserver notificationResponseObserver; - const Notifications::NotificationPresentedObserver notificationPresentedObserver; + const CoreNetworkStatus::Observer networkStatusObserver; + const CoreGeolocation::PermissionChangeObserver geolocationPermissionChangeObserver; + const CoreNotifications::PermissionChangeObserver notificationsPermissionChangeObserver; + const CoreNotifications::NotificationResponseObserver notificationResponseObserver; + const CoreNotifications::NotificationPresentedObserver notificationPresentedObserver; EvaluateJavaScriptFunction evaluateJavaScriptFunction = nullptr; NavigateFunction navigateFunction = nullptr; @@ -30,14 +32,14 @@ namespace SSC::IPC { Bluetooth bluetooth; Navigator navigator; SchemeHandlers schemeHandlers; + Preload preload; Router router; Map userConfig = getUserConfig(); SharedPointer core = nullptr; - String preload = ""; uint64_t id = 0; - #if SSC_PLATFORM_ANDROID + #if SOCKET_RUNTIME_PLATFORM_ANDROID bool isAndroidEmulator = false; #endif @@ -50,11 +52,12 @@ namespace SSC::IPC { void init (); void configureWebView (WebView* webview); void configureSchemeHandlers (const SchemeHandlers::Configuration& configuration); + void configureNavigatorMounts (); - bool route (const String& uri, SharedPointer bytes, size_t size); + bool route (const String& uri, SharedPointer bytes, size_t size); bool route ( const String& uri, - SharedPointer bytes, + SharedPointer bytes, size_t size, Router::ResultCallback ); @@ -63,10 +66,10 @@ namespace SSC::IPC { bool evaluateJavaScript (const String& source); bool dispatch (const DispatchCallback& callback); bool navigate (const String& url); - bool emit (const String& name, const String& data); - bool emit (const String& name, const JSON::Any& json); - bool send (const Message::Seq& seq, const String& data, const Post& post); - bool send (const Message::Seq& seq, const JSON::Any& json, const Post& post); + bool emit (const String& name, const String& data = ""); + bool emit (const String& name, const JSON::Any& json = {}); + bool send (const Message::Seq& seq, const String& data, const Post& post = {}); + bool send (const Message::Seq& seq, const JSON::Any& json, const Post& post = {}); }; } #endif diff --git a/src/ipc/bridge.kt b/src/ipc/bridge.kt new file mode 100644 index 0000000000..8367081ec1 --- /dev/null +++ b/src/ipc/bridge.kt @@ -0,0 +1,87 @@ +// vim: set sw=2: +package socket.runtime.ipc + +import android.content.Intent +import android.webkit.WebView +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse + +import androidx.appcompat.app.AppCompatActivity + +import socket.runtime.app.App +import socket.runtime.ipc.Navigator +import socket.runtime.ipc.SchemeHandlers +import socket.runtime.core.console +import socket.runtime.core.WebViewClient + +private fun isAndroidAssetsUri (uri: android.net.Uri): Boolean { + if (uri.pathSegments.size == 0) { + return false + } + + val scheme = uri.scheme + val host = uri.host + // handle no path segments, not currently required but future proofing + val path = uri.pathSegments?.get(0) + + if (host == "appassets.androidplatform.net") { + return true + } + + if (scheme == "file" && host == "" && path == "android_asset") { + return true + } + + return false +} + +open class Bridge (val index: Int, val activity: AppCompatActivity): WebViewClient() { + open val schemeHandlers = SchemeHandlers(this) + open val navigator = Navigator(this) + open val buffers = mutableMapOf() + + override fun shouldOverrideUrlLoading ( + view: WebView, + request: WebResourceRequest + ): Boolean { + if (isAndroidAssetsUri(request.url)) { + return false + } + + val app = App.getInstance() + val bundleIdentifier = app.getUserConfigValue("meta_bundle_identifier") + + if (request.url.host == bundleIdentifier) { + return false + } + + val allowed = this.navigator.isNavigationRequestAllowed( + view.url ?: "", + request.url.toString() + ) + + if (allowed) { + console.log("is allowed") + return false + } + + val intent = Intent(Intent.ACTION_VIEW, request.url) + + try { + this.activity.startActivity(intent) + } catch (err: Exception) { + // TODO(jwerle): handle this error gracefully + console.error(err.toString()) + return false + } + + return true + } + + override fun shouldInterceptRequest ( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + return this.schemeHandlers.handleRequest(request) + } +} diff --git a/src/ipc/client.hh b/src/ipc/client.hh index 4c4ec157c5..b6b445b933 100644 --- a/src/ipc/client.hh +++ b/src/ipc/client.hh @@ -1,5 +1,5 @@ -#ifndef SSC_IPC_CLIENT_H -#define SSC_IPC_CLIENT_H +#ifndef SOCKET_RUNTIME_IPC_CLIENT_H +#define SOCKET_RUNTIME_IPC_CLIENT_H #include "../core/core.hh" diff --git a/src/ipc/ipc.hh b/src/ipc/ipc.hh index ec467911e3..f8816d505d 100644 --- a/src/ipc/ipc.hh +++ b/src/ipc/ipc.hh @@ -1,5 +1,5 @@ -#ifndef SSC_IPC_H -#define SSC_IPC_H +#ifndef SOCKET_RUNTIME_IPC_H +#define SOCKET_RUNTIME_IPC_H #include "bridge.hh" #include "client.hh" diff --git a/src/ipc/message.cc b/src/ipc/message.cc index 346a272f54..1c4c8b0882 100644 --- a/src/ipc/message.cc +++ b/src/ipc/message.cc @@ -14,8 +14,9 @@ namespace SSC::IPC { this->cancel = message.cancel; } - Message::Message (const String& source) : Message(source, false) { - } + Message::Message (const String& source) + : Message(source, false) + {} Message::Message (const String& source, bool decodeValues) { String str = source; diff --git a/src/ipc/message.hh b/src/ipc/message.hh index 0a6285c870..17b6ea5457 100644 --- a/src/ipc/message.hh +++ b/src/ipc/message.hh @@ -1,5 +1,5 @@ -#ifndef SSC_IPC_MESSAGE_H -#define SSC_IPC_MESSAGE_H +#ifndef SOCKET_RUNTIME_IPC_MESSAGE_H +#define SOCKET_RUNTIME_IPC_MESSAGE_H #include "../core/core.hh" #include "client.hh" @@ -7,21 +7,27 @@ namespace SSC::IPC { struct MessageBuffer { size_t size = 0; - SharedPointer bytes = nullptr; - MessageBuffer(SharedPointer bytes, size_t size) - : size(size), bytes(bytes) { } - #ifdef _WIN32 + SharedPointer bytes = nullptr; + + MessageBuffer () = default; + MessageBuffer (SharedPointer bytes, size_t size) + : size(size), + bytes(bytes) + {} + + #if SOCKET_RUNTIME_PLATFORM_WINDOWS ICoreWebView2SharedBuffer* shared_buf = nullptr; - MessageBuffer(ICoreWebView2SharedBuffer* buf, size_t size) - : size(size), shared_buf(buf) { - BYTE* b = reinterpret_cast(bytes); + MessageBuffer (ICoreWebView2SharedBuffer* buf, size_t size) + : size(size), + shared_buf(buf) + { + BYTE* b = reinterpret_cast(bytes.get()); HRESULT r = buf->get_Buffer(&b); if (r != S_OK) { // TODO(trevnorris): Handle this } } #endif - MessageBuffer() = default; }; struct MessageCancellation { @@ -42,7 +48,7 @@ namespace SSC::IPC { Seq seq = ""; Map args; bool isHTTP = false; - std::shared_ptr cancel; + SharedPointer cancel; Message () = default; Message (const Message& message); @@ -52,7 +58,7 @@ namespace SSC::IPC { String get (const String& key) const; String get (const String& key, const String& fallback) const; String str () const { return this->uri; } - const char * c_str () const { return this->uri.c_str(); } + const char* c_str () const { return this->uri.c_str(); } }; } #endif diff --git a/src/ipc/message.kt b/src/ipc/message.kt new file mode 100644 index 0000000000..2814ba8642 --- /dev/null +++ b/src/ipc/message.kt @@ -0,0 +1,88 @@ +// vim: set sw=2: +package socket.runtime.ipc + +import android.net.Uri + +/** + * `Message` is a container for a parsed IPC message (ipc://...) + */ +open class Message (message: String? = null) { + var uri: Uri? = + if (message != null) { + Uri.parse(message) + } else { + Uri.parse("ipc://") + } + + var name: String + get () = uri?.host ?: "" + set (name) { + uri = uri?.buildUpon()?.authority(name)?.build() + } + + var domain: String + get () { + val parts = name.split(".") + return parts.slice(0..(parts.size - 2)).joinToString(".") + } + set (_) {} + + var value: String + get () = get("value") + set (value) { + set("value", value) + } + + var seq: String + get () = get("seq") + set (seq) { + set("seq", seq) + } + + var bytes: ByteArray? = null + + fun get (key: String, defaultValue: String = ""): String { + val value = uri?.getQueryParameter(key) + + if (value != null && value.isNotEmpty()) { + return value + } + + return defaultValue + } + + fun has (key: String): Boolean { + return get(key).isNotEmpty() + } + + fun set (key: String, value: String): Boolean { + uri = uri?.buildUpon()?.appendQueryParameter(key, value)?.build() + return uri == null + } + + fun delete (key: String): Boolean { + if (uri?.getQueryParameter(key) == null) { + return false + } + + val params = uri?.queryParameterNames + val tmp = uri?.buildUpon()?.clearQuery() + + if (params != null) { + for (param: String in params) { + if (!param.equals(key)) { + val value = uri?.getQueryParameter(param) + tmp?.appendQueryParameter(param, value) + } + } + } + + uri = tmp?.build() + + return true + } + + override fun toString(): String { + return uri?.toString() ?: "" + } +} diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index de79457e9a..05eb71d6d1 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -1,10 +1,14 @@ -#include "../core/platform.hh" +#include "../window/window.hh" +#include "../app/app.hh" + #include "navigator.hh" #include "bridge.hh" -#include "../window/window.hh" +#if SOCKET_RUNTIME_PLATFORM_ANDROID +#include "../platform/android.hh" +#endif -#if SSC_PLATFORM_APPLE +#if SOCKET_RUNTIME_PLATFORM_APPLE @implementation SSCNavigationDelegate - (void) webView: (WKWebView*) webview didFailNavigation: (WKNavigation*) navigation @@ -58,78 +62,7 @@ namespace SSC::IPC { URL() {} - void Navigator::Location::init () { - // determine HOME - #if SSC_PLATFORM_WINDOWS - static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); - #elif SSC_PLATFORM_IOS - static const auto HOME = String(NSHomeDirectory().UTF8String); - #else - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - #endif - - static const Map mappings = { - {"\\$HOST_HOME", HOME}, - {"~", HOME}, - - {"\\$HOST_CONTAINER", - #if SSC_PLATFORM_IOS - [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) objectAtIndex: 0].UTF8String - #elif SSC_PLATFORM_MACOS - // `homeDirectoryForCurrentUser` resolves to sandboxed container - // directory when in "sandbox" mode, otherwise the user's HOME directory - NSFileManager.defaultManager.homeDirectoryForCurrentUser.absoluteString.UTF8String - #elif SSC_PLATFORM_LINUX || SSC_PLATFORM_ANDROID - // TODO(@jwerle): figure out `$HOST_CONTAINER` for Linux/Android - getcwd() - #elif SSC_PLATFORM_WINDOWS - // TODO(@jwerle): figure out `$HOST_CONTAINER` for Windows - getcwd() - #else - getcwd() - #endif - }, - - {"\\$HOST_PROCESS_WORKING_DIRECTORY", - #if SSC_PLATFORM_APPLE - NSBundle.mainBundle.resourcePath.UTF8String - #else - getcwd() - #endif - } - }; - - for (const auto& entry : bridge->userConfig) { - if (entry.first.starts_with("webview_navigator_mounts_")) { - auto key = replace(entry.first, "webview_navigator_mounts_", ""); - - if (key.starts_with("android") && !platform.android) continue; - if (key.starts_with("ios") && !platform.ios) continue; - if (key.starts_with("linux") && !platform.linux) continue; - if (key.starts_with("mac") && !platform.mac) continue; - if (key.starts_with("win") && !platform.win) continue; - - key = replace(key, "android_", ""); - key = replace(key, "ios_", ""); - key = replace(key, "linux_", ""); - key = replace(key, "mac_", ""); - key = replace(key, "win_", ""); - - String path = key; - - for (const auto& map : mappings) { - path = replace(path, map.first, map.second); - } - - const auto& value = entry.second; - this->mounts.insert_or_assign(path, value); - } - } - } + void Navigator::Location::init () {} /** * . @@ -170,7 +103,7 @@ namespace SSC::IPC { const auto filename = (fs::path(dirname) / fs::path(result)).make_preferred(); // 1. Try the given path if it's a file - if (fs::is_regular_file(filename)) { + if (FileResource::isFile(filename)) { return Navigator::Location::Resolution { .pathname = "/" + replace(fs::relative(filename, dirname).string(), "\\\\", "/") }; @@ -178,7 +111,7 @@ namespace SSC::IPC { // 2. Try appending a `/` to the path and checking for an index.html const auto index = filename / fs::path("index.html"); - if (fs::is_regular_file(index)) { + if (FileResource::isFile(index)) { if (filename.string().ends_with("\\") || filename.string().ends_with("/")) { return Navigator::Location::Resolution { .pathname = "/" + replace(fs::relative(index, dirname).string(), "\\\\", "/"), @@ -194,7 +127,7 @@ namespace SSC::IPC { // 3. Check if appending a .html file extension gives a valid file const auto html = Path(filename).replace_extension(".html"); - if (fs::is_regular_file(html)) { + if (FileResource::isFile(html)) { return Navigator::Location::Resolution { .pathname = "/" + replace(fs::relative(html, dirname).string(), "\\\\", "/") }; @@ -245,16 +178,16 @@ namespace SSC::IPC { Navigator::Navigator (Bridge* bridge) : bridge(bridge), location(bridge), - serviceWorker(bridge->core->serviceWorker) + serviceWorker(App::sharedApplication()->serviceWorkerContainer) { - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE this->navigationDelegate = [SSCNavigationDelegate new]; this->navigationDelegate.navigator = this; #endif } Navigator::~Navigator () { - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->navigationDelegate) { this->navigationDelegate.navigator = nullptr; @@ -272,9 +205,9 @@ namespace SSC::IPC { } void Navigator::configureWebView (WebView* webview) { - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE webview.navigationDelegate = this->navigationDelegate; - #elif SSC_PLATFORM_LINUX + #elif SOCKET_RUNTIME_PLATFORM_LINUX g_signal_connect( G_OBJECT(webview), "decide-policy", @@ -295,7 +228,7 @@ namespace SSC::IPC { const auto action = webkit_navigation_policy_decision_get_navigation_action(navigation); const auto request = webkit_navigation_action_get_request(action); const auto currentURL = String(webkit_web_view_get_uri(webview)); - const auto requestedURL = String(webkit_uri_request_get_uri(request) + const auto requestedURL = String(webkit_uri_request_get_uri(request)); if (!navigator->handleNavigationRequest(currentURL, requestedURL)) { webkit_policy_decision_ignore(decision); @@ -306,7 +239,7 @@ namespace SSC::IPC { })), this ); - #elif SSC_PLATFORM_WINDOWS + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS EventRegistrationToken tokenNavigation; webview->add_NavigationStarting( Microsoft::WRL::Callback( @@ -400,6 +333,10 @@ namespace SSC::IPC { auto userConfig = this->bridge->userConfig; const auto allowed = split(trim(userConfig["webview_navigator_policies_allowed"]), ' '); + if (requestedURL == "about:blank") { + return true; + } + for (const auto& entry : split(userConfig["webview_protocol-handlers"], " ")) { const auto scheme = replace(trim(entry), ":", ""); if (requestedURL.starts_with(scheme + ":")) { @@ -436,7 +373,11 @@ namespace SSC::IPC { if ( requestedURL.starts_with("socket:") || + requestedURL.starts_with("node:") || requestedURL.starts_with("npm:") || + requestedURL.starts_with("ipc:") || + #if SOCKET_RUNTIME_PLATFORM_APPLE + #endif requestedURL.starts_with(devHost) ) { return true; @@ -444,4 +385,115 @@ namespace SSC::IPC { return false; } + + void Navigator::configureMounts () { + // determine HOME + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); + #elif SOCKET_RUNTIME_PLATFORM_IOS + static const auto HOME = String(NSHomeDirectory().UTF8String); + #else + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + #endif + + static const Map mappings = { + {"\\$HOST_HOME", HOME}, + {"~", HOME}, + + {"\\$HOST_CONTAINER", + #if SOCKET_RUNTIME_PLATFORM_IOS + [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) objectAtIndex: 0].UTF8String + #elif SOCKET_RUNTIME_PLATFORM_MACOS + // `homeDirectoryForCurrentUser` resolves to sandboxed container + // directory when in "sandbox" mode, otherwise the user's HOME directory + NSFileManager.defaultManager.homeDirectoryForCurrentUser.absoluteString.UTF8String + #elif SOCKET_RUNTIME_PLATFORM_LINUX || SOCKET_RUNTIME_PLATFORM_ANDROID + // TODO(@jwerle): figure out `$HOST_CONTAINER` for Linux/Android + getcwd() + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + // TODO(@jwerle): figure out `$HOST_CONTAINER` for Windows + getcwd() + #else + getcwd() + #endif + }, + + {"\\$HOST_PROCESS_WORKING_DIRECTORY", + #if SOCKET_RUNTIME_PLATFORM_APPLE + NSBundle.mainBundle.resourcePath.UTF8String + #else + getcwd() + #endif + } + }; + + for (const auto& entry : this->bridge->userConfig) { + if (entry.first.starts_with("webview_navigator_mounts_")) { + auto key = replace(entry.first, "webview_navigator_mounts_", ""); + + if (key.starts_with("android") && !platform.android) continue; + if (key.starts_with("ios") && !platform.ios) continue; + if (key.starts_with("linux") && !platform.linux) continue; + if (key.starts_with("mac") && !platform.mac) continue; + if (key.starts_with("win") && !platform.win) continue; + + key = replace(key, "android_", ""); + key = replace(key, "ios_", ""); + key = replace(key, "linux_", ""); + key = replace(key, "mac_", ""); + key = replace(key, "win_", ""); + + String path = key; + + for (const auto& map : mappings) { + path = replace(path, map.first, map.second); + } + + const auto& value = entry.second; + this->location.mounts.insert_or_assign(path, value); + #if SOCKET_RUNTIME_PLATFORM_LINUX + auto webContext = webkit_web_context_get_default(); + webkit_web_context_add_path_to_sandbox(webContext, path.c_str(), false); + #endif + } + } + } +} + +#if SOCKET_RUNTIME_PLATFORM_ANDROID +extern "C" { + using namespace SSC; + + jboolean ANDROID_EXTERNAL(ipc, Navigator, isNavigationRequestAllowed) ( + JNIEnv* env, + jobject self, + jint index, + jstring currentURLString, + jstring requestedURLString + ) { + auto app = App::sharedApplication(); + + if (!app) { + ANDROID_THROW(env, "Missing 'App' in environment"); + return false; + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + ANDROID_THROW(env, "Invalid window requested"); + return false; + } + + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + const auto currentURL = Android::StringWrap(attachment.env, currentURLString).str(); + const auto requestedURL = Android::StringWrap(attachment.env, requestedURLString).str(); + + return window->bridge.navigator.isNavigationRequestAllowed(currentURL, requestedURL); + } } +#endif diff --git a/src/ipc/navigator.hh b/src/ipc/navigator.hh index d01b480c69..958bf649cc 100644 --- a/src/ipc/navigator.hh +++ b/src/ipc/navigator.hh @@ -1,13 +1,11 @@ -#ifndef SSC_IPC_NAVIGATOR_H -#define SSC_IPC_NAVIGATOR_H +#ifndef SOCKET_RUNTIME_IPC_NAVIGATOR_H +#define SOCKET_RUNTIME_IPC_NAVIGATOR_H -#include "../core/url.hh" -#include "../core/types.hh" #include "../core/config.hh" -#include "../core/platform.hh" #include "../core/resource.hh" -#include "../core/service_worker_container.hh" -#include "../window/webview.hh" +#include "../core/url.hh" +#include "../core/webview.hh" +#include "../serviceworker/container.hh" namespace SSC::IPC { // forward @@ -15,7 +13,7 @@ namespace SSC::IPC { class Navigator; } -#if SSC_PLATFORM_APPLE +#if SOCKET_RUNTIME_PLATFORM_APPLE @interface SSCNavigationDelegate : NSObject @property (nonatomic) SSC::IPC::Navigator* navigator; - (void) webView: (WKWebView*) webView @@ -82,7 +80,7 @@ namespace SSC::IPC { Location location; Bridge* bridge = nullptr; - #if SSC_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_APPLE SSCNavigationDelegate* navigationDelegate = nullptr; #endif @@ -95,7 +93,7 @@ namespace SSC::IPC { void configureWebView (WebView* object); bool handleNavigationRequest (const String& currentURL, const String& requestedURL); bool isNavigationRequestAllowed (const String& location, const String& requestURL); + void configureMounts (); }; } - #endif diff --git a/src/ipc/navigator.kt b/src/ipc/navigator.kt new file mode 100644 index 0000000000..6e1a8e64e5 --- /dev/null +++ b/src/ipc/navigator.kt @@ -0,0 +1,22 @@ +// vim: set sw=2: +package socket.runtime.ipc + +open class Navigator (val bridge: Bridge) { + fun isNavigationRequestAllowed ( + currentURL: String, + requestedURL: String + ): Boolean { + return this.isNavigationRequestAllowed( + this.bridge.index, + currentURL, + requestedURL + ) + } + + @Throws(Exception::class) + external fun isNavigationRequestAllowed ( + index: Int, + currentURL: String, + requestedURL: String + ): Boolean +} diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc new file mode 100644 index 0000000000..9607a070ae --- /dev/null +++ b/src/ipc/preload.cc @@ -0,0 +1,665 @@ +#include "../core/core.hh" +#include "preload.hh" + +namespace SSC::IPC { + static constexpr auto DEFAULT_REFERRER_POLICY = "unsafe-url"; + static constexpr auto RUNTIME_PRELOAD_META_BEGIN_TAG = ( + R"HTML()HTML" + ); + + static constexpr auto RUNTIME_PRELOAD_META_END_TAG = ( + R"HTML()HTML" + ); + + static constexpr auto RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG = ( + R"HTML()HTML" + ); + + static constexpr auto RUNTIME_PRELOAD_MODULE_BEGIN_TAG = ( + R"HTML()HTML" + ); + + static constexpr auto RUNTIME_PRELOAD_IMPORTMAP_BEGIN_TAG = ( + R"HTML()HTML" + ); + + Preload::Preload (const PreloadOptions& options) + : options(options) + { + this->configure(); + } + + void Preload::configure () { + this->configure(this->options); + } + + void Preload::configure (const PreloadOptions& options) { + this->options = options; + this->headers = options.headers; + this->metadata = options.metadata; + + /* + auto argv = options.argv; + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + do { + // Escape backslashes in paths. + size_t lastPosition = 0; + while ((lastPosition = argv.find('\\', lastPosition)) != String::npos) { + argv.replace(lastPosition, 1, "\\\\"); + lastPosition += 2; + } + } while (0); + #endif + */ + + if (options.features.useHTMLMarkup) { + if (!this->metadata.contains("referrer")) { + this->metadata["referrer"] = DEFAULT_REFERRER_POLICY; + } + + if (!this->headers.has("referrer-policy")) { + this->headers.set("referrer-policy", this->metadata["referrer"]); + } + } + } + + Preload& Preload::append (const String& source) { + this->buffer.push_back(source); + return *this; + } + + const String& Preload::compile () { + Vector buffers; + + auto args = JSON::Object { + JSON::Object::Entries { + {"argv", JSON::Array {}}, + {"client", JSON::Object {}}, + {"config", JSON::Object {}}, + {"debug", options.debug}, + {"headless", options.headless}, + {"env", JSON::Object {}}, + {"index", options.index} + } + }; + + for (const auto& value : this->options.argv) { + args["argv"].as().push(value); + } + + // 1. compile metadata if `options.features.useHTMLMarkup == true`, otherwise skip + if (this->options.features.useHTMLMarkup) { + for (const auto &entry : this->metadata) { + buffers.push_back(tmpl( + R"HTML()HTML", + Map { + {"name", trim(entry.first)}, + {"content", trim(decodeURIComponent(entry.second))} + } + )); + } + } + + // 2. compile headers if `options.features.useHTMLMarkup == true`, otherwise skip + if (this->options.features.useHTMLMarkup) { + for (const auto &entry : this->headers) { + buffers.push_back(tmpl( + R"HTML()HTML", + Map { + {"header", toHeaderCase(entry.name)}, + {"value", trim(decodeURIComponent(entry.value.str()))} + } + )); + } + } + + // 3. compile preload `` "BEGIN" tag if `options.features.useHTMLMarkup == true`, otherwise skip + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_META_BEGIN_TAG); + } + + // 4. compile preload `` prefix if `options.features.useHTMLMarkup == true`, otherwise skip + if (this->options.features.useHTMLMarkup) { + if (this->options.features.useESM) { + buffers.push_back(RUNTIME_PRELOAD_MODULE_END_TAG); + } else { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } + } + + // 15. compile "global CommonJS" if `options.features.useGlobalCommonJS == true`, otherwise skip + if (this->options.features.useGlobalCommonJS) { + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + } + buffers.push_back(R"JAVASCRIPT( + if (globalThis.document) { + ;(async function GlobalCommonJSScope () { + const href = encodeURIComponent(globalThis.location.href) + const source = `socket:module?ref=${href}` + + const { Module } = await import(source) + const path = await import('socket:path') + const require = Module.createRequire(globalThis.location.href) + const __filename = Module.main.filename + const __dirname = path.dirname(__filename) + + Object.defineProperties(globalThis, { + module: { + configurable: true, + enumerable: false, + writable: false, + value: Module.main.scope + }, + require: { + configurable: true, + enumerable: false, + writable: false, + value: require + }, + __dirname: { + configurable: true, + enumerable: false, + writable: false, + value: __dirname + }, + __filename: { + configurable: true, + enumerable: false, + writable: false, + value: __filename + } + }) + + // reload global CommonJS scope on 'popstate' events + globalThis.addEventListener('popstate', GlobalCommonJSScope) + })(); + } + )JAVASCRIPT"); + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } + } + + // 16. compile "global NodeJS" if `options.features.useGlobalNodeJS == true`, otherwise skip + if (this->options.features.useGlobalNodeJS) { + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + } + buffers.push_back(R"JAVASCRIPT( + if (globalThis.document) { + ;(async function GlobalNodeJSScope () { + const process = await import('socket:process') + Object.defineProperties(globalThis, { + process: { + configurable: false, + enumerable: false, + writable: false, + value: process.default + }, + + global: { + configurable: false, + enumerable: false, + writable: false, + value: globalThis + } + }) + })(); + } + )JAVASCRIPT"); + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } + } + + // 17. compile `__RUNTIME_PRIMORDIAL_OVERRIDES__` -- assumes value is a valid JSON string + if (this->options.RUNTIME_PRIMORDIAL_OVERRIDES.size() > 0) { + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + } + + buffers.push_back(tmpl( + R"JAVASCRIPT( + Object.defineProperty(globalThis, '__RUNTIME_PRIMORDIAL_OVERRIDES__', { + configurable: false, + enumerable: false, + writable: false, + value: {{RUNTIME_PRIMORDIAL_OVERRIDES}} + }) + )JAVASCRIPT", + Map {{"RUNTIME_PRIMORDIAL_OVERRIDES", this->options.RUNTIME_PRIMORDIAL_OVERRIDES}} + )); + + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } + } + + // 18. compile preload `` "END" tag if `options.features.useHTMLMarkup == true`, otherwise skip + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_META_END_TAG); + } + + // 19. clear existing compiled state + this->compiled.clear(); + // 20. compile core buffers + for (const auto& buffer : buffers) { + this->compiled += buffer + "\n"; + } + // 21. user preload buffers + if (this->buffer.size() > 0) { + this->compiled += ";(() => {\n"; + this->compiled += join(this->buffer, '\n'); + this->compiled += "})();\n"; + } + if (this->options.features.useHTMLMarkup == false) { + // 22. compile 'sourceURL' source map value if `options.features.useHTMLMarkup == false` + this->compiled += "//# sourceURL=socket:/preload.js"; + } + return this->compiled; + } + + const String& Preload::str () const { + return this->compiled; + } + + const String Preload::insertIntoHTML ( + const String& html, + const InsertIntoHTMLOptions& options + ) const { + if (html.size() == 0) { + return ""; + } + + auto protocolHandlerSchemes = options.protocolHandlerSchemes; + auto userConfig = this->options.userConfig; + auto preload = this->str(); + auto output = html; + if ( + html.find(RUNTIME_PRELOAD_IMPORTMAP_BEGIN_TAG) == String::npos && + userConfig.contains("webview_importmap") && + userConfig.at("webview_importmap").size() > 0 + ) { + auto resource = FileResource(Path(userConfig.at("webview_importmap"))); + + if (resource.exists()) { + const auto bytes = resource.read(); + + if (bytes != nullptr) { + preload = ( + tmpl( + R"HTML()HTML", + Map {{"importmap", String(bytes, resource.size())}} + ) + preload + ); + } + } + } + + protocolHandlerSchemes.push_back("node:"); + protocolHandlerSchemes.push_back("npm:"); + + output = tmpl(output, Map { + {"protocol_handlers", join(protocolHandlerSchemes, " ")} + }); + + if (output.find("") != String::npos) { + output = replace(output, "", String("" + preload)); + } else if (output.find("") != String::npos) { + output = replace(output, "", String("" + preload)); + } else if (output.find("") != String::npos) { + output = replace(output, "", String("" + preload)); + } else { + output = preload + output; + } + } + + return output; + } + + const Preload createPreload (const PreloadOptions& options) { + auto preload = Preload(options); + preload.compile(); + return preload; + } +} diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh new file mode 100644 index 0000000000..3d0f95dc2e --- /dev/null +++ b/src/ipc/preload.hh @@ -0,0 +1,120 @@ +#ifndef SOCKET_RUNTIME_IPC_PRELOAD_H +#define SOCKET_RUNTIME_IPC_PRELOAD_H + +#include "../platform/platform.hh" +#include "../core/headers.hh" +#include "../core/config.hh" + +namespace SSC::IPC { + /** + * `PreloadFeatures` is a container for a variety of ways of configuring the + * compiler features for the compiled preload that is "injeced" into HTML + * documents. + */ + struct PreloadFeatures { + /** + * If `true`, the feature enables global CommonJS values such as `module`, + * `exports`, `require`, `__filename`, and `__dirname`. + */ + bool useGlobalCommonJS = true; + + /** + * If `true`, the feature enables global Node.js values like + * `process` and `global`. + */ + bool useGlobalNodeJS = true; + + /** + * If `true`, the feature enables the automatic import of a "test script" + * that is specified with the `--test ` command + * line argument. This is useful for running tests + */ + bool useTestScript = false; + + /** + * If `true`, the feature enables the compiled preload to use HTML markup + * such as `` prefix if `options.features.useHTMLMarkup == true`, otherwise skip - if (this->options.features.useHTMLMarkup) { - if (this->options.features.useESM) { - buffers.push_back(RUNTIME_PRELOAD_MODULE_END_TAG); - } else { + if (this->options.features.useHTMLMarkup) { buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); } - } - // 15. compile "global CommonJS" if `options.features.useGlobalCommonJS == true`, otherwise skip - if (this->options.features.useGlobalCommonJS) { + // 12. compile preload `` prefix if `options.features.useHTMLMarkup == true`, otherwise skip if (this->options.features.useHTMLMarkup) { - buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + if (this->options.features.useESM) { + buffers.push_back(RUNTIME_PRELOAD_MODULE_END_TAG); + } else { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } } - buffers.push_back(R"JAVASCRIPT( - if (globalThis.document) { - ;(async function GlobalNodeJSScope () { - const process = await import('socket:process') - Object.defineProperties(globalThis, { - process: { - configurable: false, - enumerable: false, - writable: false, - value: process.default - }, - global: { - configurable: false, - enumerable: false, - writable: false, - value: globalThis - } - }) - })(); + // 15. compile "global CommonJS" if `options.features.useGlobalCommonJS == true`, otherwise skip + if (this->options.features.useGlobalCommonJS) { + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + } + + buffers.push_back(R"JAVASCRIPT( + if (globalThis.document) { + ;(async function GlobalCommonJSScope () { + const href = encodeURIComponent(globalThis.location.href) + const source = `socket:module?ref=${href}` + + const { Module } = await import(source) + const path = await import('socket:path') + const require = Module.createRequire(globalThis.location.href) + const __filename = Module.main.filename + const __dirname = path.dirname(__filename) + + Object.defineProperties(globalThis, { + module: { + configurable: true, + enumerable: false, + writable: false, + value: Module.main.scope + }, + require: { + configurable: true, + enumerable: false, + writable: false, + value: require + }, + __dirname: { + configurable: true, + enumerable: false, + writable: false, + value: __dirname + }, + __filename: { + configurable: true, + enumerable: false, + writable: false, + value: __filename + } + }) + + // reload global CommonJS scope on 'popstate' events + globalThis.addEventListener('popstate', GlobalCommonJSScope) + })(); + } + )JAVASCRIPT"); + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); } - )JAVASCRIPT"); - if (this->options.features.useHTMLMarkup) { - buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); } - } - // 17. compile `__RUNTIME_PRIMORDIAL_OVERRIDES__` -- assumes value is a valid JSON string - if (this->options.RUNTIME_PRIMORDIAL_OVERRIDES.size() > 0) { - if (this->options.features.useHTMLMarkup) { - buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + // 16. compile "global NodeJS" if `options.features.useGlobalNodeJS == true`, otherwise skip + if (this->options.features.useGlobalNodeJS) { + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + } + buffers.push_back(R"JAVASCRIPT( + if (globalThis.document) { + ;(async function GlobalNodeJSScope () { + const process = await import('socket:process') + Object.defineProperties(globalThis, { + process: { + configurable: false, + enumerable: false, + writable: false, + value: process.default + }, + + global: { + configurable: false, + enumerable: false, + writable: false, + value: globalThis + } + }) + })(); + } + )JAVASCRIPT"); + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } } - buffers.push_back(tmpl( - R"JAVASCRIPT( - Object.defineProperty(globalThis, '__RUNTIME_PRIMORDIAL_OVERRIDES__', { - configurable: false, - enumerable: false, - writable: false, - value: {{RUNTIME_PRIMORDIAL_OVERRIDES}} - }) - )JAVASCRIPT", - Map {{"RUNTIME_PRIMORDIAL_OVERRIDES", this->options.RUNTIME_PRIMORDIAL_OVERRIDES}} - )); + // 17. compile `__RUNTIME_PRIMORDIAL_OVERRIDES__` -- assumes value is a valid JSON string + if (this->options.RUNTIME_PRIMORDIAL_OVERRIDES.size() > 0) { + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); + } - if (this->options.features.useHTMLMarkup) { - buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + buffers.push_back(tmpl( + R"JAVASCRIPT( + Object.defineProperty(globalThis, '__RUNTIME_PRIMORDIAL_OVERRIDES__', { + configurable: false, + enumerable: false, + writable: false, + value: {{RUNTIME_PRIMORDIAL_OVERRIDES}} + }) + )JAVASCRIPT", + Map {{"RUNTIME_PRIMORDIAL_OVERRIDES", this->options.RUNTIME_PRIMORDIAL_OVERRIDES}} + )); + + if (this->options.features.useHTMLMarkup) { + buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_END_TAG); + } } } diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh index 3d0f95dc2e..d716d0b420 100644 --- a/src/ipc/preload.hh +++ b/src/ipc/preload.hh @@ -46,6 +46,12 @@ namespace SSC::IPC { * module or running a "user script" */ bool useESM = true; + + /** + * If `true`, the feature enables the preload compiler to use include the + * a global `__args` object available on `globalThis`. + */ + bool useGlobalArgs = true; }; /** diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 5a4b3a4c9b..82c9f5a013 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -1103,38 +1103,29 @@ namespace SSC::IPC { return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID - auto bytesCopy = new char[size]{0}; - auto platformResponse = this->platformResponse; + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + const auto byteArray = attachment.env->NewByteArray(size); - memcpy(bytesCopy, bytes, size); - this->request.bridge->dispatch([=] () mutable { - auto app = App::sharedApplication(); - auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - auto byteArray = attachment.env->NewByteArray(size); - - if (bytesCopy != nullptr) { - attachment.env->SetByteArrayRegion( - byteArray, - 0, - size, - (jbyte *) bytesCopy - ); - } + attachment.env->SetByteArrayRegion( + byteArray, + 0, + size, + (jbyte *) bytes + ); - CallVoidClassMethodFromAndroidEnvironment( - attachment.env, - platformResponse, - "write", - "([B)V", - byteArray - ); + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + this->platformResponse, + "write", + "([B)V", + byteArray + ); - if (byteArray != nullptr) { - attachment.env->DeleteLocalRef(byteArray); - } + if (byteArray != nullptr) { + attachment.env->DeleteLocalRef(byteArray); + } - delete [] bytesCopy; - }); return true; #endif } @@ -1228,19 +1219,21 @@ namespace SSC::IPC { #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID auto platformResponse = this->platformResponse; - this->request.bridge->dispatch([=] () mutable { - auto app = App::sharedApplication(); - auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + if (platformResponse != nullptr) { + this->request.bridge->dispatch([=, this] () { + auto app = App::sharedApplication(); + auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - CallVoidClassMethodFromAndroidEnvironment( - attachment.env, - platformResponse, - "finish", - "()V" - ); + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + platformResponse, + "finish", + "()V" + ); - attachment.env->DeleteGlobalRef(platformResponse); - }); + attachment.env->DeleteGlobalRef(platformResponse); + }); + } #else #endif @@ -1453,18 +1446,16 @@ extern "C" { return false; } - const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - - const auto method = Android::StringWrap(attachment.env, (jstring) CallClassMethodFromAndroidEnvironment( - attachment.env, + const auto method = Android::StringWrap(env, (jstring) CallClassMethodFromAndroidEnvironment( + env, Object, requestObject, "getMethod", "()Ljava/lang/String;" )).str(); - const auto headers = Android::StringWrap(attachment.env, (jstring) CallClassMethodFromAndroidEnvironment( - attachment.env, + const auto headers = Android::StringWrap(env, (jstring) CallClassMethodFromAndroidEnvironment( + env, Object, requestObject, "getHeaders", @@ -1472,7 +1463,7 @@ extern "C" { )).str(); const auto requestBodyByteArray = (jbyteArray) CallClassMethodFromAndroidEnvironment( - attachment.env, + env, Object, requestObject, "getBody", @@ -1480,7 +1471,7 @@ extern "C" { ); const auto requestBodySize = requestBodyByteArray != nullptr - ? attachment.env->GetArrayLength(requestBodyByteArray) + ? env->GetArrayLength(requestBodyByteArray) : 0; const auto bytes = requestBodySize > 0 @@ -1488,7 +1479,7 @@ extern "C" { : nullptr; if (requestBodyByteArray) { - attachment.env->GetByteArrayRegion( + env->GetByteArrayRegion( requestBodyByteArray, 0, requestBodySize, @@ -1496,7 +1487,7 @@ extern "C" { ); } - const auto requestObjectRef = attachment.env->NewGlobalRef(requestObject); + const auto requestObjectRef = env->NewGlobalRef(requestObject); const auto request = IPC::SchemeHandlers::Request::Builder( &window->bridge.schemeHandlers, requestObjectRef @@ -1518,7 +1509,7 @@ extern "C" { }); if (!handled) { - attachment.env->DeleteGlobalRef(requestObjectRef); + env->DeleteGlobalRef(requestObjectRef); } return handled; diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 81455a6467..63d1216d0b 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -166,6 +166,7 @@ namespace SSC::IPC { Atomic finished = false; SchemeHandlers* handlers = nullptr; PlatformResponse platformResponse = nullptr; + #if SOCKET_RUNTIME_PLATFORM_LINUX GInputStream* platformResponseStream = nullptr; #endif diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index 190eacd465..fdb4d966f5 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -72,17 +72,21 @@ open class SchemeHandlers (val bridge: Bridge) { fun getUrl (): String { return this.request.url.toString().replace("https:", "socket:") } + + fun getWebResourceResponse (): WebResourceResponse? { + return this.response.response + } } open class Response (val request: Request) { val stream = PipedOutputStream() + var mimeType = "application/octet-stream" val response = WebResourceResponse( - "application/octet-stream", + mimeType, null, - PipedInputStream(stream) + null ) - var mimeType = "application/octet-stream" val headers = mutableMapOf() var pendingWrites = 0 @@ -107,17 +111,26 @@ open class SchemeHandlers (val bridge: Bridge) { fun write (bytes: ByteArray) { val stream = this.stream + console.log("begin response write") + if (this.response.data == null) { + try { + this.response.data = PipedInputStream(this.stream) + } catch (err: Exception) { + console.log("stream.connect error: ${err.toString()}") + } + } try { - pendingWrites++ - stream.write(bytes) + this.pendingWrites++ + stream.write(bytes, 0, bytes.size) } catch (err: Exception) { - console.log(err.toString()) + console.log("stream.write error: ${err.toString()}") if (!err.message.toString().contains("closed")) { console.error("socket.runtime.ipc.SchemeHandlers.Response: ${err.toString()}") } } - pendingWrites-- + this.pendingWrites-- + console.log("end response write") } fun write (string: String) { @@ -127,29 +140,22 @@ open class SchemeHandlers (val bridge: Bridge) { fun finish () { val stream = this.stream thread { - console.log("before close wait") - while (pendingWrites > 0) { - console.log("pending") + while (this.pendingWrites > 0) { + Thread.sleep(4) } - console.log("after close wait") stream.flush() - console.log("flushed") stream.close() - console.log("closed") + console.log("response closed") } } - - fun getWebResourceResponse (): WebResourceResponse? { - return this.response - } } fun handleRequest (webResourceRequest: WebResourceRequest): WebResourceResponse? { val request = Request(this.bridge, webResourceRequest) - if (this.handleRequest(bridge.index, request)) { - return request.response.getWebResourceResponse() + if (this.handleRequest(this.bridge.index, request)) { + return request.getWebResourceResponse() } return null From dc0f228dd9368d3d5936a75ea7c685573734ed11 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 21 May 2024 10:11:32 -0400 Subject: [PATCH 0740/1178] refactor(app): use android looper for dispatch --- src/app/app.cc | 36 ++++++++++++++++++------------------ src/app/app.hh | 2 -- src/app/app.kt | 1 + 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 14e3942513..c1835defd7 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -680,6 +680,12 @@ namespace SSC { int App::run (int argc, char** argv) { #if SOCKET_RUNTIME_PLATFORM_LINUX gtk_main(); + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + // MUST be acquired on "main" thread + // `run()` should called when the main activity is created + if (!this->androidLooper.isAcquired()) { + this->androidLooper.acquire(); + } #elif SOCKET_RUNTIME_PLATFORM_MACOS [NSApp run]; #elif SOCKET_RUNTIME_PLATFORM_IOS @@ -801,7 +807,7 @@ namespace SSC { // TODO(trevnorris): Need to also check a shouldExit so this doesn't run forever in case // the rest of the application needs to exit before isReady is set. while (!this->isReady) { - std::this_thread::sleep_for(std::chrono::milliseconds(16)); + msleep(16); } PostThreadMessage(mainThread, WM_APP, 0, threadCallback); @@ -809,12 +815,11 @@ namespace SSC { t.detach(); #elif SOCKET_RUNTIME_PLATFORM_ANDROID - if (this->androidLooper.isAcquired()) { - this->androidLooper.dispatch([=] () { - const auto attachment = Android::JNIEnvironmentAttachment(this->jvm); - callback(); - }); - } + auto attachment = Android::JNIEnvironmentAttachment(this->jvm); + this->androidLooper.dispatch([=, this] () { + const auto attachment = Android::JNIEnvironmentAttachment(this->jvm); + callback(); + }); #endif } @@ -926,9 +931,7 @@ namespace SSC { extern "C" { jlong ANDROID_EXTERNAL(app, App, alloc)(JNIEnv *env, jobject self) { if (App::sharedApplication() == nullptr) { - auto attachment = Android::JNIEnvironmentAttachment(env); - auto app = new App(attachment.env, self); - app->init(); + auto app = new App(env, self); } return reinterpret_cast(App::sharedApplication()); } @@ -1094,11 +1097,8 @@ extern "C" { app->jni->DeleteGlobalRef(app->appActivity); } - app->appActivity = app->jni->NewGlobalRef(appActivity); - - if (!app->androidLooper.isAcquired()) { - app->androidLooper.acquire(); - } + app->appActivity = env->NewGlobalRef(appActivity); + app->run(); if (app->windowManager.getWindowStatus(0) == WindowManager::WINDOW_NONE) { auto windowManagerOptions = WindowManagerOptions {}; @@ -1181,7 +1181,7 @@ extern "C" { } if (app->appActivity) { - app->jni->DeleteGlobalRef(app->appActivity); + env->DeleteGlobalRef(app->appActivity); app->appActivity = nullptr; } } @@ -1193,8 +1193,8 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - if (app->jni && app->self) { - app->jni->NewGlobalRef(app->self); + if (app->self) { + env->DeleteGlobalRef(app->self); } app->jni = nullptr; diff --git a/src/app/app.hh b/src/app/app.hh index ac16ea8335..74f3e81819 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -84,8 +84,6 @@ namespace SSC { jobject self; jobject appActivity; bool isAndroidEmulator = false; - int androidLooperpipeFDS[2]; - #endif ExitCallback onExit = nullptr; diff --git a/src/app/app.kt b/src/app/app.kt index 0307bab47b..520f529d42 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -91,6 +91,7 @@ open class AppActivity : WindowManagerActivity() { ) } + console.log("onCreateAppActivity") app.onCreateAppActivity(this) if (savedInstanceState == null) { From 41f3f9171d1d1ab7bb0d90978cc66ada7ed4a4a6 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 21 May 2024 10:11:47 -0400 Subject: [PATCH 0741/1178] refactor(window/manager): clean up --- src/window/manager.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/window/manager.cc b/src/window/manager.cc index 8ef73eee22..77eb6c1a44 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -231,7 +231,6 @@ namespace SSC { } } - for (const auto& tuple : options.userConfig) { windowOptions.userConfig[tuple.first] = tuple.second; } @@ -240,13 +239,13 @@ namespace SSC { this->log("Creating Window#" + std::to_string(options.index)); } - auto window = new ManagedWindow(*this, this->core, windowOptions); + auto window = std::make_shared(*this, this->core, windowOptions); window->status = WindowStatus::WINDOW_CREATED; window->onExit = this->options.onExit; window->onMessage = this->options.onMessage; - this->windows[options.index].reset(window); + this->windows[options.index] = window; return this->windows.at(options.index); } From 548d71bcfb42016778da2905bed8f6e7d67a6862 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Tue, 21 May 2024 10:12:01 -0400 Subject: [PATCH 0742/1178] refactor(serviceworker): clean up --- src/serviceworker/container.cc | 4 ++++ src/serviceworker/protocols.cc | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/serviceworker/container.cc b/src/serviceworker/container.cc index a4ae9b69cf..9914158af5 100644 --- a/src/serviceworker/container.cc +++ b/src/serviceworker/container.cc @@ -682,7 +682,11 @@ namespace SSC { this->core->setTimeout(8, [this, request, callback, ®istration] { if (!this->fetch(request, callback)) { debug( + #if SOCKET_RUNTIME_PLATFORM_APPLE "ServiceWorkerContainer: Failed to dispatch fetch request '%s %s%s' for client '%llu'", + #else + "ServiceWorkerContainer: Failed to dispatch fetch request '%s %s%s' for client '%lu'", + #endif request.method.c_str(), request.pathname.c_str(), (request.query.size() > 0 ? String("?") + request.query : String("")).c_str(), diff --git a/src/serviceworker/protocols.cc b/src/serviceworker/protocols.cc index 48e98f081e..67df2c78b2 100644 --- a/src/serviceworker/protocols.cc +++ b/src/serviceworker/protocols.cc @@ -21,6 +21,10 @@ namespace SSC { bool ServiceWorkerProtocols::registerHandler (const String& scheme, const Data data) { Lock lock(this->mutex); + if (scheme.size() == 0) { + return false; + } + if (std::find(reserved.begin(), reserved.end(), scheme) != reserved.end()) { return false; } From ec79ea9a9302bd8aa1ba0e9b28604bfd0b6e5263 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Thu, 23 May 2024 19:38:40 +0200 Subject: [PATCH 0743/1178] feature(ai): adds initial integration --- api/ai.js | 110 +++++ api/commonjs/builtins.js | 1 + bin/build-runtime-library.sh | 76 +++- bin/cflags.sh | 3 + bin/install.sh | 119 +++++- bin/ldflags.sh | 4 +- socket-runtime.pc.in | 2 +- src/cli/cli.cc | 11 + src/core/core.hh | 44 +- src/core/json.cc | 1 + src/core/modules/ai.cc | 759 +++++++++++++++++++++++++++++++++++ src/core/modules/ai.hh | 114 ++++++ src/ipc/routes.cc | 44 ++ 13 files changed, 1257 insertions(+), 31 deletions(-) create mode 100644 api/ai.js create mode 100644 src/core/modules/ai.cc create mode 100644 src/core/modules/ai.hh diff --git a/api/ai.js b/api/ai.js new file mode 100644 index 0000000000..d064141573 --- /dev/null +++ b/api/ai.js @@ -0,0 +1,110 @@ +// @ts-check +/** + * @module AI + * + * Provides high level classes for common AI tasks + * + * Example usage: + * ```js + * import { LLM } from 'socket:ai' + * ``` + */ +import ipc from './ipc.js' +import gc from './gc.js' +import { EventEmitter } from './events.js' +import { rand64 } from './crypto.js' +import * as exports from './ai.js' + +/** + * A class to interact with large language models (using llama.cpp) + * @extends EventEmitter + */ +export class LLM extends EventEmitter { + /** + * Constructs an LLM instance. + * @param {Object} options - The options for initializing the LLM. + * @param {string} options.path - The path to a valid model (.gguf). + * @param {string} options.prompt - The query that guides the model to generate a relevant and coherent responses. + * @param {string} [options.id] - The optional ID for the LLM instance. + * @throws {Error} If the model path is not provided. + */ + constructor (options = {}) { + super() + + if (!options.path) { + throw new Error('expected a path to a valid model (.gguf)') + } + + this.path = options.path + this.prompt = options.prompt + this.id = options.id || rand64() + + const opts = { + id: this.id, + prompt: this.prompt, + path: this.path + } + + globalThis.addEventListener('data', ({ detail }) => { + const { err, data, source } = detail.params + + if (err && BigInt(err.id) === this.id) { + return this.emit('error', err) + } + + if (!data || BigInt(data.id) !== this.id) return + + if (source === 'ai.llm.chat') { + if (data.complete) { + return this.emit('end') + } + + this.emit('data', data.token) + } + }) + + const result = ipc.sendSync('ai.llm.create', opts) + + if (result.err) { + throw result.err + } + } + + /** + * Tell the LLM to stop after the next token. + * @returns {Promise} A promise that resolves when the LLM stops. + */ + async stop () { + return ipc.request('ai.llm.stop', { id: this.id }) + } + + /** + * Implements `gc.finalizer` for gc'd resource cleanup. + * @param {Object} options - The options for finalizer. + * @returns {gc.Finalizer} The finalizer object. + * @ignore + */ + [gc.finalizer] (options) { + return { + args: [this.id, options], + async handle (id) { + if (process.env.DEBUG) { + console.warn('Closing Socket on garbage collection') + } + + await ipc.request('ai.llm.destroy', { id }, options) + } + } + } + + /** + * Send a message to the chat. + * @param {string} message - The message to send to the chat. + * @returns {Promise} A promise that resolves with the response from the chat. + */ + async chat (message) { + return ipc.request('ai.llm.chat', { id: this.id, message }) + } +} + +export default exports diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index 80f2f9d4d4..066d1de156 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -12,6 +12,7 @@ import _async, { } from '../async.js' // eslint-disable-next-line +import * as ai from '../ai.js' import * as application from '../application.js' import assert from '../assert.js' import * as buffer from '../buffer.js' diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index 9c3208362c..c1b632fe04 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -105,6 +105,11 @@ declare sources=( $(find "$root"/src/ipc/*.cc) $(find "$root"/src/platform/*.cc) $(find "$root"/src/serviceworker/*.cc) + "$root/build/llama/common/common.cpp" + "$root/build/llama/common/sampling.cpp" + "$root/build/llama/common/json-schema-to-grammar.cpp" + "$root/build/llama/common/grammar-parser.cpp" + "$root/build/llama/llama.cpp" "$root/src/window/manager.cc" "$root/src/window/dialog.cc" "$root/src/window/hotkey.cc" @@ -129,9 +134,11 @@ if [[ "$platform" = "android" ]]; then sources+=("$root/src/window/android.cc") elif [[ "$host" = "Darwin" ]]; then sources+=("$root/src/window/apple.mm") - if (( TARGET_OS_IPHONE)) || (( TARGET_IPHONE_SIMULATOR )); then - cflags=("-sdk" "iphoneos" "$clang") - clang="xcrun" + + if (( TARGET_OS_IPHONE)); then + clang="xcrun -sdk iphoneos "$clang"" + elif (( TARGET_IPHONE_SIMULATOR )); then + clang="xcrun -sdk iphonesimulator "$clang"" else sources+=("$root/src/core/process/unix.cc") fi @@ -154,11 +161,23 @@ mkdir -p "$output_directory" cd "$(dirname "$output_directory")" +sources+=("$output_directory/llama/build-info.cpp") + echo "# building runtime static libary ($arch-$platform)" for source in "${sources[@]}"; do declare src_directory="$root/src" + declare object="${source/.cc/$d.o}" - declare object="${object/$src_directory/$output_directory}" + object="${object/.cpp/$d.o}" + + declare build_dir="$root/build" + + if [[ "$object" =~ ^"$src_directory" ]]; then + object="${object/$src_directory/$output_directory}" + else + object="${object/$build_dir/$output_directory}" + fi + objects+=("$object") done @@ -166,6 +185,38 @@ if [[ -z "$ignore_header_mtimes" ]]; then test_headers+="$(find "$root/src"/core/*.hh)" fi +function generate_llama_build_info () { + build_number="0" + build_commit="unknown" + build_compiler="unknown" + build_target="unknown" + + if out=$(git rev-list --count HEAD); then + # git is broken on WSL so we need to strip extra newlines + build_number=$(printf '%s' "$out" | tr -d '\n') + fi + + if out=$(git rev-parse --short HEAD); then + build_commit=$(printf '%s' "$out" | tr -d '\n') + fi + + if out=$($clang --version | head -1); then + build_compiler=$out + fi + + if out=$($clang -dumpmachine); then + build_target=$out + fi + + echo "# generating llama build info" + cat > "$output_directory/llama/build-info.cpp" << LLAMA_BUILD_INFO + int LLAMA_BUILD_NUMBER = $build_number; + char const *LLAMA_COMMIT = "$build_commit"; + char const *LLAMA_COMPILER = "$build_compiler"; + char const *LLAMA_BUILD_TARGET = "$build_target"; +LLAMA_BUILD_INFO +} + function main () { trap onsignal INT TERM local i=0 @@ -177,6 +228,8 @@ function main () { cp -rf "$root/include"/* "$output_directory/include" rm -f "$output_directory/include/socket/_user-config-bytes.hh" + generate_llama_build_info + for source in "${sources[@]}"; do if (( ${#pids[@]} > max_concurrency )); then wait "${pids[0]}" 2>/dev/null @@ -185,9 +238,20 @@ function main () { { declare src_directory="$root/src" + declare object="${source/.cc/$d.o}" + object="${object/.cpp/$d.o}" + declare header="${source/.cc/.hh}" - declare object="${object/$src_directory/$output_directory}" + header="${header/.cpp/.h}" + + declare build_dir="$root/build" + + if [[ "$object" =~ ^"$src_directory" ]]; then + object="${object/$src_directory/$output_directory}" + else + object="${object/$build_dir/$output_directory}" + fi if (( force )) || ! test -f "$object" || @@ -197,7 +261,7 @@ function main () { then mkdir -p "$(dirname "$object")" echo "# compiling object ($arch-$platform) $(basename "$source")" - quiet "$clang" "${cflags[@]}" -c "$source" -o "$object" || onsignal + quiet $clang "${cflags[@]}" -c "$source" -o "$object" || onsignal echo "ok - built ${source/$src_directory\//} -> ${object/$output_directory\//} ($arch-$platform)" fi } & pids+=($!) diff --git a/bin/cflags.sh b/bin/cflags.sh index 806220e32d..0f1c5ded8d 100755 --- a/bin/cflags.sh +++ b/bin/cflags.sh @@ -52,6 +52,9 @@ cflags+=( -std=c++2a -I"$root/include" -I"$root/build/uv/include" + -I"$root/build" + -I"$root/build/llama" + -I"$root/build/llama/common" -I"$root/build/include" -DSOCKET_RUNTIME_BUILD_TIME="$(date '+%s')" -DSOCKET_RUNTIME_VERSION_HASH=$(git rev-parse --short=8 HEAD) diff --git a/bin/install.sh b/bin/install.sh index ccc496758f..7c5a01fa5a 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -220,12 +220,15 @@ function _build_cli { # local libs=($("echo" -l{socket-runtime})) local libs="" + # + # Add libuv, socket-runtime and llama + # if [[ "$(uname -s)" != *"_NT"* ]]; then - libs=($("echo" -l{uv,socket-runtime})) + libs=($("echo" -l{uv,llama,socket-runtime})) fi if [[ -n "$VERBOSE" ]]; then - echo "# cli libs: $libs, $(uname -s)" + echo "# cli libs: ${libs[@]}, $(uname -s)" fi local ldflags=($("$root/bin/ldflags.sh" --arch "$arch" --platform $platform ${libs[@]})) @@ -281,6 +284,7 @@ function _build_cli { test_sources+=("$static_libs") elif [[ "$(uname -s)" == "Linux" ]]; then static_libs+=("$BUILD_DIR/$arch-$platform/lib/libuv.a") + static_libs+=("$BUILD_DIR/$arch-$platform/lib/libllama.a") static_libs+=("$BUILD_DIR/$arch-$platform/lib/libsocket-runtime.a") fi @@ -512,7 +516,14 @@ function _prepare { mv "$tempmkl" "$BUILD_DIR/uv/CMakeLists.txt" fi - die $? "not ok - unable to clone. See trouble shooting guide in the README.md file" + die $? "not ok - unable to clone libuv. See trouble shooting guide in the README.md file" + fi + + if [ ! -d "$BUILD_DIR/llama" ]; then + git clone --depth=1 https://github.com/socketsupply/llama.cpp.git "$BUILD_DIR/llama" > /dev/null 2>&1 + rm -rf $BUILD_DIR/llama/.git + + die $? "not ok - unable to clone llama. See trouble shooting guide in the README.md file" fi echo "ok - directories prepared" @@ -764,6 +775,95 @@ function _compile_libuv_android { fi } +function _compile_llama { + target=$1 + hosttarget=$1 + platform=$2 + + if [ -z "$target" ]; then + target="$(host_arch)" + platform="desktop" + fi + + echo "# building llama.cpp for $platform ($target) on $host..." + STAGING_DIR="$BUILD_DIR/$target-$platform/llama" + + if [ ! -d "$STAGING_DIR" ]; then + mkdir -p "$STAGING_DIR" + cp -r "$BUILD_DIR"/llama/* "$STAGING_DIR" + cd "$STAGING_DIR" || exit 1 + else + cd "$STAGING_DIR" || exit 1 + fi + + mkdir -p "$STAGING_DIR/build/" + + if [ "$platform" == "desktop" ]; then + if [[ "$host" != "Win32" ]]; then + quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" + die $? "not ok - desktop configure" + + quiet cmake --build build --target clean + quiet cmake --build build -- -j"$CPU_CORES" + quiet cmake --install build + else + if ! test -f "$BUILD_DIR/$target-$platform/lib$d/libllama.lib"; then + local config="Release" + if [[ -n "$DEBUG" ]]; then + config="Debug" + fi + cd "$STAGING_DIR/build/" || exit 1 + quiet cmake -S .. -B . -DBUILD_TESTING=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_SERVER=OFF -DLLAMA_BUILD_SHARED=OFF + quiet cmake --build . --config $config + mkdir -p "$BUILD_DIR/$target-$platform/lib$d" + quiet echo "cp -up $STAGING_DIR/build/$config/libllama.lib "$BUILD_DIR/$target-$platform/lib$d/libllama.lib"" + cp -up "$STAGING_DIR/build/$config/libllama.lib" "$BUILD_DIR/$target-$platform/lib$d/libllama.lib" + if [[ -n "$DEBUG" ]]; then + cp -up "$STAGING_DIR"/build/$config/llama_a.pdb "$BUILD_DIR/$target-$platform/lib$d/llama_a.pdb" + fi; + fi + fi + + rm -f "$root/build/$(host_arch)-desktop/lib$d"/*.{so,la,dylib}* + return + fi + + if [ "$hosttarget" == "arm64" ]; then + hosttarget="arm" + fi + + local sdk="iphoneos" + [[ "$platform" == "iPhoneSimulator" ]] && sdk="iphonesimulator" + + export PLATFORM=$platform + export CC="$(xcrun -sdk $sdk -find clang)" + export CXX="$(xcrun -sdk $sdk -find clang++)" + export STRIP="$(xcrun -sdk $sdk -find strip)" + export LD="$(xcrun -sdk $sdk -find ld)" + export CPP="$CC -E" + export CFLAGS="-fembed-bitcode -arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION" + export AR=$(xcrun -sdk $sdk -find ar) + export RANLIB=$(xcrun -sdk $sdk -find ranlib) + export CPPFLAGS="-fembed-bitcode -arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION" + export LDFLAGS="-Wc,-fembed-bitcode -arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk" + + #if ! test -f CMakeLists.txt; then + quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES="$target" -DCMAKE_OSX_SYSROOT=$(xcrun --sdk $sdk --show-sdk-path) + #fi + + if [ ! $? = 0 ]; then + echo "WARNING! - iOS will not be enabled. iPhone simulator not found, try \"sudo xcode-select --switch /Applications/Xcode.app\"." + return + fi + + cmake --build build -- -j"$CPU_CORES" + cmake --install build + + cd "$BUILD_DIR" || exit 1 + rm -f "$root/build/$target-$platform/lib$d"/*.{so,la,dylib}* + echo "ok - built for $target" +} + function _compile_libuv { target=$1 hosttarget=$1 @@ -840,6 +940,7 @@ function _compile_libuv { export PLATFORM=$platform export CC="$(xcrun -sdk $sdk -find clang)" + export CXX="$(xcrun -sdk $sdk -find clang++)" export STRIP="$(xcrun -sdk $sdk -find strip)" export LD="$(xcrun -sdk $sdk -find ld)" export CPP="$CC -E" @@ -912,6 +1013,11 @@ cd "$BUILD_DIR" || exit 1 trap onsignal INT TERM +{ + _compile_llama + echo "ok - built llama for $platform ($target)" +} & _compile_llama_pid=$! + # Although we're passing -j$CPU_CORES on non Win32, we still don't get max utiliztion on macos. Start this before fat libs. { _compile_libuv @@ -931,9 +1037,14 @@ if [[ "$(uname -s)" == "Darwin" ]] && [[ -z "$NO_IOS" ]]; then _setSDKVersion iPhoneOS _compile_libuv arm64 iPhoneOS & pids+=($!) + _compile_llama arm64 iPhoneOS & pids+=($!) + _compile_libuv x86_64 iPhoneSimulator & pids+=($!) + _compile_llama x86_64 iPhoneSimulator & pids+=($!) + if [[ "$arch" = "arm64" ]]; then _compile_libuv arm64 iPhoneSimulator & pids+=($!) + _compile_llama arm64 iPhoneSimulator & pids+=($!) fi for pid in "${pids[@]}"; do wait "$pid"; done @@ -951,6 +1062,7 @@ fi if [[ "$host" != "Win32" ]]; then # non windows hosts uses make -j$CPU_CORES, wait for them to finish. wait $_compile_libuv_pid + wait $_compile_llama_pid fi if [[ -n "$BUILD_ANDROID" ]]; then @@ -974,6 +1086,7 @@ _get_web_view2 if [[ "$host" == "Win32" ]]; then # Wait for Win32 lib uv build wait $_compile_libuv_pid + wait $_compile_llama_pid fi _check_compiler_features diff --git a/bin/ldflags.sh b/bin/ldflags.sh index 40097f8e14..abfdb1d146 100755 --- a/bin/ldflags.sh +++ b/bin/ldflags.sh @@ -109,6 +109,8 @@ if [[ "$host" = "Darwin" ]]; then ldflags+=("-framework" "Network") ldflags+=("-framework" "UniformTypeIdentifiers") ldflags+=("-framework" "WebKit") + ldflags+=("-framework" "Metal") + ldflags+=("-framework" "Accelerate") ldflags+=("-framework" "UserNotifications") ldflags+=("-framework" "OSLog") ldflags+=("-ldl") @@ -118,7 +120,7 @@ elif [[ "$host" = "Linux" ]]; then elif [[ "$host" = "Win32" ]]; then if [[ -n "$DEBUG" ]]; then # https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170 - # TODO(@mribbons): Populate from vcvars64.bat + # TODO(@heapwolf): Populate from vcvars64.bat IFS=',' read -r -a libs <<< "$WIN_DEBUG_LIBS" for (( i = 0; i < ${#libs[@]}; ++i )); do ldflags+=("${libs[$i]}") diff --git a/socket-runtime.pc.in b/socket-runtime.pc.in index a608c699f2..fd473a5c97 100644 --- a/socket-runtime.pc.in +++ b/socket-runtime.pc.in @@ -3,5 +3,5 @@ Version: {{VERSION}} Description: Build and package lean, fast, native desktop and mobile applications using the web technologies you already know. URL: https://github.com/socketsupply/socket Requires: {{DEPENDENCIES}} -Libs: -L{{LIB_DIRECTORY}} -lsocket-runtime -luv {{LDFLAGS}} +Libs: -L{{LIB_DIRECTORY}} -lsocket-runtime -luv -lllama {{LDFLAGS}} Cflags: -I{{INCLUDE_DIRECTORY}} {{CFLAGS}} diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 2ea1e0b503..8e5bb63dac 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -3075,6 +3075,8 @@ int main (const int argc, const char* argv[]) { flags += " -framework Network"; flags += " -framework UserNotifications"; flags += " -framework WebKit"; + flags += " -framework Metal"; + flags += " -framework Accelerate"; flags += " -framework Carbon"; flags += " -framework Cocoa"; flags += " -framework OSLog"; @@ -3089,6 +3091,7 @@ int main (const int argc, const char* argv[]) { flags += " -L" + prefixFile("lib/" + platform.arch + "-desktop"); flags += " -lsocket-runtime"; flags += " -luv"; + flags += " -lllama"; flags += " -I\"" + Path(paths.platformSpecificOutputPath / "include").string() + "\""; files += prefixFile("objects/" + platform.arch + "-desktop/desktop/main.o"); files += prefixFile("src/init.cc"); @@ -4411,6 +4414,8 @@ int main (const int argc, const char* argv[]) { " -framework CoreLocation" + " -framework Network" + " -framework UserNotifications" + + " -framework Metal" + + " -framework Accelerate" + " -framework WebKit" + " -framework Cocoa" + " -framework OSLog" @@ -4632,6 +4637,7 @@ int main (const int argc, const char* argv[]) { << " -L" + libdir << " -lsocket-runtime" << " -luv" + << " -lllama" << " -isysroot " << iosSdkPath << "/" << " -iframeworkwithsysroot /System/Library/Frameworks/" << " -F " << iosSdkPath << "/System/Library/Frameworks/" @@ -4641,6 +4647,8 @@ int main (const int argc, const char* argv[]) { << " -framework Foundation" << " -framework Network" << " -framework UserNotifications" + << " -framework Metal" + << " -framework Accelerate" << " -framework WebKit" << " -framework UIKit" << " -fembed-bitcode" @@ -5722,6 +5730,8 @@ int main (const int argc, const char* argv[]) { compilerFlags += " -framework CoreLocation"; compilerFlags += " -framework Network"; compilerFlags += " -framework UserNotifications"; + compilerFlags += " -framework Metal"; + compilerFlags += " -framework Accelerate"; compilerFlags += " -framework WebKit"; compilerFlags += " -framework Cocoa"; compilerFlags += " -framework OSLog"; @@ -6051,6 +6061,7 @@ int main (const int argc, const char* argv[]) { << " " << extraFlags #if defined(__linux__) << " -luv" + << " -lllama" << " -lsocket-runtime" #endif << (" -L" + quote + trim(prefixFile("lib/" + platform.arch + "-desktop")) + quote) diff --git a/src/core/core.hh b/src/core/core.hh index 8ac1e7027d..bbd35ab12c 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -22,6 +22,7 @@ #include "version.hh" #include "webview.hh" +#include "modules/ai.hh" #include "modules/child_process.hh" #include "modules/dns.hh" #include "modules/fs.hh" @@ -44,9 +45,9 @@ namespace SSC { class Core { public: - #if !SOCKET_RUNTIME_PLATFORM_IOS - using ChildProcess = CoreChildProcess; - #endif + #if !SOCKET_RUNTIME_PLATFORM_IOS + using ChildProcess = CoreChildProcess; + #endif using DNS = CoreDNS; using FS = CoreFS; using Geolocation = CoreGeolocation; @@ -56,6 +57,7 @@ namespace SSC { using Platform = CorePlatform; using Timers = CoreTimers; using UDP = CoreUDP; + using AI = CoreAI; #if !SOCKET_RUNTIME_PLATFORM_IOS ChildProcess childProcess; @@ -69,6 +71,7 @@ namespace SSC { Platform platform; Timers timers; UDP udp; + AI ai; Posts posts; @@ -86,25 +89,26 @@ namespace SSC { uv_async_t eventLoopAsync; Queue eventLoopDispatchQueue; - #if SOCKET_RUNTIME_PLATFORM_APPLE - dispatch_queue_attr_t eventLoopQueueAttrs = dispatch_queue_attr_make_with_qos_class( - DISPATCH_QUEUE_SERIAL, - QOS_CLASS_DEFAULT, - -1 - ); - - dispatch_queue_t eventLoopQueue = dispatch_queue_create( - "socket.runtime.core.loop.queue", - eventLoopQueueAttrs - ); - #else - Thread *eventLoopThread = nullptr; - #endif + #if SOCKET_RUNTIME_PLATFORM_APPLE + dispatch_queue_attr_t eventLoopQueueAttrs = dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_SERIAL, + QOS_CLASS_DEFAULT, + -1 + ); + + dispatch_queue_t eventLoopQueue = dispatch_queue_create( + "socket.runtime.core.loop.queue", + eventLoopQueueAttrs + ); + #else + Thread *eventLoopThread = nullptr; + #endif Core () : - #if !SOCKET_RUNTIME_PLATFORM_IOS - childProcess(this), - #endif + #if !SOCKET_RUNTIME_PLATFORM_IOS + childProcess(this), + #endif + ai(this), dns(this), fs(this), geolocation(this), diff --git a/src/core/json.cc b/src/core/json.cc index 119720228c..dcba8178fc 100644 --- a/src/core/json.cc +++ b/src/core/json.cc @@ -75,6 +75,7 @@ namespace SSC::JSON { SSC::String String::str () const { auto escaped = replace(this->data, "\"", "\\\""); + escaped = replace(escaped, "\\n", "\\\\n"); return "\"" + replace(escaped, "\n", "\\n") + "\""; } diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc new file mode 100644 index 0000000000..26603f6b69 --- /dev/null +++ b/src/core/modules/ai.cc @@ -0,0 +1,759 @@ +#include "../core.hh" +#include "../resource.hh" +#include "ai.hh" + +namespace SSC { + static JSON::Object::Entries ERR_AI_LLM_NOEXISTS ( + const String& source, + CoreAI::ID id + ) { + return JSON::Object::Entries { + {"source", source}, + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "InternalError"}, + {"code", "ERR_AI_LLM_NOEXISTS"}, + {"message", "The requested LLM does not exist"} + }} + }; + } + + static JSON::Object::Entries ERR_AI_LLM_MESSAGE ( + const String& source, + CoreAI::ID id, + const String& message + ) { + return JSON::Object::Entries { + {"source", source}, + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "InternalError"}, + {"code", "ERR_AI_LLM_MESSAGE"}, + {"message", message} + }} + }; + } + + static JSON::Object::Entries ERR_AI_LLM_EXISTS ( + const String& source, + CoreAI::ID id + ) { + return JSON::Object::Entries { + {"source", source}, + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"type", "InternalError"}, + {"code", "ERR_AI_LLM_EXISTS"}, + {"message", "The requested LLM already exists"} + }} + }; + } + + SharedPointer CoreAI::getLLM (ID id) { + if (!this->hasLLM(id)) return nullptr; + Lock lock(this->mutex); + return this->llms.at(id); + } + + bool CoreAI::hasLLM (ID id) { + Lock lock(this->mutex); + return this->llms.find(id) != this->llms.end(); + } + + void CoreAI::createLLM( + const String& seq, + ID id, + LLMOptions options, + const CoreModule::Callback& callback + ) { + if (this->hasLLM(id)) { + auto json = ERR_AI_LLM_EXISTS("ai.llm.create", id); + return callback(seq, json, Post{}); + } + + this->core->dispatchEventLoop([=, this] { + auto llm = new LLM(options); + if (llm->err.size()) { + auto json = ERR_AI_LLM_MESSAGE("ai.llm.create", id, llm->err); + return callback(seq, json, Post{}); + return; + } + + const auto json = JSON::Object::Entries { + {"source", "ai.llm.create"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + }} + }; + + callback(seq, json, Post{}); + Lock lock(this->mutex); + this->llms[id].reset(llm); + }); + }; + + void CoreAI::chatLLM( + const String& seq, + ID id, + String message, + const CoreModule::Callback& callback + ) { + this->core->dispatchEventLoop([=, this] { + if (!this->hasLLM(id)) { + auto json = ERR_AI_LLM_NOEXISTS("ai.llm.chat", id); + return callback(seq, json, Post{}); + } + + auto llm = this->getLLM(id); + + llm->chat(message, [=](auto self, auto token, auto isComplete) { + const auto json = JSON::Object::Entries { + {"source", "ai.llm.chat"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"token", token}, + {"complete", isComplete} + }} + }; + + callback("-1", json, Post{}); + + return isComplete; + }); + }); + }; + + void CoreAI::destroyLLM( + const String& seq, + ID id, + const CoreModule::Callback& callback + ) { + this->core->dispatchEventLoop([=, this] { + if (!this->hasLLM(id)) { + auto json = ERR_AI_LLM_NOEXISTS("ai.llm.destroy", id); + return callback(seq, json, Post{}); + } + + Lock lock(this->mutex); + auto llm = this->getLLM(id); + llm->stopped = true; + + while (llm->interactive) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + this->llms.erase(id); + }); + }; + + void CoreAI::stopLLM( + const String& seq, + ID id, + const CoreModule::Callback& callback + ) { + this->core->dispatchEventLoop([=, this] { + if (!this->hasLLM(id)) { + auto json = ERR_AI_LLM_NOEXISTS("ai.llm.stop", id); + return callback(seq, json, Post{}); + } + + auto llm = this->getLLM(id); + llm->stopped = true; // remains stopped until chat is called again. + }); + }; + + LLM::Logger LLM::log = nullptr; + + void LLM::tramp(ggml_log_level level, const char* message, void* user_data) { + if (LLM::log) LLM::log(level, message, user_data); + } + + void LLM::escape(String& input) { + std::size_t input_len = input.length(); + std::size_t output_idx = 0; + + for (std::size_t input_idx = 0; input_idx < input_len; ++input_idx) { + if (input[input_idx] == '\\' && input_idx + 1 < input_len) { + switch (input[++input_idx]) { + case 'n': input[output_idx++] = '\n'; break; + case 'r': input[output_idx++] = '\r'; break; + case 't': input[output_idx++] = '\t'; break; + case '\'': input[output_idx++] = '\''; break; + case '\"': input[output_idx++] = '\"'; break; + case '\\': input[output_idx++] = '\\'; break; + case 'x': + // Handle \x12, etc + if (input_idx + 2 < input_len) { + const char x[3] = { input[input_idx + 1], input[input_idx + 2], 0 }; + char *err_p = nullptr; + const long val = std::strtol(x, &err_p, 16); + if (err_p == x + 2) { + input_idx += 2; + input[output_idx++] = char(val); + break; + } + } + // fall through + default: { + input[output_idx++] = '\\'; + input[output_idx++] = input[input_idx]; break; + } + } + } else { + input[output_idx++] = input[input_idx]; + } + } + + input.resize(output_idx); + } + + LLM::LLM (const LLMOptions options) { + // + // set up logging + // + LLM::log = [&](ggml_log_level level, const char* message, void* user_data) { + // std::cout << message << std::endl; + }; + + llama_log_set(LLM::tramp, nullptr); + + // + // set params and init the model and context + // + llama_backend_init(); + llama_numa_init(this->params.numa); + + llama_sampling_params& sparams = this->params.sparams; + this->sampling = llama_sampling_init(sparams); + + if (!this->sampling) this->err = "failed to initialize sampling subsystem"; + if (this->params.seed == LLAMA_DEFAULT_SEED) this->params.seed = time(nullptr); + + this->params.chatml = true; + this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; + this->params.n_ctx = 2048; + + FileResource resource(options.path); + + if (!resource.exists()) { + this->err = "Unable to access the model file due to permissions"; + return; + } + + this->params.model = options.path; + + std::tie(this->model, this->ctx) = llama_init_from_gpt_params(this->params); + + this->embd_inp = ::llama_tokenize(this->ctx, this->params.prompt.c_str(), true, true); + + // + // create a guidance context + // + if (sparams.cfg_scale > 1.f) { + struct llama_context_params lparams = llama_context_params_from_gpt_params(this->params); + this->guidance = llama_new_context_with_model(this->model, lparams); + } + + if (this->model == nullptr) { + this->err = "unable to load model"; + return; + } + + // + // determine the capacity of the model + // + const int n_ctx_train = llama_n_ctx_train(this->model); + const int n_ctx = llama_n_ctx(this->ctx); + + if (n_ctx > n_ctx_train) { + LOG("warning: model was trained on only %d context tokens (%d specified)\n", n_ctx_train, n_ctx); + } + + this->n_ctx = n_ctx; + + if (this->guidance) { + this->guidance_inp = ::llama_tokenize(this->guidance, sparams.cfg_negative_prompt, true, true); + std::vector original_inp = ::llama_tokenize(ctx, params.prompt.c_str(), true, true); + original_prompt_len = original_inp.size(); + guidance_offset = (int)this->guidance_inp.size() - original_prompt_len; + } + + // + // number of tokens to keep when resetting context + // + const bool add_bos = llama_should_add_bos_token(this->model); + GGML_ASSERT(llama_add_eos_token(this->model) != 1); + + if (this->params.n_keep < 0 || this->params.n_keep > (int)this->embd_inp.size() || this->params.instruct || this->params.chatml) { + this->params.n_keep = (int)this->embd_inp.size(); + } else if (add_bos) { + this->params.n_keep += add_bos; + } + + if (this->params.instruct) { + this->params.interactive_first = true; + this->params.antiprompt.emplace_back("### Instruction:\n\n"); + } else if (this->params.chatml) { + this->params.interactive_first = true; + this->params.antiprompt.emplace_back("<|im_start|>user\n"); + } else if (this->params.conversation) { + this->params.interactive_first = true; + } + + if (params.interactive_first) { + params.interactive = true; + } + + if (params.interactive) { + if (!params.antiprompt.empty()) { + for (const auto & antiprompt : params.antiprompt) { + LOG("Reverse prompt: '%s'\n", antiprompt.c_str()); + + if (params.verbose_prompt) { + auto tmp = ::llama_tokenize(ctx, antiprompt.c_str(), false, true); + + for (int i = 0; i < (int) tmp.size(); i++) { + LOG("%6d -> '%s'\n", tmp[i], llama_token_to_piece(ctx, tmp[i]).c_str()); + } + } + } + } + + if (params.input_prefix_bos) { + LOG("Input prefix with BOS\n"); + } + + if (!params.input_prefix.empty()) { + LOG("Input prefix: '%s'\n", params.input_prefix.c_str()); + + if (params.verbose_prompt) { + auto tmp = ::llama_tokenize(ctx, params.input_prefix.c_str(), true, true); + + for (int i = 0; i < (int) tmp.size(); i++) { + LOG("%6d -> '%s'\n", tmp[i], llama_token_to_piece(ctx, tmp[i]).c_str()); + } + } + } + + if (!params.input_suffix.empty()) { + LOG("Input suffix: '%s'\n", params.input_suffix.c_str()); + + if (params.verbose_prompt) { + auto tmp = ::llama_tokenize(ctx, params.input_suffix.c_str(), false, true); + + for (int i = 0; i < (int) tmp.size(); i++) { + LOG("%6d -> '%s'\n", tmp[i], llama_token_to_piece(ctx, tmp[i]).c_str()); + } + } + } + } + + // + // initialize any anti-prompts sent in as params + // + this->antiprompt_ids.reserve(this->params.antiprompt.size()); + + for (const String& antiprompt : this->params.antiprompt) { + this->antiprompt_ids.emplace_back(::llama_tokenize(this->ctx, antiprompt.c_str(), false, true)); + } + + this->path_session = params.path_prompt_cache; + }; + + LLM::~LLM () { + llama_free(this->ctx); + llama_free(this->guidance); + llama_free_model(this->model); + llama_sampling_free(this->sampling); + llama_backend_free(); + }; + + void LLM::chat (String buffer, const Cb cb) { + this->stopped = false; + int ga_i = 0; + + const int ga_n = this->params.grp_attn_n; + const int ga_w = this->params.grp_attn_w; + + if (ga_n != 1) { + GGML_ASSERT(ga_n > 0 && "grp_attn_n must be positive"); + GGML_ASSERT(ga_w % ga_n == 0 && "grp_attn_w must be a multiple of grp_attn_n"); + } + + this->interactive = this->params.interactive_first = true; + + bool display = true; + bool is_antiprompt = false; + bool input_echo = true; + + int n_past = 0; + int n_remain = this->params.n_predict; + int n_consumed = 0; + int n_session_consumed = 0; + int n_past_guidance = 0; + + std::vector input_tokens; + this->input_tokens = &input_tokens; + + std::vector output_tokens; + this->output_tokens = &output_tokens; + + std::ostringstream output_ss; + this->output_ss = &output_ss; + + std::vector embd; + std::vector embd_guidance; + + const int n_ctx = this->n_ctx; + const auto inp_pfx = ::llama_tokenize(ctx, "\n\n### Instruction:\n\n", true, true); + const auto inp_sfx = ::llama_tokenize(ctx, "\n\n### Response:\n\n", false, true); + + LOG("inp_pfx: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, inp_pfx).c_str()); + LOG("inp_sfx: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, inp_sfx).c_str()); + + const auto cml_pfx = ::llama_tokenize(ctx, "\n<|im_start|>user\n", true, true); + const auto cml_sfx = ::llama_tokenize(ctx, "<|im_end|>\n<|im_start|>assistant\n", false, true); + + while ((n_remain != 0 && !is_antiprompt) || params.interactive) { + if (!embd.empty()) { + int max_embd_size = n_ctx - 4; + + if ((int) embd.size() > max_embd_size) { + const int skipped_tokens = (int)embd.size() - max_embd_size; + embd.resize(max_embd_size); + LOG("<>", skipped_tokens, skipped_tokens != 1 ? "s" : ""); + } + + if (ga_n == 1) { + if (n_past + (int) embd.size() + std::max(0, guidance_offset) >= n_ctx) { + if (params.n_predict == -2) { + LOG("\n\ncontext full and n_predict == -%d => stopping\n", this->params.n_predict); + break; + } + + const int n_left = n_past - this->params.n_keep; + const int n_discard = n_left/2; + + LOG("context full, swapping: n_past = %d, n_left = %d, n_ctx = %d, n_keep = %d, n_discard = %d\n", + n_past, n_left, n_ctx, params.n_keep, n_discard); + + llama_kv_cache_seq_rm (ctx, 0, this->params.n_keep, this->params.n_keep + n_discard); + llama_kv_cache_seq_add(ctx, 0, this->params.n_keep + n_discard, n_past, -n_discard); + + n_past -= n_discard; + + if (this->guidance) { + n_past_guidance -= n_discard; + } + + LOG("after swap: n_past = %d, n_past_guidance = %d\n", n_past, n_past_guidance); + LOG("embd: %s\n", LOG_TOKENS_TOSTR_PRETTY(this->ctx, embd).c_str()); + LOG("clear session path\n"); + this->path_session.clear(); + } + } else { + while (n_past >= ga_i + ga_w) { + const int ib = (ga_n*ga_i)/ga_w; + const int bd = (ga_w/ga_n)*(ga_n - 1); + const int dd = (ga_w/ga_n) - ib*bd - ga_w; + + LOG("\n"); + LOG("shift: [%6d, %6d] + %6d -> [%6d, %6d]\n", ga_i, n_past, ib*bd, ga_i + ib*bd, n_past + ib*bd); + LOG("div: [%6d, %6d] / %6d -> [%6d, %6d]\n", ga_i + ib*bd, ga_i + ib*bd + ga_w, ga_n, (ga_i + ib*bd)/ga_n, (ga_i + ib*bd + ga_w)/ga_n); + LOG("shift: [%6d, %6d] + %6d -> [%6d, %6d]\n", ga_i + ib*bd + ga_w, n_past + ib*bd, dd, ga_i + ib*bd + ga_w + dd, n_past + ib*bd + dd); + + llama_kv_cache_seq_add(ctx, 0, ga_i, n_past, ib*bd); + llama_kv_cache_seq_div(ctx, 0, ga_i + ib*bd, ga_i + ib*bd + ga_w, ga_n); + llama_kv_cache_seq_add(ctx, 0, ga_i + ib*bd + ga_w, n_past + ib*bd, dd); + + n_past -= bd; + + ga_i += ga_w/ga_n; + + LOG("\nn_past_old = %d, n_past = %d, ga_i = %d\n\n", n_past + bd, n_past, ga_i); + } + } + + if (n_session_consumed < (int) this->session_tokens.size()) { + size_t i = 0; + + for ( ; i < embd.size(); i++) { + if (embd[i] != this->session_tokens[n_session_consumed]) { + this->session_tokens.resize(n_session_consumed); + break; + } + + n_past++; + n_session_consumed++; + + if (n_session_consumed >= (int) this->session_tokens.size()) { + ++i; + break; + } + } + + if (i > 0) { + embd.erase(embd.begin(), embd.begin() + i); + } + } + + if (this->guidance) { + int input_size = 0; + llama_token * input_buf = nullptr; + + if (n_past_guidance < (int)this->guidance_inp.size()) { + embd_guidance = this->guidance_inp; + + if (embd.begin() + original_prompt_len < embd.end()) { + embd_guidance.insert(embd_guidance.end(), embd.begin() + original_prompt_len, embd.end()); + } + + input_buf = embd_guidance.data(); + input_size = embd_guidance.size(); + + LOG("guidance context: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd_guidance).c_str()); + } else { + input_buf = embd.data(); + input_size = embd.size(); + } + + for (int i = 0; i < input_size; i += params.n_batch) { + int n_eval = std::min(input_size - i, params.n_batch); + + if (llama_decode(this->guidance, llama_batch_get_one(input_buf + i, n_eval, n_past_guidance, 0))) { + LOG("failed to eval\n"); + return; + } + + n_past_guidance += n_eval; + } + } + + for (int i = 0; i < (int) embd.size(); i += params.n_batch) { + int n_eval = (int) embd.size() - i; + + if (n_eval > params.n_batch) { + n_eval = params.n_batch; + } + + LOG("eval: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd).c_str()); + + if (llama_decode(ctx, llama_batch_get_one(&embd[i], n_eval, n_past, 0))) { + LOG("%s : failed to eval\n", __func__); + return; + } + + n_past += n_eval; + } + + if (!embd.empty() && !this->path_session.empty()) { + this->session_tokens.insert(this->session_tokens.end(), embd.begin(), embd.end()); + n_session_consumed = this->session_tokens.size(); + } + } + + embd.clear(); + embd_guidance.clear(); + + if ((int)this->embd_inp.size() <= n_consumed && !interactive) { + const llama_token id = llama_sampling_sample(this->sampling, this->ctx, this->guidance); + llama_sampling_accept(this->sampling, this->ctx, id, true); + + LOG("last: %s\n", LOG_TOKENS_TOSTR_PRETTY(this->ctx, this->sampling->prev).c_str()); + embd.push_back(id); + + input_echo = true; + --n_remain; + + LOG("n_remain: %d\n", n_remain); + } else { + LOG("embd_inp.size(): %d, n_consumed: %d\n", (int)this->embd_inp.size(), n_consumed); + + while ((int)this->embd_inp.size() > n_consumed) { + embd.push_back(this->embd_inp[n_consumed]); + llama_sampling_accept(this->sampling, this->ctx, this->embd_inp[n_consumed], false); + + ++n_consumed; + if ((int) embd.size() >= params.n_batch) { + break; + } + } + } + + if (input_echo && display) { + for (auto id : embd) { + const String token_str = llama_token_to_piece(ctx, id, !params.conversation); + if (this->stopped) { + this->interactive = false; + return; + } + + cb(this, token_str, false); + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + if (embd.size() > 1) { + input_tokens.push_back(id); + } else { + output_tokens.push_back(id); + output_ss << token_str; + } + } + } + + if (input_echo && (int)this->embd_inp.size() == n_consumed) { + display = true; + } + + if ((int)this->embd_inp.size() <= n_consumed) { + if (!params.antiprompt.empty()) { + const int n_prev = 32; + const String last_output = llama_sampling_prev_str(this->sampling, this->ctx, n_prev); + + is_antiprompt = false; + + for (String & antiprompt : this->params.antiprompt) { + size_t extra_padding = this->params.interactive ? 0 : 2; + size_t search_start_pos = last_output.length() > static_cast(antiprompt.length() + extra_padding) + ? last_output.length() - static_cast(antiprompt.length() + extra_padding) + : 0; + + if (last_output.find(antiprompt, search_start_pos) != String::npos) { + if (this->params.interactive) { + this->interactive = true; + } + + is_antiprompt = true; + break; + } + } + + llama_token last_token = llama_sampling_last(this->sampling); + for (std::vector ids : antiprompt_ids) { + if (ids.size() == 1 && last_token == ids[0]) { + if (this->params.interactive) { + this->interactive = true; + } + + is_antiprompt = true; + break; + } + } + + if (is_antiprompt) { + LOG("found antiprompt: %s\n", last_output.c_str()); + } + } + + if (llama_token_is_eog(model, llama_sampling_last(this->sampling))) { + LOG("found an EOG token\n"); + + if (this->params.interactive) { + if (!this->params.antiprompt.empty()) { + const auto first_antiprompt = ::llama_tokenize(this->ctx, this->params.antiprompt.front().c_str(), false, true); + this->embd_inp.insert(this->embd_inp.end(), first_antiprompt.begin(), first_antiprompt.end()); + is_antiprompt = true; + } + + this->interactive = true; + } else if (this->params.instruct || this->params.chatml) { + this->interactive = true; + } + } + + if (n_past > 0 && this->interactive) { + LOG("waiting for user input\n"); + + if (this->params.input_prefix_bos) { + LOG("adding input prefix BOS token\n"); + this->embd_inp.push_back(llama_token_bos(this->model)); + } + + if (!params.input_prefix.empty() && !params.conversation) { + LOG("appending input prefix: '%s'\n", this->params.input_prefix.c_str()); + } + + if (buffer.length() > 1) { + if (!this->params.input_suffix.empty() && !this->params.conversation) { + LOG("appending input suffix: '%s'\n", this->params.input_suffix.c_str()); + } + + LOG("buffer: '%s'\n", buffer.c_str()); + + const size_t original_size = this->embd_inp.size(); + + if (this->params.instruct && !is_antiprompt) { + LOG("inserting instruction prefix\n"); + n_consumed = this->embd_inp.size(); + embd_inp.insert(this->embd_inp.end(), inp_pfx.begin(), inp_pfx.end()); + } + + if (params.chatml && !is_antiprompt) { + LOG("inserting chatml prefix\n"); + n_consumed = this->embd_inp.size(); + embd_inp.insert(this->embd_inp.end(), cml_pfx.begin(), cml_pfx.end()); + } + + if (params.escape) { + this->escape(buffer); + } + + const auto line_pfx = ::llama_tokenize(this->ctx, this->params.input_prefix.c_str(), false, true); + const auto line_inp = ::llama_tokenize(this->ctx, buffer.c_str(), false, params.interactive_specials); + const auto line_sfx = ::llama_tokenize(this->ctx, this->params.input_suffix.c_str(), false, true); + + LOG("input tokens: %s\n", LOG_TOKENS_TOSTR_PRETTY(this->ctx, line_inp).c_str()); + + this->embd_inp.insert(this->embd_inp.end(), line_pfx.begin(), line_pfx.end()); + this->embd_inp.insert(this->embd_inp.end(), line_inp.begin(), line_inp.end()); + this->embd_inp.insert(this->embd_inp.end(), line_sfx.begin(), line_sfx.end()); + + if (this->params.instruct) { + LOG("inserting instruction suffix\n"); + this->embd_inp.insert(this->embd_inp.end(), inp_sfx.begin(), inp_sfx.end()); + } + + if (this->params.chatml) { + LOG("inserting chatml suffix\n"); + this->embd_inp.insert(this->embd_inp.end(), cml_sfx.begin(), cml_sfx.end()); + } + + for (size_t i = original_size; i < this->embd_inp.size(); ++i) { + const llama_token token = this->embd_inp[i]; + this->output_tokens->push_back(token); + output_ss << llama_token_to_piece(this->ctx, token); + } + + n_remain -= line_inp.size(); + LOG("n_remain: %d\n", n_remain); + } else { + LOG("empty line, passing control back\n"); + } + + input_echo = false; + } + + if (n_past > 0) { + if (this->interactive) { + llama_sampling_reset(this->sampling); + } + this->interactive = false; + } + } + + if (llama_token_is_eog(this->model, embd.back())) { + if (this->stopped) { + this->interactive = false; + return; + } + + if (cb(this, "", true)) return; + } + + if (this->params.interactive && n_remain <= 0 && this->params.n_predict >= 0) { + n_remain = this->params.n_predict; + this->interactive = true; + } + } + } +} diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh new file mode 100644 index 0000000000..a204bf89cb --- /dev/null +++ b/src/core/modules/ai.hh @@ -0,0 +1,114 @@ +#ifndef SOCKET_RUNTIME_CORE_AI_H +#define SOCKET_RUNTIME_CORE_AI_H + +#include "../module.hh" + +#include "llama/common/common.h" +#include "llama/llama.h" + +// #include +// #include +// #include +// #include + +#if defined (_WIN32) + #define WIN32_LEAN_AND_MEAN + + #ifndef NOMINMAX + #define NOMINMAX + #endif +#endif + +namespace SSC { + class LLM; + class Core; + + struct LLMOptions { + int attentionCapacity; + int seed; + String path; + String prompt; + }; + + class CoreAI : public CoreModule { + public: + using ID = uint64_t; + using LLMs = std::map>; + + Mutex mutex; + LLMs llms; + + void chatLLM ( + const String& seq, + ID id, + String message, + const CoreModule::Callback& callback + ); + + void createLLM ( + const String& seq, + ID id, + LLMOptions options, + const CoreModule::Callback& callback + ); + + void destroyLLM ( + const String& seq, + ID id, + const CoreModule::Callback& callback + ); + + void stopLLM ( + const String& seq, + ID id, + const CoreModule::Callback& callback + ); + + bool hasLLM (ID id); + SharedPointer getLLM (ID id); + + CoreAI (Core* core) + : CoreModule(core) + {} + }; + + class LLM { + using Cb = std::function; + using Logger = std::function; + + gpt_params params; + llama_model* model; + llama_context* ctx; + llama_context* guidance = nullptr; + struct llama_sampling_context* sampling; + + std::vector* input_tokens; + std::ostringstream* output_ss; + std::vector* output_tokens; + std::vector session_tokens; + std::vector embd_inp; + std::vector guidance_inp; + std::vector> antiprompt_ids; + + String path_session = ""; + int guidance_offset = 0; + int original_prompt_len = 0; + int n_ctx = 0; + + public: + String err = ""; + bool stopped = false; + bool interactive = false; + + void chat (String input, const Cb cb); + void escape (String& input); + + LLM(const LLMOptions options); + ~LLM(); + + static void tramp(ggml_log_level level, const char* message, void* user_data); + static Logger log; + }; +} + +#endif diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 0e74c69f1a..c5ee4cc958 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -50,6 +50,50 @@ static void mapIPCRoutes (Router *router) { ); #endif + /** + * AI + */ + router->map("ai.llm.create", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "path", "prompt"}); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + SSC::LLMOptions options; + options.path = message.get("path"); + options.prompt = message.get("prompt"); + + uint64_t modelId = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(modelId, "id", std::stoull); + + router->bridge->core->ai.createLLM(message.seq, modelId, options, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("ai.llm.destroy", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + uint64_t modelId = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(modelId, "id", std::stoull); + router->bridge->core->ai.destroyLLM(message.seq, modelId, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("ai.llm.stop", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id"}); + uint64_t modelId = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(modelId, "id", std::stoull); + router->bridge->core->ai.stopLLM(message.seq, modelId, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + + router->map("ai.llm.chat", [](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, {"id", "message"}); + + uint64_t modelId = 0; + REQUIRE_AND_GET_MESSAGE_VALUE(modelId, "id", std::stoull); + + auto value = message.get("message"); + router->bridge->core->ai.chatLLM(message.seq, modelId, value, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); + }); + /** * Attemps to exit the application * @param value The exit code From 3397450d048b307d42fdd1fcef4dbaa87f7770af Mon Sep 17 00:00:00 2001 From: heapwolf Date: Thu, 23 May 2024 19:51:00 +0200 Subject: [PATCH 0744/1178] fix(ai): improve jsdoc --- api/ai.js | 14 +- api/index.d.ts | 10208 ++++++++++++++++++++++++----------------------- 2 files changed, 5134 insertions(+), 5088 deletions(-) diff --git a/api/ai.js b/api/ai.js index d064141573..791371d83b 100644 --- a/api/ai.js +++ b/api/ai.js @@ -10,6 +10,7 @@ * ``` */ import ipc from './ipc.js' +import process from './process.js' import gc from './gc.js' import { EventEmitter } from './events.js' import { rand64 } from './crypto.js' @@ -22,9 +23,9 @@ import * as exports from './ai.js' export class LLM extends EventEmitter { /** * Constructs an LLM instance. - * @param {Object} options - The options for initializing the LLM. - * @param {string} options.path - The path to a valid model (.gguf). - * @param {string} options.prompt - The query that guides the model to generate a relevant and coherent responses. + * @param {Object} [options] - The options for initializing the LLM. + * @param {string} [options.path] - The path to a valid model (.gguf). + * @param {string} [options.prompt] - The query that guides the model to generate a relevant and coherent responses. * @param {string} [options.id] - The optional ID for the LLM instance. * @throws {Error} If the model path is not provided. */ @@ -45,7 +46,9 @@ export class LLM extends EventEmitter { path: this.path } - globalThis.addEventListener('data', ({ detail }) => { + globalThis.addEventListener('data', event => { + // @ts-ignore + const detail = event.detail const { err, data, source } = detail.params if (err && BigInt(err.id) === this.id) { @@ -79,9 +82,6 @@ export class LLM extends EventEmitter { } /** - * Implements `gc.finalizer` for gc'd resource cleanup. - * @param {Object} options - The options for finalizer. - * @returns {gc.Finalizer} The finalizer object. * @ignore */ [gc.finalizer] (options) { diff --git a/api/index.d.ts b/api/index.d.ts index 97b3db2d09..297b3949a0 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -1,6056 +1,6096 @@ -declare module "socket:buffer" { - export default Buffer; - export const File: { - new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; - prototype: File; - }; - export const Blob: { - new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; - prototype: Blob; - }; - export namespace constants { - export { kMaxLength as MAX_LENGTH }; - export { kMaxLength as MAX_STRING_LENGTH }; - } - export const btoa: any; - export const atob: any; +declare module "socket:os/constants" { + export type errno = number; /** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. + * @typedef {number} errno + * @typedef {number} signal */ /** - * @name Buffer - * @extends {Uint8Array} + * A container for all known "errno" constant values. + * Unsupported values have a default value of `0`. */ - export function Buffer(arg: any, encodingOrOffset: any, length: any): any; - export class Buffer { - /** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. - */ - /** - * @name Buffer - * @extends {Uint8Array} - */ - constructor(arg: any, encodingOrOffset: any, length: any); - get parent(): any; - get offset(): any; - _isBuffer: boolean; - swap16(): this; - swap32(): this; - swap64(): this; - toString(...args: any[]): any; - toLocaleString: any; - equals(b: any): boolean; - inspect(): string; - compare(target: any, start: any, end: any, thisStart: any, thisEnd: any): 0 | 1 | -1; - includes(val: any, byteOffset: any, encoding: any): boolean; - indexOf(val: any, byteOffset: any, encoding: any): any; - lastIndexOf(val: any, byteOffset: any, encoding: any): any; - write(string: any, offset: any, length: any, encoding: any): number; - toJSON(): { - type: string; - data: any; - }; - slice(start: any, end: any): any; - readUintLE: (offset: any, byteLength: any, noAssert: any) => any; - readUIntLE(offset: any, byteLength: any, noAssert: any): any; - readUintBE: (offset: any, byteLength: any, noAssert: any) => any; - readUIntBE(offset: any, byteLength: any, noAssert: any): any; - readUint8: (offset: any, noAssert: any) => any; - readUInt8(offset: any, noAssert: any): any; - readUint16LE: (offset: any, noAssert: any) => number; - readUInt16LE(offset: any, noAssert: any): number; - readUint16BE: (offset: any, noAssert: any) => number; - readUInt16BE(offset: any, noAssert: any): number; - readUint32LE: (offset: any, noAssert: any) => number; - readUInt32LE(offset: any, noAssert: any): number; - readUint32BE: (offset: any, noAssert: any) => number; - readUInt32BE(offset: any, noAssert: any): number; - readBigUInt64LE: any; - readBigUInt64BE: any; - readIntLE(offset: any, byteLength: any, noAssert: any): any; - readIntBE(offset: any, byteLength: any, noAssert: any): any; - readInt8(offset: any, noAssert: any): any; - readInt16LE(offset: any, noAssert: any): number; - readInt16BE(offset: any, noAssert: any): number; - readInt32LE(offset: any, noAssert: any): number; - readInt32BE(offset: any, noAssert: any): number; - readBigInt64LE: any; - readBigInt64BE: any; - readFloatLE(offset: any, noAssert: any): number; - readFloatBE(offset: any, noAssert: any): number; - readDoubleLE(offset: any, noAssert: any): number; - readDoubleBE(offset: any, noAssert: any): number; - writeUintLE: (value: any, offset: any, byteLength: any, noAssert: any) => any; - writeUIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeUintBE: (value: any, offset: any, byteLength: any, noAssert: any) => any; - writeUIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeUint8: (value: any, offset: any, noAssert: any) => any; - writeUInt8(value: any, offset: any, noAssert: any): any; - writeUint16LE: (value: any, offset: any, noAssert: any) => any; - writeUInt16LE(value: any, offset: any, noAssert: any): any; - writeUint16BE: (value: any, offset: any, noAssert: any) => any; - writeUInt16BE(value: any, offset: any, noAssert: any): any; - writeUint32LE: (value: any, offset: any, noAssert: any) => any; - writeUInt32LE(value: any, offset: any, noAssert: any): any; - writeUint32BE: (value: any, offset: any, noAssert: any) => any; - writeUInt32BE(value: any, offset: any, noAssert: any): any; - writeBigUInt64LE: any; - writeBigUInt64BE: any; - writeIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; - writeInt8(value: any, offset: any, noAssert: any): any; - writeInt16LE(value: any, offset: any, noAssert: any): any; - writeInt16BE(value: any, offset: any, noAssert: any): any; - writeInt32LE(value: any, offset: any, noAssert: any): any; - writeInt32BE(value: any, offset: any, noAssert: any): any; - writeBigInt64LE: any; - writeBigInt64BE: any; - writeFloatLE(value: any, offset: any, noAssert: any): any; - writeFloatBE(value: any, offset: any, noAssert: any): any; - writeDoubleLE(value: any, offset: any, noAssert: any): any; - writeDoubleBE(value: any, offset: any, noAssert: any): any; - copy(target: any, targetStart: any, start: any, end: any): number; - fill(val: any, start: any, end: any, encoding: any): this; - } - export namespace Buffer { - export let TYPED_ARRAY_SUPPORT: boolean; - export let poolSize: number; - /** - * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError - * if value is a number. - * Buffer.from(str[, encoding]) - * Buffer.from(array) - * Buffer.from(buffer) - * Buffer.from(arrayBuffer[, byteOffset[, length]]) - **/ - export function from(value: any, encodingOrOffset?: any, length?: any): any; - /** - * Creates a new filled Buffer instance. - * alloc(size[, fill[, encoding]]) - **/ - export function alloc(size: any, fill: any, encoding: any): Uint8Array; - /** - * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. - * */ - export function allocUnsafe(size: any): Uint8Array; - /** - * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. - */ - export function allocUnsafeSlow(size: any): Uint8Array; - export function isBuffer(b: any): boolean; - export function compare(a: any, b: any): 0 | 1 | -1; - export function isEncoding(encoding: any): boolean; - export function concat(list: any, length: any): Uint8Array; - export { byteLength }; + export const errno: any; + export type signal = number; + /** + * A container for all known "signal" constant values. + * Unsupported values have a default value of `0`. + */ + export const signal: any; + namespace _default { + export { errno }; + export { signal }; } - export const kMaxLength: 2147483647; - export function SlowBuffer(length: any): Uint8Array; - export const INSPECT_MAX_BYTES: 50; - function byteLength(string: any, encoding: any, ...args: any[]): any; + export default _default; } -declare module "socket:async/context" { +declare module "socket:errno" { /** - * @module Async.AsyncContext - * - * Async Context for JavaScript based on the TC39 proposal. - * - * Example usage: - * ```js - * // `AsyncContext` is also globally available as `globalThis.AsyncContext` - * import AsyncContext from 'socket:async/context' - * - * const var = new AsyncContext.Variable() - * var.run('top', () => { - * console.log(var.get()) // 'top' - * queueMicrotask(() => { - * var.run('nested', () => { - * console.log(var.get()) // 'nested' - * }) - * }) - * }) - * ``` - * - * @see {@link https://tc39.es/proposal-async-context} - * @see {@link https://github.com/tc39/proposal-async-context} + * Converts an `errno` code to its corresponding string message. + * @param {import('./os/constants.js').errno} {code} + * @return {string} */ + export function toString(code: any): string; /** - * @template T - * @typedef {{ - * name?: string, - * defaultValue?: T - * }} VariableOptions + * Gets the code for a given 'errno' name. + * @param {string|number} name + * @return {errno} */ + export function getCode(name: string | number): errno; /** - * @callback AnyFunc - * @template T - * @this T - * @param {...any} args - * @returns {any} + * Gets the name for a given 'errno' code + * @return {string} + * @param {string|number} code */ + export function getName(code: string | number): string; /** - * `FrozenRevert` holds a frozen Mapping that will be simply restored - * when the revert is run. - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} + * Gets the message for a 'errno' code. + * @param {number|string} code + * @return {string} */ - export class FrozenRevert { - /** - * `FrozenRevert` class constructor. - * @param {Mapping} mapping - */ - constructor(mapping: Mapping); - /** - * Restores (unchaged) mapping from this `FrozenRevert`. This function is - * called by `AsyncContext.Storage` when it reverts a current mapping to the - * previous state before a "fork". - * @param {Mapping=} [unused] - * @return {Mapping} - */ - restore(unused?: Mapping | undefined): Mapping; - #private; - } + export function getMessage(code: number | string): string; /** - * Revert holds the state on how to revert a change to the - * `AsyncContext.Storage` current `Mapping` - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} - * @template T + * @typedef {import('./os/constants.js').errno} errno */ - export class Revert { - /** - * `Revert` class constructor. - * @param {Mapping} mapping - * @param {Variable} key - */ - constructor(mapping: Mapping, key: Variable); - /** - * @type {T|undefined} - */ - get previousVariable(): T; - /** - * Restores a mapping from this `Revert`. This function is called by - * `AsyncContext.Storage` when it reverts a current mapping to the - * previous state before a "fork". - * @param {Mapping} current - * @return {Mapping} - */ - restore(current: Mapping): Mapping; - #private; + export const E2BIG: any; + export const EACCES: any; + export const EADDRINUSE: any; + export const EADDRNOTAVAIL: any; + export const EAFNOSUPPORT: any; + export const EAGAIN: any; + export const EALREADY: any; + export const EBADF: any; + export const EBADMSG: any; + export const EBUSY: any; + export const ECANCELED: any; + export const ECHILD: any; + export const ECONNABORTED: any; + export const ECONNREFUSED: any; + export const ECONNRESET: any; + export const EDEADLK: any; + export const EDESTADDRREQ: any; + export const EDOM: any; + export const EDQUOT: any; + export const EEXIST: any; + export const EFAULT: any; + export const EFBIG: any; + export const EHOSTUNREACH: any; + export const EIDRM: any; + export const EILSEQ: any; + export const EINPROGRESS: any; + export const EINTR: any; + export const EINVAL: any; + export const EIO: any; + export const EISCONN: any; + export const EISDIR: any; + export const ELOOP: any; + export const EMFILE: any; + export const EMLINK: any; + export const EMSGSIZE: any; + export const EMULTIHOP: any; + export const ENAMETOOLONG: any; + export const ENETDOWN: any; + export const ENETRESET: any; + export const ENETUNREACH: any; + export const ENFILE: any; + export const ENOBUFS: any; + export const ENODATA: any; + export const ENODEV: any; + export const ENOENT: any; + export const ENOEXEC: any; + export const ENOLCK: any; + export const ENOLINK: any; + export const ENOMEM: any; + export const ENOMSG: any; + export const ENOPROTOOPT: any; + export const ENOSPC: any; + export const ENOSR: any; + export const ENOSTR: any; + export const ENOSYS: any; + export const ENOTCONN: any; + export const ENOTDIR: any; + export const ENOTEMPTY: any; + export const ENOTSOCK: any; + export const ENOTSUP: any; + export const ENOTTY: any; + export const ENXIO: any; + export const EOPNOTSUPP: any; + export const EOVERFLOW: any; + export const EPERM: any; + export const EPIPE: any; + export const EPROTO: any; + export const EPROTONOSUPPORT: any; + export const EPROTOTYPE: any; + export const ERANGE: any; + export const EROFS: any; + export const ESPIPE: any; + export const ESRCH: any; + export const ESTALE: any; + export const ETIME: any; + export const ETIMEDOUT: any; + export const ETXTBSY: any; + export const EWOULDBLOCK: any; + export const EXDEV: any; + export const strings: any; + export { constants }; + namespace _default { + export { constants }; + export { strings }; + export { toString }; + export { getCode }; + export { getMessage }; } + export default _default; + export type errno = import("socket:os/constants").errno; + import { errno as constants } from "socket:os/constants"; +} + +declare module "socket:errors" { + export default exports; + export const ABORT_ERR: any; + export const ENCODING_ERR: any; + export const INVALID_ACCESS_ERR: any; + export const INDEX_SIZE_ERR: any; + export const NETWORK_ERR: any; + export const NOT_ALLOWED_ERR: any; + export const NOT_FOUND_ERR: any; + export const NOT_SUPPORTED_ERR: any; + export const OPERATION_ERR: any; + export const SECURITY_ERR: any; + export const TIMEOUT_ERR: any; /** - * A container for all `AsyncContext.Variable` instances and snapshot state. - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/mapping.ts} + * An `AbortError` is an error type thrown in an `onabort()` level 0 + * event handler on an `AbortSignal` instance. */ - export class Mapping { - /** - * `Mapping` class constructor. - * @param {Map, any>} data - */ - constructor(data: Map, any>); + export class AbortError extends Error { /** - * Freezes the `Mapping` preventing `AsyncContext.Variable` modifications with - * `set()` and `delete()`. + * The code given to an `ABORT_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - freeze(): void; + static get code(): any; /** - * Returns `true` if the `Mapping` is frozen, otherwise `false`. - * @return {boolean} + * `AbortError` class constructor. + * @param {AbortSignal|string} reasonOrSignal + * @param {AbortSignal=} [signal] */ - isFrozen(): boolean; + constructor(reason: any, signal?: AbortSignal | undefined, ...args: any[]); + signal: AbortSignal; + get name(): string; + get code(): string; + } + /** + * An `BadRequestError` is an error type thrown in an `onabort()` level 0 + * event handler on an `BadRequestSignal` instance. + */ + export class BadRequestError extends Error { /** - * Optionally returns a new `Mapping` if the current one is "frozen", - * otherwise it just returns the current instance. - * @return {Mapping} + * The default code given to a `BadRequestError` */ - fork(): Mapping; + static get code(): number; /** - * Returns `true` if the `Mapping` has a `AsyncContext.Variable` at `key`, - * otherwise `false. - * @template T - * @param {Variable} key - * @return {boolean} + * `BadRequestError` class constructor. + * @param {string} message + * @param {number} [code] */ - has(key: Variable): boolean; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `EncodingError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class EncodingError extends Error { /** - * Gets an `AsyncContext.Variable` value at `key`. If not set, this function - * returns `undefined`. - * @template T - * @param {Variable} key - * @return {boolean} + * The code given to an `ENCODING_ERR` `DOMException`. */ - get(key: Variable): boolean; + static get code(): any; /** - * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, - * then a "forked" (new) instance with the value set on it is returned, - * otherwise the current instance. - * @template T - * @param {Variable} key - * @param {T} value - * @return {Mapping} + * `EncodingError` class constructor. + * @param {string} message + * @param {number} [code] */ - set(key: Variable, value: T_2): Mapping; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An error type derived from an `errno` code. + */ + export class ErrnoError extends Error { + static get code(): string; + static errno: any; /** - * Delete an `AsyncContext.Variable` value at `key`. - * If the `Mapping` is frozen, then a "forked" (new) instance is returned, - * otherwise the current instance. - * @template T - * @param {Variable} key - * @param {T} value - * @return {Mapping} + * `ErrnoError` class constructor. + * @param {import('./errno').errno|string} code */ - delete(key: Variable): Mapping; + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + get name(): string; + get code(): number; #private; } /** - * A container of all `AsyncContext.Variable` data. - * @ignore - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/storage.ts} + * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class Storage { + export class FinalizationRegistryCallbackError extends Error { /** - * The current `Mapping` for this `AsyncContext`. - * @type {Mapping} + * The default code given to an `FinalizationRegistryCallbackError` */ - static "__#4@#current": Mapping; + static get code(): number; /** - * Returns `true` if the current `Mapping` has a - * `AsyncContext.Variable` at `key`, - * otherwise `false. - * @template T - * @param {Variable} key - * @return {boolean} + * `FinalizationRegistryCallbackError` class constructor. + * @param {string} message + * @param {number} [code] */ - static has(key: Variable): boolean; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `IllegalConstructorError` is an error type thrown when a constructor is + * called for a class constructor when it shouldn't be. + */ + export class IllegalConstructorError extends TypeError { /** - * Gets an `AsyncContext.Variable` value at `key` for the current `Mapping`. - * If not set, this function returns `undefined`. - * @template T - * @param {Variable} key - * @return {T|undefined} + * The default code given to an `IllegalConstructorError` */ - static get(key: Variable): T_1; + static get code(): number; /** - * Set updates the `AsyncContext.Variable` with a new value and returns a - * revert action that allows the modification to be reversed in the future. - * @template T - * @param {Variable} key - * @param {T} value - * @return {Revert|FrozenRevert} + * `IllegalConstructorError` class constructor. + * @param {string} message + * @param {number} [code] */ - static set(key: Variable, value: T_2): FrozenRevert | Revert; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `IndexSizeError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class IndexSizeError extends Error { /** - * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` - * or `Revert` which can restore the storage state to the state at - * the time of the snapshot. - * @return {FrozenRevert} + * The code given to an `INDEX_SIZE_ERR` `DOMException` */ - static snapshot(): FrozenRevert; + static get code(): any; /** - * Restores the storage `Mapping` state to state at the time the - * "revert" (`FrozenRevert` or `Revert`) was created. - * @template T - * @param {Revert|FrozenRevert} revert + * `IndexSizeError` class constructor. + * @param {string} message + * @param {number} [code] */ - static restore(revert: FrozenRevert | Revert): void; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + export const kInternalErrorCode: unique symbol; + /** + * An `InternalError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class InternalError extends Error { /** - * Switches storage `Mapping` state to the state at the time of a - * "snapshot". - * @param {FrozenRevert} snapshot - * @return {FrozenRevert} + * The default code given to an `InternalError` */ - static switch(snapshot: FrozenRevert): FrozenRevert; + static get code(): number; + /** + * `InternalError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, code?: number, ...args: any[]); + get name(): string; + /** + * @param {number|string} + */ + set code(code: string | number); + /** + * @type {number|string} + */ + get code(): string | number; + [exports.kInternalErrorCode]: number; } /** - * `AsyncContext.Variable` is a container for a value that is associated with - * the current execution flow. The value is propagated through async execution - * flows, and can be snapshot and restored with Snapshot. - * @template T - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} + * An `InvalidAccessError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class Variable { + export class InvalidAccessError extends Error { /** - * `Variable` class constructor. - * @param {VariableOptions=} [options] + * The code given to an `INVALID_ACCESS_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - constructor(options?: VariableOptions | undefined); - set defaultValue(defaultValue: T); + static get code(): any; /** - * @ignore + * `InvalidAccessError` class constructor. + * @param {string} message + * @param {number} [code] */ - get defaultValue(): T; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `NetworkError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class NetworkError extends Error { /** - * @ignore + * The code given to an `NETWORK_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - get revert(): FrozenRevert | Revert; + static get code(): any; /** - * The name of this async context variable. - * @type {string} + * `NetworkError` class constructor. + * @param {string} message + * @param {number} [code] */ + constructor(message: string, ...args: any[]); get name(): string; + get code(): string; + } + /** + * An `NotAllowedError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class NotAllowedError extends Error { /** - * Executes a function `fn` with specified arguments, - * setting a new value to the current context before the call, - * and ensuring the environment is reverted back afterwards. - * The function allows for the modification of a specific context's - * state in a controlled manner, ensuring that any changes can be undone. - * @template T, F extends AnyFunc - * @param {T} value - * @param {F} fn - * @param {...Parameters} args - * @returns {ReturnType} + * The code given to an `NOT_ALLOWED_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - run(value: T_1, fn: F, ...args: Parameters[]): ReturnType; + static get code(): any; /** - * Get the `AsyncContext.Variable` value. - * @template T - * @return {T|undefined} + * `NotAllowedError` class constructor. + * @param {string} message + * @param {number} [code] */ - get(): T_2; - #private; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } /** - * `AsyncContext.Snapshot` allows you to opaquely capture the current values of - * all `AsyncContext.Variable` instances and execute a function at a later time - * as if those values were still the current values (a snapshot and restore). - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} + * An `NotFoundError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class Snapshot { + export class NotFoundError extends Error { /** - * Wraps a given function `fn` with additional logic to take a snapshot of - * `Storage` before invoking `fn`. Returns a new function with the same - * signature as `fn` that when called, will invoke `fn` with the current - * `this` context and provided arguments, after restoring the `Storage` - * snapshot. - * - * `AsyncContext.Snapshot.wrap` is a helper which captures the current values - * of all Variables and returns a wrapped function. When invoked, this - * wrapped function restores the state of all Variables and executes the - * inner function. - * - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshotwrap} - * - * @template F - * @param {F} fn - * @returns {F} + * The code given to an `NOT_FOUND_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - static wrap(fn: F_1): F_1; + static get code(): any; /** - * Runs the given function `fn` with arguments `args`, using a `null` - * context and the current snapshot. - * - * @template F extends AnyFunc - * @param {F} fn - * @param {...Parameters} args - * @returns {ReturnType} + * `NotFoundError` class constructor. + * @param {string} message + * @param {number} [code] */ - run(fn: F, ...args: Parameters[]): ReturnType; - #private; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } /** - * `AsyncContext` container. + * An `NotSupportedError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export class AsyncContext { + export class NotSupportedError extends Error { /** - * `AsyncContext.Variable` is a container for a value that is associated with - * the current execution flow. The value is propagated through async execution - * flows, and can be snapshot and restored with Snapshot. - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} - * @type {typeof Variable} + * The code given to an `NOT_SUPPORTED_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - static Variable: typeof Variable; + static get code(): any; /** - * `AsyncContext.Snapshot` allows you to opaquely capture the current values of - * all `AsyncContext.Variable` instances and execute a function at a later time - * as if those values were still the current values (a snapshot and restore). - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} - * @type {typeof Snapshot} + * `NotSupportedError` class constructor. + * @param {string} message + * @param {number} [code] */ - static Snapshot: typeof Snapshot; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } - export default AsyncContext; - export type VariableOptions = { - name?: string; - defaultValue?: T; - }; - export type AnyFunc = () => any; -} - -declare module "socket:events" { - export const Event: { - new (type: string, eventInitDict?: EventInit): Event; - prototype: Event; - readonly NONE: 0; - readonly CAPTURING_PHASE: 1; - readonly AT_TARGET: 2; - readonly BUBBLING_PHASE: 3; - } | { - new (): {}; - }; - export const EventTarget: { - new (): {}; - }; - export const CustomEvent: { - new (type: string, eventInitDict?: CustomEventInit): CustomEvent; - prototype: CustomEvent; - } | { - new (type: any, options: any): { - "__#7@#detail": any; - readonly detail: any; - }; - }; - export const MessageEvent: { - new (type: string, eventInitDict?: MessageEventInit): MessageEvent; - prototype: MessageEvent; - } | { - new (type: any, options: any): { - "__#8@#detail": any; - "__#8@#data": any; - readonly detail: any; - readonly data: any; - }; - }; - export const ErrorEvent: { - new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; - prototype: ErrorEvent; - } | { - new (type: any, options: any): { - "__#9@#detail": any; - "__#9@#error": any; - readonly detail: any; - readonly error: any; - }; - }; - export default EventEmitter; - export function EventEmitter(): void; - export class EventEmitter { - _events: any; - _contexts: any; - _eventsCount: number; - _maxListeners: number; - setMaxListeners(n: any): this; - getMaxListeners(): any; - emit(type: any, ...args: any[]): boolean; - addListener(type: any, listener: any): any; - on(arg0: any, arg1: any): any; - prependListener(type: any, listener: any): any; - once(type: any, listener: any): this; - prependOnceListener(type: any, listener: any): this; - removeListener(type: any, listener: any): this; - off(type: any, listener: any): this; - removeAllListeners(type: any, ...args: any[]): this; - listeners(type: any): any[]; - rawListeners(type: any): any[]; - listenerCount(type: any): any; - eventNames(): (string | symbol)[]; - } - export namespace EventEmitter { - export { EventEmitter }; - export let defaultMaxListeners: number; - export function init(): void; - export function listenerCount(emitter: any, type: any): any; - export { once }; - } - export function once(emitter: any, name: any): Promise; -} - -declare module "socket:async/wrap" { - /** - * Returns `true` if a given function `fn` has the "async" wrapped tag, - * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this - * function will return `false`. - * @ignore - * @param {function} fn - * @param {boolean} - */ - export function isTagged(fn: Function): boolean; - /** - * Tags a function `fn` as being "async wrapped" so subsequent calls to - * `wrap(fn)` do not wrap an already wrapped function. - * @ignore - * @param {function} fn - * @return {function} - */ - export function tag(fn: Function): Function; - /** - * Wraps a function `fn` that captures a snapshot of the current async - * context. This function is idempotent and will not wrap a function more - * than once. - * @ignore - * @param {function} fn - * @return {function} - */ - export function wrap(fn: Function): Function; - export const symbol: unique symbol; - export default wrap; -} - -declare module "socket:diagnostics/channels" { - /** - * Normalizes a channel name to lower case replacing white space, - * hyphens (-), underscores (_), with dots (.). - * @ignore - */ - export function normalizeName(group: any, name: any): string; - /** - * Used to preallocate a minimum sized array of subscribers for - * a channel. - * @ignore - */ - export const MIN_CHANNEL_SUBSCRIBER_SIZE: 64; /** - * A general interface for diagnostic channels that can be subscribed to. + * An `ModuleNotFoundError` is an error type thrown when an imported or + * required module is not found. */ - export class Channel { - constructor(name: any); - name: any; - group: any; - /** - * Computed subscribers for all channels in this group. - * @type {Array} - */ - get subscribers(): Function[]; - /** - * Accessor for determining if channel has subscribers. This - * is always `false` for `Channel instances and `true` for `ActiveChannel` - * instances. - */ - get hasSubscribers(): boolean; - /** - * Computed number of subscribers for this channel. - */ - get length(): number; + export class ModuleNotFoundError extends exports.NotFoundError { /** - * Resets channel state. - * @param {(boolean)} [shouldOrphan = false] + * `ModuleNotFoundError` class constructor. + * @param {string} message + * @param {string[]=} [requireStack] */ - reset(shouldOrphan?: (boolean)): void; - channel(name: any): Channel; + constructor(message: string, requireStack?: string[] | undefined); + requireStack: string[]; + } + /** + * An `OperationError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class OperationError extends Error { /** - * Adds an `onMessage` subscription callback to the channel. - * @return {boolean} + * The code given to an `OPERATION_ERR` `DOMException` */ - subscribe(_: any, onMessage: any): boolean; + static get code(): any; /** - * Removes an `onMessage` subscription callback from the channel. - * @param {function} onMessage - * @return {boolean} + * `OperationError` class constructor. + * @param {string} message + * @param {number} [code] */ - unsubscribe(_: any, onMessage: Function): boolean; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `SecurityError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class SecurityError extends Error { /** - * A no-op for `Channel` instances. This function always returns `false`. - * @param {string|object} name - * @param {object=} [message] - * @return Promise + * The code given to an `SECURITY_ERR` `DOMException` */ - publish(name: string | object, message?: object | undefined): Promise; + static get code(): any; /** - * Returns a string representation of the `ChannelRegistry`. - * @ignore + * `SecurityError` class constructor. + * @param {string} message + * @param {number} [code] */ - toString(): any; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `TimeoutError` is an error type thrown when an operation timesout. + */ + export class TimeoutError extends Error { /** - * Iterator interface - * @ignore + * The code given to an `TIMEOUT_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - get [Symbol.iterator](): any[]; + static get code(): any; /** - * The `Channel` string tag. - * @ignore + * `TimeoutError` class constructor. + * @param {string} message */ - [Symbol.toStringTag](): string; - #private; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + import * as exports from "socket:errors"; + +} + +declare module "socket:buffer" { + export default Buffer; + export const File: { + new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; + prototype: File; + }; + export const Blob: { + new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; + prototype: Blob; + }; + export namespace constants { + export { kMaxLength as MAX_LENGTH }; + export { kMaxLength as MAX_STRING_LENGTH }; } + export const btoa: any; + export const atob: any; /** - * An `ActiveChannel` is a prototype implementation for a `Channel` - * that provides an interface what is considered an "active" channel. The - * `hasSubscribers` accessor always returns `true` for this class. + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. */ - export class ActiveChannel extends Channel { - unsubscribe(onMessage: any): boolean; - /** - * @param {object|any} message - * @return Promise - */ - publish(message: object | any): Promise; - } /** - * A container for a grouping of channels that are named and owned - * by this group. A `ChannelGroup` can also be a regular channel. + * @name Buffer + * @extends {Uint8Array} */ - export class ChannelGroup extends Channel { - /** - * @param {Array} channels - * @param {string} name - */ - constructor(name: string, channels: Array); - channels: Channel[]; - /** - * Subscribe to a channel or selection of channels in this group. - * @param {string} name - * @return {boolean} - */ - subscribe(name: string, onMessage: any): boolean; - /** - * Unsubscribe from a channel or selection of channels in this group. - * @param {string} name - * @return {boolean} - */ - unsubscribe(name: string, onMessage: any): boolean; + export function Buffer(arg: any, encodingOrOffset: any, length: any): any; + export class Buffer { /** - * Gets or creates a channel for this group. - * @param {string} name - * @return {Channel} + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. */ - channel(name: string): Channel; /** - * Select a test of channels from this group. - * The following syntax is supported: - * - One Channel: `group.channel` - * - All Channels: `*` - * - Many Channel: `group.*` - * - Collections: `['group.a', 'group.b', 'group.c'] or `group.a,group.b,group.c` - * @param {string|Array} keys - * @param {(boolean)} [hasSubscribers = false] - Enforce subscribers in selection - * @return {Array<{name: string, channel: Channel}>} + * @name Buffer + * @extends {Uint8Array} */ - select(keys: string | Array, hasSubscribers?: (boolean)): Array<{ - name: string; - channel: Channel; - }>; + constructor(arg: any, encodingOrOffset: any, length: any); + get parent(): any; + get offset(): any; + _isBuffer: boolean; + swap16(): this; + swap32(): this; + swap64(): this; + toString(...args: any[]): any; + toLocaleString: any; + equals(b: any): boolean; + inspect(): string; + compare(target: any, start: any, end: any, thisStart: any, thisEnd: any): 0 | 1 | -1; + includes(val: any, byteOffset: any, encoding: any): boolean; + indexOf(val: any, byteOffset: any, encoding: any): any; + lastIndexOf(val: any, byteOffset: any, encoding: any): any; + write(string: any, offset: any, length: any, encoding: any): number; + toJSON(): { + type: string; + data: any; + }; + slice(start: any, end: any): any; + readUintLE: (offset: any, byteLength: any, noAssert: any) => any; + readUIntLE(offset: any, byteLength: any, noAssert: any): any; + readUintBE: (offset: any, byteLength: any, noAssert: any) => any; + readUIntBE(offset: any, byteLength: any, noAssert: any): any; + readUint8: (offset: any, noAssert: any) => any; + readUInt8(offset: any, noAssert: any): any; + readUint16LE: (offset: any, noAssert: any) => number; + readUInt16LE(offset: any, noAssert: any): number; + readUint16BE: (offset: any, noAssert: any) => number; + readUInt16BE(offset: any, noAssert: any): number; + readUint32LE: (offset: any, noAssert: any) => number; + readUInt32LE(offset: any, noAssert: any): number; + readUint32BE: (offset: any, noAssert: any) => number; + readUInt32BE(offset: any, noAssert: any): number; + readBigUInt64LE: any; + readBigUInt64BE: any; + readIntLE(offset: any, byteLength: any, noAssert: any): any; + readIntBE(offset: any, byteLength: any, noAssert: any): any; + readInt8(offset: any, noAssert: any): any; + readInt16LE(offset: any, noAssert: any): number; + readInt16BE(offset: any, noAssert: any): number; + readInt32LE(offset: any, noAssert: any): number; + readInt32BE(offset: any, noAssert: any): number; + readBigInt64LE: any; + readBigInt64BE: any; + readFloatLE(offset: any, noAssert: any): number; + readFloatBE(offset: any, noAssert: any): number; + readDoubleLE(offset: any, noAssert: any): number; + readDoubleBE(offset: any, noAssert: any): number; + writeUintLE: (value: any, offset: any, byteLength: any, noAssert: any) => any; + writeUIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeUintBE: (value: any, offset: any, byteLength: any, noAssert: any) => any; + writeUIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeUint8: (value: any, offset: any, noAssert: any) => any; + writeUInt8(value: any, offset: any, noAssert: any): any; + writeUint16LE: (value: any, offset: any, noAssert: any) => any; + writeUInt16LE(value: any, offset: any, noAssert: any): any; + writeUint16BE: (value: any, offset: any, noAssert: any) => any; + writeUInt16BE(value: any, offset: any, noAssert: any): any; + writeUint32LE: (value: any, offset: any, noAssert: any) => any; + writeUInt32LE(value: any, offset: any, noAssert: any): any; + writeUint32BE: (value: any, offset: any, noAssert: any) => any; + writeUInt32BE(value: any, offset: any, noAssert: any): any; + writeBigUInt64LE: any; + writeBigUInt64BE: any; + writeIntLE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeIntBE(value: any, offset: any, byteLength: any, noAssert: any): any; + writeInt8(value: any, offset: any, noAssert: any): any; + writeInt16LE(value: any, offset: any, noAssert: any): any; + writeInt16BE(value: any, offset: any, noAssert: any): any; + writeInt32LE(value: any, offset: any, noAssert: any): any; + writeInt32BE(value: any, offset: any, noAssert: any): any; + writeBigInt64LE: any; + writeBigInt64BE: any; + writeFloatLE(value: any, offset: any, noAssert: any): any; + writeFloatBE(value: any, offset: any, noAssert: any): any; + writeDoubleLE(value: any, offset: any, noAssert: any): any; + writeDoubleBE(value: any, offset: any, noAssert: any): any; + copy(target: any, targetStart: any, start: any, end: any): number; + fill(val: any, start: any, end: any, encoding: any): this; } - /** - * An object mapping of named channels to `WeakRef` instances. - */ - export const registry: { - /** - * Subscribes callback `onMessage` to channel of `name`. - * @param {string} name - * @param {function} onMessage - * @return {boolean} - */ - subscribe(name: string, onMessage: Function): boolean; - /** - * Unsubscribes callback `onMessage` from channel of `name`. - * @param {string} name - * @param {function} onMessage - * @return {boolean} - */ - unsubscribe(name: string, onMessage: Function): boolean; - /** - * Predicate to determine if a named channel has subscribers. - * @param {string} name - */ - hasSubscribers(name: string): boolean; - /** - * Get or set a channel by `name`. - * @param {string} name - * @return {Channel} - */ - channel(name: string): Channel; - /** - * Creates a `ChannelGroup` for a set of channels - * @param {string} name - * @param {Array} [channels] - * @return {ChannelGroup} - */ - group(name: string, channels?: Array): ChannelGroup; - /** - * Get a channel by name. The name is normalized. - * @param {string} name - * @return {Channel?} - */ - get(name: string): Channel | null; - /** - * Checks if a channel is known by name. The name is normalized. - * @param {string} name - * @return {boolean} - */ - has(name: string): boolean; - /** - * Set a channel by name. The name is normalized. - * @param {string} name - * @param {Channel} channel - * @return {Channel?} - */ - set(name: string, channel: Channel): Channel | null; + export namespace Buffer { + export let TYPED_ARRAY_SUPPORT: boolean; + export let poolSize: number; /** - * Removes a channel by `name` - * @return {boolean} - */ - remove(name: any): boolean; + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ + export function from(value: any, encodingOrOffset?: any, length?: any): any; /** - * Returns a string representation of the `ChannelRegistry`. - * @ignore - */ - toString(): any; + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ + export function alloc(size: any, fill: any, encoding: any): Uint8Array; /** - * Returns a JSON representation of the `ChannelRegistry`. - * @return {object} - */ - toJSON(): object; + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ + export function allocUnsafe(size: any): Uint8Array; /** - * The `ChannelRegistry` string tag. - * @ignore + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. */ - [Symbol.toStringTag](): string; + export function allocUnsafeSlow(size: any): Uint8Array; + export function isBuffer(b: any): boolean; + export function compare(a: any, b: any): 0 | 1 | -1; + export function isEncoding(encoding: any): boolean; + export function concat(list: any, length: any): Uint8Array; + export { byteLength }; + } + export const kMaxLength: 2147483647; + export function SlowBuffer(length: any): Uint8Array; + export const INSPECT_MAX_BYTES: 50; + function byteLength(string: any, encoding: any, ...args: any[]): any; +} + +declare module "socket:url/urlpattern/urlpattern" { + export { me as URLPattern }; + var me: { + new (t: {}, r: any, n: any): { + "__#3@#i": any; + "__#3@#n": {}; + "__#3@#t": {}; + "__#3@#e": {}; + "__#3@#s": {}; + "__#3@#l": boolean; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + readonly hasRegExpGroups: boolean; + }; + compareComponent(t: any, r: any, n: any): number; }; - export default registry; } -declare module "socket:diagnostics/metric" { - export class Metric { - init(): void; - update(value: any): void; - destroy(): void; - toJSON(): {}; - toString(): string; - [Symbol.iterator](): any; - [Symbol.toStringTag](): string; - } - export default Metric; +declare module "socket:url/url/url" { + const _default: any; + export default _default; } -declare module "socket:diagnostics/window" { - export class RequestAnimationFrameMetric extends Metric { - constructor(options: any); - originalRequestAnimationFrame: typeof requestAnimationFrame; - requestAnimationFrame(callback: any): any; - sampleSize: any; - sampleTick: number; - channel: import("socket:diagnostics/channels").Channel; - value: { - rate: number; - samples: number; - }; - now: number; - samples: Uint8Array; - toJSON(): { - sampleSize: any; - sampleTick: number; - samples: number[]; - rate: number; - now: number; - }; +declare module "socket:querystring" { + export function unescapeBuffer(s: any, decodeSpaces: any): any; + export function unescape(s: any, decodeSpaces: any): any; + export function escape(str: any): any; + export function stringify(obj: any, sep: any, eq: any, options: any): string; + export function parse(qs: any, sep: any, eq: any, options: any): {}; + export function decode(qs: any, sep: any, eq: any, options: any): {}; + export function encode(obj: any, sep: any, eq: any, options: any): string; + namespace _default { + export { decode }; + export { encode }; + export { parse }; + export { stringify }; + export { escape }; + export { unescape }; } - export class FetchMetric extends Metric { - constructor(options: any); - originalFetch: typeof fetch; - channel: import("socket:diagnostics/channels").Channel; - fetch(resource: any, options: any, extra: any): Promise; - } - export class XMLHttpRequestMetric extends Metric { - constructor(options: any); - channel: import("socket:diagnostics/channels").Channel; - patched: { - open: { - (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; - }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; - }; - } - export class WorkerMetric extends Metric { - constructor(options: any); - GlobalWorker: { - new (scriptURL: string | URL, options?: WorkerOptions): Worker; - prototype: Worker; - }; - channel: import("socket:diagnostics/channels").Channel; - Worker: { - new (url: any, options: any, ...args: any[]): { - onmessage: (this: Worker, ev: MessageEvent) => any; - onmessageerror: (this: Worker, ev: MessageEvent) => any; - postMessage(message: any, transfer: Transferable[]): void; - postMessage(message: any, options?: StructuredSerializeOptions): void; - terminate(): void; - addEventListener(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K_1, listener: (this: Worker, ev: WorkerEventMap[K_1]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; - dispatchEvent(event: Event): boolean; - onerror: (this: AbstractWorker, ev: ErrorEvent) => any; + export default _default; +} + +declare module "socket:url/index" { + export function parse(input: any, options?: any): { + hash: any; + host: any; + hostname: any; + origin: any; + auth: string; + password: any; + pathname: any; + path: any; + port: any; + protocol: any; + search: any; + searchParams: any; + username: any; + [Symbol.toStringTag]: string; + }; + export function resolve(from: any, to: any): any; + export function format(input: any): any; + export function fileURLToPath(url: any): any; + const URLPattern_base: { + new (t: {}, r: any, n: any): { + "__#3@#i": any; + "__#3@#n": {}; + "__#3@#t": {}; + "__#3@#e": {}; + "__#3@#s": {}; + "__#3@#l": boolean; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + readonly hasRegExpGroups: boolean; }; - } - export const metrics: { - requestAnimationFrame: RequestAnimationFrameMetric; - XMLHttpRequest: XMLHttpRequestMetric; - Worker: WorkerMetric; - fetch: FetchMetric; - channel: import("socket:diagnostics/channels").ChannelGroup; - subscribe(...args: any[]): boolean; - unsubscribe(...args: any[]): boolean; - start(which: any): void; - stop(which: any): void; + compareComponent(t: any, r: any, n: any): number; }; - namespace _default { - export { metrics }; + export class URLPattern extends URLPattern_base { } - export default _default; - import { Metric } from "socket:diagnostics/metric"; -} - -declare module "socket:diagnostics/index" { - /** - * @param {string} name - * @return {import('./channels.js').Channel} - */ - export function channel(name: string): import("socket:diagnostics/channels").Channel; - export default exports; - import * as exports from "socket:diagnostics/index"; - import channels from "socket:diagnostics/channels"; - import window from "socket:diagnostics/window"; - - export { channels, window }; -} - -declare module "socket:diagnostics" { - export * from "socket:diagnostics/index"; - export default exports; - import * as exports from "socket:diagnostics/index"; + export const protocols: Set; + export default URL; + export class URL { + private constructor(); + } + export const URLSearchParams: any; + export const parseURL: any; } -declare module "socket:internal/symbols" { - export const dispose: any; - export const serialize: any; - namespace _default { - export { dispose }; - export { serialize }; - } - export default _default; +declare module "socket:url" { + export * from "socket:url/index"; + export default URL; + import URL from "socket:url/index"; } -declare module "socket:gc" { +declare module "socket:util/types" { /** - * Track `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when - * environment garbage collects object. - * @param {object} object + * Returns `true` if input is a plan `Object` instance. + * @param {any} input * @return {boolean} */ - export function ref(object: object, ...args: any[]): boolean; + export function isPlainObject(input: any): boolean; /** - * Stop tracking `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when - * environment garbage collects object. - * @param {object} object + * Returns `true` if input is an `AsyncFunction` + * @param {any} input * @return {boolean} */ - export function unref(object: object): boolean; + export function isAsyncFunction(input: any): boolean; /** - * An alias for `unref()` - * @param {object} object} + * Returns `true` if input is an `Function` + * @param {any} input * @return {boolean} */ - export function retain(object: object): boolean; + export function isFunction(input: any): boolean; /** - * Call finalize on `object` for `gc.finalizer` implementation. - * @param {object} object] - * @return {Promise} + * Returns `true` if input is an `AsyncFunction` object. + * @param {any} input + * @return {boolean} */ - export function finalize(object: object, ...args: any[]): Promise; + export function isAsyncFunctionObject(input: any): boolean; /** - * Calls all pending finalization handlers forcefully. This function - * may have unintended consequences as objects be considered finalized - * and still strongly held (retained) somewhere. + * Returns `true` if input is an `Function` object. + * @param {any} input + * @return {boolean} */ - export function release(): Promise; - export const finalizers: WeakMap; - export const kFinalizer: unique symbol; - export const finalizer: symbol; + export function isFunctionObject(input: any): boolean; /** - * @type {Set} + * Always returns `false`. + * @param {any} input + * @return {boolean} */ - export const pool: Set>; + export function isExternal(input: any): boolean; /** - * Static registry for objects to clean up underlying resources when they - * are gc'd by the environment. There is no guarantee that the `finalizer()` - * is called at any time. + * Returns `true` if input is a `Date` instance. + * @param {any} input + * @return {boolean} */ - export const registry: FinalizationRegistry; + export function isDate(input: any): boolean; /** - * Default exports which also acts a retained value to persist bound - * `Finalizer#handle()` functions from being gc'd before the - * `FinalizationRegistry` callback is called because `heldValue` must be - * strongly held (retained) in order for the callback to be called. + * Returns `true` if input is an `arguments` object. + * @param {any} input + * @return {boolean} */ - export const gc: any; - export default gc; + export function isArgumentsObject(input: any): boolean; /** - * A container for strongly (retain) referenced finalizer function - * with arguments weakly referenced to an object that will be - * garbage collected. + * Returns `true` if input is a `BigInt` object. + * @param {any} input + * @return {boolean} */ - export class Finalizer { - /** - * Creates a `Finalizer` from input. - */ - static from(handler: any): Finalizer; - /** - * `Finalizer` class constructor. - * @private - * @param {array} args - * @param {function} handle - */ - private constructor(); - args: any[]; - handle: any; - } -} - -declare module "socket:internal/async/hooks" { - export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - export function getNextAsyncResourceId(): number; - export function executionAsyncResource(): any; - export function executionAsyncId(): any; - export function triggerAsyncId(): any; - export function getDefaultExecutionAsyncId(): any; - export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; - export function getTopLevelAsyncResourceName(): any; + export function isBigIntObject(input: any): boolean; /** - * The default top level async resource ID - * @type {number} + * Returns `true` if input is a `Boolean` object. + * @param {any} input + * @return {boolean} */ - export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; - export namespace state { - let defaultExecutionAsyncId: number; - } - export namespace hooks { - let init: any[]; - let before: any[]; - let after: any[]; - let destroy: any[]; - let promiseResolve: any[]; - } + export function isBooleanObject(input: any): boolean; /** - * A base class for the `AsyncResource` class or other higher level async - * resource classes. + * Returns `true` if input is a `Number` object. + * @param {any} input + * @return {boolean} */ - export class CoreAsyncResource { - /** - * `CoreAsyncResource` class constructor. - * @param {string} type - * @param {object|number=} [options] - */ - constructor(type: string, options?: (object | number) | undefined); - /** - * The `CoreAsyncResource` type. - * @type {string} - */ - get type(): string; - /** - * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This - * value is only set to `true` if `emitDestroy()` was called, likely from - * destroying the resource manually. - * @type {boolean} - */ - get destroyed(): boolean; - /** - * The unique async resource ID. - * @return {number} - */ - asyncId(): number; - /** - * The trigger async resource ID. - * @return {number} - */ - triggerAsyncId(): number; - /** - * Manually emits destroy hook for the resource. - * @return {CoreAsyncResource} - */ - emitDestroy(): CoreAsyncResource; - /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @return {function} - */ - bind(fn: Function, thisArg?: object | undefined): Function; - /** - * Runs function `fn` in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @param {...any} [args] - * @return {any} - */ - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; - #private; - } - export class TopLevelAsyncResource extends CoreAsyncResource { - } - export const asyncContextVariable: Variable; - export const topLevelAsyncResource: TopLevelAsyncResource; - export default hooks; - import { Variable } from "socket:async/context"; -} - -declare module "socket:async/resource" { + export function isNumberObject(input: any): boolean; /** - * @typedef {{ - * triggerAsyncId?: number, - * requireManualDestroy?: boolean - * }} AsyncResourceOptions + * Returns `true` if input is a `String` object. + * @param {any} input + * @return {boolean} */ + export function isStringObject(input: any): boolean; /** - * A container that should be extended that represents a resource with - * an asynchronous execution context. + * Returns `true` if input is a `Symbol` object. + * @param {any} input + * @return {boolean} */ - export class AsyncResource extends CoreAsyncResource { - /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of an anonymous `AsyncResource`. - * @param {function} fn - * @param {object|string=} [type] - * @param {object=} [thisArg] - * @return {function} - */ - static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; - /** - * `AsyncResource` class constructor. - * @param {string} type - * @param {AsyncResourceOptions|number=} [options] - */ - constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); - } - export default AsyncResource; - export type AsyncResourceOptions = { - triggerAsyncId?: number; - requireManualDestroy?: boolean; - }; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - import { CoreAsyncResource } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/hooks" { + export function isSymbolObject(input: any): boolean; /** - * Factory for creating a `AsyncHook` instance. - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] - * @return {AsyncHook} + * Returns `true` if input is native `Error` instance. + * @param {any} input + * @return {boolean} */ - export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; + export function isNativeError(input: any): boolean; /** - * A container for `AsyncHooks` callbacks. - * @ignore + * Returns `true` if input is a `RegExp` instance. + * @param {any} input + * @return {boolean} */ - export class AsyncHookCallbacks { - /** - * `AsyncHookCallbacks` class constructor. - * @ignore - * @param {AsyncHookCallbackOptions} [options] - */ - constructor(options?: AsyncHookCallbackOptions); - init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - before(asyncId: any): void; - after(asyncId: any): void; - destroy(asyncId: any): void; - promiseResolve(asyncId: any): void; - } + export function isRegExp(input: any): boolean; /** - * A container for registering various callbacks for async resource hooks. + * Returns `true` if input is a `GeneratorFunction`. + * @param {any} input + * @return {boolean} */ - export class AsyncHook { - /** - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] - */ - constructor(callbacks?: any); - /** - * @type {boolean} - */ - get enabled(): boolean; - /** - * Enable the async hook. - * @return {AsyncHook} - */ - enable(): AsyncHook; - /** - * Disables the async hook - * @return {AsyncHook} - */ - disable(): AsyncHook; - #private; - } - export default createHook; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/storage" { + export function isGeneratorFunction(input: any): boolean; /** - * A container for storing values that remain present during - * asynchronous operations. + * Returns `true` if input is an `AsyncGeneratorFunction`. + * @param {any} input + * @return {boolean} */ - export class AsyncLocalStorage { - /** - * Binds function `fn` to run in the execution context of an - * anonymous `AsyncResource`. - * @param {function} fn - * @return {function} - */ - static bind(fn: Function): Function; - /** - * Captures the current async context and returns a function that runs - * a function in that execution context. - * @return {function} - */ - static snapshot(): Function; - /** - * @type {boolean} - */ - get enabled(): boolean; - /** - * Disables the `AsyncLocalStorage` instance. When disabled, - * `getStore()` will always return `undefined`. - */ - disable(): void; - /** - * Enables the `AsyncLocalStorage` instance. - */ - enable(): void; - /** - * Enables and sets the `AsyncLocalStorage` instance default store value. - * @param {any} store - */ - enterWith(store: any): void; - /** - * Runs function `fn` in the current asynchronous execution context with - * a given `store` value and arguments given to `fn`. - * @param {any} store - * @param {function} fn - * @param {...any} args - * @return {any} - */ - run(store: any, fn: Function, ...args: any[]): any; - exit(fn: any, ...args: any[]): any; - /** - * If the `AsyncLocalStorage` instance is enabled, it returns the current - * store value for this asynchronous execution context. - * @return {any|undefined} - */ - getStore(): any | undefined; - #private; - } - export default AsyncLocalStorage; -} - -declare module "socket:async/deferred" { + export function isAsyncGeneratorFunction(input: any): boolean; /** - * Dispatched when a `Deferred` internal promise is resolved. + * Returns `true` if input is an instance of a `Generator`. + * @param {any} input + * @return {boolean} */ - export class DeferredResolveEvent extends Event { - /** - * `DeferredResolveEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {any=} [result] - */ - constructor(type?: string | undefined, result?: any | undefined); - /** - * The `Deferred` promise result value. - * @type {any?} - */ - result: any | null; - } + export function isGeneratorObject(input: any): boolean; /** - * Dispatched when a `Deferred` internal promise is rejected. + * Returns `true` if input is a `Promise` instance. + * @param {any} input + * @return {boolean} */ - export class DeferredRejectEvent extends ErrorEvent { - /** - * `DeferredRejectEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {Error=} [error] - */ - constructor(type?: string | undefined, error?: Error | undefined); - } + export function isPromise(input: any): boolean; /** - * A utility class for creating deferred promises. - */ - export class Deferred extends EventTarget { - /** - * `Deferred` class constructor. - * @param {Deferred|Promise?} [promise] - */ - constructor(promise?: Deferred | (Promise | null)); - /** - * Function to resolve the associated promise. - * @type {function} - */ - resolve: Function; - /** - * Function to reject the associated promise. - * @type {function} - */ - reject: Function; - /** - * Attaches a fulfillment callback and a rejection callback to the promise, - * and returns a new promise resolving to the return value of the called - * callback. - * @param {function(any)=} [resolve] - * @param {function(Error)=} [reject] - */ - then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise; - /** - * Attaches a rejection callback to the promise, and returns a new promise - * resolving to the return value of the callback if it is called, or to its - * original fulfillment value if the promise is instead fulfilled. - * @param {function(Error)=} [callback] - */ - catch(callback?: ((arg0: Error) => any) | undefined): Promise; - /** - * Attaches a callback for when the promise is settled (fulfilled or rejected). - * @param {function(any?)} [callback] - */ - finally(callback?: (arg0: any | null) => any): Promise; - /** - * The promise associated with this Deferred instance. - * @type {Promise} - */ - get promise(): Promise; - /** - * A string representation of this Deferred instance. - * @type {string} - * @ignore - */ - get [Symbol.toStringTag](): string; - #private; - } - export default Deferred; -} - -declare module "socket:async" { - export default exports; - import AsyncLocalStorage from "socket:async/storage"; - import AsyncResource from "socket:async/resource"; - import AsyncContext from "socket:async/context"; - import Deferred from "socket:async/deferred"; - import { executionAsyncResource } from "socket:async/hooks"; - import { executionAsyncId } from "socket:async/hooks"; - import { triggerAsyncId } from "socket:async/hooks"; - import { createHook } from "socket:async/hooks"; - import { AsyncHook } from "socket:async/hooks"; - import * as exports from "socket:async"; - - export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; -} - -declare module "socket:application/menu" { + * Returns `true` if input is a `Map` instance. + * @param {any} input + * @return {boolean} + */ + export function isMap(input: any): boolean; /** - * Internal IPC for setting an application menu - * @ignore + * Returns `true` if input is a `Set` instance. + * @param {any} input + * @return {boolean} */ - export function setMenu(options: any, type: any): Promise; + export function isSet(input: any): boolean; /** - * Internal IPC for setting an application context menu - * @ignore + * Returns `true` if input is an instance of an `Iterator`. + * @param {any} input + * @return {boolean} */ - export function setContextMenu(options: any): Promise; + export function isIterator(input: any): boolean; /** - * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + * Returns `true` if input is an instance of an `AsyncIterator`. + * @param {any} input + * @return {boolean} */ - export class Menu extends EventTarget { - /** - * `Menu` class constructor. - * @ignore - * @param {string} type - */ - constructor(type: string); - /** - * The broadcast channel for this menu. - * @ignore - * @type {BroadcastChannel} - */ - get channel(): BroadcastChannel; - /** - * The `Menu` instance type. - * @type {('context'|'system'|'tray')?} - */ - get type(): "tray" | "system" | "context"; - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * Set the menu layout for this `Menu` instance. - * @param {string|object} layoutOrOptions - * @param {object=} [options] - */ - set(layoutOrOptions: string | object, options?: object | undefined): Promise; - #private; - } + export function isAsyncIterator(input: any): boolean; /** - * A container for various `Menu` instances. + * Returns `true` if input is an instance of a `MapIterator`. + * @param {any} input + * @return {boolean} */ - export class MenuContainer extends EventTarget { - /** - * `MenuContainer` class constructor. - * @param {EventTarget} [sourceEventTarget] - * @param {object=} [options] - */ - constructor(sourceEventTarget?: EventTarget, options?: object | undefined); - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * The `TrayMenu` instance for the application. - * @type {TrayMenu} - */ - get tray(): TrayMenu; - /** - * The `SystemMenu` instance for the application. - * @type {SystemMenu} - */ - get system(): SystemMenu; - /** - * The `ContextMenu` instance for the application. - * @type {ContextMenu} - */ - get context(): ContextMenu; - #private; - } + export function isMapIterator(input: any): boolean; /** - * A `Menu` instance that represents a context menu. + * Returns `true` if input is an instance of a `SetIterator`. + * @param {any} input + * @return {boolean} */ - export class ContextMenu extends Menu { - constructor(); - } + export function isSetIterator(input: any): boolean; /** - * A `Menu` instance that represents the system menu. + * Returns `true` if input is a `WeakMap` instance. + * @param {any} input + * @return {boolean} */ - export class SystemMenu extends Menu { - constructor(); - } + export function isWeakMap(input: any): boolean; /** - * A `Menu` instance that represents the tray menu. + * Returns `true` if input is a `WeakSet` instance. + * @param {any} input + * @return {boolean} */ - export class TrayMenu extends Menu { - constructor(); - } + export function isWeakSet(input: any): boolean; /** - * The application tray menu. - * @type {TrayMenu} + * Returns `true` if input is an `ArrayBuffer` instance. + * @param {any} input + * @return {boolean} */ - export const tray: TrayMenu; + export function isArrayBuffer(input: any): boolean; /** - * The application system menu. - * @type {SystemMenu} + * Returns `true` if input is an `DataView` instance. + * @param {any} input + * @return {boolean} */ - export const system: SystemMenu; + export function isDataView(input: any): boolean; /** - * The application context menu. - * @type {ContextMenu} + * Returns `true` if input is a `SharedArrayBuffer`. + * This will always return `false` if a `SharedArrayBuffer` + * type is not available. + * @param {any} input + * @return {boolean} */ - export const context: ContextMenu; + export function isSharedArrayBuffer(input: any): boolean; /** - * The application menus container. - * @type {MenuContainer} + * Not supported. This function will return `false` always. + * @param {any} input + * @return {boolean} */ - export const container: MenuContainer; - export default container; - import ipc from "socket:ipc"; -} - -declare module "socket:internal/events" { + export function isProxy(input: any): boolean; /** - * An event dispatched when an application URL is opening the application. + * Returns `true` if input looks like a module namespace object. + * @param {any} input + * @return {boolean} */ - export class ApplicationURLEvent extends Event { - /** - * `ApplicationURLEvent` class constructor. - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * `true` if the application URL is valid (parses correctly). - * @type {boolean} - */ - get isValid(): boolean; - /** - * Data associated with the `ApplicationURLEvent`. - * @type {?any} - */ - get data(): any; - /** - * The original source URI - * @type {?string} - */ - get source(): string; - /** - * The `URL` for the `ApplicationURLEvent`. - * @type {?URL} - */ - get url(): URL; - /** - * String tag name for an `ApplicationURLEvent` instance. - * @type {string} - */ - get [Symbol.toStringTag](): string; - #private; - } + export function isModuleNamespaceObject(input: any): boolean; /** - * An event dispacted for a registered global hotkey expression. + * Returns `true` if input is an `ArrayBuffer` of `SharedArrayBuffer`. + * @param {any} input + * @return {boolean} */ - export class HotKeyEvent extends MessageEvent { - /** - * `HotKeyEvent` class constructor. - * @ignore - * @param {string=} [type] - * @param {object=} [data] - */ - constructor(type?: string | undefined, data?: object | undefined); - /** - * The global unique ID for this hotkey binding. - * @type {number?} - */ - get id(): number; - /** - * The computed hash for this hotkey binding. - * @type {number?} - */ - get hash(): number; - /** - * The normalized hotkey expression as a sequence of tokens. - * @type {string[]} - */ - get sequence(): string[]; - /** - * The original expression of the hotkey binding. - * @type {string?} - */ - get expression(): string; - } + export function isAnyArrayBuffer(input: any): boolean; /** - * An event dispacted when a menu item is selected. + * Returns `true` if input is a "boxed" primitive. + * @param {any} input + * @return {boolean} */ - export class MenuItemEvent extends MessageEvent { - /** - * `MenuItemEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [data] - * @param {import('../application/menu.js').Menu} menu - */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); - /** - * The `Menu` this event has been dispatched for. - * @type {import('../application/menu.js').Menu?} - */ - get menu(): import("socket:application/menu").Menu; - /** - * The title of the menu item. - * @type {string?} - */ - get title(): string; - /** - * An optional tag value for the menu item that may also be the - * parent menu item title. - * @type {string?} - */ - get tag(): string; - /** - * The parent title of the menu item. - * @type {string?} - */ - get parent(): string; - #private; - } + export function isBoxedPrimitive(input: any): boolean; /** - * An event dispacted when the application receives an OS signal + * Returns `true` if input is an `ArrayBuffer` view. + * @param {any} input + * @return {boolean} */ - export class SignalEvent extends MessageEvent { - /** - * `SignalEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * The code of the signal. - * @type {import('../signal.js').signal} - */ - get code(): number; - /** - * The name of the signal. - * @type {string} - */ - get name(): string; - /** - * An optional message describing the signal - * @type {string} - */ - get message(): string; - #private; - } - namespace _default { - export { ApplicationURLEvent }; - export { MenuItemEvent }; - export { SignalEvent }; - export { HotKeyEvent }; - } - export default _default; -} - -declare module "socket:path/well-known" { + export function isArrayBufferView(input: any): boolean; /** - * Well known path to the user's "Downloads" folder. - * @type {?string} + * Returns `true` if input is a `TypedArray` instance. + * @param {any} input + * @return {boolean} */ - export const DOWNLOADS: string | null; + export function isTypedArray(input: any): boolean; /** - * Well known path to the user's "Documents" folder. - * @type {?string} + * Returns `true` if input is an `Uint8Array` instance. + * @param {any} input + * @return {boolean} */ - export const DOCUMENTS: string | null; + export function isUint8Array(input: any): boolean; /** - * Well known path to the user's "Pictures" folder. - * @type {?string} + * Returns `true` if input is an `Uint8ClampedArray` instance. + * @param {any} input + * @return {boolean} */ - export const PICTURES: string | null; + export function isUint8ClampedArray(input: any): boolean; /** - * Well known path to the user's "Desktop" folder. - * @type {?string} + * Returns `true` if input is an `Uint16Array` instance. + * @param {any} input + * @return {boolean} */ - export const DESKTOP: string | null; + export function isUint16Array(input: any): boolean; /** - * Well known path to the user's "Videos" folder. - * @type {?string} + * Returns `true` if input is an `Uint32Array` instance. + * @param {any} input + * @return {boolean} */ - export const VIDEOS: string | null; + export function isUint32Array(input: any): boolean; /** - * Well known path to the user's "Music" folder. - * @type {?string} + * Returns `true` if input is an Int8Array`` instance. + * @param {any} input + * @return {boolean} */ - export const MUSIC: string | null; + export function isInt8Array(input: any): boolean; /** - * Well known path to the application's "resources" folder. - * @type {?string} + * Returns `true` if input is an `Int16Array` instance. + * @param {any} input + * @return {boolean} */ - export const RESOURCES: string | null; + export function isInt16Array(input: any): boolean; /** - * Well known path to the application's "config" folder. - * @type {?string} + * Returns `true` if input is an `Int32Array` instance. + * @param {any} input + * @return {boolean} */ - export const CONFIG: string | null; + export function isInt32Array(input: any): boolean; /** - * Well known path to the application's "data" folder. - * @type {?string} + * Returns `true` if input is an `Float32Array` instance. + * @param {any} input + * @return {boolean} */ - export const DATA: string | null; + export function isFloat32Array(input: any): boolean; /** - * Well known path to the application's "log" folder. - * @type {?string} + * Returns `true` if input is an `Float64Array` instance. + * @param {any} input + * @return {boolean} */ - export const LOG: string | null; + export function isFloat64Array(input: any): boolean; /** - * Well known path to the application's "tmp" folder. - * @type {?string} + * Returns `true` if input is an `BigInt64Array` instance. + * @param {any} input + * @return {boolean} */ - export const TMP: string | null; + export function isBigInt64Array(input: any): boolean; /** - * Well known path to the application's "home" folder. - * This may be the user's HOME directory or the application container sandbox. - * @type {?string} + * Returns `true` if input is an `BigUint64Array` instance. + * @param {any} input + * @return {boolean} */ - export const HOME: string | null; - namespace _default { - export { DOWNLOADS }; - export { DOCUMENTS }; - export { RESOURCES }; - export { PICTURES }; - export { DESKTOP }; - export { VIDEOS }; - export { CONFIG }; - export { MUSIC }; - export { HOME }; - export { DATA }; - export { LOG }; - export { TMP }; - } - export default _default; -} - -declare module "socket:os" { + export function isBigUint64Array(input: any): boolean; /** - * Returns the operating system CPU architecture for which Socket was compiled. - * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' + * @ignore + * @param {any} input + * @return {boolean} */ - export function arch(): string; + export function isKeyObject(input: any): boolean; /** - * Returns an array of objects containing information about each CPU/core. - * @returns {Array} cpus - An array of objects containing information about each CPU/core. - * The properties of the objects are: - * - model `` - CPU model name. - * - speed `` - CPU clock speed (in MHz). - * - times `` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. - * - user `` - Time spent by this CPU or core in user mode. - * - nice `` - Time spent by this CPU or core in user mode with low priority (nice). - * - sys `` - Time spent by this CPU or core in system mode. - * - idle `` - Time spent by this CPU or core in idle mode. - * - irq `` - Time spent by this CPU or core in IRQ mode. - * @see {@link https://nodejs.org/api/os.html#os_os_cpus} + * Returns `true` if input is a `CryptoKey` instance. + * @param {any} input + * @return {boolean} */ - export function cpus(): Array; + export function isCryptoKey(input: any): boolean; /** - * Returns an object containing network interfaces that have been assigned a network address. - * @returns {object} - An object containing network interfaces that have been assigned a network address. - * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. - * The properties available on the assigned network address object include: - * - address `` - The assigned IPv4 or IPv6 address. - * - netmask `` - The IPv4 or IPv6 network mask. - * - family `` - The address family ('IPv4' or 'IPv6'). - * - mac `` - The MAC address of the network interface. - * - internal `` - Indicates whether the network interface is a loopback interface. - * - scopeid `` - The numeric scope ID (only specified when family is 'IPv6'). - * - cidr `` - The CIDR notation of the interface. - * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} + * Returns `true` if input is an `Array`. + * @param {any} input + * @return {boolean} */ - export function networkInterfaces(): object; + export const isArray: any; + export default exports; + import * as exports from "socket:util/types"; + +} + +declare module "socket:mime/index" { /** - * Returns the operating system platform. - * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_platform} - * The returned value is equivalent to `process.platform`. + * Look up a MIME type in various MIME databases. + * @param {string} query + * @return {Promise} */ - export function platform(): string; + export function lookup(query: string): Promise; /** - * Returns the operating system name. - * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_type} + * Look up a MIME type in various MIME databases synchronously. + * @param {string} query + * @return {DatabaseQueryResult[]} */ - export function type(): string; + export function lookupSync(query: string): DatabaseQueryResult[]; /** - * @returns {boolean} - `true` if the operating system is Windows. + * A container for a database lookup query. */ - export function isWindows(): boolean; + export class DatabaseQueryResult { + /** + * `DatabaseQueryResult` class constructor. + * @ignore + * @param {Database} database + * @param {string} name + * @param {string} mime + */ + constructor(database: Database, name: string, mime: string); + /** + * @type {string} + */ + name: string; + /** + * @type {string} + */ + mime: string; + database: Database; + } /** - * @returns {string} - The operating system's default directory for temporary files. + * A container for MIME types by class (audio, video, text, etc) + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} */ - export function tmpdir(): string; + export class Database { + /** + * `Database` class constructor. + * @param {string} name + */ + constructor(name: string); + /** + * The name of the MIME database. + * @type {string} + */ + name: string; + /** + * The URL of the MIME database. + * @type {URL} + */ + url: URL; + /** + * The mapping of MIME name to the MIME "content type" + * @type {Map} + */ + map: Map; + /** + * An index of MIME "content type" to the MIME name. + * @type {Map} + */ + index: Map; + /** + * An enumeration of all database entries. + * @return {Array>} + */ + entries(): Array>; + /** + * Loads database MIME entries into internal map. + * @return {Promise} + */ + load(): Promise; + /** + * Loads database MIME entries synchronously into internal map. + */ + loadSync(): void; + /** + * Lookup MIME type by name or content type + * @param {string} query + * @return {Promise} + */ + lookup(query: string): Promise; + /** + * Lookup MIME type by name or content type synchronously. + * @param {string} query + * @return {Promise} + */ + lookupSync(query: string): Promise; + /** + * Queries database map and returns an array of results + * @param {string} query + * @return {DatabaseQueryResult[]} + */ + query(query: string): DatabaseQueryResult[]; + } /** - * Get resource usage. + * A database of MIME types for 'application/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} */ - export function rusage(): any; + export const application: Database; /** - * Returns the system uptime in seconds. - * @returns {number} - The system uptime in seconds. + * A database of MIME types for 'audio/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} */ - export function uptime(): number; + export const audio: Database; /** - * Returns the operating system name. - * @returns {string} - The operating system name. + * A database of MIME types for 'font/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} */ - export function uname(): string; + export const font: Database; /** - * It's implemented in process.hrtime.bigint() - * @ignore + * A database of MIME types for 'image/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} */ - export function hrtime(): any; + export const image: Database; /** - * Node.js doesn't have this method. - * @ignore + * A database of MIME types for 'model/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} */ - export function availableMemory(): any; + export const model: Database; /** - * The host operating system. This value can be one of: - * - android - * - android-emulator - * - iphoneos - * - iphone-simulator - * - linux - * - macosx - * - unix - * - unknown - * - win32 - * @ignore - * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} + * A database of MIME types for 'multipart/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export const multipart: Database; /** - * Returns the home directory of the current user. - * @return {string} + * A database of MIME types for 'text/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} */ - export function homedir(): string; - export { constants }; + export const text: Database; /** - * @type {string} - * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. + * A database of MIME types for 'video/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} */ - export const EOL: string; + export const video: Database; + /** + * An array of known MIME databases. Custom databases can be added to this + * array in userspace for lookup with `mime.lookup()` + * @type {Database[]} + */ + export const databases: Database[]; + export class MIMEParams extends Map { + constructor(); + constructor(entries?: readonly (readonly [any, any])[]); + constructor(); + constructor(iterable?: Iterable); + } + export class MIMEType { + constructor(input: any); + set type(value: any); + get type(): any; + set subtype(value: any); + get subtype(): any; + get essence(): string; + get params(): any; + toString(): string; + toJSON(): string; + #private; + } + namespace _default { + export { Database }; + export { databases }; + export { lookup }; + export { lookupSync }; + export { MIMEParams }; + export { MIMEType }; + export { application }; + export { audio }; + export { font }; + export { image }; + export { model }; + export { multipart }; + export { text }; + export { video }; + } + export default _default; +} + +declare module "socket:mime" { + export * from "socket:mime/index"; export default exports; - import constants from "socket:os/constants"; - import * as exports from "socket:os"; - + import * as exports from "socket:mime/index"; } -declare module "socket:signal" { +declare module "socket:util" { + export function debug(section: any): { + (...args: any[]): void; + enabled: boolean; + }; + export function hasOwnProperty(object: any, property: any): any; + export function isDate(object: any): boolean; + export function isTypedArray(object: any): boolean; + export function isArrayLike(input: any): boolean; + export function isError(object: any): boolean; + export function isSymbol(value: any): boolean; + export function isNumber(value: any): boolean; + export function isBoolean(value: any): boolean; + export function isArrayBufferView(buf: any): boolean; + export function isAsyncFunction(object: any): boolean; + export function isArgumentsObject(object: any): boolean; + export function isEmptyObject(object: any): boolean; + export function isObject(object: any): boolean; + export function isUndefined(value: any): boolean; + export function isNull(value: any): boolean; + export function isNullOrUndefined(value: any): boolean; + export function isPrimitive(value: any): boolean; + export function isRegExp(value: any): boolean; + export function isPlainObject(object: any): boolean; + export function isArrayBuffer(object: any): boolean; + export function isBufferLike(object: any): boolean; + export function isFunction(value: any): boolean; + export function isErrorLike(error: any): boolean; + export function isClass(value: any): boolean; + export function isBuffer(value: any): boolean; + export function isPromiseLike(object: any): boolean; + export function toString(object: any): any; + export function toBuffer(object: any, encoding?: any): any; + export function toProperCase(string: any): any; + export function splitBuffer(buffer: any, highWaterMark: any): any[]; + export function clamp(value: any, min: any, max: any): number; + export function promisify(original: any): any; + export function inspect(value: any, options: any): any; + export namespace inspect { + let ignore: symbol; + let custom: symbol; + } + export function format(format: any, ...args: any[]): string; + export function parseJSON(string: any): any; + export function parseHeaders(headers: any): string[][]; + export function noop(): void; + export function isValidPercentageValue(input: any): boolean; + export function compareBuffers(a: any, b: any): any; + export function inherits(Constructor: any, Super: any): void; /** - * Converts an `signal` code to its corresponding string message. - * @param {import('./os/constants.js').signal} {code} - * @return {string} + * @ignore + * @param {string} source + * @return {boolean} */ - export function toString(code: any): string; + export function isESMSource(source: string): boolean; + export function deprecate(...args: any[]): void; + export { types }; + export const TextDecoder: { + new (label?: string, options?: TextDecoderOptions): TextDecoder; + prototype: TextDecoder; + }; + export const TextEncoder: { + new (): TextEncoder; + prototype: TextEncoder; + }; + export const isArray: any; + export const inspectSymbols: symbol[]; + export class IllegalConstructor { + } + export const MIMEType: typeof mime.MIMEType; + export const MIMEParams: typeof mime.MIMEParams; + export default exports; + import types from "socket:util/types"; + import mime from "socket:mime"; + import * as exports from "socket:util"; + +} + +declare module "socket:async/context" { /** - * Gets the code for a given 'signal' name. - * @param {string|number} name - * @return {signal} + * @module Async.AsyncContext + * + * Async Context for JavaScript based on the TC39 proposal. + * + * Example usage: + * ```js + * // `AsyncContext` is also globally available as `globalThis.AsyncContext` + * import AsyncContext from 'socket:async/context' + * + * const var = new AsyncContext.Variable() + * var.run('top', () => { + * console.log(var.get()) // 'top' + * queueMicrotask(() => { + * var.run('nested', () => { + * console.log(var.get()) // 'nested' + * }) + * }) + * }) + * ``` + * + * @see {@link https://tc39.es/proposal-async-context} + * @see {@link https://github.com/tc39/proposal-async-context} */ - export function getCode(name: string | number): signal; /** - * Gets the name for a given 'signal' code - * @return {string} - * @param {string|number} code + * @template T + * @typedef {{ + * name?: string, + * defaultValue?: T + * }} VariableOptions */ - export function getName(code: string | number): string; /** - * Gets the message for a 'signal' code. - * @param {number|string} code - * @return {string} + * @callback AnyFunc + * @template T + * @this T + * @param {...any} args + * @returns {any} */ - export function getMessage(code: number | string): string; /** - * Add a signal event listener. - * @param {string|number} signal - * @param {function(SignalEvent)} callback - * @param {{ once?: boolean }=} [options] + * `FrozenRevert` holds a frozen Mapping that will be simply restored + * when the revert is run. + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} */ - export function addEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { - once?: boolean; - } | undefined): void; + export class FrozenRevert { + /** + * `FrozenRevert` class constructor. + * @param {Mapping} mapping + */ + constructor(mapping: Mapping); + /** + * Restores (unchaged) mapping from this `FrozenRevert`. This function is + * called by `AsyncContext.Storage` when it reverts a current mapping to the + * previous state before a "fork". + * @param {Mapping=} [unused] + * @return {Mapping} + */ + restore(unused?: Mapping | undefined): Mapping; + #private; + } /** - * Remove a signal event listener. - * @param {string|number} signal - * @param {function(SignalEvent)} callback - * @param {{ once?: boolean }=} [options] + * Revert holds the state on how to revert a change to the + * `AsyncContext.Storage` current `Mapping` + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} + * @template T */ - export function removeEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { - once?: boolean; - } | undefined): void; - export { constants }; - export const channel: BroadcastChannel; - export const SIGHUP: any; - export const SIGINT: any; - export const SIGQUIT: any; - export const SIGILL: any; - export const SIGTRAP: any; - export const SIGABRT: any; - export const SIGIOT: any; - export const SIGBUS: any; - export const SIGFPE: any; - export const SIGKILL: any; - export const SIGUSR1: any; - export const SIGSEGV: any; - export const SIGUSR2: any; - export const SIGPIPE: any; - export const SIGALRM: any; - export const SIGTERM: any; - export const SIGCHLD: any; - export const SIGCONT: any; - export const SIGSTOP: any; - export const SIGTSTP: any; - export const SIGTTIN: any; - export const SIGTTOU: any; - export const SIGURG: any; - export const SIGXCPU: any; - export const SIGXFSZ: any; - export const SIGVTALRM: any; - export const SIGPROF: any; - export const SIGWINCH: any; - export const SIGIO: any; - export const SIGINFO: any; - export const SIGSYS: any; - export const strings: { - [x: number]: string; - }; - namespace _default { - export { addEventListener }; - export { removeEventListener }; - export { constants }; - export { channel }; - export { strings }; - export { toString }; - export { getName }; - export { getCode }; - export { getMessage }; - export { SIGHUP }; - export { SIGINT }; - export { SIGQUIT }; - export { SIGILL }; - export { SIGTRAP }; - export { SIGABRT }; - export { SIGIOT }; - export { SIGBUS }; - export { SIGFPE }; - export { SIGKILL }; - export { SIGUSR1 }; - export { SIGSEGV }; - export { SIGUSR2 }; - export { SIGPIPE }; - export { SIGALRM }; - export { SIGTERM }; - export { SIGCHLD }; - export { SIGCONT }; - export { SIGSTOP }; - export { SIGTSTP }; - export { SIGTTIN }; - export { SIGTTOU }; - export { SIGURG }; - export { SIGXCPU }; - export { SIGXFSZ }; - export { SIGVTALRM }; - export { SIGPROF }; - export { SIGWINCH }; - export { SIGIO }; - export { SIGINFO }; - export { SIGSYS }; - } - export default _default; - export type signal = import("socket:os/constants").signal; - import { SignalEvent } from "socket:internal/events"; - import { signal as constants } from "socket:os/constants"; -} - -declare module "socket:internal/streams/web" { - export class ByteLengthQueuingStrategy { - constructor(e: any); - _byteLengthQueuingStrategyHighWaterMark: any; - get highWaterMark(): any; - get size(): (e: any) => any; - } - export class CountQueuingStrategy { - constructor(e: any); - _countQueuingStrategyHighWaterMark: any; - get highWaterMark(): any; - get size(): () => number; - } - export class ReadableByteStreamController { - get byobRequest(): any; - get desiredSize(): number; - close(): void; - enqueue(e: any): void; - error(e?: any): void; - _pendingPullIntos: v; - [T](e: any): any; - [C](e: any): any; - [P](): void; - } - export class ReadableStream { - static from(e: any): any; - constructor(e?: {}, t?: {}); - get locked(): boolean; - cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; - pipeThrough(e: any, t?: {}): any; - pipeTo(e: any, t?: {}): any; - tee(): any; - values(e?: any): any; - } - export class ReadableStreamBYOBReader { - constructor(e: any); - _readIntoRequests: v; - get closed(): any; - cancel(e?: any): any; - read(e: any, t?: {}): any; - releaseLock(): void; - } - export class ReadableStreamBYOBRequest { - get view(): any; - respond(e: any): void; - respondWithNewView(e: any): void; - } - export class ReadableStreamDefaultController { - get desiredSize(): number; - close(): void; - enqueue(e?: any): void; - error(e?: any): void; - [T](e: any): any; - [C](e: any): void; - [P](): void; - } - export class ReadableStreamDefaultReader { - constructor(e: any); - _readRequests: v; - get closed(): any; - cancel(e?: any): any; - read(): any; - releaseLock(): void; + export class Revert { + /** + * `Revert` class constructor. + * @param {Mapping} mapping + * @param {Variable} key + */ + constructor(mapping: Mapping, key: Variable); + /** + * @type {T|undefined} + */ + get previousVariable(): T; + /** + * Restores a mapping from this `Revert`. This function is called by + * `AsyncContext.Storage` when it reverts a current mapping to the + * previous state before a "fork". + * @param {Mapping} current + * @return {Mapping} + */ + restore(current: Mapping): Mapping; + #private; } - export class TransformStream { - constructor(e?: {}, t?: {}, r?: {}); - get readable(): any; - get writable(): any; + /** + * A container for all `AsyncContext.Variable` instances and snapshot state. + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/mapping.ts} + */ + export class Mapping { + /** + * `Mapping` class constructor. + * @param {Map, any>} data + */ + constructor(data: Map, any>); + /** + * Freezes the `Mapping` preventing `AsyncContext.Variable` modifications with + * `set()` and `delete()`. + */ + freeze(): void; + /** + * Returns `true` if the `Mapping` is frozen, otherwise `false`. + * @return {boolean} + */ + isFrozen(): boolean; + /** + * Optionally returns a new `Mapping` if the current one is "frozen", + * otherwise it just returns the current instance. + * @return {Mapping} + */ + fork(): Mapping; + /** + * Returns `true` if the `Mapping` has a `AsyncContext.Variable` at `key`, + * otherwise `false. + * @template T + * @param {Variable} key + * @return {boolean} + */ + has(key: Variable): boolean; + /** + * Gets an `AsyncContext.Variable` value at `key`. If not set, this function + * returns `undefined`. + * @template T + * @param {Variable} key + * @return {boolean} + */ + get(key: Variable): boolean; + /** + * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, + * then a "forked" (new) instance with the value set on it is returned, + * otherwise the current instance. + * @template T + * @param {Variable} key + * @param {T} value + * @return {Mapping} + */ + set(key: Variable, value: T_2): Mapping; + /** + * Delete an `AsyncContext.Variable` value at `key`. + * If the `Mapping` is frozen, then a "forked" (new) instance is returned, + * otherwise the current instance. + * @template T + * @param {Variable} key + * @param {T} value + * @return {Mapping} + */ + delete(key: Variable): Mapping; + #private; } - export class TransformStreamDefaultController { - get desiredSize(): number; - enqueue(e?: any): void; - error(e?: any): void; - terminate(): void; + /** + * A container of all `AsyncContext.Variable` data. + * @ignore + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/storage.ts} + */ + export class Storage { + /** + * The current `Mapping` for this `AsyncContext`. + * @type {Mapping} + */ + static "__#8@#current": Mapping; + /** + * Returns `true` if the current `Mapping` has a + * `AsyncContext.Variable` at `key`, + * otherwise `false. + * @template T + * @param {Variable} key + * @return {boolean} + */ + static has(key: Variable): boolean; + /** + * Gets an `AsyncContext.Variable` value at `key` for the current `Mapping`. + * If not set, this function returns `undefined`. + * @template T + * @param {Variable} key + * @return {T|undefined} + */ + static get(key: Variable): T_1; + /** + * Set updates the `AsyncContext.Variable` with a new value and returns a + * revert action that allows the modification to be reversed in the future. + * @template T + * @param {Variable} key + * @param {T} value + * @return {Revert|FrozenRevert} + */ + static set(key: Variable, value: T_2): FrozenRevert | Revert; + /** + * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` + * or `Revert` which can restore the storage state to the state at + * the time of the snapshot. + * @return {FrozenRevert} + */ + static snapshot(): FrozenRevert; + /** + * Restores the storage `Mapping` state to state at the time the + * "revert" (`FrozenRevert` or `Revert`) was created. + * @template T + * @param {Revert|FrozenRevert} revert + */ + static restore(revert: FrozenRevert | Revert): void; + /** + * Switches storage `Mapping` state to the state at the time of a + * "snapshot". + * @param {FrozenRevert} snapshot + * @return {FrozenRevert} + */ + static switch(snapshot: FrozenRevert): FrozenRevert; } - export class WritableStream { - constructor(e?: {}, t?: {}); - get locked(): boolean; - abort(e?: any): any; - close(): any; - getWriter(): WritableStreamDefaultWriter; + /** + * `AsyncContext.Variable` is a container for a value that is associated with + * the current execution flow. The value is propagated through async execution + * flows, and can be snapshot and restored with Snapshot. + * @template T + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} + */ + export class Variable { + /** + * `Variable` class constructor. + * @param {VariableOptions=} [options] + */ + constructor(options?: VariableOptions | undefined); + set defaultValue(defaultValue: T); + /** + * @ignore + */ + get defaultValue(): T; + /** + * @ignore + */ + get revert(): FrozenRevert | Revert; + /** + * The name of this async context variable. + * @type {string} + */ + get name(): string; + /** + * Executes a function `fn` with specified arguments, + * setting a new value to the current context before the call, + * and ensuring the environment is reverted back afterwards. + * The function allows for the modification of a specific context's + * state in a controlled manner, ensuring that any changes can be undone. + * @template T, F extends AnyFunc + * @param {T} value + * @param {F} fn + * @param {...Parameters} args + * @returns {ReturnType} + */ + run(value: T_1, fn: F, ...args: Parameters[]): ReturnType; + /** + * Get the `AsyncContext.Variable` value. + * @template T + * @return {T|undefined} + */ + get(): T_2; + #private; } - export class WritableStreamDefaultController { - get abortReason(): any; - get signal(): any; - error(e?: any): void; - [w](e: any): any; - [R](): void; + /** + * `AsyncContext.Snapshot` allows you to opaquely capture the current values of + * all `AsyncContext.Variable` instances and execute a function at a later time + * as if those values were still the current values (a snapshot and restore). + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} + */ + export class Snapshot { + /** + * Wraps a given function `fn` with additional logic to take a snapshot of + * `Storage` before invoking `fn`. Returns a new function with the same + * signature as `fn` that when called, will invoke `fn` with the current + * `this` context and provided arguments, after restoring the `Storage` + * snapshot. + * + * `AsyncContext.Snapshot.wrap` is a helper which captures the current values + * of all Variables and returns a wrapped function. When invoked, this + * wrapped function restores the state of all Variables and executes the + * inner function. + * + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshotwrap} + * + * @template F + * @param {F} fn + * @returns {F} + */ + static wrap(fn: F_1): F_1; + /** + * Runs the given function `fn` with arguments `args`, using a `null` + * context and the current snapshot. + * + * @template F extends AnyFunc + * @param {F} fn + * @param {...Parameters} args + * @returns {ReturnType} + */ + run(fn: F, ...args: Parameters[]): ReturnType; + #private; } - export class WritableStreamDefaultWriter { - constructor(e: any); - _ownerWritableStream: any; - get closed(): any; - get desiredSize(): number; - get ready(): any; - abort(e?: any): any; - close(): any; - releaseLock(): void; - write(e?: any): any; + /** + * `AsyncContext` container. + */ + export class AsyncContext { + /** + * `AsyncContext.Variable` is a container for a value that is associated with + * the current execution flow. The value is propagated through async execution + * flows, and can be snapshot and restored with Snapshot. + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} + * @type {typeof Variable} + */ + static Variable: typeof Variable; + /** + * `AsyncContext.Snapshot` allows you to opaquely capture the current values of + * all `AsyncContext.Variable` instances and execute a function at a later time + * as if those values were still the current values (a snapshot and restore). + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} + * @type {typeof Snapshot} + */ + static Snapshot: typeof Snapshot; } - class v { - _cursor: number; - _size: number; - _front: { - _elements: any[]; - _next: any; + export default AsyncContext; + export type VariableOptions = { + name?: string; + defaultValue?: T; + }; + export type AnyFunc = () => any; +} + +declare module "socket:events" { + export const Event: { + new (type: string, eventInitDict?: EventInit): Event; + prototype: Event; + readonly NONE: 0; + readonly CAPTURING_PHASE: 1; + readonly AT_TARGET: 2; + readonly BUBBLING_PHASE: 3; + } | { + new (): {}; + }; + export const EventTarget: { + new (): {}; + }; + export const CustomEvent: { + new (type: string, eventInitDict?: CustomEventInit): CustomEvent; + prototype: CustomEvent; + } | { + new (type: any, options: any): { + "__#11@#detail": any; + readonly detail: any; }; - _back: { - _elements: any[]; - _next: any; + }; + export const MessageEvent: { + new (type: string, eventInitDict?: MessageEventInit): MessageEvent; + prototype: MessageEvent; + } | { + new (type: any, options: any): { + "__#12@#detail": any; + "__#12@#data": any; + readonly detail: any; + readonly data: any; }; - get length(): number; - push(e: any): void; - shift(): any; - forEach(e: any): void; - peek(): any; + }; + export const ErrorEvent: { + new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; + prototype: ErrorEvent; + } | { + new (type: any, options: any): { + "__#13@#detail": any; + "__#13@#error": any; + readonly detail: any; + readonly error: any; + }; + }; + export default EventEmitter; + export function EventEmitter(): void; + export class EventEmitter { + _events: any; + _contexts: any; + _eventsCount: number; + _maxListeners: number; + setMaxListeners(n: any): this; + getMaxListeners(): any; + emit(type: any, ...args: any[]): boolean; + addListener(type: any, listener: any): any; + on(arg0: any, arg1: any): any; + prependListener(type: any, listener: any): any; + once(type: any, listener: any): this; + prependOnceListener(type: any, listener: any): this; + removeListener(type: any, listener: any): this; + off(type: any, listener: any): this; + removeAllListeners(type: any, ...args: any[]): this; + listeners(type: any): any[]; + rawListeners(type: any): any[]; + listenerCount(type: any): any; + eventNames(): any; } - const T: unique symbol; - const C: unique symbol; - const P: unique symbol; - const w: unique symbol; - const R: unique symbol; - export {}; -} - -declare module "socket:internal/streams" { - const _default: any; - export default _default; - import { ReadableStream } from "socket:internal/streams/web"; - import { ReadableStreamBYOBReader } from "socket:internal/streams/web"; - import { ReadableByteStreamController } from "socket:internal/streams/web"; - import { ReadableStreamBYOBRequest } from "socket:internal/streams/web"; - import { ReadableStreamDefaultController } from "socket:internal/streams/web"; - import { ReadableStreamDefaultReader } from "socket:internal/streams/web"; - import { WritableStream } from "socket:internal/streams/web"; - import { WritableStreamDefaultController } from "socket:internal/streams/web"; - import { WritableStreamDefaultWriter } from "socket:internal/streams/web"; - import { TransformStream } from "socket:internal/streams/web"; - import { TransformStreamDefaultController } from "socket:internal/streams/web"; - import { ByteLengthQueuingStrategy } from "socket:internal/streams/web"; - import { CountQueuingStrategy } from "socket:internal/streams/web"; - export { ReadableStream, ReadableStreamBYOBReader, ReadableByteStreamController, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy }; + export namespace EventEmitter { + export { EventEmitter }; + export let defaultMaxListeners: number; + export function init(): void; + export function listenerCount(emitter: any, type: any): any; + export { once }; + } + export function once(emitter: any, name: any): Promise; } -declare module "socket:stream/web" { - export const TextEncoderStream: typeof UnsupportedStreamInterface; - export const TextDecoderStream: { - new (label?: string, options?: TextDecoderOptions): TextDecoderStream; - prototype: TextDecoderStream; - } | typeof UnsupportedStreamInterface; - export const CompressionStream: { - new (format: CompressionFormat): CompressionStream; - prototype: CompressionStream; - } | typeof UnsupportedStreamInterface; - export const DecompressionStream: { - new (format: CompressionFormat): DecompressionStream; - prototype: DecompressionStream; - } | typeof UnsupportedStreamInterface; - export default exports; - import { ReadableStream } from "socket:internal/streams"; - import { ReadableStreamBYOBReader } from "socket:internal/streams"; - import { ReadableByteStreamController } from "socket:internal/streams"; - import { ReadableStreamBYOBRequest } from "socket:internal/streams"; - import { ReadableStreamDefaultController } from "socket:internal/streams"; - import { ReadableStreamDefaultReader } from "socket:internal/streams"; - import { WritableStream } from "socket:internal/streams"; - import { WritableStreamDefaultController } from "socket:internal/streams"; - import { WritableStreamDefaultWriter } from "socket:internal/streams"; - import { TransformStream } from "socket:internal/streams"; - import { TransformStreamDefaultController } from "socket:internal/streams"; - import { ByteLengthQueuingStrategy } from "socket:internal/streams"; - import { CountQueuingStrategy } from "socket:internal/streams"; - class UnsupportedStreamInterface { - } - import * as exports from "socket:stream/web"; - - export { ReadableStream, ReadableStreamBYOBReader, ReadableByteStreamController, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy }; +declare module "socket:async/wrap" { + /** + * Returns `true` if a given function `fn` has the "async" wrapped tag, + * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this + * function will return `false`. + * @ignore + * @param {function} fn + * @param {boolean} + */ + export function isTagged(fn: Function): boolean; + /** + * Tags a function `fn` as being "async wrapped" so subsequent calls to + * `wrap(fn)` do not wrap an already wrapped function. + * @ignore + * @param {function} fn + * @return {function} + */ + export function tag(fn: Function): Function; + /** + * Wraps a function `fn` that captures a snapshot of the current async + * context. This function is idempotent and will not wrap a function more + * than once. + * @ignore + * @param {function} fn + * @return {function} + */ + export function wrap(fn: Function): Function; + export const symbol: unique symbol; + export default wrap; } -declare module "socket:stream" { - export function pipelinePromise(...streams: any[]): Promise; - export function pipeline(stream: any, ...streams: any[]): any; - export function isStream(stream: any): boolean; - export function isStreamx(stream: any): boolean; - export function getStreamError(stream: any): any; - export function isReadStreamx(stream: any): any; - export { web }; - export class FixedFIFO { - constructor(hwm: any); - buffer: any[]; - mask: number; - top: number; - btm: number; - next: any; - clear(): void; - push(data: any): boolean; - shift(): any; - peek(): any; - isEmpty(): boolean; - } - export class FIFO { - constructor(hwm: any); - hwm: any; - head: FixedFIFO; - tail: FixedFIFO; - length: number; - clear(): void; - push(val: any): void; - shift(): any; - peek(): any; - isEmpty(): boolean; - } - export class WritableState { - constructor(stream: any, { highWaterMark, map, mapWritable, byteLength, byteLengthWritable }?: { - highWaterMark?: number; - map?: any; - mapWritable: any; - byteLength: any; - byteLengthWritable: any; - }); - stream: any; - queue: FIFO; - highWaterMark: number; - buffered: number; - error: any; - pipeline: any; - drains: any; - byteLength: any; - map: any; - afterWrite: any; - afterUpdateNextTick: any; - get ended(): boolean; - push(data: any): boolean; - shift(): any; - end(data: any): void; - autoBatch(data: any, cb: any): any; - update(): void; - updateNonPrimary(): void; - continueUpdate(): boolean; - updateCallback(): void; - updateNextTick(): void; - } - export class ReadableState { - constructor(stream: any, { highWaterMark, map, mapReadable, byteLength, byteLengthReadable }?: { - highWaterMark?: number; - map?: any; - mapReadable: any; - byteLength: any; - byteLengthReadable: any; - }); - stream: any; - queue: FIFO; - highWaterMark: number; - buffered: number; - readAhead: boolean; - error: any; - pipeline: Pipeline; - byteLength: any; - map: any; - pipeTo: any; - afterRead: any; - afterUpdateNextTick: any; - get ended(): boolean; - pipe(pipeTo: any, cb: any): void; - push(data: any): boolean; - shift(): any; - unshift(data: any): void; - read(): any; - drain(): void; - update(): void; - updateNonPrimary(): void; - continueUpdate(): boolean; - updateCallback(): void; - updateNextTick(): void; - } - export class TransformState { - constructor(stream: any); - data: any; - afterTransform: any; - afterFinal: any; - } - export class Pipeline { - constructor(src: any, dst: any, cb: any); - from: any; - to: any; - afterPipe: any; - error: any; - pipeToFinished: boolean; - finished(): void; - done(stream: any, err: any): void; - } - export class Stream extends EventEmitter { - constructor(opts: any); - _duplexState: number; - _readableState: any; - _writableState: any; - _open(cb: any): void; - _destroy(cb: any): void; - _predestroy(): void; - get readable(): boolean; - get writable(): boolean; - get destroyed(): boolean; - get destroying(): boolean; - destroy(err: any): void; - } - export class Readable extends Stream { - static _fromAsyncIterator(ite: any, opts: any): Readable; - static from(data: any, opts: any): any; - static isBackpressured(rs: any): boolean; - static isPaused(rs: any): boolean; - _readableState: ReadableState; - _read(cb: any): void; - pipe(dest: any, cb: any): any; - read(): any; - push(data: any): boolean; - unshift(data: any): void; - resume(): this; - pause(): this; - } - export class Writable extends Stream { - static isBackpressured(ws: any): boolean; - static drained(ws: any): Promise; - _writableState: WritableState; - _writev(batch: any, cb: any): void; - _write(data: any, cb: any): void; - _final(cb: any): void; - write(data: any): boolean; - end(data: any): this; - } - export class Duplex extends Readable { - _writableState: WritableState; - _writev(batch: any, cb: any): void; - _write(data: any, cb: any): void; - _final(cb: any): void; - write(data: any): boolean; - end(data: any): this; - } - export class Transform extends Duplex { - _transformState: TransformState; - _transform(data: any, cb: any): void; - _flush(cb: any): void; - } - export class PassThrough extends Transform { - } - const _default: typeof Stream & { - web: typeof web; - Readable: typeof Readable; - Writable: typeof Writable; - Duplex: typeof Duplex; - Transform: typeof Transform; - PassThrough: typeof PassThrough; - pipeline: typeof pipeline & { - [x: symbol]: typeof pipelinePromise; - }; - }; - export default _default; - import web from "socket:stream/web"; - import { EventEmitter } from "socket:events"; -} - -declare module "socket:tty" { - export function WriteStream(fd: any): Writable; - export function ReadStream(fd: any): Readable; - export function isatty(fd: any): boolean; - namespace _default { - export { WriteStream }; - export { ReadStream }; - export { isatty }; - } - export default _default; - import { Writable } from "socket:stream"; - import { Readable } from "socket:stream"; -} - -declare module "socket:process" { - /** - * Adds callback to the 'nextTick' queue. - * @param {Function} callback - */ - export function nextTick(callback: Function): void; +declare module "socket:diagnostics/channels" { /** - * Computed high resolution time as a `BigInt`. - * @param {Array?} [time] - * @return {bigint} + * Normalizes a channel name to lower case replacing white space, + * hyphens (-), underscores (_), with dots (.). + * @ignore */ - export function hrtime(time?: Array | null): bigint; - export namespace hrtime { - function bigint(): any; - } + export function normalizeName(group: any, name: any): string; /** - * @param {number=} [code=0] - The exit code. Default: 0. + * Used to preallocate a minimum sized array of subscribers for + * a channel. + * @ignore */ - export function exit(code?: number | undefined): Promise; + export const MIN_CHANNEL_SUBSCRIBER_SIZE: 64; /** - * Returns an object describing the memory usage of the Node.js process measured in bytes. - * @returns {Object} + * A general interface for diagnostic channels that can be subscribed to. */ - export function memoryUsage(): any; - export namespace memoryUsage { - function rss(): any; - } - export class ProcessEnvironmentEvent extends Event { - constructor(type: any, key: any, value: any); - key: any; - value: any; - } - export class ProcessEnvironment extends EventTarget { - get [Symbol.toStringTag](): string; - } - export const env: any; - export default process; - const process: any; -} - -declare module "socket:url/urlpattern/urlpattern" { - export { me as URLPattern }; - var me: { - new (t: {}, r: any, n: any): { - "__#21@#i": any; - "__#21@#n": {}; - "__#21@#t": {}; - "__#21@#e": {}; - "__#21@#s": {}; - "__#21@#l": boolean; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - readonly hasRegExpGroups: boolean; - }; - compareComponent(t: any, r: any, n: any): number; - }; -} - -declare module "socket:url/url/url" { - const _default: any; - export default _default; -} - -declare module "socket:querystring" { - export function unescapeBuffer(s: any, decodeSpaces: any): any; - export function unescape(s: any, decodeSpaces: any): any; - export function escape(str: any): any; - export function stringify(obj: any, sep: any, eq: any, options: any): string; - export function parse(qs: any, sep: any, eq: any, options: any): {}; - export function decode(qs: any, sep: any, eq: any, options: any): {}; - export function encode(obj: any, sep: any, eq: any, options: any): string; - namespace _default { - export { decode }; - export { encode }; - export { parse }; - export { stringify }; - export { escape }; - export { unescape }; - } - export default _default; -} - -declare module "socket:url/index" { - export function parse(input: any, options?: any): { - hash: any; - host: any; - hostname: any; - origin: any; - auth: string; - password: any; - pathname: any; - path: any; - port: any; - protocol: any; - search: any; - searchParams: any; - username: any; - [Symbol.toStringTag]: string; - }; - export function resolve(from: any, to: any): any; - export function format(input: any): any; - export function fileURLToPath(url: any): any; - const URLPattern_base: { - new (t: {}, r: any, n: any): { - "__#21@#i": any; - "__#21@#n": {}; - "__#21@#t": {}; - "__#21@#e": {}; - "__#21@#s": {}; - "__#21@#l": boolean; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - readonly hasRegExpGroups: boolean; - }; - compareComponent(t: any, r: any, n: any): number; - }; - export class URLPattern extends URLPattern_base { - } - export const protocols: Set; - export default URL; - export class URL { - private constructor(); - } - export const URLSearchParams: any; - export const parseURL: any; -} - -declare module "socket:url" { - export * from "socket:url/index"; - export default URL; - import URL from "socket:url/index"; -} - -declare module "socket:location" { - export class Location { - get url(): URL; - get protocol(): string; - get host(): string; - get hostname(): string; - get port(): string; - get pathname(): string; - get search(): string; - get origin(): string; - get href(): string; - get hash(): string; - toString(): string; - } - const _default: Location; - export default _default; -} - -declare module "socket:path/path" { - /** - * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. - * @param {strig} ...paths - * @returns {string} - * @see {@link https://nodejs.org/api/path.html#path_path_resolve_paths} - */ - export function resolve(options: any, ...components: any[]): string; - /** - * Computes current working directory for a path - * @param {object=} [opts] - * @param {boolean=} [opts.posix] Set to `true` to force POSIX style path - * @return {string} - */ - export function cwd(opts?: object | undefined): string; - /** - * Computed location origin. Defaults to `socket:///` if not available. - * @return {string} - */ - export function origin(): string; - /** - * Computes the relative path from `from` to `to`. - * @param {object} options - * @param {PathComponent} from - * @param {PathComponent} to - * @return {string} - */ - export function relative(options: object, from: PathComponent, to: PathComponent): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {object} options - * @param {...PathComponent} components - * @return {string} - */ - export function join(options: object, ...components: PathComponent[]): string; - /** - * Computes directory name of path. - * @param {object} options - * @param {...PathComponent} components - * @return {string} - */ - export function dirname(options: object, path: any): string; - /** - * Computes base name of path. - * @param {object} options - * @param {...PathComponent} components - * @return {string} - */ - export function basename(options: object, path: any): string; - /** - * Computes extension name of path. - * @param {object} options - * @param {PathComponent} path - * @return {string} - */ - export function extname(options: object, path: PathComponent): string; - /** - * Computes normalized path - * @param {object} options - * @param {PathComponent} path - * @return {string} - */ - export function normalize(options: object, path: PathComponent): string; - /** - * Formats `Path` object into a string. - * @param {object} options - * @param {object|Path} path - * @return {string} - */ - export function format(options: object, path: object | Path): string; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {object} - */ - export function parse(path: PathComponent): object; - /** - * @typedef {(string|Path|URL|{ pathname: string }|{ url: string)} PathComponent - */ - /** - * A container for a parsed Path. - */ - export class Path { - /** - * Creates a `Path` instance from `input` and optional `cwd`. - * @param {PathComponent} input - * @param {string} [cwd] - */ - static from(input: PathComponent, cwd?: string): any; + export class Channel { + constructor(name: any); + name: any; + group: any; /** - * `Path` class constructor. - * @protected - * @param {string} pathname - * @param {string} [cwd = Path.cwd()] + * Computed subscribers for all channels in this group. + * @type {Array} */ - protected constructor(); - pattern: URLPattern; - url: any; - get pathname(): any; - get protocol(): any; - get href(): any; + get subscribers(): Function[]; /** - * `true` if the path is relative, otherwise `false. - * @type {boolean} + * Accessor for determining if channel has subscribers. This + * is always `false` for `Channel instances and `true` for `ActiveChannel` + * instances. */ - get isRelative(): boolean; + get hasSubscribers(): boolean; /** - * The working value of this path. + * Computed number of subscribers for this channel. */ - get value(): any; + get length(): number; /** - * The original source, unresolved. - * @type {string} + * Resets channel state. + * @param {(boolean)} [shouldOrphan = false] */ - get source(): string; + reset(shouldOrphan?: (boolean)): void; + channel(name: any): Channel; /** - * Computed parent path. - * @type {string} + * Adds an `onMessage` subscription callback to the channel. + * @return {boolean} */ - get parent(): string; + subscribe(_: any, onMessage: any): boolean; /** - * Computed root in path. - * @type {string} + * Removes an `onMessage` subscription callback from the channel. + * @param {function} onMessage + * @return {boolean} */ - get root(): string; + unsubscribe(_: any, onMessage: Function): boolean; /** - * Computed directory name in path. - * @type {string} + * A no-op for `Channel` instances. This function always returns `false`. + * @param {string|object} name + * @param {object=} [message] + * @return Promise */ - get dir(): string; + publish(name: string | object, message?: object | undefined): Promise; /** - * Computed base name in path. - * @type {string} + * Returns a string representation of the `ChannelRegistry`. + * @ignore */ - get base(): string; + toString(): any; /** - * Computed base name in path without path extension. - * @type {string} + * Iterator interface + * @ignore */ - get name(): string; + get [Symbol.iterator](): any[]; /** - * Computed extension name in path. - * @type {string} + * The `Channel` string tag. + * @ignore */ - get ext(): string; + [Symbol.toStringTag](): string; + #private; + } + /** + * An `ActiveChannel` is a prototype implementation for a `Channel` + * that provides an interface what is considered an "active" channel. The + * `hasSubscribers` accessor always returns `true` for this class. + */ + export class ActiveChannel extends Channel { + unsubscribe(onMessage: any): boolean; /** - * The computed drive, if given in the path. - * @type {string?} + * @param {object|any} message + * @return Promise */ - get drive(): string; + publish(message: object | any): Promise; + } + /** + * A container for a grouping of channels that are named and owned + * by this group. A `ChannelGroup` can also be a regular channel. + */ + export class ChannelGroup extends Channel { /** - * @return {URL} + * @param {Array} channels + * @param {string} name */ - toURL(): URL; + constructor(name: string, channels: Array); + channels: Channel[]; /** - * Converts this `Path` instance to a string. - * @return {string} + * Subscribe to a channel or selection of channels in this group. + * @param {string} name + * @return {boolean} */ - toString(): string; + subscribe(name: string, onMessage: any): boolean; /** - * @ignore + * Unsubscribe from a channel or selection of channels in this group. + * @param {string} name + * @return {boolean} */ - inspect(): { - root: string; - dir: string; - base: string; - ext: string; + unsubscribe(name: string, onMessage: any): boolean; + /** + * Gets or creates a channel for this group. + * @param {string} name + * @return {Channel} + */ + channel(name: string): Channel; + /** + * Select a test of channels from this group. + * The following syntax is supported: + * - One Channel: `group.channel` + * - All Channels: `*` + * - Many Channel: `group.*` + * - Collections: `['group.a', 'group.b', 'group.c'] or `group.a,group.b,group.c` + * @param {string|Array} keys + * @param {(boolean)} [hasSubscribers = false] - Enforce subscribers in selection + * @return {Array<{name: string, channel: Channel}>} + */ + select(keys: string | Array, hasSubscribers?: (boolean)): Array<{ name: string; - }; + channel: Channel; + }>; + } + /** + * An object mapping of named channels to `WeakRef` instances. + */ + export const registry: { + /** + * Subscribes callback `onMessage` to channel of `name`. + * @param {string} name + * @param {function} onMessage + * @return {boolean} + */ + subscribe(name: string, onMessage: Function): boolean; + /** + * Unsubscribes callback `onMessage` from channel of `name`. + * @param {string} name + * @param {function} onMessage + * @return {boolean} + */ + unsubscribe(name: string, onMessage: Function): boolean; + /** + * Predicate to determine if a named channel has subscribers. + * @param {string} name + */ + hasSubscribers(name: string): boolean; + /** + * Get or set a channel by `name`. + * @param {string} name + * @return {Channel} + */ + channel(name: string): Channel; + /** + * Creates a `ChannelGroup` for a set of channels + * @param {string} name + * @param {Array} [channels] + * @return {ChannelGroup} + */ + group(name: string, channels?: Array): ChannelGroup; + /** + * Get a channel by name. The name is normalized. + * @param {string} name + * @return {Channel?} + */ + get(name: string): Channel | null; + /** + * Checks if a channel is known by name. The name is normalized. + * @param {string} name + * @return {boolean} + */ + has(name: string): boolean; + /** + * Set a channel by name. The name is normalized. + * @param {string} name + * @param {Channel} channel + * @return {Channel?} + */ + set(name: string, channel: Channel): Channel | null; + /** + * Removes a channel by `name` + * @return {boolean} + */ + remove(name: any): boolean; /** + * Returns a string representation of the `ChannelRegistry`. + * @ignore + */ + toString(): any; + /** + * Returns a JSON representation of the `ChannelRegistry`. + * @return {object} + */ + toJSON(): object; + /** + * The `ChannelRegistry` string tag. * @ignore */ [Symbol.toStringTag](): string; - #private; + }; + export default registry; +} + +declare module "socket:diagnostics/metric" { + export class Metric { + init(): void; + update(value: any): void; + destroy(): void; + toJSON(): {}; + toString(): string; + [Symbol.iterator](): any; + [Symbol.toStringTag](): string; } - export default Path; - export type PathComponent = (string | Path | URL | { - pathname: string; - } | { - url: string; - }); - import { URLPattern } from "socket:url/index"; - import { URL } from "socket:url/index"; + export default Metric; } -declare module "socket:path/mounts" { - const _default: {}; +declare module "socket:diagnostics/window" { + export class RequestAnimationFrameMetric extends Metric { + constructor(options: any); + originalRequestAnimationFrame: typeof requestAnimationFrame; + requestAnimationFrame(callback: any): any; + sampleSize: any; + sampleTick: number; + channel: import("socket:diagnostics/channels").Channel; + value: { + rate: number; + samples: number; + }; + now: number; + samples: Uint8Array; + toJSON(): { + sampleSize: any; + sampleTick: number; + samples: number[]; + rate: number; + now: number; + }; + } + export class FetchMetric extends Metric { + constructor(options: any); + originalFetch: typeof fetch; + channel: import("socket:diagnostics/channels").Channel; + fetch(resource: any, options: any, extra: any): Promise; + } + export class XMLHttpRequestMetric extends Metric { + constructor(options: any); + channel: import("socket:diagnostics/channels").Channel; + patched: { + open: { + (method: string, url: string | URL): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + }; + send: (body?: Document | XMLHttpRequestBodyInit) => void; + }; + } + export class WorkerMetric extends Metric { + constructor(options: any); + GlobalWorker: { + new (scriptURL: string | URL, options?: WorkerOptions): Worker; + prototype: Worker; + }; + channel: import("socket:diagnostics/channels").Channel; + Worker: { + new (url: any, options: any, ...args: any[]): { + onmessage: (this: Worker, ev: MessageEvent) => any; + onmessageerror: (this: Worker, ev: MessageEvent) => any; + postMessage(message: any, transfer: Transferable[]): void; + postMessage(message: any, options?: StructuredSerializeOptions): void; + terminate(): void; + addEventListener(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K_1, listener: (this: Worker, ev: WorkerEventMap[K_1]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + dispatchEvent(event: Event): boolean; + onerror: (this: AbstractWorker, ev: ErrorEvent) => any; + }; + }; + } + export const metrics: { + requestAnimationFrame: RequestAnimationFrameMetric; + XMLHttpRequest: XMLHttpRequestMetric; + Worker: WorkerMetric; + fetch: FetchMetric; + channel: import("socket:diagnostics/channels").ChannelGroup; + subscribe(...args: any[]): boolean; + unsubscribe(...args: any[]): boolean; + start(which: any): void; + stop(which: any): void; + }; + namespace _default { + export { metrics }; + } export default _default; + import { Metric } from "socket:diagnostics/metric"; } -declare module "socket:path/win32" { - /** - * Computes current working directory for a path - * @param {string} - */ - export function cwd(): any; +declare module "socket:diagnostics/index" { /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} + * @param {string} name + * @return {import('./channels.js').Channel} */ - export function resolve(...components: PathComponent[]): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} + export function channel(name: string): import("socket:diagnostics/channels").Channel; + export default exports; + import * as exports from "socket:diagnostics/index"; + import channels from "socket:diagnostics/channels"; + import window from "socket:diagnostics/window"; + + export { channels, window }; +} + +declare module "socket:diagnostics" { + export * from "socket:diagnostics/index"; + export default exports; + import * as exports from "socket:diagnostics/index"; +} + +declare module "socket:internal/symbols" { + export const dispose: any; + export const serialize: any; + namespace _default { + export { dispose }; + export { serialize }; + } + export default _default; +} + +declare module "socket:gc" { + /** + * Track `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when + * environment garbage collects object. + * @param {object} object + * @return {boolean} */ - export function join(...components: PathComponent[]): string; + export function ref(object: object, ...args: any[]): boolean; /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} + * Stop tracking `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when + * environment garbage collects object. + * @param {object} object + * @return {boolean} */ - export function dirname(path: PathComponent): string; + export function unref(object: object): boolean; /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string=} [suffix] - * @return {string} + * An alias for `unref()` + * @param {object} object} + * @return {boolean} */ - export function basename(path: PathComponent, suffix?: string | undefined): string; + export function retain(object: object): boolean; /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} + * Call finalize on `object` for `gc.finalizer` implementation. + * @param {object} object] + * @return {Promise} */ - export function extname(path: PathComponent): string; + export function finalize(object: object, ...args: any[]): Promise; /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} + * Calls all pending finalization handlers forcefully. This function + * may have unintended consequences as objects be considered finalized + * and still strongly held (retained) somewhere. */ - export function isAbsolute(path: PathComponent): boolean; + export function release(): Promise; + export const finalizers: WeakMap; + export const kFinalizer: unique symbol; + export const finalizer: symbol; /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} + * @type {Set} */ - export function parse(path: PathComponent): Path; + export const pool: Set>; /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} + * Static registry for objects to clean up underlying resources when they + * are gc'd by the environment. There is no guarantee that the `finalizer()` + * is called at any time. */ - export function format(path: object | Path): string; + export const registry: FinalizationRegistry; /** - * Normalizes `path` resolving `..` and `.\` preserving trailing - * slashes. - * @param {string} path + * Default exports which also acts a retained value to persist bound + * `Finalizer#handle()` functions from being gc'd before the + * `FinalizationRegistry` callback is called because `heldValue` must be + * strongly held (retained) in order for the callback to be called. */ - export function normalize(path: string): any; + export const gc: any; + export default gc; /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} + * A container for strongly (retain) referenced finalizer function + * with arguments weakly referenced to an object that will be + * garbage collected. */ - export function relative(from: string, to: string): string; - export default exports; - export namespace win32 { - let sep: "\\"; - let delimiter: ";"; + export class Finalizer { + /** + * Creates a `Finalizer` from input. + */ + static from(handler: any): Finalizer; + /** + * `Finalizer` class constructor. + * @private + * @param {array} args + * @param {function} handle + */ + private constructor(); + args: any[]; + handle: any; } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as mounts from "socket:path/mounts"; - import * as posix from "socket:path/posix"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/win32"; - - export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; } -declare module "socket:path/posix" { - /** - * Computes current working directory for a path - * @param {string} - * @return {string} - */ - export function cwd(): string; +declare module "socket:internal/async/hooks" { + export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + export function getNextAsyncResourceId(): number; + export function executionAsyncResource(): any; + export function executionAsyncId(): any; + export function triggerAsyncId(): any; + export function getDefaultExecutionAsyncId(): any; + export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; + export function getTopLevelAsyncResourceName(): any; /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} + * The default top level async resource ID + * @type {number} */ - export function resolve(...components: PathComponent[]): string; + export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; + export namespace state { + let defaultExecutionAsyncId: number; + } + export namespace hooks { + let init: any[]; + let before: any[]; + let after: any[]; + let destroy: any[]; + let promiseResolve: any[]; + } /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} + * A base class for the `AsyncResource` class or other higher level async + * resource classes. */ - export function join(...components: PathComponent[]): string; + export class CoreAsyncResource { + /** + * `CoreAsyncResource` class constructor. + * @param {string} type + * @param {object|number=} [options] + */ + constructor(type: string, options?: (object | number) | undefined); + /** + * The `CoreAsyncResource` type. + * @type {string} + */ + get type(): string; + /** + * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This + * value is only set to `true` if `emitDestroy()` was called, likely from + * destroying the resource manually. + * @type {boolean} + */ + get destroyed(): boolean; + /** + * The unique async resource ID. + * @return {number} + */ + asyncId(): number; + /** + * The trigger async resource ID. + * @return {number} + */ + triggerAsyncId(): number; + /** + * Manually emits destroy hook for the resource. + * @return {CoreAsyncResource} + */ + emitDestroy(): CoreAsyncResource; + /** + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @return {function} + */ + bind(fn: Function, thisArg?: object | undefined): Function; + /** + * Runs function `fn` in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @param {...any} [args] + * @return {any} + */ + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + #private; + } + export class TopLevelAsyncResource extends CoreAsyncResource { + } + export const asyncContextVariable: Variable; + export const topLevelAsyncResource: TopLevelAsyncResource; + export default hooks; + import { Variable } from "socket:async/context"; +} + +declare module "socket:async/resource" { /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} + * @typedef {{ + * triggerAsyncId?: number, + * requireManualDestroy?: boolean + * }} AsyncResourceOptions */ - export function dirname(path: PathComponent): string; /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string=} [suffix] - * @return {string} + * A container that should be extended that represents a resource with + * an asynchronous execution context. */ - export function basename(path: PathComponent, suffix?: string | undefined): string; - /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} - */ - export function extname(path: PathComponent): string; - /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} - */ - export function isAbsolute(path: PathComponent): boolean; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} - */ - export function parse(path: PathComponent): Path; - /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} - */ - export function format(path: object | Path): string; - /** - * Normalizes `path` resolving `..` and `./` preserving trailing - * slashes. - * @param {string} path - */ - export function normalize(path: string): any; - /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} - */ - export function relative(from: string, to: string): string; - export default exports; - export namespace posix { - let sep: "/"; - let delimiter: ":"; + export class AsyncResource extends CoreAsyncResource { + /** + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of an anonymous `AsyncResource`. + * @param {function} fn + * @param {object|string=} [type] + * @param {object=} [thisArg] + * @return {function} + */ + static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; + /** + * `AsyncResource` class constructor. + * @param {string} type + * @param {AsyncResourceOptions|number=} [options] + */ + constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as mounts from "socket:path/mounts"; - import * as win32 from "socket:path/win32"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/posix"; - - export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path/index" { - export default exports; - import * as mounts from "socket:path/mounts"; - import * as posix from "socket:path/posix"; - import * as win32 from "socket:path/win32"; - import { Path } from "socket:path/path"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/index"; - - export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path" { - export const sep: "\\" | "/"; - export const delimiter: ":" | ";"; - export const resolve: typeof posix.win32.resolve; - export const join: typeof posix.win32.join; - export const dirname: typeof posix.win32.dirname; - export const basename: typeof posix.win32.basename; - export const extname: typeof posix.win32.extname; - export const cwd: typeof posix.win32.cwd; - export const isAbsolute: typeof posix.win32.isAbsolute; - export const parse: typeof posix.win32.parse; - export const format: typeof posix.win32.format; - export const normalize: typeof posix.win32.normalize; - export const relative: typeof posix.win32.relative; - const _default: typeof posix | typeof posix.win32; - export default _default; - import { posix } from "socket:path/index"; - import { Path } from "socket:path/index"; - import { win32 } from "socket:path/index"; - import { mounts } from "socket:path/index"; - import { DOWNLOADS } from "socket:path/index"; - import { DOCUMENTS } from "socket:path/index"; - import { RESOURCES } from "socket:path/index"; - import { PICTURES } from "socket:path/index"; - import { DESKTOP } from "socket:path/index"; - import { VIDEOS } from "socket:path/index"; - import { CONFIG } from "socket:path/index"; - import { MUSIC } from "socket:path/index"; - import { HOME } from "socket:path/index"; - import { DATA } from "socket:path/index"; - import { LOG } from "socket:path/index"; - import { TMP } from "socket:path/index"; - export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export default AsyncResource; + export type AsyncResourceOptions = { + triggerAsyncId?: number; + requireManualDestroy?: boolean; + }; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + import { CoreAsyncResource } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; } -declare module "socket:fs/stream" { - export const DEFAULT_STREAM_HIGH_WATER_MARK: number; +declare module "socket:async/hooks" { /** - * @typedef {import('./handle.js').FileHandle} FileHandle + * Factory for creating a `AsyncHook` instance. + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] + * @return {AsyncHook} */ + export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; /** - * A `Readable` stream for a `FileHandle`. + * A container for `AsyncHooks` callbacks. + * @ignore */ - export class ReadStream extends Readable { - end: any; - start: any; - handle: any; - buffer: ArrayBuffer; - signal: any; - timeout: any; - bytesRead: number; - shouldEmitClose: boolean; + export class AsyncHookCallbacks { /** - * Sets file handle for the ReadStream. - * @param {FileHandle} handle + * `AsyncHookCallbacks` class constructor. + * @ignore + * @param {AsyncHookCallbackOptions} [options] */ - setHandle(handle: FileHandle): void; + constructor(options?: AsyncHookCallbackOptions); + init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + before(asyncId: any): void; + after(asyncId: any): void; + destroy(asyncId: any): void; + promiseResolve(asyncId: any): void; + } + /** + * A container for registering various callbacks for async resource hooks. + */ + export class AsyncHook { /** - * The max buffer size for the ReadStream. + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] */ - get highWaterMark(): number; + constructor(callbacks?: any); /** - * Relative or absolute path of the underlying `FileHandle`. + * @type {boolean} */ - get path(): any; + get enabled(): boolean; /** - * `true` if the stream is in a pending state. + * Enable the async hook. + * @return {AsyncHook} */ - get pending(): boolean; - _open(callback: any): Promise; - _read(callback: any): Promise; - } - export namespace ReadStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + enable(): AsyncHook; + /** + * Disables the async hook + * @return {AsyncHook} + */ + disable(): AsyncHook; + #private; } + export default createHook; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; +} + +declare module "socket:async/storage" { /** - * A `Writable` stream for a `FileHandle`. + * A container for storing values that remain present during + * asynchronous operations. */ - export class WriteStream extends Writable { - start: any; - handle: any; - signal: any; - timeout: any; - bytesWritten: number; - shouldEmitClose: boolean; + export class AsyncLocalStorage { /** - * Sets file handle for the WriteStream. - * @param {FileHandle} handle + * Binds function `fn` to run in the execution context of an + * anonymous `AsyncResource`. + * @param {function} fn + * @return {function} */ - setHandle(handle: FileHandle): void; + static bind(fn: Function): Function; /** - * The max buffer size for the Writetream. + * Captures the current async context and returns a function that runs + * a function in that execution context. + * @return {function} */ - get highWaterMark(): number; + static snapshot(): Function; /** - * Relative or absolute path of the underlying `FileHandle`. + * @type {boolean} */ - get path(): any; + get enabled(): boolean; /** - * `true` if the stream is in a pending state. + * Disables the `AsyncLocalStorage` instance. When disabled, + * `getStore()` will always return `undefined`. */ - get pending(): boolean; - _open(callback: any): Promise; - _write(buffer: any, callback: any): any; - } - export namespace WriteStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; - } - export const FileReadStream: typeof exports.ReadStream; - export const FileWriteStream: typeof exports.WriteStream; - export default exports; - export type FileHandle = import("socket:fs/handle").FileHandle; - import { Readable } from "socket:stream"; - import { Writable } from "socket:stream"; - import * as exports from "socket:fs/stream"; - + disable(): void; + /** + * Enables the `AsyncLocalStorage` instance. + */ + enable(): void; + /** + * Enables and sets the `AsyncLocalStorage` instance default store value. + * @param {any} store + */ + enterWith(store: any): void; + /** + * Runs function `fn` in the current asynchronous execution context with + * a given `store` value and arguments given to `fn`. + * @param {any} store + * @param {function} fn + * @param {...any} args + * @return {any} + */ + run(store: any, fn: Function, ...args: any[]): any; + exit(fn: any, ...args: any[]): any; + /** + * If the `AsyncLocalStorage` instance is enabled, it returns the current + * store value for this asynchronous execution context. + * @return {any|undefined} + */ + getStore(): any | undefined; + #private; + } + export default AsyncLocalStorage; } -declare module "socket:fs/constants" { - /** - * This flag can be used with uv_fs_copyfile() to return an error if the - * destination already exists. - */ - export const COPYFILE_EXCL: 1; - /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, a fallback copy mechanism is used. - */ - export const COPYFILE_FICLONE: 2; +declare module "socket:async/deferred" { /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, an error is returned. + * Dispatched when a `Deferred` internal promise is resolved. */ - export const COPYFILE_FICLONE_FORCE: 4; - export const UV_DIRENT_UNKNOWN: any; - export const UV_DIRENT_FILE: any; - export const UV_DIRENT_DIR: any; - export const UV_DIRENT_LINK: any; - export const UV_DIRENT_FIFO: any; - export const UV_DIRENT_SOCKET: any; - export const UV_DIRENT_CHAR: any; - export const UV_DIRENT_BLOCK: any; - export const UV_FS_SYMLINK_DIR: any; - export const UV_FS_SYMLINK_JUNCTION: any; - export const O_RDONLY: any; - export const O_WRONLY: any; - export const O_RDWR: any; - export const O_APPEND: any; - export const O_ASYNC: any; - export const O_CLOEXEC: any; - export const O_CREAT: any; - export const O_DIRECT: any; - export const O_DIRECTORY: any; - export const O_DSYNC: any; - export const O_EXCL: any; - export const O_LARGEFILE: any; - export const O_NOATIME: any; - export const O_NOCTTY: any; - export const O_NOFOLLOW: any; - export const O_NONBLOCK: any; - export const O_NDELAY: any; - export const O_PATH: any; - export const O_SYNC: any; - export const O_TMPFILE: any; - export const O_TRUNC: any; - export const S_IFMT: any; - export const S_IFREG: any; - export const S_IFDIR: any; - export const S_IFCHR: any; - export const S_IFBLK: any; - export const S_IFIFO: any; - export const S_IFLNK: any; - export const S_IFSOCK: any; - export const S_IRWXU: any; - export const S_IRUSR: any; - export const S_IWUSR: any; - export const S_IXUSR: any; - export const S_IRWXG: any; - export const S_IRGRP: any; - export const S_IWGRP: any; - export const S_IXGRP: any; - export const S_IRWXO: any; - export const S_IROTH: any; - export const S_IWOTH: any; - export const S_IXOTH: any; - export const F_OK: any; - export const R_OK: any; - export const W_OK: any; - export const X_OK: any; - export default exports; - import * as exports from "socket:fs/constants"; - -} - -declare module "socket:fs/flags" { - export function normalizeFlags(flags: any): any; - export default exports; - import * as exports from "socket:fs/flags"; - -} - -declare module "socket:fs/stats" { + export class DeferredResolveEvent extends Event { + /** + * `DeferredResolveEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {any=} [result] + */ + constructor(type?: string | undefined, result?: any | undefined); + /** + * The `Deferred` promise result value. + * @type {any?} + */ + result: any | null; + } /** - * A container for various stats about a file or directory. + * Dispatched when a `Deferred` internal promise is rejected. */ - export class Stats { + export class DeferredRejectEvent extends ErrorEvent { /** - * Creates a `Stats` instance from input, optionally with `BigInt` data types - * @param {object|Stats} [stat] - * @param {fromBigInt=} [fromBigInt = false] - * @return {Stats} + * `DeferredRejectEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {Error=} [error] */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + constructor(type?: string | undefined, error?: Error | undefined); + } + /** + * A utility class for creating deferred promises. + */ + export class Deferred extends EventTarget { /** - * `Stats` class constructor. - * @param {object|Stats} stat + * `Deferred` class constructor. + * @param {Deferred|Promise?} [promise] */ - constructor(stat: object | Stats); - dev: any; - ino: any; - mode: any; - nlink: any; - uid: any; - gid: any; - rdev: any; - size: any; - blksize: any; - blocks: any; - atimeMs: any; - mtimeMs: any; - ctimeMs: any; - birthtimeMs: any; - atime: Date; - mtime: Date; - ctime: Date; - birthtime: Date; + constructor(promise?: Deferred | (Promise | null)); /** - * Returns `true` if stats represents a directory. - * @return {Boolean} + * Function to resolve the associated promise. + * @type {function} */ - isDirectory(): boolean; + resolve: Function; /** - * Returns `true` if stats represents a file. - * @return {Boolean} + * Function to reject the associated promise. + * @type {function} */ - isFile(): boolean; + reject: Function; /** - * Returns `true` if stats represents a block device. - * @return {Boolean} + * Attaches a fulfillment callback and a rejection callback to the promise, + * and returns a new promise resolving to the return value of the called + * callback. + * @param {function(any)=} [resolve] + * @param {function(Error)=} [reject] */ - isBlockDevice(): boolean; + then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise; /** - * Returns `true` if stats represents a character device. - * @return {Boolean} + * Attaches a rejection callback to the promise, and returns a new promise + * resolving to the return value of the callback if it is called, or to its + * original fulfillment value if the promise is instead fulfilled. + * @param {function(Error)=} [callback] */ - isCharacterDevice(): boolean; + catch(callback?: ((arg0: Error) => any) | undefined): Promise; /** - * Returns `true` if stats represents a symbolic link. - * @return {Boolean} + * Attaches a callback for when the promise is settled (fulfilled or rejected). + * @param {function(any?)} [callback] */ - isSymbolicLink(): boolean; + finally(callback?: (arg0: any | null) => any): Promise; /** - * Returns `true` if stats represents a FIFO. - * @return {Boolean} + * The promise associated with this Deferred instance. + * @type {Promise} */ - isFIFO(): boolean; + get promise(): Promise; /** - * Returns `true` if stats represents a socket. - * @return {Boolean} + * A string representation of this Deferred instance. + * @type {string} + * @ignore */ - isSocket(): boolean; + get [Symbol.toStringTag](): string; + #private; } + export default Deferred; +} + +declare module "socket:async" { export default exports; - import * as exports from "socket:fs/stats"; + import AsyncLocalStorage from "socket:async/storage"; + import AsyncResource from "socket:async/resource"; + import AsyncContext from "socket:async/context"; + import Deferred from "socket:async/deferred"; + import { executionAsyncResource } from "socket:async/hooks"; + import { executionAsyncId } from "socket:async/hooks"; + import { triggerAsyncId } from "socket:async/hooks"; + import { createHook } from "socket:async/hooks"; + import { AsyncHook } from "socket:async/hooks"; + import * as exports from "socket:async"; + export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; } -declare module "socket:fs/fds" { - const _default: { - types: Map; - fds: Map; - ids: Map; - readonly size: number; - get(id: any): any; - syncOpenDescriptors(): Promise; - set(id: any, fd: any, type: any): void; - has(id: any): boolean; - fd(id: any): any; - id(fd: any): any; - release(id: any, closeDescriptor?: boolean): Promise; - retain(id: any): Promise; - delete(id: any): void; - clear(): void; - typeof(id: any): any; - entries(): IterableIterator<[any, any]>; - }; - export default _default; -} - -declare module "socket:fs/handle" { - export const kOpening: unique symbol; - export const kClosing: unique symbol; - export const kClosed: unique symbol; +declare module "socket:application/menu" { /** - * A container for a descriptor tracked in `fds` and opened in the native layer. - * This class implements the Node.js `FileHandle` interface - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#class-filehandle} + * Internal IPC for setting an application menu + * @ignore */ - export class FileHandle extends EventEmitter { - static get DEFAULT_ACCESS_MODE(): any; - static get DEFAULT_OPEN_FLAGS(): string; - static get DEFAULT_OPEN_MODE(): number; + export function setMenu(options: any, type: any): Promise; + /** + * Internal IPC for setting an application context menu + * @ignore + */ + export function setContextMenu(options: any): Promise; + /** + * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + */ + export class Menu extends EventTarget { /** - * Creates a `FileHandle` from a given `id` or `fd` - * @param {string|number|FileHandle|object} id - * @return {FileHandle} + * `Menu` class constructor. + * @ignore + * @param {string} type */ - static from(id: string | number | FileHandle | object): FileHandle; + constructor(type: string); /** - * Determines if access to `path` for `mode` is possible. - * @param {string} path - * @param {number} [mode = 0o666] - * @param {object=} [options] - * @return {Promise} + * The broadcast channel for this menu. + * @ignore + * @type {BroadcastChannel} */ - static access(path: string, mode?: number, options?: object | undefined): Promise; + get channel(): BroadcastChannel; /** - * Asynchronously open a file. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesopenpath-flags-mode} - * @param {string | Buffer | URL} path - * @param {string=} [flags = 'r'] - * @param {string|number=} [mode = 0o666] - * @param {object=} [options] - * @return {Promise} + * The `Menu` instance type. + * @type {('context'|'system'|'tray')?} */ - static open(path: string | Buffer | URL, flags?: string | undefined, mode?: (string | number) | undefined, options?: object | undefined): Promise; + get type(): "tray" | "system" | "context"; /** - * `FileHandle` class constructor + * Setter for the level 1 'error'` event listener. * @ignore - * @param {object} options - */ - constructor(options: object); - flags: any; - path: any; - mode: any; - id: string; - fd: any; - /** - * `true` if the `FileHandle` instance has been opened. - * @type {boolean} + * @type {function(ErrorEvent)?} */ - get opened(): boolean; + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * `true` if the `FileHandle` is opening. - * @type {boolean} + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - get opening(): boolean; + get onerror(): (arg0: ErrorEvent) => any; /** - * `true` if the `FileHandle` is closing. - * @type {boolean} + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} */ - get closing(): boolean; + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); /** - * `true` if the `FileHandle` is closed. + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} */ - get closed(): boolean; + get onmenuitem(): (arg0: menuitemEvent) => any; /** - * Appends to a file, if handle was opened with `O_APPEND`, otherwise this - * method is just an alias to `FileHandle#writeFile()`. - * @param {string|Buffer|TypedArray|Array} data + * Set the menu layout for this `Menu` instance. + * @param {string|object} layoutOrOptions * @param {object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {object=} [options.signal] */ - appendFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise; + set(layoutOrOptions: string | object, options?: object | undefined): Promise; + #private; + } + /** + * A container for various `Menu` instances. + */ + export class MenuContainer extends EventTarget { /** - * Change permissions of file handle. - * @param {number} mode + * `MenuContainer` class constructor. + * @param {EventTarget} [sourceEventTarget] * @param {object=} [options] */ - chmod(mode: number, options?: object | undefined): Promise; + constructor(sourceEventTarget?: EventTarget, options?: object | undefined); /** - * Change ownership of file handle. - * @param {number} uid - * @param {number} gid - * @param {object=} [options] + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} */ - chown(uid: number, gid: number, options?: object | undefined): Promise; + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * Close underlying file handle - * @param {object=} [options] + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - close(options?: object | undefined): Promise; + get onerror(): (arg0: ErrorEvent) => any; /** - * Creates a `ReadStream` for the underlying file. - * @param {object=} [options] - An options object + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} */ - createReadStream(options?: object | undefined): ReadStream; + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); /** - * Creates a `WriteStream` for the underlying file. - * @param {object=} [options] - An options object + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} */ - createWriteStream(options?: object | undefined): WriteStream; + get onmenuitem(): (arg0: menuitemEvent) => any; /** - * @param {object=} [options] + * The `TrayMenu` instance for the application. + * @type {TrayMenu} */ - datasync(): Promise; + get tray(): TrayMenu; /** - * Opens the underlying descriptor for the file handle. - * @param {object=} [options] + * The `SystemMenu` instance for the application. + * @type {SystemMenu} */ - open(options?: object | undefined): Promise; + get system(): SystemMenu; /** - * Reads `length` bytes starting from `position` into `buffer` at - * `offset`. - * @param {Buffer|object} buffer - * @param {number=} [offset] - * @param {number=} [length] - * @param {number=} [position] - * @param {object=} [options] + * The `ContextMenu` instance for the application. + * @type {ContextMenu} */ - read(buffer: Buffer | object, offset?: number | undefined, length?: number | undefined, position?: number | undefined, options?: object | undefined): Promise<{ - bytesRead: number; - buffer: any; - }>; + get context(): ContextMenu; + #private; + } + /** + * A `Menu` instance that represents a context menu. + */ + export class ContextMenu extends Menu { + constructor(); + } + /** + * A `Menu` instance that represents the system menu. + */ + export class SystemMenu extends Menu { + constructor(); + } + /** + * A `Menu` instance that represents the tray menu. + */ + export class TrayMenu extends Menu { + constructor(); + } + /** + * The application tray menu. + * @type {TrayMenu} + */ + export const tray: TrayMenu; + /** + * The application system menu. + * @type {SystemMenu} + */ + export const system: SystemMenu; + /** + * The application context menu. + * @type {ContextMenu} + */ + export const context: ContextMenu; + /** + * The application menus container. + * @type {MenuContainer} + */ + export const container: MenuContainer; + export default container; + import ipc from "socket:ipc"; +} + +declare module "socket:internal/events" { + /** + * An event dispatched when an application URL is opening the application. + */ + export class ApplicationURLEvent extends Event { /** - * Reads the entire contents of a file and returns it as a buffer or a string - * specified of a given encoding specified at `options.encoding`. + * `ApplicationURLEvent` class constructor. + * @param {string=} [type] * @param {object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {object=} [options.signal] */ - readFile(options?: object | undefined): Promise; + constructor(type?: string | undefined, options?: object | undefined); /** - * Returns the stats of the underlying file. - * @param {object=} [options] - * @return {Promise} + * `true` if the application URL is valid (parses correctly). + * @type {boolean} */ - stat(options?: object | undefined): Promise; + get isValid(): boolean; /** - * Returns the stats of the underlying symbolic link. - * @param {object=} [options] - * @return {Promise} + * Data associated with the `ApplicationURLEvent`. + * @type {?any} */ - lstat(options?: object | undefined): Promise; + get data(): any; /** - * Synchronize a file's in-core state with storage device - * @return {Promise} + * The original source URI + * @type {?string} */ - sync(): Promise; + get source(): string; /** - * @param {number} [offset = 0] - * @return {Promise} + * The `URL` for the `ApplicationURLEvent`. + * @type {?URL} */ - truncate(offset?: number): Promise; + get url(): URL; /** - * Writes `length` bytes at `offset` in `buffer` to the underlying file - * at `position`. - * @param {Buffer|object} buffer - * @param {number} offset - * @param {number} length - * @param {number} position - * @param {object=} [options] - */ - write(buffer: Buffer | object, offset: number, length: number, position: number, options?: object | undefined): Promise<{ - buffer: any; - bytesWritten: number; - }>; - /** - * Writes `data` to file. - * @param {string|Buffer|TypedArray|Array} data - * @param {object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {object=} [options.signal] + * String tag name for an `ApplicationURLEvent` instance. + * @type {string} */ - writeFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise; - [exports.kOpening]: any; - [exports.kClosing]: any; - [exports.kClosed]: boolean; + get [Symbol.toStringTag](): string; #private; } /** - * A container for a directory handle tracked in `fds` and opened in the - * native layer. + * An event dispacted for a registered global hotkey expression. */ - export class DirectoryHandle extends EventEmitter { - /** - * The max number of entries that can be bufferd with the `bufferSize` - * option. - */ - static get MAX_BUFFER_SIZE(): number; - static get MAX_ENTRIES(): number; - /** - * The default number of entries `Dirent` that are buffered - * for each read request. - */ - static get DEFAULT_BUFFER_SIZE(): number; - /** - * Creates a `FileHandle` from a given `id` or `fd` - * @param {string|number|DirectoryHandle|object} id - * @return {DirectoryHandle} - */ - static from(id: string | number | DirectoryHandle | object): DirectoryHandle; + export class HotKeyEvent extends MessageEvent { /** - * Asynchronously open a directory. - * @param {string | Buffer | URL} path - * @param {object=} [options] - * @return {Promise} + * `HotKeyEvent` class constructor. + * @ignore + * @param {string=} [type] + * @param {object=} [data] */ - static open(path: string | Buffer | URL, options?: object | undefined): Promise; + constructor(type?: string | undefined, data?: object | undefined); /** - * `DirectoryHandle` class constructor - * @private - * @param {object} options + * The global unique ID for this hotkey binding. + * @type {number?} */ - private constructor(); - id: string; - path: any; - bufferSize: number; + get id(): number; /** - * DirectoryHandle file descriptor id + * The computed hash for this hotkey binding. + * @type {number?} */ - get fd(): string; + get hash(): number; /** - * `true` if the `DirectoryHandle` instance has been opened. - * @type {boolean} + * The normalized hotkey expression as a sequence of tokens. + * @type {string[]} */ - get opened(): boolean; + get sequence(): string[]; /** - * `true` if the `DirectoryHandle` is opening. - * @type {boolean} + * The original expression of the hotkey binding. + * @type {string?} */ - get opening(): boolean; + get expression(): string; + } + /** + * An event dispacted when a menu item is selected. + */ + export class MenuItemEvent extends MessageEvent { /** - * `true` if the `DirectoryHandle` is closing. - * @type {boolean} + * `MenuItemEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [data] + * @param {import('../application/menu.js').Menu} menu */ - get closing(): boolean; + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** - * `true` if `DirectoryHandle` is closed. + * The `Menu` this event has been dispatched for. + * @type {import('../application/menu.js').Menu?} */ - get closed(): boolean; + get menu(): import("socket:application/menu").Menu; /** - * Opens the underlying handle for a directory. - * @param {object=} options - * @return {Promise} + * The title of the menu item. + * @type {string?} */ - open(options?: object | undefined): Promise; + get title(): string; /** - * Close underlying directory handle - * @param {object=} [options] + * An optional tag value for the menu item that may also be the + * parent menu item title. + * @type {string?} */ - close(options?: object | undefined): Promise; + get tag(): string; /** - * Reads directory entries - * @param {object=} [options] - * @param {number=} [options.entries = DirectoryHandle.MAX_ENTRIES] + * The parent title of the menu item. + * @type {string?} */ - read(options?: object | undefined): Promise; - [exports.kOpening]: any; - [exports.kClosing]: any; - [exports.kClosed]: boolean; + get parent(): string; #private; } - export default exports; - export type TypedArray = Uint8Array | Int8Array; - import { EventEmitter } from "socket:events"; - import { Buffer } from "socket:buffer"; - import { ReadStream } from "socket:fs/stream"; - import { WriteStream } from "socket:fs/stream"; - import { Stats } from "socket:fs/stats"; - import * as exports from "socket:fs/handle"; - -} - -declare module "socket:fs/dir" { - /** - * Sorts directory entries - * @param {string|Dirent} a - * @param {string|Dirent} b - * @return {number} - */ - export function sortDirectoryEntries(a: string | Dirent, b: string | Dirent): number; - export const kType: unique symbol; /** - * A containerr for a directory and its entries. This class supports scanning - * a directory entry by entry with a `read()` method. The `Symbol.asyncIterator` - * interface is exposed along with an AsyncGenerator `entries()` method. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#class-fsdir} + * An event dispacted when the application receives an OS signal */ - export class Dir { - static from(fdOrHandle: any, options: any): exports.Dir; - /** - * `Dir` class constructor. - * @param {DirectoryHandle} handle - * @param {object=} options - */ - constructor(handle: DirectoryHandle, options?: object | undefined); - path: any; - handle: DirectoryHandle; - encoding: any; - withFileTypes: boolean; - /** - * `true` if closed, otherwise `false`. - * @ignore - * @type {boolean} - */ - get closed(): boolean; + export class SignalEvent extends MessageEvent { /** - * `true` if closing, otherwise `false`. + * `SignalEvent` class constructor * @ignore - * @type {boolean} - */ - get closing(): boolean; - /** - * Closes container and underlying handle. - * @param {object|function} options - * @param {function=} callback + * @param {string=} [type] + * @param {object=} [options] */ - close(options?: object | Function, callback?: Function | undefined): Promise; + constructor(type?: string | undefined, options?: object | undefined); /** - * Reads and returns directory entry. - * @param {object|function} options - * @param {function=} callback - * @return {Dirent|string} + * The code of the signal. + * @type {import('../signal.js').signal} */ - read(options: object | Function, callback?: Function | undefined): Dirent | string; + get code(): number; /** - * AsyncGenerator which yields directory entries. - * @param {object=} options + * The name of the signal. + * @type {string} */ - entries(options?: object | undefined): AsyncGenerator; + get name(): string; /** - * `for await (...)` AsyncGenerator support. + * An optional message describing the signal + * @type {string} */ - get [Symbol.asyncIterator](): (options?: object | undefined) => AsyncGenerator; + get message(): string; + #private; + } + namespace _default { + export { ApplicationURLEvent }; + export { MenuItemEvent }; + export { SignalEvent }; + export { HotKeyEvent }; } + export default _default; +} + +declare module "socket:path/well-known" { /** - * A container for a directory entry. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#class-fsdirent} + * Well known path to the user's "Downloads" folder. + * @type {?string} */ - export class Dirent { - static get UNKNOWN(): any; - static get FILE(): any; - static get DIR(): any; - static get LINK(): any; - static get FIFO(): any; - static get SOCKET(): any; - static get CHAR(): any; - static get BLOCK(): any; - /** - * Creates `Dirent` instance from input. - * @param {object|string} name - * @param {(string|number)=} type - */ - static from(name: object | string, type?: (string | number) | undefined): exports.Dirent; - /** - * `Dirent` class constructor. - * @param {string} name - * @param {string|number} type - */ - constructor(name: string, type: string | number); - name: string; - /** - * Read only type. - */ - get type(): number; - /** - * `true` if `Dirent` instance is a directory. - */ - isDirectory(): boolean; - /** - * `true` if `Dirent` instance is a file. - */ - isFile(): boolean; - /** - * `true` if `Dirent` instance is a block device. - */ - isBlockDevice(): boolean; - /** - * `true` if `Dirent` instance is a character device. - */ - isCharacterDevice(): boolean; - /** - * `true` if `Dirent` instance is a symbolic link. - */ - isSymbolicLink(): boolean; - /** - * `true` if `Dirent` instance is a FIFO. - */ - isFIFO(): boolean; - /** - * `true` if `Dirent` instance is a socket. - */ - isSocket(): boolean; - [exports.kType]: number; - } - export default exports; - import { DirectoryHandle } from "socket:fs/handle"; - import * as exports from "socket:fs/dir"; - -} - -declare module "socket:hooks" { + export const DOWNLOADS: string | null; /** - * Wait for a hook event to occur. - * @template {Event | T extends Event} - * @param {string|function} nameOrFunction - * @return {Promise} + * Well known path to the user's "Documents" folder. + * @type {?string} */ - export function wait(nameOrFunction: string | Function): Promise; + export const DOCUMENTS: string | null; /** - * Wait for the global Window, Document, and Runtime to be ready. - * The callback function is called exactly once. - * @param {function} callback - * @return {function} + * Well known path to the user's "Pictures" folder. + * @type {?string} */ - export function onReady(callback: Function): Function; + export const PICTURES: string | null; /** - * Wait for the global Window and Document to be ready. The callback - * function is called exactly once. - * @param {function} callback - * @return {function} + * Well known path to the user's "Desktop" folder. + * @type {?string} */ - export function onLoad(callback: Function): Function; + export const DESKTOP: string | null; /** - * Wait for the runtime to be ready. The callback - * function is called exactly once. - * @param {function} callback - * @return {function} + * Well known path to the user's "Videos" folder. + * @type {?string} */ - export function onInit(callback: Function): Function; + export const VIDEOS: string | null; /** - * Calls callback when a global exception occurs. - * 'error', 'messageerror', and 'unhandledrejection' events are handled here. - * @param {function} callback - * @return {function} + * Well known path to the user's "Music" folder. + * @type {?string} */ - export function onError(callback: Function): Function; + export const MUSIC: string | null; /** - * Subscribes to the global data pipe calling callback when - * new data is emitted on the global Window. - * @param {function} callback - * @return {function} + * Well known path to the application's "resources" folder. + * @type {?string} */ - export function onData(callback: Function): Function; + export const RESOURCES: string | null; /** - * Subscribes to global messages likely from an external `postMessage` - * invocation. - * @param {function} callback - * @return {function} + * Well known path to the application's "config" folder. + * @type {?string} */ - export function onMessage(callback: Function): Function; + export const CONFIG: string | null; /** - * Calls callback when runtime is working online. - * @param {function} callback - * @return {function} + * Well known path to the application's "data" folder. + * @type {?string} */ - export function onOnline(callback: Function): Function; + export const DATA: string | null; /** - * Calls callback when runtime is not working online. - * @param {function} callback - * @return {function} + * Well known path to the application's "log" folder. + * @type {?string} */ - export function onOffline(callback: Function): Function; + export const LOG: string | null; /** - * Calls callback when runtime user preferred language has changed. - * @param {function} callback - * @return {function} + * Well known path to the application's "tmp" folder. + * @type {?string} */ - export function onLanguageChange(callback: Function): Function; + export const TMP: string | null; /** - * Calls callback when an application permission has changed. - * @param {function} callback - * @return {function} + * Well known path to the application's "home" folder. + * This may be the user's HOME directory or the application container sandbox. + * @type {?string} */ - export function onPermissionChange(callback: Function): Function; + export const HOME: string | null; + namespace _default { + export { DOWNLOADS }; + export { DOCUMENTS }; + export { RESOURCES }; + export { PICTURES }; + export { DESKTOP }; + export { VIDEOS }; + export { CONFIG }; + export { MUSIC }; + export { HOME }; + export { DATA }; + export { LOG }; + export { TMP }; + } + export default _default; +} + +declare module "socket:os" { /** - * Calls callback in response to a presented `Notification`. - * @param {function} callback - * @return {function} + * Returns the operating system CPU architecture for which Socket was compiled. + * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' */ - export function onNotificationResponse(callback: Function): Function; + export function arch(): string; /** - * Calls callback when a `Notification` is presented. - * @param {function} callback - * @return {function} + * Returns an array of objects containing information about each CPU/core. + * @returns {Array} cpus - An array of objects containing information about each CPU/core. + * The properties of the objects are: + * - model `` - CPU model name. + * - speed `` - CPU clock speed (in MHz). + * - times `` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. + * - user `` - Time spent by this CPU or core in user mode. + * - nice `` - Time spent by this CPU or core in user mode with low priority (nice). + * - sys `` - Time spent by this CPU or core in system mode. + * - idle `` - Time spent by this CPU or core in idle mode. + * - irq `` - Time spent by this CPU or core in IRQ mode. + * @see {@link https://nodejs.org/api/os.html#os_os_cpus} */ - export function onNotificationPresented(callback: Function): Function; + export function cpus(): Array; /** - * Calls callback when a `ApplicationURL` is opened. - * @param {function} callback - * @return {function} + * Returns an object containing network interfaces that have been assigned a network address. + * @returns {object} - An object containing network interfaces that have been assigned a network address. + * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. + * The properties available on the assigned network address object include: + * - address `` - The assigned IPv4 or IPv6 address. + * - netmask `` - The IPv4 or IPv6 network mask. + * - family `` - The address family ('IPv4' or 'IPv6'). + * - mac `` - The MAC address of the network interface. + * - internal `` - Indicates whether the network interface is a loopback interface. + * - scopeid `` - The numeric scope ID (only specified when family is 'IPv6'). + * - cidr `` - The CIDR notation of the interface. + * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} */ - export function onApplicationURL(callback: Function): Function; - export const RUNTIME_INIT_EVENT_NAME: "__runtime_init__"; - export const GLOBAL_EVENTS: string[]; + export function networkInterfaces(): object; /** - * An event dispatched when the runtime has been initialized. + * Returns the operating system platform. + * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_platform} + * The returned value is equivalent to `process.platform`. */ - export class InitEvent { - constructor(); - } + export function platform(): string; /** - * An event dispatched when the runtime global has been loaded. + * Returns the operating system name. + * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_type} */ - export class LoadEvent { - constructor(); - } + export function type(): string; /** - * An event dispatched when the runtime is considered ready. + * @returns {boolean} - `true` if the operating system is Windows. */ - export class ReadyEvent { - constructor(); - } + export function isWindows(): boolean; /** - * An event dispatched when the runtime has been initialized. + * @returns {string} - The operating system's default directory for temporary files. */ - export class RuntimeInitEvent { - constructor(); - } + export function tmpdir(): string; /** - * An interface for registering callbacks for various hooks in - * the runtime. + * Get resource usage. */ - export class Hooks extends EventTarget { - /** - * @ignore - */ - static GLOBAL_EVENTS: string[]; - /** - * @ignore - */ - static InitEvent: typeof InitEvent; - /** - * @ignore - */ - static LoadEvent: typeof LoadEvent; - /** - * @ignore - */ - static ReadyEvent: typeof ReadyEvent; - /** - * @ignore - */ - static RuntimeInitEvent: typeof RuntimeInitEvent; - /** - * An array of all global events listened to in various hooks - */ - get globalEvents(): string[]; - /** - * Reference to global object - * @type {object} - */ - get global(): any; - /** - * Returns `document` in global. - * @type {Document} - */ - get document(): Document; - /** - * Returns `document` in global. - * @type {Window} - */ - get window(): Window; - /** - * Predicate for determining if the global document is ready. - * @type {boolean} - */ - get isDocumentReady(): boolean; - /** - * Predicate for determining if the global object is ready. - * @type {boolean} - */ - get isGlobalReady(): boolean; - /** - * Predicate for determining if the runtime is ready. - * @type {boolean} - */ - get isRuntimeReady(): boolean; - /** - * Predicate for determining if everything is ready. - * @type {boolean} - */ - get isReady(): boolean; - /** - * Predicate for determining if the runtime is working online. - * @type {boolean} - */ - get isOnline(): boolean; - /** - * Predicate for determining if the runtime is in a Worker context. - * @type {boolean} - */ - get isWorkerContext(): boolean; - /** - * Predicate for determining if the runtime is in a Window context. - * @type {boolean} - */ - get isWindowContext(): boolean; - /** - * Wait for a hook event to occur. - * @template {Event | T extends Event} - * @param {string|function} nameOrFunction - * @param {WaitOptions=} [options] - * @return {Promise} - */ - wait(nameOrFunction: string | Function, options?: WaitOptions | undefined): Promise; - /** - * Wait for the global Window, Document, and Runtime to be ready. - * The callback function is called exactly once. - * @param {function} callback - * @return {function} - */ - onReady(callback: Function): Function; - /** - * Wait for the global Window and Document to be ready. The callback - * function is called exactly once. - * @param {function} callback - * @return {function} - */ - onLoad(callback: Function): Function; - /** - * Wait for the runtime to be ready. The callback - * function is called exactly once. - * @param {function} callback - * @return {function} - */ - onInit(callback: Function): Function; - /** - * Calls callback when a global exception occurs. - * 'error', 'messageerror', and 'unhandledrejection' events are handled here. - * @param {function} callback - * @return {function} - */ - onError(callback: Function): Function; - /** - * Subscribes to the global data pipe calling callback when - * new data is emitted on the global Window. - * @param {function} callback - * @return {function} - */ - onData(callback: Function): Function; - /** - * Subscribes to global messages likely from an external `postMessage` - * invocation. - * @param {function} callback - * @return {function} - */ - onMessage(callback: Function): Function; - /** - * Calls callback when runtime is working online. - * @param {function} callback - * @return {function} - */ - onOnline(callback: Function): Function; - /** - * Calls callback when runtime is not working online. - * @param {function} callback - * @return {function} - */ - onOffline(callback: Function): Function; - /** - * Calls callback when runtime user preferred language has changed. - * @param {function} callback - * @return {function} - */ - onLanguageChange(callback: Function): Function; - /** - * Calls callback when an application permission has changed. - * @param {function} callback - * @return {function} - */ - onPermissionChange(callback: Function): Function; - /** - * Calls callback in response to a displayed `Notification`. - * @param {function} callback - * @return {function} - */ - onNotificationResponse(callback: Function): Function; - /** - * Calls callback when a `Notification` is presented. - * @param {function} callback - * @return {function} - */ - onNotificationPresented(callback: Function): Function; - /** - * Calls callback when a `ApplicationURL` is opened. - * @param {function} callback - * @return {function} - */ - onApplicationURL(callback: Function): Function; - #private; - } - export default hooks; - export type WaitOptions = { - signal?: AbortSignal; - }; + export function rusage(): any; /** - * `Hooks` single instance. + * Returns the system uptime in seconds. + * @returns {number} - The system uptime in seconds. + */ + export function uptime(): number; + /** + * Returns the operating system name. + * @returns {string} - The operating system name. + */ + export function uname(): string; + /** + * It's implemented in process.hrtime.bigint() * @ignore */ - const hooks: Hooks; + export function hrtime(): any; + /** + * Node.js doesn't have this method. + * @ignore + */ + export function availableMemory(): any; + /** + * The host operating system. This value can be one of: + * - android + * - android-emulator + * - iphoneos + * - iphone-simulator + * - linux + * - macosx + * - unix + * - unknown + * - win32 + * @ignore + * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} + */ + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + /** + * Returns the home directory of the current user. + * @return {string} + */ + export function homedir(): string; + export { constants }; + /** + * @type {string} + * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. + */ + export const EOL: string; + export default exports; + import constants from "socket:os/constants"; + import * as exports from "socket:os"; + } -declare module "socket:fs/watcher" { +declare module "socket:signal" { /** - * A container for a file system path watcher. + * Converts an `signal` code to its corresponding string message. + * @param {import('./os/constants.js').signal} {code} + * @return {string} */ - export class Watcher extends EventEmitter { - /** - * `Watcher` class constructor. - * @ignore - * @param {string} path - * @param {object=} [options] - * @param {AbortSignal=} [options.signal} - * @param {string|number|bigint=} [options.id] - * @param {string=} [options.encoding = 'utf8'] - */ - constructor(path: string, options?: object | undefined); - /** - * The underlying `fs.Watcher` resource id. - * @ignore - * @type {string} - */ - id: string; - /** - * The path the `fs.Watcher` is watching - * @type {string} - */ - path: string; - /** - * `true` if closed, otherwise `false. - * @type {boolean} - */ - closed: boolean; - /** - * `true` if aborted, otherwise `false`. - * @type {boolean} - */ - aborted: boolean; - /** - * The encoding of the `filename` - * @type {'utf8'|'buffer'} - */ - encoding: 'utf8' | 'buffer'; - /** - * A `AbortController` `AbortSignal` for async aborts. - * @type {AbortSignal?} - */ - signal: AbortSignal | null; - /** - * Internal event listener cancellation. - * @ignore - * @type {function?} - */ - stopListening: Function | null; - /** - * Internal starter for watcher. - * @ignore - */ - start(): Promise; - /** - * Closes watcher and stops listening for changes. - * @return {Promise} - */ - close(): Promise; - /** - * Implements the `AsyncIterator` (`Symbol.asyncIterator`) iterface. - * @ignore - * @return {AsyncIterator<{ eventType: string, filename: string }>} - */ - [Symbol.asyncIterator](): AsyncIterator<{ - eventType: string; - filename: string; - }>; - #private; + export function toString(code: any): string; + /** + * Gets the code for a given 'signal' name. + * @param {string|number} name + * @return {signal} + */ + export function getCode(name: string | number): signal; + /** + * Gets the name for a given 'signal' code + * @return {string} + * @param {string|number} code + */ + export function getName(code: string | number): string; + /** + * Gets the message for a 'signal' code. + * @param {number|string} code + * @return {string} + */ + export function getMessage(code: number | string): string; + /** + * Add a signal event listener. + * @param {string|number} signal + * @param {function(SignalEvent)} callback + * @param {{ once?: boolean }=} [options] + */ + export function addEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { + once?: boolean; + } | undefined): void; + /** + * Remove a signal event listener. + * @param {string|number} signal + * @param {function(SignalEvent)} callback + * @param {{ once?: boolean }=} [options] + */ + export function removeEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { + once?: boolean; + } | undefined): void; + export { constants }; + export const channel: BroadcastChannel; + export const SIGHUP: any; + export const SIGINT: any; + export const SIGQUIT: any; + export const SIGILL: any; + export const SIGTRAP: any; + export const SIGABRT: any; + export const SIGIOT: any; + export const SIGBUS: any; + export const SIGFPE: any; + export const SIGKILL: any; + export const SIGUSR1: any; + export const SIGSEGV: any; + export const SIGUSR2: any; + export const SIGPIPE: any; + export const SIGALRM: any; + export const SIGTERM: any; + export const SIGCHLD: any; + export const SIGCONT: any; + export const SIGSTOP: any; + export const SIGTSTP: any; + export const SIGTTIN: any; + export const SIGTTOU: any; + export const SIGURG: any; + export const SIGXCPU: any; + export const SIGXFSZ: any; + export const SIGVTALRM: any; + export const SIGPROF: any; + export const SIGWINCH: any; + export const SIGIO: any; + export const SIGINFO: any; + export const SIGSYS: any; + export const strings: { + [x: number]: string; + }; + namespace _default { + export { addEventListener }; + export { removeEventListener }; + export { constants }; + export { channel }; + export { strings }; + export { toString }; + export { getName }; + export { getCode }; + export { getMessage }; + export { SIGHUP }; + export { SIGINT }; + export { SIGQUIT }; + export { SIGILL }; + export { SIGTRAP }; + export { SIGABRT }; + export { SIGIOT }; + export { SIGBUS }; + export { SIGFPE }; + export { SIGKILL }; + export { SIGUSR1 }; + export { SIGSEGV }; + export { SIGUSR2 }; + export { SIGPIPE }; + export { SIGALRM }; + export { SIGTERM }; + export { SIGCHLD }; + export { SIGCONT }; + export { SIGSTOP }; + export { SIGTSTP }; + export { SIGTTIN }; + export { SIGTTOU }; + export { SIGURG }; + export { SIGXCPU }; + export { SIGXFSZ }; + export { SIGVTALRM }; + export { SIGPROF }; + export { SIGWINCH }; + export { SIGIO }; + export { SIGINFO }; + export { SIGSYS }; + } + export default _default; + export type signal = import("socket:os/constants").signal; + import { SignalEvent } from "socket:internal/events"; + import { signal as constants } from "socket:os/constants"; +} + +declare module "socket:internal/streams/web" { + export class ByteLengthQueuingStrategy { + constructor(e: any); + _byteLengthQueuingStrategyHighWaterMark: any; + get highWaterMark(): any; + get size(): (e: any) => any; + } + export class CountQueuingStrategy { + constructor(e: any); + _countQueuingStrategyHighWaterMark: any; + get highWaterMark(): any; + get size(): () => number; + } + export class ReadableByteStreamController { + get byobRequest(): any; + get desiredSize(): number; + close(): void; + enqueue(e: any): void; + error(e?: any): void; + _pendingPullIntos: v; + [T](e: any): any; + [C](e: any): any; + [P](): void; + } + export class ReadableStream { + static from(e: any): any; + constructor(e?: {}, t?: {}); + get locked(): boolean; + cancel(e?: any): any; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + pipeThrough(e: any, t?: {}): any; + pipeTo(e: any, t?: {}): any; + tee(): any; + values(e?: any): any; + } + export class ReadableStreamBYOBReader { + constructor(e: any); + _readIntoRequests: v; + get closed(): any; + cancel(e?: any): any; + read(e: any, t?: {}): any; + releaseLock(): void; + } + export class ReadableStreamBYOBRequest { + get view(): any; + respond(e: any): void; + respondWithNewView(e: any): void; + } + export class ReadableStreamDefaultController { + get desiredSize(): number; + close(): void; + enqueue(e?: any): void; + error(e?: any): void; + [T](e: any): any; + [C](e: any): void; + [P](): void; + } + export class ReadableStreamDefaultReader { + constructor(e: any); + _readRequests: v; + get closed(): any; + cancel(e?: any): any; + read(): any; + releaseLock(): void; + } + export class TransformStream { + constructor(e?: {}, t?: {}, r?: {}); + get readable(): any; + get writable(): any; + } + export class TransformStreamDefaultController { + get desiredSize(): number; + enqueue(e?: any): void; + error(e?: any): void; + terminate(): void; + } + export class WritableStream { + constructor(e?: {}, t?: {}); + get locked(): boolean; + abort(e?: any): any; + close(): any; + getWriter(): WritableStreamDefaultWriter; + } + export class WritableStreamDefaultController { + get abortReason(): any; + get signal(): any; + error(e?: any): void; + [w](e: any): any; + [R](): void; + } + export class WritableStreamDefaultWriter { + constructor(e: any); + _ownerWritableStream: any; + get closed(): any; + get desiredSize(): number; + get ready(): any; + abort(e?: any): any; + close(): any; + releaseLock(): void; + write(e?: any): any; + } + class v { + _cursor: number; + _size: number; + _front: { + _elements: any[]; + _next: any; + }; + _back: { + _elements: any[]; + _next: any; + }; + get length(): number; + push(e: any): void; + shift(): any; + forEach(e: any): void; + peek(): any; + } + const T: unique symbol; + const C: unique symbol; + const P: unique symbol; + const w: unique symbol; + const R: unique symbol; + export {}; +} + +declare module "socket:internal/streams" { + const _default: any; + export default _default; + import { ReadableStream } from "socket:internal/streams/web"; + import { ReadableStreamBYOBReader } from "socket:internal/streams/web"; + import { ReadableByteStreamController } from "socket:internal/streams/web"; + import { ReadableStreamBYOBRequest } from "socket:internal/streams/web"; + import { ReadableStreamDefaultController } from "socket:internal/streams/web"; + import { ReadableStreamDefaultReader } from "socket:internal/streams/web"; + import { WritableStream } from "socket:internal/streams/web"; + import { WritableStreamDefaultController } from "socket:internal/streams/web"; + import { WritableStreamDefaultWriter } from "socket:internal/streams/web"; + import { TransformStream } from "socket:internal/streams/web"; + import { TransformStreamDefaultController } from "socket:internal/streams/web"; + import { ByteLengthQueuingStrategy } from "socket:internal/streams/web"; + import { CountQueuingStrategy } from "socket:internal/streams/web"; + export { ReadableStream, ReadableStreamBYOBReader, ReadableByteStreamController, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy }; +} + +declare module "socket:stream/web" { + export const TextEncoderStream: typeof UnsupportedStreamInterface; + export const TextDecoderStream: { + new (label?: string, options?: TextDecoderOptions): TextDecoderStream; + prototype: TextDecoderStream; + } | typeof UnsupportedStreamInterface; + export const CompressionStream: { + new (format: CompressionFormat): CompressionStream; + prototype: CompressionStream; + } | typeof UnsupportedStreamInterface; + export const DecompressionStream: { + new (format: CompressionFormat): DecompressionStream; + prototype: DecompressionStream; + } | typeof UnsupportedStreamInterface; + export default exports; + import { ReadableStream } from "socket:internal/streams"; + import { ReadableStreamBYOBReader } from "socket:internal/streams"; + import { ReadableByteStreamController } from "socket:internal/streams"; + import { ReadableStreamBYOBRequest } from "socket:internal/streams"; + import { ReadableStreamDefaultController } from "socket:internal/streams"; + import { ReadableStreamDefaultReader } from "socket:internal/streams"; + import { WritableStream } from "socket:internal/streams"; + import { WritableStreamDefaultController } from "socket:internal/streams"; + import { WritableStreamDefaultWriter } from "socket:internal/streams"; + import { TransformStream } from "socket:internal/streams"; + import { TransformStreamDefaultController } from "socket:internal/streams"; + import { ByteLengthQueuingStrategy } from "socket:internal/streams"; + import { CountQueuingStrategy } from "socket:internal/streams"; + class UnsupportedStreamInterface { + } + import * as exports from "socket:stream/web"; + + export { ReadableStream, ReadableStreamBYOBReader, ReadableByteStreamController, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy }; +} + +declare module "socket:stream" { + export function pipelinePromise(...streams: any[]): Promise; + export function pipeline(stream: any, ...streams: any[]): any; + export function isStream(stream: any): boolean; + export function isStreamx(stream: any): boolean; + export function getStreamError(stream: any): any; + export function isReadStreamx(stream: any): any; + export { web }; + export class FixedFIFO { + constructor(hwm: any); + buffer: any[]; + mask: number; + top: number; + btm: number; + next: any; + clear(): void; + push(data: any): boolean; + shift(): any; + peek(): any; + isEmpty(): boolean; + } + export class FIFO { + constructor(hwm: any); + hwm: any; + head: FixedFIFO; + tail: FixedFIFO; + length: number; + clear(): void; + push(val: any): void; + shift(): any; + peek(): any; + isEmpty(): boolean; + } + export class WritableState { + constructor(stream: any, { highWaterMark, map, mapWritable, byteLength, byteLengthWritable }?: { + highWaterMark?: number; + map?: any; + mapWritable: any; + byteLength: any; + byteLengthWritable: any; + }); + stream: any; + queue: FIFO; + highWaterMark: number; + buffered: number; + error: any; + pipeline: any; + drains: any; + byteLength: any; + map: any; + afterWrite: any; + afterUpdateNextTick: any; + get ended(): boolean; + push(data: any): boolean; + shift(): any; + end(data: any): void; + autoBatch(data: any, cb: any): any; + update(): void; + updateNonPrimary(): void; + continueUpdate(): boolean; + updateCallback(): void; + updateNextTick(): void; + } + export class ReadableState { + constructor(stream: any, { highWaterMark, map, mapReadable, byteLength, byteLengthReadable }?: { + highWaterMark?: number; + map?: any; + mapReadable: any; + byteLength: any; + byteLengthReadable: any; + }); + stream: any; + queue: FIFO; + highWaterMark: number; + buffered: number; + readAhead: boolean; + error: any; + pipeline: Pipeline; + byteLength: any; + map: any; + pipeTo: any; + afterRead: any; + afterUpdateNextTick: any; + get ended(): boolean; + pipe(pipeTo: any, cb: any): void; + push(data: any): boolean; + shift(): any; + unshift(data: any): void; + read(): any; + drain(): void; + update(): void; + updateNonPrimary(): void; + continueUpdate(): boolean; + updateCallback(): void; + updateNextTick(): void; + } + export class TransformState { + constructor(stream: any); + data: any; + afterTransform: any; + afterFinal: any; + } + export class Pipeline { + constructor(src: any, dst: any, cb: any); + from: any; + to: any; + afterPipe: any; + error: any; + pipeToFinished: boolean; + finished(): void; + done(stream: any, err: any): void; } - export default Watcher; + export class Stream extends EventEmitter { + constructor(opts: any); + _duplexState: number; + _readableState: any; + _writableState: any; + _open(cb: any): void; + _destroy(cb: any): void; + _predestroy(): void; + get readable(): boolean; + get writable(): boolean; + get destroyed(): boolean; + get destroying(): boolean; + destroy(err: any): void; + } + export class Readable extends Stream { + static _fromAsyncIterator(ite: any, opts: any): Readable; + static from(data: any, opts: any): any; + static isBackpressured(rs: any): boolean; + static isPaused(rs: any): boolean; + _readableState: ReadableState; + _read(cb: any): void; + pipe(dest: any, cb: any): any; + read(): any; + push(data: any): boolean; + unshift(data: any): void; + resume(): this; + pause(): this; + } + export class Writable extends Stream { + static isBackpressured(ws: any): boolean; + static drained(ws: any): Promise; + _writableState: WritableState; + _writev(batch: any, cb: any): void; + _write(data: any, cb: any): void; + _final(cb: any): void; + write(data: any): boolean; + end(data: any): this; + } + export class Duplex extends Readable { + _writableState: WritableState; + _writev(batch: any, cb: any): void; + _write(data: any, cb: any): void; + _final(cb: any): void; + write(data: any): boolean; + end(data: any): this; + } + export class Transform extends Duplex { + _transformState: TransformState; + _transform(data: any, cb: any): void; + _flush(cb: any): void; + } + export class PassThrough extends Transform { + } + const _default: typeof Stream & { + web: typeof web; + Readable: typeof Readable; + Writable: typeof Writable; + Duplex: typeof Duplex; + Transform: typeof Transform; + PassThrough: typeof PassThrough; + pipeline: typeof pipeline & { + [x: symbol]: typeof pipelinePromise; + }; + }; + export default _default; + import web from "socket:stream/web"; import { EventEmitter } from "socket:events"; } -declare module "socket:fs/promises" { - /** - * Asynchronously check access a file. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode} - * @param {string | Buffer | URL} path - * @param {string?} [mode] - * @param {object?} [options] - */ - export function access(path: string | Buffer | URL, mode?: string | null, options?: object | null): Promise; - /** - * @see {@link https://nodejs.org/api/fs.html#fspromiseschmodpath-mode} - * @param {string | Buffer | URL} path - * @param {number} mode - * @returns {Promise} - */ - export function chmod(path: string | Buffer | URL, mode: number): Promise; - /** - * Changes ownership of file or directory at `path` with `uid` and `gid`. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @return {Promise} - */ - export function chown(path: string, uid: number, gid: number): Promise; - /** - * Asynchronously copies `src` to `dest` calling `callback` upon success or error. - * @param {string} src - The source file path. - * @param {string} dest - The destination file path. - * @param {number} flags - Modifiers for copy operation. - * @return {Promise} - */ - export function copyFile(src: string, dest: string, flags?: number): Promise; - /** - * Chages ownership of link at `path` with `uid` and `gid. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @return {Promise} - */ - export function lchown(path: string, uid: number, gid: number): Promise; - /** - * Creates a link to `dest` from `dest`. - * @param {string} src - * @param {string} dest - * @return {Promise} - */ - export function link(src: string, dest: string): Promise; - /** - * Asynchronously creates a directory. - * - * @param {string} path - The path to create - * @param {object} [options] - The optional options argument can be an integer specifying mode (permission and sticky bits), or an object with a mode property and a recursive property indicating whether parent directories should be created. Calling fs.mkdir() when path is a directory that exists results in an error only when recursive is false. - * @param {boolean} [options.recursive=false] - Recursively create missing path segments. - * @param {number} [options.mode=0o777] - Set the mode of directory, or missing path segments when recursive is true. - * @return {Promise} - Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. - */ - export function mkdir(path: string, options?: { - recursive?: boolean; - mode?: number; - }): Promise; - /** - * Asynchronously open a file. - * @see {@link https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode } - * - * @param {string | Buffer | URL} path - * @param {string=} flags - default: 'r' - * @param {number=} mode - default: 0o666 - * @return {Promise} - */ - export function open(path: string | Buffer | URL, flags?: string | undefined, mode?: number | undefined): Promise; - /** - * @see {@link https://nodejs.org/api/fs.html#fspromisesopendirpath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @param {string?} [options.encoding = 'utf8'] - * @param {number?} [options.bufferSize = 32] - * @return {Promise} - */ - export function opendir(path: string | Buffer | URL, options?: object | null): Promise; - /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options} - * @param {string | Buffer | URL} path - * @param {object?} options - * @param {string?} [options.encoding = 'utf8'] - * @param {boolean?} [options.withFileTypes = false] - */ - export function readdir(path: string | Buffer | URL, options: object | null): Promise; +declare module "socket:tty" { + export function WriteStream(fd: any): Writable; + export function ReadStream(fd: any): Readable; + export function isatty(fd: any): boolean; + namespace _default { + export { WriteStream }; + export { ReadStream }; + export { isatty }; + } + export default _default; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; +} + +declare module "socket:process" { /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options} - * @param {string} path - * @param {object?} [options] - * @param {(string|null)?} [options.encoding = null] - * @param {string?} [options.flag = 'r'] - * @param {AbortSignal?} [options.signal] - * @return {Promise} + * Adds callback to the 'nextTick' queue. + * @param {Function} callback */ - export function readFile(path: string, options?: object | null): Promise; + export function nextTick(callback: Function): void; /** - * Reads link at `path` - * @param {string} path - * @return {Promise} + * Computed high resolution time as a `BigInt`. + * @param {Array?} [time] + * @return {bigint} */ - export function readlink(path: string): Promise; + export function hrtime(time?: Array | null): bigint; + export namespace hrtime { + function bigint(): any; + } /** - * Computes real path for `path` - * @param {string} path - * @return {Promise} + * @param {number=} [code=0] - The exit code. Default: 0. */ - export function realpath(path: string): Promise; + export function exit(code?: number | undefined): Promise; /** - * Renames file or directory at `src` to `dest`. - * @param {string} src - * @param {string} dest - * @return {Promise} + * Returns an object describing the memory usage of the Node.js process measured in bytes. + * @returns {Object} */ - export function rename(src: string, dest: string): Promise; + export function memoryUsage(): any; + export namespace memoryUsage { + function rss(): any; + } + export class ProcessEnvironmentEvent extends Event { + constructor(type: any, key: any, value: any); + key: any; + value: any; + } + export class ProcessEnvironment extends EventTarget { + get [Symbol.toStringTag](): string; + } + export const env: any; + export default process; + const process: any; +} + +declare module "socket:location" { + export class Location { + get url(): URL; + get protocol(): string; + get host(): string; + get hostname(): string; + get port(): string; + get pathname(): string; + get search(): string; + get origin(): string; + get href(): string; + get hash(): string; + toString(): string; + } + const _default: Location; + export default _default; +} + +declare module "socket:path/path" { /** - * Removes directory at `path`. - * @param {string} path - * @return {Promise} + * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. + * @param {strig} ...paths + * @returns {string} + * @see {@link https://nodejs.org/api/path.html#path_path_resolve_paths} */ - export function rmdir(path: string): Promise; + export function resolve(options: any, ...components: any[]): string; /** - * Get the stats of a file - * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @param {boolean?} [options.bigint = false] - * @return {Promise} + * Computes current working directory for a path + * @param {object=} [opts] + * @param {boolean=} [opts.posix] Set to `true` to force POSIX style path + * @return {string} */ - export function stat(path: string | Buffer | URL, options?: object | null): Promise; + export function cwd(opts?: object | undefined): string; /** - * Get the stats of a symbolic link. - * @see {@link https://nodejs.org/api/fs.html#fspromiseslstatpath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @param {boolean?} [options.bigint = false] - * @return {Promise} + * Computed location origin. Defaults to `socket:///` if not available. + * @return {string} */ - export function lstat(path: string | Buffer | URL, options?: object | null): Promise; + export function origin(): string; /** - * Creates a symlink of `src` at `dest`. - * @param {string} src - * @param {string} dest - * @return {Promise} + * Computes the relative path from `from` to `to`. + * @param {object} options + * @param {PathComponent} from + * @param {PathComponent} to + * @return {string} */ - export function symlink(src: string, dest: string, type?: any): Promise; + export function relative(options: object, from: PathComponent, to: PathComponent): string; /** - * Unlinks (removes) file at `path`. - * @param {string} path - * @return {Promise} + * Joins path components. This function may not return an absolute path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} */ - export function unlink(path: string): Promise; + export function join(options: object, ...components: PathComponent[]): string; /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options} - * @param {string | Buffer | URL | FileHandle} path - filename or FileHandle - * @param {string|Buffer|Array|DataView|TypedArray} data - * @param {object?} [options] - * @param {string|null} [options.encoding = 'utf8'] - * @param {number} [options.mode = 0o666] - * @param {string} [options.flag = 'w'] - * @param {AbortSignal?} [options.signal] - * @return {Promise} + * Computes directory name of path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} */ - export function writeFile(path: string | Buffer | URL | FileHandle, data: string | Buffer | any[] | DataView | TypedArray, options?: object | null): Promise; + export function dirname(options: object, path: any): string; /** - * Watch for changes at `path` calling `callback` - * @param {string} - * @param {function|object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {AbortSignal=} [options.signal] - * @return {Watcher} + * Computes base name of path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} */ - export function watch(path: any, options?: (Function | object) | undefined): Watcher; - export type Stats = any; - export default exports; - export type Buffer = import("socket:buffer").Buffer; - export type TypedArray = Uint8Array | Int8Array; - import { FileHandle } from "socket:fs/handle"; - import { Dir } from "socket:fs/dir"; - import { Stats } from "socket:fs/stats"; - import { Watcher } from "socket:fs/watcher"; - import * as constants from "socket:fs/constants"; - import { DirectoryHandle } from "socket:fs/handle"; - import { Dirent } from "socket:fs/dir"; - import fds from "socket:fs/fds"; - import { ReadStream } from "socket:fs/stream"; - import { WriteStream } from "socket:fs/stream"; - import * as exports from "socket:fs/promises"; - - export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, ReadStream, Watcher, WriteStream }; -} - -declare module "socket:fs/index" { + export function basename(options: object, path: any): string; /** - * Asynchronously check access a file for a given mode calling `callback` - * upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} - * @param {string | Buffer | URL} path - * @param {string?|function(Error?)?} [mode = F_OK(0)] - * @param {function(Error?)?} [callback] + * Computes extension name of path. + * @param {object} options + * @param {PathComponent} path + * @return {string} */ - export function access(path: string | Buffer | URL, mode: any, callback?: ((arg0: Error | null) => any) | null): void; + export function extname(options: object, path: PathComponent): string; /** - * Synchronously check access a file for a given mode calling `callback` - * upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} - * @param {string | Buffer | URL} path - * @param {string?} [mode = F_OK(0)] + * Computes normalized path + * @param {object} options + * @param {PathComponent} path + * @return {string} */ - export function accessSync(path: string | Buffer | URL, mode?: string | null): boolean; + export function normalize(options: object, path: PathComponent): string; /** - * Checks if a path exists - * @param {string | Buffer | URL} path - * @param {function(Boolean)?} [callback] + * Formats `Path` object into a string. + * @param {object} options + * @param {object|Path} path + * @return {string} */ - export function exists(path: string | Buffer | URL, callback?: ((arg0: boolean) => any) | null): void; + export function format(options: object, path: object | Path): string; /** - * Checks if a path exists - * @param {string | Buffer | URL} path - * @param {function(Boolean)?} [callback] + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {object} */ - export function existsSync(path: string | Buffer | URL): boolean; + export function parse(path: PathComponent): object; /** - * Asynchronously changes the permissions of a file. - * No arguments other than a possible exception are given to the completion callback - * - * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} - * - * @param {string | Buffer | URL} path - * @param {number} mode - * @param {function(Error?)} callback + * @typedef {(string|Path|URL|{ pathname: string }|{ url: string)} PathComponent */ - export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): void; /** - * Synchronously changes the permissions of a file. - * - * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} - * @param {string | Buffer | URL} path - * @param {number} mode + * A container for a parsed Path. */ - export function chmodSync(path: string | Buffer | URL, mode: number): void; + export class Path { + /** + * Creates a `Path` instance from `input` and optional `cwd`. + * @param {PathComponent} input + * @param {string} [cwd] + */ + static from(input: PathComponent, cwd?: string): any; + /** + * `Path` class constructor. + * @protected + * @param {string} pathname + * @param {string} [cwd = Path.cwd()] + */ + protected constructor(); + pattern: URLPattern; + url: any; + get pathname(): any; + get protocol(): any; + get href(): any; + /** + * `true` if the path is relative, otherwise `false. + * @type {boolean} + */ + get isRelative(): boolean; + /** + * The working value of this path. + */ + get value(): any; + /** + * The original source, unresolved. + * @type {string} + */ + get source(): string; + /** + * Computed parent path. + * @type {string} + */ + get parent(): string; + /** + * Computed root in path. + * @type {string} + */ + get root(): string; + /** + * Computed directory name in path. + * @type {string} + */ + get dir(): string; + /** + * Computed base name in path. + * @type {string} + */ + get base(): string; + /** + * Computed base name in path without path extension. + * @type {string} + */ + get name(): string; + /** + * Computed extension name in path. + * @type {string} + */ + get ext(): string; + /** + * The computed drive, if given in the path. + * @type {string?} + */ + get drive(): string; + /** + * @return {URL} + */ + toURL(): URL; + /** + * Converts this `Path` instance to a string. + * @return {string} + */ + toString(): string; + /** + * @ignore + */ + inspect(): { + root: string; + dir: string; + base: string; + ext: string; + name: string; + }; + /** + * @ignore + */ + [Symbol.toStringTag](): string; + #private; + } + export default Path; + export type PathComponent = (string | Path | URL | { + pathname: string; + } | { + url: string; + }); + import { URLPattern } from "socket:url/index"; + import { URL } from "socket:url/index"; +} + +declare module "socket:path/mounts" { + const _default: {}; + export default _default; +} + +declare module "socket:path/win32" { /** - * Changes ownership of file or directory at `path` with `uid` and `gid`. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @param {function} callback + * Computes current working directory for a path + * @param {string} */ - export function chown(path: string, uid: number, gid: number, callback: Function): void; + export function cwd(): any; /** - * Changes ownership of file or directory at `path` with `uid` and `gid`. - * @param {string} path - * @param {number} uid - * @param {number} gid + * Resolves path components to an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export function chownSync(path: string, uid: number, gid: number): void; + export function resolve(...components: PathComponent[]): string; /** - * Asynchronously close a file descriptor calling `callback` upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsclosefd-callback} - * @param {number} fd - * @param {function(Error?)?} [callback] + * Joins path components. This function may not return an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export function close(fd: number, callback?: ((arg0: Error | null) => any) | null): void; + export function join(...components: PathComponent[]): string; /** - * Asynchronously copies `src` to `dest` calling `callback` upon success or error. - * @param {string} src - The source file path. - * @param {string} dest - The destination file path. - * @param {number} flags - Modifiers for copy operation. - * @param {function(Error=)=} [callback] - The function to call after completion. - * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} + * Computes directory name of path. + * @param {PathComponent} path + * @return {string} */ - export function copyFile(src: string, dest: string, flags?: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; + export function dirname(path: PathComponent): string; /** - * Synchronously copies `src` to `dest` calling `callback` upon success or error. - * @param {string} src - The source file path. - * @param {string} dest - The destination file path. - * @param {number} flags - Modifiers for copy operation. - * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} + * Computes base name of path. + * @param {PathComponent} path + * @param {string=} [suffix] + * @return {string} */ - export function copyFileSync(src: string, dest: string, flags?: number): void; + export function basename(path: PathComponent, suffix?: string | undefined): string; /** - * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @returns {ReadStream} + * Computes extension name of path. + * @param {PathComponent} path + * @return {string} */ - export function createReadStream(path: string | Buffer | URL, options?: object | null): ReadStream; + export function extname(path: PathComponent): string; /** - * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @returns {WriteStream} + * Predicate helper to determine if path is absolute. + * @param {PathComponent} path + * @return {boolean} */ - export function createWriteStream(path: string | Buffer | URL, options?: object | null): WriteStream; + export function isAbsolute(path: PathComponent): boolean; /** - * Invokes the callback with the for the file descriptor. See - * the POSIX fstat(2) documentation for more detail. - * - * @see {@link https://nodejs.org/api/fs.html#fsfstatfd-options-callback} - * - * @param {number} fd - A file descriptor. - * @param {object?|function?} [options] - An options object. - * @param {function?} callback - The function to call after completion. + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} */ - export function fstat(fd: number, options: any, callback: Function | null): void; + export function parse(path: PathComponent): Path; /** - * Request that all data for the open file descriptor is flushed - * to the storage device. - * @param {number} fd - A file descriptor. - * @param {function} callback - The function to call after completion. + * Formats `Path` object into a string. + * @param {object|Path} path + * @return {string} */ - export function fsync(fd: number, callback: Function): void; + export function format(path: object | Path): string; /** - * Truncates the file up to `offset` bytes. - * @param {number} fd - A file descriptor. - * @param {number=|function} [offset = 0] - * @param {function?} callback - The function to call after completion. + * Normalizes `path` resolving `..` and `.\` preserving trailing + * slashes. + * @param {string} path */ - export function ftruncate(fd: number, offset: any, callback: Function | null): void; + export function normalize(path: string): any; /** - * Chages ownership of link at `path` with `uid` and `gid. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @param {function} callback + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} */ - export function lchown(path: string, uid: number, gid: number, callback: Function): void; + export function relative(from: string, to: string): string; + export default exports; + export namespace win32 { + let sep: "\\"; + let delimiter: ";"; + } + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; + import * as mounts from "socket:path/mounts"; + import * as posix from "socket:path/posix"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/win32"; + + export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path/posix" { /** - * Creates a link to `dest` from `src`. - * @param {string} src - * @param {string} dest - * @param {function} + * Computes current working directory for a path + * @param {string} + * @return {string} */ - export function link(src: string, dest: string, callback: any): void; + export function cwd(): string; /** - * @ignore + * Resolves path components to an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export function mkdir(path: any, options: any, callback: any): void; + export function resolve(...components: PathComponent[]): string; /** - * @ignore - * @param {string|URL} path - * @param {object=} [options] + * Joins path components. This function may not return an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export function mkdirSync(path: string | URL, options?: object | undefined): void; + export function join(...components: PathComponent[]): string; /** - * Asynchronously open a file calling `callback` upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} - * @param {string | Buffer | URL} path - * @param {string?} [flags = 'r'] - * @param {string?} [mode = 0o666] - * @param {object?|function?} [options] - * @param {function(Error?, number?)?} [callback] + * Computes directory name of path. + * @param {PathComponent} path + * @return {string} */ - export function open(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any, callback?: ((arg0: Error | null, arg1: number | null) => any) | null): void; + export function dirname(path: PathComponent): string; /** - * Asynchronously open a directory calling `callback` upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} - * @param {string | Buffer | URL} path - * @param {object?|function(Error?, Dir?)} [options] - * @param {string?} [options.encoding = 'utf8'] - * @param {boolean?} [options.withFileTypes = false] - * @param {function(Error?, Dir?)?} callback + * Computes base name of path. + * @param {PathComponent} path + * @param {string=} [suffix] + * @return {string} */ - export function opendir(path: string | Buffer | URL, options: {}, callback: ((arg0: Error | null, arg1: Dir | null) => any) | null): void; + export function basename(path: PathComponent, suffix?: string | undefined): string; /** - * Asynchronously read from an open file descriptor. - * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} - * @param {number} fd - * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. - * @param {number} offset - The position in buffer to write the data to. - * @param {number} length - The number of bytes to read. - * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. - * @param {function(Error?, number?, Buffer?)} callback + * Computes extension name of path. + * @param {PathComponent} path + * @return {string} */ - export function read(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; + export function extname(path: PathComponent): string; /** - * Asynchronously write to an open file descriptor. - * @see {@link https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback} - * @param {number} fd - * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. - * @param {number} offset - The position in buffer to write the data to. - * @param {number} length - The number of bytes to read. - * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. - * @param {function(Error?, number?, Buffer?)} callback + * Predicate helper to determine if path is absolute. + * @param {PathComponent} path + * @return {boolean} */ - export function write(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; + export function isAbsolute(path: PathComponent): boolean; /** - * Asynchronously read all entries in a directory. - * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} - * @param {string | Buffer | URL } path - * @param {object?|function(Error?, object[])} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {boolean?} [options.withFileTypes ? false] - * @param {function(Error?, object[])} callback + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} */ - export function readdir(path: string | Buffer | URL, options: {}, callback: (arg0: Error | null, arg1: object[]) => any): void; + export function parse(path: PathComponent): Path; /** - * @param {string | Buffer | URL | number } path - * @param {object?|function(Error?, Buffer?)} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Buffer?)} callback + * Formats `Path` object into a string. + * @param {object|Path} path + * @return {string} */ - export function readFile(path: string | Buffer | URL | number, options: {}, callback: (arg0: Error | null, arg1: Buffer | null) => any): void; + export function format(path: object | Path): string; /** - * @param {string | Buffer | URL | number } path - * @param {object?|function(Error?, Buffer?)} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] + * Normalizes `path` resolving `..` and `./` preserving trailing + * slashes. + * @param {string} path */ - export function readFileSync(path: string | Buffer | URL | number, options?: {}): any; + export function normalize(path: string): any; + /** + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} + */ + export function relative(from: string, to: string): string; + export default exports; + export namespace posix { + let sep: "/"; + let delimiter: ":"; + } + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; + import * as mounts from "socket:path/mounts"; + import * as win32 from "socket:path/win32"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/posix"; + + export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path/index" { + export default exports; + import * as mounts from "socket:path/mounts"; + import * as posix from "socket:path/posix"; + import * as win32 from "socket:path/win32"; + import { Path } from "socket:path/path"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/index"; + + export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path" { + export const sep: "\\" | "/"; + export const delimiter: ":" | ";"; + export const resolve: typeof posix.win32.resolve; + export const join: typeof posix.win32.join; + export const dirname: typeof posix.win32.dirname; + export const basename: typeof posix.win32.basename; + export const extname: typeof posix.win32.extname; + export const cwd: typeof posix.win32.cwd; + export const isAbsolute: typeof posix.win32.isAbsolute; + export const parse: typeof posix.win32.parse; + export const format: typeof posix.win32.format; + export const normalize: typeof posix.win32.normalize; + export const relative: typeof posix.win32.relative; + const _default: typeof posix | typeof posix.win32; + export default _default; + import { posix } from "socket:path/index"; + import { Path } from "socket:path/index"; + import { win32 } from "socket:path/index"; + import { mounts } from "socket:path/index"; + import { DOWNLOADS } from "socket:path/index"; + import { DOCUMENTS } from "socket:path/index"; + import { RESOURCES } from "socket:path/index"; + import { PICTURES } from "socket:path/index"; + import { DESKTOP } from "socket:path/index"; + import { VIDEOS } from "socket:path/index"; + import { CONFIG } from "socket:path/index"; + import { MUSIC } from "socket:path/index"; + import { HOME } from "socket:path/index"; + import { DATA } from "socket:path/index"; + import { LOG } from "socket:path/index"; + import { TMP } from "socket:path/index"; + export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:fs/stream" { + export const DEFAULT_STREAM_HIGH_WATER_MARK: number; /** - * Reads link at `path` - * @param {string} path - * @param {function(err, string)} callback + * @typedef {import('./handle.js').FileHandle} FileHandle */ - export function readlink(path: string, callback: (arg0: err, arg1: string) => any): void; /** - * Computes real path for `path` - * @param {string} path - * @param {function(err, string)} callback + * A `Readable` stream for a `FileHandle`. */ - export function realpath(path: string, callback: (arg0: err, arg1: string) => any): void; + export class ReadStream extends Readable { + end: any; + start: any; + handle: any; + buffer: ArrayBuffer; + signal: any; + timeout: any; + bytesRead: number; + shouldEmitClose: boolean; + /** + * Sets file handle for the ReadStream. + * @param {FileHandle} handle + */ + setHandle(handle: FileHandle): void; + /** + * The max buffer size for the ReadStream. + */ + get highWaterMark(): number; + /** + * Relative or absolute path of the underlying `FileHandle`. + */ + get path(): any; + /** + * `true` if the stream is in a pending state. + */ + get pending(): boolean; + _open(callback: any): Promise; + _read(callback: any): Promise; + } + export namespace ReadStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + } /** - * Renames file or directory at `src` to `dest`. - * @param {string} src - * @param {string} dest - * @param {function} callback + * A `Writable` stream for a `FileHandle`. */ - export function rename(src: string, dest: string, callback: Function): void; + export class WriteStream extends Writable { + start: any; + handle: any; + signal: any; + timeout: any; + bytesWritten: number; + shouldEmitClose: boolean; + /** + * Sets file handle for the WriteStream. + * @param {FileHandle} handle + */ + setHandle(handle: FileHandle): void; + /** + * The max buffer size for the Writetream. + */ + get highWaterMark(): number; + /** + * Relative or absolute path of the underlying `FileHandle`. + */ + get path(): any; + /** + * `true` if the stream is in a pending state. + */ + get pending(): boolean; + _open(callback: any): Promise; + _write(buffer: any, callback: any): any; + } + export namespace WriteStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + } + export const FileReadStream: typeof exports.ReadStream; + export const FileWriteStream: typeof exports.WriteStream; + export default exports; + export type FileHandle = import("socket:fs/handle").FileHandle; + import { Readable } from "socket:stream"; + import { Writable } from "socket:stream"; + import * as exports from "socket:fs/stream"; + +} + +declare module "socket:fs/constants" { /** - * Removes directory at `path`. - * @param {string} path - * @param {function} callback + * This flag can be used with uv_fs_copyfile() to return an error if the + * destination already exists. */ - export function rmdir(path: string, callback: Function): void; + export const COPYFILE_EXCL: 1; /** - * Synchronously get the stats of a file - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, a fallback copy mechanism is used. */ - export function statSync(path: string | Buffer | URL | number, options: object | null): promises.Stats; + export const COPYFILE_FICLONE: 2; /** - * Get the stats of a file - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Stats?)} callback + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, an error is returned. */ - export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + export const COPYFILE_FICLONE_FORCE: 4; + export const UV_DIRENT_UNKNOWN: any; + export const UV_DIRENT_FILE: any; + export const UV_DIRENT_DIR: any; + export const UV_DIRENT_LINK: any; + export const UV_DIRENT_FIFO: any; + export const UV_DIRENT_SOCKET: any; + export const UV_DIRENT_CHAR: any; + export const UV_DIRENT_BLOCK: any; + export const UV_FS_SYMLINK_DIR: any; + export const UV_FS_SYMLINK_JUNCTION: any; + export const O_RDONLY: any; + export const O_WRONLY: any; + export const O_RDWR: any; + export const O_APPEND: any; + export const O_ASYNC: any; + export const O_CLOEXEC: any; + export const O_CREAT: any; + export const O_DIRECT: any; + export const O_DIRECTORY: any; + export const O_DSYNC: any; + export const O_EXCL: any; + export const O_LARGEFILE: any; + export const O_NOATIME: any; + export const O_NOCTTY: any; + export const O_NOFOLLOW: any; + export const O_NONBLOCK: any; + export const O_NDELAY: any; + export const O_PATH: any; + export const O_SYNC: any; + export const O_TMPFILE: any; + export const O_TRUNC: any; + export const S_IFMT: any; + export const S_IFREG: any; + export const S_IFDIR: any; + export const S_IFCHR: any; + export const S_IFBLK: any; + export const S_IFIFO: any; + export const S_IFLNK: any; + export const S_IFSOCK: any; + export const S_IRWXU: any; + export const S_IRUSR: any; + export const S_IWUSR: any; + export const S_IXUSR: any; + export const S_IRWXG: any; + export const S_IRGRP: any; + export const S_IWGRP: any; + export const S_IXGRP: any; + export const S_IRWXO: any; + export const S_IROTH: any; + export const S_IWOTH: any; + export const S_IXOTH: any; + export const F_OK: any; + export const R_OK: any; + export const W_OK: any; + export const X_OK: any; + export default exports; + import * as exports from "socket:fs/constants"; + +} + +declare module "socket:fs/flags" { + export function normalizeFlags(flags: any): any; + export default exports; + import * as exports from "socket:fs/flags"; + +} + +declare module "socket:fs/stats" { /** - * Get the stats of a symbolic link - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Stats?)} callback + * A container for various stats about a file or directory. */ - export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + export class Stats { + /** + * Creates a `Stats` instance from input, optionally with `BigInt` data types + * @param {object|Stats} [stat] + * @param {fromBigInt=} [fromBigInt = false] + * @return {Stats} + */ + static from(stat?: object | Stats, fromBigInt?: any): Stats; + /** + * `Stats` class constructor. + * @param {object|Stats} stat + */ + constructor(stat: object | Stats); + dev: any; + ino: any; + mode: any; + nlink: any; + uid: any; + gid: any; + rdev: any; + size: any; + blksize: any; + blocks: any; + atimeMs: any; + mtimeMs: any; + ctimeMs: any; + birthtimeMs: any; + atime: Date; + mtime: Date; + ctime: Date; + birthtime: Date; + /** + * Returns `true` if stats represents a directory. + * @return {Boolean} + */ + isDirectory(): boolean; + /** + * Returns `true` if stats represents a file. + * @return {Boolean} + */ + isFile(): boolean; + /** + * Returns `true` if stats represents a block device. + * @return {Boolean} + */ + isBlockDevice(): boolean; + /** + * Returns `true` if stats represents a character device. + * @return {Boolean} + */ + isCharacterDevice(): boolean; + /** + * Returns `true` if stats represents a symbolic link. + * @return {Boolean} + */ + isSymbolicLink(): boolean; + /** + * Returns `true` if stats represents a FIFO. + * @return {Boolean} + */ + isFIFO(): boolean; + /** + * Returns `true` if stats represents a socket. + * @return {Boolean} + */ + isSocket(): boolean; + } + export default exports; + import * as exports from "socket:fs/stats"; + +} + +declare module "socket:fs/fds" { + const _default: { + types: Map; + fds: Map; + ids: Map; + readonly size: number; + get(id: any): any; + syncOpenDescriptors(): Promise; + set(id: any, fd: any, type: any): void; + has(id: any): boolean; + fd(id: any): any; + id(fd: any): any; + release(id: any, closeDescriptor?: boolean): Promise; + retain(id: any): Promise; + delete(id: any): void; + clear(): void; + typeof(id: any): any; + entries(): IterableIterator<[any, any]>; + }; + export default _default; +} + +declare module "socket:fs/handle" { + export const kOpening: unique symbol; + export const kClosing: unique symbol; + export const kClosed: unique symbol; /** - * Creates a symlink of `src` at `dest`. - * @param {string} src - * @param {string} dest + * A container for a descriptor tracked in `fds` and opened in the native layer. + * This class implements the Node.js `FileHandle` interface + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#class-filehandle} */ - export function symlink(src: string, dest: string, type: any, callback: any): void; + export class FileHandle extends EventEmitter { + static get DEFAULT_ACCESS_MODE(): any; + static get DEFAULT_OPEN_FLAGS(): string; + static get DEFAULT_OPEN_MODE(): number; + /** + * Creates a `FileHandle` from a given `id` or `fd` + * @param {string|number|FileHandle|object} id + * @return {FileHandle} + */ + static from(id: string | number | FileHandle | object): FileHandle; + /** + * Determines if access to `path` for `mode` is possible. + * @param {string} path + * @param {number} [mode = 0o666] + * @param {object=} [options] + * @return {Promise} + */ + static access(path: string, mode?: number, options?: object | undefined): Promise; + /** + * Asynchronously open a file. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesopenpath-flags-mode} + * @param {string | Buffer | URL} path + * @param {string=} [flags = 'r'] + * @param {string|number=} [mode = 0o666] + * @param {object=} [options] + * @return {Promise} + */ + static open(path: string | Buffer | URL, flags?: string | undefined, mode?: (string | number) | undefined, options?: object | undefined): Promise; + /** + * `FileHandle` class constructor + * @ignore + * @param {object} options + */ + constructor(options: object); + flags: any; + path: any; + mode: any; + id: string; + fd: any; + /** + * `true` if the `FileHandle` instance has been opened. + * @type {boolean} + */ + get opened(): boolean; + /** + * `true` if the `FileHandle` is opening. + * @type {boolean} + */ + get opening(): boolean; + /** + * `true` if the `FileHandle` is closing. + * @type {boolean} + */ + get closing(): boolean; + /** + * `true` if the `FileHandle` is closed. + */ + get closed(): boolean; + /** + * Appends to a file, if handle was opened with `O_APPEND`, otherwise this + * method is just an alias to `FileHandle#writeFile()`. + * @param {string|Buffer|TypedArray|Array} data + * @param {object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {object=} [options.signal] + */ + appendFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise; + /** + * Change permissions of file handle. + * @param {number} mode + * @param {object=} [options] + */ + chmod(mode: number, options?: object | undefined): Promise; + /** + * Change ownership of file handle. + * @param {number} uid + * @param {number} gid + * @param {object=} [options] + */ + chown(uid: number, gid: number, options?: object | undefined): Promise; + /** + * Close underlying file handle + * @param {object=} [options] + */ + close(options?: object | undefined): Promise; + /** + * Creates a `ReadStream` for the underlying file. + * @param {object=} [options] - An options object + */ + createReadStream(options?: object | undefined): ReadStream; + /** + * Creates a `WriteStream` for the underlying file. + * @param {object=} [options] - An options object + */ + createWriteStream(options?: object | undefined): WriteStream; + /** + * @param {object=} [options] + */ + datasync(): Promise; + /** + * Opens the underlying descriptor for the file handle. + * @param {object=} [options] + */ + open(options?: object | undefined): Promise; + /** + * Reads `length` bytes starting from `position` into `buffer` at + * `offset`. + * @param {Buffer|object} buffer + * @param {number=} [offset] + * @param {number=} [length] + * @param {number=} [position] + * @param {object=} [options] + */ + read(buffer: Buffer | object, offset?: number | undefined, length?: number | undefined, position?: number | undefined, options?: object | undefined): Promise<{ + bytesRead: number; + buffer: any; + }>; + /** + * Reads the entire contents of a file and returns it as a buffer or a string + * specified of a given encoding specified at `options.encoding`. + * @param {object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {object=} [options.signal] + */ + readFile(options?: object | undefined): Promise; + /** + * Returns the stats of the underlying file. + * @param {object=} [options] + * @return {Promise} + */ + stat(options?: object | undefined): Promise; + /** + * Returns the stats of the underlying symbolic link. + * @param {object=} [options] + * @return {Promise} + */ + lstat(options?: object | undefined): Promise; + /** + * Synchronize a file's in-core state with storage device + * @return {Promise} + */ + sync(): Promise; + /** + * @param {number} [offset = 0] + * @return {Promise} + */ + truncate(offset?: number): Promise; + /** + * Writes `length` bytes at `offset` in `buffer` to the underlying file + * at `position`. + * @param {Buffer|object} buffer + * @param {number} offset + * @param {number} length + * @param {number} position + * @param {object=} [options] + */ + write(buffer: Buffer | object, offset: number, length: number, position: number, options?: object | undefined): Promise<{ + buffer: any; + bytesWritten: number; + }>; + /** + * Writes `data` to file. + * @param {string|Buffer|TypedArray|Array} data + * @param {object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {object=} [options.signal] + */ + writeFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise; + [exports.kOpening]: any; + [exports.kClosing]: any; + [exports.kClosed]: boolean; + #private; + } /** - * Unlinks (removes) file at `path`. - * @param {string} path - * @param {function} callback + * A container for a directory handle tracked in `fds` and opened in the + * native layer. */ - export function unlink(path: string, callback: Function): void; + export class DirectoryHandle extends EventEmitter { + /** + * The max number of entries that can be bufferd with the `bufferSize` + * option. + */ + static get MAX_BUFFER_SIZE(): number; + static get MAX_ENTRIES(): number; + /** + * The default number of entries `Dirent` that are buffered + * for each read request. + */ + static get DEFAULT_BUFFER_SIZE(): number; + /** + * Creates a `FileHandle` from a given `id` or `fd` + * @param {string|number|DirectoryHandle|object} id + * @return {DirectoryHandle} + */ + static from(id: string | number | DirectoryHandle | object): DirectoryHandle; + /** + * Asynchronously open a directory. + * @param {string | Buffer | URL} path + * @param {object=} [options] + * @return {Promise} + */ + static open(path: string | Buffer | URL, options?: object | undefined): Promise; + /** + * `DirectoryHandle` class constructor + * @private + * @param {object} options + */ + private constructor(); + id: string; + path: any; + bufferSize: number; + /** + * DirectoryHandle file descriptor id + */ + get fd(): string; + /** + * `true` if the `DirectoryHandle` instance has been opened. + * @type {boolean} + */ + get opened(): boolean; + /** + * `true` if the `DirectoryHandle` is opening. + * @type {boolean} + */ + get opening(): boolean; + /** + * `true` if the `DirectoryHandle` is closing. + * @type {boolean} + */ + get closing(): boolean; + /** + * `true` if `DirectoryHandle` is closed. + */ + get closed(): boolean; + /** + * Opens the underlying handle for a directory. + * @param {object=} options + * @return {Promise} + */ + open(options?: object | undefined): Promise; + /** + * Close underlying directory handle + * @param {object=} [options] + */ + close(options?: object | undefined): Promise; + /** + * Reads directory entries + * @param {object=} [options] + * @param {number=} [options.entries = DirectoryHandle.MAX_ENTRIES] + */ + read(options?: object | undefined): Promise; + [exports.kOpening]: any; + [exports.kClosing]: any; + [exports.kClosed]: boolean; + #private; + } + export default exports; + export type TypedArray = Uint8Array | Int8Array; + import { EventEmitter } from "socket:events"; + import { Buffer } from "socket:buffer"; + import { ReadStream } from "socket:fs/stream"; + import { WriteStream } from "socket:fs/stream"; + import { Stats } from "socket:fs/stats"; + import * as exports from "socket:fs/handle"; + +} + +declare module "socket:fs/dir" { /** - * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {string | Buffer | TypedArray | DataView | object } data - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.mode ? 0o666] - * @param {string?} [options.flag ? 'w'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?)} callback + * Sorts directory entries + * @param {string|Dirent} a + * @param {string|Dirent} b + * @return {number} */ - export function writeFile(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null, callback: (arg0: Error | null) => any): void; + export function sortDirectoryEntries(a: string | Dirent, b: string | Dirent): number; + export const kType: unique symbol; /** - * Writes data to a file synchronously. - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {string | Buffer | TypedArray | DataView | object } data - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.mode ? 0o666] - * @param {string?} [options.flag ? 'w'] - * @param {AbortSignal?} [options.signal] - * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} + * A containerr for a directory and its entries. This class supports scanning + * a directory entry by entry with a `read()` method. The `Symbol.asyncIterator` + * interface is exposed along with an AsyncGenerator `entries()` method. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#class-fsdir} */ - export function writeFileSync(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null): void; + export class Dir { + static from(fdOrHandle: any, options: any): exports.Dir; + /** + * `Dir` class constructor. + * @param {DirectoryHandle} handle + * @param {object=} options + */ + constructor(handle: DirectoryHandle, options?: object | undefined); + path: any; + handle: DirectoryHandle; + encoding: any; + withFileTypes: boolean; + /** + * `true` if closed, otherwise `false`. + * @ignore + * @type {boolean} + */ + get closed(): boolean; + /** + * `true` if closing, otherwise `false`. + * @ignore + * @type {boolean} + */ + get closing(): boolean; + /** + * Closes container and underlying handle. + * @param {object|function} options + * @param {function=} callback + */ + close(options?: object | Function, callback?: Function | undefined): Promise; + /** + * Reads and returns directory entry. + * @param {object|function} options + * @param {function=} callback + * @return {Dirent|string} + */ + read(options: object | Function, callback?: Function | undefined): Dirent | string; + /** + * AsyncGenerator which yields directory entries. + * @param {object=} options + */ + entries(options?: object | undefined): AsyncGenerator; + /** + * `for await (...)` AsyncGenerator support. + */ + get [Symbol.asyncIterator](): (options?: object | undefined) => AsyncGenerator; + } /** - * Watch for changes at `path` calling `callback` - * @param {string} - * @param {function|object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {?function} [callback] - * @return {Watcher} + * A container for a directory entry. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#class-fsdirent} */ - export function watch(path: any, options?: (Function | object) | undefined, callback?: Function | null): Watcher; + export class Dirent { + static get UNKNOWN(): any; + static get FILE(): any; + static get DIR(): any; + static get LINK(): any; + static get FIFO(): any; + static get SOCKET(): any; + static get CHAR(): any; + static get BLOCK(): any; + /** + * Creates `Dirent` instance from input. + * @param {object|string} name + * @param {(string|number)=} type + */ + static from(name: object | string, type?: (string | number) | undefined): exports.Dirent; + /** + * `Dirent` class constructor. + * @param {string} name + * @param {string|number} type + */ + constructor(name: string, type: string | number); + name: string; + /** + * Read only type. + */ + get type(): number; + /** + * `true` if `Dirent` instance is a directory. + */ + isDirectory(): boolean; + /** + * `true` if `Dirent` instance is a file. + */ + isFile(): boolean; + /** + * `true` if `Dirent` instance is a block device. + */ + isBlockDevice(): boolean; + /** + * `true` if `Dirent` instance is a character device. + */ + isCharacterDevice(): boolean; + /** + * `true` if `Dirent` instance is a symbolic link. + */ + isSymbolicLink(): boolean; + /** + * `true` if `Dirent` instance is a FIFO. + */ + isFIFO(): boolean; + /** + * `true` if `Dirent` instance is a socket. + */ + isSocket(): boolean; + [exports.kType]: number; + } export default exports; - export type Buffer = import("socket:buffer").Buffer; - export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - import { ReadStream } from "socket:fs/stream"; - import { WriteStream } from "socket:fs/stream"; - import { Dir } from "socket:fs/dir"; - import * as promises from "socket:fs/promises"; - import { Stats } from "socket:fs/stats"; - import { Watcher } from "socket:fs/watcher"; - import * as constants from "socket:fs/constants"; import { DirectoryHandle } from "socket:fs/handle"; - import { Dirent } from "socket:fs/dir"; - import fds from "socket:fs/fds"; - import { FileHandle } from "socket:fs/handle"; - import * as exports from "socket:fs/index"; + import * as exports from "socket:fs/dir"; - export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; -} - -declare module "socket:fs" { - export * from "socket:fs/index"; - export default exports; - import * as exports from "socket:fs/index"; -} - -declare module "socket:external/libsodium/index" { - const _default: any; - export default _default; -} - -declare module "socket:crypto/sodium" { - export {}; } -declare module "socket:crypto" { - /** - * Generate cryptographically strong random values into the `buffer` - * @param {TypedArray} buffer - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues} - * @return {TypedArray} - */ - export function getRandomValues(buffer: TypedArray, ...args: any[]): TypedArray; - /** - * Generate a random 64-bit number. - * @returns {BigInt} - A random 64-bit number. - */ - export function rand64(): BigInt; - /** - * Generate `size` random bytes. - * @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1. - * @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes. - */ - export function randomBytes(size: number): Buffer; - /** - * @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512` - * @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview. - * @returns {Promise} - A promise that resolves with an instance of socket.Buffer with the hash. - */ - export function createDigest(algorithm: string, buf: any): Promise; - /** - * A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c - * that works on strings and `ArrayBuffer` views (typed arrays) - * @param {string|Uint8Array|ArrayBuffer} value - * @param {number=} [seed = 0] - * @return {number} - */ - export function murmur3(value: string | Uint8Array | ArrayBuffer, seed?: number | undefined): number; - /** - * @typedef {Uint8Array|Int8Array} TypedArray - */ - /** - * WebCrypto API - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto} - */ - export let webcrypto: any; - /** - * A promise that resolves when all internals to be loaded/ready. - * @type {Promise} - */ - export const ready: Promise; - /** - * Maximum total size of random bytes per page - */ - export const RANDOM_BYTES_QUOTA: number; +declare module "socket:hooks" { /** - * Maximum total size for random bytes. + * Wait for a hook event to occur. + * @template {Event | T extends Event} + * @param {string|function} nameOrFunction + * @return {Promise} */ - export const MAX_RANDOM_BYTES: 281474976710655; + export function wait(nameOrFunction: string | Function): Promise; /** - * Maximum total amount of allocated per page of bytes (max/quota) + * Wait for the global Window, Document, and Runtime to be ready. + * The callback function is called exactly once. + * @param {function} callback + * @return {function} */ - export const MAX_RANDOM_BYTES_PAGES: number; - export default exports; - export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - export namespace sodium { - let ready: Promise; - } - import * as exports from "socket:crypto"; - -} - -declare module "socket:ipc" { - export function maybeMakeError(error: any, caller: any): any; + export function onReady(callback: Function): Function; /** - * Parses `seq` as integer value - * @param {string|number} seq - * @param {object=} [options] - * @param {boolean} [options.bigint = false] - * @ignore + * Wait for the global Window and Document to be ready. The callback + * function is called exactly once. + * @param {function} callback + * @return {function} */ - export function parseSeq(seq: string | number, options?: object | undefined): number | bigint; + export function onLoad(callback: Function): Function; /** - * If `debug.enabled === true`, then debug output will be printed to console. - * @param {(boolean)} [enable] - * @return {boolean} - * @ignore + * Wait for the runtime to be ready. The callback + * function is called exactly once. + * @param {function} callback + * @return {function} */ - export function debug(enable?: (boolean)): boolean; - export namespace debug { - let enabled: any; - function log(...args: any[]): any; - } + export function onInit(callback: Function): Function; /** - * Find transfers for an in worker global `postMessage` - * that is proxied to the main thread. - * @ignore + * Calls callback when a global exception occurs. + * 'error', 'messageerror', and 'unhandledrejection' events are handled here. + * @param {function} callback + * @return {function} */ - export function findMessageTransfers(transfers: any, object: any): any; + export function onError(callback: Function): Function; /** - * @ignore + * Subscribes to the global data pipe calling callback when + * new data is emitted on the global Window. + * @param {function} callback + * @return {function} */ - export function postMessage(message: any, ...args: any[]): any; + export function onData(callback: Function): Function; /** - * Waits for the native IPC layer to be ready and exposed on the - * global window object. - * @ignore + * Subscribes to global messages likely from an external `postMessage` + * invocation. + * @param {function} callback + * @return {function} */ - export function ready(): Promise; + export function onMessage(callback: Function): Function; /** - * Sends a synchronous IPC command over XHR returning a `Result` - * upon success or error. - * @param {string} command - * @param {any?} [value] - * @param {object?} [options] - * @return {Result} - * @ignore + * Calls callback when runtime is working online. + * @param {function} callback + * @return {function} */ - export function sendSync(command: string, value?: any | null, options?: object | null, buffer: any): Result; + export function onOnline(callback: Function): Function; /** - * Emit event to be dispatched on `window` object. - * @param {string} name - * @param {any} value - * @param {EventTarget=} [target = window] - * @param {Object=} options + * Calls callback when runtime is not working online. + * @param {function} callback + * @return {function} */ - export function emit(name: string, value: any, target?: EventTarget | undefined, options?: any | undefined): Promise; + export function onOffline(callback: Function): Function; /** - * Resolves a request by `seq` with possible value. - * @param {string} seq - * @param {any} value - * @ignore + * Calls callback when runtime user preferred language has changed. + * @param {function} callback + * @return {function} */ - export function resolve(seq: string, value: any): Promise; + export function onLanguageChange(callback: Function): Function; /** - * Sends an async IPC command request with parameters. - * @param {string} command - * @param {any=} value - * @param {object=} [options] - * @param {boolean=} [options.cache=false] - * @param {boolean=} [options.bytes=false] - * @return {Promise} + * Calls callback when an application permission has changed. + * @param {function} callback + * @return {function} */ - export function send(command: string, value?: any | undefined, options?: object | undefined): Promise; + export function onPermissionChange(callback: Function): Function; /** - * Sends an async IPC command request with parameters and buffered bytes. - * @param {string} command - * @param {any=} value - * @param {(Buffer|Uint8Array|ArrayBuffer|string|Array)=} buffer - * @param {object=} options - * @ignore + * Calls callback in response to a presented `Notification`. + * @param {function} callback + * @return {function} */ - export function write(command: string, value?: any | undefined, buffer?: (Buffer | Uint8Array | ArrayBuffer | string | any[]) | undefined, options?: object | undefined): Promise; + export function onNotificationResponse(callback: Function): Function; /** - * Sends an async IPC command request with parameters requesting a response - * with buffered bytes. - * @param {string} command - * @param {any=} value - * @param {object=} options - * @ignore + * Calls callback when a `Notification` is presented. + * @param {function} callback + * @return {function} */ - export function request(command: string, value?: any | undefined, options?: object | undefined): Promise; + export function onNotificationPresented(callback: Function): Function; /** - * Factory for creating a proxy based IPC API. - * @param {string} domain - * @param {(function|object)=} ctx - * @param {string=} [ctx.default] - * @return {Proxy} - * @ignore + * Calls callback when a `ApplicationURL` is opened. + * @param {function} callback + * @return {function} */ - export function createBinding(domain: string, ctx?: (Function | object) | undefined): ProxyConstructor; + export function onApplicationURL(callback: Function): Function; + export const RUNTIME_INIT_EVENT_NAME: "__runtime_init__"; + export const GLOBAL_EVENTS: string[]; /** - * Represents an OK IPC status. - * @ignore + * An event dispatched when the runtime has been initialized. */ - export const OK: 0; + export class InitEvent { + constructor(); + } /** - * Represents an ERROR IPC status. - * @ignore + * An event dispatched when the runtime global has been loaded. */ - export const ERROR: 1; + export class LoadEvent { + constructor(); + } /** - * Timeout in milliseconds for IPC requests. - * @ignore + * An event dispatched when the runtime is considered ready. */ - export const TIMEOUT: number; + export class ReadyEvent { + constructor(); + } /** - * Symbol for the `ipc.debug.enabled` property - * @ignore + * An event dispatched when the runtime has been initialized. */ - export const kDebugEnabled: unique symbol; + export class RuntimeInitEvent { + constructor(); + } /** - * @ignore + * An interface for registering callbacks for various hooks in + * the runtime. */ - export class Headers extends globalThis.Headers { + export class Hooks extends EventTarget { /** * @ignore */ - static from(input: any): any; + static GLOBAL_EVENTS: string[]; /** * @ignore */ - get length(): number; + static InitEvent: typeof InitEvent; /** * @ignore */ - toJSON(): { - [k: string]: string; - }; - } - const Message_base: any; - /** - * A container for a IPC message based on a `ipc://` URI scheme. - * @ignore - */ - export class Message extends Message_base { - [x: string]: any; + static LoadEvent: typeof LoadEvent; /** - * The expected protocol for an IPC message. * @ignore */ - static get PROTOCOL(): string; + static ReadyEvent: typeof ReadyEvent; /** - * Creates a `Message` instance from a variety of input. - * @param {string|URL|Message|Buffer|object} input - * @param {(object|string|URLSearchParams)=} [params] - * @param {(ArrayBuffer|Uint8Array|string)?} [bytes] - * @return {Message} * @ignore */ - static from(input: string | URL | Message | Buffer | object, params?: (object | string | URLSearchParams) | undefined, bytes?: (ArrayBuffer | Uint8Array | string) | null): Message; + static RuntimeInitEvent: typeof RuntimeInitEvent; /** - * Predicate to determine if `input` is valid for constructing - * a new `Message` instance. - * @param {string|URL|Message|Buffer|object} input - * @return {boolean} - * @ignore + * An array of all global events listened to in various hooks */ - static isValidInput(input: string | URL | Message | Buffer | object): boolean; + get globalEvents(): string[]; /** - * `Message` class constructor. - * @protected - * @param {string|URL} input - * @param {(object|Uint8Array)?} [bytes] - * @ignore + * Reference to global object + * @type {object} */ - protected constructor(); + get global(): any; /** - * @type {Uint8Array?} - * @ignore + * Returns `document` in global. + * @type {Document} */ - bytes: Uint8Array | null; + get document(): Document; /** - * Computed IPC message name. - * @type {string} - * @ignore + * Returns `document` in global. + * @type {Window} */ - get command(): string; + get window(): Window; /** - * Computed IPC message name. - * @type {string} - * @ignore + * Predicate for determining if the global document is ready. + * @type {boolean} */ - get name(): string; + get isDocumentReady(): boolean; /** - * Computed `id` value for the command. - * @type {string} - * @ignore + * Predicate for determining if the global object is ready. + * @type {boolean} */ - get id(): string; + get isGlobalReady(): boolean; /** - * Computed `seq` (sequence) value for the command. - * @type {string} - * @ignore + * Predicate for determining if the runtime is ready. + * @type {boolean} */ - get seq(): string; + get isRuntimeReady(): boolean; /** - * Computed message value potentially given in message parameters. - * This value is automatically decoded, but not treated as JSON. - * @type {string} - * @ignore + * Predicate for determining if everything is ready. + * @type {boolean} */ - get value(): string; + get isReady(): boolean; /** - * Computed `index` value for the command potentially referring to - * the window index the command is scoped to or originating from. If not - * specified in the message parameters, then this value defaults to `-1`. - * @type {number} - * @ignore + * Predicate for determining if the runtime is working online. + * @type {boolean} */ - get index(): number; + get isOnline(): boolean; /** - * Computed value parsed as JSON. This value is `null` if the value is not present - * or it is invalid JSON. - * @type {object?} - * @ignore + * Predicate for determining if the runtime is in a Worker context. + * @type {boolean} */ - get json(): any; + get isWorkerContext(): boolean; /** - * Computed readonly object of message parameters. - * @type {object} - * @ignore + * Predicate for determining if the runtime is in a Window context. + * @type {boolean} */ - get params(): any; + get isWindowContext(): boolean; /** - * Gets unparsed message parameters. - * @type {Array>} - * @ignore + * Wait for a hook event to occur. + * @template {Event | T extends Event} + * @param {string|function} nameOrFunction + * @param {WaitOptions=} [options] + * @return {Promise} */ - get rawParams(): string[][]; + wait(nameOrFunction: string | Function, options?: WaitOptions | undefined): Promise; /** - * Returns computed parameters as entries - * @return {Array>} - * @ignore + * Wait for the global Window, Document, and Runtime to be ready. + * The callback function is called exactly once. + * @param {function} callback + * @return {function} */ - entries(): Array>; + onReady(callback: Function): Function; /** - * Set a parameter `value` by `key`. - * @param {string} key - * @param {any} value - * @ignore + * Wait for the global Window and Document to be ready. The callback + * function is called exactly once. + * @param {function} callback + * @return {function} */ - set(key: string, value: any): any; + onLoad(callback: Function): Function; /** - * Get a parameter value by `key`. - * @param {string} key - * @param {any} defaultValue - * @return {any} - * @ignore + * Wait for the runtime to be ready. The callback + * function is called exactly once. + * @param {function} callback + * @return {function} */ - get(key: string, defaultValue: any): any; + onInit(callback: Function): Function; /** - * Delete a parameter by `key`. - * @param {string} key - * @return {boolean} - * @ignore + * Calls callback when a global exception occurs. + * 'error', 'messageerror', and 'unhandledrejection' events are handled here. + * @param {function} callback + * @return {function} */ - delete(key: string): boolean; + onError(callback: Function): Function; /** - * Computed parameter keys. - * @return {Array} - * @ignore + * Subscribes to the global data pipe calling callback when + * new data is emitted on the global Window. + * @param {function} callback + * @return {function} */ - keys(): Array; + onData(callback: Function): Function; /** - * Computed parameter values. - * @return {Array} - * @ignore + * Subscribes to global messages likely from an external `postMessage` + * invocation. + * @param {function} callback + * @return {function} */ - values(): Array; + onMessage(callback: Function): Function; /** - * Predicate to determine if parameter `key` is present in parameters. - * @param {string} key - * @return {boolean} - * @ignore + * Calls callback when runtime is working online. + * @param {function} callback + * @return {function} */ - has(key: string): boolean; + onOnline(callback: Function): Function; + /** + * Calls callback when runtime is not working online. + * @param {function} callback + * @return {function} + */ + onOffline(callback: Function): Function; + /** + * Calls callback when runtime user preferred language has changed. + * @param {function} callback + * @return {function} + */ + onLanguageChange(callback: Function): Function; + /** + * Calls callback when an application permission has changed. + * @param {function} callback + * @return {function} + */ + onPermissionChange(callback: Function): Function; + /** + * Calls callback in response to a displayed `Notification`. + * @param {function} callback + * @return {function} + */ + onNotificationResponse(callback: Function): Function; + /** + * Calls callback when a `Notification` is presented. + * @param {function} callback + * @return {function} + */ + onNotificationPresented(callback: Function): Function; + /** + * Calls callback when a `ApplicationURL` is opened. + * @param {function} callback + * @return {function} + */ + onApplicationURL(callback: Function): Function; + #private; } + export default hooks; + export type WaitOptions = { + signal?: AbortSignal; + }; /** - * A result type used internally for handling - * IPC result values from the native layer that are in the form - * of `{ err?, data? }`. The `data` and `err` properties on this - * type of object are in tuple form and be accessed at `[data?,err?]` + * `Hooks` single instance. * @ignore */ - export class Result { + const hooks: Hooks; +} + +declare module "socket:fs/watcher" { + /** + * A container for a file system path watcher. + */ + export class Watcher extends EventEmitter { /** - * Creates a `Result` instance from input that may be an object - * like `{ err?, data? }`, an `Error` instance, or just `data`. - * @param {(object|Error|any)?} result - * @param {Error|object} [maybeError] - * @param {string} [maybeSource] - * @param {object|string|Headers} [maybeHeaders] - * @return {Result} + * `Watcher` class constructor. * @ignore + * @param {string} path + * @param {object=} [options] + * @param {AbortSignal=} [options.signal} + * @param {string|number|bigint=} [options.id] + * @param {string=} [options.encoding = 'utf8'] */ - static from(result: (object | Error | any) | null, maybeError?: Error | object, maybeSource?: string, maybeHeaders?: object | string | Headers): Result; + constructor(path: string, options?: object | undefined); /** - * `Result` class constructor. - * @private - * @param {string?} [id = null] - * @param {Error?} [err = null] - * @param {object?} [data = null] - * @param {string?} [source = null] - * @param {(object|string|Headers)?} [headers = null] + * The underlying `fs.Watcher` resource id. * @ignore + * @type {string} */ - private constructor(); + id: string; /** - * The unique ID for this result. + * The path the `fs.Watcher` is watching * @type {string} - * @ignore */ - id: string; + path: string; /** - * An optional error in the result. - * @type {Error?} - * @ignore + * `true` if closed, otherwise `false. + * @type {boolean} */ - err: Error | null; + closed: boolean; /** - * Result data if given. - * @type {(string|object|Uint8Array)?} - * @ignore + * `true` if aborted, otherwise `false`. + * @type {boolean} */ - data: (string | object | Uint8Array) | null; + aborted: boolean; /** - * The source of this result. - * @type {string?} - * @ignore + * The encoding of the `filename` + * @type {'utf8'|'buffer'} */ - source: string | null; + encoding: 'utf8' | 'buffer'; /** - * Result headers, if given. - * @type {Headers?} - * @ignore + * A `AbortController` `AbortSignal` for async aborts. + * @type {AbortSignal?} */ - headers: Headers | null; + signal: AbortSignal | null; /** - * Computed result length. + * Internal event listener cancellation. * @ignore + * @type {function?} */ - get length(): any; + stopListening: Function | null; /** + * Internal starter for watcher. * @ignore */ - toJSON(): { - headers: { - [k: string]: string; - }; - source: string; - data: any; - err: { - name: string; - message: string; - stack?: string; - cause?: unknown; - type: any; - code: any; - }; - }; + start(): Promise; /** - * Generator for an `Iterable` interface over this instance. + * Closes watcher and stops listening for changes. + * @return {Promise} + */ + close(): Promise; + /** + * Implements the `AsyncIterator` (`Symbol.asyncIterator`) iterface. * @ignore + * @return {AsyncIterator<{ eventType: string, filename: string }>} */ - [Symbol.iterator](): Generator; + [Symbol.asyncIterator](): AsyncIterator<{ + eventType: string; + filename: string; + }>; + #private; } - /** - * @ignore - */ - export const primordials: any; - export default exports; - import { Buffer } from "socket:buffer"; - import { URL } from "socket:url/index"; - import * as exports from "socket:ipc"; - + export default Watcher; + import { EventEmitter } from "socket:events"; } -declare module "socket:os/constants" { - export type errno = number; +declare module "socket:fs/promises" { /** - * @typedef {number} errno - * @typedef {number} signal + * Asynchronously check access a file. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode} + * @param {string | Buffer | URL} path + * @param {string?} [mode] + * @param {object?} [options] */ + export function access(path: string | Buffer | URL, mode?: string | null, options?: object | null): Promise; /** - * A container for all known "errno" constant values. - * Unsupported values have a default value of `0`. + * @see {@link https://nodejs.org/api/fs.html#fspromiseschmodpath-mode} + * @param {string | Buffer | URL} path + * @param {number} mode + * @returns {Promise} */ - export const errno: any; - export type signal = number; + export function chmod(path: string | Buffer | URL, mode: number): Promise; /** - * A container for all known "signal" constant values. - * Unsupported values have a default value of `0`. + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid + * @return {Promise} */ - export const signal: any; - namespace _default { - export { errno }; - export { signal }; - } - export default _default; -} - -declare module "socket:errno" { + export function chown(path: string, uid: number, gid: number): Promise; /** - * Converts an `errno` code to its corresponding string message. - * @param {import('./os/constants.js').errno} {code} - * @return {string} + * Asynchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @return {Promise} */ - export function toString(code: any): string; + export function copyFile(src: string, dest: string, flags?: number): Promise; /** - * Gets the code for a given 'errno' name. - * @param {string|number} name - * @return {errno} + * Chages ownership of link at `path` with `uid` and `gid. + * @param {string} path + * @param {number} uid + * @param {number} gid + * @return {Promise} + */ + export function lchown(path: string, uid: number, gid: number): Promise; + /** + * Creates a link to `dest` from `dest`. + * @param {string} src + * @param {string} dest + * @return {Promise} + */ + export function link(src: string, dest: string): Promise; + /** + * Asynchronously creates a directory. + * + * @param {string} path - The path to create + * @param {object} [options] - The optional options argument can be an integer specifying mode (permission and sticky bits), or an object with a mode property and a recursive property indicating whether parent directories should be created. Calling fs.mkdir() when path is a directory that exists results in an error only when recursive is false. + * @param {boolean} [options.recursive=false] - Recursively create missing path segments. + * @param {number} [options.mode=0o777] - Set the mode of directory, or missing path segments when recursive is true. + * @return {Promise} - Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. + */ + export function mkdir(path: string, options?: { + recursive?: boolean; + mode?: number; + }): Promise; + /** + * Asynchronously open a file. + * @see {@link https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode } + * + * @param {string | Buffer | URL} path + * @param {string=} flags - default: 'r' + * @param {number=} mode - default: 0o666 + * @return {Promise} + */ + export function open(path: string | Buffer | URL, flags?: string | undefined, mode?: number | undefined): Promise; + /** + * @see {@link https://nodejs.org/api/fs.html#fspromisesopendirpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {string?} [options.encoding = 'utf8'] + * @param {number?} [options.bufferSize = 32] + * @return {Promise} + */ + export function opendir(path: string | Buffer | URL, options?: object | null): Promise; + /** + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options} + * @param {string | Buffer | URL} path + * @param {object?} options + * @param {string?} [options.encoding = 'utf8'] + * @param {boolean?} [options.withFileTypes = false] + */ + export function readdir(path: string | Buffer | URL, options: object | null): Promise; + /** + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options} + * @param {string} path + * @param {object?} [options] + * @param {(string|null)?} [options.encoding = null] + * @param {string?} [options.flag = 'r'] + * @param {AbortSignal?} [options.signal] + * @return {Promise} + */ + export function readFile(path: string, options?: object | null): Promise; + /** + * Reads link at `path` + * @param {string} path + * @return {Promise} + */ + export function readlink(path: string): Promise; + /** + * Computes real path for `path` + * @param {string} path + * @return {Promise} + */ + export function realpath(path: string): Promise; + /** + * Renames file or directory at `src` to `dest`. + * @param {string} src + * @param {string} dest + * @return {Promise} + */ + export function rename(src: string, dest: string): Promise; + /** + * Removes directory at `path`. + * @param {string} path + * @return {Promise} */ - export function getCode(name: string | number): errno; + export function rmdir(path: string): Promise; /** - * Gets the name for a given 'errno' code - * @return {string} - * @param {string|number} code + * Get the stats of a file + * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {boolean?} [options.bigint = false] + * @return {Promise} */ - export function getName(code: string | number): string; + export function stat(path: string | Buffer | URL, options?: object | null): Promise; /** - * Gets the message for a 'errno' code. - * @param {number|string} code - * @return {string} + * Get the stats of a symbolic link. + * @see {@link https://nodejs.org/api/fs.html#fspromiseslstatpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {boolean?} [options.bigint = false] + * @return {Promise} */ - export function getMessage(code: number | string): string; + export function lstat(path: string | Buffer | URL, options?: object | null): Promise; /** - * @typedef {import('./os/constants.js').errno} errno + * Creates a symlink of `src` at `dest`. + * @param {string} src + * @param {string} dest + * @return {Promise} */ - export const E2BIG: any; - export const EACCES: any; - export const EADDRINUSE: any; - export const EADDRNOTAVAIL: any; - export const EAFNOSUPPORT: any; - export const EAGAIN: any; - export const EALREADY: any; - export const EBADF: any; - export const EBADMSG: any; - export const EBUSY: any; - export const ECANCELED: any; - export const ECHILD: any; - export const ECONNABORTED: any; - export const ECONNREFUSED: any; - export const ECONNRESET: any; - export const EDEADLK: any; - export const EDESTADDRREQ: any; - export const EDOM: any; - export const EDQUOT: any; - export const EEXIST: any; - export const EFAULT: any; - export const EFBIG: any; - export const EHOSTUNREACH: any; - export const EIDRM: any; - export const EILSEQ: any; - export const EINPROGRESS: any; - export const EINTR: any; - export const EINVAL: any; - export const EIO: any; - export const EISCONN: any; - export const EISDIR: any; - export const ELOOP: any; - export const EMFILE: any; - export const EMLINK: any; - export const EMSGSIZE: any; - export const EMULTIHOP: any; - export const ENAMETOOLONG: any; - export const ENETDOWN: any; - export const ENETRESET: any; - export const ENETUNREACH: any; - export const ENFILE: any; - export const ENOBUFS: any; - export const ENODATA: any; - export const ENODEV: any; - export const ENOENT: any; - export const ENOEXEC: any; - export const ENOLCK: any; - export const ENOLINK: any; - export const ENOMEM: any; - export const ENOMSG: any; - export const ENOPROTOOPT: any; - export const ENOSPC: any; - export const ENOSR: any; - export const ENOSTR: any; - export const ENOSYS: any; - export const ENOTCONN: any; - export const ENOTDIR: any; - export const ENOTEMPTY: any; - export const ENOTSOCK: any; - export const ENOTSUP: any; - export const ENOTTY: any; - export const ENXIO: any; - export const EOPNOTSUPP: any; - export const EOVERFLOW: any; - export const EPERM: any; - export const EPIPE: any; - export const EPROTO: any; - export const EPROTONOSUPPORT: any; - export const EPROTOTYPE: any; - export const ERANGE: any; - export const EROFS: any; - export const ESPIPE: any; - export const ESRCH: any; - export const ESTALE: any; - export const ETIME: any; - export const ETIMEDOUT: any; - export const ETXTBSY: any; - export const EWOULDBLOCK: any; - export const EXDEV: any; - export const strings: any; - export { constants }; - namespace _default { - export { constants }; - export { strings }; - export { toString }; - export { getCode }; - export { getMessage }; - } - export default _default; - export type errno = import("socket:os/constants").errno; - import { errno as constants } from "socket:os/constants"; -} - -declare module "socket:errors" { - export default exports; - export const ABORT_ERR: any; - export const ENCODING_ERR: any; - export const INVALID_ACCESS_ERR: any; - export const INDEX_SIZE_ERR: any; - export const NETWORK_ERR: any; - export const NOT_ALLOWED_ERR: any; - export const NOT_FOUND_ERR: any; - export const NOT_SUPPORTED_ERR: any; - export const OPERATION_ERR: any; - export const SECURITY_ERR: any; - export const TIMEOUT_ERR: any; + export function symlink(src: string, dest: string, type?: any): Promise; /** - * An `AbortError` is an error type thrown in an `onabort()` level 0 - * event handler on an `AbortSignal` instance. + * Unlinks (removes) file at `path`. + * @param {string} path + * @return {Promise} */ - export class AbortError extends Error { - /** - * The code given to an `ABORT_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `AbortError` class constructor. - * @param {AbortSignal|string} reasonOrSignal - * @param {AbortSignal=} [signal] - */ - constructor(reason: any, signal?: AbortSignal | undefined, ...args: any[]); - signal: AbortSignal; - get name(): string; - get code(): string; - } + export function unlink(path: string): Promise; /** - * An `BadRequestError` is an error type thrown in an `onabort()` level 0 - * event handler on an `BadRequestSignal` instance. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options} + * @param {string | Buffer | URL | FileHandle} path - filename or FileHandle + * @param {string|Buffer|Array|DataView|TypedArray} data + * @param {object?} [options] + * @param {string|null} [options.encoding = 'utf8'] + * @param {number} [options.mode = 0o666] + * @param {string} [options.flag = 'w'] + * @param {AbortSignal?} [options.signal] + * @return {Promise} */ - export class BadRequestError extends Error { - /** - * The default code given to a `BadRequestError` - */ - static get code(): number; - /** - * `BadRequestError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function writeFile(path: string | Buffer | URL | FileHandle, data: string | Buffer | any[] | DataView | TypedArray, options?: object | null): Promise; /** - * An `EncodingError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Watch for changes at `path` calling `callback` + * @param {string} + * @param {function|object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {AbortSignal=} [options.signal] + * @return {Watcher} */ - export class EncodingError extends Error { - /** - * The code given to an `ENCODING_ERR` `DOMException`. - */ - static get code(): any; - /** - * `EncodingError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function watch(path: any, options?: (Function | object) | undefined): Watcher; + export type Stats = any; + export default exports; + export type Buffer = import("socket:buffer").Buffer; + export type TypedArray = Uint8Array | Int8Array; + import { FileHandle } from "socket:fs/handle"; + import { Dir } from "socket:fs/dir"; + import { Stats } from "socket:fs/stats"; + import { Watcher } from "socket:fs/watcher"; + import * as constants from "socket:fs/constants"; + import { DirectoryHandle } from "socket:fs/handle"; + import { Dirent } from "socket:fs/dir"; + import fds from "socket:fs/fds"; + import { ReadStream } from "socket:fs/stream"; + import { WriteStream } from "socket:fs/stream"; + import * as exports from "socket:fs/promises"; + + export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, ReadStream, Watcher, WriteStream }; +} + +declare module "socket:fs/index" { /** - * An error type derived from an `errno` code. + * Asynchronously check access a file for a given mode calling `callback` + * upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?|function(Error?)?} [mode = F_OK(0)] + * @param {function(Error?)?} [callback] */ - export class ErrnoError extends Error { - static get code(): string; - static errno: any; - /** - * `ErrnoError` class constructor. - * @param {import('./errno').errno|string} code - */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); - get name(): string; - get code(): number; - #private; - } + export function access(path: string | Buffer | URL, mode: any, callback?: ((arg0: Error | null) => any) | null): void; + /** + * Synchronously check access a file for a given mode calling `callback` + * upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?} [mode = F_OK(0)] + */ + export function accessSync(path: string | Buffer | URL, mode?: string | null): boolean; /** - * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] */ - export class FinalizationRegistryCallbackError extends Error { - /** - * The default code given to an `FinalizationRegistryCallbackError` - */ - static get code(): number; - /** - * `FinalizationRegistryCallbackError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function exists(path: string | Buffer | URL, callback?: ((arg0: boolean) => any) | null): void; /** - * An `IllegalConstructorError` is an error type thrown when a constructor is - * called for a class constructor when it shouldn't be. + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] */ - export class IllegalConstructorError extends TypeError { - /** - * The default code given to an `IllegalConstructorError` - */ - static get code(): number; - /** - * `IllegalConstructorError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function existsSync(path: string | Buffer | URL): boolean; /** - * An `IndexSizeError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Asynchronously changes the permissions of a file. + * No arguments other than a possible exception are given to the completion callback + * + * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * + * @param {string | Buffer | URL} path + * @param {number} mode + * @param {function(Error?)} callback */ - export class IndexSizeError extends Error { - /** - * The code given to an `INDEX_SIZE_ERR` `DOMException` - */ - static get code(): any; - /** - * `IndexSizeError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - export const kInternalErrorCode: unique symbol; + export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): void; /** - * An `InternalError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Synchronously changes the permissions of a file. + * + * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * @param {string | Buffer | URL} path + * @param {number} mode */ - export class InternalError extends Error { - /** - * The default code given to an `InternalError` - */ - static get code(): number; - /** - * `InternalError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, code?: number, ...args: any[]); - get name(): string; - /** - * @param {number|string} - */ - set code(code: string | number); - /** - * @type {number|string} - */ - get code(): string | number; - [exports.kInternalErrorCode]: number; - } + export function chmodSync(path: string | Buffer | URL, mode: number): void; /** - * An `InvalidAccessError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid + * @param {function} callback */ - export class InvalidAccessError extends Error { - /** - * The code given to an `INVALID_ACCESS_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `InvalidAccessError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function chown(path: string, uid: number, gid: number, callback: Function): void; /** - * An `NetworkError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid */ - export class NetworkError extends Error { - /** - * The code given to an `NETWORK_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `NetworkError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function chownSync(path: string, uid: number, gid: number): void; /** - * An `NotAllowedError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Asynchronously close a file descriptor calling `callback` upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsclosefd-callback} + * @param {number} fd + * @param {function(Error?)?} [callback] */ - export class NotAllowedError extends Error { - /** - * The code given to an `NOT_ALLOWED_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `NotAllowedError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function close(fd: number, callback?: ((arg0: Error | null) => any) | null): void; /** - * An `NotFoundError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Asynchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @param {function(Error=)=} [callback] - The function to call after completion. + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ - export class NotFoundError extends Error { - /** - * The code given to an `NOT_FOUND_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `NotFoundError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function copyFile(src: string, dest: string, flags?: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; /** - * An `NotSupportedError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Synchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} */ - export class NotSupportedError extends Error { - /** - * The code given to an `NOT_SUPPORTED_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `NotSupportedError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function copyFileSync(src: string, dest: string, flags?: number): void; /** - * An `ModuleNotFoundError` is an error type thrown when an imported or - * required module is not found. + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @returns {ReadStream} */ - export class ModuleNotFoundError extends exports.NotFoundError { - /** - * `ModuleNotFoundError` class constructor. - * @param {string} message - * @param {string[]=} [requireStack] - */ - constructor(message: string, requireStack?: string[] | undefined); - requireStack: string[]; - } + export function createReadStream(path: string | Buffer | URL, options?: object | null): ReadStream; /** - * An `OperationError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @returns {WriteStream} */ - export class OperationError extends Error { - /** - * The code given to an `OPERATION_ERR` `DOMException` - */ - static get code(): any; - /** - * `OperationError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function createWriteStream(path: string | Buffer | URL, options?: object | null): WriteStream; /** - * An `SecurityError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * Invokes the callback with the for the file descriptor. See + * the POSIX fstat(2) documentation for more detail. + * + * @see {@link https://nodejs.org/api/fs.html#fsfstatfd-options-callback} + * + * @param {number} fd - A file descriptor. + * @param {object?|function?} [options] - An options object. + * @param {function?} callback - The function to call after completion. */ - export class SecurityError extends Error { - /** - * The code given to an `SECURITY_ERR` `DOMException` - */ - static get code(): any; - /** - * `SecurityError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } + export function fstat(fd: number, options: any, callback: Function | null): void; /** - * An `TimeoutError` is an error type thrown when an operation timesout. + * Request that all data for the open file descriptor is flushed + * to the storage device. + * @param {number} fd - A file descriptor. + * @param {function} callback - The function to call after completion. */ - export class TimeoutError extends Error { - /** - * The code given to an `TIMEOUT_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `TimeoutError` class constructor. - * @param {string} message - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - import * as exports from "socket:errors"; - -} - -declare module "socket:util/types" { + export function fsync(fd: number, callback: Function): void; /** - * Returns `true` if input is a plan `Object` instance. - * @param {any} input - * @return {boolean} + * Truncates the file up to `offset` bytes. + * @param {number} fd - A file descriptor. + * @param {number=|function} [offset = 0] + * @param {function?} callback - The function to call after completion. */ - export function isPlainObject(input: any): boolean; + export function ftruncate(fd: number, offset: any, callback: Function | null): void; /** - * Returns `true` if input is an `AsyncFunction` - * @param {any} input - * @return {boolean} + * Chages ownership of link at `path` with `uid` and `gid. + * @param {string} path + * @param {number} uid + * @param {number} gid + * @param {function} callback */ - export function isAsyncFunction(input: any): boolean; + export function lchown(path: string, uid: number, gid: number, callback: Function): void; /** - * Returns `true` if input is an `Function` - * @param {any} input - * @return {boolean} + * Creates a link to `dest` from `src`. + * @param {string} src + * @param {string} dest + * @param {function} */ - export function isFunction(input: any): boolean; + export function link(src: string, dest: string, callback: any): void; /** - * Returns `true` if input is an `AsyncFunction` object. - * @param {any} input - * @return {boolean} + * @ignore */ - export function isAsyncFunctionObject(input: any): boolean; + export function mkdir(path: any, options: any, callback: any): void; /** - * Returns `true` if input is an `Function` object. - * @param {any} input - * @return {boolean} + * @ignore + * @param {string|URL} path + * @param {object=} [options] */ - export function isFunctionObject(input: any): boolean; + export function mkdirSync(path: string | URL, options?: object | undefined): void; /** - * Always returns `false`. - * @param {any} input - * @return {boolean} + * Asynchronously open a file calling `callback` upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?} [flags = 'r'] + * @param {string?} [mode = 0o666] + * @param {object?|function?} [options] + * @param {function(Error?, number?)?} [callback] */ - export function isExternal(input: any): boolean; + export function open(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any, callback?: ((arg0: Error | null, arg1: number | null) => any) | null): void; /** - * Returns `true` if input is a `Date` instance. - * @param {any} input - * @return {boolean} + * Asynchronously open a directory calling `callback` upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL} path + * @param {object?|function(Error?, Dir?)} [options] + * @param {string?} [options.encoding = 'utf8'] + * @param {boolean?} [options.withFileTypes = false] + * @param {function(Error?, Dir?)?} callback */ - export function isDate(input: any): boolean; + export function opendir(path: string | Buffer | URL, options: {}, callback: ((arg0: Error | null, arg1: Dir | null) => any) | null): void; /** - * Returns `true` if input is an `arguments` object. - * @param {any} input - * @return {boolean} + * Asynchronously read from an open file descriptor. + * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} + * @param {number} fd + * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. + * @param {number} offset - The position in buffer to write the data to. + * @param {number} length - The number of bytes to read. + * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. + * @param {function(Error?, number?, Buffer?)} callback */ - export function isArgumentsObject(input: any): boolean; + export function read(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; /** - * Returns `true` if input is a `BigInt` object. - * @param {any} input - * @return {boolean} + * Asynchronously write to an open file descriptor. + * @see {@link https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback} + * @param {number} fd + * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. + * @param {number} offset - The position in buffer to write the data to. + * @param {number} length - The number of bytes to read. + * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. + * @param {function(Error?, number?, Buffer?)} callback */ - export function isBigIntObject(input: any): boolean; + export function write(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; /** - * Returns `true` if input is a `Boolean` object. - * @param {any} input - * @return {boolean} + * Asynchronously read all entries in a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL } path + * @param {object?|function(Error?, object[])} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {boolean?} [options.withFileTypes ? false] + * @param {function(Error?, object[])} callback */ - export function isBooleanObject(input: any): boolean; + export function readdir(path: string | Buffer | URL, options: {}, callback: (arg0: Error | null, arg1: object[]) => any): void; /** - * Returns `true` if input is a `Number` object. - * @param {any} input - * @return {boolean} + * @param {string | Buffer | URL | number } path + * @param {object?|function(Error?, Buffer?)} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Buffer?)} callback */ - export function isNumberObject(input: any): boolean; + export function readFile(path: string | Buffer | URL | number, options: {}, callback: (arg0: Error | null, arg1: Buffer | null) => any): void; /** - * Returns `true` if input is a `String` object. - * @param {any} input - * @return {boolean} + * @param {string | Buffer | URL | number } path + * @param {object?|function(Error?, Buffer?)} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] */ - export function isStringObject(input: any): boolean; + export function readFileSync(path: string | Buffer | URL | number, options?: {}): any; /** - * Returns `true` if input is a `Symbol` object. - * @param {any} input - * @return {boolean} + * Reads link at `path` + * @param {string} path + * @param {function(err, string)} callback */ - export function isSymbolObject(input: any): boolean; + export function readlink(path: string, callback: (arg0: err, arg1: string) => any): void; /** - * Returns `true` if input is native `Error` instance. - * @param {any} input - * @return {boolean} + * Computes real path for `path` + * @param {string} path + * @param {function(err, string)} callback */ - export function isNativeError(input: any): boolean; + export function realpath(path: string, callback: (arg0: err, arg1: string) => any): void; /** - * Returns `true` if input is a `RegExp` instance. - * @param {any} input - * @return {boolean} + * Renames file or directory at `src` to `dest`. + * @param {string} src + * @param {string} dest + * @param {function} callback */ - export function isRegExp(input: any): boolean; + export function rename(src: string, dest: string, callback: Function): void; /** - * Returns `true` if input is a `GeneratorFunction`. - * @param {any} input - * @return {boolean} + * Removes directory at `path`. + * @param {string} path + * @param {function} callback */ - export function isGeneratorFunction(input: any): boolean; + export function rmdir(path: string, callback: Function): void; /** - * Returns `true` if input is an `AsyncGeneratorFunction`. - * @param {any} input - * @return {boolean} + * Synchronously get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] */ - export function isAsyncGeneratorFunction(input: any): boolean; + export function statSync(path: string | Buffer | URL | number, options: object | null): promises.Stats; /** - * Returns `true` if input is an instance of a `Generator`. - * @param {any} input - * @return {boolean} + * Get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback */ - export function isGeneratorObject(input: any): boolean; + export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; /** - * Returns `true` if input is a `Promise` instance. - * @param {any} input - * @return {boolean} + * Get the stats of a symbolic link + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback */ - export function isPromise(input: any): boolean; + export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; /** - * Returns `true` if input is a `Map` instance. - * @param {any} input - * @return {boolean} + * Creates a symlink of `src` at `dest`. + * @param {string} src + * @param {string} dest */ - export function isMap(input: any): boolean; + export function symlink(src: string, dest: string, type: any, callback: any): void; /** - * Returns `true` if input is a `Set` instance. - * @param {any} input - * @return {boolean} + * Unlinks (removes) file at `path`. + * @param {string} path + * @param {function} callback */ - export function isSet(input: any): boolean; + export function unlink(path: string, callback: Function): void; /** - * Returns `true` if input is an instance of an `Iterator`. - * @param {any} input - * @return {boolean} + * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?)} callback */ - export function isIterator(input: any): boolean; + export function writeFile(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null, callback: (arg0: Error | null) => any): void; /** - * Returns `true` if input is an instance of an `AsyncIterator`. - * @param {any} input - * @return {boolean} + * Writes data to a file synchronously. + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} */ - export function isAsyncIterator(input: any): boolean; + export function writeFileSync(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null): void; /** - * Returns `true` if input is an instance of a `MapIterator`. - * @param {any} input - * @return {boolean} + * Watch for changes at `path` calling `callback` + * @param {string} + * @param {function|object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {?function} [callback] + * @return {Watcher} */ - export function isMapIterator(input: any): boolean; + export function watch(path: any, options?: (Function | object) | undefined, callback?: Function | null): Watcher; + export default exports; + export type Buffer = import("socket:buffer").Buffer; + export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; + import { ReadStream } from "socket:fs/stream"; + import { WriteStream } from "socket:fs/stream"; + import { Dir } from "socket:fs/dir"; + import * as promises from "socket:fs/promises"; + import { Stats } from "socket:fs/stats"; + import { Watcher } from "socket:fs/watcher"; + import * as constants from "socket:fs/constants"; + import { DirectoryHandle } from "socket:fs/handle"; + import { Dirent } from "socket:fs/dir"; + import fds from "socket:fs/fds"; + import { FileHandle } from "socket:fs/handle"; + import * as exports from "socket:fs/index"; + + export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; +} + +declare module "socket:fs" { + export * from "socket:fs/index"; + export default exports; + import * as exports from "socket:fs/index"; +} + +declare module "socket:external/libsodium/index" { + const _default: any; + export default _default; +} + +declare module "socket:crypto/sodium" { + export {}; +} + +declare module "socket:crypto" { /** - * Returns `true` if input is an instance of a `SetIterator`. - * @param {any} input - * @return {boolean} + * Generate cryptographically strong random values into the `buffer` + * @param {TypedArray} buffer + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues} + * @return {TypedArray} */ - export function isSetIterator(input: any): boolean; + export function getRandomValues(buffer: TypedArray, ...args: any[]): TypedArray; /** - * Returns `true` if input is a `WeakMap` instance. - * @param {any} input - * @return {boolean} + * Generate a random 64-bit number. + * @returns {BigInt} - A random 64-bit number. */ - export function isWeakMap(input: any): boolean; + export function rand64(): BigInt; /** - * Returns `true` if input is a `WeakSet` instance. - * @param {any} input - * @return {boolean} + * Generate `size` random bytes. + * @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1. + * @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes. */ - export function isWeakSet(input: any): boolean; + export function randomBytes(size: number): Buffer; /** - * Returns `true` if input is an `ArrayBuffer` instance. - * @param {any} input - * @return {boolean} + * @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512` + * @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview. + * @returns {Promise} - A promise that resolves with an instance of socket.Buffer with the hash. */ - export function isArrayBuffer(input: any): boolean; + export function createDigest(algorithm: string, buf: any): Promise; /** - * Returns `true` if input is an `DataView` instance. - * @param {any} input - * @return {boolean} + * A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c + * that works on strings and `ArrayBuffer` views (typed arrays) + * @param {string|Uint8Array|ArrayBuffer} value + * @param {number=} [seed = 0] + * @return {number} */ - export function isDataView(input: any): boolean; + export function murmur3(value: string | Uint8Array | ArrayBuffer, seed?: number | undefined): number; /** - * Returns `true` if input is a `SharedArrayBuffer`. - * This will always return `false` if a `SharedArrayBuffer` - * type is not available. - * @param {any} input - * @return {boolean} + * @typedef {Uint8Array|Int8Array} TypedArray */ - export function isSharedArrayBuffer(input: any): boolean; /** - * Not supported. This function will return `false` always. - * @param {any} input - * @return {boolean} + * WebCrypto API + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto} */ - export function isProxy(input: any): boolean; + export let webcrypto: any; /** - * Returns `true` if input looks like a module namespace object. - * @param {any} input - * @return {boolean} + * A promise that resolves when all internals to be loaded/ready. + * @type {Promise} */ - export function isModuleNamespaceObject(input: any): boolean; + export const ready: Promise; /** - * Returns `true` if input is an `ArrayBuffer` of `SharedArrayBuffer`. - * @param {any} input - * @return {boolean} + * Maximum total size of random bytes per page */ - export function isAnyArrayBuffer(input: any): boolean; + export const RANDOM_BYTES_QUOTA: number; /** - * Returns `true` if input is a "boxed" primitive. - * @param {any} input - * @return {boolean} + * Maximum total size for random bytes. */ - export function isBoxedPrimitive(input: any): boolean; + export const MAX_RANDOM_BYTES: 281474976710655; /** - * Returns `true` if input is an `ArrayBuffer` view. - * @param {any} input - * @return {boolean} + * Maximum total amount of allocated per page of bytes (max/quota) */ - export function isArrayBufferView(input: any): boolean; + export const MAX_RANDOM_BYTES_PAGES: number; + export default exports; + export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; + export namespace sodium { + let ready: Promise; + } + import * as exports from "socket:crypto"; + +} + +declare module "socket:ipc" { + export function maybeMakeError(error: any, caller: any): any; /** - * Returns `true` if input is a `TypedArray` instance. - * @param {any} input - * @return {boolean} + * Parses `seq` as integer value + * @param {string|number} seq + * @param {object=} [options] + * @param {boolean} [options.bigint = false] + * @ignore */ - export function isTypedArray(input: any): boolean; + export function parseSeq(seq: string | number, options?: object | undefined): number | bigint; /** - * Returns `true` if input is an `Uint8Array` instance. - * @param {any} input + * If `debug.enabled === true`, then debug output will be printed to console. + * @param {(boolean)} [enable] * @return {boolean} + * @ignore */ - export function isUint8Array(input: any): boolean; + export function debug(enable?: (boolean)): boolean; + export namespace debug { + let enabled: any; + function log(...args: any[]): any; + } /** - * Returns `true` if input is an `Uint8ClampedArray` instance. - * @param {any} input - * @return {boolean} + * Find transfers for an in worker global `postMessage` + * that is proxied to the main thread. + * @ignore */ - export function isUint8ClampedArray(input: any): boolean; + export function findMessageTransfers(transfers: any, object: any): any; /** - * Returns `true` if input is an `Uint16Array` instance. - * @param {any} input - * @return {boolean} + * @ignore */ - export function isUint16Array(input: any): boolean; + export function postMessage(message: any, ...args: any[]): any; /** - * Returns `true` if input is an `Uint32Array` instance. - * @param {any} input - * @return {boolean} + * Waits for the native IPC layer to be ready and exposed on the + * global window object. + * @ignore */ - export function isUint32Array(input: any): boolean; + export function ready(): Promise; /** - * Returns `true` if input is an Int8Array`` instance. - * @param {any} input - * @return {boolean} + * Sends a synchronous IPC command over XHR returning a `Result` + * upon success or error. + * @param {string} command + * @param {any?} [value] + * @param {object?} [options] + * @return {Result} + * @ignore */ - export function isInt8Array(input: any): boolean; + export function sendSync(command: string, value?: any | null, options?: object | null, buffer: any): Result; /** - * Returns `true` if input is an `Int16Array` instance. - * @param {any} input - * @return {boolean} + * Emit event to be dispatched on `window` object. + * @param {string} name + * @param {any} value + * @param {EventTarget=} [target = window] + * @param {Object=} options */ - export function isInt16Array(input: any): boolean; + export function emit(name: string, value: any, target?: EventTarget | undefined, options?: any | undefined): Promise; /** - * Returns `true` if input is an `Int32Array` instance. - * @param {any} input - * @return {boolean} + * Resolves a request by `seq` with possible value. + * @param {string} seq + * @param {any} value + * @ignore */ - export function isInt32Array(input: any): boolean; + export function resolve(seq: string, value: any): Promise; /** - * Returns `true` if input is an `Float32Array` instance. - * @param {any} input - * @return {boolean} + * Sends an async IPC command request with parameters. + * @param {string} command + * @param {any=} value + * @param {object=} [options] + * @param {boolean=} [options.cache=false] + * @param {boolean=} [options.bytes=false] + * @return {Promise} */ - export function isFloat32Array(input: any): boolean; + export function send(command: string, value?: any | undefined, options?: object | undefined): Promise; /** - * Returns `true` if input is an `Float64Array` instance. - * @param {any} input - * @return {boolean} + * Sends an async IPC command request with parameters and buffered bytes. + * @param {string} command + * @param {any=} value + * @param {(Buffer|Uint8Array|ArrayBuffer|string|Array)=} buffer + * @param {object=} options + * @ignore */ - export function isFloat64Array(input: any): boolean; + export function write(command: string, value?: any | undefined, buffer?: (Buffer | Uint8Array | ArrayBuffer | string | any[]) | undefined, options?: object | undefined): Promise; /** - * Returns `true` if input is an `BigInt64Array` instance. - * @param {any} input - * @return {boolean} + * Sends an async IPC command request with parameters requesting a response + * with buffered bytes. + * @param {string} command + * @param {any=} value + * @param {object=} options + * @ignore */ - export function isBigInt64Array(input: any): boolean; + export function request(command: string, value?: any | undefined, options?: object | undefined): Promise; /** - * Returns `true` if input is an `BigUint64Array` instance. - * @param {any} input - * @return {boolean} + * Factory for creating a proxy based IPC API. + * @param {string} domain + * @param {(function|object)=} ctx + * @param {string=} [ctx.default] + * @return {Proxy} + * @ignore */ - export function isBigUint64Array(input: any): boolean; + export function createBinding(domain: string, ctx?: (Function | object) | undefined): ProxyConstructor; /** + * Represents an OK IPC status. * @ignore - * @param {any} input - * @return {boolean} */ - export function isKeyObject(input: any): boolean; + export const OK: 0; /** - * Returns `true` if input is a `CryptoKey` instance. - * @param {any} input - * @return {boolean} + * Represents an ERROR IPC status. + * @ignore */ - export function isCryptoKey(input: any): boolean; + export const ERROR: 1; /** - * Returns `true` if input is an `Array`. - * @param {any} input - * @return {boolean} + * Timeout in milliseconds for IPC requests. + * @ignore */ - export const isArray: any; - export default exports; - import * as exports from "socket:util/types"; - -} - -declare module "socket:mime/index" { + export const TIMEOUT: number; /** - * Look up a MIME type in various MIME databases. - * @param {string} query - * @return {Promise} + * Symbol for the `ipc.debug.enabled` property + * @ignore */ - export function lookup(query: string): Promise; + export const kDebugEnabled: unique symbol; /** - * Look up a MIME type in various MIME databases synchronously. - * @param {string} query - * @return {DatabaseQueryResult[]} + * @ignore */ - export function lookupSync(query: string): DatabaseQueryResult[]; + export class Headers extends globalThis.Headers { + /** + * @ignore + */ + static from(input: any): any; + /** + * @ignore + */ + get length(): number; + /** + * @ignore + */ + toJSON(): { + [k: string]: string; + }; + } + const Message_base: any; /** - * A container for a database lookup query. + * A container for a IPC message based on a `ipc://` URI scheme. + * @ignore */ - export class DatabaseQueryResult { + export class Message extends Message_base { + [x: string]: any; /** - * `DatabaseQueryResult` class constructor. + * The expected protocol for an IPC message. * @ignore - * @param {Database} database - * @param {string} name - * @param {string} mime */ - constructor(database: Database, name: string, mime: string); + static get PROTOCOL(): string; + /** + * Creates a `Message` instance from a variety of input. + * @param {string|URL|Message|Buffer|object} input + * @param {(object|string|URLSearchParams)=} [params] + * @param {(ArrayBuffer|Uint8Array|string)?} [bytes] + * @return {Message} + * @ignore + */ + static from(input: string | URL | Message | Buffer | object, params?: (object | string | URLSearchParams) | undefined, bytes?: (ArrayBuffer | Uint8Array | string) | null): Message; + /** + * Predicate to determine if `input` is valid for constructing + * a new `Message` instance. + * @param {string|URL|Message|Buffer|object} input + * @return {boolean} + * @ignore + */ + static isValidInput(input: string | URL | Message | Buffer | object): boolean; + /** + * `Message` class constructor. + * @protected + * @param {string|URL} input + * @param {(object|Uint8Array)?} [bytes] + * @ignore + */ + protected constructor(); + /** + * @type {Uint8Array?} + * @ignore + */ + bytes: Uint8Array | null; /** + * Computed IPC message name. * @type {string} + * @ignore */ - name: string; + get command(): string; + /** + * Computed IPC message name. + * @type {string} + * @ignore + */ + get name(): string; /** + * Computed `id` value for the command. * @type {string} + * @ignore */ - mime: string; - database: Database; - } - /** - * A container for MIME types by class (audio, video, text, etc) - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} - */ - export class Database { + get id(): string; /** - * `Database` class constructor. - * @param {string} name + * Computed `seq` (sequence) value for the command. + * @type {string} + * @ignore */ - constructor(name: string); + get seq(): string; /** - * The name of the MIME database. + * Computed message value potentially given in message parameters. + * This value is automatically decoded, but not treated as JSON. * @type {string} + * @ignore */ - name: string; + get value(): string; /** - * The URL of the MIME database. - * @type {URL} + * Computed `index` value for the command potentially referring to + * the window index the command is scoped to or originating from. If not + * specified in the message parameters, then this value defaults to `-1`. + * @type {number} + * @ignore + */ + get index(): number; + /** + * Computed value parsed as JSON. This value is `null` if the value is not present + * or it is invalid JSON. + * @type {object?} + * @ignore + */ + get json(): any; + /** + * Computed readonly object of message parameters. + * @type {object} + * @ignore + */ + get params(): any; + /** + * Gets unparsed message parameters. + * @type {Array>} + * @ignore + */ + get rawParams(): string[][]; + /** + * Returns computed parameters as entries + * @return {Array>} + * @ignore + */ + entries(): Array>; + /** + * Set a parameter `value` by `key`. + * @param {string} key + * @param {any} value + * @ignore + */ + set(key: string, value: any): any; + /** + * Get a parameter value by `key`. + * @param {string} key + * @param {any} defaultValue + * @return {any} + * @ignore + */ + get(key: string, defaultValue: any): any; + /** + * Delete a parameter by `key`. + * @param {string} key + * @return {boolean} + * @ignore + */ + delete(key: string): boolean; + /** + * Computed parameter keys. + * @return {Array} + * @ignore + */ + keys(): Array; + /** + * Computed parameter values. + * @return {Array} + * @ignore + */ + values(): Array; + /** + * Predicate to determine if parameter `key` is present in parameters. + * @param {string} key + * @return {boolean} + * @ignore + */ + has(key: string): boolean; + } + /** + * A result type used internally for handling + * IPC result values from the native layer that are in the form + * of `{ err?, data? }`. The `data` and `err` properties on this + * type of object are in tuple form and be accessed at `[data?,err?]` + * @ignore + */ + export class Result { + /** + * Creates a `Result` instance from input that may be an object + * like `{ err?, data? }`, an `Error` instance, or just `data`. + * @param {(object|Error|any)?} result + * @param {Error|object} [maybeError] + * @param {string} [maybeSource] + * @param {object|string|Headers} [maybeHeaders] + * @return {Result} + * @ignore + */ + static from(result: (object | Error | any) | null, maybeError?: Error | object, maybeSource?: string, maybeHeaders?: object | string | Headers): Result; + /** + * `Result` class constructor. + * @private + * @param {string?} [id = null] + * @param {Error?} [err = null] + * @param {object?} [data = null] + * @param {string?} [source = null] + * @param {(object|string|Headers)?} [headers = null] + * @ignore */ - url: URL; + private constructor(); /** - * The mapping of MIME name to the MIME "content type" - * @type {Map} + * The unique ID for this result. + * @type {string} + * @ignore */ - map: Map; + id: string; /** - * An index of MIME "content type" to the MIME name. - * @type {Map} + * An optional error in the result. + * @type {Error?} + * @ignore */ - index: Map; + err: Error | null; /** - * An enumeration of all database entries. - * @return {Array>} + * Result data if given. + * @type {(string|object|Uint8Array)?} + * @ignore */ - entries(): Array>; + data: (string | object | Uint8Array) | null; /** - * Loads database MIME entries into internal map. - * @return {Promise} + * The source of this result. + * @type {string?} + * @ignore */ - load(): Promise; + source: string | null; /** - * Loads database MIME entries synchronously into internal map. + * Result headers, if given. + * @type {Headers?} + * @ignore */ - loadSync(): void; + headers: Headers | null; /** - * Lookup MIME type by name or content type - * @param {string} query - * @return {Promise} + * Computed result length. + * @ignore */ - lookup(query: string): Promise; + get length(): any; /** - * Lookup MIME type by name or content type synchronously. - * @param {string} query - * @return {Promise} + * @ignore */ - lookupSync(query: string): Promise; + toJSON(): { + headers: { + [k: string]: string; + }; + source: string; + data: any; + err: { + name: string; + message: string; + stack?: string; + cause?: unknown; + type: any; + code: any; + }; + }; /** - * Queries database map and returns an array of results - * @param {string} query - * @return {DatabaseQueryResult[]} + * Generator for an `Iterable` interface over this instance. + * @ignore */ - query(query: string): DatabaseQueryResult[]; + [Symbol.iterator](): Generator; } /** - * A database of MIME types for 'application/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} - */ - export const application: Database; - /** - * A database of MIME types for 'audio/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} - */ - export const audio: Database; - /** - * A database of MIME types for 'font/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} - */ - export const font: Database; - /** - * A database of MIME types for 'image/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} - */ - export const image: Database; - /** - * A database of MIME types for 'model/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} - */ - export const model: Database; - /** - * A database of MIME types for 'multipart/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} - */ - export const multipart: Database; - /** - * A database of MIME types for 'text/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} - */ - export const text: Database; - /** - * A database of MIME types for 'video/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} - */ - export const video: Database; - /** - * An array of known MIME databases. Custom databases can be added to this - * array in userspace for lookup with `mime.lookup()` - * @type {Database[]} + * @ignore */ - export const databases: Database[]; - export class MIMEParams extends Map { - constructor(); - constructor(entries?: readonly (readonly [any, any])[]); - constructor(); - constructor(iterable?: Iterable); - } - export class MIMEType { - constructor(input: any); - set type(value: any); - get type(): any; - set subtype(value: any); - get subtype(): any; - get essence(): string; - get params(): any; - toString(): string; - toJSON(): string; - #private; - } - namespace _default { - export { Database }; - export { databases }; - export { lookup }; - export { lookupSync }; - export { MIMEParams }; - export { MIMEType }; - export { application }; - export { audio }; - export { font }; - export { image }; - export { model }; - export { multipart }; - export { text }; - export { video }; - } - export default _default; -} - -declare module "socket:mime" { - export * from "socket:mime/index"; + export const primordials: any; export default exports; - import * as exports from "socket:mime/index"; + import { Buffer } from "socket:buffer"; + import { URL } from "socket:url/index"; + import * as exports from "socket:ipc"; + } -declare module "socket:util" { - export function debug(section: any): { - (...args: any[]): void; - enabled: boolean; - }; - export function hasOwnProperty(object: any, property: any): any; - export function isDate(object: any): boolean; - export function isTypedArray(object: any): boolean; - export function isArrayLike(input: any): boolean; - export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; - export function isNumber(value: any): boolean; - export function isBoolean(value: any): boolean; - export function isArrayBufferView(buf: any): boolean; - export function isAsyncFunction(object: any): boolean; - export function isArgumentsObject(object: any): boolean; - export function isEmptyObject(object: any): boolean; - export function isObject(object: any): boolean; - export function isUndefined(value: any): boolean; - export function isNull(value: any): boolean; - export function isNullOrUndefined(value: any): boolean; - export function isPrimitive(value: any): boolean; - export function isRegExp(value: any): boolean; - export function isPlainObject(object: any): boolean; - export function isArrayBuffer(object: any): boolean; - export function isBufferLike(object: any): boolean; - export function isFunction(value: any): boolean; - export function isErrorLike(error: any): boolean; - export function isClass(value: any): boolean; - export function isBuffer(value: any): boolean; - export function isPromiseLike(object: any): boolean; - export function toString(object: any): any; - export function toBuffer(object: any, encoding?: any): any; - export function toProperCase(string: any): any; - export function splitBuffer(buffer: any, highWaterMark: any): any[]; - export function clamp(value: any, min: any, max: any): number; - export function promisify(original: any): any; - export function inspect(value: any, options: any): any; - export namespace inspect { - let ignore: symbol; - let custom: symbol; - } - export function format(format: any, ...args: any[]): string; - export function parseJSON(string: any): any; - export function parseHeaders(headers: any): string[][]; - export function noop(): void; - export function isValidPercentageValue(input: any): boolean; - export function compareBuffers(a: any, b: any): any; - export function inherits(Constructor: any, Super: any): void; +declare module "socket:ai" { /** - * @ignore - * @param {string} source - * @return {boolean} + * A class to interact with large language models (using llama.cpp) + * @extends EventEmitter */ - export function isESMSource(source: string): boolean; - export function deprecate(...args: any[]): void; - export { types }; - export const TextDecoder: { - new (label?: string, options?: TextDecoderOptions): TextDecoder; - prototype: TextDecoder; - }; - export const TextEncoder: { - new (): TextEncoder; - prototype: TextEncoder; - }; - export const isArray: any; - export const inspectSymbols: symbol[]; - export class IllegalConstructor { + export class LLM extends EventEmitter { + /** + * Constructs an LLM instance. + * @param {Object} [options] - The options for initializing the LLM. + * @param {string} [options.path] - The path to a valid model (.gguf). + * @param {string} [options.prompt] - The query that guides the model to generate a relevant and coherent responses. + * @param {string} [options.id] - The optional ID for the LLM instance. + * @throws {Error} If the model path is not provided. + */ + constructor(options?: { + path?: string; + prompt?: string; + id?: string; + }); + path: string; + prompt: string; + id: string | BigInt; + /** + * Tell the LLM to stop after the next token. + * @returns {Promise} A promise that resolves when the LLM stops. + */ + stop(): Promise; + /** + * Send a message to the chat. + * @param {string} message - The message to send to the chat. + * @returns {Promise} A promise that resolves with the response from the chat. + */ + chat(message: string): Promise; } - export const MIMEType: typeof mime.MIMEType; - export const MIMEParams: typeof mime.MIMEParams; export default exports; - import types from "socket:util/types"; - import mime from "socket:mime"; - import * as exports from "socket:util"; + import { EventEmitter } from "socket:events"; + import * as exports from "socket:ai"; } @@ -7514,6 +7554,7 @@ declare module "socket:vm" { filename?: string; context?: object; }; + import { SharedWorker } from "socket:internal/shared-worker"; } declare module "socket:worker_threads/init" { @@ -7552,7 +7593,7 @@ declare module "socket:worker_threads" { * A pool of known worker threads. * @type {} */ - export const workers: () => () => any; + export const workers: () => () => any; /** * `true` if this is the "main" thread, otherwise `false` * The "main" thread is the top level webview window. @@ -7709,6 +7750,7 @@ declare module "socket:worker_threads" { import { Readable } from "socket:stream"; import { SHARE_ENV } from "socket:worker_threads/init"; import init from "socket:worker_threads/init"; + import { env } from "socket:process"; export { SHARE_ENV, init }; } @@ -7896,6 +7938,7 @@ declare module "socket:child_process" { import { AsyncResource } from "socket:async/resource"; import { EventEmitter } from "socket:events"; import { Worker } from "socket:worker_threads"; + import signal from "socket:signal"; } declare module "socket:constants" { @@ -8410,7 +8453,7 @@ declare module "socket:fs/web" { export function createFile(filename: string, options?: { fd: fs.FileHandle; highWaterMark?: number; - } | undefined): File; + }): File; /** * Creates a `FileSystemWritableFileStream` instance backed * by `socket:fs:` module from a given `FileSystemFileHandle` instance. @@ -8706,6 +8749,7 @@ declare module "socket:extension" { * @typedef {number} Pointer */ const $loaded: unique symbol; + import path from "socket:path"; } declare module "socket:fetch/fetch" { @@ -13714,7 +13758,7 @@ declare module "socket:commonjs/package" { static parse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - } | undefined): ParsedPackageName | null; + }): ParsedPackageName | null; /** * Returns `true` if the given `input` can be parsed by `Name.parse` or given * as input to the `Name` class constructor. @@ -13725,7 +13769,7 @@ declare module "socket:commonjs/package" { static canParse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - } | undefined): boolean; + }): boolean; /** * Creates a new `Name` from input. * @param {string|URL} input @@ -14494,6 +14538,7 @@ declare module "socket:commonjs/module" { import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import builtins from "socket:commonjs/builtins"; } declare module "socket:module" { @@ -15714,11 +15759,11 @@ declare module "socket:internal/promise" { */ constructor(resolver: ResolverFunction); [resourceSymbol]: { - "__#11@#type": any; - "__#11@#destroyed": boolean; - "__#11@#asyncId": number; - "__#11@#triggerAsyncId": any; - "__#11@#requireManualDestroy": boolean; + "__#15@#type": any; + "__#15@#destroyed": boolean; + "__#15@#asyncId": number; + "__#15@#triggerAsyncId": any; + "__#15@#requireManualDestroy": boolean; readonly type: string; readonly destroyed: boolean; asyncId(): number; @@ -15855,6 +15900,7 @@ declare module "socket:internal/pickers" { [keyof]; }>; }; + import { FileSystemHandle } from "socket:fs/web"; } declare module "socket:internal/primitives" { From c33684048436fc73f0ad7fc7f8ecb793d8707f82 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Fri, 24 May 2024 15:51:32 +0200 Subject: [PATCH 0745/1178] update(docs): improve readme, fix(window): add window instance property check --- README.md | 29 +++++++++++++++++++++++++++-- src/core/modules/ai.cc | 2 ++ src/window/apple.mm | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index de76ab5220..506bcafc47 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,12 @@ The `Socket runtime CLI` outputs hybrid native-web apps that combine your code w | Module | Node.js | Socket | | ----------------- | ---------- | --------- | | assert | ✔︎ | ⏱ | -| async/await | ✔︎ | ✔︎ | +| async_hooks | ✔︎ | ✔︎ | | buffer | ✔︎ | ✔︎️ | | child_process | ✔︎ | ✔︎️ \* | | console | ✔︎ | ✔︎ | | crypto | ✔︎ | ✔︎ \* | -| dgram | ✔︎ | ✔︎️ | +| udp/dgram | ✔︎ | ✔︎️ | | dns | ✔︎ | ✔︎️ | | os | ✔︎ | ✔︎️ | | encoding | ✔︎ | ✔︎ | @@ -40,13 +40,18 @@ The `Socket runtime CLI` outputs hybrid native-web apps that combine your code w | fetch | ✔︎ | ✔︎ | | fs/promises | ✔︎ | ✔︎ | | fs | ✔︎ | ✔︎ | +| http | ✔︎ | ✔︎ | +| https | ✔︎ | ✔︎ | +| net | ✔︎ | ⏱ | | path | ✔︎ | ✔︎ | | process | ✔︎ | ✔︎ | | streams | ✔︎ | ✔︎ | | string_decoder | ✔︎ | ⏱ | | test | ✔︎ | ✔︎️ | | timers | ✔︎ | ⏱ | +| tty | ✔︎ | ✔︎️ | | uuid | ✔︎ | ⏱ | +| worker_threads | ✔︎ | ✔︎ | | vm | ✔︎ | ✔︎ | | ESM | ✔︎ | ✔︎ | | CJS | ✔︎ | ✔︎ | @@ -75,6 +80,26 @@ The full documentation can be found on the [Socket Runtime](https://socketsupply The `Socket Runtime` documentation covers Socket APIs, includes examples, multiple guides (`Apple`, `Desktop`, and `Mobile`), `P2P` documentation, and more. +### Development + +If you are developing socket, and you want your apps to point to your dev branch. + + +#### Step 1 + +```bash +cd ~/projects/socket # navigate into the location where you cloned this repo +./bin/uninstall.sh # remove existing installation +npm rm -g @socketsupply/socket # remove any global links or prior global npm installs +npm run relink # this will call `./bin/publish-npm-modules.sh --link` (accepts NO_ANDROID=1, NO_IOS=1, and DEBUG=1) +``` + +```bash +cd ~/projects/ # navigate into your project (replacing with whatever it's actually called +npm link @socketsupply/socket # link socket and you'll be ready to go. +``` + + ### Testing `Socket` provides a built-in `test runner` similar to `node:test` which outputs the test results in [TAP](https://testanything.org/) format. diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 26603f6b69..a71cad5b74 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -584,6 +584,7 @@ namespace SSC { for (auto id : embd) { const String token_str = llama_token_to_piece(ctx, id, !params.conversation); if (this->stopped) { + llama_sampling_reset(this->sampling); this->interactive = false; return; } @@ -743,6 +744,7 @@ namespace SSC { if (llama_token_is_eog(this->model, embd.back())) { if (this->stopped) { + llama_sampling_reset(this->sampling); this->interactive = false; return; } diff --git a/src/window/apple.mm b/src/window/apple.mm index cfd595155a..8e8c9b9c15 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -910,7 +910,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } const Window::Size Window::getSize () const { - if (this->window == nullptr) { + if (this->window == nullptr || this->window->frame == nullptr) { return Size {0, 0}; } From 41a30efdb408a06901922984f9f40498e0f4e3dc Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 25 May 2024 09:10:49 +0200 Subject: [PATCH 0746/1178] update(docs): update support matrix, fix(core): window property check --- README.md | 79 +++++++++++++++++++++++------------------- src/core/modules/ai.cc | 35 ++++++++----------- src/window/apple.mm | 4 +-- 3 files changed, 61 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 506bcafc47..79dec6430a 100644 --- a/README.md +++ b/README.md @@ -21,45 +21,54 @@ The `Socket runtime CLI` outputs hybrid native-web apps that combine your code w ### Compatibility Matrix +Socket Supports both ESM and CommonJS + > [!NOTE] -> Socket supports many of the Node.js APIs. It is **NOT** a drop in replacement for Node.js, nor will it ever be since socket is for building software and node.js is for building servers. Below is a high level overview of partially supported APIs and modules. - -| Module | Node.js | Socket | -| ----------------- | ---------- | --------- | -| assert | ✔︎ | ⏱ | -| async_hooks | ✔︎ | ✔︎ | -| buffer | ✔︎ | ✔︎️ | -| child_process | ✔︎ | ✔︎️ \* | -| console | ✔︎ | ✔︎ | -| crypto | ✔︎ | ✔︎ \* | -| udp/dgram | ✔︎ | ✔︎️ | -| dns | ✔︎ | ✔︎️ | -| os | ✔︎ | ✔︎️ | -| encoding | ✔︎ | ✔︎ | -| events | ✔︎ | ✔︎ | -| fetch | ✔︎ | ✔︎ | -| fs/promises | ✔︎ | ✔︎ | -| fs | ✔︎ | ✔︎ | -| http | ✔︎ | ✔︎ | -| https | ✔︎ | ✔︎ | -| net | ✔︎ | ⏱ | -| path | ✔︎ | ✔︎ | -| process | ✔︎ | ✔︎ | -| streams | ✔︎ | ✔︎ | -| string_decoder | ✔︎ | ⏱ | -| test | ✔︎ | ✔︎️ | -| timers | ✔︎ | ⏱ | -| tty | ✔︎ | ✔︎️ | -| uuid | ✔︎ | ⏱ | -| worker_threads | ✔︎ | ✔︎ | -| vm | ✔︎ | ✔︎ | -| ESM | ✔︎ | ✔︎ | -| CJS | ✔︎ | ✔︎ | -| URL | ✔︎ | ✔︎ | +> Socket supports many of the Node.js APIs. It is **NOT** a drop in replacement for Node.js, it most likely wont ever be since Socket is for building software and node.js is for building servers. Below is a high level overview of fully or partially supported APIs and modules. + +| Module | Node.js | Socket | +| ----------------- | ---------- | -------- | +| assert | √ | ⏱ | +| async_hooks | √ | √ | +| buffer | √ | √ | +| child_process | √ | √ \* | +| cluster | √ | NR | +| console | √ | √ | +| crypto | √ | √ \* | +| dgram (udp) | √ | √ | +| diagnostics_channel | √ | √ | +| dns | √ | √ | +| encoding | √ | √ | +| events | √ | √ | +| fetch | √ | √ | +| fs | √ | √ | +| fs/promises | √ | √ | +| http | √ | √ | +| http2 | √ | √ | +| https | √ | √ | +| inspector | √ | ⏱ | +| module | √ | √ | +| net | √ | ⏱ | +| os | √ | √ | +| path | √ | √ | +| process | √ | √ | +| streams | √ | √ | +| string_decoder | √ | √ | +| test | √ | √ | +| timers | √ | √ | +| tls | √ | ⏱ | +| trace_events | EXPERIMENTAL | ⏱ | +| tty | √ | √ | +| URL | √ | √ | +| uuid | √ | ⏱ \*\* | +| vm | √ | √ | +| WASI | EXPERIMENTAL | NR | +| worker_threads | √ | √ | _⏱ = planned support_ +_NR = Not Relevant or not necessary since socket doesn't require high-concurrency_ _\* = Supported but Works differently; may be refactored to match the nodejs API_ -_\*\* = Use fetch instead_ +_\*\* = Use `crypto.randomUUID()` instead_ ### FAQ diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index a71cad5b74..a270c7e1d8 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -385,12 +385,7 @@ namespace SSC { bool display = true; bool is_antiprompt = false; bool input_echo = true; - - int n_past = 0; int n_remain = this->params.n_predict; - int n_consumed = 0; - int n_session_consumed = 0; - int n_past_guidance = 0; std::vector input_tokens; this->input_tokens = &input_tokens; @@ -414,7 +409,7 @@ namespace SSC { const auto cml_pfx = ::llama_tokenize(ctx, "\n<|im_start|>user\n", true, true); const auto cml_sfx = ::llama_tokenize(ctx, "<|im_end|>\n<|im_start|>assistant\n", false, true); - while ((n_remain != 0 && !is_antiprompt) || params.interactive) { + while ((n_remain != 0 && !is_antiprompt) || this->params.interactive) { if (!embd.empty()) { int max_embd_size = n_ctx - 4; @@ -426,7 +421,7 @@ namespace SSC { if (ga_n == 1) { if (n_past + (int) embd.size() + std::max(0, guidance_offset) >= n_ctx) { - if (params.n_predict == -2) { + if (this->params.n_predict == -2) { LOG("\n\ncontext full and n_predict == -%d => stopping\n", this->params.n_predict); break; } @@ -435,7 +430,7 @@ namespace SSC { const int n_discard = n_left/2; LOG("context full, swapping: n_past = %d, n_left = %d, n_ctx = %d, n_keep = %d, n_discard = %d\n", - n_past, n_left, n_ctx, params.n_keep, n_discard); + n_past, n_left, n_ctx, this->params.n_keep, n_discard); llama_kv_cache_seq_rm (ctx, 0, this->params.n_keep, this->params.n_keep + n_discard); llama_kv_cache_seq_add(ctx, 0, this->params.n_keep + n_discard, n_past, -n_discard); @@ -517,8 +512,8 @@ namespace SSC { input_size = embd.size(); } - for (int i = 0; i < input_size; i += params.n_batch) { - int n_eval = std::min(input_size - i, params.n_batch); + for (int i = 0; i < input_size; i += this->params.n_batch) { + int n_eval = std::min(input_size - i, this->params.n_batch); if (llama_decode(this->guidance, llama_batch_get_one(input_buf + i, n_eval, n_past_guidance, 0))) { LOG("failed to eval\n"); @@ -529,11 +524,11 @@ namespace SSC { } } - for (int i = 0; i < (int) embd.size(); i += params.n_batch) { + for (int i = 0; i < (int) embd.size(); i += this->params.n_batch) { int n_eval = (int) embd.size() - i; - if (n_eval > params.n_batch) { - n_eval = params.n_batch; + if (n_eval > this->params.n_batch) { + n_eval = this->params.n_batch; } LOG("eval: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd).c_str()); @@ -574,7 +569,7 @@ namespace SSC { llama_sampling_accept(this->sampling, this->ctx, this->embd_inp[n_consumed], false); ++n_consumed; - if ((int) embd.size() >= params.n_batch) { + if ((int) embd.size() >= this->params.n_batch) { break; } } @@ -582,7 +577,7 @@ namespace SSC { if (input_echo && display) { for (auto id : embd) { - const String token_str = llama_token_to_piece(ctx, id, !params.conversation); + const String token_str = llama_token_to_piece(ctx, id, !this->params.conversation); if (this->stopped) { llama_sampling_reset(this->sampling); this->interactive = false; @@ -607,7 +602,7 @@ namespace SSC { } if ((int)this->embd_inp.size() <= n_consumed) { - if (!params.antiprompt.empty()) { + if (!this->params.antiprompt.empty()) { const int n_prev = 32; const String last_output = llama_sampling_prev_str(this->sampling, this->ctx, n_prev); @@ -670,7 +665,7 @@ namespace SSC { this->embd_inp.push_back(llama_token_bos(this->model)); } - if (!params.input_prefix.empty() && !params.conversation) { + if (!this->params.input_prefix.empty() && !this->params.conversation) { LOG("appending input prefix: '%s'\n", this->params.input_prefix.c_str()); } @@ -689,18 +684,18 @@ namespace SSC { embd_inp.insert(this->embd_inp.end(), inp_pfx.begin(), inp_pfx.end()); } - if (params.chatml && !is_antiprompt) { + if (this->params.chatml && !is_antiprompt) { LOG("inserting chatml prefix\n"); n_consumed = this->embd_inp.size(); embd_inp.insert(this->embd_inp.end(), cml_pfx.begin(), cml_pfx.end()); } - if (params.escape) { + if (this->params.escape) { this->escape(buffer); } const auto line_pfx = ::llama_tokenize(this->ctx, this->params.input_prefix.c_str(), false, true); - const auto line_inp = ::llama_tokenize(this->ctx, buffer.c_str(), false, params.interactive_specials); + const auto line_inp = ::llama_tokenize(this->ctx, buffer.c_str(), false, this->params.interactive_specials); const auto line_sfx = ::llama_tokenize(this->ctx, this->params.input_suffix.c_str(), false, true); LOG("input tokens: %s\n", LOG_TOKENS_TOSTR_PRETTY(this->ctx, line_inp).c_str()); diff --git a/src/window/apple.mm b/src/window/apple.mm index 8e8c9b9c15..e35256849b 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -894,7 +894,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } Window::Size Window::getSize () { - if (this->window == nullptr) { + if (this->window == nullptr || this->window.frame == nullptr) { return Size {0, 0}; } @@ -910,7 +910,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } const Window::Size Window::getSize () const { - if (this->window == nullptr || this->window->frame == nullptr) { + if (this->window == nullptr || this->window.frame == nullptr) { return Size {0, 0}; } From 1abb154757dc64dde5f17be84315356e000b2c0e Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 25 May 2024 09:17:06 +0200 Subject: [PATCH 0747/1178] update(docs): update support matrix --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 79dec6430a..0de00e60fc 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ Socket Supports both ESM and CommonJS | assert | √ | ⏱ | | async_hooks | √ | √ | | buffer | √ | √ | -| child_process | √ | √ \* | -| cluster | √ | NR | +| child_process | √ | √ | +| cluster | √ | § | | console | √ | √ | | crypto | √ | √ \* | | dgram (udp) | √ | √ | @@ -57,18 +57,18 @@ Socket Supports both ESM and CommonJS | test | √ | √ | | timers | √ | √ | | tls | √ | ⏱ | -| trace_events | EXPERIMENTAL | ⏱ | | tty | √ | √ | | URL | √ | √ | -| uuid | √ | ⏱ \*\* | +| uuid | √ | ⏱ ☀︎ | | vm | √ | √ | -| WASI | EXPERIMENTAL | NR | | worker_threads | √ | √ | -_⏱ = planned support_ -_NR = Not Relevant or not necessary since socket doesn't require high-concurrency_ -_\* = Supported but Works differently; may be refactored to match the nodejs API_ -_\*\* = Use `crypto.randomUUID()` instead_ +| Symbol | Meaning | +| :----: | :-------------------------------------------------------------------------- | +| ⏱ | Planned support | +| § | Not Relevant or not necessary since socket doesn't require high-concurrency | +| \* | Supported but Works differently; may be refactored to match the nodejs API | +| ☀︎ | Use `crypto.randomUUID()` instead | ### FAQ @@ -81,7 +81,7 @@ Check the FAQs on our [Website](https://socketsupply.co/guides/#faq) to learn mo `Create Socket App` is similar to React's `Create React App`, we provide a few basic boilerplates and some strong opinions so you can get coding on a production-quality app as quickly as possible. Please check [create-socket-app Repo](https://github.com/socketsupply/create-socket-app) to get started and to learn more. You can also check our `Examples` in the [Examples Repo](https://github.com/socketsupply/socket-examples). - +§§§ ### Documentation From c244a584db2319ae73477e4388486ad9cd1a09ee Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 25 May 2024 09:18:27 +0200 Subject: [PATCH 0748/1178] update(docs): update content --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0de00e60fc..9b2e66dc41 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ npm rm -g @socketsupply/socket # remove any global links or prior global npm in npm run relink # this will call `./bin/publish-npm-modules.sh --link` (accepts NO_ANDROID=1, NO_IOS=1, and DEBUG=1) ``` +#### Step 2 + ```bash cd ~/projects/ # navigate into your project (replacing with whatever it's actually called npm link @socketsupply/socket # link socket and you'll be ready to go. From 289b2ec034ed5599dd27ffd52f4e0bde90801c16 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 25 May 2024 11:25:56 +0200 Subject: [PATCH 0749/1178] fix(ai): add back missing varibales, dont check frame property --- src/core/modules/ai.hh | 4 ++++ src/window/apple.mm | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh index a204bf89cb..ef1576021b 100644 --- a/src/core/modules/ai.hh +++ b/src/core/modules/ai.hh @@ -94,6 +94,10 @@ namespace SSC { int guidance_offset = 0; int original_prompt_len = 0; int n_ctx = 0; + int n_past = 0; + int n_consumed = 0; + int n_session_consumed = 0; + int n_past_guidance = 0; public: String err = ""; diff --git a/src/window/apple.mm b/src/window/apple.mm index e35256849b..cfd595155a 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -894,7 +894,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } Window::Size Window::getSize () { - if (this->window == nullptr || this->window.frame == nullptr) { + if (this->window == nullptr) { return Size {0, 0}; } @@ -910,7 +910,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } const Window::Size Window::getSize () const { - if (this->window == nullptr || this->window.frame == nullptr) { + if (this->window == nullptr) { return Size {0, 0}; } From aa9d9b6ec570b234e645cdc29634d5dd5b279b09 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 25 May 2024 21:32:35 +0200 Subject: [PATCH 0750/1178] fix(node): works in nodejs again --- api/README.md | 2 +- api/async/deferred.js | 1 + api/diagnostics/window.js | 3 +- api/index.d.ts | 114 +- api/network.js | 6 +- api/node/index.js | 6 +- api/signal.js | 2 +- api/stream-relay.js | 3 - api/stream-relay/api.js | 362 ----- api/stream-relay/cache.js | 361 ----- api/stream-relay/encryption.js | 288 ---- api/stream-relay/index.js | 2167 ----------------------------- api/stream-relay/nat.js | 185 --- api/stream-relay/packets.js | 496 ------- api/stream-relay/proxy.js | 281 ---- api/stream-relay/worker.js | 82 -- bin/update-stream-relay-source.sh | 20 - package.json | 4 +- 18 files changed, 62 insertions(+), 4321 deletions(-) delete mode 100644 api/stream-relay.js delete mode 100644 api/stream-relay/api.js delete mode 100644 api/stream-relay/cache.js delete mode 100644 api/stream-relay/encryption.js delete mode 100644 api/stream-relay/index.js delete mode 100644 api/stream-relay/nat.js delete mode 100644 api/stream-relay/packets.js delete mode 100644 api/stream-relay/proxy.js delete mode 100644 api/stream-relay/worker.js delete mode 100755 bin/update-stream-relay-source.sh diff --git a/api/README.md b/api/README.md index d532029043..e9daf2f16e 100644 --- a/api/README.md +++ b/api/README.md @@ -1644,7 +1644,7 @@ Sends an async IPC command request with parameters. External docs: https://socketsupply.co/guides/#p2p-guide - Provides a higher level API over the stream-relay protocol. + Provides a higher level API over the latica protocol. diff --git a/api/async/deferred.js b/api/async/deferred.js index 60d8426146..25a447325d 100644 --- a/api/async/deferred.js +++ b/api/async/deferred.js @@ -1,4 +1,5 @@ /* global ErrorEvent */ +import { ErrorEvent } from '../events.js' /** * Dispatched when a `Deferred` internal promise is resolved. diff --git a/api/diagnostics/window.js b/api/diagnostics/window.js index 803b2e75f5..556fd25ea2 100644 --- a/api/diagnostics/window.js +++ b/api/diagnostics/window.js @@ -182,7 +182,8 @@ export class XMLHttpRequestMetric extends Metric { export class WorkerMetric extends Metric { constructor (options) { super() - this.GlobalWorker = globalThis.Worker + // TODO(@heapwolf): this fix for node causes the ts-defs to lose a lot of type info + this.GlobalWorker = this.isSocketRuntime ? globalThis.Worker : class {} this.channel = dc.channel('Worker') this.Worker = class Worker extends this.GlobalWorker { constructor (url, options, ...args) { diff --git a/api/index.d.ts b/api/index.d.ts index 297b3949a0..702d6327d6 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -2035,22 +2035,12 @@ declare module "socket:diagnostics/window" { GlobalWorker: { new (scriptURL: string | URL, options?: WorkerOptions): Worker; prototype: Worker; + } | { + new (): {}; }; channel: import("socket:diagnostics/channels").Channel; Worker: { - new (url: any, options: any, ...args: any[]): { - onmessage: (this: Worker, ev: MessageEvent) => any; - onmessageerror: (this: Worker, ev: MessageEvent) => any; - postMessage(message: any, transfer: Transferable[]): void; - postMessage(message: any, options?: StructuredSerializeOptions): void; - terminate(): void; - addEventListener(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K_1, listener: (this: Worker, ev: WorkerEventMap[K_1]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; - dispatchEvent(event: Event): boolean; - onerror: (this: AbstractWorker, ev: ErrorEvent) => any; - }; + new (url: any, options: any, ...args: any[]): {}; }; } export const metrics: { @@ -2439,7 +2429,7 @@ declare module "socket:async/deferred" { /** * Dispatched when a `Deferred` internal promise is rejected. */ - export class DeferredRejectEvent extends ErrorEvent { + export class DeferredRejectEvent { /** * `DeferredRejectEvent` class constructor * @ignore @@ -10796,7 +10786,7 @@ declare module "socket:i18n" { import Enumeration from "socket:enumeration"; } -declare module "socket:stream-relay/packets" { +declare module "socket:latica/packets" { /** * The magic bytes prefixing every packet. They are the * 2nd, 3rd, 5th, and 7th, prime numbers. @@ -11095,7 +11085,7 @@ declare module "socket:stream-relay/packets" { import { Buffer } from "socket:buffer"; } -declare module "socket:stream-relay/encryption" { +declare module "socket:latica/encryption" { /** * Class for handling encryption and key management. */ @@ -11222,7 +11212,7 @@ declare module "socket:stream-relay/encryption" { import Buffer from "socket:buffer"; } -declare module "socket:stream-relay/cache" { +declare module "socket:latica/cache" { /** * @typedef {Packet} CacheEntry * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver @@ -11398,10 +11388,10 @@ declare module "socket:stream-relay/cache" { export type CacheEntry = Packet; export type CacheEntrySiblingResolver = (arg0: CacheEntry, arg1: CacheEntry) => number; import { Buffer } from "socket:buffer"; - import { Packet } from "socket:stream-relay/packets"; + import { Packet } from "socket:latica/packets"; } -declare module "socket:stream-relay/nat" { +declare module "socket:latica/nat" { /** * The NAT type is encoded using 5 bits: * @@ -11500,7 +11490,7 @@ declare module "socket:stream-relay/nat" { export function connectionStrategy(a: any, b: any): 0 | 1 | 2 | 3 | 4; } -declare module "socket:stream-relay/index" { +declare module "socket:latica/index" { /** * Computes rate limit predicate value for a port and address pair for a given * threshold updating an input rates map. This method is accessed concurrently, @@ -11944,39 +11934,18 @@ declare module "socket:stream-relay/index" { }): Promise; } export default Peer; - import { Packet } from "socket:stream-relay/packets"; - import { sha256 } from "socket:stream-relay/packets"; - import { Cache } from "socket:stream-relay/cache"; - import { Encryption } from "socket:stream-relay/encryption"; - import * as NAT from "socket:stream-relay/nat"; + import { Packet } from "socket:latica/packets"; + import { sha256 } from "socket:latica/packets"; + import { Cache } from "socket:latica/cache"; + import { Encryption } from "socket:latica/encryption"; + import * as NAT from "socket:latica/nat"; import { Buffer } from "socket:buffer"; - import { PacketPing } from "socket:stream-relay/packets"; - import { PacketPublish } from "socket:stream-relay/packets"; + import { PacketPing } from "socket:latica/packets"; + import { PacketPublish } from "socket:latica/packets"; export { Packet, sha256, Cache, Encryption, NAT }; } -declare module "socket:node/index" { - export default network; - export const network: any; - import { Cache } from "socket:stream-relay/index"; - import { sha256 } from "socket:stream-relay/index"; - import { Encryption } from "socket:stream-relay/index"; - import { Packet } from "socket:stream-relay/index"; - import { NAT } from "socket:stream-relay/index"; - export { Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:index" { - import { network } from "socket:node/index"; - import { Cache } from "socket:node/index"; - import { sha256 } from "socket:node/index"; - import { Encryption } from "socket:node/index"; - import { Packet } from "socket:node/index"; - import { NAT } from "socket:node/index"; - export { network, Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:stream-relay/proxy" { +declare module "socket:latica/proxy" { export default PeerWorkerProxy; /** * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. @@ -12013,7 +11982,7 @@ declare module "socket:stream-relay/proxy" { import { Deferred } from "socket:async"; } -declare module "socket:stream-relay/api" { +declare module "socket:latica/api" { export default api; /** * Initializes and returns the network bus. @@ -12028,14 +11997,35 @@ declare module "socket:stream-relay/api" { export function api(options: object, events: object, dgram: object): Promise; } +declare module "socket:node/index" { + export default network; + export function network(options: any): Promise; + import { Cache } from "socket:latica/index"; + import { sha256 } from "socket:latica/index"; + import { Encryption } from "socket:latica/index"; + import { Packet } from "socket:latica/index"; + import { NAT } from "socket:latica/index"; + export { Cache, sha256, Encryption, Packet, NAT }; +} + +declare module "socket:index" { + import { network } from "socket:node/index"; + import { Cache } from "socket:node/index"; + import { sha256 } from "socket:node/index"; + import { Encryption } from "socket:node/index"; + import { Packet } from "socket:node/index"; + import { NAT } from "socket:node/index"; + export { network, Cache, sha256, Encryption, Packet, NAT }; +} + declare module "socket:network" { export default network; export function network(options: any): Promise; - import { Cache } from "socket:stream-relay/index"; - import { sha256 } from "socket:stream-relay/index"; - import { Encryption } from "socket:stream-relay/index"; - import { Packet } from "socket:stream-relay/index"; - import { NAT } from "socket:stream-relay/index"; + import { Cache } from "socket:latica/index"; + import { sha256 } from "socket:latica/index"; + import { Encryption } from "socket:latica/index"; + import { Packet } from "socket:latica/index"; + import { NAT } from "socket:latica/index"; export { Cache, sha256, Encryption, Packet, NAT }; } @@ -15032,12 +15022,6 @@ declare module "socket:notification" { import URL from "socket:url"; } -declare module "socket:stream-relay" { - export * from "socket:stream-relay/index"; - export default def; - import def from "socket:stream-relay/index"; -} - declare module "socket:service-worker/instance" { export function createServiceWorker(currentState?: any, options?: any): any; export const SHARED_WORKER_URL: string; @@ -15977,6 +15961,10 @@ declare module "socket:internal/worker" { export default _default; } +declare module "socket:latica/worker" { + export {}; +} + declare module "socket:npm/module" { /** * @typedef {{ @@ -16426,10 +16414,6 @@ declare module "socket:service-worker/worker" { export {}; } -declare module "socket:stream-relay/worker" { - export {}; -} - declare module "socket:test/harness" { /** * @typedef {import('./index').Test} Test diff --git a/api/network.js b/api/network.js index 38dbc448ee..6f78ee46cd 100644 --- a/api/network.js +++ b/api/network.js @@ -1,13 +1,13 @@ /** * @module Network * - * Provides a higher level API over the stream-relay protocol. + * Provides a higher level API over the latica protocol. * * @see {@link https://socketsupply.co/guides/#p2p-guide} * */ -import api from './stream-relay/api.js' -import { Cache, Packet, sha256, Encryption, NAT } from './stream-relay/index.js' +import api from './latica/api.js' +import { Cache, Packet, sha256, Encryption, NAT } from './latica/index.js' import events from './events.js' import dgram from './dgram.js' diff --git a/api/node/index.js b/api/node/index.js index 2895b74c65..60e6fc96fc 100644 --- a/api/node/index.js +++ b/api/node/index.js @@ -1,9 +1,9 @@ -import sugar from '../stream-relay/sugar.js' -import { Cache, Packet, sha256, Encryption, NAT } from '../stream-relay/index.js' +import api from '../latica/api.js' +import { Cache, Packet, sha256, Encryption, NAT } from '../latica/index.js' import events from 'node:events' import dgram from 'node:dgram' -const network = sugar(dgram, events) +const network = options => api(options, events, dgram) export { network, Cache, sha256, Encryption, Packet, NAT } export default network diff --git a/api/signal.js b/api/signal.js index 78f83d59d7..691af94a51 100644 --- a/api/signal.js +++ b/api/signal.js @@ -168,7 +168,7 @@ if (!/android|ios/i.test(os.platform())) { onSignal(event.data.signal) }) - globalThis.addEventListener('signal', (event) => { + globalThis.addEventListener?.('signal', (event) => { onSignal(event.detail.signal) channel.postMessage(event.detail) }) diff --git a/api/stream-relay.js b/api/stream-relay.js deleted file mode 100644 index 0599d2fd31..0000000000 --- a/api/stream-relay.js +++ /dev/null @@ -1,3 +0,0 @@ -import def from './stream-relay/index.js' -export * from './stream-relay/index.js' -export default def diff --git a/api/stream-relay/api.js b/api/stream-relay/api.js deleted file mode 100644 index 1c1210a4a2..0000000000 --- a/api/stream-relay/api.js +++ /dev/null @@ -1,362 +0,0 @@ -import { Peer, Encryption, sha256 } from './index.js' -import { PeerWorkerProxy } from './proxy.js' -import { sodium } from '../crypto.js' -import { Buffer } from '../buffer.js' -import { isBufferLike } from '../util.js' -import { Packet, CACHE_TTL } from './packets.js' - -/** - * Initializes and returns the network bus. - * - * @async - * @function - * @param {object} options - Configuration options for the network bus. - * @param {object} events - A nodejs compatibe implementation of the events module. - * @param {object} dgram - A nodejs compatible implementation of the dgram module. - * @returns {Promise} - A promise that resolves to the initialized network bus. - */ -async function api (options = {}, events, dgram) { - await sodium.ready - const bus = new events.EventEmitter() - bus._on = bus.on - bus._once = bus.once - bus._emit = bus.emit - - if (!options.indexed) { - if (!options.clusterId && !options.config?.clusterId) { - throw new Error('expected options.clusterId') - } - - if (typeof options.signingKeys !== 'object') throw new Error('expected options.signingKeys to be of type Object') - if (options.signingKeys.publicKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.publicKey to be of type Uint8Array') - if (options.signingKeys.privateKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.privateKey to be of type Uint8Array') - } - - let clusterId = bus.clusterId = options.clusterId || options.config?.clusterId - - if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters - - const Ctor = globalThis.isSocketRuntime ? PeerWorkerProxy : Peer - const _peer = new Ctor(options, dgram) - - _peer.onJoin = (packet, ...args) => { - if (!Buffer.from(packet.clusterId).equals(clusterId)) return - bus._emit('#join', packet, ...args) - } - - _peer.onPacket = (packet, ...args) => { - if (!Buffer.from(packet.clusterId).equals(clusterId)) return - bus._emit('#packet', packet, ...args) - } - - _peer.onStream = (packet, ...args) => { - if (!Buffer.from(packet.clusterId).equals(clusterId)) return - bus._emit('#stream', packet, ...args) - } - - _peer.onData = (...args) => bus._emit('#data', ...args) - _peer.onSend = (...args) => bus._emit('#send', ...args) - _peer.onFirewall = (...args) => bus._emit('#firewall', ...args) - _peer.onMulticast = (...args) => bus._emit('#multicast', ...args) - _peer.onJoin = (...args) => bus._emit('#join', ...args) - _peer.onSync = (...args) => bus._emit('#sync', ...args) - _peer.onSyncStart = (...args) => bus._emit('#sync-start', ...args) - _peer.onSyncEnd = (...args) => bus._emit('#sync-end', ...args) - _peer.onConnection = (...args) => bus._emit('#connection', ...args) - _peer.onDisconnection = (...args) => bus._emit('#disconnection', ...args) - _peer.onQuery = (...args) => bus._emit('#query', ...args) - _peer.onNat = (...args) => bus._emit('#network-change', ...args) - _peer.onWarn = (...args) => bus._emit('#warning', ...args) - _peer.onState = (...args) => bus._emit('#state', ...args) - _peer.onConnecting = (...args) => bus._emit('#connecting', ...args) - _peer.onConnection = (...args) => bus._emit('#connection', ...args) - - // TODO check if its not a network error - _peer.onError = (...args) => bus._emit('#error', ...args) - - _peer.onReady = info => { - Object.assign(_peer, { - isReady: true, - ...info - }) - - bus._emit('#ready', info) - } - - bus.subclusters = new Map() - - /** - * Gets general, read only information of the network peer. - * - * @function - * @returns {object} - The general information. - */ - bus.getInfo = () => _peer.getInfo() - - /** - * Gets the read only state of the network peer. - * - * @function - * @returns {object} - The address information. - */ - bus.getState = () => _peer.getState() - - /** - * Indexes a new peer in the network. - * - * @function - * @param {object} params - Peer information. - * @param {string} params.peerId - The peer ID. - * @param {string} params.address - The peer address. - * @param {number} params.port - The peer port. - * @throws {Error} - Throws an error if required parameters are missing. - */ - bus.addIndexedPeer = ({ peerId, address, port }) => { - return _peer.addIndexedPeer({ peerId, address, port }) - } - - bus.close = () => _peer.close() - bus.sync = (peerId) => _peer.sync(peerId) - bus.reconnect = () => _peer.reconnect() - bus.disconnect = () => _peer.disconnect() - - bus.sealUnsigned = (m, v = options.signingKeys) => _peer.sealUnsigned(m, v) - bus.openUnsigned = (m, v = options.signingKeys) => _peer.openUnsigned(m, v) - - bus.seal = (m, v = options.signingKeys) => _peer.seal(m, v) - bus.open = (m, v = options.signingKeys) => _peer.open(m, v) - - bus.query = (...args) => _peer.query(...args) - - const pack = async (eventName, value, opts = {}) => { - if (typeof eventName !== 'string') throw new Error('event name must be a string') - if (eventName.length === 0) throw new Error('event name too short') - - if (opts.ttl) opts.ttl = Math.min(opts.ttl, CACHE_TTL) - - const args = { - clusterId, - ...opts, - usr1: await sha256(eventName, { bytes: true }) - } - - if (!isBufferLike(value) && typeof value === 'object') { - try { - args.message = Buffer.from(JSON.stringify(value)) - } catch (err) { - return bus._emit('error', err) - } - } else { - args.message = Buffer.from(value) - } - - args.usr2 = Buffer.from(options.signingKeys.publicKey) - args.sig = Encryption.sign(args.message, options.signingKeys.privateKey) - - return args - } - - const unpack = async packet => { - let opened - let verified - const scid = Buffer.from(packet.subclusterId).toString('base64') - const sub = bus.subclusters.get(scid) - if (!sub) return {} - - try { - opened = await _peer.open(packet.message, scid) - } catch (err) { - sub._emit('warning', err) - return {} - } - - if (packet.sig) { - try { - if (Encryption.verify(opened.data || opened, packet.sig, packet.usr2)) { - verified = true - } - } catch (err) { - sub._emit('warning', err) - return {} - } - } - - return { opened, verified } - } - - /** - * Publishes an event to the network bus. - * - * @async - * @function - * @param {string} eventName - The name of the event. - * @param {any} value - The value associated with the event. - * @param {object} opts - Additional options for publishing. - * @returns {Promise} - A promise that resolves to the published event details. - */ - bus.emit = async (eventName, value, opts = {}) => { - const args = await pack(eventName, value, opts) - if (!options.sharedKey) { - throw new Error('Can\'t emit to the top level cluster, a shared key was not provided in the constructor or the arguments options') - } - - return await _peer.publish(options.sharedKey || opts.sharedKey, args) - } - - bus.on = async (eventName, cb) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - bus._on(eventName, cb) - } - - bus.subcluster = async (options = {}) => { - if (!options.sharedKey?.constructor.name) { - throw new Error('expected options.sharedKey to be of type Uint8Array') - } - - const derivedKeys = await Encryption.createKeyPair(options.sharedKey) - const subclusterId = Buffer.from(derivedKeys.publicKey) - const scid = subclusterId.toString('base64') - - if (bus.subclusters.has(scid)) return bus.subclusters.get(scid) - - const sub = new events.EventEmitter() - sub._emit = sub.emit - sub._on = sub.on - sub.peers = new Map() - - bus.subclusters.set(scid, sub) - - sub.peerId = _peer.peerId - sub.subclusterId = subclusterId - sub.sharedKey = options.sharedKey - sub.derivedKeys = derivedKeys - - sub.emit = async (eventName, value, opts = {}) => { - opts.clusterId = opts.clusterId || clusterId - opts.subclusterId = opts.subclusterId || sub.subclusterId - - const args = await pack(eventName, value, opts) - - if (sub.peers.values().length) { - let packets = [] - - for (const p of sub.peers.values()) { - const r = await p._peer.write(sub.sharedKey, args) - if (packets.length === 0) packets = r - } - - for (const packet of packets) { - const p = Packet.from(packet) - const pid = Buffer.from(packet.packetId).toString('hex') - _peer.cache.insert(pid, p) - - _peer.unpublished[pid] = Date.now() - if (globalThis.navigator && !globalThis.navigator.onLine) continue - - _peer.mcast(packet) - } - return packets - } else { - const packets = await _peer.publish(sub.sharedKey, args) - return packets - } - } - - sub.on = async (eventName, cb) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - sub._on(eventName, cb) - } - - sub.off = async (eventName, fn) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - sub.removeListener(eventName, fn) - } - - sub.join = () => _peer.join(sub.sharedKey, options) - - bus._on('#ready', () => { - const scid = Buffer.from(sub.subclusterId).toString('base64') - const subcluster = bus.subclusters.get(scid) - if (subcluster) _peer.join(subcluster.sharedKey, options) - }) - - _peer.join(sub.sharedKey, options) - return sub - } - - bus._on('#join', async (packet, peer) => { - const scid = Buffer.from(packet.subclusterId).toString('base64') - const sub = bus.subclusters.get(scid) - if (!sub) return - - let ee = sub.peers.get(peer.peerId) - - if (!ee) { - ee = new events.EventEmitter() - - ee._on = ee.on - ee._emit = ee.emit - - ee.peerId = peer.peerId - ee.address = peer.address - ee.port = peer.port - - ee.emit = async (eventName, value, opts = {}) => { - if (!ee._peer.write) return - - opts.clusterId = opts.clusterId || clusterId - opts.subclusterId = opts.subclusterId || sub.subclusterId - - const args = await pack(eventName, value, opts) - return peer.write(sub.sharedKey, args) - } - - ee.on = async (eventName, cb) => { - if (eventName[0] !== '#') eventName = await sha256(eventName) - ee._on(eventName, cb) - } - } - - const oldPeer = sub.peers.has(peer.peerId) - const portChange = oldPeer.port !== peer.port - const addressChange = oldPeer.address !== peer.address - const natChange = oldPeer.natType !== peer.natType - const change = portChange || addressChange || natChange - - ee._peer = peer - - sub.peers.set(peer.peerId, ee) - if (!oldPeer || change) sub._emit('#join', ee, packet) - }) - - const handlePacket = async (packet, peer, port, address) => { - const scid = Buffer.from(packet.subclusterId).toString('base64') - const sub = bus.subclusters.get(scid) - if (!sub) return - - const eventName = Buffer.from(packet.usr1).toString('hex') - const { verified, opened } = await unpack(packet) - if (verified) packet.verified = true - - sub._emit(eventName, opened, packet) - - const ee = sub.peers.get(packet.streamFrom || peer?.peerId) - if (ee) ee._emit(eventName, opened, packet) - } - - bus._on('#stream', handlePacket) - bus._on('#packet', handlePacket) - - bus._on('#disconnection', peer => { - for (const sub of bus.subclusters) { - sub._emit('#leave', peer) - sub.peers.delete(peer.peerId) - } - }) - - await _peer.init() - return bus -} - -export { api } -export default api diff --git a/api/stream-relay/cache.js b/api/stream-relay/cache.js deleted file mode 100644 index 1893b72a99..0000000000 --- a/api/stream-relay/cache.js +++ /dev/null @@ -1,361 +0,0 @@ -import { isBufferLike, toBuffer } from '../util.js' -import { Buffer } from '../buffer.js' -import { createDigest } from '../crypto.js' -import { Packet, PacketPublish, PACKET_BYTES, sha256 } from './packets.js' - -const EMPTY_CACHE = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' - -export const trim = (/** @type {Buffer} */ buf) => { - return buf.toString().split('~')[0].split('\x00')[0] -} - -/** - * Tries to convert input to a `Buffer`, if possible, otherwise `null`. - * @ignore - * @param {(string|Buffer|Uint8Array)?} m - * @return {Buffer?} - */ -function toBufferMaybe (m) { - return isBufferLike(m) || typeof m === 'string' - ? toBuffer(m) - : m && typeof m === 'object' - ? toBuffer(JSON.stringify(m)) - : null -} - -/** - * Default max size of a `Cache` instance. - */ -export const DEFAULT_MAX_SIZE = Math.ceil(16_000_000 / PACKET_BYTES) - -/** - * @typedef {Packet} CacheEntry - * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver - */ - -/** - * Default cache sibling resolver that computes a delta between - * two entries clocks. - * @param {CacheEntry} a - * @param {CacheEntry} b - * @return {number} - */ -export function defaultSiblingResolver (a, b) { - return a.clock - b.clock -} - -/** - * Internal mapping of packet IDs to packet data used by `Cache`. - */ -export class CacheData extends Map {} - -/** - * A class for storing a cache of packets by ID. This class includes a scheme - * for reconciling disjointed packet caches in a large distributed system. The - * following are key design characteristics. - * - * Space Efficiency: This scheme can be space-efficient because it summarizes - * the cache's contents in a compact binary format. By sharing these summaries, - * two computers can quickly determine whether their caches have common data or - * differences. - * - * Bandwidth Efficiency: Sharing summaries instead of the full data can save - * bandwidth. If the differences between the caches are small, sharing summaries - * allows for more efficient data synchronization. - * - * Time Efficiency: The time efficiency of this scheme depends on the size of - * the cache and the differences between the two caches. Generating summaries - * and comparing them can be faster than transferring and comparing the entire - * dataset, especially for large caches. - * - * Complexity: The scheme introduces some complexity due to the need to encode - * and decode summaries. In some cases, the overhead introduced by this - * complexity might outweigh the benefits, especially if the caches are - * relatively small. In this case, you should be using a query. - * - * Data Synchronization Needs: The efficiency also depends on the data - * synchronization needs. If the data needs to be synchronized in real-time, - * this scheme might not be suitable. It's more appropriate for cases where - * periodic or batch synchronization is acceptable. - * - * Scalability: The scheme's efficiency can vary depending on the scalability - * of the system. As the number of cache entries or computers involved - * increases, the complexity of generating and comparing summaries will stay - * bound to a maximum of 16Mb. - * - */ -export class Cache { - data = new CacheData() - maxSize = DEFAULT_MAX_SIZE - - static HASH_SIZE_BYTES = 20 - - /** - * `Cache` class constructor. - * @param {CacheData?} [data] - */ - constructor (data = new CacheData(), siblingResolver = defaultSiblingResolver) { - if (data instanceof Map) { - this.data = data - } else if (Array.isArray(data)) { - this.data = new CacheData(data) - } - - if (typeof siblingResolver === 'function') { - this.siblingResolver = siblingResolver - } else { - this.siblingResolver = defaultSiblingResolver - } - } - - /** - * Readonly count of the number of cache entries. - * @type {number} - */ - get size () { - return this.data.size - } - - /** - * Readonly size of the cache in bytes. - * @type {number} - */ - get bytes () { - return this.data.size * PACKET_BYTES - } - - /** - * Inserts a `CacheEntry` value `v` into the cache at key `k`. - * @param {string} k - * @param {CacheEntry} v - * @return {boolean} - */ - insert (k, v) { - if (v.type !== PacketPublish.type) return false - if (this.has(k)) return false - - if (this.data.size === this.maxSize) { - const oldest = [...this.data.values()] - .sort((a, b) => a.timestamp - b.timestamp) - // take a slice of 128 of the oldest candidates and - // eject 32 that are mostly from non-related clusters, - // some random and cluster-related. - .pop() - - this.data.delete(oldest.packetId.toString('hex')) - if (this.onEjected) this.onEjected(oldest) - } - - v.timestamp = Date.now() - if (!v.ttl) v.ttl = Packet.ttl // use default TTL if none provided - - this.data.set(k, v) - if (this.onInsert) this.onInsert(k, v) - return true - } - - /** - * Gets a `CacheEntry` value at key `k`. - * @param {string} k - * @return {CacheEntry?} - */ - get (k) { - return this.data.get(k) - } - - /** - * @param {string} k - * @return {boolean} - */ - delete (k) { - if (!this.has(k)) return false - this.data.delete(k) - return true - } - - /** - * Predicate to determine if cache contains an entry at key `k`. - * @param {string} k - * @return {boolean} - */ - has (k) { - return this.data.has(k) - } - - /** - * Composes an indexed packet into a new `Packet` - * @param {Packet} packet - */ - async compose (packet, source = this.data) { - let previous = packet - - if (packet?.index > 0) previous = source.get(packet.previousId.toString('hex')) - if (!previous) return null - - const { meta, size, indexes, ts } = previous.message - - // follow the chain to get the buffers in order - const bufs = [...source.values()] - .filter(p => Buffer.from(p.previousId).toString('hex') === Buffer.from(previous.packetId).toString('hex')) - .sort((a, b) => a.index - b.index) - - if (!indexes || bufs.length < indexes) return null - - // concat and then hash, the original should match - const messages = bufs.map(p => p.message) - const buffers = messages.map(toBufferMaybe).filter(Boolean) - const message = Buffer.concat(buffers, size) - - if (!meta.ts) meta.ts = ts - // generate a new packet ID - const packetId = await sha256(Buffer.concat([packet.previousId, message]), { bytes: true }) - - return Packet.from({ - ...packet, - packetId, - message, - isComposed: true, - index: -1, - meta - }) - } - - async sha1 (value, toHex) { - const buf = await createDigest('SHA-1', isBufferLike(value) ? value : Buffer.from(value)) - if (!toHex) return buf - return buf.toString('hex') - } - - /** - * - * The summarize method returns a terse yet comparable summary of the cache - * contents. - * - * Think of the cache as a trie of hex characters, the summary returns a - * checksum for the current level of the trie and for its 16 children. - * - * This is similar to a merkel tree as equal subtrees can easily be detected - * without the need for further recursion. When the subtree checksums are - * inequivalent then further negotiation at lower levels may be required, this - * process continues until the two trees become synchonized. - * - * When the prefix is empty, the summary will return an array of 16 checksums - * these checksums provide a way of comparing that subtree with other peers. - * - * When a variable-length hexidecimal prefix is provided, then only cache - * member hashes sharing this prefix will be considered. - * - * For each hex character provided in the prefix, the trie will decend by one - * level, each level divides the 2^128 address space by 16. For exmaple... - * - * ``` - * Level 0 1 2 - * ---------------- - * 2b00 - * aa0e ━┓ ━┓ - * aa1b ┃ ┃ - * aae3 ┃ ┃ ━┓ - * aaea ┃ ┃ ┃ - * aaeb ┃ ━┛ ━┛ - * ab00 ┃ ━┓ - * ab1e ┃ ┃ - * ab2a ┃ ┃ - * abef ┃ ┃ - * abf0 ━┛ ━┛ - * bff9 - * ``` - * - * @param {string} prefix - a string of lowercased hexidecimal characters - * @return {Object} - * - */ - async summarize (prefix = '', predicate = o => false) { - // each level has 16 children (0x0-0xf) - const children = new Array(16).fill(null).map(_ => []) - - // partition the cache into children - for (const [key, packet] of this.data.entries()) { - if (!key || !key.slice) continue - if (prefix.length && !key.startsWith(prefix)) continue - if (!predicate(packet)) continue - const hex = key.slice(prefix.length, prefix.length + 1) - if (children[parseInt(hex, 16)]) children[parseInt(hex, 16)].push(key) - } - - // compute a checksum for all child members (deterministically) - // if the bucket is empty, return null - const buckets = await Promise.all(children.map(child => { - return child.length ? this.sha1(child.sort().join(''), true) : Promise.resolve(null) - })) - - let hash - // compute a summary hash (checksum of all other hashes) - - if (!buckets.every(b => b === null)) { - hash = await this.sha1(buckets.join(''), true) - } else { - hash = EMPTY_CACHE - } - - return { prefix, hash, buckets } - } - - /** - * The encodeSummary method provides a compact binary encoding of the output - * of summary() - * - * @param {Object} summary - the output of calling summary() - * @return {Buffer} - **/ - static encodeSummary (summary) { - // prefix is variable-length hex string encoded with the first byte indicating the length - const prefixBin = Buffer.alloc(1 + Math.ceil(summary.prefix.length / 2)) - prefixBin.writeUInt8(summary.prefix.length, 0) - const prefixHex = summary.prefix.length % 2 ? '0' + summary.prefix : summary.prefix - Buffer.from(prefixHex, 'hex').copy(prefixBin, 1) - - // hash is the summary hash (checksum of all other hashes) - const hashBin = Buffer.from(summary.hash, 'hex') - - // buckets are rows of { offset, sum } where the sum is not null - const bucketBin = Buffer.concat(summary.buckets.map((sum, offset) => { - // empty buckets are omitted from the encoding - if (!sum) return Buffer.alloc(0) - - // write the child offset as a uint8 - const offsetBin = Buffer.alloc(1) - offsetBin.writeUInt8(offset, 0) - - return Buffer.concat([offsetBin, Buffer.from(sum, 'hex')]) - })) - - return Buffer.concat([prefixBin, hashBin, bucketBin]) - } - - /** - * The decodeSummary method decodes the output of encodeSummary() - * - * @param {Buffer} bin - the output of calling encodeSummary() - * @return {Object} summary - **/ - static decodeSummary (bin) { - let o = 0 // byte offset - - // prefix is variable-length hex string encoded with the first byte indicating the length - const plen = bin.readUint8(o++) - const prefix = bin.slice(o, o += Math.ceil(plen / 2)).toString('hex').slice(-plen) - - // hash is the summary hash (checksum of all other hashes) - const hash = bin.slice(o, o += Cache.HASH_SIZE_BYTES).toString('hex') - - // buckets are rows of { offset, sum } where the sum is not null - const buckets = new Array(16).fill(null) - while (o < bin.length) { - buckets[bin.readUint8(o++)] = bin.slice(o, o += Cache.HASH_SIZE_BYTES).toString('hex') - } - - return { prefix, hash, buckets } - } -} - -export default Cache diff --git a/api/stream-relay/encryption.js b/api/stream-relay/encryption.js deleted file mode 100644 index 8b0ded7055..0000000000 --- a/api/stream-relay/encryption.js +++ /dev/null @@ -1,288 +0,0 @@ -import { sodium } from '../crypto.js' -import Buffer from '../buffer.js' -import { sha256 } from './packets.js' -import { isArrayLike, isBufferLike } from '../util.js' - -/** - * Class for handling encryption and key management. - */ -export class Encryption { - /** - * Mapping of public keys to key objects. - * @type {Object.} - */ - keys = {} - - /** - * Creates a shared key based on the provided seed or generates a random one. - * @param {Uint8Array|string} seed - Seed for key generation. - * @returns {Promise} - Shared key. - */ - static async createSharedKey (seed) { - await sodium.ready - - if (seed) return sodium.crypto_generichash(32, seed) - return sodium.randombytes_buf(32) - } - - /** - * Creates a key pair for signing and verification. - * @param {Uint8Array|string} seed - Seed for key generation. - * @returns {Promise<{ publicKey: Uint8Array, privateKey: Uint8Array }>} - Key pair. - */ - static async createKeyPair (seed) { - await sodium.ready - seed = seed || sodium.randombytes_buf(32) - - if (typeof seed === 'string') { - seed = sodium.crypto_generichash(32, seed) - } - - return sodium.crypto_sign_seed_keypair(seed) - } - - /** - * Creates an ID using SHA-256 hash. - * @param {string} str - String to hash. - * @returns {Promise} - SHA-256 hash. - */ - static async createId (str) { - await sodium.ready - return await sha256(str || sodium.randombytes_buf(32)) - } - - /** - * Creates a cluster ID using SHA-256 hash with specified output size. - * @param {string} str - String to hash. - * @returns {Promise} - SHA-256 hash with specified output size. - */ - static async createClusterId (str) { - await sodium.ready - return await sha256(str || sodium.randombytes_buf(32), { bytes: true }) - } - - /** - * Adds a key pair to the keys mapping. - * @param {Uint8Array|string} publicKey - Public key. - * @param {Uint8Array} privateKey - Private key. - */ - add (publicKey, privateKey) { - if (!publicKey) throw new Error('encryption.add expects publicKey') - if (!privateKey) throw new Error('encryption.add expects privateKey') - - const to = Buffer.from(publicKey).toString('base64') - this.keys[to] = { publicKey, privateKey, ts: Date.now() } - } - - /** - * Removes a key from the keys mapping. - * @param {Uint8Array|string} publicKey - Public key. - */ - remove (publicKey) { - delete this.keys[Buffer.from(publicKey).toString('base64')] - } - - /** - * Checks if a key is in the keys mapping. - * @param {Uint8Array|string} to - Public key or Uint8Array. - * @returns {boolean} - True if the key is present, false otherwise. - */ - has (to) { - if (to instanceof Uint8Array) { - to = Buffer.from(to).toString('base64') - } - - return !!this.keys[to] - } - - /** - * Signs a message using the given secret key. - * @param {Buffer} b - The message to sign. - * @param {Uint8Array} sk - The secret key to use. - * @returns {Uint8Array} - Signature. - */ - static sign (b, sk) { - const ct = b.subarray(sodium.crypto_sign_BYTES) - const sig = sodium.crypto_sign_detached(ct, sk) - return sig - } - - /** - * Verifies the signature of a message using the given public key. - * @param {Buffer} b - The message to verify. - * @param {Uint8Array} sig - The signature to check. - * @param {Uint8Array} pk - The public key to use. - * @returns {number} - Returns non-zero if the buffer could not be verified. - */ - static verify (b, sig, pk) { - if (sig.length !== sodium.crypto_sign_BYTES) return false - const ct = b.subarray(sodium.crypto_sign_BYTES) - return sodium.crypto_sign_verify_detached(sig, ct, pk) - } - - /** - * Opens a sealed message using the specified key. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found. - */ - openUnsigned (message, v) { - if (typeof v === 'string') v = this.keys[v] - if (!v) throw new Error(`ENOKEY (key=${v})`) - - const pk = toPK(v.publicKey) - const sk = toSK(v.privateKey) - return Buffer.from(sodium.crypto_box_seal_open(message, pk, sk)) - } - - sealUnsigned (message, v) { - if (typeof v === 'string') v = this.keys[v] - if (!v) throw new Error(`ENOKEY (key=${v})`) - - this.add(v.publicKey, v.privateKey) - - const pk = toPK(v.publicKey) - const pt = toUint8Array(message) - const ct = sodium.crypto_box_seal(pt, pk) - return sodium.crypto_box_seal(toUint8Array(ct), pk) - } - - /** - * Decrypts a sealed and signed message for a specific receiver. - * @param {Buffer} message - The sealed message. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Decrypted message. - * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. - */ - open (message, v) { - if (typeof v === 'string') v = this.keys[v] - if (!v) throw new Error(`ENOKEY (key=${v})`) - - const pk = toPK(v.publicKey) - const sk = toSK(v.privateKey) - const buf = sodium.crypto_box_seal_open(message, pk, sk) - - if (buf.byteLength <= sodium.crypto_sign_BYTES) { - throw new Error('EMALFORMED') - } - - const sig = buf.subarray(0, sodium.crypto_sign_BYTES) - const ct = buf.subarray(sodium.crypto_sign_BYTES) - - if (!sodium.crypto_sign_verify_detached(sig, ct, v.publicKey)) { - throw new Error('ENOTVERIFIED') - } - - return Buffer.from(sodium.crypto_box_seal_open(ct, pk, sk)) - } - - /** - * Seals and signs a message for a specific receiver using their public key. - * - * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on - * a plaintext `message` for a `receiver` identity. This prevents repudiation - * attacks and doesn't rely on packet chain guarantees. - * - * let ct = Seal(sender | pt, receiver) - * let sig = Sign(ct, sk) - * let out = Seal(sig | ct) - * - * In an setup between Alice & Bob, this means: - * - Only Bob sees the plaintext - * - Alice wrote the plaintext and the ciphertext - * - Only Bob can see that Alice wrote the plaintext and ciphertext - * - Bob cannot forward the message without invalidating Alice's signature. - * - The outer encryption serves to prevent an attacker from replacing Alice's - * signature. As with _sign-encrypt-sign (SES), ESE is a variant of - * including the recipient's name inside the plaintext, which is then signed - * and encrypted Alice signs her plaintext along with her ciphertext, so as - * to protect herself from a laintext-substitution attack. At the same time, - * Alice's signed plaintext gives Bob non-repudiation. - * - * @see https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html - * - * @param {Buffer} message - The message to seal. - * @param {Object|string} v - Key object or public key. - * @returns {Buffer} - Sealed message. - * @throws {Error} - Throws ENOKEY if the key is not found. - */ - seal (message, v) { - if (typeof v === 'string') v = this.keys[v] - if (!v) throw new Error(`ENOKEY (key=${v})`) - - this.add(v.publicKey, v.privateKey) - - const pk = toPK(v.publicKey) - const pt = toUint8Array(message) - const ct = sodium.crypto_box_seal(pt, pk) - const sig = sodium.crypto_sign_detached(ct, v.privateKey) - return sodium.crypto_box_seal(toUint8Array(Buffer.concat([sig, ct])), pk) - } -} - -/** - * Converts an Ed25519 public key to a Curve25519 public key. - * @param {Uint8Array} pk - Ed25519 public key. - * @returns {Uint8Array} - Curve25519 public key. - */ -function toPK (pk) { - try { - return sodium.crypto_sign_ed25519_pk_to_curve25519(pk) - } catch {} - - return new Uint8Array(0) -} - -/** - * Converts an Ed25519 secret key to a Curve25519 secret key. - * @param {Uint8Array} sk - Ed25519 secret key. - * @returns {Uint8Array} - Curve25519 secret key. - */ -function toSK (sk) { - try { - return sodium.crypto_sign_ed25519_sk_to_curve25519(sk) - } catch {} - - return new Uint8Array(0) -} - -/** - * Converts different types of input to a Uint8Array. - * @param {Uint8Array|string|Buffer} buffer - Input buffer. - * @returns {Uint8Array} - Uint8Array representation of the input. - */ -const textEncoder = new TextEncoder() - -/** - * Converts different types of input to a Uint8Array. - * @param {Uint8Array|string|Buffer} buffer - Input buffer. - * @returns {Uint8Array} - Uint8Array representation of the input. - */ -function toUint8Array (buffer) { - if (buffer instanceof Uint8Array) { - return buffer - } - - if (typeof buffer === 'string') { - return textEncoder.encode(buffer) - } - - if (buffer?.buffer instanceof ArrayBuffer) { - return new Uint8Array(buffer.buffer) - } - - if (isArrayLike(buffer) || isBufferLike(buffer)) { - return new Uint8Array(buffer) - } - - if (buffer instanceof ArrayBuffer) { - return new Uint8Array(buffer) - } - - if (buffer && Symbol.iterator in buffer) { - return new Uint8Array(buffer) - } - - return new Uint8Array(0) -} diff --git a/api/stream-relay/index.js b/api/stream-relay/index.js deleted file mode 100644 index f24af29817..0000000000 --- a/api/stream-relay/index.js +++ /dev/null @@ -1,2167 +0,0 @@ -/** - * @module network - * @status Experimental - * - * This module provides primitives for creating a p2p network. - */ -import { isBufferLike } from '../util.js' -import { Buffer } from '../buffer.js' -import { sodium, randomBytes } from '../crypto.js' - -import { Encryption } from './encryption.js' -import { Cache } from './cache.js' -import * as NAT from './nat.js' - -import { - Packet, - PacketPing, - PacketPong, - PacketIntro, - PacketPublish, - PacketStream, - PacketSync, - PacketJoin, - PacketQuery, - sha256, - VERSION -} from './packets.js' - -let logcount = 0 -const process = globalThis.process || globalThis.window?.__args -const COLOR_GRAY = '\x1b[90m' -const COLOR_WHITE = '\x1b[37m' -const COLOR_RESET = '\x1b[0m' - -export const debug = (pid, ...args) => { - if (typeof process === 'undefined' || !process.env.DEBUG) return - const output = COLOR_GRAY + - String(logcount++).padStart(6) + ' │ ' + COLOR_WHITE + - pid.slice(0, 4) + ' ' + args.join(' ') + COLOR_RESET - - if (new RegExp(process.env.DEBUG).test(output)) console.log(output) -} - -export { Packet, sha256, Cache, Encryption, NAT } - -/** - * Retry delay in milliseconds for ping. - * @type {number} - */ -export const PING_RETRY = 500 - -/** - * Probe wait timeout in milliseconds. - * @type {number} - */ -export const PROBE_WAIT = 512 - -/** - * Default keep alive timeout. - * @type {number} - */ -export const DEFAULT_KEEP_ALIVE = 30_000 - -/** - * Default rate limit threshold in milliseconds. - * @type {number} - */ -export const DEFAULT_RATE_LIMIT_THRESHOLD = 8000 - -const PRIV_PORTS = 1024 -const MAX_PORTS = 65535 - PRIV_PORTS -const MAX_BANDWIDTH = 1024 * 32 - -const PEERID_REGEX = /^([A-Fa-f0-9]{2}){32}$/ - -/** - * Port generator factory function. - * @param {object} ports - the cache to use (a set) - * @param {number?} p - initial port - * @return {number} - */ -export const getRandomPort = (ports = new Set(), p) => { - do { - p = Math.max(1024, Math.ceil(Math.random() * 0xffff)) - } while (ports.has(p) && ports.size < MAX_PORTS) - - ports.add(p) - return p -} - -const isReplicatable = type => ( - type === PacketPublish.type || - type === PacketJoin.type -) - -/** - * Computes rate limit predicate value for a port and address pair for a given - * threshold updating an input rates map. This method is accessed concurrently, - * the rates object makes operations atomic to avoid race conditions. - * - * @param {Map} rates - * @param {number} type - * @param {number} port - * @param {string} address - * @return {boolean} - */ -export function rateLimit (rates, type, port, address, subclusterIdQuota) { - const R = isReplicatable(type) - const key = (R ? 'R' : 'C') + ':' + address + ':' + port - const quota = subclusterIdQuota || (R ? 512 : 4096) - const time = Math.floor(Date.now() / 60000) - const rate = rates.get(key) || { time, quota, used: 0 } - - rate.mtime = Date.now() // checked by mainLoop for garabge collection - - if (time !== rate.time) { - rate.time = time - if (rate.used > rate.quota) rate.quota -= 1 - else if (rate.used < quota) rate.quota += 1 - rate.used = 0 - } - - rate.used += 1 - - rates.set(key, rate) - if (rate.used >= rate.quota) return true -} - -/** - * A `RemotePeer` represents an initial, discovered, or connected remote peer. - * Typically, you will not need to create instances of this class directly. - */ -export class RemotePeer { - peerId = null - address = null - port = 0 - natType = null - clusters = {} - pingId = null - distance = 0 - connected = false - opening = 0 - probed = 0 - proxy = null - clock = 0 - uptime = 0 - lastUpdate = 0 - lastRequest = 0 - localPeer = null - - /** - * `RemotePeer` class constructor. - * @param {{ - * peerId?: string, - * address?: string, - * port?: number, - * natType?: number, - * clusters: object, - * reflectionId?: string, - * distance?: number, - * publicKey?: string, - * privateKey?: string, - * clock?: number, - * lastUpdate?: number, - * lastRequest?: number - * }} o - */ - constructor (o, peer) { - this.localPeer = peer - - if (!o.peerId) throw new Error('expected .peerId') - if (o.indexed) o.natType = NAT.UNRESTRICTED - if (o.natType && !NAT.isValid(o.natType)) throw new Error(`invalid .natType (${o.natType})`) - - const cid = o.clusterId?.toString('base64') - const scid = o.subclusterId?.toString('base64') - - if (cid && scid) { - this.clusters[cid] = { [scid]: { rateLimit: MAX_BANDWIDTH } } - } - - Object.assign(this, o) - } - - async write (sharedKey, args) { - let rinfo = this - if (this.proxy) rinfo = this.proxy - - const keys = await Encryption.createKeyPair(sharedKey) - - args.subclusterId = Buffer.from(keys.publicKey) - args.clusterId = Buffer.from(this.localPeer.clusterId, 'base64') - args.usr3 = Buffer.from(this.peerId, 'hex') - args.usr4 = Buffer.from(this.localPeer.peerId, 'hex') - args.message = this.localPeer.encryption.seal(args.message, keys) - - const packets = await this.localPeer._message2packets(PacketStream, args.message, args) - - if (this.proxy) { - debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) - } - - for (const packet of packets) { - const from = this.localPeer.peerId.slice(0, 6) - const to = this.peerId.slice(0, 6) - debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) - - this.localPeer.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) - await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) - } - - return packets - } -} - -/** - * `Peer` class factory. - * @param {{ createSocket: function('udp4', null, object?): object }} options - */ -export class Peer { - port = null - address = null - natType = NAT.UNKNOWN - nextNatType = NAT.UNKNOWN - clusters = {} - reflectionId = null - reflectionTimeout = null - reflectionStage = 0 - reflectionRetry = 1 - reflectionFirstResponder = null - peerId = '' - isListening = false - ctime = Date.now() - lastUpdate = 0 - lastSync = 0 - closing = false - clock = 0 - unpublished = {} - cache = null - uptime = 0 - maxHops = 16 - bdpCache = /** @type {number[]} */ ([]) - - dgram = () => { throw new Error('dgram implementation required in constructor as second argument') } - - onListening = null - onDelete = null - - sendQueue = [] - firewall = null - rates = new Map() - streamBuffer = new Map() - gate = new Map() - returnRoutes = new Map() - - metrics = { - i: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, REJECTED: 0 }, - o: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 } - } - - peers = JSON.parse(/* snapshot_start=1691579150299, filter=easy,static */` - [{"address":"44.213.42.133","port":10885,"peerId":"4825fe0475c44bc0222e76c5fa7cf4759cd5ef8c66258c039653f06d329a9af5","natType":31,"indexed":true},{"address":"107.20.123.15","port":31503,"peerId":"2de8ac51f820a5b9dc8a3d2c0f27ccc6e12a418c9674272a10daaa609eab0b41","natType":31,"indexed":true},{"address":"54.227.171.107","port":43883,"peerId":"7aa3d21ceb527533489af3888ea6d73d26771f30419578e85fba197b15b3d18d","natType":31,"indexed":true},{"address":"54.157.134.116","port":34420,"peerId":"1d2315f6f16e5f560b75fbfaf274cad28c12eb54bb921f32cf93087d926f05a9","natType":31,"indexed":true},{"address":"184.169.205.9","port":52489,"peerId":"db00d46e23d99befe42beb32da65ac3343a1579da32c3f6f89f707d5f71bb052","natType":31,"indexed":true},{"address":"35.158.123.13","port":31501,"peerId":"4ba1d23266a2d2833a3275c1d6e6f7ce4b8657e2f1b8be11f6caf53d0955db88","natType":31,"indexed":true},{"address":"3.68.89.3","port":22787,"peerId":"448b083bd8a495ce684d5837359ce69d0ff8a5a844efe18583ab000c99d3a0ff","natType":31,"indexed":true},{"address":"3.76.100.161","port":25761,"peerId":"07bffa90d89bf74e06ff7f83938b90acb1a1c5ce718d1f07854c48c6c12cee49","natType":31,"indexed":true},{"address":"3.70.241.230","port":61926,"peerId":"1d7ee8d965794ee286ac425d060bab27698a1de92986dc6f4028300895c6aa5c","natType":31,"indexed":true},{"address":"3.70.160.181","port":41141,"peerId":"707c07171ac9371b2f1de23e78dad15d29b56d47abed5e5a187944ed55fc8483","natType":31,"indexed":true},{"address":"3.122.250.236","port":64236,"peerId":"a830615090d5cdc3698559764e853965a0d27baad0e3757568e6c7362bc6a12a","natType":31,"indexed":true},{"address":"18.130.98.23","port":25111,"peerId":"ba483c1477ab7a99de2d9b60358d9641ff6a6dc6ef4e3d3e1fc069b19ac89da4","natType":31,"indexed":true},{"address":"13.42.10.247","port":2807,"peerId":"032b79de5b4581ee39c6d15b12908171229a8eb1017cf68fd356af6bbbc21892","natType":31,"indexed":true},{"address":"18.229.140.216","port":36056,"peerId":"73d726c04c05fb3a8a5382e7a4d7af41b4e1661aadf9020545f23781fefe3527","natType":31,"indexed":true}] - `/* snapshot_end=1691579150299 */).map((/** @type {object} */ o) => new RemotePeer({ ...o, indexed: true }, this)) - - /** - * `Peer` class constructor. - * @param {object=} opts - Options - * @param {Buffer} opts.peerId - A 32 byte buffer (ie, `Encryption.createId()`). - * @param {Buffer} opts.clusterId - A 32 byte buffer (ie, `Encryption.createClusterId()`). - * @param {number=} opts.port - A port number. - * @param {number=} opts.probeInternalPort - An internal port number (semi-private for testing). - * @param {number=} opts.probeExternalPort - An external port number (semi-private for testing). - * @param {number=} opts.natType - A nat type. - * @param {string=} opts.address - An ipv4 address. - * @param {number=} opts.keepalive - The interval of the main loop. - * @param {function=} opts.siblingResolver - A function that can be used to determine canonical data in case two packets have concurrent clock values. - * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). - */ - constructor (persistedState = {}, dgram) { - const config = persistedState?.config ?? persistedState ?? {} - - this.encryption = new Encryption() - - if (!config.peerId) throw new Error('constructor expected .peerId') - if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error('invalid .peerId') - - // - // The purpose of this.config is to seperate transitioned state from initial state. - // - this.config = { - keepalive: DEFAULT_KEEP_ALIVE, - ...config - } - - this.dgram = () => dgram - - let cacheData - - if (persistedState?.data?.length > 0) { - cacheData = new Map(persistedState.data) - } - - this.cache = new Cache(cacheData, config.siblingResolver) - this.cache.onEjected = p => this.mcast(p) - - this.unpublished = persistedState?.unpublished || {} - this._onError = err => this.onError && this.onError(err) - - Object.assign(this, config) - - if (!this.indexed && !this.clusterId) throw new Error('constructor expected .clusterId') - if (typeof this.peerId !== 'string') throw new Error('peerId should be of type string') - - this.port = config.port || null - this.natType = config.natType || null - this.address = config.address || null - - this.socket = this.dgram().createSocket('udp4', null, this) - this.probeSocket = this.dgram().createSocket('udp4', null, this).unref() - - const isRecoverable = err => - err.code === 'ECONNRESET' || - err.code === 'ECONNREFUSED' || - err.code === 'EADDRINUSE' || - err.code === 'ETIMEDOUT' - - this.socket.on('error', err => isRecoverable(err) && this._listen()) - this.probeSocket.on('error', err => isRecoverable(err) && this._listen()) - } - - /** - * An implementation for clearning an interval that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearInterval (tid) { - clearInterval(tid) - } - - /** - * An implementation for clearning a timeout that can be overridden by the test suite - * @param Number the number that identifies the timer - * @return {undefined} - * @ignore - */ - _clearTimeout (tid) { - clearTimeout(tid) - } - - /** - * An implementation of an internal timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setInterval (fn, t) { - return setInterval(fn, t) - } - - /** - * An implementation of an timeout timer that can be overridden by the test suite - * @return {Number} - * @ignore - */ - _setTimeout (fn, t) { - return setTimeout(fn, t) - } - - /** - * A method that encapsulates the listing procedure - * @return {undefined} - * @ignore - */ - async _listen () { - await sodium.ready - - this.socket.removeAllListeners() - this.probeSocket.removeAllListeners() - - this.socket.on('message', (...args) => this._onMessage(...args)) - this.socket.on('error', (...args) => this._onError(...args)) - this.probeSocket.on('message', (...args) => this._onProbeMessage(...args)) - this.probeSocket.on('error', (...args) => this._onError(...args)) - - this.socket.setMaxListeners(2048) - this.probeSocket.setMaxListeners(2048) - - const listening = Promise.all([ - new Promise(resolve => this.socket.on('listening', resolve)), - new Promise(resolve => this.probeSocket.on('listening', resolve)) - ]) - - this.socket.bind(this.config.port || 0) - this.probeSocket.bind(this.config.probeInternalPort || 0) - - await listening - - this.config.port = this.socket.address().port - this.config.probeInternalPort = this.probeSocket.address().port - - if (this.onListening) this.onListening() - this.isListening = true - - debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) - } - - /* - * This method will bind the sockets, begin pinging known peers, and start - * the main program loop. - * @return {Any} - */ - async init (cb) { - if (!this.isListening) await this._listen() - if (cb) this.onReady = cb - - // tell all well-known peers that we would like to hear from them, if - // we hear from any we can ask for the reflection information we need. - for (const peer of this.peers.filter(p => p.indexed)) { - await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) - } - - this._mainLoop(Date.now()) - this.mainLoopTimer = this._setInterval(ts => this._mainLoop(ts), this.config.keepalive) - - if (this.indexed && this.onReady) return this.onReady(await this.getInfo()) - } - - /** - * Continuously evaluate the state of the peer and its network - * @return {undefined} - * @ignore - */ - async _mainLoop (ts) { - if (this.closing) return this._clearInterval(this.mainLoopTimer) - - // Node 21.x will need this... - // const offline = typeof globalThis.navigator.onLine !== 'undefined' && !globalThis.navigator.onLine - const offline = globalThis.navigator && !globalThis.navigator.onLine - if (offline) { - if (this.onConnecting) this.onConnecting({ code: -2, status: 'Offline' }) - return true - } - - if (!this.reflectionId) this.requestReflection() - if (this.onInterval) this.onInterval() - - this.uptime += this.config.keepalive - - // wait for nat type to be discovered - if (!NAT.isValid(this.natType)) return true - - for (const [k, packet] of [...this.cache.data]) { - const p = Packet.from(packet) - if (!p) continue - if (!p.timestamp) p.timestamp = ts - const clusterId = p.clusterId.toString('base64') - - const mult = this.clusters[clusterId] ? 2 : 1 - const ttl = (p.ttl < Packet.ttl) ? p.ttl : Packet.ttl * mult - const deadline = p.timestamp + ttl - - if (deadline <= ts) { - if (p.hops < this.maxHops) this.mcast(p) - this.cache.delete(k) - debug(this.peerId, '-- DELETE', k, this.cache.size) - if (this.onDelete) this.onDelete(p) - } - } - - for (let [k, v] of this.gate.entries()) { - v -= 1 - if (!v) this.gate.delete(k) - else this.gate.set(k, v) - } - - for (let [k, v] of this.returnRoutes.entries()) { - v -= 1 - if (!v) this.returnRoutes.delete(k) - else this.returnRoutes.set(k, v) - } - - // prune peer list - for (const [i, peer] of Object.entries(this.peers)) { - if (peer.indexed) continue - const expired = (peer.lastUpdate + this.config.keepalive) < Date.now() - if (expired) { // || !NAT.isValid(peer.natType)) { - const p = this.peers.splice(i, 1) - if (this.onDisconnect) this.onDisconnect(p) - continue - } - } - - // heartbeat - const { hash } = await this.cache.summarize('', this.cachePredicate) - for (const [, peer] of Object.entries(this.peers)) { - this.ping(peer, false, { - message: { - requesterPeerId: this.peerId, - natType: this.natType, - cacheSummaryHash: hash || null, - cacheSize: this.cache.size - } - }) - } - - // if this peer has previously tried to join any clusters, multicast a - // join messages for each into the network so we are always searching. - for (const cluster of Object.values(this.clusters)) { - for (const subcluster of Object.values(cluster)) { - this.join(subcluster.sharedKey, subcluster) - } - } - return true - } - - /** - * Enqueue packets to be sent to the network - * @param {Buffer} data - An encoded packet - * @param {number} port - The desination port of the remote host - * @param {string} address - The destination address of the remote host - * @param {Socket=this.socket} socket - The socket to send on - * @return {undefined} - * @ignore - */ - send (data, port, address, socket = this.socket) { - this.sendQueue.push({ data, port, address, socket }) - this._scheduleSend() - } - - /** - * @private - */ - _scheduleSend () { - if (this.sendTimeout) this._clearTimeout(this.sendTimeout) - this.sendTimeout = this._setTimeout(() => { this._dequeue() }) - } - - /** - * @private - */ - _dequeue () { - if (!this.sendQueue.length) return - const { data, port, address, socket } = this.sendQueue.shift() - - socket.send(data, port, address, err => { - if (this.sendQueue.length) this._scheduleSend() - if (err) return this._onError(err) - - const packet = Packet.decode(data) - if (!packet) return - - this.metrics.o[packet.type]++ - delete this.unpublished[packet.packetId.toString('hex')] - if (this.onSend && packet.type) this.onSend(packet, port, address) - debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) - }) - } - - /** - * Send any unpublished packets - * @return {undefined} - * @ignore - */ - async sendUnpublished () { - for (const [packetId] of Object.entries(this.unpublished)) { - const packet = this.cache.get(packetId) - - if (!packet) { // it may have been purged already - delete this.unpublished[packetId] - continue - } - - await this.mcast(packet) - debug(this.peerId, `-> RESEND (packetId=${packetId})`) - if (this.onState) await this.onState(this.getState()) - } - } - - /** - * Get the serializable state of the peer (can be passed to the constructor or create method) - * @return {undefined} - */ - async getState () { - this.config.clock = this.clock // save off the clock - - const peers = this.peers.map(p => { - p = { ...p } - delete p.localPeer - return p - }) - - return { - peers, - config: this.config, - data: [...this.cache.data.entries()], - unpublished: this.unpublished - } - } - - async getInfo () { - return { - address: this.address, - port: this.port, - clock: this.clock, - uptime: this.uptime, - natType: this.natType, - natName: NAT.toString(this.natType), - peerId: this.peerId - } - } - - async cacheInsert (packet) { - this.cache.insert(packet.packetId.toString('hex'), Packet.from(packet)) - } - - async addIndexedPeer (info) { - if (!info.peerId) throw new Error('options.peerId required') - if (!info.address) throw new Error('options.address required') - if (!info.port) throw new Error('options.port required') - info.indexed = true - this.peers.push(new RemotePeer(info)) - } - - async reconnect () { - this.lastUpdate = 0 - this.requestReflection() - } - - async disconnect () { - this.natType = null - this.reflectionStage = 0 - this.reflectionId = null - this.reflectionTimeout = null - this.probeReflectionTimeout = null - } - - async sealUnsigned (...args) { - return this.encryption.sealUnsigned(...args) - } - - async openUnsigned (...args) { - return this.encryption.openUnsigned(...args) - } - - async seal (...args) { - return this.encryption.seal(...args) - } - - async open (...args) { - return this.encryption.open(...args) - } - - async addEncryptionKey (...args) { - return this.encryption.add(...args) - } - - /** - * Get a selection of known peers - * @return {Array} - * @ignore - */ - getPeers (packet, peers, ignorelist, filter = o => o) { - const rand = () => Math.random() - 0.5 - - const base = p => { - if (ignorelist.findIndex(ilp => (ilp.port === p.port) && (ilp.address === p.address)) > -1) return false - if (p.lastUpdate === 0) return false - if (p.lastUpdate < Date.now() - (this.config.keepalive * 4)) return false - if (this.peerId === p.peerId) return false // same as me - if (packet.message.requesterPeerId === p.peerId) return false // same as requester - @todo: is this true in all cases? - if (!p.port || !NAT.isValid(p.natType)) return false - return true - } - - const candidates = peers - .filter(filter) - .filter(base) - .sort(rand) - - const list = candidates.slice(0, 3) - - if (!list.some(p => p.indexed)) { - const indexed = candidates.filter(p => p.indexed && !list.includes(p)) - if (indexed.length) list.push(indexed[0]) - } - - const clusterId = packet.clusterId.toString('base64') - const friends = candidates.filter(p => p.clusters && p.clusters[clusterId] && !list.includes(p)) - if (friends.length) { - list.unshift(friends[0]) - list.unshift(...candidates.filter(c => c.address === friends[0].address && c.peerId === friends[0].peerId)) - } - - return list - } - - /** - * Send an eventually consistent packet to a selection of peers (fanout) - * @return {undefined} - * @ignore - */ - async mcast (packet, ignorelist = []) { - const peers = this.getPeers(packet, this.peers, ignorelist) - const pid = packet.packetId.toString('hex') - - packet.hops += 1 - - for (const peer of peers) { - this.send(await Packet.encode(packet), peer.port, peer.address) - } - - if (this.onMulticast) this.onMulticast(packet) - if (this.gate.has(pid)) return - this.gate.set(pid, 1) - } - - /** - * The process of determining this peer's NAT behavior (firewall and dependentness) - * @return {undefined} - * @ignore - */ - async requestReflection () { - if (this.closing || this.indexed || this.reflectionId) { - debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) - return - } - - if (this.natType && (this.lastUpdate > 0 && (Date.now() - this.config.keepalive) < this.lastUpdate)) { - debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) - return - } - - debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) - if (this.onConnecting) this.onConnecting({ code: -1, status: `Entering reflection (lastUpdate ${Date.now() - this.lastUpdate}ms)` }) - - const peers = [...this.peers] - .filter(p => p.lastUpdate !== 0) - .filter(p => p.natType === NAT.UNRESTRICTED || p.natType === NAT.ADDR_RESTRICTED || p.indexed) - - if (peers.length < 2) { - if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) - debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') - - if (++this.reflectionRetry > 16) this.reflectionRetry = 1 - return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) - } - - this.reflectionRetry = 1 - - const requesterPeerId = this.peerId - const opts = { requesterPeerId, isReflection: true } - - this.reflectionId = opts.reflectionId = randomBytes(6).toString('hex').padStart(12, '0') - - if (this.onConnecting) { - this.onConnecting({ code: 0.5, status: `Found ${peers.length} elegible peers for reflection` }) - } - // - // # STEP 1 - // The purpose of this step is strictily to discover the external port of - // the probe socket. - // - if (this.reflectionStage === 0) { - if (this.onConnecting) this.onConnecting({ code: 1, status: 'Discover External Port' }) - // start refelection with an zeroed NAT type - if (this.reflectionTimeout) this._clearTimeout(this.reflectionTimeout) - this.reflectionStage = 1 - - debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) - const list = peers.filter(p => p.probed).sort(() => Math.random() - 0.5) - const peer = list.length ? list[0] : peers[0] - peer.probed = Date.now() // mark this peer as being used to provide port info - this.ping(peer, false, { message: { ...opts, isProbe: true } }, this.probeSocket) - - // we expect onMessageProbe to fire and clear this timer or it will timeout - this.probeReflectionTimeout = this._setTimeout(() => { - this.probeReflectionTimeout = null - if (this.reflectionStage !== 1) return - debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) - if (this.onConnecting) this.onConnecting({ code: 1, status: 'Timeout' }) - - this.reflectionStage = 1 - this.reflectionId = null - this.requestReflection() - }, 1024) - - debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) - return - } - - // - // # STEP 2 - // - // The purpose of step 2 is twofold: - // - // 1) ask two different peers for the external port and address for our primary socket. - // If they are different, we can determine that our NAT is a `ENDPOINT_DEPENDENT`. - // - // 2) ask the peers to also reply to our probe socket from their probe socket. - // These packets will both be dropped for `FIREWALL_ALLOW_KNOWN_IP_AND_PORT` and will both - // arrive for `FIREWALL_ALLOW_ANY`. If one packet arrives (which will always be from the peer - // which was previously probed), this indicates `FIREWALL_ALLOW_KNOWN_IP`. - // - if (this.reflectionStage === 1) { - this.reflectionStage = 2 - const { probeExternalPort } = this.config - if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Discover NAT' }) - - // peer1 is the most recently probed (likely the same peer used in step1) - // using the most recent guarantees that the the NAT mapping is still open - const peer1 = peers.filter(p => p.probed).sort((a, b) => b.probed - a.probed)[0] - - // peer has NEVER previously been probed - const peer2 = peers.filter(p => !p.probed).sort(() => Math.random() - 0.5)[0] - - if (!peer1 || !peer2) { - debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') - if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Insufficent Peers' }) - return this._setTimeout(() => this.requestReflection(), 256) - } - - debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) - - // reset reflection variables to defaults - this.nextNatType = NAT.UNKNOWN - this.reflectionFirstResponder = null - - this.ping(peer1, false, { message: { ...opts, probeExternalPort } }) - this.ping(peer2, false, { message: { ...opts, probeExternalPort } }) - - if (this.onConnecting) { - this.onConnecting({ code: 2, status: `Requesting reflection from ${peer1.address}` }) - this.onConnecting({ code: 2, status: `Requesting reflection from ${peer2.address}` }) - } - - if (this.reflectionTimeout) { - this._clearTimeout(this.reflectionTimeout) - this.reflectionTimeout = null - } - - this.reflectionTimeout = this._setTimeout(ts => { - this.reflectionTimeout = null - if (this.reflectionStage !== 2) return - if (this.onConnecting) this.onConnecting({ code: 2, status: 'Timeout' }) - this.reflectionStage = 1 - this.reflectionId = null - debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) - return this.requestReflection() - }, 2048) - } - } - - /** - * Ping another peer - * @return {PacketPing} - * @ignore - */ - async ping (peer, withRetry, props, socket) { - if (!peer) { - return - } - - props.message.requesterPeerId = this.peerId - props.message.uptime = this.uptime - props.message.timestamp = Date.now() - props.clusterId = this.config.clusterId - - const packet = new PacketPing(props) - const data = await Packet.encode(packet) - - const send = async () => { - if (this.closing) return false - - const p = this.peers.find(p => p.peerId === peer.peerId) - // if (p?.reflectionId && p.reflectionId === packet.message.reflectionId) { - // return false - // } - - this.send(data, peer.port, peer.address, socket) - if (p) p.lastRequest = Date.now() - } - - send() - - if (withRetry) { - this._setTimeout(send, PING_RETRY) - this._setTimeout(send, PING_RETRY * 4) - } - - return packet - } - - /** - * Get a peer - * @return {RemotePeer} - * @ignore - */ - getPeer (id) { - return this.peers.find(p => p.peerId === id) - } - - /** - * This should be called at least once when an app starts to multicast - * this peer, and starts querying the network to discover peers. - * @param {object} keys - Created by `Encryption.createKeyPair()`. - * @param {object=} args - Options - * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. - * @return {RemotePeer} - */ - async join (sharedKey, args = { rateLimit: MAX_BANDWIDTH }) { - const keys = await Encryption.createKeyPair(sharedKey) - this.encryption.add(keys.publicKey, keys.privateKey) - - if (!this.port || !this.natType) return - - args.sharedKey = sharedKey - - const clusterId = args.clusterId || this.config.clusterId - const subclusterId = Buffer.from(keys.publicKey) - - const cid = clusterId?.toString('base64') - const scid = subclusterId?.toString('base64') - - this.clusters[cid] ??= {} - this.clusters[cid][scid] = args - - this.clock += 1 - - const packet = new PacketJoin({ - clock: this.clock, - clusterId, - subclusterId, - message: { - requesterPeerId: this.peerId, - natType: this.natType, - address: this.address, - port: this.port - } - }) - - debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) - if (this.onState) await this.onState(this.getState()) - - this.mcast(packet) - this.gate.set(packet.packetId.toString('hex'), 1) - } - - /** - * @param {Packet} T - The constructor to be used to create packets. - * @param {Any} message - The message to be split and packaged. - * @return {Array>} - * @ignore - */ - async _message2packets (T, message, args) { - const { clusterId, subclusterId, packet, nextId, meta = {}, usr1, usr2, sig } = args - - let messages = [message] - const len = message?.byteLength ?? message?.length ?? 0 - let clock = packet?.clock || 0 - - const siblings = [...this.cache.data.values()] - .filter(Boolean) - .filter(p => p?.previousId?.toString('hex') === packet?.packetId?.toString('hex')) - - if (siblings.length) { - // if there are siblings of the previous packet - // pick the highest clock value, the parent packet or the sibling - const sort = (a, b) => a.clock - b.clock - const sib = siblings.sort(sort).reverse()[0] - clock = Math.max(clock, sib.clock) + 1 - } - - clock += 1 - - if (len > 1024) { // Split packets that have messages bigger than Packet.maxLength - messages = [{ - meta, - ts: Date.now(), - size: message.length, - indexes: Math.ceil(message.length / 1024) - }] - let pos = 0 - while (pos < message.length) messages.push(message.slice(pos, pos += 1024)) - } - - // turn each message into an actual packet - const packets = messages.map(message => new T({ - ...args, - clusterId, - subclusterId, - clock, - message, - usr1, - usr2, - usr3: args.usr3, - usr4: args.usr4, - sig - })) - - if (packet) packets[0].previousId = packet.packetId - if (nextId) packets[packets.length - 1].nextId = nextId - - // set the .packetId (any maybe the .previousId and .nextId) - for (let i = 0; i < packets.length; i++) { - if (packets.length > 1) packets[i].index = i - - if (i === 0) { - packets[0].packetId = await sha256(packets[0].message, { bytes: true }) - } else { - // all fragments will have the same previous packetId - // the index is used to stitch them back together in order. - packets[i].previousId = packets[0].packetId - } - - if (packets[i + 1]) { - packets[i + 1].packetId = await sha256( - Buffer.concat([ - await sha256(packets[i].packetId, { bytes: true }), - await sha256(packets[i + 1].message, { bytes: true }) - ]), - { bytes: true } - ) - packets[i].nextId = packets[i + 1].packetId - } - } - - return packets - } - - /** - * Sends a packet into the network that will be replicated and buffered. - * Each peer that receives it will buffer it until TTL and then replicate - * it provided it has has not exceeded their maximum number of allowed hops. - * - * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. - * @param {object} args - The arguments to be applied. - * @param {Buffer} args.message - The message to be encrypted by keys and sent. - * @param {Packet=} args.packet - The previous packet in the packet chain. - * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. - * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. - * @return {Array} - */ - async publish (sharedKey, args) { // wtf to do here, we need subclusterId and the actual user keys - if (!sharedKey) throw new Error('.publish() expected "sharedKey" argument in first position') - if (!isBufferLike(args.message)) throw new Error('.publish() will only accept a message of type buffer') - - const keys = await Encryption.createKeyPair(sharedKey) - - args.subclusterId = Buffer.from(keys.publicKey) - args.clusterId = args.clusterId || this.config.clusterId - - const message = this.encryption.seal(args.message, keys) - const packets = await this._message2packets(PacketPublish, message, args) - - for (const packet of packets) { - this.cacheInsert(packet) - - if (this.onPacket) this.onPacket(packet, this.port, this.address, true) - - this.unpublished[packet.packetId.toString('hex')] = Date.now() - if (globalThis.navigator && !globalThis.navigator.onLine) continue - - this.mcast(packet) - } - - return packets - } - - /** - * @return {undefined} - */ - async sync (peer) { - if (typeof peer === 'string') { - peer = this.peers.find(p => p.peerId === peer) - } - - const rinfo = peer?.proxy || peer - - this.lastSync = Date.now() - const summary = await this.cache.summarize('', this.cachePredicate) - - debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) - if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) - - // if we are out of sync send our cache summary - const data = await Packet.encode(new PacketSync({ - message: Cache.encodeSummary(summary) - })) - - this.send(data, rinfo.port, rinfo.address, peer.socket) - } - - close () { - this._clearInterval(this.mainLoopTimer) - - if (this.closing) return - - this.closing = true - this.socket.close() - - if (this.onClose) this.onClose() - } - - /** - * Deploy a query into the network - * @return {undefined} - * - */ - async query (query) { - const packet = new PacketQuery({ - message: query, - usr1: Buffer.from(String(Date.now())), - usr3: Buffer.from(randomBytes(32)), - usr4: Buffer.from(String(1)) - }) - const data = await Packet.encode(packet) - - const p = Packet.decode(data) // finalize a packet - const pid = p.packetId.toString('hex') - - if (this.gate.has(pid)) return - this.returnRoutes.set(p.usr3.toString('hex'), {}) - this.gate.set(pid, 1) // don't accidentally spam - - debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) - - await this.mcast(p) - } - - /** - * - * This is a default implementation for deciding what to summarize - * from the cache when receiving a request to sync. that can be overridden - * - */ - cachePredicate (packet) { - return packet.version === VERSION && packet.timestamp > Date.now() - Packet.ttl - } - - /** - * A connection was made, add the peer to the local list of known - * peers and call the onConnection if it is defined by the user. - * - * @return {undefined} - * @ignore - */ - async _onConnection (packet, peerId, port, address, proxy, socket) { - if (this.closing) return - - const natType = packet.message.natType - - const { clusterId, subclusterId } = packet - - let peer = this.getPeer(peerId) - - if (!peer) { - peer = new RemotePeer({ peerId }) - - if (this.peers.length >= 256) { - // TODO evicting an older peer definitely needs some more thought. - const oldPeerIndex = this.peers.findIndex(p => !p.lastUpdate && !p.indexed) - if (oldPeerIndex > -1) this.peers.splice(oldPeerIndex, 1) - } - - this.peers.push(peer) - } - - peer.connected = true - peer.lastUpdate = Date.now() - peer.port = port - peer.natType = natType - peer.address = address - if (proxy) peer.proxy = proxy - if (socket) peer.socket = socket - - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') - - if (cid) peer.clusters[cid] ??= {} - - if (cid && scid) { - const cluster = peer.clusters[cid] - cluster[scid] = { rateLimit: MAX_BANDWIDTH } - } - - if (!peer.localPeer) peer.localPeer = this - if (!this.connections) this.connections = new Map() - - debug(this.peerId, '<- CONNECTION ( ' + - `peerId=${peer.peerId.slice(0, 6)}, ` + - `address=${address}:${port}, ` + - `type=${packet.type}, ` + - `cluster=${cid.slice(0, 8)}, ` + - `sub-cluster=${scid.slice(0, 8)})` - ) - - if (this.onJoin && this.clusters[cid]) { - this.onJoin(packet, peer, port, address) - } - - if (!this.connections.has(peer)) { - this.onConnection && this.onConnection(packet, peer, port, address) - this.connections.set(peer, packet.message.cacheSummaryHash) - } - } - - /** - * Received a Sync Packet - * @return {undefined} - * @ignore - */ - async _onSync (packet, port, address) { - this.metrics.i[packet.type]++ - - this.lastSync = Date.now() - const pid = packet.packetId?.toString('hex') - - if (!isBufferLike(packet.message)) return - if (this.gate.has(pid)) return - - this.gate.set(pid, 1) - - const remote = Cache.decodeSummary(packet.message) - const local = await this.cache.summarize(remote.prefix, this.cachePredicate) - - if (!remote || !remote.hash || !local || !local.hash || local.hash === remote.hash) { - if (this.onSyncFinished) this.onSyncFinished(packet, port, address) - return - } - - if (this.onSync) this.onSync(packet, port, address, { remote, local }) - - const remoteBuckets = remote.buckets.filter(Boolean).length - debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) - - for (let i = 0; i < local.buckets.length; i++) { - // - // nothing to send/sync, expect peer to send everything they have - // - if (!local.buckets[i] && !remote.buckets[i]) continue - - // - // you dont have any of these, im going to send them to you - // - if (!remote.buckets[i]) { - for (const [key, p] of this.cache.data.entries()) { - if (!key.startsWith(local.prefix + i.toString(16))) continue - - const packet = Packet.from(p) - if (!this.cachePredicate(packet)) continue - - const pid = packet.packetId.toString('hex') - debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) - - this.send(await Packet.encode(packet), port, address) - } - } else { - // - // need more details about what exactly isn't synce'd - // - const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate) - const data = await Packet.encode(new PacketSync({ - message: Cache.encodeSummary(nextLevel) - })) - this.send(data, port, address) - } - } - } - - /** - * Received a Query Packet - * - * a -> b -> c -> (d) -> c -> b -> a - * - * @return {undefined} - * @example - * - * ```js - * peer.onQuery = (packet) => { - * // - * // read a database or something - * // - * return { - * message: Buffer.from('hello'), - * publicKey: '', - * privateKey: '' - * } - * } - * ``` - */ - async _onQuery (packet, port, address) { - this.metrics.i[packet.type]++ - - const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) - - const queryTimestamp = parseInt(Buffer.from(packet.usr1).toString(), 10) - const queryId = Buffer.from(packet.usr3).toString('hex') - const queryType = parseInt(Buffer.from(packet.usr4).toString(), 10) - - // if the timestamp in usr1 is older than now - 2s, bail - if (queryTimestamp < (Date.now() - 2048)) return - - const type = queryType === 1 ? 'question' : 'answer' - debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) - - let rinfo = { port, address } - - // - // receiving an answer - // - if (this.returnRoutes.has(queryId)) { - rinfo = this.returnRoutes.get(queryId) - - let p = packet.copy() - if (p.index > -1) p = await this.cache.compose(p) - - if (p?.index === -1) { - this.returnRoutes.delete(p.previousId.toString('hex')) - p.type = PacketPublish.type - delete p.usr3 - delete p.usr4 - if (this.onAnswer) return this.onAnswer(p.message, p, port, address) - } - - if (!rinfo.address) return - } else { - // - // receiving a query - // - this.returnRoutes.set(queryId, { address, port }) - - const query = packet.message - const packets = [] - - // - // The requestor is looking for an exact packetId. In this case, - // the peer has a packet with a previousId or nextId that points - // to a packetId they don't have. There is no need to specify the - // index in the query, split packets will have a nextId. - // - // if cache packet = { nextId: 'deadbeef...' } - // then query = { packetId: packet.nextId } - // or query = { packetId: packet.previousId } - // - if (query.packetId && this.cache.has(query.packetId)) { - const p = this.cache.get(query.packetId) - if (p) packets.push(p) - } else if (this.onQuery) { - const q = await this.onQuery(query) - if (q) packets.push(...await this._message2packets(PacketQuery, q.message, q)) - } - - if (packets.length) { - for (const p of packets) { - p.type = PacketQuery.type // convert the type during transport - p.usr3 = packet.usr3 // ensure the packet has the queryId - p.usr4 = Buffer.from(String(2)) // mark it as an answer packet - this.send(await Packet.encode(p), rinfo.port, rinfo.address) - } - return - } - } - - if (packet.hops >= this.maxHops) return - debug(this.peerId, '>> QUERY RELAY', port, address) - return await this.mcast(packet) - } - - /** - * Received a Ping Packet - * @return {undefined} - * @ignore - */ - async _onPing (packet, port, address) { - this.metrics.i[packet.type]++ - - this.lastUpdate = Date.now() - const { reflectionId, isReflection, isConnection, isHeartbeat } = packet.message - - if (packet.message.requesterPeerId === this.peerId) return - - const { probeExternalPort, isProbe, pingId } = packet.message - - if (isHeartbeat) { - // const peer = this.getPeer(packet.message.requesterPeerId) - // if (peer && natType) peer.natType = natType - return - } - - // if (peer && reflectionId) peer.reflectionId = reflectionId - if (!port) port = packet.message.port - if (!address) address = packet.message.address - - const message = { - cacheSize: this.cache.size, - uptime: this.uptime, - responderPeerId: this.peerId, - requesterPeerId: packet.message.requesterPeerId, - port, - isProbe, - address - } - - if (reflectionId) message.reflectionId = reflectionId - if (isHeartbeat) message.isHeartbeat = Date.now() - if (pingId) message.pingId = pingId - - if (isReflection) { - message.isReflection = true - message.port = port - message.address = address - } else { - message.natType = this.natType - } - - if (isConnection) { - const peerId = packet.message.requesterPeerId - this._onConnection(packet, peerId, port, address) - - message.isConnection = true - delete message.address - delete message.port - delete message.isProbe - } - - const { hash } = await this.cache.summarize('', this.cachePredicate) - message.cacheSummaryHash = hash - - const packetPong = new PacketPong({ message }) - const buf = await Packet.encode(packetPong) - - this.send(buf, port, address) - - if (probeExternalPort) { - message.port = probeExternalPort - const packetPong = new PacketPong({ message }) - const buf = await Packet.encode(packetPong) - this.send(buf, probeExternalPort, address, this.probeSocket) - } - } - - /** - * Received a Pong Packet - * @return {undefined} - * @ignore - */ - async _onPong (packet, port, address) { - this.metrics.i[packet.type]++ - - this.lastUpdate = Date.now() - - const { reflectionId, pingId, isReflection, responderPeerId } = packet.message - - debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) - const peer = this.getPeer(packet.message.responderPeerId) - - if (packet.message.isConnection) { - if (pingId) peer.pingId = pingId - this._onConnection(packet, packet.message.responderPeerId, port, address) - return - } - - if (!peer) return - - if (isReflection && !this.indexed) { - if (reflectionId !== this.reflectionId) return - - this._clearTimeout(this.reflectionTimeout) - - if (!this.reflectionFirstResponder) { - this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } - if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) - } else { - if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) - if (packet.message.address !== this.address) return - - this.nextNatType |= ( - packet.message.port === this.reflectionFirstResponder.packet.message.port - ) - ? NAT.MAPPING_ENDPOINT_INDEPENDENT - : NAT.MAPPING_ENDPOINT_DEPENDENT - - debug( - this.peerId, - `++ NAT REFLECT - STATE UPDATE (natType=${this.natType}, nextType=${this.nextNatType})`, - packet.message.port, - this.reflectionFirstResponder.packet.message.port - ) - - // wait PROBE_WAIT milliseconds for zero or more probe responses to arrive. - this._setTimeout(async () => { - // build the NAT type by combining information about the firewall with - // information about the endpoint independence - let natType = this.nextNatType - - // in the case where we recieved zero probe responses, we assume the firewall - // is of the hardest type 'FIREWALL_ALLOW_KNOWN_IP_AND_PORT'. - if (!NAT.isFirewallDefined(natType)) natType |= NAT.FIREWALL_ALLOW_KNOWN_IP_AND_PORT - - // if ((natType & NAT.MAPPING_ENDPOINT_DEPENDENT) === 1) natType = NAT.ENDPOINT_RESTRICTED - - if (NAT.isValid(natType)) { - // const oldType = this.natType - this.natType = natType - this.reflectionId = null - this.reflectionStage = 0 - - // if (natType !== oldType) { - // alert all connected peers of our new NAT type - for (const peer of this.peers) { - peer.lastRequest = Date.now() - - debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) - - await this.ping(peer, false, { - message: { - requesterPeerId: this.peerId, - natType: this.natType, - cacheSize: this.cache.size, - isConnection: true - } - }) - } - - if (this.onNat) this.onNat(this.natType) - - debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) - this.sendUnpublished() - // } - - if (this.onConnecting) this.onConnecting({ code: 3, status: `Discovered! (nat=${NAT.toString(this.natType)})` }) - if (this.onReady) this.onReady(await this.getInfo()) - } - - this.reflectionId = null - this.reflectionFirstResponder = null - }, PROBE_WAIT) - } - - this.address = packet.message.address - this.port = packet.message.port - debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) - } - } - - /** - * Received an Intro Packet - * @return {undefined} - * @ignore - */ - async _onIntro (packet, port, address, _, opts = { attempts: 0 }) { - this.metrics.i[packet.type]++ - if (this.closing) return - - const pid = packet.packetId.toString('hex') - // the packet needs to be gated, but should allow for attempt - // recursion so that the fallback can still be selected. - if (this.gate.has(pid) && opts.attempts === 0) return - this.gate.set(pid, 1) - - const ts = packet.usr1.length && Number(packet.usr1.toString()) - - if (packet.hops >= this.maxHops) return - if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return - if (packet.message.requesterPeerId === this.peerId) return // intro to myself? - if (packet.message.responderPeerId === this.peerId) return // intro from myself? - - // this is the peer that is being introduced to the new peers - const peerId = packet.message.requesterPeerId - const peerPort = packet.message.port - const peerAddress = packet.message.address - const natType = packet.message.natType - const { clusterId, subclusterId, clock } = packet - - // already introduced in the laste minute, just drop the packet - if (opts.attempts === 0 && this.gate.has(peerId + peerAddress + peerPort)) return - this.gate.set(peerId + peerAddress + peerPort, 2) - - // we already know this peer, and we're even connected to them! - let peer = this.getPeer(peerId) - if (!peer) peer = new RemotePeer({ peerId, natType, port: peerPort, address: peerAddress, clock, clusterId, subclusterId }) - if (peer.connected) return // already connected - if (clock > 0 && clock < peer.clock) return - peer.clock = clock - - // a mutex per inbound peer to ensure that it's not connecting concurrently, - // the check of the attempts ensures its allowed to recurse before failing so - // it can still fall back - if (this.gate.has('CONN' + peer.peerId) && opts.attempts === 0) return - this.gate.set('CONN' + peer.peerId, 1) - - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') - - debug(this.peerId, '<- INTRO (' + - `isRendezvous=${packet.message.isRendezvous}, ` + - `from=${address}:${port}, ` + - `to=${packet.message.address}:${packet.message.port}, ` + - `clustering=${cid.slice(0, 4)}/${scid.slice(0, 4)}` + - ')') - - if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) - - const pingId = Math.random().toString(16).slice(2) - const { hash } = await this.cache.summarize('', this.cachePredicate) - - const props = { - clusterId, - subclusterId, - message: { - natType: this.natType, - isConnection: true, - cacheSummaryHash: hash || null, - pingId: packet.message.pingId, - requesterPeerId: this.peerId - } - } - - const strategy = NAT.connectionStrategy(this.natType, packet.message.natType) - const proxyCandidate = this.peers.find(p => p.peerId === packet.message.responderPeerId) - - if (opts.attempts >= 2) { - this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) - return false - } - - this._setTimeout(() => { - if (this.getPeer(peer.peerId)) return - opts.attempts = 2 - this._onIntro(packet, port, address, _, opts) - }, 1024 * 2) - - if (packet.message.isRendezvous) { - debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) - } - - debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) - - if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { - debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) - - let i = 0 - if (!this.socketPool) { - this.socketPool = Array.from({ length: 256 }, (_, index) => { - return this.dgram().createSocket('udp4', null, this, index).unref() - }) - } - - // A probes 1 target port on B from 1024 source ports - // (this is 1.59% of the search clusterId) - // B probes 256 target ports on A from 1 source port - // (this is 0.40% of the search clusterId) - // - // Probability of successful traversal: 98.35% - // - const interval = this._setInterval(async () => { - // send messages until we receive a message from them. giveup after sending ±1024 - // packets and fall back to using the peer that sent this as the initial proxy. - if (i++ >= 1024) { - this._clearInterval(interval) - - opts.attempts++ - this._onIntro(packet, port, address, _, opts) - return false - } - - const p = { - clusterId, - subclusterId, - message: { - requesterPeerId: this.peerId, - cacheSummaryHash: hash || null, - natType: this.natType, - uptime: this.uptime, - isConnection: true, - timestamp: Date.now(), - pingId - } - } - - const data = await Packet.encode(new PacketPing(p)) - - const rand = () => Math.random() - 0.5 - const pooledSocket = this.socketPool.sort(rand).find(s => !s.active) - if (!pooledSocket) return // TODO recover from exausted socket pool - - // mark socket as active & deactivate it after timeout - pooledSocket.active = true - pooledSocket.reclaim = this._setTimeout(() => { - pooledSocket.active = false - pooledSocket.removeAllListeners() - }, 1024) - - pooledSocket.on('message', async (msg, rinfo) => { - // if (rinfo.port !== peerPort || rinfo.address !== peerAddress) return - - // cancel scheduled events - this._clearInterval(interval) - this._clearTimeout(pooledSocket.reclaim) - - // remove any events currently bound on the socket - pooledSocket.removeAllListeners() - pooledSocket.on('message', (msg, rinfo) => { - this._onMessage(msg, rinfo) - }) - - this._onConnection(packet, peer.peerId, rinfo.port, rinfo.address, undefined, pooledSocket) - - const p = { - clusterId, - subclusterId, - clock: this.clock, - message: { - requesterPeerId: this.peerId, - natType: this.natType, - isConnection: true - } - } - - const data = await Packet.encode(new PacketPing(p)) - - pooledSocket.send(data, rinfo.port, rinfo.address) - - // create a new socket to replace it in the pool - const oldIndex = this.socketPool.findIndex(s => s === pooledSocket) - this.socketPool[oldIndex] = this.dgram().createSocket('udp4', null, this).unref() - - this._onMessage(msg, rinfo) - }) - - try { - pooledSocket.send(data, peerPort, peerAddress) - } catch (err) { - console.error('STRATEGY_TRAVERSAL_CONNECT error', err) - } - }, 10) - - return - } - - if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { - // TODO could allow multiple proxies - this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) - debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') - } - - if (strategy === NAT.STRATEGY_TRAVERSAL_OPEN) { - peer.opening = Date.now() - - const portsCache = new Set() - - if (!this.bdpCache.length) { - globalThis.bdpCache = this.bdpCache = Array.from({ length: 1024 }, () => getRandomPort(portsCache)) - } - - for (const port of this.bdpCache) { - this.send(Buffer.from([0x1]), port, packet.message.address) - } - - return - } - - if (strategy === NAT.STRATEGY_DIRECT_CONNECT) { - debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') - } - - if (strategy === NAT.STRATEGY_DEFER) { - debug(this.peerId, '++ NAT STRATEGY_DEFER') - } - - this.ping(peer, true, props) - } - - /** - * Received an Join Packet - * @return {undefined} - * @ignore - */ - async _onJoin (packet, port, address, data) { - this.metrics.i[packet.type]++ - - const pid = packet.packetId.toString('hex') - if (packet.message.requesterPeerId === this.peerId) return - if (this.gate.has(pid)) return - if (packet.clusterId.length !== 32) return - - this.lastUpdate = Date.now() - - const peerId = packet.message.requesterPeerId - const rendezvousDeadline = packet.message.rendezvousDeadline - const clusterId = packet.clusterId - const subclusterId = packet.subclusterId - const peerAddress = packet.message.address - const peerPort = packet.message.port - - // prevents premature pruning; a peer is not directly connecting - const peer = this.peers.find(p => p.peerId === peerId) - if (peer) peer.lastUpdate = Date.now() - - // a rendezvous isn't relevant if it's too old, just drop the packet - if (rendezvousDeadline && rendezvousDeadline < Date.now()) return - - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') - - debug(this.peerId, '<- JOIN (' + - `peerId=${peerId.slice(0, 6)}, ` + - `clock=${packet.clock}, ` + - `hops=${packet.hops}, ` + - `clusterId=${cid}, ` + - `subclusterId=${scid}, ` + - `address=${address}:${port})` - ) - - // - // This packet represents a peer who wants to join the network and is a - // member of our cluster. The packet was replicated though the network - // and contains the details about where the peer can be reached, in this - // case we want to ping that peer so we can be introduced to them. - // - if (rendezvousDeadline && !this.indexed && this.clusters[cid]) { - if (!packet.message.rendezvousRequesterPeerId) { - const pid = packet.packetId.toString('hex') - this.gate.set(pid, 2) - - // TODO it would tighten up the transition time between dropped peers - // if we check strategy from (packet.message.natType, this.natType) and - // make introductions that create more mutually known peers. - debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) - - const data = await Packet.encode(new PacketJoin({ - clock: packet.clock, - subclusterId: packet.subclusterId, - clusterId: packet.clusterId, - message: { - requesterPeerId: this.peerId, - natType: this.natType, - address: this.address, - port: this.port, - rendezvousType: packet.message.natType, - rendezvousRequesterPeerId: packet.message.requesterPeerId - } - })) - - this.send( - data, - packet.message.rendezvousPort, - packet.message.rendezvousAddress - ) - } - } - - const filter = p => ( - p.connected && // you can't intro peers who aren't connected - p.peerId !== packet.message.requesterPeerId && - p.peerId !== packet.message.rendezvousRequesterPeerId && - !p.indexed - ) - - let peers = this.getPeers(packet, this.peers, [{ port, address }], filter) - - // - // A peer who belongs to the same cluster as the peer who's replicated - // join was discovered, sent us a join that has a specification for who - // they want to be introduced to. - // - if (packet.message.rendezvousRequesterPeerId && this.peerId === packet.message.rendezvousPeerId) { - const peer = this.peers.find(p => p.peerId === packet.message.rendezvousRequesterPeerId) - - if (!peer) { - debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) - return - } - - // peer.natType = packet.message.rendezvousType - peers = [peer] - - debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) - } - - for (const peer of peers) { - const message1 = { - requesterPeerId: peer.peerId, - responderPeerId: this.peerId, - isRendezvous: !!packet.message.rendezvousPeerId, - natType: peer.natType, - address: peer.address, - port: peer.port - } - - const message2 = { - requesterPeerId: packet.message.requesterPeerId, - responderPeerId: this.peerId, - isRendezvous: !!packet.message.rendezvousPeerId, - natType: packet.message.natType, - address: packet.message.address, - port: packet.message.port - } - - const opts = { - hops: packet.hops + 1, - clusterId, - subclusterId, - usr1: String(Date.now()) - } - - const intro1 = await Packet.encode(new PacketIntro({ ...opts, message: message1 })) - const intro2 = await Packet.encode(new PacketIntro({ ...opts, message: message2 })) - - // - // Send intro1 to the peer described in the message - // Send intro2 to the peer in this loop - // - debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) - debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) - - peer.lastRequest = Date.now() - - this.send(intro2, peer.port, peer.address) - this.send(intro1, packet.message.port, packet.message.address) - - this.gate.set(Packet.decode(intro1).packetId.toString('hex'), 2) - this.gate.set(Packet.decode(intro2).packetId.toString('hex'), 2) - } - - this.gate.set(packet.packetId.toString('hex'), 2) - - if (packet.hops >= this.maxHops) return - if (this.indexed && !packet.clusterId) return - - if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED && !packet.message.rendezvousDeadline) { - packet.message.rendezvousAddress = this.address - packet.message.rendezvousPort = this.port - packet.message.rendezvousType = this.natType - packet.message.rendezvousPeerId = this.peerId - packet.message.rendezvousDeadline = Date.now() + this.config.keepalive - } - - debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) - this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) - - if (packet.hops <= 1) { - this._onConnection(packet, packet.message.requesterPeerId, port, address) - } - } - - /** - * Received an Publish Packet - * @return {undefined} - * @ignore - */ - async _onPublish (packet, port, address, data) { - this.metrics.i[packet.type]++ - - // only cache if this packet if i am part of this subclusterId - // const cluster = this.clusters[packet.clusterId] - // if (cluster && cluster[packet.subclusterId]) { - - const pid = packet.packetId.toString('hex') - if (this.cache.has(pid)) { - debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) - return - } - - debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) - this.cacheInsert(packet) - - const ignorelist = [{ address, port }] - const scid = packet.subclusterId.toString('base64') - - if (!this.indexed && this.encryption.has(scid)) { - let p = packet.copy() - if (p.index > -1) p = await this.cache.compose(p) - if (p?.index === -1 && this.onPacket) this.onPacket(p, port, address) - } - - if (packet.hops >= this.maxHops) return - this.mcast(packet, ignorelist) - - // } - } - - /** - * Received an Stream Packet - * @return {undefined} - * @ignore - */ - async _onStream (packet, port, address, data) { - this.metrics.i[packet.type]++ - - const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) - - const streamTo = packet.usr3.toString('hex') - const streamFrom = packet.usr4.toString('hex') - - // only help packets with a higher hop count if they are in our cluster - // if (packet.hops > 2 && !this.clusters[packet.cluster]) return - - const peerFrom = this.peers.find(p => p.peerId.toString('hex') === streamFrom.toString('hex')) - if (!peerFrom) return - - // stream message is for this peer - if (streamTo.toString('hex') === this.peerId.toString('hex')) { - const scid = packet.subclusterId.toString('base64') - - if (this.encryption.has(scid)) { - let p = packet.copy() // clone the packet so it's not modified - - if (packet.index > -1) { // if it needs to be composed... - p.timestamp = Date.now() - this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial - - p = await this.cache.compose(p, this.streamBuffer) // try to compose - if (!p) return // could not compose - - if (p) { // if successful, delete the artifacts - const previousId = p.index === 0 ? p.packetId : p.previousId - const pid = previousId.toString('hex') - - this.streamBuffer.forEach((v, k) => { - if (k === pid) this.streamBuffer.delete(k) - if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) - }) - } - } - - if (this.onStream) this.onStream(p, peerFrom, port, address) - } - - return - } - - // stream message is for another peer - const peerTo = this.peers.find(p => p.peerId === streamTo) - if (!peerTo) { - debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) - return - } - - if (packet.hops >= this.maxHops) { - debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) - return - } - - debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) - this.send(await Packet.encode(packet), peerTo.port, peerTo.address) - if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) - } - - /** - * Received any packet on the probe port to determine the firewall: - * are you port restricted, host restricted, or unrestricted. - * @return {undefined} - * @ignore - */ - _onProbeMessage (data, { port, address }) { - this._clearTimeout(this.probeReflectionTimeout) - - const packet = Packet.decode(data) - if (!packet || packet.version !== VERSION) return - if (packet?.type !== 2) return - - const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) - - const { reflectionId } = packet.message - debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) - - if (this.onProbe) this.onProbe(data, port, address) - if (this.reflectionId !== reflectionId || !this.reflectionId) return - - // reflection stage is encoded in the last hex char of the reflectionId, or 0 if not available. - // const reflectionStage = reflectionId ? parseInt(reflectionId.slice(-1), 16) : 0 - - if (this.reflectionStage === 1) { - debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) - if (!packet.message?.port) return // message must include a port number - - // successfully discovered the probe socket external port - this.config.probeExternalPort = packet.message.port - - // move to next reflection stage - this.reflectionStage = 1 - this.reflectionId = null - this.requestReflection() - return - } - - if (this.reflectionStage === 2) { - debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) - - // if we have previously sent an outbount message to this peer on the probe port - // then our NAT will have a mapping for their IP, but not their IP+Port. - if (!NAT.isFirewallDefined(this.nextNatType)) { - this.nextNatType |= NAT.FIREWALL_ALLOW_KNOWN_IP - debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) - } else { - this.nextNatType |= NAT.FIREWALL_ALLOW_ANY - debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) - } - - // wait for all messages to arrive - } - } - - /** - * When a packet is received it is decoded, the packet contains the type - * of the message. Based on the message type it is routed to a function. - * like WebSockets, don't answer queries unless we know its another SRP peer. - * - * @param {Buffer|Uint8Array} data - * @param {{ port: number, address: string }} info - */ - async _onMessage (data, { port, address }) { - const packet = Packet.decode(data) - if (!packet || packet.version !== VERSION) return - - const peer = this.peers.find(p => p.address === address && p.port === port) - if (peer) peer.lastUpdate = Date.now() - - const cid = packet.clusterId.toString('base64') - const scid = packet.subclusterId.toString('base64') - - // debug('<- PACKET', packet.type, port, address) - const clusters = this.clusters[cid] - const subcluster = clusters && clusters[scid] - - if (!this.config.limitExempt) { - if (rateLimit(this.rates, packet.type, port, address, subcluster)) { - debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) - this.metrics.i.REJECTED++ - return - } - if (this.onLimit && !this.onLimit(packet, port, address)) return - } - - const args = [packet, port, address, data] - - if (this.firewall) if (!this.firewall(...args)) return - if (this.onData) this.onData(...args) - - switch (packet.type) { - case PacketPing.type: return this._onPing(...args) - case PacketPong.type: return this._onPong(...args) - } - - if (!this.natType && !this.indexed) return - - switch (packet.type) { - case PacketIntro.type: return this._onIntro(...args) - case PacketJoin.type: return this._onJoin(...args) - case PacketPublish.type: return this._onPublish(...args) - case PacketStream.type: return this._onStream(...args) - case PacketSync.type: return this._onSync(...args) - case PacketQuery.type: return this._onQuery(...args) - } - } -} - -export default Peer diff --git a/api/stream-relay/nat.js b/api/stream-relay/nat.js deleted file mode 100644 index 327ecc377b..0000000000 --- a/api/stream-relay/nat.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * The NAT type is encoded using 5 bits: - * - * 0b00001 : the lsb indicates if endpoint dependence information is included - * 0b00010 : the second bit indicates the endpoint dependence value - * - * 0b00100 : the third bit indicates if firewall information is included - * 0b01000 : the fourth bit describes which requests can pass the firewall, only known IPs (0) or any IP (1) - * 0b10000 : the fifth bit describes which requests can pass the firewall, only known ports (0) or any port (1) - */ - -/** - * Every remote will see the same IP:PORT mapping for this peer. - * - * :3333 ┌──────┐ - * :1111 ┌───▶ │ R1 │ - * ┌──────┐ ┌───────┐ │ └──────┘ - * │ P1 ├───▶│ NAT ├──┤ - * └──────┘ └───────┘ │ ┌──────┐ - * └───▶ │ R2 │ - * :3333 └──────┘ - */ -export const MAPPING_ENDPOINT_INDEPENDENT = 0b00011 - -/** - * Every remote will see a different IP:PORT mapping for this peer. - * - * :4444 ┌──────┐ - * :1111 ┌───▶ │ R1 │ - * ┌──────┐ ┌───────┐ │ └──────┘ - * │ P1 ├───▶│ NAT ├──┤ - * └──────┘ └───────┘ │ ┌──────┐ - * └───▶ │ R2 │ - * :5555 └──────┘ - */ -export const MAPPING_ENDPOINT_DEPENDENT = 0b00001 - -/** - * The firewall allows the port mapping to be accessed by: - * - Any IP:PORT combination (FIREWALL_ALLOW_ANY) - * - Any PORT on a previously connected IP (FIREWALL_ALLOW_KNOWN_IP) - * - Only from previously connected IP:PORT combinations (FIREWALL_ALLOW_KNOWN_IP_AND_PORT) - */ -export const FIREWALL_ALLOW_ANY = 0b11100 -export const FIREWALL_ALLOW_KNOWN_IP = 0b01100 -export const FIREWALL_ALLOW_KNOWN_IP_AND_PORT = 0b00100 - -/** - * The initial state of the nat is unknown and its value is 0 - */ -export const UNKNOWN = 0 - -/** - * Full-cone NAT, also known as one-to-one NAT - * - * Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. - * - * @summary its a packet party at this mapping and everyone's invited - */ -export const UNRESTRICTED = (FIREWALL_ALLOW_ANY | MAPPING_ENDPOINT_INDEPENDENT) - -/** - * (Address)-restricted-cone NAT - * - * An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only - * if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter. - * - * @summary The NAT will drop your packets unless a peer within its network has previously messaged you from *any* port. - */ -export const ADDR_RESTRICTED = (FIREWALL_ALLOW_KNOWN_IP | MAPPING_ENDPOINT_INDEPENDENT) - -/** - * Port-restricted cone NAT - * - * An external host (hAddr:hPort) can send packets to iAddr:iPort by sending - * packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to - * hAddr:hPort. - * - * @summary The NAT will drop your packets unless a peer within its network - * has previously messaged you from this *specific* port. - */ -export const PORT_RESTRICTED = (FIREWALL_ALLOW_KNOWN_IP_AND_PORT | MAPPING_ENDPOINT_INDEPENDENT) - -/** - * Symmetric NAT - * - * Only an external host that receives a packet from an internal host can send - * a packet back. - * - * @summary The NAT will only accept replies to a correspondence initialized - * by itself, the mapping it created is only valid for you. - */ -export const ENDPOINT_RESTRICTED = (FIREWALL_ALLOW_KNOWN_IP_AND_PORT | MAPPING_ENDPOINT_DEPENDENT) - -/** - * Returns true iff a valid MAPPING_ENDPOINT_* flag has been added (indicated by the lsb). - */ -export const isEndpointDependenceDefined = nat => (nat & 0b00001) === 0b00001 - -/** - * Returns true iff a valid FIREWALL_ALLOW_* flag has been added (indicated by the third bit). - */ -export const isFirewallDefined = nat => (nat & 0b00100) === 0b00100 - -/** - * Returns true iff both FIREWALL_ALLOW_* and MAPPING_ENDPOINT_* flags have been added. - */ -export const isValid = nat => isEndpointDependenceDefined(nat) && isFirewallDefined(nat) - -/** - * toString returns a human-readable label for the NAT enum - */ -export const toString = n => { - switch (n) { - case UNRESTRICTED: return 'UNRESTRICTED' - case ADDR_RESTRICTED: return 'ADDR_RESTRICTED' - case PORT_RESTRICTED: return 'PORT_RESTRICTED' - case ENDPOINT_RESTRICTED: return 'ENDPOINT_RESTRICTED' - default: return 'UNKNOWN' - } -} - -/** - * toStringStrategy returns a human-readable label for the STRATEGY enum - */ -export const toStringStrategy = n => { - switch (n) { - case STRATEGY_DEFER: return 'STRATEGY_DEFER' - case STRATEGY_DIRECT_CONNECT: return 'STRATEGY_DIRECT_CONNECT' - case STRATEGY_TRAVERSAL_OPEN: return 'STRATEGY_TRAVERSAL_OPEN' - case STRATEGY_TRAVERSAL_CONNECT: return 'STRATEGY_TRAVERSAL_CONNECT' - case STRATEGY_PROXY: return 'STRATEGY_PROXY' - default: return 'STRATEGY_UNKNOWN' - } -} - -export const STRATEGY_DEFER = 0 // do nothing and let the other side initialize the connection -export const STRATEGY_DIRECT_CONNECT = 1 // simply connect -export const STRATEGY_TRAVERSAL_OPEN = 2 // spam random ports and listen for replies -export const STRATEGY_TRAVERSAL_CONNECT = 3 // spam random ports but dont bother listening -export const STRATEGY_PROXY = 4 // use a third-party proxy - -/** - * ConnectionStrategy returns the best strategy to use when attempting to connect from a->b. - */ -export const connectionStrategy = (a, b) => { - switch (b) { - // b always accepts connections - case UNRESTRICTED: return STRATEGY_DIRECT_CONNECT - - // b only accepts connections from an IP it has previously communicated with - case ADDR_RESTRICTED: { - switch (a) { - case UNRESTRICTED: return STRATEGY_DEFER - case ADDR_RESTRICTED: return STRATEGY_DIRECT_CONNECT // both sides attempt, one will succeed - case PORT_RESTRICTED: return STRATEGY_DIRECT_CONNECT // a is hinting, b is guessing - case ENDPOINT_RESTRICTED: return STRATEGY_DIRECT_CONNECT // a is hinting, b is guessing - } - break - } - - // b only accepts connections from an IP:PORT it has previously communicated with - case PORT_RESTRICTED: { - switch (a) { - case UNRESTRICTED: return STRATEGY_DEFER - case ADDR_RESTRICTED: return STRATEGY_DIRECT_CONNECT // a is guessing, b is hinting - case PORT_RESTRICTED: return STRATEGY_DIRECT_CONNECT // both guess, will take too long, most resign to proxying - case ENDPOINT_RESTRICTED: return STRATEGY_TRAVERSAL_CONNECT // try connecting - } - break - } - - // b only accepts replies to its own messages - case ENDPOINT_RESTRICTED: { - switch (a) { - case UNRESTRICTED: return STRATEGY_DEFER - case ADDR_RESTRICTED: return STRATEGY_DIRECT_CONNECT // the 3 successive packets will penetrate - case PORT_RESTRICTED: return STRATEGY_TRAVERSAL_OPEN // open up some ports - case ENDPOINT_RESTRICTED: return STRATEGY_PROXY // unroutable - } - } - } - - return STRATEGY_PROXY -} diff --git a/api/stream-relay/packets.js b/api/stream-relay/packets.js deleted file mode 100644 index c46ac43b50..0000000000 --- a/api/stream-relay/packets.js +++ /dev/null @@ -1,496 +0,0 @@ -import { randomBytes } from '../crypto.js' -import { isBufferLike } from '../util.js' -import { Buffer } from '../buffer.js' -import { debug } from './index.js' - -/** - * Hash function factory. - * @return {function(object|Buffer|string): Promise} - */ -function createHashFunction () { - const encoder = new TextEncoder() - let crypto = null - - if (!globalThis.process?.versions?.node) { - if (!crypto) crypto = globalThis.crypto?.subtle - - return async (seed, opts = {}) => { - const encoding = opts.encoding || 'hex' - const bytes = opts.bytes - - if (typeof seed === 'object' && !isBufferLike(seed)) { - seed = JSON.stringify(seed) - } - - if (seed && typeof seed === 'string') { - seed = encoder.encode(seed) - } - - let value - - if (seed) { - value = Buffer.from(await crypto.digest('SHA-256', seed)) - if (bytes) return value - return value.toString(encoding) - } - - value = Buffer.from(randomBytes(32)) - if (opts.bytes) return value - return value.toString(encoding) - } - } - - return async (seed, opts = {}) => { - const encoding = opts.encoding || 'hex' - const bytes = opts.bytes - - if (typeof seed === 'object' && !isBufferLike(seed)) { - seed = JSON.stringify(seed) - } - - if (!crypto) { // eslint-disable-next-line - crypto = await new Function('return import(\'crypto\')')() - } - - let value - - if (seed) { - value = crypto.createHash('sha256').update(seed) - if (bytes) return Buffer.from(value.digest(encoding), encoding) - return value.digest(encoding) - } - - value = randomBytes(32) - if (opts.bytes) return value - return value.toString(encoding) - } -} - -const getMethod = (type, bytes, isSigned) => { - const bits = bytes << 3 - const isBigEndian = bits === 8 ? '' : 'BE' - - if (![8, 16, 32].includes(bits)) { - throw new Error(`${bits} is invalid, expected 8, 16, or 32`) - } - - return `${type}${isSigned ? '' : 'U'}Int${bits}${isBigEndian}` -} - -/** - * The magic bytes prefixing every packet. They are the - * 2nd, 3rd, 5th, and 7th, prime numbers. - * @type {number[]} - */ -export const MAGIC_BYTES_PREFIX = [0x03, 0x05, 0x0b, 0x11] - -/** - * The version of the protocol. - */ -export const VERSION = 6 - -/** - * The size in bytes of the prefix magic bytes. - */ -export const MAGIC_BYTES = 4 - -/** - * The maximum size of the user message. - */ -export const MESSAGE_BYTES = 1024 - -/** - * The cache TTL in milliseconds. - */ -export const CACHE_TTL = 60_000 * 60 * 6 - -export const PACKET_SPEC = { - type: { bytes: 1, encoding: 'number' }, - version: { bytes: 2, encoding: 'number', default: VERSION }, - clock: { bytes: 4, encoding: 'number', default: 1 }, - hops: { bytes: 4, encoding: 'number', default: 0 }, - index: { bytes: 4, encoding: 'number', default: -1, signed: true }, - ttl: { bytes: 4, encoding: 'number', default: CACHE_TTL }, - clusterId: { bytes: 32, encoding: 'base64', default: [0b0] }, - subclusterId: { bytes: 32, encoding: 'base64', default: [0b0] }, - previousId: { bytes: 32, encoding: 'hex', default: [0b0] }, - packetId: { bytes: 32, encoding: 'hex', default: [0b0] }, - nextId: { bytes: 32, encoding: 'hex', default: [0b0] }, - usr1: { bytes: 32, default: [0b0] }, - usr2: { bytes: 32, default: [0b0] }, - usr3: { bytes: 32, default: [0b0] }, - usr4: { bytes: 32, default: [0b0] }, - message: { bytes: 1024, default: [0b0] }, - sig: { bytes: 64, default: [0b0] } -} - -let FRAME_BYTES = MAGIC_BYTES - -for (const spec of Object.values(PACKET_SPEC)) { - FRAME_BYTES += spec.bytes -} - -/** - * The size in bytes of the total packet frame and message. - */ -export const PACKET_BYTES = FRAME_BYTES + MESSAGE_BYTES - -/** - * The maximum distance that a packet can be replicated. - */ -export const MAX_HOPS = 16 - -/** - * @param {object} message - * @param {{ [key: string]: { required: boolean, type: string }}} constraints - */ -export const validatePacket = (o, constraints) => { - if (!o) throw new Error('Expected object') - const allowedKeys = Object.keys(constraints) - const actualKeys = Object.keys(o) - const unknown = actualKeys.filter(k => allowedKeys.indexOf(k) === -1) - if (unknown.length) throw new Error(`Unexpected keys [${unknown}]`) - - for (const [key, con] of Object.entries(constraints)) { - if (con.required && !o[key]) { - debug(new Error(`${key} is required (${JSON.stringify(o, null, 2)})`), JSON.stringify(o)) - } - - const type = ({}).toString.call(o[key]).slice(8, -1).toLowerCase() - - if (o[key] && type !== con.type) { - debug(`expected .${key} to be of type ${con.type}, got ${type} in packet.. ` + JSON.stringify(o)) - } - } -} - -/** - * Used to store the size of each field - */ -const SIZE = 2 - -const isEncodedAsJSON = ({ type, index }) => ( - type === PacketPing.type || - type === PacketPong.type || - type === PacketJoin.type || - type === PacketIntro.type || - type === PacketQuery.type || - index === 0 -) - -/** - * Computes a SHA-256 hash of input returning a hex encoded string. - * @type {function(string|Buffer|Uint8Array): Promise} - */ -export const sha256 = createHashFunction() - -/** - * Decodes `buf` into a `Packet`. - * @param {Buffer} buf - * @return {Packet} - */ -export const decode = buf => { - if (!Packet.isPacket(buf)) return null - - buf = buf.slice(MAGIC_BYTES) - - const o = new Packet() - let offset = 0 - - for (const [k, spec] of Object.entries(PACKET_SPEC)) { - o[k] = spec.default - - try { - if (spec.encoding === 'number') { - const method = getMethod('read', spec.bytes, spec.signed) - o[k] = buf[method](offset) - offset += spec.bytes - continue - } - - const size = buf.readUInt16BE(offset) - offset += SIZE - let value = buf.slice(offset, offset + size) - offset += size - - if (spec.bytes && size > spec.bytes) return null - - if (spec.encoding === 'hex') value = Buffer.from(value, 'hex') - if (spec.encoding === 'base64') value = Buffer.from(value, 'base64') - if (spec.encoding === 'utf8') value = value.toString() - - if (k === 'message' && isEncodedAsJSON(o)) { - try { value = JSON.parse(value.toString()) } catch {} - } - - o[k] = value - } catch (err) { - return null // completely bail - } - } - - return o -} - -export const getTypeFromBytes = (buf) => buf.byteLength > 4 ? buf.readUInt8(4) : 0 - -export class Packet { - static ttl = CACHE_TTL - static maxLength = MESSAGE_BYTES - - /** - * Returns an empty `Packet` instance. - * @return {Packet} - */ - static empty () { - return new this() - } - - /** - * @param {Packet|object} packet - * @return {Packet} - */ - static from (packet) { - if (packet instanceof Packet) return packet - return new Packet(packet) - } - - /** - * @param {Packet} packet - * @return {Packet} - */ - copy () { - return Object.assign(new Packet({}), this) - } - - /** - * Determines if input is a packet. - * @param {Buffer|Uint8Array|number[]|object|Packet} packet - * @return {boolean} - */ - static isPacket (packet) { - if (isBufferLike(packet) || Array.isArray(packet)) { - const prefix = Buffer.from(packet).slice(0, MAGIC_BYTES) - const magic = Buffer.from(MAGIC_BYTES_PREFIX) - return magic.compare(prefix) === 0 - } else if (packet && typeof packet === 'object') { - // check if every key on `Packet` exists in `packet` - return Object.keys(PACKET_SPEC).every(k => k in packet) - } - - return false - } - - /** - * `Packet` class constructor. - * @param {Packet|object?} options - */ - constructor (options = {}) { - for (const [k, v] of Object.entries(PACKET_SPEC)) { - this[k] = typeof options[k] === 'undefined' - ? v.default - : options[k] - - if (Array.isArray(this[k]) || ArrayBuffer.isView(this[k])) { - this[k] = Buffer.from(this[k]) - } - } - - // extras that might not come over the wire - this.timestamp = options.timestamp || Date.now() - this.isComposed = options.isComposed || false - this.isReconciled = options.isReconciled || false - this.meta = options.meta || {} - } - - /** - */ - static async encode (p) { - p = { ...p } - - const buf = Buffer.alloc(PACKET_BYTES) // buf length bust be < UDP MTU (usually ~1500) - if (!p.message) return buf - - const isBuffer = isBufferLike(p.message) - const isObject = typeof p.message === 'object' - - if (p.index <= 0 && isObject && !isBuffer) { - p.message = JSON.stringify(p.message) - } else if (p.index <= 0 && typeof p !== 'string' && !isBuffer) { - p.message = String(p.message) - } - - if (p.message?.length > Packet.MESSAGE_BYTES) throw new Error('ETOOBIG') - - // we only have p.nextId when we know ahead of time, if it's empty that's fine. - if (p.packetId.length === 1 && p.packetId[0] === 0) { - const bufs = [p.previousId, p.message, p.nextId].map(v => Buffer.from(v)) - p.packetId = await sha256(Buffer.concat(bufs), { bytes: true }) - } - - if (p.clock === 2e9) p.clock = 0 - - const bufs = [Buffer.from(MAGIC_BYTES_PREFIX)] - - // an encoded packet has a fixed number of fields each with variable length - // the order of bufs will be consistent regardless of the field order in p - for (const [k, spec] of Object.entries(PACKET_SPEC)) { - // if p[k] is larger than specified in the spec, throw - - const value = p[k] || spec.default - - if (spec.encoding === 'number') { - const buf = Buffer.alloc(spec.bytes) - const value = typeof p[k] !== 'undefined' ? p[k] : spec.default - const bytesRequired = (32 - Math.clz32(value) >> 3) || 1 - - if (bytesRequired > spec.bytes) { - throw new Error(`key=${k}, value=${value} bytes=${bytesRequired}, max-bytes=${spec.bytes}, encoding=${spec.encoding}`) - } - - const method = getMethod('write', spec.bytes, spec.signed) - buf[method](value) - bufs.push(buf) - continue - } - - const encoded = Buffer.from(value || spec.default, spec.encoding) - - if (value?.length && encoded.length > spec.bytes) { - throw new Error(`${k} is invalid, ${value.length} is greater than ${spec.bytes} (encoding=${spec.encoding})`) - } - - // create a buffer from the size of the field and the actual value of p[k] - const bytes = value.length - const buf = Buffer.alloc(SIZE + bytes) - const offset = buf.writeUInt16BE(bytes) - encoded.copy(buf, offset) - bufs.push(buf) - } - - return Buffer.concat(bufs, FRAME_BYTES) - } - - static decode (buf) { - return decode(buf) - } -} - -export class PacketPing extends Packet { - static type = 1 - constructor ({ message, clusterId, subclusterId }) { - super({ type: PacketPing.type, message, clusterId, subclusterId }) - - validatePacket(message, { - requesterPeerId: { required: true, type: 'string' }, - cacheSummaryHash: { type: 'string' }, - probeExternalPort: { type: 'number' }, - reflectionId: { type: 'string' }, - pingId: { type: 'string' }, - natType: { type: 'number' }, - uptime: { type: 'number' }, - isHeartbeat: { type: 'number' }, - cacheSize: { type: 'number' }, - isConnection: { type: 'boolean' }, - isReflection: { type: 'boolean' }, - isProbe: { type: 'boolean' }, - isDebug: { type: 'boolean' }, - timestamp: { type: 'number' } - }) - } -} - -export class PacketPong extends Packet { - static type = 2 - constructor ({ message, clusterId, subclusterId }) { - super({ type: PacketPong.type, message, clusterId, subclusterId }) - - validatePacket(message, { - requesterPeerId: { required: true, type: 'string' }, - responderPeerId: { required: true, type: 'string' }, - cacheSummaryHash: { type: 'string' }, - port: { type: 'number' }, - address: { type: 'string' }, - uptime: { type: 'number' }, - cacheSize: { type: 'number' }, - natType: { type: 'number' }, - isReflection: { type: 'boolean' }, - isHeartbeat: { type: 'number' }, - isConnection: { type: 'boolean' }, - reflectionId: { type: 'string' }, - pingId: { type: 'string' }, - isDebug: { type: 'boolean' }, - isProbe: { type: 'boolean' }, - rejected: { type: 'boolean' } - }) - } -} - -export class PacketIntro extends Packet { - static type = 3 - constructor ({ clock, hops, clusterId, subclusterId, usr1, message }) { - super({ type: PacketIntro.type, clock, hops, clusterId, subclusterId, usr1, message }) - - validatePacket(message, { - requesterPeerId: { required: true, type: 'string' }, - responderPeerId: { required: true, type: 'string' }, - isRendezvous: { type: 'boolean' }, - natType: { required: true, type: 'number' }, - address: { required: true, type: 'string' }, - port: { required: true, type: 'number' }, - timestamp: { type: 'number' } - }) - } -} - -export class PacketJoin extends Packet { - static type = 4 - constructor ({ clock, hops, clusterId, subclusterId, message }) { - super({ type: PacketJoin.type, clock, hops, clusterId, subclusterId, message }) - - validatePacket(message, { - rendezvousAddress: { type: 'string' }, - rendezvousPort: { type: 'number' }, - rendezvousType: { type: 'number' }, - rendezvousPeerId: { type: 'string' }, - rendezvousDeadline: { type: 'number' }, - rendezvousRequesterPeerId: { type: 'string' }, - requesterPeerId: { required: true, type: 'string' }, - natType: { required: true, type: 'number' }, - initial: { type: 'boolean' }, - address: { required: true, type: 'string' }, - port: { required: true, type: 'number' }, - isConnection: { type: 'boolean' } - }) - } -} - -export class PacketPublish extends Packet { - static type = 5 // no need to validatePacket, message is whatever you want - constructor ({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }) { - super({ type: PacketPublish.type, message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }) - } -} - -export class PacketStream extends Packet { - static type = 6 - constructor ({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }) { - super({ type: PacketStream.type, message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }) - } -} - -export class PacketSync extends Packet { - static type = 7 - constructor ({ packetId, message = Buffer.from([0b0]) }) { - super({ type: PacketSync.type, packetId, message }) - } -} - -export class PacketQuery extends Packet { - static type = 8 - constructor ({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message = {} }) { - super({ type: PacketQuery.type, packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }) - } -} - -export default Packet diff --git a/api/stream-relay/proxy.js b/api/stream-relay/proxy.js deleted file mode 100644 index dc6850e9c2..0000000000 --- a/api/stream-relay/proxy.js +++ /dev/null @@ -1,281 +0,0 @@ -/** - * A utility to run run the protocol in a thread seperate from the UI. - * - * import { network } from - * - * Socket Node - * --- --- - * API API - * Proxy Protocol - * Protocol - * - */ -import { Deferred } from '../async.js' -import path from '../path.js' -const { pathname } = new URL(import.meta.url) - -function deepClone (object, map = new Map()) { - if (map.has(object)) return map.get(object) - - const isNull = object === null - const isNotObject = typeof object !== 'object' - const isArrayBuffer = object instanceof ArrayBuffer - const isArray = Array.isArray(object) - const isUint8Array = object instanceof Uint8Array - const isMessagePort = object instanceof MessagePort - - if (isMessagePort || isNotObject || isNull || isArrayBuffer) return object - if (isUint8Array) return new Uint8Array(object) - if (isArrayBuffer) return object.slice(0) - - if (isArray) { - const clonedArray = [] - map.set(object, clonedArray) - for (const item of object) { - clonedArray.push(deepClone(item, map)) - } - return clonedArray - } - - const clonedObj = {} - map.set(object, clonedObj) - for (const key in object) { - clonedObj[key] = deepClone(object[key], map) - } - - return clonedObj -} - -function transferOwnership (...objects) { - const transfers = [] - - function add (value) { - if (!transfers.includes(value)) { - transfers.push(value) - } - } - - objects.forEach(object => { - if (object instanceof ArrayBuffer || ArrayBuffer.isView(object)) { - add(object.buffer) - } else if (Array.isArray(object) || (object && typeof object === 'object')) { - for (const value of Object.values(object)) { - if (value instanceof MessagePort) add(value) - } - } - }) - - return transfers -} - -/** - * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. - * @param {{ createSocket: function('udp4', null, object?): object }} options - */ -class PeerWorkerProxy { - #promises = new Map() - #channel = null - #worker = null - #index = 0 - #port = null - - constructor (options, port, fn) { - if (!options.isWorkerThread) { - this.#channel = new MessageChannel() - this.#worker = new window.Worker(path.join(path.dirname(pathname), 'worker.js')) - - this.#worker.addEventListener('error', err => { - throw err - }) - - this.#worker.postMessage({ - port: this.#channel.port2 - }, [this.#channel.port2]) - - // when the main thread receives a message from the worker - this.#channel.port1.onmessage = ({ data: args }) => { - const { - err, - prop, - data, - seq - } = args - - if (!prop && err) { - throw new Error(err) - } - - if (prop && typeof this[prop] === 'function') { - try { - if (Array.isArray(data)) { - this[prop](...data) - } else { - this[prop](data) - } - } catch (err) { - throw new Error(err) - } - return - } - - const p = this.#promises.get(seq) - if (!p) return - - if (!p) { - console.warn(`No promise was found for the sequence (${seq})`) - return - } - - if (err) { - p.reject(err) - } else { - p.resolve(data) - } - - this.#promises.delete(seq) - } - - this.callWorkerThread('create', options) - return - } - - this.#port = port - this.#port.onmessage = fn.bind(this) - } - - async init () { - return await this.callWorkerThread('init') - } - - async reconnect () { - return await this.callWorkerThread('reconnect') - } - - async disconnect () { - return await this.callWorkerThread('disconnect') - } - - async getInfo () { - return await this.callWorkerThread('getInfo') - } - - async getState () { - return await this.callWorkerThread('getState') - } - - async open (...args) { - return await this.callWorkerThread('open', args) - } - - async seal (...args) { - return await this.callWorkerThread('seal', args) - } - - async sealUnsigned (...args) { - return await this.callWorkerThread('sealUnsigned', args) - } - - async openUnsigned (...args) { - return await this.callWorkerThread('openUnsigned', args) - } - - async addEncryptionKey (...args) { - return await this.callWorkerThread('addEncryptionKey', args) - } - - async send (...args) { - return await this.callWorkerThread('send', args) - } - - async sendUnpublished (...args) { - return await this.callWorkerThread('sendUnpublished', args) - } - - async cacheInsert (...args) { - return await this.callWorkerThread('cacheInsert', args) - } - - async mcast (...args) { - return await this.callWorkerThread('mcast', args) - } - - async requestReflection (...args) { - return await this.callWorkerThread('requestReflection', args) - } - - async join (...args) { - return await this.callWorkerThread('join', args) - } - - async publish (...args) { - return await this.callWorkerThread('publish', args) - } - - async sync (...args) { - return await this.callWorkerThread('sync', args) - } - - async close (...args) { - return await this.callWorkerThread('close', args) - } - - async query (...args) { - return await this.callWorkerThread('query', args) - } - - async compileCachePredicate (src) { - return await this.callWorkerThread('compileCachePredicate', src) - } - - callWorkerThread (prop, data) { - let transfer = [] - - if (data) { - data = deepClone(data) - transfer = transferOwnership(data) - } - - const seq = ++this.#index - const d = new Deferred() - - this.#channel.port1.postMessage( - { prop, data, seq }, - { transfer } - ) - - this.#promises.set(seq, d) - return d - } - - callMainThread (prop, args) { - for (const i in args) { - const arg = args[i] - if (!arg?.peerId) continue - args[i] = { ...arg } - delete args[i].localPeer // don't copy this over - } - - try { - this.#port.postMessage( - { data: deepClone(args), prop }, - { transfer: transferOwnership(args) } - ) - } catch (err) { - this.#port.postMessage({ data: { err: err.message, prop } }) - } - } - - resolveMainThread (seq, data) { - try { - this.#port.postMessage( - { data: deepClone(data), seq }, - { transfer: transferOwnership(data) } - ) - } catch (err) { - this.#port.postMessage({ data: { err: err.message } }) - } - } -} - -export { PeerWorkerProxy } -export default PeerWorkerProxy diff --git a/api/stream-relay/worker.js b/api/stream-relay/worker.js deleted file mode 100644 index c4b1845b7e..0000000000 --- a/api/stream-relay/worker.js +++ /dev/null @@ -1,82 +0,0 @@ -import { Peer } from './index.js' -import { PeerWorkerProxy } from './proxy.js' -import dgram from '../dgram.js' - -let proxy -let peer - -globalThis.addEventListener('message', ({ data: source }) => { - if (proxy) return - - const opts = { isWorkerThread: true } - - proxy = new PeerWorkerProxy(opts, source.port, async function ({ data: args }) { - const { - prop, - data, - seq - } = args - - switch (prop) { - case 'create': { - peer = new Peer(data, dgram) - - peer.onConnecting = (...args) => this.callMainThread('onConnecting', args) - peer.onConnection = (...args) => this.callMainThread('onConnection', args) - peer.onDisconnection = (...args) => this.callMainThread('onDisconnection', args) - peer.onJoin = (...args) => this.callMainThread('onJoin', args) - peer.onPacket = (...args) => this.callMainThread('onPacket', args) - peer.onStream = (...args) => this.callMainThread('onStream', args) - peer.onData = (...args) => this.callMainThread('onData', args) - peer.onSend = (...args) => this.callMainThread('onSend', args) - peer.onFirewall = (...args) => this.callMainThread('onFirewall', args) - peer.onMulticast = (...args) => this.callMainThread('onMulticast', args) - peer.onJoin = (...args) => this.callMainThread('onJoin', args) - peer.onSync = (...args) => this.callMainThread('onSync', args) - peer.onSyncStart = (...args) => this.callMainThread('onSyncStart', args) - peer.onSyncEnd = (...args) => this.callMainThread('onSyncEnd', args) - peer.onQuery = (...args) => this.callMainThread('onQuery', args) - peer.onNat = (...args) => this.callMainThread('onNat', args) - peer.onState = (...args) => this.callMainThread('onState', args) - peer.onWarn = (...args) => this.callMainThread('onWarn', args) - peer.onError = (...args) => this.callMainThread('onError', args) - peer.onReady = (...args) => this.callMainThread('onReady', args) - break - } - - case 'compileCachePredicate': { - // eslint-disable-next-line - let predicate = new Function(`return ${data.toString()}`)() - predicate = predicate.bind(peer) - peer.cachePredicate = packet => predicate(packet) - break - } - - default: { - if (isNaN(seq) && peer[prop]) { - peer[prop] = data - return - } - - let r - - try { - if (typeof peer[prop] === 'function') { - if (Array.isArray(data)) { - r = await peer[prop](...data) - } else { - r = await peer[prop](data) - } - } else { - r = peer[prop] - } - } catch (err) { - console.error(err) - return this.resolveMainThread(seq, { err }) - } - - this.resolveMainThread(seq, { data: r }) - } - } - }) -}) diff --git a/bin/update-stream-relay-source.sh b/bin/update-stream-relay-source.sh deleted file mode 100755 index 2796bffa61..0000000000 --- a/bin/update-stream-relay-source.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -version="${1:-"1.0.23-0"}" - -rm -rf api/stream-relay.js || exit $? -rm -rf api/stream-relay || exit $? -cp -rf node_modules/@socketsupply/stream-relay/src api/stream-relay || exit $? -rm -rf node_modules/@socketsupply/{socket,socket-{darwin,linux,win32}*,stream-relay} || exit $? - -for file in $(find api/stream-relay -type f); do - sed -i '' -e "s/'socket:\(.*\)'/'..\/\1.js'/g" "$file" || exit $? -done - -{ - echo "import def from './stream-relay/index.js'" - echo "export * from './stream-relay/index.js'" - echo "export default def" -} >> api/stream-relay.js - -tree api/stream-relay diff --git a/package.json b/package.json index 1dfc3e9d6f..ac888988c9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test:android": "cp -f .ssc.env test | echo && cd test && npm install --silent --no-audit && npm run test:android", "test:android-emulator": "cp -f .ssc.env test | echo && cd test && npm install --silent --no-audit && npm run test:android-emulator", "test:clean": "cd test && rm -rf dist", - "update-stream-relay-source": "./bin/update-stream-relay-source.sh", + "update-network-protocol": "./bin/update-network-protocol.sh", "relink": "./bin/publish-npm-modules.sh --link" }, "private": true, @@ -26,7 +26,7 @@ "whatwg-url": "^14.0.0" }, "optionalDependencies": { - "@socketsupply/stream-relay": "^1.0.23-0" + "@socketsupply/latica": "^0.1.0" }, "standard": { "ignore": [ From 3f5282f52993624a73f83557052dc0e8562e7806 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Sat, 25 May 2024 21:33:07 +0200 Subject: [PATCH 0751/1178] refactor(network): add protocol files --- api/latica/api.js | 362 ++++++ api/latica/cache.js | 361 ++++++ api/latica/encryption.js | 288 +++++ api/latica/index.js | 2167 ++++++++++++++++++++++++++++++++ api/latica/nat.js | 185 +++ api/latica/packets.js | 496 ++++++++ api/latica/proxy.js | 281 +++++ api/latica/worker.js | 82 ++ bin/update-network-protocol.sh | 20 + 9 files changed, 4242 insertions(+) create mode 100644 api/latica/api.js create mode 100644 api/latica/cache.js create mode 100644 api/latica/encryption.js create mode 100644 api/latica/index.js create mode 100644 api/latica/nat.js create mode 100644 api/latica/packets.js create mode 100644 api/latica/proxy.js create mode 100644 api/latica/worker.js create mode 100755 bin/update-network-protocol.sh diff --git a/api/latica/api.js b/api/latica/api.js new file mode 100644 index 0000000000..1c1210a4a2 --- /dev/null +++ b/api/latica/api.js @@ -0,0 +1,362 @@ +import { Peer, Encryption, sha256 } from './index.js' +import { PeerWorkerProxy } from './proxy.js' +import { sodium } from '../crypto.js' +import { Buffer } from '../buffer.js' +import { isBufferLike } from '../util.js' +import { Packet, CACHE_TTL } from './packets.js' + +/** + * Initializes and returns the network bus. + * + * @async + * @function + * @param {object} options - Configuration options for the network bus. + * @param {object} events - A nodejs compatibe implementation of the events module. + * @param {object} dgram - A nodejs compatible implementation of the dgram module. + * @returns {Promise} - A promise that resolves to the initialized network bus. + */ +async function api (options = {}, events, dgram) { + await sodium.ready + const bus = new events.EventEmitter() + bus._on = bus.on + bus._once = bus.once + bus._emit = bus.emit + + if (!options.indexed) { + if (!options.clusterId && !options.config?.clusterId) { + throw new Error('expected options.clusterId') + } + + if (typeof options.signingKeys !== 'object') throw new Error('expected options.signingKeys to be of type Object') + if (options.signingKeys.publicKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.publicKey to be of type Uint8Array') + if (options.signingKeys.privateKey?.constructor.name !== 'Uint8Array') throw new Error('expected options.signingKeys.privateKey to be of type Uint8Array') + } + + let clusterId = bus.clusterId = options.clusterId || options.config?.clusterId + + if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters + + const Ctor = globalThis.isSocketRuntime ? PeerWorkerProxy : Peer + const _peer = new Ctor(options, dgram) + + _peer.onJoin = (packet, ...args) => { + if (!Buffer.from(packet.clusterId).equals(clusterId)) return + bus._emit('#join', packet, ...args) + } + + _peer.onPacket = (packet, ...args) => { + if (!Buffer.from(packet.clusterId).equals(clusterId)) return + bus._emit('#packet', packet, ...args) + } + + _peer.onStream = (packet, ...args) => { + if (!Buffer.from(packet.clusterId).equals(clusterId)) return + bus._emit('#stream', packet, ...args) + } + + _peer.onData = (...args) => bus._emit('#data', ...args) + _peer.onSend = (...args) => bus._emit('#send', ...args) + _peer.onFirewall = (...args) => bus._emit('#firewall', ...args) + _peer.onMulticast = (...args) => bus._emit('#multicast', ...args) + _peer.onJoin = (...args) => bus._emit('#join', ...args) + _peer.onSync = (...args) => bus._emit('#sync', ...args) + _peer.onSyncStart = (...args) => bus._emit('#sync-start', ...args) + _peer.onSyncEnd = (...args) => bus._emit('#sync-end', ...args) + _peer.onConnection = (...args) => bus._emit('#connection', ...args) + _peer.onDisconnection = (...args) => bus._emit('#disconnection', ...args) + _peer.onQuery = (...args) => bus._emit('#query', ...args) + _peer.onNat = (...args) => bus._emit('#network-change', ...args) + _peer.onWarn = (...args) => bus._emit('#warning', ...args) + _peer.onState = (...args) => bus._emit('#state', ...args) + _peer.onConnecting = (...args) => bus._emit('#connecting', ...args) + _peer.onConnection = (...args) => bus._emit('#connection', ...args) + + // TODO check if its not a network error + _peer.onError = (...args) => bus._emit('#error', ...args) + + _peer.onReady = info => { + Object.assign(_peer, { + isReady: true, + ...info + }) + + bus._emit('#ready', info) + } + + bus.subclusters = new Map() + + /** + * Gets general, read only information of the network peer. + * + * @function + * @returns {object} - The general information. + */ + bus.getInfo = () => _peer.getInfo() + + /** + * Gets the read only state of the network peer. + * + * @function + * @returns {object} - The address information. + */ + bus.getState = () => _peer.getState() + + /** + * Indexes a new peer in the network. + * + * @function + * @param {object} params - Peer information. + * @param {string} params.peerId - The peer ID. + * @param {string} params.address - The peer address. + * @param {number} params.port - The peer port. + * @throws {Error} - Throws an error if required parameters are missing. + */ + bus.addIndexedPeer = ({ peerId, address, port }) => { + return _peer.addIndexedPeer({ peerId, address, port }) + } + + bus.close = () => _peer.close() + bus.sync = (peerId) => _peer.sync(peerId) + bus.reconnect = () => _peer.reconnect() + bus.disconnect = () => _peer.disconnect() + + bus.sealUnsigned = (m, v = options.signingKeys) => _peer.sealUnsigned(m, v) + bus.openUnsigned = (m, v = options.signingKeys) => _peer.openUnsigned(m, v) + + bus.seal = (m, v = options.signingKeys) => _peer.seal(m, v) + bus.open = (m, v = options.signingKeys) => _peer.open(m, v) + + bus.query = (...args) => _peer.query(...args) + + const pack = async (eventName, value, opts = {}) => { + if (typeof eventName !== 'string') throw new Error('event name must be a string') + if (eventName.length === 0) throw new Error('event name too short') + + if (opts.ttl) opts.ttl = Math.min(opts.ttl, CACHE_TTL) + + const args = { + clusterId, + ...opts, + usr1: await sha256(eventName, { bytes: true }) + } + + if (!isBufferLike(value) && typeof value === 'object') { + try { + args.message = Buffer.from(JSON.stringify(value)) + } catch (err) { + return bus._emit('error', err) + } + } else { + args.message = Buffer.from(value) + } + + args.usr2 = Buffer.from(options.signingKeys.publicKey) + args.sig = Encryption.sign(args.message, options.signingKeys.privateKey) + + return args + } + + const unpack = async packet => { + let opened + let verified + const scid = Buffer.from(packet.subclusterId).toString('base64') + const sub = bus.subclusters.get(scid) + if (!sub) return {} + + try { + opened = await _peer.open(packet.message, scid) + } catch (err) { + sub._emit('warning', err) + return {} + } + + if (packet.sig) { + try { + if (Encryption.verify(opened.data || opened, packet.sig, packet.usr2)) { + verified = true + } + } catch (err) { + sub._emit('warning', err) + return {} + } + } + + return { opened, verified } + } + + /** + * Publishes an event to the network bus. + * + * @async + * @function + * @param {string} eventName - The name of the event. + * @param {any} value - The value associated with the event. + * @param {object} opts - Additional options for publishing. + * @returns {Promise} - A promise that resolves to the published event details. + */ + bus.emit = async (eventName, value, opts = {}) => { + const args = await pack(eventName, value, opts) + if (!options.sharedKey) { + throw new Error('Can\'t emit to the top level cluster, a shared key was not provided in the constructor or the arguments options') + } + + return await _peer.publish(options.sharedKey || opts.sharedKey, args) + } + + bus.on = async (eventName, cb) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + bus._on(eventName, cb) + } + + bus.subcluster = async (options = {}) => { + if (!options.sharedKey?.constructor.name) { + throw new Error('expected options.sharedKey to be of type Uint8Array') + } + + const derivedKeys = await Encryption.createKeyPair(options.sharedKey) + const subclusterId = Buffer.from(derivedKeys.publicKey) + const scid = subclusterId.toString('base64') + + if (bus.subclusters.has(scid)) return bus.subclusters.get(scid) + + const sub = new events.EventEmitter() + sub._emit = sub.emit + sub._on = sub.on + sub.peers = new Map() + + bus.subclusters.set(scid, sub) + + sub.peerId = _peer.peerId + sub.subclusterId = subclusterId + sub.sharedKey = options.sharedKey + sub.derivedKeys = derivedKeys + + sub.emit = async (eventName, value, opts = {}) => { + opts.clusterId = opts.clusterId || clusterId + opts.subclusterId = opts.subclusterId || sub.subclusterId + + const args = await pack(eventName, value, opts) + + if (sub.peers.values().length) { + let packets = [] + + for (const p of sub.peers.values()) { + const r = await p._peer.write(sub.sharedKey, args) + if (packets.length === 0) packets = r + } + + for (const packet of packets) { + const p = Packet.from(packet) + const pid = Buffer.from(packet.packetId).toString('hex') + _peer.cache.insert(pid, p) + + _peer.unpublished[pid] = Date.now() + if (globalThis.navigator && !globalThis.navigator.onLine) continue + + _peer.mcast(packet) + } + return packets + } else { + const packets = await _peer.publish(sub.sharedKey, args) + return packets + } + } + + sub.on = async (eventName, cb) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + sub._on(eventName, cb) + } + + sub.off = async (eventName, fn) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + sub.removeListener(eventName, fn) + } + + sub.join = () => _peer.join(sub.sharedKey, options) + + bus._on('#ready', () => { + const scid = Buffer.from(sub.subclusterId).toString('base64') + const subcluster = bus.subclusters.get(scid) + if (subcluster) _peer.join(subcluster.sharedKey, options) + }) + + _peer.join(sub.sharedKey, options) + return sub + } + + bus._on('#join', async (packet, peer) => { + const scid = Buffer.from(packet.subclusterId).toString('base64') + const sub = bus.subclusters.get(scid) + if (!sub) return + + let ee = sub.peers.get(peer.peerId) + + if (!ee) { + ee = new events.EventEmitter() + + ee._on = ee.on + ee._emit = ee.emit + + ee.peerId = peer.peerId + ee.address = peer.address + ee.port = peer.port + + ee.emit = async (eventName, value, opts = {}) => { + if (!ee._peer.write) return + + opts.clusterId = opts.clusterId || clusterId + opts.subclusterId = opts.subclusterId || sub.subclusterId + + const args = await pack(eventName, value, opts) + return peer.write(sub.sharedKey, args) + } + + ee.on = async (eventName, cb) => { + if (eventName[0] !== '#') eventName = await sha256(eventName) + ee._on(eventName, cb) + } + } + + const oldPeer = sub.peers.has(peer.peerId) + const portChange = oldPeer.port !== peer.port + const addressChange = oldPeer.address !== peer.address + const natChange = oldPeer.natType !== peer.natType + const change = portChange || addressChange || natChange + + ee._peer = peer + + sub.peers.set(peer.peerId, ee) + if (!oldPeer || change) sub._emit('#join', ee, packet) + }) + + const handlePacket = async (packet, peer, port, address) => { + const scid = Buffer.from(packet.subclusterId).toString('base64') + const sub = bus.subclusters.get(scid) + if (!sub) return + + const eventName = Buffer.from(packet.usr1).toString('hex') + const { verified, opened } = await unpack(packet) + if (verified) packet.verified = true + + sub._emit(eventName, opened, packet) + + const ee = sub.peers.get(packet.streamFrom || peer?.peerId) + if (ee) ee._emit(eventName, opened, packet) + } + + bus._on('#stream', handlePacket) + bus._on('#packet', handlePacket) + + bus._on('#disconnection', peer => { + for (const sub of bus.subclusters) { + sub._emit('#leave', peer) + sub.peers.delete(peer.peerId) + } + }) + + await _peer.init() + return bus +} + +export { api } +export default api diff --git a/api/latica/cache.js b/api/latica/cache.js new file mode 100644 index 0000000000..1893b72a99 --- /dev/null +++ b/api/latica/cache.js @@ -0,0 +1,361 @@ +import { isBufferLike, toBuffer } from '../util.js' +import { Buffer } from '../buffer.js' +import { createDigest } from '../crypto.js' +import { Packet, PacketPublish, PACKET_BYTES, sha256 } from './packets.js' + +const EMPTY_CACHE = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' + +export const trim = (/** @type {Buffer} */ buf) => { + return buf.toString().split('~')[0].split('\x00')[0] +} + +/** + * Tries to convert input to a `Buffer`, if possible, otherwise `null`. + * @ignore + * @param {(string|Buffer|Uint8Array)?} m + * @return {Buffer?} + */ +function toBufferMaybe (m) { + return isBufferLike(m) || typeof m === 'string' + ? toBuffer(m) + : m && typeof m === 'object' + ? toBuffer(JSON.stringify(m)) + : null +} + +/** + * Default max size of a `Cache` instance. + */ +export const DEFAULT_MAX_SIZE = Math.ceil(16_000_000 / PACKET_BYTES) + +/** + * @typedef {Packet} CacheEntry + * @typedef {function(CacheEntry, CacheEntry): number} CacheEntrySiblingResolver + */ + +/** + * Default cache sibling resolver that computes a delta between + * two entries clocks. + * @param {CacheEntry} a + * @param {CacheEntry} b + * @return {number} + */ +export function defaultSiblingResolver (a, b) { + return a.clock - b.clock +} + +/** + * Internal mapping of packet IDs to packet data used by `Cache`. + */ +export class CacheData extends Map {} + +/** + * A class for storing a cache of packets by ID. This class includes a scheme + * for reconciling disjointed packet caches in a large distributed system. The + * following are key design characteristics. + * + * Space Efficiency: This scheme can be space-efficient because it summarizes + * the cache's contents in a compact binary format. By sharing these summaries, + * two computers can quickly determine whether their caches have common data or + * differences. + * + * Bandwidth Efficiency: Sharing summaries instead of the full data can save + * bandwidth. If the differences between the caches are small, sharing summaries + * allows for more efficient data synchronization. + * + * Time Efficiency: The time efficiency of this scheme depends on the size of + * the cache and the differences between the two caches. Generating summaries + * and comparing them can be faster than transferring and comparing the entire + * dataset, especially for large caches. + * + * Complexity: The scheme introduces some complexity due to the need to encode + * and decode summaries. In some cases, the overhead introduced by this + * complexity might outweigh the benefits, especially if the caches are + * relatively small. In this case, you should be using a query. + * + * Data Synchronization Needs: The efficiency also depends on the data + * synchronization needs. If the data needs to be synchronized in real-time, + * this scheme might not be suitable. It's more appropriate for cases where + * periodic or batch synchronization is acceptable. + * + * Scalability: The scheme's efficiency can vary depending on the scalability + * of the system. As the number of cache entries or computers involved + * increases, the complexity of generating and comparing summaries will stay + * bound to a maximum of 16Mb. + * + */ +export class Cache { + data = new CacheData() + maxSize = DEFAULT_MAX_SIZE + + static HASH_SIZE_BYTES = 20 + + /** + * `Cache` class constructor. + * @param {CacheData?} [data] + */ + constructor (data = new CacheData(), siblingResolver = defaultSiblingResolver) { + if (data instanceof Map) { + this.data = data + } else if (Array.isArray(data)) { + this.data = new CacheData(data) + } + + if (typeof siblingResolver === 'function') { + this.siblingResolver = siblingResolver + } else { + this.siblingResolver = defaultSiblingResolver + } + } + + /** + * Readonly count of the number of cache entries. + * @type {number} + */ + get size () { + return this.data.size + } + + /** + * Readonly size of the cache in bytes. + * @type {number} + */ + get bytes () { + return this.data.size * PACKET_BYTES + } + + /** + * Inserts a `CacheEntry` value `v` into the cache at key `k`. + * @param {string} k + * @param {CacheEntry} v + * @return {boolean} + */ + insert (k, v) { + if (v.type !== PacketPublish.type) return false + if (this.has(k)) return false + + if (this.data.size === this.maxSize) { + const oldest = [...this.data.values()] + .sort((a, b) => a.timestamp - b.timestamp) + // take a slice of 128 of the oldest candidates and + // eject 32 that are mostly from non-related clusters, + // some random and cluster-related. + .pop() + + this.data.delete(oldest.packetId.toString('hex')) + if (this.onEjected) this.onEjected(oldest) + } + + v.timestamp = Date.now() + if (!v.ttl) v.ttl = Packet.ttl // use default TTL if none provided + + this.data.set(k, v) + if (this.onInsert) this.onInsert(k, v) + return true + } + + /** + * Gets a `CacheEntry` value at key `k`. + * @param {string} k + * @return {CacheEntry?} + */ + get (k) { + return this.data.get(k) + } + + /** + * @param {string} k + * @return {boolean} + */ + delete (k) { + if (!this.has(k)) return false + this.data.delete(k) + return true + } + + /** + * Predicate to determine if cache contains an entry at key `k`. + * @param {string} k + * @return {boolean} + */ + has (k) { + return this.data.has(k) + } + + /** + * Composes an indexed packet into a new `Packet` + * @param {Packet} packet + */ + async compose (packet, source = this.data) { + let previous = packet + + if (packet?.index > 0) previous = source.get(packet.previousId.toString('hex')) + if (!previous) return null + + const { meta, size, indexes, ts } = previous.message + + // follow the chain to get the buffers in order + const bufs = [...source.values()] + .filter(p => Buffer.from(p.previousId).toString('hex') === Buffer.from(previous.packetId).toString('hex')) + .sort((a, b) => a.index - b.index) + + if (!indexes || bufs.length < indexes) return null + + // concat and then hash, the original should match + const messages = bufs.map(p => p.message) + const buffers = messages.map(toBufferMaybe).filter(Boolean) + const message = Buffer.concat(buffers, size) + + if (!meta.ts) meta.ts = ts + // generate a new packet ID + const packetId = await sha256(Buffer.concat([packet.previousId, message]), { bytes: true }) + + return Packet.from({ + ...packet, + packetId, + message, + isComposed: true, + index: -1, + meta + }) + } + + async sha1 (value, toHex) { + const buf = await createDigest('SHA-1', isBufferLike(value) ? value : Buffer.from(value)) + if (!toHex) return buf + return buf.toString('hex') + } + + /** + * + * The summarize method returns a terse yet comparable summary of the cache + * contents. + * + * Think of the cache as a trie of hex characters, the summary returns a + * checksum for the current level of the trie and for its 16 children. + * + * This is similar to a merkel tree as equal subtrees can easily be detected + * without the need for further recursion. When the subtree checksums are + * inequivalent then further negotiation at lower levels may be required, this + * process continues until the two trees become synchonized. + * + * When the prefix is empty, the summary will return an array of 16 checksums + * these checksums provide a way of comparing that subtree with other peers. + * + * When a variable-length hexidecimal prefix is provided, then only cache + * member hashes sharing this prefix will be considered. + * + * For each hex character provided in the prefix, the trie will decend by one + * level, each level divides the 2^128 address space by 16. For exmaple... + * + * ``` + * Level 0 1 2 + * ---------------- + * 2b00 + * aa0e ━┓ ━┓ + * aa1b ┃ ┃ + * aae3 ┃ ┃ ━┓ + * aaea ┃ ┃ ┃ + * aaeb ┃ ━┛ ━┛ + * ab00 ┃ ━┓ + * ab1e ┃ ┃ + * ab2a ┃ ┃ + * abef ┃ ┃ + * abf0 ━┛ ━┛ + * bff9 + * ``` + * + * @param {string} prefix - a string of lowercased hexidecimal characters + * @return {Object} + * + */ + async summarize (prefix = '', predicate = o => false) { + // each level has 16 children (0x0-0xf) + const children = new Array(16).fill(null).map(_ => []) + + // partition the cache into children + for (const [key, packet] of this.data.entries()) { + if (!key || !key.slice) continue + if (prefix.length && !key.startsWith(prefix)) continue + if (!predicate(packet)) continue + const hex = key.slice(prefix.length, prefix.length + 1) + if (children[parseInt(hex, 16)]) children[parseInt(hex, 16)].push(key) + } + + // compute a checksum for all child members (deterministically) + // if the bucket is empty, return null + const buckets = await Promise.all(children.map(child => { + return child.length ? this.sha1(child.sort().join(''), true) : Promise.resolve(null) + })) + + let hash + // compute a summary hash (checksum of all other hashes) + + if (!buckets.every(b => b === null)) { + hash = await this.sha1(buckets.join(''), true) + } else { + hash = EMPTY_CACHE + } + + return { prefix, hash, buckets } + } + + /** + * The encodeSummary method provides a compact binary encoding of the output + * of summary() + * + * @param {Object} summary - the output of calling summary() + * @return {Buffer} + **/ + static encodeSummary (summary) { + // prefix is variable-length hex string encoded with the first byte indicating the length + const prefixBin = Buffer.alloc(1 + Math.ceil(summary.prefix.length / 2)) + prefixBin.writeUInt8(summary.prefix.length, 0) + const prefixHex = summary.prefix.length % 2 ? '0' + summary.prefix : summary.prefix + Buffer.from(prefixHex, 'hex').copy(prefixBin, 1) + + // hash is the summary hash (checksum of all other hashes) + const hashBin = Buffer.from(summary.hash, 'hex') + + // buckets are rows of { offset, sum } where the sum is not null + const bucketBin = Buffer.concat(summary.buckets.map((sum, offset) => { + // empty buckets are omitted from the encoding + if (!sum) return Buffer.alloc(0) + + // write the child offset as a uint8 + const offsetBin = Buffer.alloc(1) + offsetBin.writeUInt8(offset, 0) + + return Buffer.concat([offsetBin, Buffer.from(sum, 'hex')]) + })) + + return Buffer.concat([prefixBin, hashBin, bucketBin]) + } + + /** + * The decodeSummary method decodes the output of encodeSummary() + * + * @param {Buffer} bin - the output of calling encodeSummary() + * @return {Object} summary + **/ + static decodeSummary (bin) { + let o = 0 // byte offset + + // prefix is variable-length hex string encoded with the first byte indicating the length + const plen = bin.readUint8(o++) + const prefix = bin.slice(o, o += Math.ceil(plen / 2)).toString('hex').slice(-plen) + + // hash is the summary hash (checksum of all other hashes) + const hash = bin.slice(o, o += Cache.HASH_SIZE_BYTES).toString('hex') + + // buckets are rows of { offset, sum } where the sum is not null + const buckets = new Array(16).fill(null) + while (o < bin.length) { + buckets[bin.readUint8(o++)] = bin.slice(o, o += Cache.HASH_SIZE_BYTES).toString('hex') + } + + return { prefix, hash, buckets } + } +} + +export default Cache diff --git a/api/latica/encryption.js b/api/latica/encryption.js new file mode 100644 index 0000000000..8b0ded7055 --- /dev/null +++ b/api/latica/encryption.js @@ -0,0 +1,288 @@ +import { sodium } from '../crypto.js' +import Buffer from '../buffer.js' +import { sha256 } from './packets.js' +import { isArrayLike, isBufferLike } from '../util.js' + +/** + * Class for handling encryption and key management. + */ +export class Encryption { + /** + * Mapping of public keys to key objects. + * @type {Object.} + */ + keys = {} + + /** + * Creates a shared key based on the provided seed or generates a random one. + * @param {Uint8Array|string} seed - Seed for key generation. + * @returns {Promise} - Shared key. + */ + static async createSharedKey (seed) { + await sodium.ready + + if (seed) return sodium.crypto_generichash(32, seed) + return sodium.randombytes_buf(32) + } + + /** + * Creates a key pair for signing and verification. + * @param {Uint8Array|string} seed - Seed for key generation. + * @returns {Promise<{ publicKey: Uint8Array, privateKey: Uint8Array }>} - Key pair. + */ + static async createKeyPair (seed) { + await sodium.ready + seed = seed || sodium.randombytes_buf(32) + + if (typeof seed === 'string') { + seed = sodium.crypto_generichash(32, seed) + } + + return sodium.crypto_sign_seed_keypair(seed) + } + + /** + * Creates an ID using SHA-256 hash. + * @param {string} str - String to hash. + * @returns {Promise} - SHA-256 hash. + */ + static async createId (str) { + await sodium.ready + return await sha256(str || sodium.randombytes_buf(32)) + } + + /** + * Creates a cluster ID using SHA-256 hash with specified output size. + * @param {string} str - String to hash. + * @returns {Promise} - SHA-256 hash with specified output size. + */ + static async createClusterId (str) { + await sodium.ready + return await sha256(str || sodium.randombytes_buf(32), { bytes: true }) + } + + /** + * Adds a key pair to the keys mapping. + * @param {Uint8Array|string} publicKey - Public key. + * @param {Uint8Array} privateKey - Private key. + */ + add (publicKey, privateKey) { + if (!publicKey) throw new Error('encryption.add expects publicKey') + if (!privateKey) throw new Error('encryption.add expects privateKey') + + const to = Buffer.from(publicKey).toString('base64') + this.keys[to] = { publicKey, privateKey, ts: Date.now() } + } + + /** + * Removes a key from the keys mapping. + * @param {Uint8Array|string} publicKey - Public key. + */ + remove (publicKey) { + delete this.keys[Buffer.from(publicKey).toString('base64')] + } + + /** + * Checks if a key is in the keys mapping. + * @param {Uint8Array|string} to - Public key or Uint8Array. + * @returns {boolean} - True if the key is present, false otherwise. + */ + has (to) { + if (to instanceof Uint8Array) { + to = Buffer.from(to).toString('base64') + } + + return !!this.keys[to] + } + + /** + * Signs a message using the given secret key. + * @param {Buffer} b - The message to sign. + * @param {Uint8Array} sk - The secret key to use. + * @returns {Uint8Array} - Signature. + */ + static sign (b, sk) { + const ct = b.subarray(sodium.crypto_sign_BYTES) + const sig = sodium.crypto_sign_detached(ct, sk) + return sig + } + + /** + * Verifies the signature of a message using the given public key. + * @param {Buffer} b - The message to verify. + * @param {Uint8Array} sig - The signature to check. + * @param {Uint8Array} pk - The public key to use. + * @returns {number} - Returns non-zero if the buffer could not be verified. + */ + static verify (b, sig, pk) { + if (sig.length !== sodium.crypto_sign_BYTES) return false + const ct = b.subarray(sodium.crypto_sign_BYTES) + return sodium.crypto_sign_verify_detached(sig, ct, pk) + } + + /** + * Opens a sealed message using the specified key. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found. + */ + openUnsigned (message, v) { + if (typeof v === 'string') v = this.keys[v] + if (!v) throw new Error(`ENOKEY (key=${v})`) + + const pk = toPK(v.publicKey) + const sk = toSK(v.privateKey) + return Buffer.from(sodium.crypto_box_seal_open(message, pk, sk)) + } + + sealUnsigned (message, v) { + if (typeof v === 'string') v = this.keys[v] + if (!v) throw new Error(`ENOKEY (key=${v})`) + + this.add(v.publicKey, v.privateKey) + + const pk = toPK(v.publicKey) + const pt = toUint8Array(message) + const ct = sodium.crypto_box_seal(pt, pk) + return sodium.crypto_box_seal(toUint8Array(ct), pk) + } + + /** + * Decrypts a sealed and signed message for a specific receiver. + * @param {Buffer} message - The sealed message. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Decrypted message. + * @throws {Error} - Throws ENOKEY if the key is not found, EMALFORMED if the message is malformed, ENOTVERIFIED if the message cannot be verified. + */ + open (message, v) { + if (typeof v === 'string') v = this.keys[v] + if (!v) throw new Error(`ENOKEY (key=${v})`) + + const pk = toPK(v.publicKey) + const sk = toSK(v.privateKey) + const buf = sodium.crypto_box_seal_open(message, pk, sk) + + if (buf.byteLength <= sodium.crypto_sign_BYTES) { + throw new Error('EMALFORMED') + } + + const sig = buf.subarray(0, sodium.crypto_sign_BYTES) + const ct = buf.subarray(sodium.crypto_sign_BYTES) + + if (!sodium.crypto_sign_verify_detached(sig, ct, v.publicKey)) { + throw new Error('ENOTVERIFIED') + } + + return Buffer.from(sodium.crypto_box_seal_open(ct, pk, sk)) + } + + /** + * Seals and signs a message for a specific receiver using their public key. + * + * `Seal(message, receiver)` performs an _encrypt-sign-encrypt_ (ESE) on + * a plaintext `message` for a `receiver` identity. This prevents repudiation + * attacks and doesn't rely on packet chain guarantees. + * + * let ct = Seal(sender | pt, receiver) + * let sig = Sign(ct, sk) + * let out = Seal(sig | ct) + * + * In an setup between Alice & Bob, this means: + * - Only Bob sees the plaintext + * - Alice wrote the plaintext and the ciphertext + * - Only Bob can see that Alice wrote the plaintext and ciphertext + * - Bob cannot forward the message without invalidating Alice's signature. + * - The outer encryption serves to prevent an attacker from replacing Alice's + * signature. As with _sign-encrypt-sign (SES), ESE is a variant of + * including the recipient's name inside the plaintext, which is then signed + * and encrypted Alice signs her plaintext along with her ciphertext, so as + * to protect herself from a laintext-substitution attack. At the same time, + * Alice's signed plaintext gives Bob non-repudiation. + * + * @see https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html + * + * @param {Buffer} message - The message to seal. + * @param {Object|string} v - Key object or public key. + * @returns {Buffer} - Sealed message. + * @throws {Error} - Throws ENOKEY if the key is not found. + */ + seal (message, v) { + if (typeof v === 'string') v = this.keys[v] + if (!v) throw new Error(`ENOKEY (key=${v})`) + + this.add(v.publicKey, v.privateKey) + + const pk = toPK(v.publicKey) + const pt = toUint8Array(message) + const ct = sodium.crypto_box_seal(pt, pk) + const sig = sodium.crypto_sign_detached(ct, v.privateKey) + return sodium.crypto_box_seal(toUint8Array(Buffer.concat([sig, ct])), pk) + } +} + +/** + * Converts an Ed25519 public key to a Curve25519 public key. + * @param {Uint8Array} pk - Ed25519 public key. + * @returns {Uint8Array} - Curve25519 public key. + */ +function toPK (pk) { + try { + return sodium.crypto_sign_ed25519_pk_to_curve25519(pk) + } catch {} + + return new Uint8Array(0) +} + +/** + * Converts an Ed25519 secret key to a Curve25519 secret key. + * @param {Uint8Array} sk - Ed25519 secret key. + * @returns {Uint8Array} - Curve25519 secret key. + */ +function toSK (sk) { + try { + return sodium.crypto_sign_ed25519_sk_to_curve25519(sk) + } catch {} + + return new Uint8Array(0) +} + +/** + * Converts different types of input to a Uint8Array. + * @param {Uint8Array|string|Buffer} buffer - Input buffer. + * @returns {Uint8Array} - Uint8Array representation of the input. + */ +const textEncoder = new TextEncoder() + +/** + * Converts different types of input to a Uint8Array. + * @param {Uint8Array|string|Buffer} buffer - Input buffer. + * @returns {Uint8Array} - Uint8Array representation of the input. + */ +function toUint8Array (buffer) { + if (buffer instanceof Uint8Array) { + return buffer + } + + if (typeof buffer === 'string') { + return textEncoder.encode(buffer) + } + + if (buffer?.buffer instanceof ArrayBuffer) { + return new Uint8Array(buffer.buffer) + } + + if (isArrayLike(buffer) || isBufferLike(buffer)) { + return new Uint8Array(buffer) + } + + if (buffer instanceof ArrayBuffer) { + return new Uint8Array(buffer) + } + + if (buffer && Symbol.iterator in buffer) { + return new Uint8Array(buffer) + } + + return new Uint8Array(0) +} diff --git a/api/latica/index.js b/api/latica/index.js new file mode 100644 index 0000000000..f24af29817 --- /dev/null +++ b/api/latica/index.js @@ -0,0 +1,2167 @@ +/** + * @module network + * @status Experimental + * + * This module provides primitives for creating a p2p network. + */ +import { isBufferLike } from '../util.js' +import { Buffer } from '../buffer.js' +import { sodium, randomBytes } from '../crypto.js' + +import { Encryption } from './encryption.js' +import { Cache } from './cache.js' +import * as NAT from './nat.js' + +import { + Packet, + PacketPing, + PacketPong, + PacketIntro, + PacketPublish, + PacketStream, + PacketSync, + PacketJoin, + PacketQuery, + sha256, + VERSION +} from './packets.js' + +let logcount = 0 +const process = globalThis.process || globalThis.window?.__args +const COLOR_GRAY = '\x1b[90m' +const COLOR_WHITE = '\x1b[37m' +const COLOR_RESET = '\x1b[0m' + +export const debug = (pid, ...args) => { + if (typeof process === 'undefined' || !process.env.DEBUG) return + const output = COLOR_GRAY + + String(logcount++).padStart(6) + ' │ ' + COLOR_WHITE + + pid.slice(0, 4) + ' ' + args.join(' ') + COLOR_RESET + + if (new RegExp(process.env.DEBUG).test(output)) console.log(output) +} + +export { Packet, sha256, Cache, Encryption, NAT } + +/** + * Retry delay in milliseconds for ping. + * @type {number} + */ +export const PING_RETRY = 500 + +/** + * Probe wait timeout in milliseconds. + * @type {number} + */ +export const PROBE_WAIT = 512 + +/** + * Default keep alive timeout. + * @type {number} + */ +export const DEFAULT_KEEP_ALIVE = 30_000 + +/** + * Default rate limit threshold in milliseconds. + * @type {number} + */ +export const DEFAULT_RATE_LIMIT_THRESHOLD = 8000 + +const PRIV_PORTS = 1024 +const MAX_PORTS = 65535 - PRIV_PORTS +const MAX_BANDWIDTH = 1024 * 32 + +const PEERID_REGEX = /^([A-Fa-f0-9]{2}){32}$/ + +/** + * Port generator factory function. + * @param {object} ports - the cache to use (a set) + * @param {number?} p - initial port + * @return {number} + */ +export const getRandomPort = (ports = new Set(), p) => { + do { + p = Math.max(1024, Math.ceil(Math.random() * 0xffff)) + } while (ports.has(p) && ports.size < MAX_PORTS) + + ports.add(p) + return p +} + +const isReplicatable = type => ( + type === PacketPublish.type || + type === PacketJoin.type +) + +/** + * Computes rate limit predicate value for a port and address pair for a given + * threshold updating an input rates map. This method is accessed concurrently, + * the rates object makes operations atomic to avoid race conditions. + * + * @param {Map} rates + * @param {number} type + * @param {number} port + * @param {string} address + * @return {boolean} + */ +export function rateLimit (rates, type, port, address, subclusterIdQuota) { + const R = isReplicatable(type) + const key = (R ? 'R' : 'C') + ':' + address + ':' + port + const quota = subclusterIdQuota || (R ? 512 : 4096) + const time = Math.floor(Date.now() / 60000) + const rate = rates.get(key) || { time, quota, used: 0 } + + rate.mtime = Date.now() // checked by mainLoop for garabge collection + + if (time !== rate.time) { + rate.time = time + if (rate.used > rate.quota) rate.quota -= 1 + else if (rate.used < quota) rate.quota += 1 + rate.used = 0 + } + + rate.used += 1 + + rates.set(key, rate) + if (rate.used >= rate.quota) return true +} + +/** + * A `RemotePeer` represents an initial, discovered, or connected remote peer. + * Typically, you will not need to create instances of this class directly. + */ +export class RemotePeer { + peerId = null + address = null + port = 0 + natType = null + clusters = {} + pingId = null + distance = 0 + connected = false + opening = 0 + probed = 0 + proxy = null + clock = 0 + uptime = 0 + lastUpdate = 0 + lastRequest = 0 + localPeer = null + + /** + * `RemotePeer` class constructor. + * @param {{ + * peerId?: string, + * address?: string, + * port?: number, + * natType?: number, + * clusters: object, + * reflectionId?: string, + * distance?: number, + * publicKey?: string, + * privateKey?: string, + * clock?: number, + * lastUpdate?: number, + * lastRequest?: number + * }} o + */ + constructor (o, peer) { + this.localPeer = peer + + if (!o.peerId) throw new Error('expected .peerId') + if (o.indexed) o.natType = NAT.UNRESTRICTED + if (o.natType && !NAT.isValid(o.natType)) throw new Error(`invalid .natType (${o.natType})`) + + const cid = o.clusterId?.toString('base64') + const scid = o.subclusterId?.toString('base64') + + if (cid && scid) { + this.clusters[cid] = { [scid]: { rateLimit: MAX_BANDWIDTH } } + } + + Object.assign(this, o) + } + + async write (sharedKey, args) { + let rinfo = this + if (this.proxy) rinfo = this.proxy + + const keys = await Encryption.createKeyPair(sharedKey) + + args.subclusterId = Buffer.from(keys.publicKey) + args.clusterId = Buffer.from(this.localPeer.clusterId, 'base64') + args.usr3 = Buffer.from(this.peerId, 'hex') + args.usr4 = Buffer.from(this.localPeer.peerId, 'hex') + args.message = this.localPeer.encryption.seal(args.message, keys) + + const packets = await this.localPeer._message2packets(PacketStream, args.message, args) + + if (this.proxy) { + debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) + } + + for (const packet of packets) { + const from = this.localPeer.peerId.slice(0, 6) + const to = this.peerId.slice(0, 6) + debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) + + this.localPeer.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) + await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) + } + + return packets + } +} + +/** + * `Peer` class factory. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ +export class Peer { + port = null + address = null + natType = NAT.UNKNOWN + nextNatType = NAT.UNKNOWN + clusters = {} + reflectionId = null + reflectionTimeout = null + reflectionStage = 0 + reflectionRetry = 1 + reflectionFirstResponder = null + peerId = '' + isListening = false + ctime = Date.now() + lastUpdate = 0 + lastSync = 0 + closing = false + clock = 0 + unpublished = {} + cache = null + uptime = 0 + maxHops = 16 + bdpCache = /** @type {number[]} */ ([]) + + dgram = () => { throw new Error('dgram implementation required in constructor as second argument') } + + onListening = null + onDelete = null + + sendQueue = [] + firewall = null + rates = new Map() + streamBuffer = new Map() + gate = new Map() + returnRoutes = new Map() + + metrics = { + i: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, REJECTED: 0 }, + o: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 } + } + + peers = JSON.parse(/* snapshot_start=1691579150299, filter=easy,static */` + [{"address":"44.213.42.133","port":10885,"peerId":"4825fe0475c44bc0222e76c5fa7cf4759cd5ef8c66258c039653f06d329a9af5","natType":31,"indexed":true},{"address":"107.20.123.15","port":31503,"peerId":"2de8ac51f820a5b9dc8a3d2c0f27ccc6e12a418c9674272a10daaa609eab0b41","natType":31,"indexed":true},{"address":"54.227.171.107","port":43883,"peerId":"7aa3d21ceb527533489af3888ea6d73d26771f30419578e85fba197b15b3d18d","natType":31,"indexed":true},{"address":"54.157.134.116","port":34420,"peerId":"1d2315f6f16e5f560b75fbfaf274cad28c12eb54bb921f32cf93087d926f05a9","natType":31,"indexed":true},{"address":"184.169.205.9","port":52489,"peerId":"db00d46e23d99befe42beb32da65ac3343a1579da32c3f6f89f707d5f71bb052","natType":31,"indexed":true},{"address":"35.158.123.13","port":31501,"peerId":"4ba1d23266a2d2833a3275c1d6e6f7ce4b8657e2f1b8be11f6caf53d0955db88","natType":31,"indexed":true},{"address":"3.68.89.3","port":22787,"peerId":"448b083bd8a495ce684d5837359ce69d0ff8a5a844efe18583ab000c99d3a0ff","natType":31,"indexed":true},{"address":"3.76.100.161","port":25761,"peerId":"07bffa90d89bf74e06ff7f83938b90acb1a1c5ce718d1f07854c48c6c12cee49","natType":31,"indexed":true},{"address":"3.70.241.230","port":61926,"peerId":"1d7ee8d965794ee286ac425d060bab27698a1de92986dc6f4028300895c6aa5c","natType":31,"indexed":true},{"address":"3.70.160.181","port":41141,"peerId":"707c07171ac9371b2f1de23e78dad15d29b56d47abed5e5a187944ed55fc8483","natType":31,"indexed":true},{"address":"3.122.250.236","port":64236,"peerId":"a830615090d5cdc3698559764e853965a0d27baad0e3757568e6c7362bc6a12a","natType":31,"indexed":true},{"address":"18.130.98.23","port":25111,"peerId":"ba483c1477ab7a99de2d9b60358d9641ff6a6dc6ef4e3d3e1fc069b19ac89da4","natType":31,"indexed":true},{"address":"13.42.10.247","port":2807,"peerId":"032b79de5b4581ee39c6d15b12908171229a8eb1017cf68fd356af6bbbc21892","natType":31,"indexed":true},{"address":"18.229.140.216","port":36056,"peerId":"73d726c04c05fb3a8a5382e7a4d7af41b4e1661aadf9020545f23781fefe3527","natType":31,"indexed":true}] + `/* snapshot_end=1691579150299 */).map((/** @type {object} */ o) => new RemotePeer({ ...o, indexed: true }, this)) + + /** + * `Peer` class constructor. + * @param {object=} opts - Options + * @param {Buffer} opts.peerId - A 32 byte buffer (ie, `Encryption.createId()`). + * @param {Buffer} opts.clusterId - A 32 byte buffer (ie, `Encryption.createClusterId()`). + * @param {number=} opts.port - A port number. + * @param {number=} opts.probeInternalPort - An internal port number (semi-private for testing). + * @param {number=} opts.probeExternalPort - An external port number (semi-private for testing). + * @param {number=} opts.natType - A nat type. + * @param {string=} opts.address - An ipv4 address. + * @param {number=} opts.keepalive - The interval of the main loop. + * @param {function=} opts.siblingResolver - A function that can be used to determine canonical data in case two packets have concurrent clock values. + * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). + */ + constructor (persistedState = {}, dgram) { + const config = persistedState?.config ?? persistedState ?? {} + + this.encryption = new Encryption() + + if (!config.peerId) throw new Error('constructor expected .peerId') + if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error('invalid .peerId') + + // + // The purpose of this.config is to seperate transitioned state from initial state. + // + this.config = { + keepalive: DEFAULT_KEEP_ALIVE, + ...config + } + + this.dgram = () => dgram + + let cacheData + + if (persistedState?.data?.length > 0) { + cacheData = new Map(persistedState.data) + } + + this.cache = new Cache(cacheData, config.siblingResolver) + this.cache.onEjected = p => this.mcast(p) + + this.unpublished = persistedState?.unpublished || {} + this._onError = err => this.onError && this.onError(err) + + Object.assign(this, config) + + if (!this.indexed && !this.clusterId) throw new Error('constructor expected .clusterId') + if (typeof this.peerId !== 'string') throw new Error('peerId should be of type string') + + this.port = config.port || null + this.natType = config.natType || null + this.address = config.address || null + + this.socket = this.dgram().createSocket('udp4', null, this) + this.probeSocket = this.dgram().createSocket('udp4', null, this).unref() + + const isRecoverable = err => + err.code === 'ECONNRESET' || + err.code === 'ECONNREFUSED' || + err.code === 'EADDRINUSE' || + err.code === 'ETIMEDOUT' + + this.socket.on('error', err => isRecoverable(err) && this._listen()) + this.probeSocket.on('error', err => isRecoverable(err) && this._listen()) + } + + /** + * An implementation for clearning an interval that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearInterval (tid) { + clearInterval(tid) + } + + /** + * An implementation for clearning a timeout that can be overridden by the test suite + * @param Number the number that identifies the timer + * @return {undefined} + * @ignore + */ + _clearTimeout (tid) { + clearTimeout(tid) + } + + /** + * An implementation of an internal timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setInterval (fn, t) { + return setInterval(fn, t) + } + + /** + * An implementation of an timeout timer that can be overridden by the test suite + * @return {Number} + * @ignore + */ + _setTimeout (fn, t) { + return setTimeout(fn, t) + } + + /** + * A method that encapsulates the listing procedure + * @return {undefined} + * @ignore + */ + async _listen () { + await sodium.ready + + this.socket.removeAllListeners() + this.probeSocket.removeAllListeners() + + this.socket.on('message', (...args) => this._onMessage(...args)) + this.socket.on('error', (...args) => this._onError(...args)) + this.probeSocket.on('message', (...args) => this._onProbeMessage(...args)) + this.probeSocket.on('error', (...args) => this._onError(...args)) + + this.socket.setMaxListeners(2048) + this.probeSocket.setMaxListeners(2048) + + const listening = Promise.all([ + new Promise(resolve => this.socket.on('listening', resolve)), + new Promise(resolve => this.probeSocket.on('listening', resolve)) + ]) + + this.socket.bind(this.config.port || 0) + this.probeSocket.bind(this.config.probeInternalPort || 0) + + await listening + + this.config.port = this.socket.address().port + this.config.probeInternalPort = this.probeSocket.address().port + + if (this.onListening) this.onListening() + this.isListening = true + + debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) + } + + /* + * This method will bind the sockets, begin pinging known peers, and start + * the main program loop. + * @return {Any} + */ + async init (cb) { + if (!this.isListening) await this._listen() + if (cb) this.onReady = cb + + // tell all well-known peers that we would like to hear from them, if + // we hear from any we can ask for the reflection information we need. + for (const peer of this.peers.filter(p => p.indexed)) { + await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) + } + + this._mainLoop(Date.now()) + this.mainLoopTimer = this._setInterval(ts => this._mainLoop(ts), this.config.keepalive) + + if (this.indexed && this.onReady) return this.onReady(await this.getInfo()) + } + + /** + * Continuously evaluate the state of the peer and its network + * @return {undefined} + * @ignore + */ + async _mainLoop (ts) { + if (this.closing) return this._clearInterval(this.mainLoopTimer) + + // Node 21.x will need this... + // const offline = typeof globalThis.navigator.onLine !== 'undefined' && !globalThis.navigator.onLine + const offline = globalThis.navigator && !globalThis.navigator.onLine + if (offline) { + if (this.onConnecting) this.onConnecting({ code: -2, status: 'Offline' }) + return true + } + + if (!this.reflectionId) this.requestReflection() + if (this.onInterval) this.onInterval() + + this.uptime += this.config.keepalive + + // wait for nat type to be discovered + if (!NAT.isValid(this.natType)) return true + + for (const [k, packet] of [...this.cache.data]) { + const p = Packet.from(packet) + if (!p) continue + if (!p.timestamp) p.timestamp = ts + const clusterId = p.clusterId.toString('base64') + + const mult = this.clusters[clusterId] ? 2 : 1 + const ttl = (p.ttl < Packet.ttl) ? p.ttl : Packet.ttl * mult + const deadline = p.timestamp + ttl + + if (deadline <= ts) { + if (p.hops < this.maxHops) this.mcast(p) + this.cache.delete(k) + debug(this.peerId, '-- DELETE', k, this.cache.size) + if (this.onDelete) this.onDelete(p) + } + } + + for (let [k, v] of this.gate.entries()) { + v -= 1 + if (!v) this.gate.delete(k) + else this.gate.set(k, v) + } + + for (let [k, v] of this.returnRoutes.entries()) { + v -= 1 + if (!v) this.returnRoutes.delete(k) + else this.returnRoutes.set(k, v) + } + + // prune peer list + for (const [i, peer] of Object.entries(this.peers)) { + if (peer.indexed) continue + const expired = (peer.lastUpdate + this.config.keepalive) < Date.now() + if (expired) { // || !NAT.isValid(peer.natType)) { + const p = this.peers.splice(i, 1) + if (this.onDisconnect) this.onDisconnect(p) + continue + } + } + + // heartbeat + const { hash } = await this.cache.summarize('', this.cachePredicate) + for (const [, peer] of Object.entries(this.peers)) { + this.ping(peer, false, { + message: { + requesterPeerId: this.peerId, + natType: this.natType, + cacheSummaryHash: hash || null, + cacheSize: this.cache.size + } + }) + } + + // if this peer has previously tried to join any clusters, multicast a + // join messages for each into the network so we are always searching. + for (const cluster of Object.values(this.clusters)) { + for (const subcluster of Object.values(cluster)) { + this.join(subcluster.sharedKey, subcluster) + } + } + return true + } + + /** + * Enqueue packets to be sent to the network + * @param {Buffer} data - An encoded packet + * @param {number} port - The desination port of the remote host + * @param {string} address - The destination address of the remote host + * @param {Socket=this.socket} socket - The socket to send on + * @return {undefined} + * @ignore + */ + send (data, port, address, socket = this.socket) { + this.sendQueue.push({ data, port, address, socket }) + this._scheduleSend() + } + + /** + * @private + */ + _scheduleSend () { + if (this.sendTimeout) this._clearTimeout(this.sendTimeout) + this.sendTimeout = this._setTimeout(() => { this._dequeue() }) + } + + /** + * @private + */ + _dequeue () { + if (!this.sendQueue.length) return + const { data, port, address, socket } = this.sendQueue.shift() + + socket.send(data, port, address, err => { + if (this.sendQueue.length) this._scheduleSend() + if (err) return this._onError(err) + + const packet = Packet.decode(data) + if (!packet) return + + this.metrics.o[packet.type]++ + delete this.unpublished[packet.packetId.toString('hex')] + if (this.onSend && packet.type) this.onSend(packet, port, address) + debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) + }) + } + + /** + * Send any unpublished packets + * @return {undefined} + * @ignore + */ + async sendUnpublished () { + for (const [packetId] of Object.entries(this.unpublished)) { + const packet = this.cache.get(packetId) + + if (!packet) { // it may have been purged already + delete this.unpublished[packetId] + continue + } + + await this.mcast(packet) + debug(this.peerId, `-> RESEND (packetId=${packetId})`) + if (this.onState) await this.onState(this.getState()) + } + } + + /** + * Get the serializable state of the peer (can be passed to the constructor or create method) + * @return {undefined} + */ + async getState () { + this.config.clock = this.clock // save off the clock + + const peers = this.peers.map(p => { + p = { ...p } + delete p.localPeer + return p + }) + + return { + peers, + config: this.config, + data: [...this.cache.data.entries()], + unpublished: this.unpublished + } + } + + async getInfo () { + return { + address: this.address, + port: this.port, + clock: this.clock, + uptime: this.uptime, + natType: this.natType, + natName: NAT.toString(this.natType), + peerId: this.peerId + } + } + + async cacheInsert (packet) { + this.cache.insert(packet.packetId.toString('hex'), Packet.from(packet)) + } + + async addIndexedPeer (info) { + if (!info.peerId) throw new Error('options.peerId required') + if (!info.address) throw new Error('options.address required') + if (!info.port) throw new Error('options.port required') + info.indexed = true + this.peers.push(new RemotePeer(info)) + } + + async reconnect () { + this.lastUpdate = 0 + this.requestReflection() + } + + async disconnect () { + this.natType = null + this.reflectionStage = 0 + this.reflectionId = null + this.reflectionTimeout = null + this.probeReflectionTimeout = null + } + + async sealUnsigned (...args) { + return this.encryption.sealUnsigned(...args) + } + + async openUnsigned (...args) { + return this.encryption.openUnsigned(...args) + } + + async seal (...args) { + return this.encryption.seal(...args) + } + + async open (...args) { + return this.encryption.open(...args) + } + + async addEncryptionKey (...args) { + return this.encryption.add(...args) + } + + /** + * Get a selection of known peers + * @return {Array} + * @ignore + */ + getPeers (packet, peers, ignorelist, filter = o => o) { + const rand = () => Math.random() - 0.5 + + const base = p => { + if (ignorelist.findIndex(ilp => (ilp.port === p.port) && (ilp.address === p.address)) > -1) return false + if (p.lastUpdate === 0) return false + if (p.lastUpdate < Date.now() - (this.config.keepalive * 4)) return false + if (this.peerId === p.peerId) return false // same as me + if (packet.message.requesterPeerId === p.peerId) return false // same as requester - @todo: is this true in all cases? + if (!p.port || !NAT.isValid(p.natType)) return false + return true + } + + const candidates = peers + .filter(filter) + .filter(base) + .sort(rand) + + const list = candidates.slice(0, 3) + + if (!list.some(p => p.indexed)) { + const indexed = candidates.filter(p => p.indexed && !list.includes(p)) + if (indexed.length) list.push(indexed[0]) + } + + const clusterId = packet.clusterId.toString('base64') + const friends = candidates.filter(p => p.clusters && p.clusters[clusterId] && !list.includes(p)) + if (friends.length) { + list.unshift(friends[0]) + list.unshift(...candidates.filter(c => c.address === friends[0].address && c.peerId === friends[0].peerId)) + } + + return list + } + + /** + * Send an eventually consistent packet to a selection of peers (fanout) + * @return {undefined} + * @ignore + */ + async mcast (packet, ignorelist = []) { + const peers = this.getPeers(packet, this.peers, ignorelist) + const pid = packet.packetId.toString('hex') + + packet.hops += 1 + + for (const peer of peers) { + this.send(await Packet.encode(packet), peer.port, peer.address) + } + + if (this.onMulticast) this.onMulticast(packet) + if (this.gate.has(pid)) return + this.gate.set(pid, 1) + } + + /** + * The process of determining this peer's NAT behavior (firewall and dependentness) + * @return {undefined} + * @ignore + */ + async requestReflection () { + if (this.closing || this.indexed || this.reflectionId) { + debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) + return + } + + if (this.natType && (this.lastUpdate > 0 && (Date.now() - this.config.keepalive) < this.lastUpdate)) { + debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) + return + } + + debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) + if (this.onConnecting) this.onConnecting({ code: -1, status: `Entering reflection (lastUpdate ${Date.now() - this.lastUpdate}ms)` }) + + const peers = [...this.peers] + .filter(p => p.lastUpdate !== 0) + .filter(p => p.natType === NAT.UNRESTRICTED || p.natType === NAT.ADDR_RESTRICTED || p.indexed) + + if (peers.length < 2) { + if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) + debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') + + if (++this.reflectionRetry > 16) this.reflectionRetry = 1 + return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) + } + + this.reflectionRetry = 1 + + const requesterPeerId = this.peerId + const opts = { requesterPeerId, isReflection: true } + + this.reflectionId = opts.reflectionId = randomBytes(6).toString('hex').padStart(12, '0') + + if (this.onConnecting) { + this.onConnecting({ code: 0.5, status: `Found ${peers.length} elegible peers for reflection` }) + } + // + // # STEP 1 + // The purpose of this step is strictily to discover the external port of + // the probe socket. + // + if (this.reflectionStage === 0) { + if (this.onConnecting) this.onConnecting({ code: 1, status: 'Discover External Port' }) + // start refelection with an zeroed NAT type + if (this.reflectionTimeout) this._clearTimeout(this.reflectionTimeout) + this.reflectionStage = 1 + + debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) + const list = peers.filter(p => p.probed).sort(() => Math.random() - 0.5) + const peer = list.length ? list[0] : peers[0] + peer.probed = Date.now() // mark this peer as being used to provide port info + this.ping(peer, false, { message: { ...opts, isProbe: true } }, this.probeSocket) + + // we expect onMessageProbe to fire and clear this timer or it will timeout + this.probeReflectionTimeout = this._setTimeout(() => { + this.probeReflectionTimeout = null + if (this.reflectionStage !== 1) return + debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) + if (this.onConnecting) this.onConnecting({ code: 1, status: 'Timeout' }) + + this.reflectionStage = 1 + this.reflectionId = null + this.requestReflection() + }, 1024) + + debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) + return + } + + // + // # STEP 2 + // + // The purpose of step 2 is twofold: + // + // 1) ask two different peers for the external port and address for our primary socket. + // If they are different, we can determine that our NAT is a `ENDPOINT_DEPENDENT`. + // + // 2) ask the peers to also reply to our probe socket from their probe socket. + // These packets will both be dropped for `FIREWALL_ALLOW_KNOWN_IP_AND_PORT` and will both + // arrive for `FIREWALL_ALLOW_ANY`. If one packet arrives (which will always be from the peer + // which was previously probed), this indicates `FIREWALL_ALLOW_KNOWN_IP`. + // + if (this.reflectionStage === 1) { + this.reflectionStage = 2 + const { probeExternalPort } = this.config + if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Discover NAT' }) + + // peer1 is the most recently probed (likely the same peer used in step1) + // using the most recent guarantees that the the NAT mapping is still open + const peer1 = peers.filter(p => p.probed).sort((a, b) => b.probed - a.probed)[0] + + // peer has NEVER previously been probed + const peer2 = peers.filter(p => !p.probed).sort(() => Math.random() - 0.5)[0] + + if (!peer1 || !peer2) { + debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') + if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Insufficent Peers' }) + return this._setTimeout(() => this.requestReflection(), 256) + } + + debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) + + // reset reflection variables to defaults + this.nextNatType = NAT.UNKNOWN + this.reflectionFirstResponder = null + + this.ping(peer1, false, { message: { ...opts, probeExternalPort } }) + this.ping(peer2, false, { message: { ...opts, probeExternalPort } }) + + if (this.onConnecting) { + this.onConnecting({ code: 2, status: `Requesting reflection from ${peer1.address}` }) + this.onConnecting({ code: 2, status: `Requesting reflection from ${peer2.address}` }) + } + + if (this.reflectionTimeout) { + this._clearTimeout(this.reflectionTimeout) + this.reflectionTimeout = null + } + + this.reflectionTimeout = this._setTimeout(ts => { + this.reflectionTimeout = null + if (this.reflectionStage !== 2) return + if (this.onConnecting) this.onConnecting({ code: 2, status: 'Timeout' }) + this.reflectionStage = 1 + this.reflectionId = null + debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) + return this.requestReflection() + }, 2048) + } + } + + /** + * Ping another peer + * @return {PacketPing} + * @ignore + */ + async ping (peer, withRetry, props, socket) { + if (!peer) { + return + } + + props.message.requesterPeerId = this.peerId + props.message.uptime = this.uptime + props.message.timestamp = Date.now() + props.clusterId = this.config.clusterId + + const packet = new PacketPing(props) + const data = await Packet.encode(packet) + + const send = async () => { + if (this.closing) return false + + const p = this.peers.find(p => p.peerId === peer.peerId) + // if (p?.reflectionId && p.reflectionId === packet.message.reflectionId) { + // return false + // } + + this.send(data, peer.port, peer.address, socket) + if (p) p.lastRequest = Date.now() + } + + send() + + if (withRetry) { + this._setTimeout(send, PING_RETRY) + this._setTimeout(send, PING_RETRY * 4) + } + + return packet + } + + /** + * Get a peer + * @return {RemotePeer} + * @ignore + */ + getPeer (id) { + return this.peers.find(p => p.peerId === id) + } + + /** + * This should be called at least once when an app starts to multicast + * this peer, and starts querying the network to discover peers. + * @param {object} keys - Created by `Encryption.createKeyPair()`. + * @param {object=} args - Options + * @param {number=MAX_BANDWIDTH} args.rateLimit - How many requests per second to allow for this subclusterId. + * @return {RemotePeer} + */ + async join (sharedKey, args = { rateLimit: MAX_BANDWIDTH }) { + const keys = await Encryption.createKeyPair(sharedKey) + this.encryption.add(keys.publicKey, keys.privateKey) + + if (!this.port || !this.natType) return + + args.sharedKey = sharedKey + + const clusterId = args.clusterId || this.config.clusterId + const subclusterId = Buffer.from(keys.publicKey) + + const cid = clusterId?.toString('base64') + const scid = subclusterId?.toString('base64') + + this.clusters[cid] ??= {} + this.clusters[cid][scid] = args + + this.clock += 1 + + const packet = new PacketJoin({ + clock: this.clock, + clusterId, + subclusterId, + message: { + requesterPeerId: this.peerId, + natType: this.natType, + address: this.address, + port: this.port + } + }) + + debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) + if (this.onState) await this.onState(this.getState()) + + this.mcast(packet) + this.gate.set(packet.packetId.toString('hex'), 1) + } + + /** + * @param {Packet} T - The constructor to be used to create packets. + * @param {Any} message - The message to be split and packaged. + * @return {Array>} + * @ignore + */ + async _message2packets (T, message, args) { + const { clusterId, subclusterId, packet, nextId, meta = {}, usr1, usr2, sig } = args + + let messages = [message] + const len = message?.byteLength ?? message?.length ?? 0 + let clock = packet?.clock || 0 + + const siblings = [...this.cache.data.values()] + .filter(Boolean) + .filter(p => p?.previousId?.toString('hex') === packet?.packetId?.toString('hex')) + + if (siblings.length) { + // if there are siblings of the previous packet + // pick the highest clock value, the parent packet or the sibling + const sort = (a, b) => a.clock - b.clock + const sib = siblings.sort(sort).reverse()[0] + clock = Math.max(clock, sib.clock) + 1 + } + + clock += 1 + + if (len > 1024) { // Split packets that have messages bigger than Packet.maxLength + messages = [{ + meta, + ts: Date.now(), + size: message.length, + indexes: Math.ceil(message.length / 1024) + }] + let pos = 0 + while (pos < message.length) messages.push(message.slice(pos, pos += 1024)) + } + + // turn each message into an actual packet + const packets = messages.map(message => new T({ + ...args, + clusterId, + subclusterId, + clock, + message, + usr1, + usr2, + usr3: args.usr3, + usr4: args.usr4, + sig + })) + + if (packet) packets[0].previousId = packet.packetId + if (nextId) packets[packets.length - 1].nextId = nextId + + // set the .packetId (any maybe the .previousId and .nextId) + for (let i = 0; i < packets.length; i++) { + if (packets.length > 1) packets[i].index = i + + if (i === 0) { + packets[0].packetId = await sha256(packets[0].message, { bytes: true }) + } else { + // all fragments will have the same previous packetId + // the index is used to stitch them back together in order. + packets[i].previousId = packets[0].packetId + } + + if (packets[i + 1]) { + packets[i + 1].packetId = await sha256( + Buffer.concat([ + await sha256(packets[i].packetId, { bytes: true }), + await sha256(packets[i + 1].message, { bytes: true }) + ]), + { bytes: true } + ) + packets[i].nextId = packets[i + 1].packetId + } + } + + return packets + } + + /** + * Sends a packet into the network that will be replicated and buffered. + * Each peer that receives it will buffer it until TTL and then replicate + * it provided it has has not exceeded their maximum number of allowed hops. + * + * @param {object} keys - the public and private key pair created by `Encryption.createKeyPair()`. + * @param {object} args - The arguments to be applied. + * @param {Buffer} args.message - The message to be encrypted by keys and sent. + * @param {Packet=} args.packet - The previous packet in the packet chain. + * @param {Buffer} args.usr1 - 32 bytes of arbitrary clusterId in the protocol framing. + * @param {Buffer} args.usr2 - 32 bytes of arbitrary clusterId in the protocol framing. + * @return {Array} + */ + async publish (sharedKey, args) { // wtf to do here, we need subclusterId and the actual user keys + if (!sharedKey) throw new Error('.publish() expected "sharedKey" argument in first position') + if (!isBufferLike(args.message)) throw new Error('.publish() will only accept a message of type buffer') + + const keys = await Encryption.createKeyPair(sharedKey) + + args.subclusterId = Buffer.from(keys.publicKey) + args.clusterId = args.clusterId || this.config.clusterId + + const message = this.encryption.seal(args.message, keys) + const packets = await this._message2packets(PacketPublish, message, args) + + for (const packet of packets) { + this.cacheInsert(packet) + + if (this.onPacket) this.onPacket(packet, this.port, this.address, true) + + this.unpublished[packet.packetId.toString('hex')] = Date.now() + if (globalThis.navigator && !globalThis.navigator.onLine) continue + + this.mcast(packet) + } + + return packets + } + + /** + * @return {undefined} + */ + async sync (peer) { + if (typeof peer === 'string') { + peer = this.peers.find(p => p.peerId === peer) + } + + const rinfo = peer?.proxy || peer + + this.lastSync = Date.now() + const summary = await this.cache.summarize('', this.cachePredicate) + + debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) + if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) + + // if we are out of sync send our cache summary + const data = await Packet.encode(new PacketSync({ + message: Cache.encodeSummary(summary) + })) + + this.send(data, rinfo.port, rinfo.address, peer.socket) + } + + close () { + this._clearInterval(this.mainLoopTimer) + + if (this.closing) return + + this.closing = true + this.socket.close() + + if (this.onClose) this.onClose() + } + + /** + * Deploy a query into the network + * @return {undefined} + * + */ + async query (query) { + const packet = new PacketQuery({ + message: query, + usr1: Buffer.from(String(Date.now())), + usr3: Buffer.from(randomBytes(32)), + usr4: Buffer.from(String(1)) + }) + const data = await Packet.encode(packet) + + const p = Packet.decode(data) // finalize a packet + const pid = p.packetId.toString('hex') + + if (this.gate.has(pid)) return + this.returnRoutes.set(p.usr3.toString('hex'), {}) + this.gate.set(pid, 1) // don't accidentally spam + + debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) + + await this.mcast(p) + } + + /** + * + * This is a default implementation for deciding what to summarize + * from the cache when receiving a request to sync. that can be overridden + * + */ + cachePredicate (packet) { + return packet.version === VERSION && packet.timestamp > Date.now() - Packet.ttl + } + + /** + * A connection was made, add the peer to the local list of known + * peers and call the onConnection if it is defined by the user. + * + * @return {undefined} + * @ignore + */ + async _onConnection (packet, peerId, port, address, proxy, socket) { + if (this.closing) return + + const natType = packet.message.natType + + const { clusterId, subclusterId } = packet + + let peer = this.getPeer(peerId) + + if (!peer) { + peer = new RemotePeer({ peerId }) + + if (this.peers.length >= 256) { + // TODO evicting an older peer definitely needs some more thought. + const oldPeerIndex = this.peers.findIndex(p => !p.lastUpdate && !p.indexed) + if (oldPeerIndex > -1) this.peers.splice(oldPeerIndex, 1) + } + + this.peers.push(peer) + } + + peer.connected = true + peer.lastUpdate = Date.now() + peer.port = port + peer.natType = natType + peer.address = address + if (proxy) peer.proxy = proxy + if (socket) peer.socket = socket + + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') + + if (cid) peer.clusters[cid] ??= {} + + if (cid && scid) { + const cluster = peer.clusters[cid] + cluster[scid] = { rateLimit: MAX_BANDWIDTH } + } + + if (!peer.localPeer) peer.localPeer = this + if (!this.connections) this.connections = new Map() + + debug(this.peerId, '<- CONNECTION ( ' + + `peerId=${peer.peerId.slice(0, 6)}, ` + + `address=${address}:${port}, ` + + `type=${packet.type}, ` + + `cluster=${cid.slice(0, 8)}, ` + + `sub-cluster=${scid.slice(0, 8)})` + ) + + if (this.onJoin && this.clusters[cid]) { + this.onJoin(packet, peer, port, address) + } + + if (!this.connections.has(peer)) { + this.onConnection && this.onConnection(packet, peer, port, address) + this.connections.set(peer, packet.message.cacheSummaryHash) + } + } + + /** + * Received a Sync Packet + * @return {undefined} + * @ignore + */ + async _onSync (packet, port, address) { + this.metrics.i[packet.type]++ + + this.lastSync = Date.now() + const pid = packet.packetId?.toString('hex') + + if (!isBufferLike(packet.message)) return + if (this.gate.has(pid)) return + + this.gate.set(pid, 1) + + const remote = Cache.decodeSummary(packet.message) + const local = await this.cache.summarize(remote.prefix, this.cachePredicate) + + if (!remote || !remote.hash || !local || !local.hash || local.hash === remote.hash) { + if (this.onSyncFinished) this.onSyncFinished(packet, port, address) + return + } + + if (this.onSync) this.onSync(packet, port, address, { remote, local }) + + const remoteBuckets = remote.buckets.filter(Boolean).length + debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) + + for (let i = 0; i < local.buckets.length; i++) { + // + // nothing to send/sync, expect peer to send everything they have + // + if (!local.buckets[i] && !remote.buckets[i]) continue + + // + // you dont have any of these, im going to send them to you + // + if (!remote.buckets[i]) { + for (const [key, p] of this.cache.data.entries()) { + if (!key.startsWith(local.prefix + i.toString(16))) continue + + const packet = Packet.from(p) + if (!this.cachePredicate(packet)) continue + + const pid = packet.packetId.toString('hex') + debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) + + this.send(await Packet.encode(packet), port, address) + } + } else { + // + // need more details about what exactly isn't synce'd + // + const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate) + const data = await Packet.encode(new PacketSync({ + message: Cache.encodeSummary(nextLevel) + })) + this.send(data, port, address) + } + } + } + + /** + * Received a Query Packet + * + * a -> b -> c -> (d) -> c -> b -> a + * + * @return {undefined} + * @example + * + * ```js + * peer.onQuery = (packet) => { + * // + * // read a database or something + * // + * return { + * message: Buffer.from('hello'), + * publicKey: '', + * privateKey: '' + * } + * } + * ``` + */ + async _onQuery (packet, port, address) { + this.metrics.i[packet.type]++ + + const pid = packet.packetId.toString('hex') + if (this.gate.has(pid)) return + this.gate.set(pid, 1) + + const queryTimestamp = parseInt(Buffer.from(packet.usr1).toString(), 10) + const queryId = Buffer.from(packet.usr3).toString('hex') + const queryType = parseInt(Buffer.from(packet.usr4).toString(), 10) + + // if the timestamp in usr1 is older than now - 2s, bail + if (queryTimestamp < (Date.now() - 2048)) return + + const type = queryType === 1 ? 'question' : 'answer' + debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) + + let rinfo = { port, address } + + // + // receiving an answer + // + if (this.returnRoutes.has(queryId)) { + rinfo = this.returnRoutes.get(queryId) + + let p = packet.copy() + if (p.index > -1) p = await this.cache.compose(p) + + if (p?.index === -1) { + this.returnRoutes.delete(p.previousId.toString('hex')) + p.type = PacketPublish.type + delete p.usr3 + delete p.usr4 + if (this.onAnswer) return this.onAnswer(p.message, p, port, address) + } + + if (!rinfo.address) return + } else { + // + // receiving a query + // + this.returnRoutes.set(queryId, { address, port }) + + const query = packet.message + const packets = [] + + // + // The requestor is looking for an exact packetId. In this case, + // the peer has a packet with a previousId or nextId that points + // to a packetId they don't have. There is no need to specify the + // index in the query, split packets will have a nextId. + // + // if cache packet = { nextId: 'deadbeef...' } + // then query = { packetId: packet.nextId } + // or query = { packetId: packet.previousId } + // + if (query.packetId && this.cache.has(query.packetId)) { + const p = this.cache.get(query.packetId) + if (p) packets.push(p) + } else if (this.onQuery) { + const q = await this.onQuery(query) + if (q) packets.push(...await this._message2packets(PacketQuery, q.message, q)) + } + + if (packets.length) { + for (const p of packets) { + p.type = PacketQuery.type // convert the type during transport + p.usr3 = packet.usr3 // ensure the packet has the queryId + p.usr4 = Buffer.from(String(2)) // mark it as an answer packet + this.send(await Packet.encode(p), rinfo.port, rinfo.address) + } + return + } + } + + if (packet.hops >= this.maxHops) return + debug(this.peerId, '>> QUERY RELAY', port, address) + return await this.mcast(packet) + } + + /** + * Received a Ping Packet + * @return {undefined} + * @ignore + */ + async _onPing (packet, port, address) { + this.metrics.i[packet.type]++ + + this.lastUpdate = Date.now() + const { reflectionId, isReflection, isConnection, isHeartbeat } = packet.message + + if (packet.message.requesterPeerId === this.peerId) return + + const { probeExternalPort, isProbe, pingId } = packet.message + + if (isHeartbeat) { + // const peer = this.getPeer(packet.message.requesterPeerId) + // if (peer && natType) peer.natType = natType + return + } + + // if (peer && reflectionId) peer.reflectionId = reflectionId + if (!port) port = packet.message.port + if (!address) address = packet.message.address + + const message = { + cacheSize: this.cache.size, + uptime: this.uptime, + responderPeerId: this.peerId, + requesterPeerId: packet.message.requesterPeerId, + port, + isProbe, + address + } + + if (reflectionId) message.reflectionId = reflectionId + if (isHeartbeat) message.isHeartbeat = Date.now() + if (pingId) message.pingId = pingId + + if (isReflection) { + message.isReflection = true + message.port = port + message.address = address + } else { + message.natType = this.natType + } + + if (isConnection) { + const peerId = packet.message.requesterPeerId + this._onConnection(packet, peerId, port, address) + + message.isConnection = true + delete message.address + delete message.port + delete message.isProbe + } + + const { hash } = await this.cache.summarize('', this.cachePredicate) + message.cacheSummaryHash = hash + + const packetPong = new PacketPong({ message }) + const buf = await Packet.encode(packetPong) + + this.send(buf, port, address) + + if (probeExternalPort) { + message.port = probeExternalPort + const packetPong = new PacketPong({ message }) + const buf = await Packet.encode(packetPong) + this.send(buf, probeExternalPort, address, this.probeSocket) + } + } + + /** + * Received a Pong Packet + * @return {undefined} + * @ignore + */ + async _onPong (packet, port, address) { + this.metrics.i[packet.type]++ + + this.lastUpdate = Date.now() + + const { reflectionId, pingId, isReflection, responderPeerId } = packet.message + + debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) + const peer = this.getPeer(packet.message.responderPeerId) + + if (packet.message.isConnection) { + if (pingId) peer.pingId = pingId + this._onConnection(packet, packet.message.responderPeerId, port, address) + return + } + + if (!peer) return + + if (isReflection && !this.indexed) { + if (reflectionId !== this.reflectionId) return + + this._clearTimeout(this.reflectionTimeout) + + if (!this.reflectionFirstResponder) { + this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } + if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) + debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) + } else { + if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) + debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) + if (packet.message.address !== this.address) return + + this.nextNatType |= ( + packet.message.port === this.reflectionFirstResponder.packet.message.port + ) + ? NAT.MAPPING_ENDPOINT_INDEPENDENT + : NAT.MAPPING_ENDPOINT_DEPENDENT + + debug( + this.peerId, + `++ NAT REFLECT - STATE UPDATE (natType=${this.natType}, nextType=${this.nextNatType})`, + packet.message.port, + this.reflectionFirstResponder.packet.message.port + ) + + // wait PROBE_WAIT milliseconds for zero or more probe responses to arrive. + this._setTimeout(async () => { + // build the NAT type by combining information about the firewall with + // information about the endpoint independence + let natType = this.nextNatType + + // in the case where we recieved zero probe responses, we assume the firewall + // is of the hardest type 'FIREWALL_ALLOW_KNOWN_IP_AND_PORT'. + if (!NAT.isFirewallDefined(natType)) natType |= NAT.FIREWALL_ALLOW_KNOWN_IP_AND_PORT + + // if ((natType & NAT.MAPPING_ENDPOINT_DEPENDENT) === 1) natType = NAT.ENDPOINT_RESTRICTED + + if (NAT.isValid(natType)) { + // const oldType = this.natType + this.natType = natType + this.reflectionId = null + this.reflectionStage = 0 + + // if (natType !== oldType) { + // alert all connected peers of our new NAT type + for (const peer of this.peers) { + peer.lastRequest = Date.now() + + debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) + + await this.ping(peer, false, { + message: { + requesterPeerId: this.peerId, + natType: this.natType, + cacheSize: this.cache.size, + isConnection: true + } + }) + } + + if (this.onNat) this.onNat(this.natType) + + debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) + this.sendUnpublished() + // } + + if (this.onConnecting) this.onConnecting({ code: 3, status: `Discovered! (nat=${NAT.toString(this.natType)})` }) + if (this.onReady) this.onReady(await this.getInfo()) + } + + this.reflectionId = null + this.reflectionFirstResponder = null + }, PROBE_WAIT) + } + + this.address = packet.message.address + this.port = packet.message.port + debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) + } + } + + /** + * Received an Intro Packet + * @return {undefined} + * @ignore + */ + async _onIntro (packet, port, address, _, opts = { attempts: 0 }) { + this.metrics.i[packet.type]++ + if (this.closing) return + + const pid = packet.packetId.toString('hex') + // the packet needs to be gated, but should allow for attempt + // recursion so that the fallback can still be selected. + if (this.gate.has(pid) && opts.attempts === 0) return + this.gate.set(pid, 1) + + const ts = packet.usr1.length && Number(packet.usr1.toString()) + + if (packet.hops >= this.maxHops) return + if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return + if (packet.message.requesterPeerId === this.peerId) return // intro to myself? + if (packet.message.responderPeerId === this.peerId) return // intro from myself? + + // this is the peer that is being introduced to the new peers + const peerId = packet.message.requesterPeerId + const peerPort = packet.message.port + const peerAddress = packet.message.address + const natType = packet.message.natType + const { clusterId, subclusterId, clock } = packet + + // already introduced in the laste minute, just drop the packet + if (opts.attempts === 0 && this.gate.has(peerId + peerAddress + peerPort)) return + this.gate.set(peerId + peerAddress + peerPort, 2) + + // we already know this peer, and we're even connected to them! + let peer = this.getPeer(peerId) + if (!peer) peer = new RemotePeer({ peerId, natType, port: peerPort, address: peerAddress, clock, clusterId, subclusterId }) + if (peer.connected) return // already connected + if (clock > 0 && clock < peer.clock) return + peer.clock = clock + + // a mutex per inbound peer to ensure that it's not connecting concurrently, + // the check of the attempts ensures its allowed to recurse before failing so + // it can still fall back + if (this.gate.has('CONN' + peer.peerId) && opts.attempts === 0) return + this.gate.set('CONN' + peer.peerId, 1) + + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') + + debug(this.peerId, '<- INTRO (' + + `isRendezvous=${packet.message.isRendezvous}, ` + + `from=${address}:${port}, ` + + `to=${packet.message.address}:${packet.message.port}, ` + + `clustering=${cid.slice(0, 4)}/${scid.slice(0, 4)}` + + ')') + + if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) + + const pingId = Math.random().toString(16).slice(2) + const { hash } = await this.cache.summarize('', this.cachePredicate) + + const props = { + clusterId, + subclusterId, + message: { + natType: this.natType, + isConnection: true, + cacheSummaryHash: hash || null, + pingId: packet.message.pingId, + requesterPeerId: this.peerId + } + } + + const strategy = NAT.connectionStrategy(this.natType, packet.message.natType) + const proxyCandidate = this.peers.find(p => p.peerId === packet.message.responderPeerId) + + if (opts.attempts >= 2) { + this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) + return false + } + + this._setTimeout(() => { + if (this.getPeer(peer.peerId)) return + opts.attempts = 2 + this._onIntro(packet, port, address, _, opts) + }, 1024 * 2) + + if (packet.message.isRendezvous) { + debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) + } + + debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) + + if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { + debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) + + let i = 0 + if (!this.socketPool) { + this.socketPool = Array.from({ length: 256 }, (_, index) => { + return this.dgram().createSocket('udp4', null, this, index).unref() + }) + } + + // A probes 1 target port on B from 1024 source ports + // (this is 1.59% of the search clusterId) + // B probes 256 target ports on A from 1 source port + // (this is 0.40% of the search clusterId) + // + // Probability of successful traversal: 98.35% + // + const interval = this._setInterval(async () => { + // send messages until we receive a message from them. giveup after sending ±1024 + // packets and fall back to using the peer that sent this as the initial proxy. + if (i++ >= 1024) { + this._clearInterval(interval) + + opts.attempts++ + this._onIntro(packet, port, address, _, opts) + return false + } + + const p = { + clusterId, + subclusterId, + message: { + requesterPeerId: this.peerId, + cacheSummaryHash: hash || null, + natType: this.natType, + uptime: this.uptime, + isConnection: true, + timestamp: Date.now(), + pingId + } + } + + const data = await Packet.encode(new PacketPing(p)) + + const rand = () => Math.random() - 0.5 + const pooledSocket = this.socketPool.sort(rand).find(s => !s.active) + if (!pooledSocket) return // TODO recover from exausted socket pool + + // mark socket as active & deactivate it after timeout + pooledSocket.active = true + pooledSocket.reclaim = this._setTimeout(() => { + pooledSocket.active = false + pooledSocket.removeAllListeners() + }, 1024) + + pooledSocket.on('message', async (msg, rinfo) => { + // if (rinfo.port !== peerPort || rinfo.address !== peerAddress) return + + // cancel scheduled events + this._clearInterval(interval) + this._clearTimeout(pooledSocket.reclaim) + + // remove any events currently bound on the socket + pooledSocket.removeAllListeners() + pooledSocket.on('message', (msg, rinfo) => { + this._onMessage(msg, rinfo) + }) + + this._onConnection(packet, peer.peerId, rinfo.port, rinfo.address, undefined, pooledSocket) + + const p = { + clusterId, + subclusterId, + clock: this.clock, + message: { + requesterPeerId: this.peerId, + natType: this.natType, + isConnection: true + } + } + + const data = await Packet.encode(new PacketPing(p)) + + pooledSocket.send(data, rinfo.port, rinfo.address) + + // create a new socket to replace it in the pool + const oldIndex = this.socketPool.findIndex(s => s === pooledSocket) + this.socketPool[oldIndex] = this.dgram().createSocket('udp4', null, this).unref() + + this._onMessage(msg, rinfo) + }) + + try { + pooledSocket.send(data, peerPort, peerAddress) + } catch (err) { + console.error('STRATEGY_TRAVERSAL_CONNECT error', err) + } + }, 10) + + return + } + + if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { + // TODO could allow multiple proxies + this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) + debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') + } + + if (strategy === NAT.STRATEGY_TRAVERSAL_OPEN) { + peer.opening = Date.now() + + const portsCache = new Set() + + if (!this.bdpCache.length) { + globalThis.bdpCache = this.bdpCache = Array.from({ length: 1024 }, () => getRandomPort(portsCache)) + } + + for (const port of this.bdpCache) { + this.send(Buffer.from([0x1]), port, packet.message.address) + } + + return + } + + if (strategy === NAT.STRATEGY_DIRECT_CONNECT) { + debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') + } + + if (strategy === NAT.STRATEGY_DEFER) { + debug(this.peerId, '++ NAT STRATEGY_DEFER') + } + + this.ping(peer, true, props) + } + + /** + * Received an Join Packet + * @return {undefined} + * @ignore + */ + async _onJoin (packet, port, address, data) { + this.metrics.i[packet.type]++ + + const pid = packet.packetId.toString('hex') + if (packet.message.requesterPeerId === this.peerId) return + if (this.gate.has(pid)) return + if (packet.clusterId.length !== 32) return + + this.lastUpdate = Date.now() + + const peerId = packet.message.requesterPeerId + const rendezvousDeadline = packet.message.rendezvousDeadline + const clusterId = packet.clusterId + const subclusterId = packet.subclusterId + const peerAddress = packet.message.address + const peerPort = packet.message.port + + // prevents premature pruning; a peer is not directly connecting + const peer = this.peers.find(p => p.peerId === peerId) + if (peer) peer.lastUpdate = Date.now() + + // a rendezvous isn't relevant if it's too old, just drop the packet + if (rendezvousDeadline && rendezvousDeadline < Date.now()) return + + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') + + debug(this.peerId, '<- JOIN (' + + `peerId=${peerId.slice(0, 6)}, ` + + `clock=${packet.clock}, ` + + `hops=${packet.hops}, ` + + `clusterId=${cid}, ` + + `subclusterId=${scid}, ` + + `address=${address}:${port})` + ) + + // + // This packet represents a peer who wants to join the network and is a + // member of our cluster. The packet was replicated though the network + // and contains the details about where the peer can be reached, in this + // case we want to ping that peer so we can be introduced to them. + // + if (rendezvousDeadline && !this.indexed && this.clusters[cid]) { + if (!packet.message.rendezvousRequesterPeerId) { + const pid = packet.packetId.toString('hex') + this.gate.set(pid, 2) + + // TODO it would tighten up the transition time between dropped peers + // if we check strategy from (packet.message.natType, this.natType) and + // make introductions that create more mutually known peers. + debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) + + const data = await Packet.encode(new PacketJoin({ + clock: packet.clock, + subclusterId: packet.subclusterId, + clusterId: packet.clusterId, + message: { + requesterPeerId: this.peerId, + natType: this.natType, + address: this.address, + port: this.port, + rendezvousType: packet.message.natType, + rendezvousRequesterPeerId: packet.message.requesterPeerId + } + })) + + this.send( + data, + packet.message.rendezvousPort, + packet.message.rendezvousAddress + ) + } + } + + const filter = p => ( + p.connected && // you can't intro peers who aren't connected + p.peerId !== packet.message.requesterPeerId && + p.peerId !== packet.message.rendezvousRequesterPeerId && + !p.indexed + ) + + let peers = this.getPeers(packet, this.peers, [{ port, address }], filter) + + // + // A peer who belongs to the same cluster as the peer who's replicated + // join was discovered, sent us a join that has a specification for who + // they want to be introduced to. + // + if (packet.message.rendezvousRequesterPeerId && this.peerId === packet.message.rendezvousPeerId) { + const peer = this.peers.find(p => p.peerId === packet.message.rendezvousRequesterPeerId) + + if (!peer) { + debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) + return + } + + // peer.natType = packet.message.rendezvousType + peers = [peer] + + debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + } + + for (const peer of peers) { + const message1 = { + requesterPeerId: peer.peerId, + responderPeerId: this.peerId, + isRendezvous: !!packet.message.rendezvousPeerId, + natType: peer.natType, + address: peer.address, + port: peer.port + } + + const message2 = { + requesterPeerId: packet.message.requesterPeerId, + responderPeerId: this.peerId, + isRendezvous: !!packet.message.rendezvousPeerId, + natType: packet.message.natType, + address: packet.message.address, + port: packet.message.port + } + + const opts = { + hops: packet.hops + 1, + clusterId, + subclusterId, + usr1: String(Date.now()) + } + + const intro1 = await Packet.encode(new PacketIntro({ ...opts, message: message1 })) + const intro2 = await Packet.encode(new PacketIntro({ ...opts, message: message2 })) + + // + // Send intro1 to the peer described in the message + // Send intro2 to the peer in this loop + // + debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) + debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + + peer.lastRequest = Date.now() + + this.send(intro2, peer.port, peer.address) + this.send(intro1, packet.message.port, packet.message.address) + + this.gate.set(Packet.decode(intro1).packetId.toString('hex'), 2) + this.gate.set(Packet.decode(intro2).packetId.toString('hex'), 2) + } + + this.gate.set(packet.packetId.toString('hex'), 2) + + if (packet.hops >= this.maxHops) return + if (this.indexed && !packet.clusterId) return + + if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED && !packet.message.rendezvousDeadline) { + packet.message.rendezvousAddress = this.address + packet.message.rendezvousPort = this.port + packet.message.rendezvousType = this.natType + packet.message.rendezvousPeerId = this.peerId + packet.message.rendezvousDeadline = Date.now() + this.config.keepalive + } + + debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) + this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) + + if (packet.hops <= 1) { + this._onConnection(packet, packet.message.requesterPeerId, port, address) + } + } + + /** + * Received an Publish Packet + * @return {undefined} + * @ignore + */ + async _onPublish (packet, port, address, data) { + this.metrics.i[packet.type]++ + + // only cache if this packet if i am part of this subclusterId + // const cluster = this.clusters[packet.clusterId] + // if (cluster && cluster[packet.subclusterId]) { + + const pid = packet.packetId.toString('hex') + if (this.cache.has(pid)) { + debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) + return + } + + debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) + this.cacheInsert(packet) + + const ignorelist = [{ address, port }] + const scid = packet.subclusterId.toString('base64') + + if (!this.indexed && this.encryption.has(scid)) { + let p = packet.copy() + if (p.index > -1) p = await this.cache.compose(p) + if (p?.index === -1 && this.onPacket) this.onPacket(p, port, address) + } + + if (packet.hops >= this.maxHops) return + this.mcast(packet, ignorelist) + + // } + } + + /** + * Received an Stream Packet + * @return {undefined} + * @ignore + */ + async _onStream (packet, port, address, data) { + this.metrics.i[packet.type]++ + + const pid = packet.packetId.toString('hex') + if (this.gate.has(pid)) return + this.gate.set(pid, 1) + + const streamTo = packet.usr3.toString('hex') + const streamFrom = packet.usr4.toString('hex') + + // only help packets with a higher hop count if they are in our cluster + // if (packet.hops > 2 && !this.clusters[packet.cluster]) return + + const peerFrom = this.peers.find(p => p.peerId.toString('hex') === streamFrom.toString('hex')) + if (!peerFrom) return + + // stream message is for this peer + if (streamTo.toString('hex') === this.peerId.toString('hex')) { + const scid = packet.subclusterId.toString('base64') + + if (this.encryption.has(scid)) { + let p = packet.copy() // clone the packet so it's not modified + + if (packet.index > -1) { // if it needs to be composed... + p.timestamp = Date.now() + this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial + + p = await this.cache.compose(p, this.streamBuffer) // try to compose + if (!p) return // could not compose + + if (p) { // if successful, delete the artifacts + const previousId = p.index === 0 ? p.packetId : p.previousId + const pid = previousId.toString('hex') + + this.streamBuffer.forEach((v, k) => { + if (k === pid) this.streamBuffer.delete(k) + if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) + }) + } + } + + if (this.onStream) this.onStream(p, peerFrom, port, address) + } + + return + } + + // stream message is for another peer + const peerTo = this.peers.find(p => p.peerId === streamTo) + if (!peerTo) { + debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) + return + } + + if (packet.hops >= this.maxHops) { + debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) + return + } + + debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) + this.send(await Packet.encode(packet), peerTo.port, peerTo.address) + if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) + } + + /** + * Received any packet on the probe port to determine the firewall: + * are you port restricted, host restricted, or unrestricted. + * @return {undefined} + * @ignore + */ + _onProbeMessage (data, { port, address }) { + this._clearTimeout(this.probeReflectionTimeout) + + const packet = Packet.decode(data) + if (!packet || packet.version !== VERSION) return + if (packet?.type !== 2) return + + const pid = packet.packetId.toString('hex') + if (this.gate.has(pid)) return + this.gate.set(pid, 1) + + const { reflectionId } = packet.message + debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) + + if (this.onProbe) this.onProbe(data, port, address) + if (this.reflectionId !== reflectionId || !this.reflectionId) return + + // reflection stage is encoded in the last hex char of the reflectionId, or 0 if not available. + // const reflectionStage = reflectionId ? parseInt(reflectionId.slice(-1), 16) : 0 + + if (this.reflectionStage === 1) { + debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) + if (!packet.message?.port) return // message must include a port number + + // successfully discovered the probe socket external port + this.config.probeExternalPort = packet.message.port + + // move to next reflection stage + this.reflectionStage = 1 + this.reflectionId = null + this.requestReflection() + return + } + + if (this.reflectionStage === 2) { + debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) + + // if we have previously sent an outbount message to this peer on the probe port + // then our NAT will have a mapping for their IP, but not their IP+Port. + if (!NAT.isFirewallDefined(this.nextNatType)) { + this.nextNatType |= NAT.FIREWALL_ALLOW_KNOWN_IP + debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) + } else { + this.nextNatType |= NAT.FIREWALL_ALLOW_ANY + debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) + } + + // wait for all messages to arrive + } + } + + /** + * When a packet is received it is decoded, the packet contains the type + * of the message. Based on the message type it is routed to a function. + * like WebSockets, don't answer queries unless we know its another SRP peer. + * + * @param {Buffer|Uint8Array} data + * @param {{ port: number, address: string }} info + */ + async _onMessage (data, { port, address }) { + const packet = Packet.decode(data) + if (!packet || packet.version !== VERSION) return + + const peer = this.peers.find(p => p.address === address && p.port === port) + if (peer) peer.lastUpdate = Date.now() + + const cid = packet.clusterId.toString('base64') + const scid = packet.subclusterId.toString('base64') + + // debug('<- PACKET', packet.type, port, address) + const clusters = this.clusters[cid] + const subcluster = clusters && clusters[scid] + + if (!this.config.limitExempt) { + if (rateLimit(this.rates, packet.type, port, address, subcluster)) { + debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) + this.metrics.i.REJECTED++ + return + } + if (this.onLimit && !this.onLimit(packet, port, address)) return + } + + const args = [packet, port, address, data] + + if (this.firewall) if (!this.firewall(...args)) return + if (this.onData) this.onData(...args) + + switch (packet.type) { + case PacketPing.type: return this._onPing(...args) + case PacketPong.type: return this._onPong(...args) + } + + if (!this.natType && !this.indexed) return + + switch (packet.type) { + case PacketIntro.type: return this._onIntro(...args) + case PacketJoin.type: return this._onJoin(...args) + case PacketPublish.type: return this._onPublish(...args) + case PacketStream.type: return this._onStream(...args) + case PacketSync.type: return this._onSync(...args) + case PacketQuery.type: return this._onQuery(...args) + } + } +} + +export default Peer diff --git a/api/latica/nat.js b/api/latica/nat.js new file mode 100644 index 0000000000..327ecc377b --- /dev/null +++ b/api/latica/nat.js @@ -0,0 +1,185 @@ +/** + * The NAT type is encoded using 5 bits: + * + * 0b00001 : the lsb indicates if endpoint dependence information is included + * 0b00010 : the second bit indicates the endpoint dependence value + * + * 0b00100 : the third bit indicates if firewall information is included + * 0b01000 : the fourth bit describes which requests can pass the firewall, only known IPs (0) or any IP (1) + * 0b10000 : the fifth bit describes which requests can pass the firewall, only known ports (0) or any port (1) + */ + +/** + * Every remote will see the same IP:PORT mapping for this peer. + * + * :3333 ┌──────┐ + * :1111 ┌───▶ │ R1 │ + * ┌──────┐ ┌───────┐ │ └──────┘ + * │ P1 ├───▶│ NAT ├──┤ + * └──────┘ └───────┘ │ ┌──────┐ + * └───▶ │ R2 │ + * :3333 └──────┘ + */ +export const MAPPING_ENDPOINT_INDEPENDENT = 0b00011 + +/** + * Every remote will see a different IP:PORT mapping for this peer. + * + * :4444 ┌──────┐ + * :1111 ┌───▶ │ R1 │ + * ┌──────┐ ┌───────┐ │ └──────┘ + * │ P1 ├───▶│ NAT ├──┤ + * └──────┘ └───────┘ │ ┌──────┐ + * └───▶ │ R2 │ + * :5555 └──────┘ + */ +export const MAPPING_ENDPOINT_DEPENDENT = 0b00001 + +/** + * The firewall allows the port mapping to be accessed by: + * - Any IP:PORT combination (FIREWALL_ALLOW_ANY) + * - Any PORT on a previously connected IP (FIREWALL_ALLOW_KNOWN_IP) + * - Only from previously connected IP:PORT combinations (FIREWALL_ALLOW_KNOWN_IP_AND_PORT) + */ +export const FIREWALL_ALLOW_ANY = 0b11100 +export const FIREWALL_ALLOW_KNOWN_IP = 0b01100 +export const FIREWALL_ALLOW_KNOWN_IP_AND_PORT = 0b00100 + +/** + * The initial state of the nat is unknown and its value is 0 + */ +export const UNKNOWN = 0 + +/** + * Full-cone NAT, also known as one-to-one NAT + * + * Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. + * + * @summary its a packet party at this mapping and everyone's invited + */ +export const UNRESTRICTED = (FIREWALL_ALLOW_ANY | MAPPING_ENDPOINT_INDEPENDENT) + +/** + * (Address)-restricted-cone NAT + * + * An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only + * if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter. + * + * @summary The NAT will drop your packets unless a peer within its network has previously messaged you from *any* port. + */ +export const ADDR_RESTRICTED = (FIREWALL_ALLOW_KNOWN_IP | MAPPING_ENDPOINT_INDEPENDENT) + +/** + * Port-restricted cone NAT + * + * An external host (hAddr:hPort) can send packets to iAddr:iPort by sending + * packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to + * hAddr:hPort. + * + * @summary The NAT will drop your packets unless a peer within its network + * has previously messaged you from this *specific* port. + */ +export const PORT_RESTRICTED = (FIREWALL_ALLOW_KNOWN_IP_AND_PORT | MAPPING_ENDPOINT_INDEPENDENT) + +/** + * Symmetric NAT + * + * Only an external host that receives a packet from an internal host can send + * a packet back. + * + * @summary The NAT will only accept replies to a correspondence initialized + * by itself, the mapping it created is only valid for you. + */ +export const ENDPOINT_RESTRICTED = (FIREWALL_ALLOW_KNOWN_IP_AND_PORT | MAPPING_ENDPOINT_DEPENDENT) + +/** + * Returns true iff a valid MAPPING_ENDPOINT_* flag has been added (indicated by the lsb). + */ +export const isEndpointDependenceDefined = nat => (nat & 0b00001) === 0b00001 + +/** + * Returns true iff a valid FIREWALL_ALLOW_* flag has been added (indicated by the third bit). + */ +export const isFirewallDefined = nat => (nat & 0b00100) === 0b00100 + +/** + * Returns true iff both FIREWALL_ALLOW_* and MAPPING_ENDPOINT_* flags have been added. + */ +export const isValid = nat => isEndpointDependenceDefined(nat) && isFirewallDefined(nat) + +/** + * toString returns a human-readable label for the NAT enum + */ +export const toString = n => { + switch (n) { + case UNRESTRICTED: return 'UNRESTRICTED' + case ADDR_RESTRICTED: return 'ADDR_RESTRICTED' + case PORT_RESTRICTED: return 'PORT_RESTRICTED' + case ENDPOINT_RESTRICTED: return 'ENDPOINT_RESTRICTED' + default: return 'UNKNOWN' + } +} + +/** + * toStringStrategy returns a human-readable label for the STRATEGY enum + */ +export const toStringStrategy = n => { + switch (n) { + case STRATEGY_DEFER: return 'STRATEGY_DEFER' + case STRATEGY_DIRECT_CONNECT: return 'STRATEGY_DIRECT_CONNECT' + case STRATEGY_TRAVERSAL_OPEN: return 'STRATEGY_TRAVERSAL_OPEN' + case STRATEGY_TRAVERSAL_CONNECT: return 'STRATEGY_TRAVERSAL_CONNECT' + case STRATEGY_PROXY: return 'STRATEGY_PROXY' + default: return 'STRATEGY_UNKNOWN' + } +} + +export const STRATEGY_DEFER = 0 // do nothing and let the other side initialize the connection +export const STRATEGY_DIRECT_CONNECT = 1 // simply connect +export const STRATEGY_TRAVERSAL_OPEN = 2 // spam random ports and listen for replies +export const STRATEGY_TRAVERSAL_CONNECT = 3 // spam random ports but dont bother listening +export const STRATEGY_PROXY = 4 // use a third-party proxy + +/** + * ConnectionStrategy returns the best strategy to use when attempting to connect from a->b. + */ +export const connectionStrategy = (a, b) => { + switch (b) { + // b always accepts connections + case UNRESTRICTED: return STRATEGY_DIRECT_CONNECT + + // b only accepts connections from an IP it has previously communicated with + case ADDR_RESTRICTED: { + switch (a) { + case UNRESTRICTED: return STRATEGY_DEFER + case ADDR_RESTRICTED: return STRATEGY_DIRECT_CONNECT // both sides attempt, one will succeed + case PORT_RESTRICTED: return STRATEGY_DIRECT_CONNECT // a is hinting, b is guessing + case ENDPOINT_RESTRICTED: return STRATEGY_DIRECT_CONNECT // a is hinting, b is guessing + } + break + } + + // b only accepts connections from an IP:PORT it has previously communicated with + case PORT_RESTRICTED: { + switch (a) { + case UNRESTRICTED: return STRATEGY_DEFER + case ADDR_RESTRICTED: return STRATEGY_DIRECT_CONNECT // a is guessing, b is hinting + case PORT_RESTRICTED: return STRATEGY_DIRECT_CONNECT // both guess, will take too long, most resign to proxying + case ENDPOINT_RESTRICTED: return STRATEGY_TRAVERSAL_CONNECT // try connecting + } + break + } + + // b only accepts replies to its own messages + case ENDPOINT_RESTRICTED: { + switch (a) { + case UNRESTRICTED: return STRATEGY_DEFER + case ADDR_RESTRICTED: return STRATEGY_DIRECT_CONNECT // the 3 successive packets will penetrate + case PORT_RESTRICTED: return STRATEGY_TRAVERSAL_OPEN // open up some ports + case ENDPOINT_RESTRICTED: return STRATEGY_PROXY // unroutable + } + } + } + + return STRATEGY_PROXY +} diff --git a/api/latica/packets.js b/api/latica/packets.js new file mode 100644 index 0000000000..c46ac43b50 --- /dev/null +++ b/api/latica/packets.js @@ -0,0 +1,496 @@ +import { randomBytes } from '../crypto.js' +import { isBufferLike } from '../util.js' +import { Buffer } from '../buffer.js' +import { debug } from './index.js' + +/** + * Hash function factory. + * @return {function(object|Buffer|string): Promise} + */ +function createHashFunction () { + const encoder = new TextEncoder() + let crypto = null + + if (!globalThis.process?.versions?.node) { + if (!crypto) crypto = globalThis.crypto?.subtle + + return async (seed, opts = {}) => { + const encoding = opts.encoding || 'hex' + const bytes = opts.bytes + + if (typeof seed === 'object' && !isBufferLike(seed)) { + seed = JSON.stringify(seed) + } + + if (seed && typeof seed === 'string') { + seed = encoder.encode(seed) + } + + let value + + if (seed) { + value = Buffer.from(await crypto.digest('SHA-256', seed)) + if (bytes) return value + return value.toString(encoding) + } + + value = Buffer.from(randomBytes(32)) + if (opts.bytes) return value + return value.toString(encoding) + } + } + + return async (seed, opts = {}) => { + const encoding = opts.encoding || 'hex' + const bytes = opts.bytes + + if (typeof seed === 'object' && !isBufferLike(seed)) { + seed = JSON.stringify(seed) + } + + if (!crypto) { // eslint-disable-next-line + crypto = await new Function('return import(\'crypto\')')() + } + + let value + + if (seed) { + value = crypto.createHash('sha256').update(seed) + if (bytes) return Buffer.from(value.digest(encoding), encoding) + return value.digest(encoding) + } + + value = randomBytes(32) + if (opts.bytes) return value + return value.toString(encoding) + } +} + +const getMethod = (type, bytes, isSigned) => { + const bits = bytes << 3 + const isBigEndian = bits === 8 ? '' : 'BE' + + if (![8, 16, 32].includes(bits)) { + throw new Error(`${bits} is invalid, expected 8, 16, or 32`) + } + + return `${type}${isSigned ? '' : 'U'}Int${bits}${isBigEndian}` +} + +/** + * The magic bytes prefixing every packet. They are the + * 2nd, 3rd, 5th, and 7th, prime numbers. + * @type {number[]} + */ +export const MAGIC_BYTES_PREFIX = [0x03, 0x05, 0x0b, 0x11] + +/** + * The version of the protocol. + */ +export const VERSION = 6 + +/** + * The size in bytes of the prefix magic bytes. + */ +export const MAGIC_BYTES = 4 + +/** + * The maximum size of the user message. + */ +export const MESSAGE_BYTES = 1024 + +/** + * The cache TTL in milliseconds. + */ +export const CACHE_TTL = 60_000 * 60 * 6 + +export const PACKET_SPEC = { + type: { bytes: 1, encoding: 'number' }, + version: { bytes: 2, encoding: 'number', default: VERSION }, + clock: { bytes: 4, encoding: 'number', default: 1 }, + hops: { bytes: 4, encoding: 'number', default: 0 }, + index: { bytes: 4, encoding: 'number', default: -1, signed: true }, + ttl: { bytes: 4, encoding: 'number', default: CACHE_TTL }, + clusterId: { bytes: 32, encoding: 'base64', default: [0b0] }, + subclusterId: { bytes: 32, encoding: 'base64', default: [0b0] }, + previousId: { bytes: 32, encoding: 'hex', default: [0b0] }, + packetId: { bytes: 32, encoding: 'hex', default: [0b0] }, + nextId: { bytes: 32, encoding: 'hex', default: [0b0] }, + usr1: { bytes: 32, default: [0b0] }, + usr2: { bytes: 32, default: [0b0] }, + usr3: { bytes: 32, default: [0b0] }, + usr4: { bytes: 32, default: [0b0] }, + message: { bytes: 1024, default: [0b0] }, + sig: { bytes: 64, default: [0b0] } +} + +let FRAME_BYTES = MAGIC_BYTES + +for (const spec of Object.values(PACKET_SPEC)) { + FRAME_BYTES += spec.bytes +} + +/** + * The size in bytes of the total packet frame and message. + */ +export const PACKET_BYTES = FRAME_BYTES + MESSAGE_BYTES + +/** + * The maximum distance that a packet can be replicated. + */ +export const MAX_HOPS = 16 + +/** + * @param {object} message + * @param {{ [key: string]: { required: boolean, type: string }}} constraints + */ +export const validatePacket = (o, constraints) => { + if (!o) throw new Error('Expected object') + const allowedKeys = Object.keys(constraints) + const actualKeys = Object.keys(o) + const unknown = actualKeys.filter(k => allowedKeys.indexOf(k) === -1) + if (unknown.length) throw new Error(`Unexpected keys [${unknown}]`) + + for (const [key, con] of Object.entries(constraints)) { + if (con.required && !o[key]) { + debug(new Error(`${key} is required (${JSON.stringify(o, null, 2)})`), JSON.stringify(o)) + } + + const type = ({}).toString.call(o[key]).slice(8, -1).toLowerCase() + + if (o[key] && type !== con.type) { + debug(`expected .${key} to be of type ${con.type}, got ${type} in packet.. ` + JSON.stringify(o)) + } + } +} + +/** + * Used to store the size of each field + */ +const SIZE = 2 + +const isEncodedAsJSON = ({ type, index }) => ( + type === PacketPing.type || + type === PacketPong.type || + type === PacketJoin.type || + type === PacketIntro.type || + type === PacketQuery.type || + index === 0 +) + +/** + * Computes a SHA-256 hash of input returning a hex encoded string. + * @type {function(string|Buffer|Uint8Array): Promise} + */ +export const sha256 = createHashFunction() + +/** + * Decodes `buf` into a `Packet`. + * @param {Buffer} buf + * @return {Packet} + */ +export const decode = buf => { + if (!Packet.isPacket(buf)) return null + + buf = buf.slice(MAGIC_BYTES) + + const o = new Packet() + let offset = 0 + + for (const [k, spec] of Object.entries(PACKET_SPEC)) { + o[k] = spec.default + + try { + if (spec.encoding === 'number') { + const method = getMethod('read', spec.bytes, spec.signed) + o[k] = buf[method](offset) + offset += spec.bytes + continue + } + + const size = buf.readUInt16BE(offset) + offset += SIZE + let value = buf.slice(offset, offset + size) + offset += size + + if (spec.bytes && size > spec.bytes) return null + + if (spec.encoding === 'hex') value = Buffer.from(value, 'hex') + if (spec.encoding === 'base64') value = Buffer.from(value, 'base64') + if (spec.encoding === 'utf8') value = value.toString() + + if (k === 'message' && isEncodedAsJSON(o)) { + try { value = JSON.parse(value.toString()) } catch {} + } + + o[k] = value + } catch (err) { + return null // completely bail + } + } + + return o +} + +export const getTypeFromBytes = (buf) => buf.byteLength > 4 ? buf.readUInt8(4) : 0 + +export class Packet { + static ttl = CACHE_TTL + static maxLength = MESSAGE_BYTES + + /** + * Returns an empty `Packet` instance. + * @return {Packet} + */ + static empty () { + return new this() + } + + /** + * @param {Packet|object} packet + * @return {Packet} + */ + static from (packet) { + if (packet instanceof Packet) return packet + return new Packet(packet) + } + + /** + * @param {Packet} packet + * @return {Packet} + */ + copy () { + return Object.assign(new Packet({}), this) + } + + /** + * Determines if input is a packet. + * @param {Buffer|Uint8Array|number[]|object|Packet} packet + * @return {boolean} + */ + static isPacket (packet) { + if (isBufferLike(packet) || Array.isArray(packet)) { + const prefix = Buffer.from(packet).slice(0, MAGIC_BYTES) + const magic = Buffer.from(MAGIC_BYTES_PREFIX) + return magic.compare(prefix) === 0 + } else if (packet && typeof packet === 'object') { + // check if every key on `Packet` exists in `packet` + return Object.keys(PACKET_SPEC).every(k => k in packet) + } + + return false + } + + /** + * `Packet` class constructor. + * @param {Packet|object?} options + */ + constructor (options = {}) { + for (const [k, v] of Object.entries(PACKET_SPEC)) { + this[k] = typeof options[k] === 'undefined' + ? v.default + : options[k] + + if (Array.isArray(this[k]) || ArrayBuffer.isView(this[k])) { + this[k] = Buffer.from(this[k]) + } + } + + // extras that might not come over the wire + this.timestamp = options.timestamp || Date.now() + this.isComposed = options.isComposed || false + this.isReconciled = options.isReconciled || false + this.meta = options.meta || {} + } + + /** + */ + static async encode (p) { + p = { ...p } + + const buf = Buffer.alloc(PACKET_BYTES) // buf length bust be < UDP MTU (usually ~1500) + if (!p.message) return buf + + const isBuffer = isBufferLike(p.message) + const isObject = typeof p.message === 'object' + + if (p.index <= 0 && isObject && !isBuffer) { + p.message = JSON.stringify(p.message) + } else if (p.index <= 0 && typeof p !== 'string' && !isBuffer) { + p.message = String(p.message) + } + + if (p.message?.length > Packet.MESSAGE_BYTES) throw new Error('ETOOBIG') + + // we only have p.nextId when we know ahead of time, if it's empty that's fine. + if (p.packetId.length === 1 && p.packetId[0] === 0) { + const bufs = [p.previousId, p.message, p.nextId].map(v => Buffer.from(v)) + p.packetId = await sha256(Buffer.concat(bufs), { bytes: true }) + } + + if (p.clock === 2e9) p.clock = 0 + + const bufs = [Buffer.from(MAGIC_BYTES_PREFIX)] + + // an encoded packet has a fixed number of fields each with variable length + // the order of bufs will be consistent regardless of the field order in p + for (const [k, spec] of Object.entries(PACKET_SPEC)) { + // if p[k] is larger than specified in the spec, throw + + const value = p[k] || spec.default + + if (spec.encoding === 'number') { + const buf = Buffer.alloc(spec.bytes) + const value = typeof p[k] !== 'undefined' ? p[k] : spec.default + const bytesRequired = (32 - Math.clz32(value) >> 3) || 1 + + if (bytesRequired > spec.bytes) { + throw new Error(`key=${k}, value=${value} bytes=${bytesRequired}, max-bytes=${spec.bytes}, encoding=${spec.encoding}`) + } + + const method = getMethod('write', spec.bytes, spec.signed) + buf[method](value) + bufs.push(buf) + continue + } + + const encoded = Buffer.from(value || spec.default, spec.encoding) + + if (value?.length && encoded.length > spec.bytes) { + throw new Error(`${k} is invalid, ${value.length} is greater than ${spec.bytes} (encoding=${spec.encoding})`) + } + + // create a buffer from the size of the field and the actual value of p[k] + const bytes = value.length + const buf = Buffer.alloc(SIZE + bytes) + const offset = buf.writeUInt16BE(bytes) + encoded.copy(buf, offset) + bufs.push(buf) + } + + return Buffer.concat(bufs, FRAME_BYTES) + } + + static decode (buf) { + return decode(buf) + } +} + +export class PacketPing extends Packet { + static type = 1 + constructor ({ message, clusterId, subclusterId }) { + super({ type: PacketPing.type, message, clusterId, subclusterId }) + + validatePacket(message, { + requesterPeerId: { required: true, type: 'string' }, + cacheSummaryHash: { type: 'string' }, + probeExternalPort: { type: 'number' }, + reflectionId: { type: 'string' }, + pingId: { type: 'string' }, + natType: { type: 'number' }, + uptime: { type: 'number' }, + isHeartbeat: { type: 'number' }, + cacheSize: { type: 'number' }, + isConnection: { type: 'boolean' }, + isReflection: { type: 'boolean' }, + isProbe: { type: 'boolean' }, + isDebug: { type: 'boolean' }, + timestamp: { type: 'number' } + }) + } +} + +export class PacketPong extends Packet { + static type = 2 + constructor ({ message, clusterId, subclusterId }) { + super({ type: PacketPong.type, message, clusterId, subclusterId }) + + validatePacket(message, { + requesterPeerId: { required: true, type: 'string' }, + responderPeerId: { required: true, type: 'string' }, + cacheSummaryHash: { type: 'string' }, + port: { type: 'number' }, + address: { type: 'string' }, + uptime: { type: 'number' }, + cacheSize: { type: 'number' }, + natType: { type: 'number' }, + isReflection: { type: 'boolean' }, + isHeartbeat: { type: 'number' }, + isConnection: { type: 'boolean' }, + reflectionId: { type: 'string' }, + pingId: { type: 'string' }, + isDebug: { type: 'boolean' }, + isProbe: { type: 'boolean' }, + rejected: { type: 'boolean' } + }) + } +} + +export class PacketIntro extends Packet { + static type = 3 + constructor ({ clock, hops, clusterId, subclusterId, usr1, message }) { + super({ type: PacketIntro.type, clock, hops, clusterId, subclusterId, usr1, message }) + + validatePacket(message, { + requesterPeerId: { required: true, type: 'string' }, + responderPeerId: { required: true, type: 'string' }, + isRendezvous: { type: 'boolean' }, + natType: { required: true, type: 'number' }, + address: { required: true, type: 'string' }, + port: { required: true, type: 'number' }, + timestamp: { type: 'number' } + }) + } +} + +export class PacketJoin extends Packet { + static type = 4 + constructor ({ clock, hops, clusterId, subclusterId, message }) { + super({ type: PacketJoin.type, clock, hops, clusterId, subclusterId, message }) + + validatePacket(message, { + rendezvousAddress: { type: 'string' }, + rendezvousPort: { type: 'number' }, + rendezvousType: { type: 'number' }, + rendezvousPeerId: { type: 'string' }, + rendezvousDeadline: { type: 'number' }, + rendezvousRequesterPeerId: { type: 'string' }, + requesterPeerId: { required: true, type: 'string' }, + natType: { required: true, type: 'number' }, + initial: { type: 'boolean' }, + address: { required: true, type: 'string' }, + port: { required: true, type: 'number' }, + isConnection: { type: 'boolean' } + }) + } +} + +export class PacketPublish extends Packet { + static type = 5 // no need to validatePacket, message is whatever you want + constructor ({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }) { + super({ type: PacketPublish.type, message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }) + } +} + +export class PacketStream extends Packet { + static type = 6 + constructor ({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }) { + super({ type: PacketStream.type, message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }) + } +} + +export class PacketSync extends Packet { + static type = 7 + constructor ({ packetId, message = Buffer.from([0b0]) }) { + super({ type: PacketSync.type, packetId, message }) + } +} + +export class PacketQuery extends Packet { + static type = 8 + constructor ({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message = {} }) { + super({ type: PacketQuery.type, packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }) + } +} + +export default Packet diff --git a/api/latica/proxy.js b/api/latica/proxy.js new file mode 100644 index 0000000000..dc6850e9c2 --- /dev/null +++ b/api/latica/proxy.js @@ -0,0 +1,281 @@ +/** + * A utility to run run the protocol in a thread seperate from the UI. + * + * import { network } from + * + * Socket Node + * --- --- + * API API + * Proxy Protocol + * Protocol + * + */ +import { Deferred } from '../async.js' +import path from '../path.js' +const { pathname } = new URL(import.meta.url) + +function deepClone (object, map = new Map()) { + if (map.has(object)) return map.get(object) + + const isNull = object === null + const isNotObject = typeof object !== 'object' + const isArrayBuffer = object instanceof ArrayBuffer + const isArray = Array.isArray(object) + const isUint8Array = object instanceof Uint8Array + const isMessagePort = object instanceof MessagePort + + if (isMessagePort || isNotObject || isNull || isArrayBuffer) return object + if (isUint8Array) return new Uint8Array(object) + if (isArrayBuffer) return object.slice(0) + + if (isArray) { + const clonedArray = [] + map.set(object, clonedArray) + for (const item of object) { + clonedArray.push(deepClone(item, map)) + } + return clonedArray + } + + const clonedObj = {} + map.set(object, clonedObj) + for (const key in object) { + clonedObj[key] = deepClone(object[key], map) + } + + return clonedObj +} + +function transferOwnership (...objects) { + const transfers = [] + + function add (value) { + if (!transfers.includes(value)) { + transfers.push(value) + } + } + + objects.forEach(object => { + if (object instanceof ArrayBuffer || ArrayBuffer.isView(object)) { + add(object.buffer) + } else if (Array.isArray(object) || (object && typeof object === 'object')) { + for (const value of Object.values(object)) { + if (value instanceof MessagePort) add(value) + } + } + }) + + return transfers +} + +/** + * `Proxy` class factory, returns a Proxy class that is a proxy to the Peer. + * @param {{ createSocket: function('udp4', null, object?): object }} options + */ +class PeerWorkerProxy { + #promises = new Map() + #channel = null + #worker = null + #index = 0 + #port = null + + constructor (options, port, fn) { + if (!options.isWorkerThread) { + this.#channel = new MessageChannel() + this.#worker = new window.Worker(path.join(path.dirname(pathname), 'worker.js')) + + this.#worker.addEventListener('error', err => { + throw err + }) + + this.#worker.postMessage({ + port: this.#channel.port2 + }, [this.#channel.port2]) + + // when the main thread receives a message from the worker + this.#channel.port1.onmessage = ({ data: args }) => { + const { + err, + prop, + data, + seq + } = args + + if (!prop && err) { + throw new Error(err) + } + + if (prop && typeof this[prop] === 'function') { + try { + if (Array.isArray(data)) { + this[prop](...data) + } else { + this[prop](data) + } + } catch (err) { + throw new Error(err) + } + return + } + + const p = this.#promises.get(seq) + if (!p) return + + if (!p) { + console.warn(`No promise was found for the sequence (${seq})`) + return + } + + if (err) { + p.reject(err) + } else { + p.resolve(data) + } + + this.#promises.delete(seq) + } + + this.callWorkerThread('create', options) + return + } + + this.#port = port + this.#port.onmessage = fn.bind(this) + } + + async init () { + return await this.callWorkerThread('init') + } + + async reconnect () { + return await this.callWorkerThread('reconnect') + } + + async disconnect () { + return await this.callWorkerThread('disconnect') + } + + async getInfo () { + return await this.callWorkerThread('getInfo') + } + + async getState () { + return await this.callWorkerThread('getState') + } + + async open (...args) { + return await this.callWorkerThread('open', args) + } + + async seal (...args) { + return await this.callWorkerThread('seal', args) + } + + async sealUnsigned (...args) { + return await this.callWorkerThread('sealUnsigned', args) + } + + async openUnsigned (...args) { + return await this.callWorkerThread('openUnsigned', args) + } + + async addEncryptionKey (...args) { + return await this.callWorkerThread('addEncryptionKey', args) + } + + async send (...args) { + return await this.callWorkerThread('send', args) + } + + async sendUnpublished (...args) { + return await this.callWorkerThread('sendUnpublished', args) + } + + async cacheInsert (...args) { + return await this.callWorkerThread('cacheInsert', args) + } + + async mcast (...args) { + return await this.callWorkerThread('mcast', args) + } + + async requestReflection (...args) { + return await this.callWorkerThread('requestReflection', args) + } + + async join (...args) { + return await this.callWorkerThread('join', args) + } + + async publish (...args) { + return await this.callWorkerThread('publish', args) + } + + async sync (...args) { + return await this.callWorkerThread('sync', args) + } + + async close (...args) { + return await this.callWorkerThread('close', args) + } + + async query (...args) { + return await this.callWorkerThread('query', args) + } + + async compileCachePredicate (src) { + return await this.callWorkerThread('compileCachePredicate', src) + } + + callWorkerThread (prop, data) { + let transfer = [] + + if (data) { + data = deepClone(data) + transfer = transferOwnership(data) + } + + const seq = ++this.#index + const d = new Deferred() + + this.#channel.port1.postMessage( + { prop, data, seq }, + { transfer } + ) + + this.#promises.set(seq, d) + return d + } + + callMainThread (prop, args) { + for (const i in args) { + const arg = args[i] + if (!arg?.peerId) continue + args[i] = { ...arg } + delete args[i].localPeer // don't copy this over + } + + try { + this.#port.postMessage( + { data: deepClone(args), prop }, + { transfer: transferOwnership(args) } + ) + } catch (err) { + this.#port.postMessage({ data: { err: err.message, prop } }) + } + } + + resolveMainThread (seq, data) { + try { + this.#port.postMessage( + { data: deepClone(data), seq }, + { transfer: transferOwnership(data) } + ) + } catch (err) { + this.#port.postMessage({ data: { err: err.message } }) + } + } +} + +export { PeerWorkerProxy } +export default PeerWorkerProxy diff --git a/api/latica/worker.js b/api/latica/worker.js new file mode 100644 index 0000000000..c4b1845b7e --- /dev/null +++ b/api/latica/worker.js @@ -0,0 +1,82 @@ +import { Peer } from './index.js' +import { PeerWorkerProxy } from './proxy.js' +import dgram from '../dgram.js' + +let proxy +let peer + +globalThis.addEventListener('message', ({ data: source }) => { + if (proxy) return + + const opts = { isWorkerThread: true } + + proxy = new PeerWorkerProxy(opts, source.port, async function ({ data: args }) { + const { + prop, + data, + seq + } = args + + switch (prop) { + case 'create': { + peer = new Peer(data, dgram) + + peer.onConnecting = (...args) => this.callMainThread('onConnecting', args) + peer.onConnection = (...args) => this.callMainThread('onConnection', args) + peer.onDisconnection = (...args) => this.callMainThread('onDisconnection', args) + peer.onJoin = (...args) => this.callMainThread('onJoin', args) + peer.onPacket = (...args) => this.callMainThread('onPacket', args) + peer.onStream = (...args) => this.callMainThread('onStream', args) + peer.onData = (...args) => this.callMainThread('onData', args) + peer.onSend = (...args) => this.callMainThread('onSend', args) + peer.onFirewall = (...args) => this.callMainThread('onFirewall', args) + peer.onMulticast = (...args) => this.callMainThread('onMulticast', args) + peer.onJoin = (...args) => this.callMainThread('onJoin', args) + peer.onSync = (...args) => this.callMainThread('onSync', args) + peer.onSyncStart = (...args) => this.callMainThread('onSyncStart', args) + peer.onSyncEnd = (...args) => this.callMainThread('onSyncEnd', args) + peer.onQuery = (...args) => this.callMainThread('onQuery', args) + peer.onNat = (...args) => this.callMainThread('onNat', args) + peer.onState = (...args) => this.callMainThread('onState', args) + peer.onWarn = (...args) => this.callMainThread('onWarn', args) + peer.onError = (...args) => this.callMainThread('onError', args) + peer.onReady = (...args) => this.callMainThread('onReady', args) + break + } + + case 'compileCachePredicate': { + // eslint-disable-next-line + let predicate = new Function(`return ${data.toString()}`)() + predicate = predicate.bind(peer) + peer.cachePredicate = packet => predicate(packet) + break + } + + default: { + if (isNaN(seq) && peer[prop]) { + peer[prop] = data + return + } + + let r + + try { + if (typeof peer[prop] === 'function') { + if (Array.isArray(data)) { + r = await peer[prop](...data) + } else { + r = await peer[prop](data) + } + } else { + r = peer[prop] + } + } catch (err) { + console.error(err) + return this.resolveMainThread(seq, { err }) + } + + this.resolveMainThread(seq, { data: r }) + } + } + }) +}) diff --git a/bin/update-network-protocol.sh b/bin/update-network-protocol.sh new file mode 100755 index 0000000000..716b31885f --- /dev/null +++ b/bin/update-network-protocol.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +version="${1:-"1.0.23-0"}" + +rm -rf api/latica.js || exit $? +rm -rf api/latica || exit $? +cp -rf node_modules/@socketsupply/latica/src api/latica || exit $? +rm -rf node_modules/@socketsupply/{socket,socket-{darwin,linux,win32}*,latica} || exit $? + +for file in $(find api/latica -type f); do + sed -i '' -e "s/'socket:\(.*\)'/'..\/\1.js'/g" "$file" || exit $? +done + +{ + echo "import def from './latica/index.js'" + echo "export * from './latica/index.js'" + echo "export default def" +} >> api/latica.js + +tree api/latica From f715945049abfbe816459ea5e38f4f49100fada4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 08:24:09 -0400 Subject: [PATCH 0752/1178] refactor(api/commonjs): handle 'npm:' prefix in 'require()' --- api/commonjs/require.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 6b318324f2..c71918e495 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -144,6 +144,10 @@ export function createRequire (options) { * @ignore */ function applyResolvers (input, options = null) { + if (typeof input === 'string' && input.startsWith('npm:')) { + input = input.slice(4) + } + const resolvers = Array .from([]) .concat(options?.resolvers) From cc3b8aaee8f9c3b4ab25f45ac8ba6947da2dc570 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:39:33 +0200 Subject: [PATCH 0753/1178] refactor(desktop/main): show service worker window in debug mode --- src/desktop/main.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 3c0dcb591f..faa4341c1f 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1114,6 +1114,10 @@ MAIN { serviceWorkerWindow->navigate( "socket://" + userConfig["meta_bundle_identifier"] + "/socket/service-worker/index.html" ); + + if (Env::get("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG").size() > 0) { + serviceWorkerWindow->show(); + } } else if (userConfig["webview_service_worker_mode"] == "hybrid") { app.serviceWorkerContainer.init(&defaultWindow->bridge); } From 4ca6c09f3deeffe798476a12dc75f6519a388957 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:45:05 +0200 Subject: [PATCH 0754/1178] refactor(core): introduce tracer, improve resource streaming --- src/core/config.cc | 8 +- src/core/core.cc | 40 ++++-- src/core/json.cc | 5 + src/core/json.hh | 17 ++- src/core/modules/fs.cc | 6 +- src/core/modules/fs.hh | 6 +- src/core/resource.cc | 280 +++++++++++++++++++++++++++++++++++++++++ src/core/resource.hh | 65 +++++++++- src/core/trace.cc | 166 ++++++++++++++++++++++++ src/core/trace.hh | 208 ++++++++++++++++++++++++++++++ 10 files changed, 775 insertions(+), 26 deletions(-) create mode 100644 src/core/trace.cc create mode 100644 src/core/trace.hh diff --git a/src/core/config.cc b/src/core/config.cc index a065f61c54..0a63ad27e5 100644 --- a/src/core/config.cc +++ b/src/core/config.cc @@ -13,16 +13,18 @@ namespace SSC { const Map getUserConfig () { static const auto bytes = socket_runtime_init_get_user_config_bytes(); static const auto size = socket_runtime_init_get_user_config_bytes_size(); - static const auto userConfig = INI::parse(String((const char*) bytes, size)); + static const auto userConfig = INI::parse(String((char*)bytes, size)); return userConfig; } const String getDevHost () { - return socket_runtime_init_get_dev_host(); + static const auto devHost = socket_runtime_init_get_dev_host(); + return devHost; } int getDevPort () { - return socket_runtime_init_get_dev_port(); + static const auto devPort = socket_runtime_init_get_dev_port(); + return devPort; } Config::Config (const String& source) { diff --git a/src/core/core.cc b/src/core/core.cc index e3af06475a..b10ba929b5 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -3,7 +3,6 @@ namespace SSC { Post Core::getPost (uint64_t id) { - Lock lock(this->postsMutex); if (this->posts.find(id) == this->posts.end()) { return Post{}; } @@ -19,12 +18,10 @@ namespace SSC { } bool Core::hasPost (uint64_t id) { - Lock lock(this->postsMutex); return posts.find(id) != posts.end(); } bool Core::hasPostBody (const char* body) { - Lock lock(this->postsMutex); if (body == nullptr) return false; for (const auto& tuple : posts) { auto post = tuple.second; @@ -76,8 +73,6 @@ namespace SSC { } String Core::createPost (String seq, String params, Post post) { - Lock lock(this->postsMutex); - if (post.id == 0) { post.id = rand64(); } @@ -152,14 +147,30 @@ namespace SSC { return *timeout == 0; }, + /* + .check = [](GSource* source) -> gboolean { + auto core = reinterpret_cast(source)->core; + auto loop = core->getEventLoop(); + auto timeout = core->getEventLoopTimeout(); + + if (timeout == 0) { + return true; + } + + //auto condition = + + return true; + }, + */ + .dispatch = []( GSource *source, GSourceFunc callback, gpointer user_data ) -> gboolean { auto core = reinterpret_cast(source)->core; - Lock lock(core->loopMutex); auto loop = core->getEventLoop(); + Lock lock(core->loopMutex); uv_run(loop, UV_RUN_NOWAIT); return G_SOURCE_CONTINUE; } @@ -175,17 +186,25 @@ namespace SSC { Lock lock(this->loopMutex); uv_loop_init(&eventLoop); eventLoopAsync.data = (void *) this; + uv_async_init(&eventLoop, &eventLoopAsync, [](uv_async_t *handle) { - auto core = reinterpret_cast(handle->data); + auto core = reinterpret_cast(handle->data); + while (true) { - std::function dispatch; - { + Function dispatch = nullptr; + + do { Lock lock(core->loopMutex); if (core->eventLoopDispatchQueue.size() == 0) break; dispatch = core->eventLoopDispatchQueue.front(); core->eventLoopDispatchQueue.pop(); + } while (0); + + if (dispatch == nullptr) { + break; } - if (dispatch != nullptr) dispatch(); + + dispatch(); } }); @@ -199,6 +218,7 @@ namespace SSC { (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR) ); + g_source_set_priority(source, G_PRIORITY_HIGH); g_source_attach(source, nullptr); #endif } diff --git a/src/core/json.cc b/src/core/json.cc index dcba8178fc..4ec5f12c67 100644 --- a/src/core/json.cc +++ b/src/core/json.cc @@ -144,6 +144,11 @@ namespace SSC::JSON { this->type = Type::Number; } + Any::Any (long long number) { + this->pointer = SharedPointer(new Number((double) number)); + this->type = Type::Number; + } + #if SOCKET_RUNTIME_PLATFORM_APPLE Any::Any (size_t number) { this->pointer = SharedPointer(new Number((double) number)); diff --git a/src/core/json.hh b/src/core/json.hh index 40e2d5571c..f4921453d0 100644 --- a/src/core/json.hh +++ b/src/core/json.hh @@ -160,19 +160,12 @@ namespace SSC::JSON { this->type = Type::Null; } - ~Any () { - this->pointer = nullptr; - this->type = Type::Any; - } - - Any (const Any &any) { - this->pointer = any.pointer; + Any (const Any& any) : pointer(any.pointer) { this->type = any.type; } - Any (Type type, SharedPointer pointer) { + Any (Type type, SharedPointer pointer) : pointer(pointer) { this->type = type; - this->pointer = pointer; } Any (std::nullptr_t); @@ -184,6 +177,7 @@ namespace SSC::JSON { Any (uint32_t); Any (int32_t); Any (double); + Any (long long); #if SOCKET_RUNTIME_PLATFORM_APPLE Any (size_t); Any (ssize_t); @@ -205,6 +199,11 @@ namespace SSC::JSON { Any (const GError*); #endif + ~Any () { + this->pointer = nullptr; + this->type = Type::Any; + } + SSC::String str () const; template T& as () const { diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index b741ce7c78..0546557aa4 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -1,4 +1,5 @@ #include "../headers.hh" +#include "../trace.hh" #include "../json.hh" #include "../core.hh" #include "fs.hh" @@ -30,6 +31,9 @@ namespace SSC { #if defined(UV_DIRENT_BLOCK) CONSTANT(UV_DIRENT_BLOCK) #endif + #if defined(UV_FS_O_FILEMAP) + CONSTANT(UV_FS_O_FILEMAP) + #endif #if defined(O_RDONLY) CONSTANT(O_RDONLY) #endif @@ -961,7 +965,7 @@ namespace SSC { size_t offset, const CoreModule::Callback& callback ) const { - this->core->dispatchEventLoop([=, this]() { + this->core->dispatchEventLoop([=, this]() mutable { auto desc = getDescriptor(id); if (desc == nullptr) { diff --git a/src/core/modules/fs.hh b/src/core/modules/fs.hh index 541fa4678e..9e8c15f377 100644 --- a/src/core/modules/fs.hh +++ b/src/core/modules/fs.hh @@ -4,6 +4,7 @@ #include "../file_system_watcher.hh" #include "../resource.hh" #include "../module.hh" +#include "../trace.hh" #if SOCKET_RUNTIME_PLATFORM_ANDROID #include "../../platform/android.hh" @@ -36,6 +37,7 @@ namespace SSC { ID id; SharedPointer descriptor = nullptr; SharedPointer buffer = nullptr; + Tracer tracer; uv_fs_t req; uv_buf_t buf; // 256 which corresponds to DirectoryHandle.MAX_BUFFER_SIZE @@ -44,7 +46,7 @@ namespace SSC { int result = 0; bool recursive; - RequestContext () = default; + RequestContext () = delete; RequestContext (SharedPointer descriptor) : RequestContext(descriptor, "", nullptr) {} @@ -57,7 +59,7 @@ namespace SSC { SharedPointer descriptor, const String& seq, const Callback& callback - ) { + ) : tracer("CoreFS::RequestContext") { this->id = rand64(); this->seq = seq; this->req.data = (void*) this; diff --git a/src/core/resource.cc b/src/core/resource.cc index 991a2e0c25..1aea300223 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -656,4 +656,284 @@ namespace SSC { return ""; } + + FileResource::ReadStream FileResource::stream (const ReadStream::Options& options) { + return ReadStream(ReadStream::Options(this->path, options.highWaterMark, this->size())); + } + + FileResource::ReadStream::ReadStream (const Options& options) + : options(options) + {} + + FileResource::ReadStream::~ReadStream () { + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_LINUX + if (this->file != nullptr) { + g_object_unref(this->file); + this->file = nullptr; + } + + if (this->stream != nullptr) { + g_input_stream_close( + reinterpret_cast(this->stream), + nullptr, + nullptr + ); + g_object_unref(this->stream); + this->stream = nullptr; + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + } + + FileResource::ReadStream::ReadStream ( + const ReadStream& stream + ) : options(stream.options), + offset(stream.offset.load()), + ended(stream.ended.load()) + { + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_LINUX + this->file = stream.file; + this->stream = stream.stream; + + if (this->file) { + g_object_ref(this->file); + } + + if (this->stream) { + g_object_ref(this->stream); + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + } + + FileResource::ReadStream::ReadStream (ReadStream&& stream) + : options(stream.options), + offset(stream.offset.load()), + ended(stream.ended.load()) + { + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_LINUX + this->file = stream.file; + this->stream = stream.stream; + if (stream.file) { + stream.file = nullptr; + } + + if (stream.stream) { + stream.stream = nullptr; + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + } + + FileResource::ReadStream& FileResource::ReadStream::operator = ( + const ReadStream& stream + ) { + this->options.highWaterMark = stream.options.highWaterMark; + this->options.resourcePath = stream.options.resourcePath; + this->options.size = stream.options.size; + this->offset = stream.offset.load(); + this->ended = stream.ended.load(); + + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_LINUX + this->file = stream.file; + this->stream = stream.stream; + + if (this->file) { + g_object_ref(this->file); + } + + if (this->stream) { + g_object_ref(this->stream); + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + return *this; + } + + FileResource::ReadStream& FileResource::ReadStream::operator = ( + ReadStream&& stream + ) { + this->options.highWaterMark = stream.options.highWaterMark; + this->options.resourcePath = stream.options.resourcePath; + this->options.size = stream.options.size; + this->offset = stream.offset.load(); + this->ended = stream.ended.load(); + + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_LINUX + this->file = stream.file; + this->stream = stream.stream; + if (stream.file) { + stream.file = nullptr; + } + + if (stream.stream) { + stream.stream = nullptr; + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + return *this; + } + + const FileResource::ReadStream::Buffer FileResource::ReadStream::read (off_t offset, size_t highWaterMark) { + if (offset == -1) { + offset = this->offset; + } + + if (highWaterMark == -1) { + highWaterMark = this->options.highWaterMark; + } + + const auto remaining = this->remaining(offset); + const auto size = highWaterMark > remaining ? remaining : highWaterMark; + auto buffer = Buffer(size); + + if (buffer.size > 0 && buffer.bytes != nullptr) { + #if SOCKET_RUNTIME_PLATFORM_APPLE + if (this->data == nullptr) { + this->data = [NSData dataWithContentsOfURL: this->resource.url]; + @try { + [this->data + getBytes: buffer.bytes.get(), + range: NSMakeRange(offset, size) + ]; + } @catch (NSException* error) { + this->error = [NSError + errorWithDomain: error.name + code: 0 + userInfo: @{ + NSUnderlyingErrorKey: error, + NSDebugDescriptionErrorKey: error.userInfo ?: @{}, + NSLocalizedFailureReasonErrorKey: (error.reason ?: @"???") + }]; + } + } + #elif SOCKET_RUNTIME_PLATFORM_LINUX + if (this->file == nullptr) { + this->file = g_file_new_for_path(this->options.resourcePath.c_str()); + } + + if (this->stream == nullptr) { + this->stream = g_file_read(this->file, nullptr, nullptr); + } + + if (size == 0 || highWaterMark == 0) { + return buffer; + } + + if (offset > this->offset) { + g_input_stream_skip( + reinterpret_cast(this->stream), + offset - this->offset, + nullptr, + nullptr + ); + + this->offset = offset; + } + + buffer.size = g_input_stream_read( + reinterpret_cast(this->stream), + buffer.bytes.get(), + size, + nullptr, + &this->error + ); + + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + } + + if (this->error) { + buffer.size = 0; + debug( + "FileResource::ReadStream: read error: %s", + #if SOCKET_RUNTIME_PLATFORM_APPLE + error.localizedDescription.UTF8String + #elif SOCKET_RUNTIME_PLATFORM_LINUX + error->message + #else + "An unknown error occurred" + #endif + ); + } + + if (buffer.size <= 0) { + buffer.bytes = nullptr; + buffer.size = 0; + this->ended = true; + } else { + this->offset += buffer.size; + this->ended = this->offset >= this->options.size; + } + + return buffer; + } + + size_t FileResource::ReadStream::remaining (off_t offset) const { + const auto size = this->options.size; + if (offset > -1) { + return size - offset; + } + + return size - this->offset; + } + + FileResource::ReadStream::Buffer::Buffer (size_t size) + : bytes(std::make_shared(size)), + size(size) + { + memset(this->bytes.get(), 0, size); + } + + FileResource::ReadStream::Buffer::Buffer (const Options& options) + : bytes(std::make_shared(options.highWaterMark)), + size(0) + { + memset(this->bytes.get(), 0, options.highWaterMark); + } + + FileResource::ReadStream::Buffer::Buffer (const Buffer& buffer) { + this->size = buffer.size.load(); + this->bytes = buffer.bytes; + } + + FileResource::ReadStream::Buffer::Buffer (Buffer&& buffer) { + this->size = buffer.size.load(); + this->bytes = buffer.bytes; + buffer.size = 0; + buffer.bytes = nullptr; + } + + FileResource::ReadStream::Buffer& FileResource::ReadStream::Buffer::operator = ( + const Buffer& buffer + ) { + this->size = buffer.size.load(); + this->bytes = buffer.bytes; + return *this; + } + + FileResource::ReadStream::Buffer& FileResource::ReadStream::Buffer::operator = ( + Buffer&& buffer + ) { + this->size = buffer.size.load(); + this->bytes = buffer.bytes; + buffer.size = 0; + buffer.bytes = nullptr; + return *this; + } + + bool FileResource::ReadStream::Buffer::isEmpty () const { + return this->size == 0 || this->bytes == nullptr; + } } diff --git a/src/core/resource.hh b/src/core/resource.hh index d93fc621e6..dc730e9e7e 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -36,6 +36,69 @@ namespace SSC { bool cache; }; + class ReadStream { + public: + #if SOCKET_RUNTIME_PLATFORM_APPLE + using Error = NSError; + #elif SOCKET_RUNTIME_PLATFORM_LINUX + using Error = GError; + #else + using Error = char*; + #endif + + struct Options { + Path resourcePath; + size_t highWaterMark = 64 * 1024; + size_t size = 0; + + Options ( + const Path& resourcePath = Path(""), + size_t highWaterMark = 64 * 1024, + size_t size = 0 + ) + : highWaterMark(highWaterMark), + resourcePath(resourcePath), + size(size) + {} + }; + + struct Buffer { + Atomic size = 0; + SharedPointer bytes; + Buffer (size_t size); + Buffer (const Options& options); + Buffer (const Buffer& buffer); + Buffer (Buffer&& buffer); + Buffer& operator= (const Buffer&); + Buffer& operator= (Buffer&&); + bool isEmpty () const; + }; + + #if SOCKET_RUNTIME_PLATFORM_APPLE + NSData* data = nullptr; + #elif SOCKET_RUNTIME_PLATFORM_LINUX + GFileInputStream* stream = nullptr; + GFile* file = nullptr; + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + #endif + + Options options; + Error* error = nullptr; + Atomic offset = 0; + Atomic ended = false; + + ReadStream (const Options& options); + ~ReadStream (); + ReadStream (const ReadStream&); + ReadStream (ReadStream&&); + ReadStream& operator= (const ReadStream&); + ReadStream& operator= (ReadStream&&); + + const Buffer read (off_t offset = -1, size_t highWaterMark = -1); + size_t remaining (off_t offset = -1) const; + }; + Cache cache; Options options; SharedPointer bytes = nullptr; @@ -49,7 +112,6 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_APPLE NSURL* url = nullptr; - #elif SOCKET_RUNTIME_PLATFORM_APPLE #endif FileResource (const Path& resourcePath, const Options& options = {}); @@ -70,6 +132,7 @@ namespace SSC { const char* read () const; const char* read (bool cached = false); const String str (bool cached = false); + ReadStream stream (const ReadStream::Options& options = {}); }; } #endif diff --git a/src/core/trace.cc b/src/core/trace.cc new file mode 100644 index 0000000000..8e7fe5cad7 --- /dev/null +++ b/src/core/trace.cc @@ -0,0 +1,166 @@ +#include "trace.hh" + +namespace SSC { + Tracer::Tracer (const String& name) + : name(name), + spans(std::make_shared()) + {} + + Tracer::Tracer (const Tracer& tracer) + : name(tracer.name), + spans(tracer.spans) + {} + + Tracer::Tracer (Tracer&& tracer) noexcept + : name(tracer.name), + spans(std::move(tracer.spans)) + {} + + Tracer& Tracer::operator= (const Tracer& tracer) { + if (this != &tracer) { + this->name = tracer.name; + this->spans = tracer.spans; + } + return *this; + } + + Tracer& Tracer::operator= (Tracer&& tracer) noexcept { + if (this != &tracer) { + this->name = tracer.name; + this->spans = std::move(tracer.spans); + } + return *this; + } + + Tracer::SharedSpan Tracer::span (const String& name, const Span::ID id) { + auto span = std::make_shared(*this, name); + + if (id > 0) { + span->id = id; + } + + do { + Lock lock(this->mutex); + const auto i = this->spans->size(); + this->spans->emplace_back(span); + this->index[span->id] = i; + } while (0); + + return span; + } + + Tracer::SharedSpan Tracer::span (const Span::ID id) { + Lock lock(this->mutex); + if (this->index.contains(id)) { + const auto i = this->index[id]; + if (i < this->spans->size()) { + return this->spans->at(id); + } + } + + return nullptr; + } + + size_t Tracer::size (bool onlyActive) const { + size_t count = 0; + for (const auto& span : *this->spans) { + if (onlyActive && !span->ended) { + continue; + } + + count++; + } + + return count; + } + + JSON::Object Tracer::json () const { + JSON::Array spans; + + for (const auto& span : *this->spans) { + // only top level spans + if (span->parent == nullptr) { + spans.push(span->json()); + } + } + + return JSON::Object::Entries { + {"name", this->name}, + {"spans", spans} + }; + } + + Tracer::Duration Tracer::Timing::now () { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()); + } + + void Tracer::Timing::stop () { + using namespace std::chrono; + this->end = TimePoint(Timing::now()); + this->duration = duration_cast(this->end.load() - this->start.load()); + } + + JSON::Object Tracer::Timing::json () const { + using namespace std::chrono; + const auto duration = duration_cast(this->duration.load()).count(); + const auto start = time_point_cast(this->start.load()).time_since_epoch().count(); + const auto end = time_point_cast(this->end.load()).time_since_epoch().count(); + return JSON::Object::Entries { + {"start", start}, + {"end", end}, + {"duration", duration} + }; + } + + Tracer::Timing::Timing () + : start(TimePoint(Timing::now())) + {} + + Tracer::Span::Span (Tracer& tracer, const String& name) + : tracer(tracer), + name(name) + {} + + Tracer::Span::~Span () { + this->end(); + } + + bool Tracer::Span::end () { + if (this->ended) { + return false; + } + + this->timing.stop(); + this->ended = true; + return true; + } + + JSON::Object Tracer::Span::json () const { + JSON::Array spans; + + for (const auto id : this->spans) { + const auto span = this->tracer.span(id); + if (span != nullptr) { + spans.push(span->json()); + } + } + + return JSON::Object::Entries { + {"name", this->name}, + {"timing", this->timing.json()}, + {"spans", spans} + }; + } + + Tracer::SharedSpan Tracer::Span::span (const String& name, const Span::ID id) { + auto span = this->tracer.span(name, id); + span->parent = this; + return span; + } + + long Tracer::Span::duration () const { + using namespace std::chrono; + return duration_cast(this->timing.duration.load()).count(); + } +} diff --git a/src/core/trace.hh b/src/core/trace.hh new file mode 100644 index 0000000000..be89a45e8a --- /dev/null +++ b/src/core/trace.hh @@ -0,0 +1,208 @@ +#ifndef SOCKET_RUNTIME_CORE_TRACE_H +#define SOCKET_RUNTIME_CORE_TRACE_H + +#include "../platform/platform.hh" +#include "json.hh" + +namespace SSC { + /** + * The `Tracer` class manages multiple `Tracer::Span` instances, allowing + * spans to be created and tracked across multiple threads. + * It ensures thread-safe access to the spans and provides support for + * copying and moving, making it suitable for use in async and + * multi-threaded contexts. + */ + class Tracer { + public: + // forward + class Span; + + /** + * A high resolution time time point used by the `Tracer::Span` class. + */ + using TimePoint = std::chrono::time_point; + /** + * A tracer duration quantized to milliseconds + */ + using Duration = std::chrono::milliseconds; + + /** + * A shared pointer `Tracer::Span` type + */ + using SharedSpan = SharedPointer; + + /** + * A collection of `SharedSpan` instances + */ + using SharedSpanCollection = Vector; + + /** + * A mapping type of `Span::ID` to `Vector` index for fast span access + */ + using SharedSpanColletionIndex = std::map; + + /** + * A container for `Tracer` timing. + */ + struct Timing { + Atomic start; + Atomic end; + Atomic duration; + static Duration now (); + Timing (); + void stop (); + JSON::Object json () const; + }; + + /** + * The `Tracer::Span` class represents a "time span", tracking its start + * time and providing the ability to end it, either automatically upon + * destruction or manually via the end() method. It is thread-safe and + * ensures that the duration is printed only once. + */ + class Span { + public: + using ID = uint64_t; + + /** + * A unique ID for this `Span`. + */ + ID id = rand64(); + + /** + * The name of this span + */ + String name; + + /** + * The span timing. + */ + Timing timing; + + /** + * This value is `true` if the span has ended + */ + Atomic ended = false; + + /** + * A strong reference to the `Tracer` that created this `Span`. + */ + Tracer& tracer; + + /** + * A weak pointer to a parent `Span` that created this `Span`. + * This value may be `nullptr`. + */ + Span* parent = nullptr; + + /** + * A vector of `Span::ID` that point to a `Span` instance in a + * `Tracer`. + */ + Vector spans; + + /** + * Initializes a new span with the given name and starts the timer. + */ + Span (Tracer& tracer, const String& name); + + /** + * Ends the span, storing the duration if it has not been ended + * already. + */ + ~Span (); + + /** + * Ends the span, storing the duration, and ensures thread-safe + * access. If the span has already been ended, this method does + * nothing. + * + * This function returns `true` if the span ended for the first time, + * otherwise `false` + */ + bool end (); + + /** + * Computed JSON representation of this `Tracer::Span` instance. + */ + JSON::Object json () const; + + /** + * Creates a new `Span` with the given name, adds it to the + * collection of spans, and returns a "shared span" (a shared pointer + * to the span). This functinon ensures thread-safe access to the + * collection. The returned `Span` is a "child" of this `Span`. + */ + SharedSpan span (const String& name, const ID id = 0); + + /** + * Returns the computed duration for an "ended" `Span` + */ + long duration () const; + }; + + /** + * A collection of shared spans owned by this `Tracer` instance. + */ + SharedPointer spans; + + /** + * The shared span collection index + */ + SharedSpanColletionIndex index; + + /** + * Used for thread synchronization + */ + Mutex mutex; + + /** + * The name of the `Tracer` instance. + */ + String name; + + /** + * Initializes a new Tracer instance with an empty collection of spans. + */ + Tracer (const String& name); + // copy + Tracer (const Tracer&); + // move + Tracer (Tracer&&) noexcept; + ~Tracer () = default; + + // copy + Tracer& operator= (const Tracer&); + // move + Tracer& operator= (Tracer&&) noexcept; + + /** + * Creates a new `Tracer::Span` with the given name, adds it to the + * collection of spans, and returns a "shared span" (a shared pointer + * to the span). This functinon ensures thread-safe access to the + * collection. + */ + SharedSpan span (const String& name, const Span::ID id = 0); + + /** + * Gets a span by `Span::ID`. This function _will not_ create a new + * `Span`, but instead return a "null" `SharedSpan`. + */ + SharedSpan span (const Span::ID id); + + /** + * The number of spans in this tracer. This function accepts an + * `onlyActive` boolean to get the computed "active" (not ended) + * spans in a trace. + */ + size_t size (bool onlyActive = false) const; + + /** + * Computed JSON representation of this `Tracer` instance and its + * `Tracer::Span` children. + */ + JSON::Object json () const; + }; +} + +#endif From d30d3dd5b015b20e813ccca4eeaf20b93d23d76c Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:45:57 +0200 Subject: [PATCH 0755/1178] refactor(ipc): user shared pointers instead of 'const ref' --- src/ipc/bridge.cc | 125 ++++++++-------- src/ipc/client.hh | 10 +- src/ipc/preload.cc | 6 + src/ipc/router.cc | 14 +- src/ipc/scheme_handlers.cc | 283 +++++++++++++++++++++++-------------- src/ipc/scheme_handlers.hh | 57 ++++++-- 6 files changed, 299 insertions(+), 196 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index b4cd8088d9..d7d0975633 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -17,17 +17,15 @@ namespace SSC::IPC { // `socket:///socket/.js` resolve to the exact // same module static constexpr auto ESM_IMPORT_PROXY_TEMPLATE = - R"S( - /** - * This module exists to provide a proxy to a canonical URL for a module - * so `socket:` and `socket:///socket/.js` - * resolve to the exact same module instance. - * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} - */ - import module from '{{url}}' - export * from '{{url}}' - export default module - )S"; +R"S(/** + * This module exists to provide a proxy to a canonical URL for a module + * so `socket:` and `socket:///socket/.js` + * resolve to the exact same module instance. + * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} + */ +import module from '{{url}}' +export * from '{{url}}' +export default module)S"; static const Vector allowedNodeCoreModules = { "async_hooks", @@ -265,7 +263,6 @@ namespace SSC::IPC { } void Bridge::configureWebView (WebView* webview) { - this->schemeHandlers.configureWebView(webview); this->navigator.configureWebView(webview); } @@ -370,12 +367,12 @@ namespace SSC::IPC { void Bridge::configureSchemeHandlers (const SchemeHandlers::Configuration& configuration) { this->schemeHandlers.configure(configuration); this->schemeHandlers.registerSchemeHandler("ipc", [this]( - const auto& request, + const auto request, const auto bridge, auto& callbacks, auto callback ) { - auto message = Message(request.url(), true); + auto message = Message(request->url(), true); // handle special 'ipc://post' case if (message.name == "post") { @@ -437,10 +434,10 @@ namespace SSC::IPC { } }; - const auto size = request.body.size; - const auto bytes = request.body.bytes; - const auto invoked = this->router.invoke(message, request.body.bytes, size, [request, message, callback](Result result) { - if (!request.isActive()) { + const auto size = request->body.size; + const auto bytes = request->body.bytes; + const auto invoked = this->router.invoke(message, request->body.bytes, size, [request, message, callback](Result result) { + if (!request->isActive()) { return; } @@ -461,7 +458,7 @@ namespace SSC::IPC { const char* data, bool finished ) mutable { - if (request.isCancelled()) { + if (request->isCancelled()) { if (message.cancel->handler != nullptr) { message.cancel->handler(message.cancel->data); } @@ -493,7 +490,7 @@ namespace SSC::IPC { size_t size, bool finished ) mutable { - if (request.isCancelled()) { + if (request->isCancelled()) { if (message.cancel->handler != nullptr) { message.cancel->handler(message.cancel->data); } @@ -527,7 +524,7 @@ namespace SSC::IPC { {"err", JSON::Object::Entries { {"message", "Not found"}, {"type", "NotFoundError"}, - {"url", request.url()} + {"url", request->url()} }} }); @@ -536,7 +533,7 @@ namespace SSC::IPC { }); this->schemeHandlers.registerSchemeHandler("socket", [this]( - const auto& request, + const auto request, const auto bridge, auto& callbacks, auto callback @@ -555,18 +552,18 @@ namespace SSC::IPC { String contentLocation; // application resource or service worker request at `socket:///*` - if (request.hostname == bundleIdentifier) { - const auto resolved = this->navigator.location.resolve(request.pathname, applicationResources); + if (request->hostname == bundleIdentifier) { + const auto resolved = this->navigator.location.resolve(request->pathname, applicationResources); if (resolved.redirect) { - if (request.method == "GET") { + if (request->method == "GET") { auto location = resolved.pathname; - if (request.query.size() > 0) { - location += "?" + request.query; + if (request->query.size() > 0) { + location += "?" + request->query; } - if (request.fragment.size() > 0) { - location += "#" + request.fragment; + if (request->fragment.size() > 0) { + location += "#" + request->fragment; } response.redirect(location); @@ -576,7 +573,7 @@ namespace SSC::IPC { resourcePath = applicationResources + resolved.pathname; } else if (resolved.isMount()) { resourcePath = applicationResources + resolved.mount.filename; - } else if (request.pathname == "" || request.pathname == "/") { + } else if (request->pathname == "" || request->pathname == "/") { if (userConfig.contains("webview_default_index")) { resourcePath = userConfig["webview_default_index"]; if (resourcePath.starts_with("./")) { @@ -606,7 +603,7 @@ namespace SSC::IPC { response.setHeader("content-location", contentLocation); } - if (request.method == "OPTIONS") { + if (request->method == "OPTIONS") { response.setHeader("access-control-allow-origin", "*"); response.setHeader("access-control-allow-methods", "GET, HEAD"); response.setHeader("access-control-allow-headers", "*"); @@ -614,7 +611,7 @@ namespace SSC::IPC { response.writeHead(200); } - if (request.method == "HEAD") { + if (request->method == "HEAD") { const auto contentType = resource.mimeType(); const auto contentLength = resource.size(); @@ -629,7 +626,7 @@ namespace SSC::IPC { response.writeHead(200); } - if (request.method == "GET") { + if (request->method == "GET") { if (resource.mimeType() != "text/html") { response.send(resource); } else { @@ -650,18 +647,18 @@ namespace SSC::IPC { if (this->navigator.serviceWorker.registrations.size() > 0) { const auto fetch = ServiceWorkerContainer::FetchRequest { - request.method, - request.scheme, - request.hostname, - request.pathname, - request.query, - request.headers, - ServiceWorkerContainer::FetchBody { request.body.size, request.body.bytes }, - ServiceWorkerContainer::Client { request.client.id, this->preload } + request->method, + request->scheme, + request->hostname, + request->pathname, + request->query, + request->headers, + ServiceWorkerContainer::FetchBody { request->body.size, request->body.bytes }, + ServiceWorkerContainer::Client { request->client.id, this->preload } }; const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { - if (!request.isActive()) { + if (!request->isActive()) { return; } @@ -677,7 +674,7 @@ namespace SSC::IPC { if (fetched) { this->core->setTimeout(32000, [request] () mutable { - if (request.isActive()) { + if (request->isActive()) { auto response = SchemeHandlers::Response(request, 408); response.fail("ServiceWorker request timed out."); } @@ -692,8 +689,8 @@ namespace SSC::IPC { // module or stdlib import/fetch `socket:/` which will just // proxy an import into a normal resource request above - if (request.hostname.size() == 0) { - auto pathname = request.pathname; + if (request->hostname.size() == 0) { + auto pathname = request->pathname; if (!pathname.ends_with(".js")) { pathname += ".js"; @@ -713,7 +710,7 @@ namespace SSC::IPC { "socket://" + bundleIdentifier + contentLocation + - (request.query.size() > 0 ? "?" + request.query : "") + (request->query.size() > 0 ? "?" + request->query : "") ); const auto moduleImportProxy = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map { @@ -746,7 +743,7 @@ namespace SSC::IPC { }); this->schemeHandlers.registerSchemeHandler("node", [this]( - const auto& request, + const auto request, const auto router, auto& callbacks, auto callback @@ -766,11 +763,11 @@ namespace SSC::IPC { // module or stdlib import/fetch `socket:/` which will just // proxy an import into a normal resource request above - if (request.hostname.size() == 0) { + if (request->hostname.size() == 0) { const auto isAllowedNodeCoreModule = allowedNodeCoreModules.end() != std::find( allowedNodeCoreModules.begin(), allowedNodeCoreModules.end(), - request.pathname.substr(1) + request->pathname.substr(1) ); if (!isAllowedNodeCoreModule) { @@ -778,7 +775,7 @@ namespace SSC::IPC { return callback(response); } - auto pathname = request.pathname; + auto pathname = request->pathname; if (!pathname.ends_with(".js")) { pathname += ".js"; @@ -795,7 +792,7 @@ namespace SSC::IPC { if (!resource.exists()) { if (!pathname.ends_with(".js")) { - pathname = request.pathname; + pathname = request->pathname; if (!pathname.starts_with("/")) { pathname = "/" + pathname; @@ -905,38 +902,38 @@ namespace SSC::IPC { }); this->schemeHandlers.registerSchemeHandler(scheme, [this]( - const auto& request, + const auto request, const auto bridge, auto& callbacks, auto callback ) { if (this->navigator.serviceWorker.registrations.size() > 0) { - auto hostname = request.hostname; - auto pathname = request.pathname; + auto hostname = request->hostname; + auto pathname = request->pathname; - if (request.scheme == "npm") { + if (request->scheme == "npm") { hostname = this->userConfig["meta_bundle_identifier"]; } - const auto scope = this->navigator.serviceWorker.protocols.getServiceWorkerScope(request.scheme); + const auto scope = this->navigator.serviceWorker.protocols.getServiceWorkerScope(request->scheme); if (scope.size() > 0) { pathname = scope + pathname; } const auto fetch = ServiceWorkerContainer::FetchRequest { - request.method, - request.scheme, + request->method, + request->scheme, hostname, pathname, - request.query, - request.headers, - ServiceWorkerContainer::FetchBody { request.body.size, request.body.bytes }, - ServiceWorkerContainer::Client { request.client.id, this->preload } + request->query, + request->headers, + ServiceWorkerContainer::FetchBody { request->body.size, request->body.bytes }, + ServiceWorkerContainer::Client { request->client.id, this->preload } }; const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { - if (!request.isActive()) { + if (!request->isActive()) { return; } @@ -954,7 +951,7 @@ namespace SSC::IPC { if (fetched) { this->core->setTimeout(32000, [request] () mutable { - if (request.isActive()) { + if (request->isActive()) { auto response = SchemeHandlers::Response(request, 408); response.fail("Protocol handler ServiceWorker request timed out."); } diff --git a/src/ipc/client.hh b/src/ipc/client.hh index b6b445b933..0fc25bc3ff 100644 --- a/src/ipc/client.hh +++ b/src/ipc/client.hh @@ -2,15 +2,13 @@ #define SOCKET_RUNTIME_IPC_CLIENT_H #include "../core/core.hh" +#include "preload.hh" namespace SSC::IPC { struct Client { - struct CurrentRequest { - String url; - }; - - String id; - CurrentRequest currentRequest; + using ID = uint64_t; + ID id = 0; + IPC::Preload preload; }; } #endif diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 148ed7e593..f8fdf3083b 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -599,10 +599,14 @@ namespace SSC::IPC { ) { auto resource = FileResource(Path(userConfig.at("webview_importmap"))); + debug("has importmap config"); + if (resource.exists()) { + debug("importmap exists"); const auto bytes = resource.read(); if (bytes != nullptr) { + debug("injecting importmap"); preload = ( tmpl( R"HTML()HTML", @@ -610,6 +614,8 @@ namespace SSC::IPC { ) + preload ); } + } else { + debug("importmap doesn't excist"); } } diff --git a/src/ipc/router.cc b/src/ipc/router.cc index a2b2ea1499..1767b45797 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -1,5 +1,6 @@ #include "bridge.hh" #include "router.hh" +#include "../core/trace.hh" namespace SSC::IPC { Router::Router (Bridge* bridge) @@ -149,24 +150,31 @@ namespace SSC::IPC { } } + Tracer tracer("IPC::Router"); if (context.async) { - return this->bridge->dispatch([context, msg, callback, this]() mutable { - context.callback(msg, this, [msg, callback, this](const auto result) mutable { + auto span = tracer.span("invoke (async)"); + return this->bridge->dispatch([=, this]() mutable { + context.callback(msg, this, [=, this](const auto result) mutable { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { callback(result); } + + span->end(); }); }); } - context.callback(msg, this, [msg, callback, this](const auto result) mutable { + auto span = tracer.span("invoke (sync)"); + context.callback(msg, this, [=, this](const auto result) mutable { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { callback(result); } + + span->end(); }); return true; diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 82c9f5a013..911f70d1c8 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -9,13 +9,6 @@ using namespace SSC; using namespace SSC::IPC; -namespace SSC::IPC { - static struct { - SchemeHandlers::RequestMap map; - Mutex mutex; - } requests; -} - #if SOCKET_RUNTIME_PLATFORM_APPLE using Task = id; @@ -137,7 +130,6 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer } auto bridge = &window->bridge; - auto request = IPC::SchemeHandlers::Request::Builder(&bridge->schemeHandlers, schemeRequest) .setMethod(String(webkit_uri_scheme_request_get_http_method(schemeRequest))) // copies all request soup headers @@ -146,7 +138,7 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer .setBody(webkit_uri_scheme_request_get_http_body(schemeRequest)) .build(); - const auto handled = bridge->schemeHandlers.handleRequest(request, [=](const auto response) { + const auto handled = bridge->schemeHandlers.handleRequest(request, [=](const auto& response) { // TODO(@jwerle): handle uri scheme response }); @@ -295,9 +287,15 @@ namespace SSC::IPC { } bool SchemeHandlers::hasHandlerForScheme (const String& scheme) { + Lock lock(this->mutex); return this->handlers.contains(scheme); } + SchemeHandlers::Handler& SchemeHandlers::getHandlerForScheme (const String& scheme) { + Lock lock(this->mutex); + return this->handlers.at(scheme); + } + bool SchemeHandlers::registerSchemeHandler (const String& scheme, const Handler& handler) { if (scheme.size() == 0 || this->hasHandlerForScheme(scheme)) { return false; @@ -378,30 +376,29 @@ namespace SSC::IPC { } bool SchemeHandlers::handleRequest ( - const Request& request, + SharedPointer request, const HandlerCallback callback ) { // request was not finalized, likely not from a `Request::Builder` - if (!request.finalized) { + if (request == nullptr || !request->finalized) { return false; } - Lock lock(requests.mutex); - if (requests.map.contains(request.id)) { + if (this->isRequestActive(request->id)) { return false; } // respond with a 404 if somehow we are trying to respond to a // request scheme we do not know about, we do not need to call // `request.finalize()` as we'll just respond to the request right away - if (!this->handlers.contains(request.scheme)) { + if (!this->hasHandlerForScheme(request->scheme)) { auto response = IPC::SchemeHandlers::Response(request, 404); // make sure the response was finished first response.finish(); // notify finished, even for 404 - if (response.request.callbacks.finish != nullptr) { - response.request.callbacks.finish(); + if (response.request->callbacks.finish != nullptr) { + response.request->callbacks.finish(); } if (callback != nullptr) { @@ -411,75 +408,72 @@ namespace SSC::IPC { return true; } - const auto handler = this->handlers.at(request.scheme); + const auto handler = this->getHandlerForScheme(request->scheme); + const auto id = request->id; // fail if there is somehow not a handler for this request scheme if (handler == nullptr) { return false; } - if (request.error != nullptr) { + do { + Lock lock(this->mutex); + this->activeRequests.insert_or_assign(request->id, request); + } while (0); + + if (request->error != nullptr) { + Lock lock(this->mutex); auto response = IPC::SchemeHandlers::Response(request, 500); - response.fail(request.error); + response.fail(request->error); + this->activeRequests.erase(id); return true; } - const auto result = requests.map.insert_or_assign(request.id, std::move(request)); - const auto id = request.id; + auto span = request->tracer.span("handler"); - #if !SOCKET_RUNTIME_PLATFORM_ANDROID - this->bridge->dispatch([=, this] { - #endif - if (request.isActive() && !request.isCancelled()) { - // stored request reference - auto& req = result.first->second; - - handler(req, this->bridge, req.callbacks, [this, id, callback](auto& response) { + this->bridge->dispatch([=, this] () mutable { + if (request != nullptr && request->isActive() && !request->isCancelled()) { + handler(request, this->bridge, request->callbacks, [=, this](auto& response) mutable { // make sure the response was finished before // calling the `callback` function below response.finish(); // notify finished - if (response.request.callbacks.finish != nullptr) { - response.request.callbacks.finish(); + if (response.request->callbacks.finish != nullptr) { + response.request->callbacks.finish(); } if (callback != nullptr) { callback(response); } - Lock lock(requests.mutex); - requests.map.erase(id); + span->end(); + + do { + Lock lock(this->mutex); + this->activeRequests.erase(id); + } while (0); }); } - #if !SOCKET_RUNTIME_PLATFORM_ANDROID }); - #endif return true; } bool SchemeHandlers::isRequestActive (uint64_t id) { - Lock lock(requests.mutex); - return requests.map.contains(id); + Lock lock(this->mutex); + return this->activeRequests.contains(id); } bool SchemeHandlers::isRequestCancelled (uint64_t id) { - Lock lock(requests.mutex); + Lock lock(this->mutex); return ( id > 0 && - requests.map.contains(id) && - requests.map.at(id).cancelled + this->activeRequests.contains(id) && + this->activeRequests.at(id)->cancelled ); } - void SchemeHandlers::configureWebView (WebView* webview) { - #if SOCKET_RUNTIME_PLATFORM_APPLE - #elif SOCKET_RUNTIME_PLATFORM_LINUX - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - #endif - } - SchemeHandlers::Request::Builder::Builder ( SchemeHandlers* handlers, PlatformRequest platformRequest @@ -511,13 +505,13 @@ namespace SSC::IPC { ? userConfig.at("meta_bundle_identifier") : ""; - this->request.reset(new Request( + this->request = std::make_shared( handlers, platformRequest, Request::Options { .scheme = url.scheme } - )); + ); // default client id, can be overloaded with 'runtime-client-id' header this->request->client.id = handlers->bridge->id; @@ -674,10 +668,10 @@ namespace SSC::IPC { return *this; } - SchemeHandlers::Request& SchemeHandlers::Request::Builder::build () { + SharedPointer SchemeHandlers::Request::Builder::build () { this->request->error = this->error; this->request->finalize(); - return *this->request; + return this->request; } SchemeHandlers::Request::Request ( @@ -693,12 +687,15 @@ namespace SSC::IPC { pathname(options.pathname), query(options.query), fragment(options.fragment), - headers(options.headers) + headers(options.headers), + tracer("IPC::SchemeHandlers::Request") { this->platformRequest = platformRequest; } - SchemeHandlers::Request::~Request () {} + SchemeHandlers::Request::~Request () { + this->platformRequest = nullptr; + } static void copyRequest ( SchemeHandlers::Request* destination, @@ -728,15 +725,20 @@ namespace SSC::IPC { destination->cancelled = source.cancelled.load(); destination->bridge = source.bridge; + destination->tracer = source.tracer; destination->handlers = source.handlers; destination->platformRequest = source.platformRequest; } - SchemeHandlers::Request::Request (const Request& request) noexcept { + SchemeHandlers::Request::Request (const Request& request) noexcept + : tracer("IPC::SchemeHandlers::Request") + { copyRequest(this, request); } - SchemeHandlers::Request::Request (Request&& request) noexcept { + SchemeHandlers::Request::Request (Request&& request) noexcept + : tracer("SchemeHandlers::Request") + { copyRequest(this, request); } @@ -768,7 +770,7 @@ namespace SSC::IPC { const String SchemeHandlers::Request::str () const { if (this->hostname.size() > 0) { - return ( + return trim( this->scheme + "://" + this->hostname + @@ -778,7 +780,7 @@ namespace SSC::IPC { ); } - return ( + return trim( this->scheme + ":" + this->pathname.substr(1) + @@ -836,17 +838,18 @@ namespace SSC::IPC { } SchemeHandlers::Response::Response ( - const Request& request, + SharedPointer request, int statusCode, const Headers headers - ) : request(std::move(request)), - handlers(request.handlers), - client(request.client), - id(request.id) + ) : request(request), + handlers(request->handlers), + client(request->client), + id(request->id), + tracer("IPC::SchemeHandlers::Response") { const auto defaultHeaders = split( - this->request.bridge->userConfig.contains("webview_headers") - ? this->request.bridge->userConfig.at("webview_headers") + this->request->bridge->userConfig.contains("webview_headers") + ? this->request->bridge->userConfig.at("webview_headers") : "", '\n' ); @@ -876,17 +879,20 @@ namespace SSC::IPC { destination->finished = source.finished.load(); destination->handlers = source.handlers; destination->statusCode = source.statusCode; + destination->ownedBuffers = source.ownedBuffers; destination->platformResponse = source.platformResponse; } SchemeHandlers::Response::Response (const Response& response) noexcept - : request(response.request) + : request(response.request), + tracer("IPC::SchemeHandlers::Response") { copyResponse(this, response); } SchemeHandlers::Response::Response (Response&& response) noexcept - : request(response.request) + : request(response.request), + tracer("IPC::SchemeHandlers::Response") { copyResponse(this, response); } @@ -923,7 +929,7 @@ namespace SSC::IPC { return false; } - if (this->request.platformRequest == nullptr) { + if (this->request->platformRequest == nullptr) { debug("IPC::SchemeHandlers::Response: Failed to write head. Request is in an invalid state"); return false; } @@ -949,7 +955,7 @@ namespace SSC::IPC { headerFields[@(entry.name.c_str())] = @(entry.value.c_str()); } - auto platformRequest = this->request.platformRequest; + auto platformRequest = this->request->platformRequest; if (platformRequest != nullptr && platformRequest.request != nullptr) { const auto url = platformRequest.request.URL; if (url != nullptr) { @@ -978,8 +984,11 @@ namespace SSC::IPC { } catch (...) {} } - this->platformResponseStream = g_memory_input_stream_new(); - this->platformResponse = webkit_uri_scheme_response_new(platformResponseStream, size); + if (this->platformResponseStream == nullptr) { + this->platformResponseStream = g_memory_input_stream_new(); + } + + this->platformResponse = webkit_uri_scheme_response_new(this->platformResponseStream, size); auto requestHeaders = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); for (const auto& entry : this->headers) { @@ -1017,7 +1026,7 @@ namespace SSC::IPC { CallClassMethodFromAndroidEnvironment( attachment.env, Object, - this->request.platformRequest, + this->request->platformRequest, "getResponse", "()Lsocket/runtime/ipc/SchemeHandlers$Response;" ) @@ -1061,6 +1070,8 @@ namespace SSC::IPC { } bool SchemeHandlers::Response::write (size_t size, const char* bytes) { + Lock lock(this->mutex); + if ( !this->handlers->isRequestActive(this->id) || this->handlers->isRequestCancelled(this->id) @@ -1086,20 +1097,25 @@ namespace SSC::IPC { #if SOCKET_RUNTIME_PLATFORM_APPLE const auto data = [NSData dataWithBytes: bytes length: size]; @try { - [this->request.platformRequest didReceiveData: data]; + [this->request->platformRequest didReceiveData: data]; } @catch (::id) { return false; } return true; #elif SOCKET_RUNTIME_PLATFORM_LINUX - auto data = new char[size]{0}; - memcpy(data, bytes, size); + auto span = this->tracer.span("write"); + auto buffer = new char[size]{0}; + memcpy(buffer, bytes, size); g_memory_input_stream_add_data( reinterpret_cast(this->platformResponseStream), - reinterpret_cast(data), + reinterpret_cast(buffer), (gssize) size, - g_free + nullptr ); + span->end(); + this->request->bridge->core->setTimeout(1024, [buffer] () { + delete [] buffer; + }); return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID @@ -1135,14 +1151,23 @@ namespace SSC::IPC { bool SchemeHandlers::Response::write (size_t size, SharedPointer bytes) { if (bytes != nullptr && size > 0) { - return this->write(size, bytes.get()); + this->ownedBuffers.push_back(bytes); // claim ownership for life time of request + return this->write(size, this->ownedBuffers.back().get()); } return false; } bool SchemeHandlers::Response::write (const String& source) { - return this->write(source.size(), source.data()); + const auto size = source.size(); + + if (size == 0) { + return false; + } + + auto bytes = std::make_shared(size); + memcpy(bytes.get(), source.c_str(), size); + return this->write(size, bytes); } bool SchemeHandlers::Response::write (const JSON::Any& json) { @@ -1152,10 +1177,12 @@ namespace SSC::IPC { bool SchemeHandlers::Response::write (const FileResource& resource) { auto responseResource = FileResource(resource); + auto app = App::sharedApplication(); + const auto contentLength = responseResource.size(); const auto contentType = responseResource.mimeType(); - if (contentType.size() > 0) { + if (contentType.size() > 0 && !this->hasHeader("content-type")) { this->setHeader("content-type", contentType); } @@ -1164,13 +1191,23 @@ namespace SSC::IPC { } if (contentLength > 0) { - const auto data = responseResource.read(); - return this->write(responseResource.size(), data); + this->pendingWrites++; + this->writeHead(); + const auto bytes = responseResource.read(); + const auto result = this->write(contentLength, bytes); + this->pendingWrites--; + return result; } return false; } + bool SchemeHandlers::Response::write ( + const FileResource::ReadStream::Buffer& buffer + ) { + return this->write(buffer.size, buffer.bytes); + } + bool SchemeHandlers::Response::send (const String& source) { return this->write(source) && this->finish(); } @@ -1189,6 +1226,10 @@ namespace SSC::IPC { return false; } + while (this->pendingWrites > 0) { + msleep(1); + } + if ( !this->handlers->isRequestActive(this->id) || this->handlers->isRequestCancelled(this->id) @@ -1197,53 +1238,69 @@ namespace SSC::IPC { } if (!this->platformResponse) { - if (!this->writeHead()) { + if (!this->writeHead() || !this->platformResponse) { return false; } } #if SOCKET_RUNTIME_PLATFORM_APPLE @try { - [this->request.platformRequest didFinish]; + [this->request->platformRequest didFinish]; } @catch (::id) {} #if !__has_feature(objc_arc) [this->platformResponse release]; #endif + this->platformResponse = nullptr; #elif SOCKET_RUNTIME_PLATFORM_LINUX - webkit_uri_scheme_request_finish_with_response( - this->request.platformRequest, - this->platformResponse - ); - g_object_unref(this->platformResponseStream); - this->platformResponseStream = nullptr; + if (this->request->platformRequest) { + auto platformRequest = this->request->platformRequest; + auto platformResponse = this->platformResponse; + auto platformResponseStream = this->platformResponseStream; + + this->platformResponseStream = nullptr; + this->platformResponse = nullptr; + + //if (WEBKIT_IS_URI_SCHEME_REQUEST(platformRequest)) { + webkit_uri_scheme_request_finish_with_response( + platformRequest, + platformResponse + ); + //} + + if (platformResponseStream) { + g_input_stream_close(platformResponseStream, nullptr, nullptr); + g_object_unref(platformResponseStream); + } + } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID - auto platformResponse = this->platformResponse; - if (platformResponse != nullptr) { - this->request.bridge->dispatch([=, this] () { + if (this->platformResponse != nullptr) { + auto ownedBuffers = this->ownedBuffers; + this->request->bridge->dispatch([=, this] () { auto app = App::sharedApplication(); auto attachment = Android::JNIEnvironmentAttachment(app->jvm); CallVoidClassMethodFromAndroidEnvironment( attachment.env, - platformResponse, + this->platformResponse, "finish", "()V" ); - attachment.env->DeleteGlobalRef(platformResponse); + this->platformResponse = nullptr; + attachment.env->DeleteGlobalRef(this->platformResponse); }); } #else + this->platformResponse = nullptr; #endif - this->platformResponse = nullptr; this->finished = true; return true; } void SchemeHandlers::Response::setHeader (const String& name, const Headers::Value& value) { - const auto bridge = this->request.handlers->bridge; + const auto bridge = this->request->handlers->bridge; if (toLowerCase(name) == "referer") { if (bridge->navigator.location.workers.contains(value.string)) { const auto workerLocation = bridge->navigator.location.workers[value.string]; @@ -1312,8 +1369,8 @@ namespace SSC::IPC { } bool SchemeHandlers::Response::fail (const String& reason) { - const auto bundleIdentifier = this->request.bridge->userConfig.contains("meta_bundle_identifier") - ? this->request.bridge->userConfig.at("meta_bundle_identifier") + const auto bundleIdentifier = this->request->bridge->userConfig.contains("meta_bundle_identifier") + ? this->request->bridge->userConfig.at("meta_bundle_identifier") : ""; if ( @@ -1325,13 +1382,14 @@ namespace SSC::IPC { } #if SOCKET_RUNTIME_PLATFORM_APPLE + const auto error = [NSError + errorWithDomain: @(bundleIdentifier.c_str()) + code: 1 + userInfo: @{NSLocalizedDescriptionKey: @(reason.c_str())} + ]; + @try { - [this->request.platformRequest - didFailWithError: [NSError - errorWithDomain: @(bundleIdentifier.c_str()) - code: 1 - userInfo: @{NSLocalizedDescriptionKey: @(reason.c_str())} - ]]; + [this->request->platformRequest didFailWithError: error]; } @catch (::id e) { // ignore possible 'NSInternalInconsistencyException' return false; @@ -1348,11 +1406,18 @@ namespace SSC::IPC { return false; } - webkit_uri_scheme_request_finish_error(this->request.platformRequest, error); + if (WEBKIT_IS_URI_SCHEME_REQUEST(this->request->platformRequest)) { + webkit_uri_scheme_request_finish_error(this->request->platformRequest, error); + } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID - #else #endif + + // notify fail + if (this->request->callbacks.fail != nullptr) { + this->request->callbacks.fail(error); + } + this->finished = true; return true; } @@ -1368,9 +1433,9 @@ namespace SSC::IPC { } if (location.starts_with("/")) { - this->setHeader("location", this->request.origin + location); + this->setHeader("location", this->request->origin + location); } else if (location.starts_with(".")) { - this->setHeader("location", this->request.origin + location.substr(1)); + this->setHeader("location", this->request->origin + location.substr(1)); } else { this->setHeader("location", location); } @@ -1379,7 +1444,7 @@ namespace SSC::IPC { return false; } - if (this->request.method != "HEAD" && this->request.method != "OPTIONS") { + if (this->request->method != "HEAD" && this->request->method != "OPTIONS") { const auto content = tmpl( redirectSourceTemplate, {{"url", location}} diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 63d1216d0b..8e96706018 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -2,19 +2,31 @@ #define SOCKET_RUNTIME_IPC_SCHEME_HANDLERS_H #include "../core/core.hh" +#include "../core/trace.hh" #include "../core/webview.hh" #if SOCKET_RUNTIME_PLATFORM_ANDROID #include "../android/platform.hh" #endif +#include "client.hh" + namespace SSC::IPC { class Bridge; class SchemeHandlers; } namespace SSC::IPC { + /** + * An opaque containere for platform internals + */ class SchemeHandlersInternals; + + /** + * A container for registering scheme handlers attached to an `IPC::Bridge` + * that can handle WebView requests for runtime and custom user defined + * protocol schemes. + */ class SchemeHandlers { private: SchemeHandlersInternals* internals = nullptr; @@ -25,13 +37,9 @@ namespace SSC::IPC { #elif SOCKET_RUNTIME_PLATFORM_LINUX using Error = GError; #else - using Error = char; + using Error = char*; #endif - struct Client { - uint64_t id = 0; - }; - struct Body { size_t size = 0; SharedPointer bytes = nullptr; @@ -40,6 +48,7 @@ namespace SSC::IPC { struct RequestCallbacks { Function cancel; Function finish; + Function fail; }; #if SOCKET_RUNTIME_PLATFORM_APPLE @@ -55,7 +64,7 @@ namespace SSC::IPC { using PlatformRequest = jobject; using PlatformResponse = jobject; #else - // TODO: error + #error "IPC::SchemeHandlers are not supported on this platform" #endif struct Request { @@ -73,7 +82,7 @@ namespace SSC::IPC { struct Builder { String absoluteURL; Error* error = nullptr; - UniquePointer request = nullptr; + SharedPointer request = nullptr; Builder ( SchemeHandlers* handlers, @@ -101,7 +110,7 @@ namespace SSC::IPC { Builder& setBody (const Body& body); Builder& setBody (size_t size, const char* bytes); Builder& setCallbacks (const RequestCallbacks& callbacks); - Request& build (); + SharedPointer build (); }; uint64_t id = rand64(); @@ -120,6 +129,8 @@ namespace SSC::IPC { String originalURL; RequestCallbacks callbacks; + Tracer tracer; + Atomic finalized = false; Atomic cancelled = false; @@ -128,6 +139,7 @@ namespace SSC::IPC { SchemeHandlers* handlers = nullptr; PlatformRequest platformRequest; + Request () = delete; Request ( SchemeHandlers* handlers, PlatformRequest platformRequest, @@ -158,12 +170,21 @@ namespace SSC::IPC { size_t count () const noexcept; }; - const Request request; + SharedPointer request; uint64_t id = rand64(); int statusCode = 200; Headers headers; Client client; + + Mutex mutex; + Atomic pendingWrites = 0; Atomic finished = false; + + Vector> ownedBuffers; + Vector writeThreads; + + Tracer tracer; + SchemeHandlers* handlers = nullptr; PlatformResponse platformResponse = nullptr; @@ -172,7 +193,7 @@ namespace SSC::IPC { #endif Response ( - const Request& request, + SharedPointer request, int statusCode = 200, const Headers headers = {} ); @@ -188,6 +209,7 @@ namespace SSC::IPC { bool write (const String& source); bool write (const JSON::Any& json); bool write (const FileResource& resource); + bool write (const FileResource::ReadStream::Buffer& buffer); bool send (const String& source); bool send (const JSON::Any& json); bool send (const FileResource& resource); @@ -214,14 +236,14 @@ namespace SSC::IPC { using HandlerCallback = Function; using Handler = Function, const Bridge*, RequestCallbacks& callbacks, HandlerCallback )>; using HandlerMap = std::map; - using RequestMap = std::map; + using RequestMap = std::map>; struct Configuration { WebViewSettings* webview; @@ -230,7 +252,9 @@ namespace SSC::IPC { Configuration configuration; HandlerMap handlers; + Mutex mutex; Bridge* bridge = nullptr; + RequestMap activeRequests; #if SOCKET_RUNTIME_PLATFORM_WINDOWS Set> coreWebView2CustomSchemeRegistrations; @@ -238,15 +262,20 @@ namespace SSC::IPC { SchemeHandlers (Bridge* bridge); ~SchemeHandlers (); + SchemeHandlers (const SchemeHandlers&) = delete; + SchemeHandlers (SchemeHandlers&&) = delete; + + SchemeHandlers& operator= (const SchemeHandlers&) = delete; + SchemeHandlers& operator= (SchemeHandlers&&) = delete; void init (); void configure (const Configuration& configuration); - void configureWebView (WebView* webview); bool hasHandlerForScheme (const String& scheme); bool registerSchemeHandler (const String& scheme, const Handler& handler); - bool handleRequest (const Request& request, const HandlerCallback calllback = nullptr); + bool handleRequest (SharedPointer request, const HandlerCallback calllback = nullptr); bool isRequestActive (uint64_t id); bool isRequestCancelled (uint64_t id); + Handler& getHandlerForScheme (const String& scheme); }; } #endif From a9b0d77d15867a5114a3547d9f3d7b191bef72f8 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:46:13 +0200 Subject: [PATCH 0756/1178] refactor(cli): include 'libllama' in linux build --- src/cli/cli.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 8e5bb63dac..5b3ac07888 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -4820,6 +4820,7 @@ int main (const int argc, const char* argv[]) { files += prefixFile("src/init.cc"); files += prefixFile("lib/" + platform.arch + "-desktop/libsocket-runtime.a"); files += prefixFile("lib/" + platform.arch + "-desktop/libuv.a"); + files += prefixFile("lib/" + platform.arch + "-desktop/libllama.a"); pathResources = paths.pathBin; From d1338f5498c6ab007d1c348c7c67136bea83d935 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:46:40 +0200 Subject: [PATCH 0757/1178] refactor(bin): improve building llama for desktop --- bin/cflags.sh | 1 + bin/install.sh | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/bin/cflags.sh b/bin/cflags.sh index 0f1c5ded8d..53522abab7 100755 --- a/bin/cflags.sh +++ b/bin/cflags.sh @@ -115,6 +115,7 @@ done if [[ -n "$DEBUG" ]]; then cflags+=("-g") cflags+=("-O0") + cflags+=("-DSOCKET_RUNTIME_BUILD_DEBUG=1") else cflags+=("-Os") fi diff --git a/bin/install.sh b/bin/install.sh index 7c5a01fa5a..30102a86d0 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -146,6 +146,9 @@ if [[ "$host" != "Win32" ]]; then fi fi +quiet command -v cmake +die $? "not ok - missing cmake, \"$(advice 'cmake')\"" + if [[ "$(uname -s)" != *"_NT"* ]]; then quiet command -v make die $? "not ok - missing build tools, try \"$(advice "make")\"" @@ -799,13 +802,22 @@ function _compile_llama { mkdir -p "$STAGING_DIR/build/" if [ "$platform" == "desktop" ]; then + declare cmake_args=( + -DBUILD_TESTING=OFF + -DLLAMA_BUILD_TESTS=OFF + -DLLAMA_BUILD_SERVER=OFF + -DLLAMA_BUILD_SHARED=OFF + -DLLAMA_BUILD_EXAMPLES=OFF + ) + if [[ "$host" != "Win32" ]]; then - quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" - die $? "not ok - desktop configure" - - quiet cmake --build build --target clean - quiet cmake --build build -- -j"$CPU_CORES" + quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" ${cmake_args[@]} + die $? "not ok - libllama.a (desktop)" + + quiet cmake --build build --target clean && + quiet cmake --build build -- -j"$CPU_CORES" && quiet cmake --install build + die $? "not ok - libllama.a (desktop)" else if ! test -f "$BUILD_DIR/$target-$platform/lib$d/libllama.lib"; then local config="Release" @@ -813,7 +825,7 @@ function _compile_llama { config="Debug" fi cd "$STAGING_DIR/build/" || exit 1 - quiet cmake -S .. -B . -DBUILD_TESTING=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_SERVER=OFF -DLLAMA_BUILD_SHARED=OFF + quiet cmake -S .. -B . ${cmake_args[@]} quiet cmake --build . --config $config mkdir -p "$BUILD_DIR/$target-$platform/lib$d" quiet echo "cp -up $STAGING_DIR/build/$config/libllama.lib "$BUILD_DIR/$target-$platform/lib$d/libllama.lib"" From edda094746e98627b954b0b4b13f74ea41799253 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:47:51 +0200 Subject: [PATCH 0758/1178] refactor(api/fs): increase high water mark --- api/fs/constants.js | 1 + api/fs/stream.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/fs/constants.js b/api/fs/constants.js index e6460c2bcb..f4625f2764 100644 --- a/api/fs/constants.js +++ b/api/fs/constants.js @@ -31,6 +31,7 @@ export const UV_DIRENT_CHAR = constants.UV_DIRENT_CHAR || 6 export const UV_DIRENT_BLOCK = constants.UV_DIRENT_BLOCK || 7 export const UV_FS_SYMLINK_DIR = constants.UV_FS_SYMLINK_DIR || 1 export const UV_FS_SYMLINK_JUNCTION = constants.UV_FS_SYMLINK_JUNCTION || 2 +export const UV_FS_O_FILEMAP = constants.UV_FS_O_FILEMAP || 0 export const O_RDONLY = constants.O_RDONLY || 0 export const O_WRONLY = constants.O_WRONLY || 1 diff --git a/api/fs/stream.js b/api/fs/stream.js index 52dfe96570..368d27ea7e 100644 --- a/api/fs/stream.js +++ b/api/fs/stream.js @@ -6,7 +6,7 @@ import { AbortError } from '../errors.js' import * as exports from './stream.js' -export const DEFAULT_STREAM_HIGH_WATER_MARK = 256 * 1024 +export const DEFAULT_STREAM_HIGH_WATER_MARK = 512 * 1024 /** * @typedef {import('./handle.js').FileHandle} FileHandle From 1d1c74a2274a68c564444b0a06c895467a457ff6 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 15:48:23 +0200 Subject: [PATCH 0759/1178] refactor(serviceworker): improve architecture --- src/serviceworker/container.hh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/serviceworker/container.hh b/src/serviceworker/container.hh index 8fc196c9ad..5b1d7193f4 100644 --- a/src/serviceworker/container.hh +++ b/src/serviceworker/container.hh @@ -3,6 +3,7 @@ #include "../core/headers.hh" #include "../core/json.hh" +#include "../ipc/client.hh" #include "../ipc/preload.hh" #include "protocols.hh" @@ -14,7 +15,8 @@ namespace SSC { class ServiceWorkerContainer { public: - using ID = uint64_t; + using ID = IPC::Client::ID; + using Client = IPC::Client; struct RegistrationOptions { enum class Type { Classic, Module }; @@ -26,11 +28,6 @@ namespace SSC { ID id = 0; }; - struct Client { - ID id = 0; - IPC::Preload preload; - }; - struct Registration { enum class State { None, @@ -135,5 +132,4 @@ namespace SSC { bool claimClients (const String& scope); }; } - #endif From 4ba99d18db54f2b22e4da209e430de06d8c1a886 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 27 May 2024 16:02:11 +0200 Subject: [PATCH 0760/1178] refactor(core,ipc): fix typos for apple --- src/core/json.cc | 10 +++++----- src/core/json.hh | 3 ++- src/core/resource.cc | 5 +++-- src/ipc/scheme_handlers.cc | 13 ++++++------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/json.cc b/src/core/json.cc index 4ec5f12c67..8bdf0bab7b 100644 --- a/src/core/json.cc +++ b/src/core/json.cc @@ -144,11 +144,6 @@ namespace SSC::JSON { this->type = Type::Number; } - Any::Any (long long number) { - this->pointer = SharedPointer(new Number((double) number)); - this->type = Type::Number; - } - #if SOCKET_RUNTIME_PLATFORM_APPLE Any::Any (size_t number) { this->pointer = SharedPointer(new Number((double) number)); @@ -159,6 +154,11 @@ namespace SSC::JSON { this->pointer = SharedPointer(new Number((double) number)); this->type = Type::Number; } +#else + Any::Any (long long number) { + this->pointer = SharedPointer(new Number((double) number)); + this->type = Type::Number; + } #endif Any::Any (const Number number) { diff --git a/src/core/json.hh b/src/core/json.hh index f4921453d0..cc174f1e98 100644 --- a/src/core/json.hh +++ b/src/core/json.hh @@ -177,10 +177,11 @@ namespace SSC::JSON { Any (uint32_t); Any (int32_t); Any (double); - Any (long long); #if SOCKET_RUNTIME_PLATFORM_APPLE Any (size_t); Any (ssize_t); + #else + Any (long long); #endif Any (const Number); Any (const char); diff --git a/src/core/resource.cc b/src/core/resource.cc index 1aea300223..0cd6e213c8 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -800,10 +800,11 @@ namespace SSC { if (buffer.size > 0 && buffer.bytes != nullptr) { #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->data == nullptr) { - this->data = [NSData dataWithContentsOfURL: this->resource.url]; + auto url = [NSURL fileURLWithPath: @(this->options.resourcePath.string().c_str())]; + this->data = [NSData dataWithContentsOfURL: url]; @try { [this->data - getBytes: buffer.bytes.get(), + getBytes: buffer.bytes.get() range: NSMakeRange(offset, size) ]; } @catch (NSException* error) { diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 911f70d1c8..e837bf4043 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -53,14 +53,13 @@ using Task = id; - (void) webView: (SSCWebView*) webview stopURLSchemeTask: (Task) task { - Lock lock(requests.mutex); if (tasks.contains(task)) { const auto id = tasks[task]; - if (requests.map.contains(id)) { - auto& request = requests.map.at(id); - request.cancelled = true; - if (request.callbacks.cancel != nullptr) { - request.callbacks.cancel(); + if (self.handlers->isRequestActive(id)) { + auto request = self.handlers->activeRequests[id]; + request->cancelled = true; + if (request->callbacks.cancel != nullptr) { + request->callbacks.cancel(); } } } @@ -93,7 +92,7 @@ using Task = id; .setBody(task.request.HTTPBody) .build(); - [self enqueueTask: task withRequestID: request.id]; + [self enqueueTask: task withRequestID: request->id]; const auto handled = self.handlers->handleRequest(request, [=](const auto& response) { [self finalizeTask: task]; }); From 04ee2e02ba8d441e9b1744c4d876655170f7c541 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Mon, 27 May 2024 15:39:42 +0200 Subject: [PATCH 0761/1178] docs(ai): improve docs --- api/ai.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/api/ai.js b/api/ai.js index 791371d83b..787c649080 100644 --- a/api/ai.js +++ b/api/ai.js @@ -2,11 +2,32 @@ /** * @module AI * - * Provides high level classes for common AI tasks + * Provides high level classes for common AI tasks. + * + * If you download a model like `mistral-7b-openorca.Q4_0.gguf` from Hugging + * Face, you can construct in JavaScript with a prompt. Prompt syntax isn't + * concrete like programming syntax, so you'll usually want to know what the + * author has to say about prompting, for example this might be worth reading... + * + * https://docs.mistral.ai/guides/prompting_capabilities * * Example usage: + * * ```js * import { LLM } from 'socket:ai' + * + * const llm = new LLM({ + * path: 'model.gguf', + * prompt: '...' // insert your prompt here. + * }) + * + * llm.on('end', () => { + * // end of the token stream. + * }) + * + * llm.on('data', data => { + * // a new token has arrived in the token stream. + * }) * ``` */ import ipc from './ipc.js' From 5d471fd833e91e7e68670c1e90ed62c88796ed45 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Tue, 28 May 2024 17:50:03 +0200 Subject: [PATCH 0762/1178] fix(api): fixes module resolution for multi-platform packages --- api/commonjs/module.js | 45 ++++++++++++++++++++++++++++----------- api/commonjs/package.js | 1 + api/commonjs/require.js | 10 ++++++--- api/npm/module.js | 7 +++++- api/npm/service-worker.js | 8 ++++++- 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/api/commonjs/module.js b/api/commonjs/module.js index d0fd3af21b..d31c75e14a 100644 --- a/api/commonjs/module.js +++ b/api/commonjs/module.js @@ -8,6 +8,7 @@ import { Loader } from './loader.js' import location from '../location.js' import process from '../process.js' import path from '../path.js' +import fs from '../fs.js' /** * @typedef {function(string, Module, function(string): any): any} ModuleResolver @@ -417,23 +418,40 @@ export class Module extends EventTarget { return this.cache.get(location.origin) } + let packageInfo = '' + + try { + packageInfo = fs.readFileSync('package.json', 'utf8') + } catch (err) { + } + + if (packageInfo.length) { + try { + packageInfo = JSON.parse(packageInfo) + } catch (err) { + console.warn(err) + packageInfo = null + } + } + const main = new Module(location.origin, { state: new State({ loaded: true }), parent: null, package: new Package(location.origin, { id: location.href, type: 'commonjs', - info: application.config, + info: packageInfo || application.config, index: '', prefix: '', manifest: '', // eslint-disable-next-line - name: application.config.build_name, + name: packageInfo?.name || application.config.build_name, // eslint-disable-next-line - version: application.config.meta_version, + version: packageInfo?.version || application.config.meta_version, // eslint-disable-next-line - description: application.config.meta_description, + description: packageInfo?.description || application.config.meta_description, + imports: packageInfo?.imports || {}, exports: { '.': { default: location.pathname @@ -620,15 +638,18 @@ export class Module extends EventTarget { // includes `.browser` field mapping for (const key in this.package.imports) { const value = this.package.imports[key] - if (value) { - this.#resolvers.push((specifier, ctx, next) => { - if (specifier === key) { - return value.default ?? value.browser ?? next(specifier) - } + if (!value) continue + + this.#resolvers.push((specifier, ctx, next) => { + if (specifier === key) { + return (typeof value === 'string' + ? value + : value.default ?? value.browser ?? next(specifier) + ) + } - return next(specifier) - }) - } + return next(specifier) + }) } if (this.#parent) { diff --git a/api/commonjs/package.js b/api/commonjs/package.js index d35adf99e8..ba5ee6f467 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -601,6 +601,7 @@ export class Package { ? options.type : this.#type this.#exports = options.exports ?? this.#exports + this.#imports = options.imports ?? this.#imports this.#license = options.license ?? DEFAULT_LICENSE this.#version = options.version ?? this.#name.version ?? DEFAULT_PACKAGE_VERSION this.#description = options.description ?? '' diff --git a/api/commonjs/require.js b/api/commonjs/require.js index c71918e495..9644a447a4 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -104,9 +104,14 @@ export function createRequire (options) { paths }) + const allResolvers = module.resolvers + .concat(resolvers) + .concat(main.resolvers) + .filter(isFunction) + return Object.assign(require, { extensions: loaders, - resolvers: module.resolvers.concat(resolvers).filter(isFunction), + resolvers: allResolvers, resolve, loaders, module, @@ -151,8 +156,7 @@ export function createRequire (options) { const resolvers = Array .from([]) .concat(options?.resolvers) - .concat(require.resolvers) - .concat(module.resolvers) + .concat(allResolvers) .filter(Boolean) return next(input) diff --git a/api/npm/module.js b/api/npm/module.js index 8875e40eed..8a59dc33a2 100644 --- a/api/npm/module.js +++ b/api/npm/module.js @@ -1,5 +1,6 @@ import { DEFAULT_PACKAGE_PREFIX, Package } from '../commonjs/package.js' import { Loader } from '../commonjs/loader.js' +import { isESMSource } from '../util.js' import location from '../location.js' import path from '../path.js' @@ -50,13 +51,17 @@ export async function resolve (specifier, origin = null, options = null) { try { pkg.load() + const url = pkg.type === type ? pkg.resolve(pathname, { prefix, type }) : pkg.resolve(pathname, { prefix }) + + const src = pkg.loader.load(url).text + return { package: pkg, origin: pkg.origin, - type: pkg.type, + type: isESMSource(src) ? 'module' : 'commonjs', url } } catch (err) { diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index a11d1367a8..ffd7e01198 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -25,9 +25,15 @@ export async function onRequest (request, env, ctx) { const url = new URL(request.url) const origin = url.origin.replace('npm://', 'socket://') const referer = request.headers.get('referer') - const specifier = url.pathname.replace('/socket/npm/', '') + let specifier = url.pathname.replace('/socket/npm/', '') const importOrigins = url.searchParams.getAll('origin').concat(url.searchParams.getAll('origin[]')) + if (typeof specifier === 'string') { + try { + specifier = (new URL(require.resolve(specifier))).toString() + } catch {} + } + debug(`${DEBUG_LABEL}: fetch: %s`, specifier) let resolved = null From e64a1b73009b1f8bb4d17d6cea5f2caca1a0094a Mon Sep 17 00:00:00 2001 From: heapwolf Date: Thu, 30 May 2024 14:20:03 +0200 Subject: [PATCH 0763/1178] refactor(ai): working llm on ios --- api/ai.js | 5 ++ bin/build-runtime-library.sh | 10 +++- bin/cflags.sh | 1 + bin/install.sh | 105 +++++++++++++++++++++++------------ src/cli/cli.cc | 1 + src/cli/templates.hh | 15 +++++ src/core/modules/ai.cc | 52 +++++++++++------ src/core/modules/ai.hh | 2 +- 8 files changed, 133 insertions(+), 58 deletions(-) diff --git a/api/ai.js b/api/ai.js index 787c649080..32057e1dc6 100644 --- a/api/ai.js +++ b/api/ai.js @@ -78,6 +78,11 @@ export class LLM extends EventEmitter { if (!data || BigInt(data.id) !== this.id) return + if (source === 'ai.llm.log') { + this.emit('log', data.message) + return + } + if (source === 'ai.llm.chat') { if (data.complete) { return this.emit('end') diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index c1b632fe04..beeddf2176 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -161,8 +161,6 @@ mkdir -p "$output_directory" cd "$(dirname "$output_directory")" -sources+=("$output_directory/llama/build-info.cpp") - echo "# building runtime static libary ($arch-$platform)" for source in "${sources[@]}"; do declare src_directory="$root/src" @@ -181,6 +179,8 @@ for source in "${sources[@]}"; do objects+=("$object") done +objects+=("$output_directory/llama/build-info.o") + if [[ -z "$ignore_header_mtimes" ]]; then test_headers+="$(find "$root/src"/core/*.hh)" fi @@ -209,12 +209,16 @@ function generate_llama_build_info () { fi echo "# generating llama build info" - cat > "$output_directory/llama/build-info.cpp" << LLAMA_BUILD_INFO + declare source="$output_directory/llama/build-info.cpp" + + cat > $source << LLAMA_BUILD_INFO int LLAMA_BUILD_NUMBER = $build_number; char const *LLAMA_COMMIT = "$build_commit"; char const *LLAMA_COMPILER = "$build_compiler"; char const *LLAMA_BUILD_TARGET = "$build_target"; LLAMA_BUILD_INFO + + quiet $clang "${cflags[@]}" -c $source -o ${source/cpp/o} || onsignal } function main () { diff --git a/bin/cflags.sh b/bin/cflags.sh index 53522abab7..369320cf5a 100755 --- a/bin/cflags.sh +++ b/bin/cflags.sh @@ -50,6 +50,7 @@ cflags+=( $CFLAG $CXXFLAGS -std=c++2a + -ferror-limit=6 -I"$root/include" -I"$root/build/uv/include" -I"$root/build" diff --git a/bin/install.sh b/bin/install.sh index 30102a86d0..1f035d39ae 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -524,7 +524,7 @@ function _prepare { if [ ! -d "$BUILD_DIR/llama" ]; then git clone --depth=1 https://github.com/socketsupply/llama.cpp.git "$BUILD_DIR/llama" > /dev/null 2>&1 - rm -rf $BUILD_DIR/llama/.git + # rm -rf $BUILD_DIR/llama/.git die $? "not ok - unable to clone llama. See trouble shooting guide in the README.md file" fi @@ -544,7 +544,7 @@ function _install { fi fi - # TODO(@mribbons): Set lib types based on platform, after mobile CI is working + # TODO(@heapwolf): Set lib types based on platform, after mobile CI is working if test -d "$BUILD_DIR/$arch-$platform/objects"; then echo "# copying objects to $SOCKET_HOME/objects/$arch-$platform" @@ -570,8 +570,10 @@ function _install { echo "# copying libraries to $SOCKET_HOME/lib$_d/$arch-$platform" rm -rf "$SOCKET_HOME/lib$_d/$arch-$platform" mkdir -p "$SOCKET_HOME/lib$_d/$arch-$platform" + if [[ "$platform" != "android" ]]; then cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.a "$SOCKET_HOME/lib$_d/$arch-$platform" + cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.metallib "$SOCKET_HOME/lib$_d/$arch-$platform" fi if [[ "$host" == "Win32" ]] && [[ "$platform" == "desktop" ]]; then cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.lib "$SOCKET_HOME/lib$_d/$arch-$platform" @@ -778,6 +780,40 @@ function _compile_libuv_android { fi } +function _compile_metal { + target=$1 + hosttarget=$1 + platform=$2 + + if [ -z "$target" ]; then + target="$(host_arch)" + platform="desktop" + fi + + echo "# building METAL for $platform ($target) on $host..." + STAGING_DIR="$BUILD_DIR/$target-$platform/llama" + + if [ ! -d "$STAGING_DIR" ]; then + mkdir -p "$STAGING_DIR" + cp -r "$BUILD_DIR"/llama/* "$STAGING_DIR" + cd "$STAGING_DIR" || exit 1 + else + cd "$STAGING_DIR" || exit 1 + fi + + local sdk="iphoneos" + [[ "$platform" == "iPhoneSimulator" ]] && sdk="iphonesimulator" + + mkdir -p "$STAGING_DIR/build/" + mkdir -p ../lib + + xcrun -sdk $sdk metal -O3 -c ggml-metal.metal -o ggml-metal.air + xcrun -sdk $sdk metallib ggml-metal.air -o ../lib/default.metallib + rm *.air + + echo "ok - metal built for $platform" +} + function _compile_llama { target=$1 hosttarget=$1 @@ -799,22 +835,24 @@ function _compile_llama { cd "$STAGING_DIR" || exit 1 fi + local sdk="iphoneos" + [[ "$platform" == "iPhoneSimulator" ]] && sdk="iphonesimulator" + mkdir -p "$STAGING_DIR/build/" + mkdir -p ../bin - if [ "$platform" == "desktop" ]; then - declare cmake_args=( - -DBUILD_TESTING=OFF - -DLLAMA_BUILD_TESTS=OFF - -DLLAMA_BUILD_SERVER=OFF - -DLLAMA_BUILD_SHARED=OFF - -DLLAMA_BUILD_EXAMPLES=OFF - ) + declare cmake_args=( + -DLLAMA_BUILD_TESTS=OFF + -DLLAMA_BUILD_SERVER=OFF + -DLLAMA_BUILD_EXAMPLES=OFF + ) + if [ "$platform" == "desktop" ]; then if [[ "$host" != "Win32" ]]; then quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" ${cmake_args[@]} die $? "not ok - libllama.a (desktop)" - quiet cmake --build build --target clean && + quiet cmake --build build && quiet cmake --build build -- -j"$CPU_CORES" && quiet cmake --install build die $? "not ok - libllama.a (desktop)" @@ -840,40 +878,28 @@ function _compile_llama { return fi - if [ "$hosttarget" == "arm64" ]; then - hosttarget="arm" - fi + # https://github.com/ggerganov/llama.cpp/discussions/4508 - local sdk="iphoneos" - [[ "$platform" == "iPhoneSimulator" ]] && sdk="iphonesimulator" + declare ar=$(xcrun -sdk $sdk -find ar) + declare cc=$(xcrun -sdk $sdk -find clang) + declare cxx=$(xcrun -sdk $sdk -find clang++) + declare cflags="--target=$target-apple-ios -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION -DLLAMA_METAL_EMBED_LIBRARY=ON" - export PLATFORM=$platform - export CC="$(xcrun -sdk $sdk -find clang)" - export CXX="$(xcrun -sdk $sdk -find clang++)" - export STRIP="$(xcrun -sdk $sdk -find strip)" - export LD="$(xcrun -sdk $sdk -find ld)" - export CPP="$CC -E" - export CFLAGS="-fembed-bitcode -arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION" - export AR=$(xcrun -sdk $sdk -find ar) - export RANLIB=$(xcrun -sdk $sdk -find ranlib) - export CPPFLAGS="-fembed-bitcode -arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION" - export LDFLAGS="-Wc,-fembed-bitcode -arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk" + echo "AR: $ar" + echo "CFLAGS: $cflags" - #if ! test -f CMakeLists.txt; then - quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES="$target" -DCMAKE_OSX_SYSROOT=$(xcrun --sdk $sdk --show-sdk-path) - #fi + AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make libllama.a if [ ! $? = 0 ]; then - echo "WARNING! - iOS will not be enabled. iPhone simulator not found, try \"sudo xcode-select --switch /Applications/Xcode.app\"." + die $? "not ok - iOS will not be enabled. Unable to compile libllama." return fi - cmake --build build -- -j"$CPU_CORES" - cmake --install build + cp libllama.a ../lib cd "$BUILD_DIR" || exit 1 rm -f "$root/build/$target-$platform/lib$d"/*.{so,la,dylib}* - echo "ok - built for $target" + echo "ok - built llama for $target" } function _compile_libuv { @@ -976,7 +1002,7 @@ function _compile_libuv { cd "$BUILD_DIR" || exit 1 rm -f "$root/build/$target-$platform/lib$d"/*.{so,la,dylib}* - echo "ok - built for $target" + echo "ok - built libuv for $target" } function _check_compiler_features { @@ -1025,6 +1051,10 @@ cd "$BUILD_DIR" || exit 1 trap onsignal INT TERM +_compile_metal arm64 iPhoneOS +_compile_metal x86_64 iPhoneSimulator +_compile_metal arm64 iPhoneSimulator + { _compile_llama echo "ok - built llama for $platform ($target)" @@ -1055,6 +1085,7 @@ if [[ "$(uname -s)" == "Darwin" ]] && [[ -z "$NO_IOS" ]]; then _compile_llama x86_64 iPhoneSimulator & pids+=($!) if [[ "$arch" = "arm64" ]]; then + echo "lol" _compile_libuv arm64 iPhoneSimulator & pids+=($!) _compile_llama arm64 iPhoneSimulator & pids+=($!) fi @@ -1073,7 +1104,7 @@ fi if [[ "$host" != "Win32" ]]; then # non windows hosts uses make -j$CPU_CORES, wait for them to finish. - wait $_compile_libuv_pid + # wait $_compile_libuv_pid wait $_compile_llama_pid fi @@ -1097,7 +1128,7 @@ _get_web_view2 if [[ "$host" == "Win32" ]]; then # Wait for Win32 lib uv build - wait $_compile_libuv_pid + # wait $_compile_libuv_pid wait $_compile_llama_pid fi diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 5b3ac07888..d16713ffb9 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -6034,6 +6034,7 @@ int main (const int argc, const char* argv[]) { auto d = String(debugBuild ? "d" : ""); auto static_uv = prefixFile("lib" + d + "\\" + platform.arch + "-desktop\\libuv.lib"); auto static_runtime = trim(prefixFile("lib" + d + "\\" + platform.arch + "-desktop\\libsocket-runtime" + d + ".a")); + // TODO(@heapwolf): llama for win32 #else auto d = ""; auto static_uv = ""; diff --git a/src/cli/templates.hh b/src/cli/templates.hh index f254ed3269..5b4c1b13ce 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -700,13 +700,17 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! 17A7F8F229358D220051D146 /* init.cc in Sources */ = {isa = PBXBuildFile; fileRef = 17A7F8EE29358D180051D146 /* init.cc */; }; 17A7F8F529358D430051D146 /* libsocket-runtime.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17A7F8F329358D430051D146 /* libsocket-runtime.a */; }; 17A7F8F629358D430051D146 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17A7F8F429358D430051D146 /* libuv.a */; }; + 17A7F8F629358D4A0051D146 /* libllama.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17A7F8F429358D430051D147 /* libllama.a */; }; 17A7F8F729358D4D0051D146 /* main.o in Frameworks */ = {isa = PBXBuildFile; fileRef = 17A7F8F129358D180051D146 /* main.o */; }; 17C230BA28E9398700301440 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17C230B928E9398700301440 /* Foundation.framework */; }; 290F7EBF2768C49000486988 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 294A3C792763E9C6007B5B9A /* UIKit.framework */; }; 290F7F87276BC2B000486988 /* lib in Resources */ = {isa = PBXBuildFile; fileRef = 290F7F86276BC2B000486988 /* lib */; }; 29124C5D2761336B001832A0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29124C5B2761336B001832A0 /* LaunchScreen.storyboard */; }; 294A3C852764EAB7007B5B9A /* ui in Resources */ = {isa = PBXBuildFile; fileRef = 294A3C842764EAB7007B5B9A /* ui */; }; + 034B592125768A7B005D0134 /* default.metallib in Resources */ = {isa = PBXBuildFile; fileRef = 034B592025768A7B005D0134 /* default.metallib */; }; 294A3CA02768C429007B5B9A /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 294A3C7B2763EA7F007B5B9A /* WebKit.framework */; }; + 2996EDB22770BC1F00C672A0 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2996EDB12770BC1F00C672B1 /* Accelerate.framework */; }; + 2996EDB22770BC1F00C672A1 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2996EDB12770BC1F00C672A1 /* Metal.framework */; }; 2996EDB22770BC1F00C672A2 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2996EDB12770BC1F00C672A2 /* Network.framework */; }; 2996EDB22770BC1F00C672A3 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2996EDB12770BC1F00C672A3 /* CoreBluetooth.framework */; }; 2996EDB22770BC1F00C672A4 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2996EDB12770BC1F00C672A4 /* UserNotifications.framework */; }; @@ -722,17 +726,21 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! 17A7F8F129358D180051D146 /* main.o */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.objfile"; path = main.o; sourceTree = ""; }; 17A7F8F329358D430051D146 /* libsocket-runtime.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libsocket-runtime.a"; path = "lib/libsocket-runtime.a"; sourceTree = ""; }; 17A7F8F429358D430051D146 /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = lib/libuv.a; sourceTree = ""; }; + 17A7F8F429358D430051D147 /* libllama.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libllama.a; path = lib/libllama.a; sourceTree = ""; }; 17C230B928E9398700301440 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 17E73FEE28FCD3360087604F /* libuv-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libuv-ios.a"; path = "lib/libuv-ios.a"; sourceTree = ""; }; 290F7F86276BC2B000486988 /* lib */ = {isa = PBXFileReference; lastKnownFileType = folder; path = lib; sourceTree = ""; }; 29124C4A27613369001832A0 /* {{build_name}}.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "{{build_name}}.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 29124C5C2761336B001832A0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29124C5E2761336B001832A0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 034B592025768A7B005D0134 /* default.metallib */ = {isa = PBXFileReference; lastKnownFileType = "archive.metal-library"; path = "lib/default.metallib"; sourceTree = ""; }; 29124C5E2761336B001832A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 294A3C792763E9C6007B5B9A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 294A3C7B2763EA7F007B5B9A /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 294A3C842764EAB7007B5B9A /* ui */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ui; sourceTree = ""; }; 294A3C9027677424007B5B9A /* socket.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = socket.entitlements; sourceTree = ""; }; + 2996EDB12770BC1F00C672B1 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + 2996EDB12770BC1F00C672A1 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; 2996EDB12770BC1F00C672A2 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; }; 2996EDB12770BC1F00C672A3 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; 2996EDB12770BC1F00C672A4 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; @@ -749,8 +757,11 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! 179989D22A867B260041EDC1 /* UniformTypeIdentifiers.framework in Frameworks */, 17A7F8F529358D430051D146 /* libsocket-runtime.a in Frameworks */, 17A7F8F629358D430051D146 /* libuv.a in Frameworks */, + 17A7F8F629358D4A0051D146 /* libllama.a in Frameworks */, 17A7F8F729358D4D0051D146 /* main.o in Frameworks */, 17C230BA28E9398700301440 /* Foundation.framework in Frameworks */, + 2996EDB22770BC1F00C672A0 /* Accelerate.framework in Frameworks */, + 2996EDB22770BC1F00C672A1 /* Metal.framework in Frameworks */, 2996EDB22770BC1F00C672A2 /* Network.framework in Frameworks */, 2996EDB22770BC1F00C672A3 /* CoreBluetooth.framework in Frameworks */, 2996EDB22770BC1F00C672A4 /* UserNotifications.framework in Frameworks */, @@ -819,8 +830,11 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! 179989D12A867B260041EDC1 /* UniformTypeIdentifiers.framework */, 17A7F8F329358D430051D146 /* libsocket-runtime.a */, 17A7F8F429358D430051D146 /* libuv.a */, + 17A7F8F429358D430051D147 /* libllama.a */, 17E73FEE28FCD3360087604F /* libuv-ios.a */, 17C230B928E9398700301440 /* Foundation.framework */, + 2996EDB12770BC1F00C672A1 /* Metal.framework */, + 2996EDB12770BC1F00C672B1 /* Accelerate.framework */, 2996EDB12770BC1F00C672A2 /* Network.framework */, 2996EDB12770BC1F00C672A3 /* CoreBluetooth.framework */, 2996EDB12770BC1F00C672A4 /* UserNotifications.framework */, @@ -890,6 +904,7 @@ constexpr auto gXCodeProject = R"ASCII(// !$*UTF8*$! files = ( 29124C5D2761336B001832A0 /* LaunchScreen.storyboard in Resources */, 294A3C852764EAB7007B5B9A /* ui in Resources */, + 034B592125768A7B005D0134 /* default.metallib in Resources */, 2996EDB22770BC1F00C672A5 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index a270c7e1d8..3d268089d1 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -72,6 +72,18 @@ namespace SSC { } this->core->dispatchEventLoop([=, this] { + /* auto log = [&](String message) { + const auto json = JSON::Object::Entries { + {"source", "ai.llm.log"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"message", message} + }} + }; + + callback("-1", json, Post{}); + }; */ + auto llm = new LLM(options); if (llm->err.size()) { auto json = ERR_AI_LLM_MESSAGE("ai.llm.create", id, llm->err); @@ -162,12 +174,6 @@ namespace SSC { }); }; - LLM::Logger LLM::log = nullptr; - - void LLM::tramp(ggml_log_level level, const char* message, void* user_data) { - if (LLM::log) LLM::log(level, message, user_data); - } - void LLM::escape(String& input) { std::size_t input_len = input.length(); std::size_t output_idx = 0; @@ -207,15 +213,11 @@ namespace SSC { input.resize(output_idx); } - LLM::LLM (const LLMOptions options) { - // - // set up logging - // - LLM::log = [&](ggml_log_level level, const char* message, void* user_data) { - // std::cout << message << std::endl; - }; - - llama_log_set(LLM::tramp, nullptr); + LLM::LLM (LLMOptions options) { + llama_log_set([](ggml_log_level level, const char* message, void* llm) { + NSLog(@"LLMSTATUS: %s", message); + // llm.log(message); + }, this); // // set params and init the model and context @@ -233,17 +235,33 @@ namespace SSC { this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; this->params.n_ctx = 2048; - FileResource resource(options.path); + #if SOCKET_RUNTIME_PLATFORM_IOS + this->params.use_mmap = false; + #endif - if (!resource.exists()) { + FileResource modelResource(options.path); + + if (!modelResource.exists()) { this->err = "Unable to access the model file due to permissions"; return; } + FileResource metalResource(String("ggml-metal")); + this->params.model = options.path; std::tie(this->model, this->ctx) = llama_init_from_gpt_params(this->params); + if (this->ctx == nullptr) { + this->err = "Unable to create the context"; + return; + } + + if (this->model == nullptr) { + this->err = "Unable to create the model"; + return; + } + this->embd_inp = ::llama_tokenize(this->ctx, this->params.prompt.c_str(), true, true); // diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh index ef1576021b..37ab254704 100644 --- a/src/core/modules/ai.hh +++ b/src/core/modules/ai.hh @@ -107,7 +107,7 @@ namespace SSC { void chat (String input, const Cb cb); void escape (String& input); - LLM(const LLMOptions options); + LLM(LLMOptions options); ~LLM(); static void tramp(ggml_log_level level, const char* message, void* user_data); From 0447745d207f8241974a3c62530591a8e83f1c34 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Fri, 31 May 2024 19:03:28 +0200 Subject: [PATCH 0764/1178] fix(ai): set echo false to start --- api/ai.js | 2 +- src/core/modules/ai.cc | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/api/ai.js b/api/ai.js index 32057e1dc6..b83e37aebe 100644 --- a/api/ai.js +++ b/api/ai.js @@ -88,7 +88,7 @@ export class LLM extends EventEmitter { return this.emit('end') } - this.emit('data', data.token) + this.emit('data', decodeURIComponent(data.token)) } }) diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 3d268089d1..4711e64fe8 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -123,7 +123,7 @@ namespace SSC { {"source", "ai.llm.chat"}, {"data", JSON::Object::Entries { {"id", std::to_string(id)}, - {"token", token}, + {"token", encodeURIComponent(token)}, {"complete", isComplete} }} }; @@ -232,13 +232,10 @@ namespace SSC { if (this->params.seed == LLAMA_DEFAULT_SEED) this->params.seed = time(nullptr); this->params.chatml = true; + this->params.verbose_prompt = false; this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; this->params.n_ctx = 2048; - #if SOCKET_RUNTIME_PLATFORM_IOS - this->params.use_mmap = false; - #endif - FileResource modelResource(options.path); if (!modelResource.exists()) { @@ -402,7 +399,7 @@ namespace SSC { bool display = true; bool is_antiprompt = false; - bool input_echo = true; + bool input_echo = false; int n_remain = this->params.n_predict; std::vector input_tokens; From 8d160cd800d0a478b81fae0c89027047ba69e22f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:22:43 +0200 Subject: [PATCH 0765/1178] feat(desktop/extension): introduce web process extension for linux desktop --- api/fs/dir.js | 1 + api/fs/handle.js | 32 ++++- api/fs/stream.js | 11 +- api/ipc.js | 52 ++++++- bin/build-runtime-library.sh | 18 ++- bin/install.sh | 14 +- src/app/app.cc | 83 +++--------- src/app/app.hh | 5 +- src/cli/cli.cc | 38 +++++- src/core/config.cc | 3 +- src/core/core.cc | 14 +- src/core/json.cc | 4 + src/core/json.hh | 1 + src/core/modules/ai.cc | 34 +++-- src/core/modules/ai.hh | 6 +- src/core/modules/fs.cc | 2 +- src/core/resource.cc | 186 ++++++++++++++++++++++++- src/core/resource.hh | 49 +++++-- src/core/trace.cc | 58 +++++++- src/core/trace.hh | 47 ++++++- src/core/webview.hh | 2 +- src/desktop/extension.hh | 17 +++ src/desktop/extension/linux.cc | 240 +++++++++++++++++++++++++++++++++ src/desktop/main.cc | 20 +-- src/ipc/navigator.cc | 10 ++ src/ipc/preload.cc | 6 - src/ipc/routes.cc | 143 +------------------- src/ipc/scheme_handlers.cc | 72 +++++----- src/ipc/scheme_handlers.hh | 11 +- src/platform/platform.hh | 6 +- src/window/linux.cc | 167 ++++++++++++++++++----- src/window/window.hh | 14 +- 32 files changed, 986 insertions(+), 380 deletions(-) create mode 100644 src/desktop/extension.hh create mode 100644 src/desktop/extension/linux.cc diff --git a/api/fs/dir.js b/api/fs/dir.js index 198ef8c37e..5cb3083b0c 100644 --- a/api/fs/dir.js +++ b/api/fs/dir.js @@ -127,6 +127,7 @@ export class Dir { } } + console.log({ results }) results = results.map((result) => { if (this.withFileTypes) { result = Dirent.from(result) diff --git a/api/fs/handle.js b/api/fs/handle.js index e17039b65e..67cae637da 100644 --- a/api/fs/handle.js +++ b/api/fs/handle.js @@ -238,7 +238,10 @@ export class FileHandle extends EventEmitter { async handle (id) { if (fds.has(id)) { console.warn('Closing fs.FileHandle on garbage collection') - await ipc.request('fs.close', { id }, options) + await ipc.request('fs.close', { id }, { + ...options + }) + fds.release(id, false) } } @@ -308,7 +311,9 @@ export class FileHandle extends EventEmitter { this[kClosing] = new Deferred() - const result = await ipc.request('fs.close', { id: this.id }, options) + const result = await ipc.request('fs.close', { id: this.id }, { + ...options + }) if (result.err) { return this[kClosing].reject(result.err) @@ -437,7 +442,9 @@ export class FileHandle extends EventEmitter { mode, path, flags - }, options) + }, { + ...options + }) if (result.err) { return this[kOpening].reject(result.err) @@ -554,7 +561,11 @@ export class FileHandle extends EventEmitter { id, size: length, offset: position - }, { signal, timeout, responseType: 'arraybuffer' }) + }, { + responseType: 'arraybuffer', + timeout, + signal + }) if (result.err) { throw result.err @@ -638,7 +649,9 @@ export class FileHandle extends EventEmitter { throw new Error('FileHandle is not opened') } - const result = await ipc.request('fs.fstat', { ...options, id: this.id }) + const result = await ipc.request('fs.fstat', { id: this.id }, { + ...options + }) if (result.err) { throw result.err @@ -659,7 +672,9 @@ export class FileHandle extends EventEmitter { throw new Error('FileHandle is not opened') } - const result = await ipc.request('fs.lstat', { ...options, path: this.path }) + const result = await ipc.request('fs.lstat', { path: this.path }, { + ...options + }) if (result.err) { throw result.err @@ -1119,7 +1134,10 @@ export class DirectoryHandle extends EventEmitter { throw result.err } - return result.data + return result.data.map((entry) => ({ + type: entry.type, + name: decodeURIComponent(entry.name) + })) } } diff --git a/api/fs/stream.js b/api/fs/stream.js index 368d27ea7e..c77940a687 100644 --- a/api/fs/stream.js +++ b/api/fs/stream.js @@ -27,7 +27,9 @@ export class ReadStream extends Readable { throw new AbortError(options.signal) } - if (typeof options?.highWaterMark !== 'number') { + if (typeof options?.highWaterMark === 'number') { + this._readableState.highWaterMark = options.highWaterMark + } else { this._readableState.highWaterMark = this.constructor.highWaterMark } @@ -149,9 +151,8 @@ export class ReadStream extends Readable { } if (typeof result.bytesRead === 'number' && result.bytesRead > 0) { - const slice = new Uint8Array(buffer.slice(0, result.bytesRead)) this.bytesRead += result.bytesRead - this.push(slice) + this.push(Buffer.from(buffer.slice(0, result.bytesRead))) if (this.bytesRead >= this.end) { this.push(null) @@ -175,7 +176,9 @@ export class WriteStream extends Writable { constructor (options) { super(options) - if (typeof options?.highWaterMark !== 'number') { + if (typeof options?.highWaterMark === 'number') { + this._writableState.highWaterMark = options.highWaterMark + } else { this._writableState.highWaterMark = this.constructor.highWaterMark } diff --git a/api/ipc.js b/api/ipc.js index 5a6a0ca7e2..0ca82795e6 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1253,11 +1253,32 @@ export async function write (command, value, buffer, options) { await ready() - const signal = options?.signal - const request = new globalThis.XMLHttpRequest() const params = new IPCSearchParams(value, Date.now()) const uri = `ipc://${command}?${params}` + if ( + typeof GlobalIPCExtensionPostMessage === 'function' && + (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) + ) { + let response = null + try { + response = await GlobalIPCExtensionPostMessage(uri, buffer) + } catch (err) { + return Result.from(null, err) + } + + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch {} + } + + return Result.from(response, null, command) + } + + const signal = options?.signal + const request = new globalThis.XMLHttpRequest() + let resolved = false let aborted = false let timeout = null @@ -1350,11 +1371,34 @@ export async function request (command, value, options) { await ready() - const signal = options?.signal - const request = new globalThis.XMLHttpRequest() const params = new IPCSearchParams(value, Date.now()) const uri = `ipc://${command}` + if ( + typeof GlobalIPCExtensionPostMessage === 'function' && + (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) + ) { + let response = null + try { + response = await GlobalIPCExtensionPostMessage(`${uri}?${params}`) + } catch (err) { + return Result.from(null, err) + } + + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch (err) { + console.log({err, response}) + } + } + + return Result.from(response, null, command) + } + + const signal = options?.signal + const request = new globalThis.XMLHttpRequest() + let resolved = false let aborted = false let timeout = null diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index beeddf2176..f1c14cc995 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -221,6 +221,18 @@ LLAMA_BUILD_INFO quiet $clang "${cflags[@]}" -c $source -o ${source/cpp/o} || onsignal } +function build_linux_desktop_extension_object () { + declare source="$root/src/desktop/extension/linux.cc" + declare destination="$root/build/$arch-$platform/objects/extensions/linux.o" + mkdir -p "$(dirname "$destination")" + if ! test -f "$object" || (( $(stat_mtime "$source") > $(stat_mtime "$destination") )); then + quiet $clang "${cflags[@]}" -c "$source" -o "$destination" || onsignal + return $? + fi + + return 0 +} + function main () { trap onsignal INT TERM local i=0 @@ -232,7 +244,11 @@ function main () { cp -rf "$root/include"/* "$output_directory/include" rm -f "$output_directory/include/socket/_user-config-bytes.hh" - generate_llama_build_info + generate_llama_build_info || return $? + + if [[ "$host" = "Linux" ]] && [[ "$platform" = "desktop" ]]; then + build_linux_desktop_extension_object || return $? + fi for source in "${sources[@]}"; do if (( ${#pids[@]} > max_concurrency )); then diff --git a/bin/install.sh b/bin/install.sh index 1f035d39ae..d6b0fc3c54 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -609,6 +609,18 @@ function _install { cp -rfp "$root"/include/* "$SOCKET_HOME/include" rm -f "$SOCKET_HOME/include/socket/_user-config-bytes.hh" + mkdir -p "$SOCKET_HOME/include/llama" + for header in $(find "$root/build/llama" -name *.h); do + if [[ "$header" =~ examples/ ]]; then continue; fi + if [[ "$header" =~ tests/ ]]; then continue; fi + + local llama_build_dir="$root/build/llama/" + local destination="$SOCKET_HOME/include/llama/${header/$llama_build_dir/}" + + mkdir -p "$(dirname "$destination")" + cp -f "$header" "$destination" + done + if [[ -f "$root/$SSC_ENV_FILENAME" ]]; then if [[ -f "$SOCKET_HOME/$SSC_ENV_FILENAME" ]]; then echo "# warn - Won't overwrite $SOCKET_HOME/$SSC_ENV_FILENAME" @@ -940,7 +952,7 @@ function _compile_libuv { die $? "not ok - desktop configure" fi - quiet make clean + quiet make quiet make "-j$CPU_CORES" quiet make install fi diff --git a/src/app/app.cc b/src/app/app.cc index c1835defd7..050dc05c57 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -600,8 +600,10 @@ namespace SSC { applicationInstance = this; } + #if !SOCKET_RUNTIME_DESKTOP_EXTENSION const auto cwd = getcwd(); uv_chdir(cwd.c_str()); + #endif this->init(); } #endif @@ -613,63 +615,12 @@ namespace SSC { } void App::init () { - #if SOCKET_RUNTIME_PLATFORM_LINUX + Env::set("UV_THREADPOOL_SIZE", "256"); + #if SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_PLATFORM_LINUX gtk_init_check(0, nullptr); auto webContext = webkit_web_context_get_default(); - g_signal_connect( - G_OBJECT(webContext), - "initialize-notification-permissions", - G_CALLBACK(+[]( - WebKitWebContext* webContext, - gpointer userData - ) { - static const auto app = App::sharedApplication(); - static const auto bundleIdentifier = app->userConfig["meta_bundle_identifier"]; - static const auto areNotificationsAllowed = ( - !app->userConfig.contains("permissions_allow_notifications") || - app->userConfig.at("permissions_allow_notifications") != "false" - ); - - const auto uri = "socket://" + bundleIdentifier; - const auto origin = webkit_security_origin_new_for_uri(uri.c_str()); - - GList* allowed = nullptr; - GList* disallowed = nullptr; - - webkit_security_origin_ref(origin); - - if (origin && allowed && disallowed) { - if (areNotificationsAllowed) { - disallowed = g_list_append(disallowed, (gpointer) origin); - } else { - allowed = g_list_append(allowed, (gpointer) origin); - } - - if (allowed && disallowed) { - webkit_web_context_initialize_notification_permissions( - webContext, - allowed, - disallowed - ); - } - } - - if (allowed) { - g_list_free(allowed); - } - - if (disallowed) { - g_list_free(disallowed); - } - - if (origin) { - webkit_security_origin_unref(origin); - } - }), - nullptr - ); #elif SOCKET_RUNTIME_PLATFORM_MACOS this->applicationDelegate = [SSCApplicationDelegate new]; this->applicationDelegate.app = this; @@ -678,7 +629,7 @@ namespace SSC { } int App::run (int argc, char** argv) { - #if SOCKET_RUNTIME_PLATFORM_LINUX + #if SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_DESKTOP_EXTENSION gtk_main(); #elif SOCKET_RUNTIME_PLATFORM_ANDROID // MUST be acquired on "main" thread @@ -735,7 +686,7 @@ namespace SSC { this->core->shutdown(); // Distinguish window closing with app exiting shouldExit = true; - #if SOCKET_RUNTIME_PLATFORM_LINUX + #if SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_DESKTOP_EXTENSION gtk_main_quit(); #elif SOCKET_RUNTIME_PLATFORM_MACOS // if not launched from the cli, just use `terminate()` @@ -770,19 +721,17 @@ namespace SSC { void App::dispatch (Function callback) { #if SOCKET_RUNTIME_PLATFORM_LINUX - // @TODO - auto threadCallback = new Function(callback); - - g_idle_add_full( - G_PRIORITY_HIGH_IDLE, - (GSourceFunc)([](void* callback) -> int { - (*static_cast*>(callback))(); + g_main_context_invoke( + nullptr, + +[](gpointer userData) -> gboolean { + auto callback = reinterpret_cast*>(userData); + if (*callback != nullptr) { + (*callback)(); + delete callback; + } return G_SOURCE_REMOVE; - }), - threadCallback, - [](void* callback) { - delete static_cast*>(callback); - } + }, + new Function(callback) ); #elif SOCKET_RUNTIME_PLATFORM_APPLE auto priority = DISPATCH_QUEUE_PRIORITY_DEFAULT; diff --git a/src/app/app.hh b/src/app/app.hh index 74f3e81819..7a8cddd6ed 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -1,10 +1,11 @@ #ifndef SOCKET_RUNTIME_APP_APP_H #define SOCKET_RUNTIME_APP_APP_H -#include "../core/core.hh" #include "../window/window.hh" #include "../serviceworker/container.hh" +#include "../core/core.hh" + #if SOCKET_RUNTIME_PLATFORM_ANDROID #include "../platform/android.hh" #endif @@ -93,8 +94,8 @@ namespace SSC { bool w32ShowConsole = false; WindowManager windowManager; - SharedPointer core = nullptr; ServiceWorkerContainer serviceWorkerContainer; + SharedPointer core = nullptr; Map userConfig = SSC::getUserConfig(); #if SOCKET_RUNTIME_PLATFORM_WINDOWS diff --git a/src/cli/cli.cc b/src/cli/cli.cc index d16713ffb9..c21d5272af 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2130,10 +2130,11 @@ int main (const int argc, const char* argv[]) { } } - ini += "\n"; - ini += "[ssc]\n"; - ini += "argv = " + arguments; - ini += "\n"; + if (arguments.size() > 0) { + ini += "\n"; + ini += "[ssc]\n"; + ini += "argv = " + arguments; + } ini += "\n"; if (configExists) { @@ -4814,6 +4815,7 @@ int main (const int argc, const char* argv[]) { flags += " -I" + Path(paths.platformSpecificOutputPath / "include").string(); flags += " -I" + prefixFile(); flags += " -I" + prefixFile("include"); + flags += " -I" + prefixFile("include"); flags += " -L" + prefixFile("lib/" + platform.arch + "-desktop"); files += prefixFile("objects/" + platform.arch + "-desktop/desktop/main.o"); @@ -4847,6 +4849,34 @@ int main (const int argc, const char* argv[]) { "apps" }; + { + auto desktopExtensionsPath = pathResources / "lib" / "extensions"; + auto CXX = Env::get("CXX", "clang++"); + + fs::create_directories(desktopExtensionsPath); + + StringStream command; + command + << CXX + << " -shared" + << " " << flags + << " " << prefixFile("lib/" + platform.arch + "-desktop/libsocket-runtime.a") + << " " << prefixFile("lib/" + platform.arch + "-desktop/libuv.a") + << " " << prefixFile("objects/" + platform.arch + "-desktop/extensions/linux.o") + << " -o " << (desktopExtensionsPath / "libsocket-runtime-desktop-extension.so").string() + ; + + log(command.str()); + auto result = exec(command.str().c_str()); + if (result.exitCode != 0) { + log("ERROR: failed to compile desktop runtime extension"); + if (flagVerboseMode) { + log(result.output); + } + exit(1); + } + } + fs::create_directories(pathIcons); fs::create_directories(pathResources); fs::create_directories(pathManifestFile); diff --git a/src/core/config.cc b/src/core/config.cc index 0a63ad27e5..98a2fea483 100644 --- a/src/core/config.cc +++ b/src/core/config.cc @@ -13,7 +13,8 @@ namespace SSC { const Map getUserConfig () { static const auto bytes = socket_runtime_init_get_user_config_bytes(); static const auto size = socket_runtime_init_get_user_config_bytes_size(); - static const auto userConfig = INI::parse(String((char*)bytes, size)); + static const auto string = String(reinterpret_cast(bytes), size); + static const auto userConfig = INI::parse(string); return userConfig; } diff --git a/src/core/core.cc b/src/core/core.cc index b10ba929b5..07ec2d10de 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -147,21 +147,21 @@ namespace SSC { return *timeout == 0; }, - /* .check = [](GSource* source) -> gboolean { auto core = reinterpret_cast(source)->core; - auto loop = core->getEventLoop(); - auto timeout = core->getEventLoopTimeout(); + auto tag = reinterpret_cast(source)->tag; + const auto timeout = core->getEventLoopTimeout(); if (timeout == 0) { return true; } - //auto condition = - - return true; + const auto condition = g_source_query_unix_fd(source, tag); + return ( + ((condition & G_IO_IN) == G_IO_IN) || + ((condition & G_IO_OUT) == G_IO_OUT) + ); }, - */ .dispatch = []( GSource *source, diff --git a/src/core/json.cc b/src/core/json.cc index 8bdf0bab7b..3aa7c791bf 100644 --- a/src/core/json.cc +++ b/src/core/json.cc @@ -99,6 +99,10 @@ namespace SSC::JSON { this->type = Type::String; } + Any::Any (const SSC::Path path) + : Any(path.string()) + {} + Any::Any (const SSC::String string) { this->pointer = SharedPointer(new String(string)); this->type = Type::String; diff --git a/src/core/json.hh b/src/core/json.hh index cc174f1e98..75a0ea5439 100644 --- a/src/core/json.hh +++ b/src/core/json.hh @@ -187,6 +187,7 @@ namespace SSC::JSON { Any (const char); Any (const char *); Any (const SSC::String); + Any (const SSC::Path); Any (const String); Any (const Object); Any (const ObjectEntries); diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 4711e64fe8..1d3896cf5d 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -66,12 +66,12 @@ namespace SSC { LLMOptions options, const CoreModule::Callback& callback ) { - if (this->hasLLM(id)) { - auto json = ERR_AI_LLM_EXISTS("ai.llm.create", id); - return callback(seq, json, Post{}); - } - this->core->dispatchEventLoop([=, this] { + if (this->hasLLM(id)) { + auto json = ERR_AI_LLM_EXISTS("ai.llm.create", id); + return callback(seq, json, Post{}); + } + /* auto log = [&](String message) { const auto json = JSON::Object::Entries { {"source", "ai.llm.log"}, @@ -84,11 +84,10 @@ namespace SSC { callback("-1", json, Post{}); }; */ - auto llm = new LLM(options); + auto llm = std::make_shared(options); if (llm->err.size()) { auto json = ERR_AI_LLM_MESSAGE("ai.llm.create", id, llm->err); return callback(seq, json, Post{}); - return; } const auto json = JSON::Object::Entries { @@ -100,7 +99,7 @@ namespace SSC { callback(seq, json, Post{}); Lock lock(this->mutex); - this->llms[id].reset(llm); + this->llms.emplace(id, llm); }); }; @@ -215,7 +214,7 @@ namespace SSC { LLM::LLM (LLMOptions options) { llama_log_set([](ggml_log_level level, const char* message, void* llm) { - NSLog(@"LLMSTATUS: %s", message); + debug("LLMSTATUS: %s", message); // llm.log(message); }, this); @@ -236,14 +235,27 @@ namespace SSC { this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; this->params.n_ctx = 2048; + #if SOCKET_RUNTIME_PLATFORM_IOS + this->params.use_mmap = false; + #endif + FileResource modelResource(options.path); + FileResource metalResource(String("ggml-metal")); - if (!modelResource.exists()) { + if (!modelResource.hasAccess()) { this->err = "Unable to access the model file due to permissions"; return; } - FileResource metalResource(String("ggml-metal")); + if (!modelResource.exists()) { + this->err = "The model file does not exist"; + return; + } + + if (metalResource.exists() && !metalResource.hasAccess()) { + this->err = "Unable to access the GGML metal file due to permissions"; + return; + } this->params.model = options.path; diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh index 37ab254704..ce598bd8ee 100644 --- a/src/core/modules/ai.hh +++ b/src/core/modules/ai.hh @@ -1,10 +1,10 @@ #ifndef SOCKET_RUNTIME_CORE_AI_H #define SOCKET_RUNTIME_CORE_AI_H -#include "../module.hh" +#include +#include -#include "llama/common/common.h" -#include "llama/llama.h" +#include "../module.hh" // #include // #include diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index 0546557aa4..e9b6e22e9c 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -759,7 +759,7 @@ namespace SSC { for (int i = 0; i < req->result; ++i) { auto entry = JSON::Object::Entries { {"type", desc->dir->dirents[i].type}, - {"name", desc->dir->dirents[i].name} + {"name", encodeURIComponent(desc->dir->dirents[i].name)} }; entries.push_back(entry); diff --git a/src/core/resource.cc b/src/core/resource.cc index 0cd6e213c8..f943ea43a7 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -1,4 +1,5 @@ #include "resource.hh" +#include "config.hh" #include "debug.hh" #include "core.hh" @@ -40,6 +41,16 @@ namespace SSC { {"video/ogg", { ".ogv" }} }; + Resource::Resource (const String& type, const String& name) + : name(name), + type(type), + tracer(name) + {} + + bool Resource::hasAccess () const noexcept { + return this->accessing; + } + #if SOCKET_RUNTIME_PLATFORM_ANDROID static Path getRelativeAndroidAssetManagerPath (const Path& resourcePath) { auto resourcesPath = FileResource::getResourcesPath(); @@ -198,11 +209,170 @@ namespace SSC { #endif } + const FileResource::WellKnownPaths& FileResource::getWellKnownPaths () { + static const auto paths = WellKnownPaths {}; + return paths; + } + + FileResource::WellKnownPaths::WellKnownPaths () { + static auto userConfig = getUserConfig(); + static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + + this->resources = FileResource::getResourcesPath(); + this->tmp = fs::temp_directory_path(); + #if SOCKET_RUNTIME_PLATFORM_APPLE + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + + static const auto fileManager = NSFileManager.defaultManager; + + #define DIRECTORY_PATH_FROM_FILE_MANAGER(type) ( \ + String([fileManager \ + URLForDirectory: type \ + inDomain: NSUserDomainMask \ + appropriateForURL: nil \ + create: NO \ + error: nil \ + ].path.UTF8String) \ + ) + + // overload with main bundle resources path for macos/ios + this->downloads = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSDownloadsDirectory)); + this->documents = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSDocumentDirectory)); + this->pictures = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSPicturesDirectory)); + this->desktop = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSDesktopDirectory)); + this->videos = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory)); + this->music = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory)); + this->config = Path(HOME + "/Library/Application Support/" + bundleIdentifier); + this->home = Path(String(NSHomeDirectory().UTF8String)); + this->data = Path(HOME + "/Library/Application Support/" + bundleIdentifier); + this->log = Path(HOME + "/Library/Logs/" + bundleIdentifier); + this->tmp = Path(String(NSTemporaryDirectory().UTF8String)); + + #undef DIRECTORY_PATH_FROM_FILE_MANAGER + + #elif SOCKET_RUNTIME_PLATFORM_LINUX + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + + static const auto XDG_DOCUMENTS_DIR = Env::get("XDG_DOCUMENTS_DIR"); + static const auto XDG_DOWNLOAD_DIR = Env::get("XDG_DOWNLOAD_DIR"); + static const auto XDG_PICTURES_DIR = Env::get("XDG_PICTURES_DIR"); + static const auto XDG_DESKTOP_DIR = Env::get("XDG_DESKTOP_DIR"); + static const auto XDG_VIDEOS_DIR = Env::get("XDG_VIDEOS_DIR"); + static const auto XDG_MUSIC_DIR = Env::get("XDG_MUSIC_DIR"); + + static const auto XDG_CONFIG_HOME = Env::get("XDG_CONFIG_HOME", HOME + "/.config"); + static const auto XDG_DATA_HOME = Env::get("XDG_DATA_HOME", HOME + "/.local/share"); + + if (XDG_DOCUMENTS_DIR.size() > 0) { + this->documents = Path(XDG_DOCUMENTS_DIR); + } else { + this->documents = Path(HOME) / "Documents"; + } + + if (XDG_DOWNLOAD_DIR.size() > 0) { + this->downloads = Path(XDG_DOWNLOAD_DIR); + } else { + this->downloads = Path(HOME) / "Downloads"; + } + + if (XDG_DESKTOP_DIR.size() > 0) { + this->desktop = Path(XDG_DESKTOP_DIR); + } else { + this->desktop = Path(HOME) / "Desktop"; + } + + if (XDG_PICTURES_DIR.size() > 0) { + this->pictures = Path(XDG_PICTURES_DIR); + } else if (fs::exists(Path(HOME) / "Images")) { + this->pictures = Path(HOME) / "Images"; + } else if (fs::exists(Path(HOME) / "Photos")) { + this->pictures = Path(HOME) / "Photos"; + } else { + this->pictures = Path(HOME) / "Pictures"; + } + + if (XDG_VIDEOS_DIR.size() > 0) { + this->videos = Path(XDG_VIDEOS_DIR); + } else { + this->videos = Path(HOME) / "Videos"; + } + + if (XDG_MUSIC_DIR.size() > 0) { + this->music = Path(XDG_MUSIC_DIR); + } else { + this->music = Path(HOME) / "Music"; + } + + this->config = Path(XDG_CONFIG_HOME) / bundleIdentifier; + this->home = Path(HOME); + this->data = Path(XDG_DATA_HOME) / bundleIdentifier; + this->log = this->config; + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + static const auto HOME = Env::get("HOMEPATH", Env::get("HOME")); + static const auto USERPROFILE = Env::get("USERPROFILE", HOME); + this->downloads = Path(USERPROFILE) / "Downloads"; + this->documents = Path(USERPROFILE) / "Documents"; + this->desktop = Path(USERPROFILE) / "Desktop"; + this->pictures = Path(USERPROFILE) / "Pictures"; + this->videos = Path(USERPROFILE) / "Videos"; + this->music = Path(USERPROFILE) / "Music"; + this->config = Path(Env::get("APPDATA")) / bundleIdentifier; + this->home = Path(USERPROFILE); + this->data = Path(Env::get("APPDATA")) / bundleIdentifier; + this->log = this->config; + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + // TODO + #endif + } + + JSON::Object FileResource::WellKnownPaths::json () const { + return JSON::Object::Entries { + {"resources", this->resources}, + {"downloads", this->downloads}, + {"documents", this->documents}, + {"pictures", this->pictures}, + {"desktop", this->desktop}, + {"videos", this->videos}, + {"music", this->music}, + {"config", this->config}, + {"home", this->home}, + {"data", this->data}, + {"log", this->log}, + {"tmp", this->tmp} + }; + } + + const Vector FileResource::WellKnownPaths::entries () const { + auto entries = Vector(); + entries.push_back(this->resources); + entries.push_back(this->downloads); + entries.push_back(this->documents); + entries.push_back(this->pictures); + entries.push_back(this->desktop); + entries.push_back(this->videos); + entries.push_back(this->music); + entries.push_back(this->config); + entries.push_back(this->home); + entries.push_back(this->data); + entries.push_back(this->log); + entries.push_back(this->tmp); + return entries; + } + FileResource::FileResource ( const Path& resourcePath, const Options& options - ) : options(options) { + ) : Resource("FileResource", resourcePath.string()) { this->path = fs::absolute(resourcePath); + this->options = options; this->startAccessing(); } @@ -214,7 +384,9 @@ namespace SSC { this->stopAccessing(); } - FileResource::FileResource (const FileResource& resource) { + FileResource::FileResource (const FileResource& resource) + : Resource("FileResource", resource.name) + { this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; @@ -222,7 +394,9 @@ namespace SSC { this->startAccessing(); } - FileResource::FileResource (FileResource&& resource) { + FileResource::FileResource (FileResource&& resource) + : Resource("FileResource", resource.name) + { this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; @@ -342,10 +516,6 @@ namespace SSC { #endif } - bool FileResource::hasAccess () const noexcept { - return this->accessing; - } - const String FileResource::mimeType () const noexcept { if (!this->accessing) { return ""; @@ -548,6 +718,7 @@ namespace SSC { memcpy(this->bytes.get(), data.bytes, data.length); } #elif SOCKET_RUNTIME_PLATFORM_LINUX + auto span = this->tracer.span("read"); GError* error = nullptr; char* contents = nullptr; gsize size = 0; @@ -561,6 +732,7 @@ namespace SSC { if (contents) { g_free(contents); } + span->end(); #elif SOCKET_RUNTIME_PLATFORM_WINDOWS auto handle = CreateFile( this->path.c_str(), diff --git a/src/core/resource.hh b/src/core/resource.hh index dc730e9e7e..5aea62743f 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -2,31 +2,28 @@ #define SOCKET_RUNTIME_CORE_RESOURCE_H #include "../platform/platform.hh" + #if SOCKET_RUNTIME_PLATFORM_ANDROID #include "../platform/android.hh" #endif +#include "trace.hh" + namespace SSC { class Resource { public: Atomic accessing = false; - Resource () = default; + const String name; + const String type; + Tracer tracer; + Resource (const String& type, const String& name); + bool hasAccess () const noexcept; virtual bool startAccessing () = 0; virtual bool stopAccessing () = 0; }; class FileResource : public Resource { public: - #if SOCKET_RUNTIME_PLATFORM_ANDROID - static void setSharedAndroidAssetManager (Android::AssetManager*); - static Android::AssetManager* getSharedAndroidAssetManager (); - #endif - - static bool isFile (const String& resourcePath); - static bool isFile (const Path& resourcePath); - static bool isDirectory (const String& resourcePath); - static bool isDirectory (const Path& resourcePath); - struct Cache { SharedPointer bytes = nullptr; size_t size = 0; @@ -36,6 +33,25 @@ namespace SSC { bool cache; }; + struct WellKnownPaths { + Path resources; + Path downloads; + Path documents; + Path pictures; + Path desktop; + Path videos; + Path config; + Path music; + Path home; + Path data; + Path log; + Path tmp; + + WellKnownPaths (); + const Vector entries () const; + JSON::Object json () const; + }; + class ReadStream { public: #if SOCKET_RUNTIME_PLATFORM_APPLE @@ -107,6 +123,16 @@ namespace SSC { static Path getResourcesPath (); static Path getResourcePath (const Path& resourcePath); static Path getResourcePath (const String& resourcePath); + static bool isFile (const String& resourcePath); + static bool isFile (const Path& resourcePath); + static bool isDirectory (const String& resourcePath); + static bool isDirectory (const Path& resourcePath); + static const WellKnownPaths& getWellKnownPaths (); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + static void setSharedAndroidAssetManager (Android::AssetManager*); + static Android::AssetManager* getSharedAndroidAssetManager (); + #endif Path path; @@ -125,7 +151,6 @@ namespace SSC { bool startAccessing (); bool stopAccessing (); bool exists () const noexcept; - bool hasAccess () const noexcept; const String mimeType () const noexcept; size_t size (bool cached = false) noexcept; size_t size () const noexcept; diff --git a/src/core/trace.cc b/src/core/trace.cc index 8e7fe5cad7..adcdab7713 100644 --- a/src/core/trace.cc +++ b/src/core/trace.cc @@ -61,7 +61,7 @@ namespace SSC { return nullptr; } - size_t Tracer::size (bool onlyActive) const { + size_t Tracer::size (bool onlyActive) const noexcept { size_t count = 0; for (const auto& span : *this->spans) { if (onlyActive && !span->ended) { @@ -85,11 +85,54 @@ namespace SSC { } return JSON::Object::Entries { + {"id", this->id}, {"name", this->name}, - {"spans", spans} + {"spans", spans}, + {"type", "Tracer"} }; } + const Tracer::SharedSpan Tracer::begin (const String& name) { + Lock lock(this->mutex); + + for (const auto& span : *this->spans) { + if (span->name == name) { + return span; + } + } + + return this->span(name); + } + + bool Tracer::end (const String& name) { + Lock lock(this->mutex); + for (const auto& span : *this->spans) { + if (span->name == name) { + span->end(); + return true; + } + } + + return false; + } + + const Tracer::Iterator Tracer::begin () const noexcept { + return this->spans->begin(); + } + + const Tracer::Iterator Tracer::end () const noexcept { + return this->spans->end(); + } + + const bool Tracer::clear () noexcept { + Lock lock(this->mutex); + if (this->spans->size() > 0) { + this->spans->clear(); + return true; + } + return false; + } + Tracer::Duration Tracer::Timing::now () { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()); @@ -109,7 +152,8 @@ namespace SSC { return JSON::Object::Entries { {"start", start}, {"end", end}, - {"duration", duration} + {"duration", duration}, + {"type", "Timing"} }; } @@ -147,9 +191,15 @@ namespace SSC { } return JSON::Object::Entries { + {"id", this->id}, {"name", this->name}, {"timing", this->timing.json()}, - {"spans", spans} + {"spans", spans}, + {"type", "Span"}, + {"tracer", JSON::Object::Entries { + {"id", this->tracer.id}, + {"name", this->tracer.name} + }} }; } diff --git a/src/core/trace.hh b/src/core/trace.hh index be89a45e8a..8ce01a2550 100644 --- a/src/core/trace.hh +++ b/src/core/trace.hh @@ -41,6 +41,16 @@ namespace SSC { */ using SharedSpanColletionIndex = std::map; + /** + * A `Tracer` ID type + */ + using ID = uint64_t; + + /** + * Iterator for a `Tracer` + */ + using Iterator = SharedSpanCollection::const_iterator; + /** * A container for `Tracer` timing. */ @@ -64,6 +74,11 @@ namespace SSC { public: using ID = uint64_t; + /** + * Iterator for a `Span` + */ + using Iterator = Vector::const_iterator; + /** * A unique ID for this `Span`. */ @@ -161,6 +176,11 @@ namespace SSC { */ String name; + /** + * A unique ID for this `Tracer`. + */ + ID id = rand64(); + /** * Initializes a new Tracer instance with an empty collection of spans. */ @@ -195,13 +215,38 @@ namespace SSC { * `onlyActive` boolean to get the computed "active" (not ended) * spans in a trace. */ - size_t size (bool onlyActive = false) const; + size_t size (bool onlyActive = false) const noexcept; /** * Computed JSON representation of this `Tracer` instance and its * `Tracer::Span` children. */ JSON::Object json () const; + + /** + * Create or return a span by name to "begin" a span + */ + const SharedSpan begin (const String& name); + + /** + * Ends a named span if one exists. + */ + bool end (const String& name); + + /** + * Get the beginning of iterator to the vector `Span` instances. + */ + const Iterator begin () const noexcept; + + /** + * Get the end of iterator to the vector of `Span` instances. + */ + const Iterator end () const noexcept; + + /** + * Clears all `Span` entries in the `Tracer`. + */ + const bool clear () noexcept; }; } diff --git a/src/core/webview.hh b/src/core/webview.hh index 10ab076acf..dac6a0d507 100644 --- a/src/core/webview.hh +++ b/src/core/webview.hh @@ -79,7 +79,7 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_APPLE using WebView = SSCWebView; using WebViewSettings = WKWebViewConfiguration; -#elif SOCKET_RUNTIME_PLATFORM_LINUX +#elif SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_DESKTOP_EXTENSION using WebView = WebKitWebView; using WebViewSettings = WebKitSettings; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS diff --git a/src/desktop/extension.hh b/src/desktop/extension.hh new file mode 100644 index 0000000000..16b8b301ca --- /dev/null +++ b/src/desktop/extension.hh @@ -0,0 +1,17 @@ +#ifndef SOCKET_RUNTIME_DESKTOP_EXTENSION_H +#define SOCKET_RUNTIME_DESKTOP_EXTENSION_H + +#include "../platform/platform.hh" + +namespace SSC { + struct WebExtensionContext { + struct ConfigData { + char* bytes = nullptr; + size_t size = 0; + }; + + ConfigData config; + }; +} + +#endif diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc new file mode 100644 index 0000000000..0444c9fb6b --- /dev/null +++ b/src/desktop/extension/linux.cc @@ -0,0 +1,240 @@ +#define SOCKET_RUNTIME_DESKTOP_EXTENSION 1 +#include "../../platform/platform.hh" +#include "../../core/resource.hh" +#include "../../core/debug.hh" +#include "../../core/trace.hh" +#include "../../core/url.hh" +#include "../../app/app.hh" + +#include "../extension.hh" + +using namespace SSC; + +#if SOCKET_RUNTIME_PLATFORM_LINUX +#if defined(__cplusplus) +extern "C" { +#endif + static WebExtensionContext context; + static SharedPointer bridge = nullptr; + static bool isMainApplicationDebugEnabled = false; + + static void onMessageResolver ( + JSCValue* resolve, + JSCValue* reject, + IPC::Message* message + ) { + auto context = jsc_value_get_context(resolve); + auto app = App::sharedApplication(); + + if (bridge == nullptr) { + g_object_ref(context); + bridge = std::make_shared(app->core, app->userConfig); + bridge->evaluateJavaScriptFunction = [context] (const auto source) { + auto _ = jsc_context_evaluate(context, source.c_str(), source.size()); + }; + bridge->init(); + } + + auto routed = bridge->route(message->str(), message->buffer.bytes, message->buffer.size, [=](auto result) { + if (result.post.body != nullptr) { + auto array = jsc_value_new_typed_array( + context, + JSC_TYPED_ARRAY_UINT8, + result.post.length + ); + + gsize size = 0; + auto bytes = jsc_value_typed_array_get_data(array, &size); + memcpy(bytes, result.post.body.get(), size); + + auto _ = jsc_value_function_call( + resolve, + JSC_TYPE_VALUE, + array, + G_TYPE_NONE + ); + } else { + auto json = result.json().str(); + if (json.size() > 0) { + auto _ = jsc_value_function_call( + resolve, + JSC_TYPE_VALUE, + jsc_value_new_string(context, json.c_str()), + G_TYPE_NONE + ); + } + } + + g_object_unref(context); + g_object_unref(resolve); + g_object_unref(reject); + }); + + if (routed) { + g_object_ref(context); + g_object_ref(resolve); + g_object_ref(reject); + } else { + auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"}, + {"source", message->name} + }} + }; + + auto _ = jsc_value_function_call( + resolve, + JSC_TYPE_VALUE, + jsc_value_new_string(context, JSON::Object(json).str().c_str()), + G_TYPE_NONE + ); + } + + delete message; + } + + static JSCValue* onMessage (const char* source, JSCValue* value, gpointer userData) { + auto context = reinterpret_cast(userData); + auto Promise = jsc_context_get_value(context, "Promise"); + auto message = new IPC::Message(source); + auto resolver = jsc_value_new_function( + context, + nullptr, + G_CALLBACK(onMessageResolver), + message, + nullptr, + G_TYPE_NONE, + 2, + JSC_TYPE_VALUE, + JSC_TYPE_VALUE + ); + + if (jsc_value_is_typed_array(value)) { + auto bytes = jsc_value_typed_array_get_data(value, &message->buffer.size); + message->buffer.bytes = std::make_shared(message->buffer.size); + memcpy( + message->buffer.bytes.get(), + bytes, + message->buffer.size + ); + } + + auto promise = jsc_value_constructor_call( + Promise, + JSC_TYPE_VALUE, + resolver, + G_TYPE_NONE + ); + + g_object_unref(Promise); + g_object_unref(resolver); + return promise; + } + + static void onDocumentLoaded ( + WebKitWebPage* page, + gpointer userData + ) { + auto frame = webkit_web_page_get_main_frame(page); + auto context = webkit_frame_get_js_context(frame); + auto GlobalIPCExtensionPostMessage = jsc_value_new_function( + context, + "GlobalIPCExtensionPostMessage", + G_CALLBACK(onMessage), + context, + nullptr, + JSC_TYPE_VALUE, + 2, + G_TYPE_STRING, + JSC_TYPE_VALUE + ); + + jsc_context_set_value( + context, + "GlobalIPCExtensionPostMessage", + GlobalIPCExtensionPostMessage + ); + } + + static void onPageCreated ( + WebKitWebExtension* extension, + WebKitWebPage* page, + gpointer userData + ) { + auto userConfig = getUserConfig(); + g_signal_connect( + page, + "document-loaded", + G_CALLBACK(onDocumentLoaded), + nullptr + ); + } + + G_MODULE_EXPORT void webkit_web_extension_initialize_with_user_data ( + WebKitWebExtension* extension, + const GVariant* userData + ) { + g_signal_connect( + extension, + "page-created", + G_CALLBACK(onPageCreated), + nullptr + ); + + if (!context.config.bytes) { + context.config.size = g_variant_get_size(const_cast(userData)); + if (context.config.size) { + context.config.bytes = new char[context.config.size]{0}; + + memcpy( + context.config.bytes, + g_variant_get_data(const_cast(userData)), + context.config.size + ); + } + } + + static App app(0); + auto userConfig = getUserConfig(); + auto cwd = userConfig["web-process-extension_cwd"]; + if (cwd.size() > 0) { + setcwd(cwd); + uv_chdir(cwd.c_str()); + } + } + + const unsigned char* socket_runtime_init_get_user_config_bytes () { + return reinterpret_cast(context.config.bytes); + } + + unsigned int socket_runtime_init_get_user_config_bytes_size () { + return context.config.size; + } + + bool socket_runtime_init_is_debug_enabled () { + return isMainApplicationDebugEnabled; + } + + const char* socket_runtime_init_get_dev_host () { + auto userConfig = getUserConfig(); + if (userConfig.contains("web-process-extension_host")) { + return userConfig["web-process-extension_host"].c_str(); + } + return ""; + } + + int socket_runtime_init_get_dev_port () { + auto userConfig = getUserConfig(); + if (userConfig.contains("web-process-extension_port")) { + try { + return std::stoi(userConfig["web-process-extension_port"]);; + } catch (...) {} + } + + return 0; + } +#if defined(__cplusplus) +} +#endif +#endif diff --git a/src/desktop/main.cc b/src/desktop/main.cc index faa4341c1f..919a6380f4 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -7,6 +7,8 @@ #if SOCKET_RUNTIME_PLATFORM_LINUX #include #include + +#include "extension.hh" #endif #include @@ -65,7 +67,7 @@ static Function shutdownHandler; // other windows who may be subscribers static void defaultWindowSignalHandler (int signal) { auto app = App::sharedApplication(); - if (app != nullptr) { + if (app != nullptr && app->core->platform.wasFirstDOMContentLoadedEventDispatched) { auto defaultWindow = app->windowManager.getWindow(0); if (defaultWindow != nullptr) { if (defaultWindow->status < WindowManager::WindowStatus::WINDOW_CLOSING) { @@ -326,7 +328,7 @@ MAIN { static Function pollForMessage = [connection]() { Thread thread([connection] () { - while (dbus_connection_read_write_dispatch(connection, 100)); + while (dbus_connection_read_write_dispatch(connection, 256)); app_ptr->dispatch(pollForMessage); }); @@ -1010,9 +1012,7 @@ MAIN { SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSYS) #endif -#if SOCKET_RUNTIME_PLATFORM_LINUX - //Thread mainThread([&]() { -#endif + app.dispatch([=]() { Vector properties = { "window_width", "window_height", "window_min_width", "window_min_height", @@ -1152,7 +1152,7 @@ MAIN { auto app = App::sharedApplication(); while (!app->core->platform.wasFirstDOMContentLoadedEventDispatched) { - std::this_thread::sleep_for(std::chrono::milliseconds(128)); + msleep(128); } do { @@ -1169,6 +1169,7 @@ MAIN { t.detach(); } + }); // // # Event Loop @@ -1176,15 +1177,8 @@ MAIN { // thread and run it until it returns a non-zero int. // while (app.run(argc, argv) == 0); -#if SOCKET_RUNTIME_PLATFORM_LINUX - //}); -#endif #if SOCKET_RUNTIME_PLATFORM_LINUX - //if (mainThread.joinable()) { - //mainThread.join(); - //} - dbus_connection_unref(connection); #endif diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index 341fc0c83b..47599822ab 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -465,6 +465,16 @@ namespace SSC::IPC { #endif } } + + #if SOCKET_RUNTIME_PLATFORM_LINUX + const auto wellKnownPaths = FileResource::getWellKnownPaths(); + auto webContext = webkit_web_context_get_default(); + for (const auto& entry : wellKnownPaths.entries()) { + if (FileResource::isDirectory(entry)) { + webkit_web_context_add_path_to_sandbox(webContext, entry.c_str(), false); + } + } + #endif } } diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index f8fdf3083b..148ed7e593 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -599,14 +599,10 @@ namespace SSC::IPC { ) { auto resource = FileResource(Path(userConfig.at("webview_importmap"))); - debug("has importmap config"); - if (resource.exists()) { - debug("importmap exists"); const auto bytes = resource.read(); if (bytes != nullptr) { - debug("injecting importmap"); preload = ( tmpl( R"HTML()HTML", @@ -614,8 +610,6 @@ namespace SSC::IPC { ) + preload ); } - } else { - debug("importmap doesn't excist"); } } diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index c5ee4cc958..b600f954e7 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1754,148 +1754,7 @@ static void mapIPCRoutes (Router *router) { }); router->map("os.paths", [=](auto message, auto router, auto reply) { - const auto bundleIdentifier = router->bridge->userConfig["meta_bundle_identifier"]; - - JSON::Object json; - - // paths - String resources = getcwd(); - String downloads; - String documents; - String pictures; - String desktop; - String videos; - String config; - String music; - String home; - String data; - String log; - String tmp = fs::temp_directory_path().string(); - - #if SOCKET_RUNTIME_PLATFORM_APPLE - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - - static const auto fileManager = NSFileManager.defaultManager; - - #define DIRECTORY_PATH_FROM_FILE_MANAGER(type) ( \ - String([fileManager \ - URLForDirectory: type \ - inDomain: NSUserDomainMask \ - appropriateForURL: nil \ - create: NO \ - error: nil \ - ].path.UTF8String) \ - ) - - // overload with main bundle resources path for macos/ios - resources = String(NSBundle.mainBundle.resourcePath.UTF8String); - downloads = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDownloadsDirectory); - documents = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDocumentDirectory); - pictures = DIRECTORY_PATH_FROM_FILE_MANAGER(NSPicturesDirectory); - desktop = DIRECTORY_PATH_FROM_FILE_MANAGER(NSDesktopDirectory); - videos = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory); - music = DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory); - config = HOME + "/Library/Application Support/" + bundleIdentifier; - home = String(NSHomeDirectory().UTF8String); - data = HOME + "/Library/Application Support/" + bundleIdentifier; - log = HOME + "/Library/Logs/" + bundleIdentifier; - tmp = String(NSTemporaryDirectory().UTF8String); - - #undef DIRECTORY_PATH_FROM_FILE_MANAGER - - #elif SOCKET_RUNTIME_PLATFORM_LINUX - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - - static const auto XDG_DOCUMENTS_DIR = Env::get("XDG_DOCUMENTS_DIR"); - static const auto XDG_DOWNLOAD_DIR = Env::get("XDG_DOWNLOAD_DIR"); - static const auto XDG_PICTURES_DIR = Env::get("XDG_PICTURES_DIR"); - static const auto XDG_DESKTOP_DIR = Env::get("XDG_DESKTOP_DIR"); - static const auto XDG_VIDEOS_DIR = Env::get("XDG_VIDEOS_DIR"); - static const auto XDG_MUSIC_DIR = Env::get("XDG_MUSIC_DIR"); - - static const auto XDG_CONFIG_HOME = Env::get("XDG_CONFIG_HOME", HOME + "/.config"); - static const auto XDG_DATA_HOME = Env::get("XDG_DATA_HOME", HOME + "/.local/share"); - - if (XDG_DOCUMENTS_DIR.size() > 0) { - documents = XDG_DOCUMENTS_DIR; - } else { - documents = (Path(HOME) / "Documents").string(); - } - - if (XDG_DOWNLOAD_DIR.size() > 0) { - downloads = XDG_DOWNLOAD_DIR; - } else { - downloads = (Path(HOME) / "Downloads").string(); - } - - if (XDG_DESKTOP_DIR.size() > 0) { - desktop = XDG_DESKTOP_DIR; - } else { - desktop = (Path(HOME) / "Desktop").string(); - } - - if (XDG_PICTURES_DIR.size() > 0) { - pictures = XDG_PICTURES_DIR; - } else if (fs::exists(Path(HOME) / "Images")) { - pictures = (Path(HOME) / "Images").string(); - } else if (fs::exists(Path(HOME) / "Photos")) { - pictures = (Path(HOME) / "Photos").string(); - } else { - pictures = (Path(HOME) / "Pictures").string(); - } - - if (XDG_VIDEOS_DIR.size() > 0) { - videos = XDG_VIDEOS_DIR; - } else { - videos = (Path(HOME) / "Videos").string(); - } - - if (XDG_MUSIC_DIR.size() > 0) { - music = XDG_MUSIC_DIR; - } else { - music = (Path(HOME) / "Music").string(); - } - - config = XDG_CONFIG_HOME + "/" + bundleIdentifier; - home = Path(HOME).string(); - data = XDG_DATA_HOME + "/" + bundleIdentifier; - log = config; - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - static const auto HOME = Env::get("HOMEPATH", Env::get("HOME")); - static const auto USERPROFILE = Env::get("USERPROFILE", HOME); - downloads = (Path(USERPROFILE) / "Downloads").string(); - documents = (Path(USERPROFILE) / "Documents").string(); - desktop = (Path(USERPROFILE) / "Desktop").string(); - pictures = (Path(USERPROFILE) / "Pictures").string(); - videos = (Path(USERPROFILE) / "Videos").string(); - music = (Path(USERPROFILE) / "Music").string(); - config = (Path(Env::get("APPDATA")) / bundleIdentifier).string(); - home = Path(USERPROFILE).string(); - data = (Path(Env::get("APPDATA")) / bundleIdentifier).string(); - log = config; - #endif - - json["resources"] = resources; - json["downloads"] = downloads; - json["documents"] = documents; - json["pictures"] = pictures; - json["desktop"] = desktop; - json["videos"] = videos; - json["music"] = music; - json["config"] = config; - json["home"] = home; - json["data"] = data; - json["log"] = log; - json["tmp"] = tmp; - + const auto json = FileResource::getWellKnownPaths().json(); return reply(Result::Data { message, json }); }); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index e837bf4043..2e1b2be7f6 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -128,6 +128,8 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer return; } + auto tracer = Tracer("onURISchemeRequest"); + auto bridge = &window->bridge; auto request = IPC::SchemeHandlers::Request::Builder(&bridge->schemeHandlers, schemeRequest) .setMethod(String(webkit_uri_scheme_request_get_http_method(schemeRequest))) @@ -137,7 +139,9 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer .setBody(webkit_uri_scheme_request_get_http_body(schemeRequest)) .build(); - const auto handled = bridge->schemeHandlers.handleRequest(request, [=](const auto& response) { + auto span = tracer.begin("handleRequest"); + const auto handled = bridge->schemeHandlers.handleRequest(request, [=](const auto& response) mutable { + tracer.end("handleRequest"); // TODO(@jwerle): handle uri scheme response }); @@ -878,7 +882,7 @@ namespace SSC::IPC { destination->finished = source.finished.load(); destination->handlers = source.handlers; destination->statusCode = source.statusCode; - destination->ownedBuffers = source.ownedBuffers; + destination->buffers = source.buffers; destination->platformResponse = source.platformResponse; } @@ -1068,7 +1072,10 @@ namespace SSC::IPC { return false; } - bool SchemeHandlers::Response::write (size_t size, const char* bytes) { + bool SchemeHandlers::Response::write ( + size_t size, + SharedPointer bytes + ) { Lock lock(this->mutex); if ( @@ -1094,7 +1101,7 @@ namespace SSC::IPC { if (size > 0 && bytes != nullptr) { #if SOCKET_RUNTIME_PLATFORM_APPLE - const auto data = [NSData dataWithBytes: bytes length: size]; + const auto data = [NSData dataWithBytes: bytes.get() length: size]; @try { [this->request->platformRequest didReceiveData: data]; } @catch (::id) { @@ -1103,18 +1110,14 @@ namespace SSC::IPC { return true; #elif SOCKET_RUNTIME_PLATFORM_LINUX auto span = this->tracer.span("write"); - auto buffer = new char[size]{0}; - memcpy(buffer, bytes, size); + this->buffers.push_back(bytes); g_memory_input_stream_add_data( reinterpret_cast(this->platformResponseStream), - reinterpret_cast(buffer), + reinterpret_cast(bytes.get()), (gssize) size, nullptr ); span->end(); - this->request->bridge->core->setTimeout(1024, [buffer] () { - delete [] buffer; - }); return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID @@ -1148,15 +1151,6 @@ namespace SSC::IPC { return false; } - bool SchemeHandlers::Response::write (size_t size, SharedPointer bytes) { - if (bytes != nullptr && size > 0) { - this->ownedBuffers.push_back(bytes); // claim ownership for life time of request - return this->write(size, this->ownedBuffers.back().get()); - } - - return false; - } - bool SchemeHandlers::Response::write (const String& source) { const auto size = source.size(); @@ -1169,6 +1163,10 @@ namespace SSC::IPC { return this->write(size, bytes); } + bool SchemeHandlers::Response::write (size_t size, const char* bytes) { + return size > 0 && bytes != nullptr && this->write(String(bytes, size)); + } + bool SchemeHandlers::Response::write (const JSON::Any& json) { this->setHeader("content-type", "application/json"); return this->write(json.str()); @@ -1190,12 +1188,10 @@ namespace SSC::IPC { } if (contentLength > 0) { - this->pendingWrites++; this->writeHead(); - const auto bytes = responseResource.read(); - const auto result = this->write(contentLength, bytes); - this->pendingWrites--; - return result; + if (responseResource.read()) { + return this->write(contentLength, responseResource.bytes); + } } return false; @@ -1225,10 +1221,6 @@ namespace SSC::IPC { return false; } - while (this->pendingWrites > 0) { - msleep(1); - } - if ( !this->handlers->isRequestActive(this->id) || this->handlers->isRequestCancelled(this->id) @@ -1259,22 +1251,24 @@ namespace SSC::IPC { this->platformResponseStream = nullptr; this->platformResponse = nullptr; - //if (WEBKIT_IS_URI_SCHEME_REQUEST(platformRequest)) { - webkit_uri_scheme_request_finish_with_response( - platformRequest, - platformResponse - ); - //} + webkit_uri_scheme_request_finish_with_response( + platformRequest, + platformResponse + ); - if (platformResponseStream) { - g_input_stream_close(platformResponseStream, nullptr, nullptr); - g_object_unref(platformResponseStream); - } + g_object_unref(platformResponse); + auto buffers = this->buffers; + this->request->bridge->core->setInterval(16, [buffers, platformResponseStream] (auto cancel) { + if (!G_IS_INPUT_STREAM(platformResponseStream) || !g_input_stream_has_pending(platformResponseStream)) { + g_object_unref(platformResponseStream); + cancel(); + } + }); } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID if (this->platformResponse != nullptr) { - auto ownedBuffers = this->ownedBuffers; + auto buffers = this->buffers; this->request->bridge->dispatch([=, this] () { auto app = App::sharedApplication(); auto attachment = Android::JNIEnvironmentAttachment(app->jvm); diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 8e96706018..4bd92067e1 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -54,7 +54,7 @@ namespace SSC::IPC { #if SOCKET_RUNTIME_PLATFORM_APPLE using PlatformRequest = id; using PlatformResponse = NSHTTPURLResponse*; - #elif SOCKET_RUNTIME_PLATFORM_LINUX + #elif SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_DESKTOP_EXTENSION using PlatformRequest = WebKitURISchemeRequest*; using PlatformResponse = WebKitURISchemeResponse*; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS @@ -64,7 +64,8 @@ namespace SSC::IPC { using PlatformRequest = jobject; using PlatformResponse = jobject; #else - #error "IPC::SchemeHandlers are not supported on this platform" + using PlatformRequest = void*; + using PlatformResponse = void*; #endif struct Request { @@ -177,11 +178,9 @@ namespace SSC::IPC { Client client; Mutex mutex; - Atomic pendingWrites = 0; Atomic finished = false; - Vector> ownedBuffers; - Vector writeThreads; + Vector> buffers; Tracer tracer; @@ -204,12 +203,12 @@ namespace SSC::IPC { Response& operator= (const Response&) noexcept; Response& operator= (Response&&) noexcept; - bool write (size_t size, const char* bytes); bool write (size_t size, SharedPointer); bool write (const String& source); bool write (const JSON::Any& json); bool write (const FileResource& resource); bool write (const FileResource::ReadStream::Buffer& buffer); + bool write (size_t size, const char* bytes); bool send (const String& source); bool send (const JSON::Any& json); bool send (const FileResource& resource); diff --git a/src/platform/platform.hh b/src/platform/platform.hh index 6baf63d0b4..48549fcd49 100644 --- a/src/platform/platform.hh +++ b/src/platform/platform.hh @@ -39,7 +39,11 @@ #include #include #include -#include + #if SOCKET_RUNTIME_DESKTOP_EXTENSION + #include + #else + #include + #endif #endif // `__linux__` // Android (Linux) diff --git a/src/window/linux.cc b/src/window/linux.cc index 679700aae8..9d4eebdb39 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1,4 +1,5 @@ #include +#include #include "../app/app.hh" #include "window.hh" @@ -11,6 +12,100 @@ static GtkTargetEntry droppableTypes[] = { #define DEFAULT_MONITOR_HEIGHT 364 namespace SSC { + static void initializeWebContextFromWindow (Window* window) { + static Atomic isInitialized = false; + + if (isInitialized) { + return; + } + + auto webContext = webkit_web_context_get_default(); + + // mounts are configured for all contexts just once + window->bridge.configureNavigatorMounts(); + + g_signal_connect( + G_OBJECT(webContext), + "initialize-notification-permissions", + G_CALLBACK(+[]( + WebKitWebContext* webContext, + gpointer userData + ) { + static const auto app = App::sharedApplication(); + static const auto bundleIdentifier = app->userConfig["meta_bundle_identifier"]; + static const auto areNotificationsAllowed = ( + !app->userConfig.contains("permissions_allow_notifications") || + app->userConfig.at("permissions_allow_notifications") != "false" + ); + + const auto uri = "socket://" + bundleIdentifier; + const auto origin = webkit_security_origin_new_for_uri(uri.c_str()); + + GList* allowed = nullptr; + GList* disallowed = nullptr; + + webkit_security_origin_ref(origin); + + if (origin && allowed && disallowed) { + if (areNotificationsAllowed) { + disallowed = g_list_append(disallowed, (gpointer) origin); + } else { + allowed = g_list_append(allowed, (gpointer) origin); + } + + if (allowed && disallowed) { + webkit_web_context_initialize_notification_permissions( + webContext, + allowed, + disallowed + ); + } + } + + if (allowed) { + g_list_free(allowed); + } + + if (disallowed) { + g_list_free(disallowed); + } + + if (origin) { + webkit_security_origin_unref(origin); + } + }), + nullptr + ); + + webkit_web_context_set_sandbox_enabled(webContext, true); + + auto extensionsPath = FileResource::getResourcePath(Path("lib/extensions")); + webkit_web_context_set_web_extensions_directory( + webContext, + extensionsPath.c_str() + ); + + auto cwd = getcwd(); + auto bytes = socket_runtime_init_get_user_config_bytes(); + auto size = socket_runtime_init_get_user_config_bytes_size(); + static auto data = String(reinterpret_cast(bytes), size); + data += "[web-process-extension]\ncwd = " + cwd + "\n"; + + webkit_web_context_set_web_extensions_initialization_user_data( + webContext, + g_variant_new_from_data( + G_VARIANT_TYPE("ay"), // an array of "bytes" + data.c_str(), + data.size(), + true, + nullptr, + nullptr + ) + ); + + isInitialized = true; + } + Window::Window (SharedPointer core, const WindowOptions& options) : core(core), options(options), @@ -18,33 +113,46 @@ namespace SSC { hotkey(this), dialog(this) { - setenv("GTK_OVERLAY_SCROLLING", "1", 1); + Env::set("GTK_OVERLAY_SCROLLING", "1"); auto userConfig = options.userConfig; auto webContext = webkit_web_context_get_default(); + this->bridge.userConfig = userConfig; + if (options.index == 0) { - // only Window#0 should set this value - webkit_web_context_set_sandbox_enabled(webContext, true); + initializeWebContextFromWindow(this); } - this->bridge.userConfig = userConfig; - this->bridge.configureNavigatorMounts(); this->settings = webkit_settings_new(); - // ALWAYS on or off - webkit_settings_set_enable_webgl(this->settings, true); // TODO(@jwerle); make configurable with '[permissions] allow_media' - webkit_settings_set_enable_media(this->settings, true); - webkit_settings_set_enable_webaudio(this->settings, true); webkit_settings_set_zoom_text_only(this->settings, false); - webkit_settings_set_enable_mediasource(this->settings, true); + webkit_settings_set_media_playback_allows_inline(this->settings, true); // TODO(@jwerle); make configurable with '[permissions] allow_dialogs' webkit_settings_set_allow_modal_dialogs(this->settings, true); - webkit_settings_set_enable_dns_prefetching(this->settings, true); + webkit_settings_set_hardware_acceleration_policy( + this->settings, + userConfig["permissions_hardware_acceleration_disabled"] == "true" + ? WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER + : WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS + ); + + webkit_settings_set_enable_webgl(this->settings, true); + webkit_settings_set_enable_media(this->settings, true); + webkit_settings_set_enable_webaudio(this->settings, true); + webkit_settings_set_enable_mediasource(this->settings, true); webkit_settings_set_enable_encrypted_media(this->settings, true); - webkit_settings_set_media_playback_allows_inline(this->settings, true); + webkit_settings_set_enable_dns_prefetching(this->settings, true); + webkit_settings_set_enable_smooth_scrolling(this->settings, true); webkit_settings_set_enable_developer_extras(this->settings, options.debug); - webkit_settings_set_allow_universal_access_from_file_urls(this->settings, true); + webkit_settings_set_enable_back_forward_navigation_gestures(this->settings, true); + + auto userAgent = String(webkit_settings_get_user_agent(settings)); + + webkit_settings_set_user_agent( + settings, + (userAgent + " " + "SocketRuntime/" + SSC::VERSION_STRING).c_str() + ); webkit_settings_set_enable_media_stream( this->settings, @@ -136,11 +244,12 @@ namespace SSC { gtk_widget_realize(GTK_WIDGET(this->window)); if (options.resizable) { - gtk_window_set_default_size(GTK_WINDOW(window), options.width, options.height); + gtk_window_set_default_size(GTK_WINDOW(this->window), options.width, options.height); } else { gtk_widget_set_size_request(this->window, options.width, options.height); } + gtk_window_set_decorated(GTK_WINDOW(this->window), options.frameless == false); gtk_window_set_resizable(GTK_WINDOW(this->window), options.resizable); gtk_window_set_position(GTK_WINDOW(this->window), GTK_WIN_POS_CENTER); gtk_widget_set_can_focus(GTK_WIDGET(this->window), true); @@ -752,7 +861,6 @@ namespace SSC { G_CALLBACK(onDestroy), this ); - */ g_signal_connect( G_OBJECT(this->window), @@ -770,6 +878,7 @@ namespace SSC { }), this ); + */ g_signal_connect( G_OBJECT(this->window), @@ -835,14 +944,14 @@ namespace SSC { } if (this->webview) { - g_object_unref(this->webview); + g_object_unref(GTK_WIDGET(this->webview)); this->webview = nullptr; } if (this->window) { auto w = this->window; this->window = nullptr; - gtk_widget_destroy(w); + // gtk_widget_destroy(w); } if (this->accelGroup) { @@ -851,7 +960,7 @@ namespace SSC { } if (this->vbox) { - //g_object_unref(this->vbox); + g_object_unref(this->vbox); this->vbox = nullptr; } } @@ -922,18 +1031,16 @@ namespace SSC { void Window::eval (const String& source) { if (this->webview) { - App::sharedApplication()->dispatch([=, this] { - webkit_web_view_evaluate_javascript( - this->webview, - String(source).c_str(), - -1, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr - ); - }); + webkit_web_view_evaluate_javascript( + this->webview, + String(source).c_str(), + -1, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + ); } } diff --git a/src/window/window.hh b/src/window/window.hh index 4ad6f1b8fe..eaba52c2d8 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -165,15 +165,15 @@ namespace SSC { int index = 0; /** - * This value is `true` when the window has closed an is indicating that the - * application is exiting + * This value is `true` when the window has closed an is indicating + * that the application is exiting */ Atomic isExiting = false; /** * A pointer to the platform WebView. */ - WebView* webview; + WebView* webview = nullptr; /** * A controller for showing system dialogs such as a "file picker" @@ -198,9 +198,15 @@ namespace SSC { GtkWidget* menutray = nullptr; GtkWidget* contextMenu = nullptr; + #if SOCKET_RUNTIME_DESKTOP_EXTENSION + void* userContentManager; + void* policies; + void* settings; + #else WebKitUserContentManager* userContentManager; WebKitWebsitePolicies* policies; WebKitSettings* settings; + #endif int contextMenuID; double dragLastX = 0; @@ -285,8 +291,6 @@ namespace SSC { if (seq.find("R") == 0) { this->eval(getResolveToRenderProcessJavaScript(seq, state, value)); } - - this->onMessage(IPC::getResolveToMainProcessMessage(seq, state, value)); } void resolvePromise ( From 70b60531527d75bae90c886f330080d8e6359f39 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:23:11 +0200 Subject: [PATCH 0766/1178] refactor(bin): build linux desktop web process extension --- bin/build-runtime-library.sh | 4 +++- bin/install.sh | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index f1c14cc995..7251d373ee 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -224,9 +224,11 @@ LLAMA_BUILD_INFO function build_linux_desktop_extension_object () { declare source="$root/src/desktop/extension/linux.cc" declare destination="$root/build/$arch-$platform/objects/extensions/linux.o" + mkdir -p "$(dirname "$destination")" + if ! test -f "$object" || (( $(stat_mtime "$source") > $(stat_mtime "$destination") )); then - quiet $clang "${cflags[@]}" -c "$source" -o "$destination" || onsignal + quiet $clang "${cflags[@]}" -DSOCKET_RUNTIME_DESKTOP_EXTENSION=1 -c "$source" -o "$destination" || onsignal return $? fi diff --git a/bin/install.sh b/bin/install.sh index d6b0fc3c54..2d5a097025 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -573,7 +573,9 @@ function _install { if [[ "$platform" != "android" ]]; then cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.a "$SOCKET_HOME/lib$_d/$arch-$platform" - cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.metallib "$SOCKET_HOME/lib$_d/$arch-$platform" + if [[ "$host" == "Darwin" ]]; then + cp -rfp "$BUILD_DIR/$arch-$platform"/lib/*.metallib "$SOCKET_HOME/lib/$arch-$platform" + fi fi if [[ "$host" == "Win32" ]] && [[ "$platform" == "desktop" ]]; then cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.lib "$SOCKET_HOME/lib$_d/$arch-$platform" @@ -587,7 +589,7 @@ function _install { exit 1 fi - if [ "$host" == "Linux" ]; then + if [ "$host" == "Linux" ] || [ "$host" == "Darwin" ]; then echo "# copying pkgconfig to $SOCKET_HOME/pkgconfig" rm -rf "$SOCKET_HOME/pkgconfig" mkdir -p "$SOCKET_HOME/pkgconfig" From 66fff5066de0c6d9c912650f68dc37eaaafb6569 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:23:28 +0200 Subject: [PATCH 0767/1178] refactor(app): improve app init --- src/app/app.cc | 10 ++++------ src/app/app.hh | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 050dc05c57..acfd8437e2 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -572,7 +572,8 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_ANDROID App::App (JNIEnv* env, jobject self, SharedPointer core) - : core(core), + : userConfig(getUserConfig()), + core(core), windowManager(core), serviceWorkerContainer(core), jvm(env), @@ -592,7 +593,8 @@ namespace SSC { {} App::App (SharedPointer core) - : core(core), + : userConfig(getUserConfig()), + core(core), windowManager(core), serviceWorkerContainer(core) { @@ -615,12 +617,8 @@ namespace SSC { } void App::init () { - Env::set("UV_THREADPOOL_SIZE", "256"); #if SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_PLATFORM_LINUX gtk_init_check(0, nullptr); - - auto webContext = webkit_web_context_get_default(); - #elif SOCKET_RUNTIME_PLATFORM_MACOS this->applicationDelegate = [SSCApplicationDelegate new]; this->applicationDelegate.app = this; diff --git a/src/app/app.hh b/src/app/app.hh index 7a8cddd6ed..e6e2ebf3a7 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -96,7 +96,7 @@ namespace SSC { WindowManager windowManager; ServiceWorkerContainer serviceWorkerContainer; SharedPointer core = nullptr; - Map userConfig = SSC::getUserConfig(); + Map userConfig; #if SOCKET_RUNTIME_PLATFORM_WINDOWS App (void *); From 7a100c29c9a6c398ebd855bd17ebbe706e29d273 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:24:10 +0200 Subject: [PATCH 0768/1178] refactor(core/modules/fs.cc): dispatch event loop for 'fs.constants' --- src/core/modules/fs.cc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index e9b6e22e9c..35fe6f6c38 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -2051,12 +2051,14 @@ namespace SSC { const String& seq, const CoreModule::Callback& callback ) const { - static const auto data = JSON::Object(FS_CONSTANTS); - static const auto json = JSON::Object::Entries { - {"source", "fs.constants"}, - {"data", data} - }; + this->core->dispatchEventLoop([=, this]() { + static const auto data = JSON::Object(FS_CONSTANTS); + static const auto json = JSON::Object::Entries { + {"source", "fs.constants"}, + {"data", data} + }; - callback(seq, json, Post {}); + callback(seq, json, Post {}); + }); } } From e05d46030438105480fa145f28884b2215302485 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:24:23 +0200 Subject: [PATCH 0769/1178] refactor(core/modules/os.cc): dispatch event loop for 'os.constants' --- src/core/modules/os.cc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/modules/os.cc b/src/core/modules/os.cc index b1c3d90f22..5fc47f0592 100644 --- a/src/core/modules/os.cc +++ b/src/core/modules/os.cc @@ -659,12 +659,14 @@ namespace SSC { const String& seq, const CoreModule::Callback& callback ) const { - static const auto data = JSON::Object(OS_CONSTANTS); - static const auto json = JSON::Object::Entries { - {"source", "os.constants"}, - {"data", data} - }; + this->core->dispatchEventLoop([=, this]() { + static const auto data = JSON::Object(OS_CONSTANTS); + static const auto json = JSON::Object::Entries { + {"source", "os.constants"}, + {"data", data} + }; - callback(seq, json, Post {}); + callback(seq, json, Post {}); + }); } } From 5ceeeac046bc7eb2e5a516d7f6284a975bc92d36 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:25:03 +0200 Subject: [PATCH 0770/1178] refactor(core/modules/platform): include frame type and frame source in 'event()' --- src/core/modules/platform.cc | 22 ++-------------------- src/core/modules/platform.hh | 2 ++ 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index c35301f815..2abbf6e26c 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -9,6 +9,8 @@ namespace SSC { const String& seq, const String& event, const String& data, + const String& frameType, + const String& frameSource, const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { @@ -17,26 +19,6 @@ namespace SSC { Lock lock(this->core->fs.mutex); this->wasFirstDOMContentLoadedEventDispatched = true; - - for (const auto& tuple : this->core->fs.descriptors) { - auto desc = tuple.second; - if (desc != nullptr) { - desc->stale = true; - } else { - this->core->fs.descriptors.erase(tuple.first); - } - } - - #if !SOCKET_RUNTIME_PLATFORM_ANDROID - for (const auto& tuple : this->core->fs.watchers) { - auto watcher = tuple.second; - if (watcher != nullptr) { - watcher->stop(); - } - } - - this->core->fs.watchers.clear(); - #endif } const auto json = JSON::Object::Entries { diff --git a/src/core/modules/platform.hh b/src/core/modules/platform.hh index 622ba4df65..7db2ff3509 100644 --- a/src/core/modules/platform.hh +++ b/src/core/modules/platform.hh @@ -17,6 +17,8 @@ namespace SSC { const String& seq, const String& event, const String& data, + const String& frameType, + const String& frameSource, const CoreModule::Callback& callback ); From 2da06230a0347dabf39216b7aa2b24a6d28228b9 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:25:26 +0200 Subject: [PATCH 0771/1178] refactor(core/modules/timers): use 'SharedPointer' for 'Timer' instances --- src/core/modules/timers.cc | 28 +++++++++++++++++----------- src/core/modules/timers.hh | 5 +++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/core/modules/timers.cc b/src/core/modules/timers.cc index dd41383f80..5f11859333 100644 --- a/src/core/modules/timers.cc +++ b/src/core/modules/timers.cc @@ -2,6 +2,12 @@ #include "timers.hh" namespace SSC { + CoreTimers::Timer::Timer (CoreTimers* timers, ID id, Callback callback) + : timers(timers), + id(id), + callback(callback) + {} + const CoreTimers::ID CoreTimers::createTimer ( uint64_t timeout, uint64_t interval, @@ -11,26 +17,26 @@ namespace SSC { auto id = rand64(); auto loop = this->core->getEventLoop(); - auto handle = Timer { + auto handle = std::make_shared( this, id, callback - }; + ); if (interval > 0) { - handle.repeat = true; + handle->repeat = true; } - this->handles.insert_or_assign(handle.id, handle); + this->handles.emplace(handle->id, handle); this->core->dispatchEventLoop([=, this]() { Lock lock(this->mutex); if (this->handles.contains(id)) { - auto& handle = this->handles.at(id); - uv_handle_set_data((uv_handle_t*) &handle.timer, &handle); - uv_timer_init(loop, &handle.timer); + auto handle = this->handles.at(id); + uv_handle_set_data((uv_handle_t*) &handle->timer, handle.get()); + uv_timer_init(loop, &handle->timer); uv_timer_start( - &handle.timer, + &handle->timer, [](uv_timer_t* timer) { auto handle = reinterpret_cast(uv_handle_get_data((uv_handle_t*) timer)); if (handle != nullptr) { @@ -66,9 +72,9 @@ namespace SSC { return false; } - auto& handle = this->handles.at(id); - handle.cancelled = true; - uv_timer_stop(&handle.timer); + auto handle = this->handles.at(id); + handle->cancelled = true; + uv_timer_stop(&handle->timer); this->handles.erase(id); return true; } diff --git a/src/core/modules/timers.hh b/src/core/modules/timers.hh index b75cd23f4c..bec2513d2a 100644 --- a/src/core/modules/timers.hh +++ b/src/core/modules/timers.hh @@ -17,13 +17,14 @@ namespace SSC { struct Timer { CoreTimers* timers = nullptr; ID id = 0; - Callback callback; + Callback callback = nullptr; bool repeat = false; bool cancelled = false; uv_timer_t timer; + Timer (CoreTimers* timers, ID id, Callback callback); }; - using Handles = std::map; + using Handles = std::map>; Handles handles; Mutex mutex; From a16a4f57afd6b43bd7aba3e22ed22debf5073323 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:26:09 +0200 Subject: [PATCH 0772/1178] refactor(core): improve resource and shared pointer buffer lifecycles --- src/core/core.cc | 84 ++++++++++++++++++++++++++++++++++++++++---- src/core/core.hh | 10 ++++++ src/core/post.hh | 4 +-- src/core/resource.cc | 10 +++--- src/core/resource.hh | 2 +- src/core/socket.hh | 9 ++--- src/core/trace.hh | 2 +- 7 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 07ec2d10de..a9246ed5f7 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -196,6 +196,7 @@ namespace SSC { do { Lock lock(core->loopMutex); if (core->eventLoopDispatchQueue.size() == 0) break; + dispatch = core->eventLoopDispatchQueue.front(); core->eventLoopDispatchQueue.pop(); } while (0); @@ -335,8 +336,9 @@ namespace SSC { uv_timer_cb invoke; }; - static Timer releaseWeakDescriptors = { - .timeout = 256, // in milliseconds + static Timer releaseStrongReferenceDescriptors = { + .repeated = true, + .timeout = 1024, // in milliseconds .invoke = [](uv_timer_t *handle) { auto core = reinterpret_cast(handle->data); Vector ids; @@ -374,6 +376,33 @@ namespace SSC { } }; + static Timer releaseStrongReferenceSharedPointerBuffers = { + .repeated = true, + .timeout = 16, // in milliseconds + .invoke = [](uv_timer_t *handle) { + auto core = reinterpret_cast(handle->data); + Lock lock(core->mutex); + for (int i = 0; i < core->sharedPointerBuffers.size(); ++i) { + auto& entry = core->sharedPointerBuffers[i]; + // expired + if (entry.ttl <= 16) { + entry.pointer = nullptr; + entry.ttl = 0; + if (i == core->sharedPointerBuffers.size() - 1) { + core->sharedPointerBuffers.pop_back(); + break; + } + } else { + entry.ttl = entry.ttl - 16; + } + } + + if (core->sharedPointerBuffers.size() == 0) { + uv_timer_stop(&releaseStrongReferenceSharedPointerBuffers.handle); + } + } + }; + void Core::initTimers () { if (didTimersInit) { return; @@ -384,7 +413,8 @@ namespace SSC { auto loop = getEventLoop(); Vector timersToInit = { - &releaseWeakDescriptors + &releaseStrongReferenceDescriptors, + &releaseStrongReferenceSharedPointerBuffers }; for (const auto& timer : timersToInit) { @@ -399,7 +429,8 @@ namespace SSC { Lock lock(this->timersMutex); Vector timersToStart = { - &releaseWeakDescriptors + &releaseStrongReferenceDescriptors, + &releaseStrongReferenceSharedPointerBuffers }; for (const auto &timer : timersToStart) { @@ -419,7 +450,7 @@ namespace SSC { } } - didTimersStart = false; + didTimersStart = true; } void Core::stopTimers () { @@ -430,7 +461,8 @@ namespace SSC { Lock lock(this->timersMutex); Vector timersToStop = { - &releaseWeakDescriptors + &releaseStrongReferenceDescriptors, + &releaseStrongReferenceSharedPointerBuffers }; for (const auto& timer : timersToStop) { @@ -473,4 +505,44 @@ namespace SSC { bool Core::clearInterval (const CoreTimers::ID id) { return this->timers.clearInterval(id); } + + void Core::retainSharedPointerBuffer ( + SharedPointer pointer, + unsigned int ttl + ) { + if (pointer == nullptr) { + return; + } + + Lock lock(this->mutex); + for (auto& entry : this->sharedPointerBuffers) { + if (entry.ttl == 0 && entry.pointer == nullptr) { + entry.ttl = ttl; + entry.pointer = pointer; + return; + } + } + + this->sharedPointerBuffers.emplace_back(SharedPointerBuffer { + pointer, + ttl + }); + + uv_timer_again(&releaseStrongReferenceSharedPointerBuffers.handle); + } + + void Core::releaseSharedPointerBuffer (SharedPointer pointer) { + if (pointer == nullptr) { + return; + } + + Lock lock(this->mutex); + for (auto& entry : this->sharedPointerBuffers) { + if (entry.pointer.get() == pointer.get()) { + entry.pointer = nullptr; + entry.ttl = 0; + return; + } + } + } } diff --git a/src/core/core.hh b/src/core/core.hh index bbd35ab12c..0d80fab659 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -59,6 +59,11 @@ namespace SSC { using UDP = CoreUDP; using AI = CoreAI; + struct SharedPointerBuffer { + SharedPointer pointer; + unsigned int ttl = 0; + }; + #if !SOCKET_RUNTIME_PLATFORM_IOS ChildProcess childProcess; #endif @@ -73,8 +78,10 @@ namespace SSC { UDP udp; AI ai; + Vector sharedPointerBuffers; Posts posts; + Mutex mutex; Mutex loopMutex; Mutex postsMutex; Mutex timersMutex; @@ -129,6 +136,9 @@ namespace SSC { // called when the application is shutting down void shutdown (); + void retainSharedPointerBuffer (SharedPointer pointer, unsigned int ttl); + void releaseSharedPointerBuffer (SharedPointer pointer); + // core module post data management Post getPost (uint64_t id); bool hasPost (uint64_t id); diff --git a/src/core/post.hh b/src/core/post.hh index 709a2464d0..fda395226d 100644 --- a/src/core/post.hh +++ b/src/core/post.hh @@ -23,8 +23,8 @@ namespace SSC { size_t length = 0; String headers = ""; String workerId = ""; - SharedPointer eventStream; - SharedPointer chunkStream; + SharedPointer eventStream = nullptr; + SharedPointer chunkStream = nullptr; }; using Posts = std::map; diff --git a/src/core/resource.cc b/src/core/resource.cc index f943ea43a7..c2cd8eaf2c 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -406,6 +406,7 @@ namespace SSC { resource.bytes = nullptr; resource.cache.size = 0; resource.cache.bytes = nullptr; + resource.accessing = false; this->startAccessing(); } @@ -417,9 +418,7 @@ namespace SSC { this->options = resource.options; this->accessing = resource.accessing.load(); - if (this->accessing) { - this->startAccessing(); - } + this->startAccessing(); return *this; } @@ -434,10 +433,9 @@ namespace SSC { resource.bytes = nullptr; resource.cache.size = 0; resource.cache.bytes = nullptr; + resource.accessing = false; - if (this->accessing) { - this->startAccessing(); - } + this->startAccessing(); return *this; } diff --git a/src/core/resource.hh b/src/core/resource.hh index 5aea62743f..395099aa0f 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -80,7 +80,7 @@ namespace SSC { struct Buffer { Atomic size = 0; - SharedPointer bytes; + SharedPointer bytes = nullptr; Buffer (size_t size); Buffer (const Options& options); Buffer (const Buffer& buffer); diff --git a/src/core/socket.hh b/src/core/socket.hh index 19475024a3..ac9d403417 100644 --- a/src/core/socket.hh +++ b/src/core/socket.hh @@ -75,13 +75,14 @@ namespace SSC { Callback callback; Socket* socket = nullptr; RequestContext (Callback callback) { this->callback = callback; } - RequestContext (size_t size, SharedPointer bytes, Callback callback) { + RequestContext (size_t size, SharedPointer bytes, Callback callback) + : size(size), + bytes(bytes), + callback(callback) + { if (bytes != nullptr) { this->buffer = uv_buf_init(bytes.get(), size); - this->bytes = bytes; } - this->size = size; - this->callback = callback; } }; diff --git a/src/core/trace.hh b/src/core/trace.hh index 8ce01a2550..21c1739951 100644 --- a/src/core/trace.hh +++ b/src/core/trace.hh @@ -159,7 +159,7 @@ namespace SSC { /** * A collection of shared spans owned by this `Tracer` instance. */ - SharedPointer spans; + SharedPointer spans = nullptr; /** * The shared span collection index From bb09cf11538bd776bf3cc079e4b76986ca09456b Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 3 Jun 2024 12:27:09 +0200 Subject: [PATCH 0773/1178] refactor(ipc): improve scheme handler memory state --- src/ipc/bridge.cc | 39 ++++-- src/ipc/bridge.hh | 4 +- src/ipc/ipc.hh | 9 -- src/ipc/message.cc | 24 ++-- src/ipc/message.hh | 2 +- src/ipc/navigator.cc | 9 +- src/ipc/preload.cc | 3 + src/ipc/preload.hh | 2 +- src/ipc/router.cc | 11 +- src/ipc/routes.cc | 246 +++++++++++++++++++------------------ src/ipc/scheme_handlers.cc | 227 +++++++++++++++++----------------- src/ipc/scheme_handlers.hh | 10 +- 12 files changed, 298 insertions(+), 288 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index d7d0975633..734e128dd9 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -13,13 +13,13 @@ namespace SSC::IPC { static Mutex mutex; // The `ESM_IMPORT_PROXY_TEMPLATE` is used to provide an ESM module as - // a proxy to a canonical URL for a module so `socket:` and - // `socket:///socket/.js` resolve to the exact + // a proxy to a canonical URL for a module so `{{protocol}}:{{specifier}}` and + // `{{protocol}}://{{bundle_identifier}}/socket/{{pathname}}` resolve to the exact // same module static constexpr auto ESM_IMPORT_PROXY_TEMPLATE = R"S(/** * This module exists to provide a proxy to a canonical URL for a module - * so `socket:` and `socket:///socket/.js` + * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` * resolve to the exact same module instance. * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} */ @@ -369,7 +369,7 @@ export default module)S"; this->schemeHandlers.registerSchemeHandler("ipc", [this]( const auto request, const auto bridge, - auto& callbacks, + auto callbacks, auto callback ) { auto message = Message(request->url(), true); @@ -428,7 +428,7 @@ export default module)S"; message.isHTTP = true; message.cancel = std::make_shared(); - callbacks.cancel = [message] () { + callbacks->cancel = [message] () { if (message.cancel->handler != nullptr) { message.cancel->handler(message.cancel->data); } @@ -436,7 +436,7 @@ export default module)S"; const auto size = request->body.size; const auto bytes = request->body.bytes; - const auto invoked = this->router.invoke(message, request->body.bytes, size, [request, message, callback](Result result) { + const auto invoked = this->router.invoke(message, request->body.bytes, size, [=](Result result) { if (!request->isActive()) { return; } @@ -535,7 +535,7 @@ export default module)S"; this->schemeHandlers.registerSchemeHandler("socket", [this]( const auto request, const auto bridge, - auto& callbacks, + auto callbacks, auto callback ) { auto userConfig = this->userConfig; @@ -636,6 +636,7 @@ export default module)S"; response.setHeader("content-type", "text/html"); response.setHeader("content-length", html.size()); + response.setHeader("cache-control", "public"); response.writeHead(200); response.write(html); } @@ -690,6 +691,7 @@ export default module)S"; // module or stdlib import/fetch `socket:/` which will just // proxy an import into a normal resource request above if (request->hostname.size() == 0) { + const auto specifier = request->pathname.substr(1); auto pathname = request->pathname; if (!pathname.ends_with(".js")) { @@ -716,7 +718,10 @@ export default module)S"; const auto moduleImportProxy = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map { {"url", url}, {"commit", VERSION_HASH_STRING}, - {"pathname", pathname} + {"protocol", "socket"}, + {"pathname", pathname}, + {"specifier", specifier}, + {"bundle_identifier", bundleIdentifier} }); const auto contentType = resource.mimeType(); @@ -745,7 +750,7 @@ export default module)S"; this->schemeHandlers.registerSchemeHandler("node", [this]( const auto request, const auto router, - auto& callbacks, + auto callbacks, auto callback ) { auto userConfig = this->userConfig; @@ -811,21 +816,29 @@ export default module)S"; if (resource.exists()) { const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; - const auto module = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map {{"url", url}}); + const auto moduleImportProxy = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map { + {"url", url}, + {"commit", VERSION_HASH_STRING}, + {"protocol", "node"}, + {"pathname", pathname}, + {"specifier", pathname.substr(1)}, + {"bundle_identifier", bundleIdentifier} + }); + const auto contentType = resource.mimeType(); if (contentType.size() > 0) { response.setHeader("content-type", contentType); } - response.setHeader("content-length", module.size()); + response.setHeader("content-length", moduleImportProxy.size()); if (contentLocation.size() > 0) { response.setHeader("content-location", contentLocation); } response.writeHead(200); - response.write(trim(module)); + response.write(trim(moduleImportProxy)); } return callback(response); @@ -904,7 +917,7 @@ export default module)S"; this->schemeHandlers.registerSchemeHandler(scheme, [this]( const auto request, const auto bridge, - auto& callbacks, + auto callbacks, auto callback ) { if (this->navigator.serviceWorker.registrations.size() > 0) { diff --git a/src/ipc/bridge.hh b/src/ipc/bridge.hh index 0713a06dd5..a0eee75467 100644 --- a/src/ipc/bridge.hh +++ b/src/ipc/bridge.hh @@ -34,7 +34,7 @@ namespace SSC::IPC { SchemeHandlers schemeHandlers; Preload preload; Router router; - Map userConfig = getUserConfig(); + Map userConfig; SharedPointer core = nullptr; uint64_t id = 0; @@ -46,7 +46,7 @@ namespace SSC::IPC { Bridge () = delete; Bridge (const Bridge&) = delete; Bridge (Bridge&&) = delete; - Bridge (SharedPointercore, Map userConfig = getUserConfig()); + Bridge (SharedPointercore, Map userConfig); ~Bridge (); Bridge& operator = (const Bridge&) = delete; diff --git a/src/ipc/ipc.hh b/src/ipc/ipc.hh index f8816d505d..86f75abbe1 100644 --- a/src/ipc/ipc.hh +++ b/src/ipc/ipc.hh @@ -9,13 +9,4 @@ #include "router.hh" #include "scheme_handlers.hh" -namespace SSC::IPC { - inline String getResolveToMainProcessMessage ( - const String& seq, - const String& state, - const String& value - ) { - return String("ipc://resolve?seq=" + seq + "&state=" + state + "&value=" + value); - } -} #endif diff --git a/src/ipc/message.cc b/src/ipc/message.cc index 1c4c8b0882..f806bf9949 100644 --- a/src/ipc/message.cc +++ b/src/ipc/message.cc @@ -1,18 +1,18 @@ #include "message.hh" namespace SSC::IPC { - Message::Message (const Message& message) { - this->buffer.bytes = message.buffer.bytes; - this->buffer.size = message.buffer.size; - this->value = message.value; - this->index = message.index; - this->name = message.name; - this->seq = message.seq; - this->uri = message.uri; - this->args = message.args; - this->isHTTP = message.isHTTP; - this->cancel = message.cancel; - } + Message::Message (const Message& message) + : value(message.value), + index(message.index), + name(message.name), + seq(message.seq), + uri(message.uri), + args(message.args), + isHTTP(message.isHTTP), + cancel(message.cancel), + buffer(message.buffer), + client(message.client) + {} Message::Message (const String& source) : Message(source, false) diff --git a/src/ipc/message.hh b/src/ipc/message.hh index 17b6ea5457..9042c99e36 100644 --- a/src/ipc/message.hh +++ b/src/ipc/message.hh @@ -48,7 +48,7 @@ namespace SSC::IPC { Seq seq = ""; Map args; bool isHTTP = false; - SharedPointer cancel; + SharedPointer cancel = nullptr; Message () = default; Message (const Message& message); diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index 47599822ab..cf85023ae7 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -435,6 +435,8 @@ namespace SSC::IPC { } }; + static const auto wellKnownPaths = FileResource::getWellKnownPaths(); + for (const auto& entry : this->bridge->userConfig) { if (entry.first.starts_with("webview_navigator_mounts_")) { auto key = replace(entry.first, "webview_navigator_mounts_", ""); @@ -461,16 +463,17 @@ namespace SSC::IPC { this->location.mounts.insert_or_assign(path, value); #if SOCKET_RUNTIME_PLATFORM_LINUX auto webContext = webkit_web_context_get_default(); - webkit_web_context_add_path_to_sandbox(webContext, path.c_str(), false); + if (path != wellKnownPaths.home.string()) { + webkit_web_context_add_path_to_sandbox(webContext, path.c_str(), false); + } #endif } } #if SOCKET_RUNTIME_PLATFORM_LINUX - const auto wellKnownPaths = FileResource::getWellKnownPaths(); auto webContext = webkit_web_context_get_default(); for (const auto& entry : wellKnownPaths.entries()) { - if (FileResource::isDirectory(entry)) { + if (FileResource::isDirectory(entry) && entry != wellKnownPaths.home) { webkit_web_context_add_path_to_sandbox(webContext, entry.c_str(), false); } } diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 148ed7e593..046cf82ec6 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -39,6 +39,9 @@ namespace SSC::IPC { : options(options) { this->configure(); + if (this->options.userConfig.size() == 0) { + this->options.userConfig = getUserConfig(); + } } void Preload::configure () { diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh index d716d0b420..7f1921087c 100644 --- a/src/ipc/preload.hh +++ b/src/ipc/preload.hh @@ -70,7 +70,7 @@ namespace SSC::IPC { Headers headers; // depends on 'features.useHTMLMarkup' Map metadata; // depends on 'features.useHTMLMarkup' Map env; - Map userConfig = getUserConfig(); + Map userConfig; Vector argv; String userScript = ""; diff --git a/src/ipc/router.cc b/src/ipc/router.cc index 1767b45797..93bfa0a018 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -78,7 +78,9 @@ namespace SSC::IPC { size_t size ) { return this->invoke(uri, bytes, size, [this](auto result) { - this->bridge->send(result.seq, result.str(), result.post); + this->bridge->dispatch([=, this] () { + this->bridge->send(result.seq, result.str(), result.post); + }); }); } @@ -150,9 +152,7 @@ namespace SSC::IPC { } } - Tracer tracer("IPC::Router"); if (context.async) { - auto span = tracer.span("invoke (async)"); return this->bridge->dispatch([=, this]() mutable { context.callback(msg, this, [=, this](const auto result) mutable { if (result.seq == "-1") { @@ -160,21 +160,16 @@ namespace SSC::IPC { } else { callback(result); } - - span->end(); }); }); } - auto span = tracer.span("invoke (sync)"); context.callback(msg, this, [=, this](const auto result) mutable { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { callback(result); } - - span->end(); }); return true; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index b600f954e7..7f0cf92fc0 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -213,14 +213,16 @@ static void mapIPCRoutes (Router *router) { return reply(Result::Err { message, "Application is invalid state" }); } - const auto window = app->windowManager.getWindow(0); + app->dispatch([=]() { + const auto window = app->windowManager.getWindow(0); - if (window == nullptr) { - return reply(Result::Err { message, "Application is invalid state" }); - } + if (window == nullptr) { + return reply(Result::Err { message, "Application is invalid state" }); + } - window->setTrayMenu(message.value); - reply(Result::Data { message, JSON::Object {} }); + window->setTrayMenu(message.value); + reply(Result::Data { message, JSON::Object {} }); + }); #else reply(Result::Err { message, @@ -249,14 +251,16 @@ static void mapIPCRoutes (Router *router) { return reply(Result::Err { message, "Application is invalid state" }); } - const auto window = app->windowManager.getWindow(0); + app->dispatch([=]() { + const auto window = app->windowManager.getWindow(0); - if (window == nullptr) { - return reply(Result::Err { message, "Application is invalid state" }); - } + if (window == nullptr) { + return reply(Result::Err { message, "Application is invalid state" }); + } - window->setSystemMenu(message.value); - reply(Result::Data { message, JSON::Object {} }); + window->setSystemMenu(message.value); + reply(Result::Data { message, JSON::Object {} }); + }); #else reply(Result::Err { message, @@ -288,22 +292,24 @@ static void mapIPCRoutes (Router *router) { return reply(Result::Err { message, "Application is invalid state" }); } - const auto window = app->windowManager.getWindow(0); + app->dispatch([=]() { + const auto window = app->windowManager.getWindow(0); - if (window == nullptr) { - return reply(Result::Err { message, "Application is invalid state" }); - } + if (window == nullptr) { + return reply(Result::Err { message, "Application is invalid state" }); + } - const auto enabled = message.get("enabled") == "true"; - int indexMain; - int indexSub; + const auto enabled = message.get("enabled") == "true"; + int indexMain; + int indexSub; - REQUIRE_AND_GET_MESSAGE_VALUE(indexMain, "indexMain", std::stoi); - REQUIRE_AND_GET_MESSAGE_VALUE(indexSub, "indexSub", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(indexMain, "indexMain", std::stoi); + REQUIRE_AND_GET_MESSAGE_VALUE(indexSub, "indexSub", std::stoi); - window->setSystemMenuItemEnabled(enabled, indexMain, indexSub); + window->setSystemMenuItemEnabled(enabled, indexMain, indexSub); - reply(Result::Data { message, JSON::Object {} }); + reply(Result::Data { message, JSON::Object {} }); + }); #else reply(Result::Err { message, @@ -1997,6 +2003,8 @@ static void mapIPCRoutes (Router *router) { message.seq, message.value, message.get("data"), + frameType, + frameSource, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) ); }); @@ -3029,83 +3037,83 @@ static void mapIPCRoutes (Router *router) { }); } - if ( - app->windowManager.getWindow(targetWindowIndex) != nullptr && - app->windowManager.getWindowStatus(targetWindowIndex) != WindowManager::WindowStatus::WINDOW_NONE - ) { - return reply(Result::Err { - message, - "Window with index " + message.get("targetWindowIndex") + " already exists" - }); - } - - const auto window = app->windowManager.getWindow(0); - const auto screen = window->getScreenSize(); - auto options = WindowOptions {}; - - options.shouldExitApplicationOnClose = message.get("shouldExitApplicationOnClose") == "true" ? true : false; - options.headless = app->userConfig["build_headless"] == "true"; - - if (message.get("headless") == "true") { - options.headless = true; - } else if (message.get("headless") == "false") { - options.headless = false; - } - - if (message.has("radius")) { - options.radius = std::stof(message.get("radius")); - } - - if (message.has("margin")) { - options.margin = std::stof(message.get("margin")); - } - - options.width = message.get("width").size() - ? window->getSizeInPixels(message.get("width"), screen.width) - : 0; - - options.height = message.get("height").size() - ? window->getSizeInPixels(message.get("height"), screen.height) - : 0; + app->dispatch([=]() { + if ( + app->windowManager.getWindow(targetWindowIndex) != nullptr && + app->windowManager.getWindowStatus(targetWindowIndex) != WindowManager::WindowStatus::WINDOW_NONE + ) { + return reply(Result::Err { + message, + "Window with index " + message.get("targetWindowIndex") + " already exists" + }); + } - options.minWidth = message.get("minWidth").size() - ? window->getSizeInPixels(message.get("minWidth"), screen.width) - : 0; + const auto window = app->windowManager.getWindow(0); + const auto screen = window->getScreenSize(); + auto options = WindowOptions {}; - options.minHeight = message.get("minHeight").size() - ? window->getSizeInPixels(message.get("minHeight"), screen.height) - : 0; + options.shouldExitApplicationOnClose = message.get("shouldExitApplicationOnClose") == "true" ? true : false; + options.headless = app->userConfig["build_headless"] == "true"; - options.maxWidth = message.get("maxWidth").size() - ? window->getSizeInPixels(message.get("maxWidth"), screen.width) - : screen.width; + if (message.get("headless") == "true") { + options.headless = true; + } else if (message.get("headless") == "false") { + options.headless = false; + } - options.maxHeight = message.get("maxHeight").size() - ? window->getSizeInPixels(message.get("maxHeight"), screen.height) - : screen.height; + if (message.has("radius")) { + options.radius = std::stof(message.get("radius")); + } - options.resizable = message.get("resizable") == "true" ? true : false; - options.frameless = message.get("frameless") == "true" ? true : false; - options.closable = message.get("closable") == "true" ? true : false; - options.maximizable = message.get("maximizable") == "true" ? true : false; - options.minimizable = message.get("minimizable") == "true" ? true : false; - options.aspectRatio = message.get("aspectRatio"); - options.titlebarStyle = message.get("titlebarStyle"); - options.windowControlOffsets = message.get("windowControlOffsets"); - options.backgroundColorLight = message.get("backgroundColorLight"); - options.backgroundColorDark = message.get("backgroundColorDark"); - options.utility = message.get("utility") == "true" ? true : false; - options.debug = message.get("debug") == "true" ? true : false; - options.userScript = message.get("userScript"); - options.index = targetWindowIndex; - options.RUNTIME_PRIMORDIAL_OVERRIDES = message.get("__runtime_primordial_overrides__"); - options.userConfig = INI::parse(message.get("config")); + if (message.has("margin")) { + options.margin = std::stof(message.get("margin")); + } - if (options.index >= SOCKET_RUNTIME_MAX_WINDOWS) { - options.features.useGlobalCommonJS = false; - } + options.width = message.get("width").size() + ? window->getSizeInPixels(message.get("width"), screen.width) + : 0; + + options.height = message.get("height").size() + ? window->getSizeInPixels(message.get("height"), screen.height) + : 0; + + options.minWidth = message.get("minWidth").size() + ? window->getSizeInPixels(message.get("minWidth"), screen.width) + : 0; + + options.minHeight = message.get("minHeight").size() + ? window->getSizeInPixels(message.get("minHeight"), screen.height) + : 0; + + options.maxWidth = message.get("maxWidth").size() + ? window->getSizeInPixels(message.get("maxWidth"), screen.width) + : screen.width; + + options.maxHeight = message.get("maxHeight").size() + ? window->getSizeInPixels(message.get("maxHeight"), screen.height) + : screen.height; + + options.resizable = message.get("resizable") == "true" ? true : false; + options.frameless = message.get("frameless") == "true" ? true : false; + options.closable = message.get("closable") == "true" ? true : false; + options.maximizable = message.get("maximizable") == "true" ? true : false; + options.minimizable = message.get("minimizable") == "true" ? true : false; + options.aspectRatio = message.get("aspectRatio"); + options.titlebarStyle = message.get("titlebarStyle"); + options.windowControlOffsets = message.get("windowControlOffsets"); + options.backgroundColorLight = message.get("backgroundColorLight"); + options.backgroundColorDark = message.get("backgroundColorDark"); + options.utility = message.get("utility") == "true" ? true : false; + options.debug = message.get("debug") == "true" ? true : false; + options.userScript = message.get("userScript"); + options.index = targetWindowIndex; + options.RUNTIME_PRIMORDIAL_OVERRIDES = message.get("__runtime_primordial_overrides__"); + options.userConfig = INI::parse(message.get("config")); + + if (options.index >= SOCKET_RUNTIME_MAX_WINDOWS) { + options.features.useGlobalCommonJS = false; + } - app->dispatch([=]() { auto createdWindow = app->windowManager.createWindow(options); if (message.has("title")) { @@ -3142,20 +3150,22 @@ static void mapIPCRoutes (Router *router) { REQUIRE_AND_GET_MESSAGE_VALUE(targetWindowIndex, "targetWindowIndex", std::stoi); - const auto window = app->windowManager.getWindow(targetWindowIndex); - const auto windowStatus = app->windowManager.getWindowStatus(targetWindowIndex); + app->dispatch([=]() { + const auto window = app->windowManager.getWindow(targetWindowIndex); + const auto windowStatus = app->windowManager.getWindowStatus(targetWindowIndex); - if (!window || windowStatus == WindowManager::WindowStatus::WINDOW_NONE) { - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Target window not found"}, - {"type", "NotFoundError"} - } - }); - } + if (!window || windowStatus == WindowManager::WindowStatus::WINDOW_NONE) { + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Target window not found"}, + {"type", "NotFoundError"} + } + }); + } - reply(Result::Data { message, window->getBackgroundColor() }); + reply(Result::Data { message, window->getBackgroundColor() }); + }); }); /** @@ -3178,20 +3188,22 @@ static void mapIPCRoutes (Router *router) { REQUIRE_AND_GET_MESSAGE_VALUE(targetWindowIndex, "targetWindowIndex", std::stoi); - const auto window = app->windowManager.getWindow(targetWindowIndex); - const auto windowStatus = app->windowManager.getWindowStatus(targetWindowIndex); + app->dispatch([=]() { + const auto window = app->windowManager.getWindow(targetWindowIndex); + const auto windowStatus = app->windowManager.getWindowStatus(targetWindowIndex); - if (!window || windowStatus == WindowManager::WindowStatus::WINDOW_NONE) { - return reply(Result::Err { - message, - JSON::Object::Entries { - {"message", "Target window not found"}, - {"type", "NotFoundError"} - } - }); - } + if (!window || windowStatus == WindowManager::WindowStatus::WINDOW_NONE) { + return reply(Result::Err { + message, + JSON::Object::Entries { + {"message", "Target window not found"}, + {"type", "NotFoundError"} + } + }); + } - reply(Result::Data { message, window->getTitle() }); + reply(Result::Data { message, window->getTitle() }); + }); }); /** diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 2e1b2be7f6..51c00ff50c 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -128,8 +128,6 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer return; } - auto tracer = Tracer("onURISchemeRequest"); - auto bridge = &window->bridge; auto request = IPC::SchemeHandlers::Request::Builder(&bridge->schemeHandlers, schemeRequest) .setMethod(String(webkit_uri_scheme_request_get_http_method(schemeRequest))) @@ -139,10 +137,7 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer .setBody(webkit_uri_scheme_request_get_http_body(schemeRequest)) .build(); - auto span = tracer.begin("handleRequest"); const auto handled = bridge->schemeHandlers.handleRequest(request, [=](const auto& response) mutable { - tracer.end("handleRequest"); - // TODO(@jwerle): handle uri scheme response }); if (!handled) { @@ -152,7 +147,95 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer } } #elif SOCKET_RUNTIME_PLATFORM_ANDROID -void ANDROID_EXTERNAL(ipc, SchemeHandlers, onWebResourceRequest) () { +extern "C" { + jboolean ANDROID_EXTERNAL(ipc, SchemeHandlers, handleRequest) ( + JNIEnv* env, + jobject self, + jint index, + jobject requestObject + ) { + auto app = App::sharedApplication(); + + if (!app) { + ANDROID_THROW(env, "Missing 'App' in environment"); + return false; + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + ANDROID_THROW(env, "Invalid window requested"); + return false; + } + + const auto method = Android::StringWrap(env, (jstring) CallClassMethodFromAndroidEnvironment( + env, + Object, + requestObject, + "getMethod", + "()Ljava/lang/String;" + )).str(); + + const auto headers = Android::StringWrap(env, (jstring) CallClassMethodFromAndroidEnvironment( + env, + Object, + requestObject, + "getHeaders", + "()Ljava/lang/String;" + )).str(); + + const auto requestBodyByteArray = (jbyteArray) CallClassMethodFromAndroidEnvironment( + env, + Object, + requestObject, + "getBody", + "()[B" + ); + + const auto requestBodySize = requestBodyByteArray != nullptr + ? env->GetArrayLength(requestBodyByteArray) + : 0; + + const auto bytes = requestBodySize > 0 + ? new char[requestBodySize]{0} + : nullptr; + + if (requestBodyByteArray) { + env->GetByteArrayRegion( + requestBodyByteArray, + 0, + requestBodySize, + (jbyte*) bytes + ); + } + + const auto requestObjectRef = env->NewGlobalRef(requestObject); + const auto request = IPC::SchemeHandlers::Request::Builder( + &window->bridge.schemeHandlers, + requestObjectRef + ) + .setMethod(method) + // copies all request soup headers + .setHeaders(headers) + // reads and copies request stream body + .setBody(requestBodySize, bytes) + .build(); + + const auto handled = window->bridge.schemeHandlers.handleRequest(request, [=](const auto& response) { + if (bytes) { + delete [] bytes; + } + + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + attachment.env->DeleteGlobalRef(requestObjectRef); + }); + + if (!handled) { + env->DeleteGlobalRef(requestObjectRef); + } + + return handled; + } } #endif @@ -294,7 +377,7 @@ namespace SSC::IPC { return this->handlers.contains(scheme); } - SchemeHandlers::Handler& SchemeHandlers::getHandlerForScheme (const String& scheme) { + SchemeHandlers::Handler SchemeHandlers::getHandlerForScheme (const String& scheme) { Lock lock(this->mutex); return this->handlers.at(scheme); } @@ -421,7 +504,7 @@ namespace SSC::IPC { do { Lock lock(this->mutex); - this->activeRequests.insert_or_assign(request->id, request); + this->activeRequests.emplace(request->id, request); } while (0); if (request->error != nullptr) { @@ -435,8 +518,10 @@ namespace SSC::IPC { auto span = request->tracer.span("handler"); this->bridge->dispatch([=, this] () mutable { + Lock lock(this->mutex); + auto request = this->activeRequests[id]; if (request != nullptr && request->isActive() && !request->isCancelled()) { - handler(request, this->bridge, request->callbacks, [=, this](auto& response) mutable { + handler(request, this->bridge, &request->callbacks, [=, this](auto& response) mutable { // make sure the response was finished before // calling the `callback` function below response.finish(); @@ -628,7 +713,11 @@ namespace SSC::IPC { return *this; } - if (this->request->method == "POST" || this->request->method == "PUT" || this->request->method == "PATCH") { + if ( + this->request->method == "POST" || + this->request->method == "PUT" || + this->request->method == "PATCH" + ) { GError* error = nullptr; this->request->body.bytes = std::make_shared(MAX_URI_SCHEME_REQUEST_BODY_BYTES); const auto success = g_input_stream_read_all( @@ -645,7 +734,11 @@ namespace SSC::IPC { #endif SchemeHandlers::Request::Builder& SchemeHandlers::Request::Builder::setBody (const Body& body) { - if (this->request->method == "POST" || this->request->method == "PUT" || this->request->method == "PATCH") { + if ( + this->request->method == "POST" || + this->request->method == "PUT" || + this->request->method == "PATCH" + ) { this->request->body = body; } return *this; @@ -1110,13 +1203,13 @@ namespace SSC::IPC { return true; #elif SOCKET_RUNTIME_PLATFORM_LINUX auto span = this->tracer.span("write"); - this->buffers.push_back(bytes); g_memory_input_stream_add_data( reinterpret_cast(this->platformResponseStream), reinterpret_cast(bytes.get()), (gssize) size, nullptr ); + this->request->bridge->core->retainSharedPointerBuffer(bytes, 512); span->end(); return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS @@ -1189,9 +1282,7 @@ namespace SSC::IPC { if (contentLength > 0) { this->writeHead(); - if (responseResource.read()) { - return this->write(contentLength, responseResource.bytes); - } + return this->write(contentLength, responseResource.read()); } return false; @@ -1256,14 +1347,7 @@ namespace SSC::IPC { platformResponse ); - g_object_unref(platformResponse); - auto buffers = this->buffers; - this->request->bridge->core->setInterval(16, [buffers, platformResponseStream] (auto cancel) { - if (!G_IS_INPUT_STREAM(platformResponseStream) || !g_input_stream_has_pending(platformResponseStream)) { - g_object_unref(platformResponseStream); - cancel(); - } - }); + g_object_unref(platformResponseStream); } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID @@ -1399,8 +1483,10 @@ namespace SSC::IPC { return false; } - if (WEBKIT_IS_URI_SCHEME_REQUEST(this->request->platformRequest)) { + if (this->request && WEBKIT_IS_URI_SCHEME_REQUEST(this->request->platformRequest)) { webkit_uri_scheme_request_finish_error(this->request->platformRequest, error); + } else { + return false; } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID @@ -1481,96 +1567,3 @@ namespace SSC::IPC { } } } - -#if SOCKET_RUNTIME_PLATFORM_ANDROID -extern "C" { - jboolean ANDROID_EXTERNAL(ipc, SchemeHandlers, handleRequest) ( - JNIEnv* env, - jobject self, - jint index, - jobject requestObject - ) { - auto app = App::sharedApplication(); - - if (!app) { - ANDROID_THROW(env, "Missing 'App' in environment"); - return false; - } - - const auto window = app->windowManager.getWindow(index); - - if (!window) { - ANDROID_THROW(env, "Invalid window requested"); - return false; - } - - const auto method = Android::StringWrap(env, (jstring) CallClassMethodFromAndroidEnvironment( - env, - Object, - requestObject, - "getMethod", - "()Ljava/lang/String;" - )).str(); - - const auto headers = Android::StringWrap(env, (jstring) CallClassMethodFromAndroidEnvironment( - env, - Object, - requestObject, - "getHeaders", - "()Ljava/lang/String;" - )).str(); - - const auto requestBodyByteArray = (jbyteArray) CallClassMethodFromAndroidEnvironment( - env, - Object, - requestObject, - "getBody", - "()[B" - ); - - const auto requestBodySize = requestBodyByteArray != nullptr - ? env->GetArrayLength(requestBodyByteArray) - : 0; - - const auto bytes = requestBodySize > 0 - ? new char[requestBodySize]{0} - : nullptr; - - if (requestBodyByteArray) { - env->GetByteArrayRegion( - requestBodyByteArray, - 0, - requestBodySize, - (jbyte*) bytes - ); - } - - const auto requestObjectRef = env->NewGlobalRef(requestObject); - const auto request = IPC::SchemeHandlers::Request::Builder( - &window->bridge.schemeHandlers, - requestObjectRef - ) - .setMethod(method) - // copies all request soup headers - .setHeaders(headers) - // reads and copies request stream body - .setBody(requestBodySize, bytes) - .build(); - - const auto handled = window->bridge.schemeHandlers.handleRequest(request, [=](const auto& response) { - if (bytes) { - delete [] bytes; - } - - const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - attachment.env->DeleteGlobalRef(requestObjectRef); - }); - - if (!handled) { - env->DeleteGlobalRef(requestObjectRef); - } - - return handled; - } -} -#endif diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 4bd92067e1..694e40a343 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -46,9 +46,9 @@ namespace SSC::IPC { }; struct RequestCallbacks { - Function cancel; - Function finish; - Function fail; + Function cancel = nullptr; + Function finish = nullptr; + Function fail = nullptr; }; #if SOCKET_RUNTIME_PLATFORM_APPLE @@ -237,7 +237,7 @@ namespace SSC::IPC { using Handler = Function, const Bridge*, - RequestCallbacks& callbacks, + RequestCallbacks* callbacks, HandlerCallback )>; @@ -274,7 +274,7 @@ namespace SSC::IPC { bool handleRequest (SharedPointer request, const HandlerCallback calllback = nullptr); bool isRequestActive (uint64_t id); bool isRequestCancelled (uint64_t id); - Handler& getHandlerForScheme (const String& scheme); + Handler getHandlerForScheme (const String& scheme); }; } #endif From 4ab8227a4171eeb37712b3fee9ee0297ce2f2297 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 5 Jun 2024 13:43:44 +0200 Subject: [PATCH 0774/1178] fix(api/util.js): fix ESM detection regex --- api/util.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/util.js b/api/util.js index 3b3853bde8..de25423b61 100644 --- a/api/util.js +++ b/api/util.js @@ -989,13 +989,15 @@ export function inherits (Constructor, Super) { Object.setPrototypeOf(Constructor.prototype, Super.prototype) } +export const ESM_TEST_REGEX = /\b(import\s*[\w{},*\s]*\s*from\s*['"][^'"]+['"]|export\s+(?:\*\s*from\s*['"][^'"]+['"]|default\s*from\s*['"][^'"]+['"]|[\w{}*\s,]+))\s*(?:;|\b)/ + /** * @ignore * @param {string} source * @return {boolean} */ export function isESMSource (source) { - if (/\b(import\s*[\w{},*\s]*\s*from\s*['"][^'"]+['"]|export\s*(?:\*\s*from\s*['"][^'"]+['"]|default\s*from\s*['"][^'"]+['"]|[\w{}*\s,]+))\s*(?:;|\b)/.test(source)) { + if (ESM_TEST_REGEX.test(source)) { return true } From aac282d71d1056e4a5c6a541bcef38a3bdc674e7 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 5 Jun 2024 13:43:55 +0200 Subject: [PATCH 0775/1178] chore(api/fs): clean up --- api/fs/dir.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/fs/dir.js b/api/fs/dir.js index 5cb3083b0c..198ef8c37e 100644 --- a/api/fs/dir.js +++ b/api/fs/dir.js @@ -127,7 +127,6 @@ export class Dir { } } - console.log({ results }) results = results.map((result) => { if (this.withFileTypes) { result = Dirent.from(result) From f07ac2fa8da2c0efa9d7112aab5c73daee8eaf0f Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 5 Jun 2024 13:45:18 +0200 Subject: [PATCH 0776/1178] refactor(api/ipc.js): clean up --- api/ipc.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 0ca82795e6..6a8973a206 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1257,12 +1257,12 @@ export async function write (command, value, buffer, options) { const uri = `ipc://${command}?${params}` if ( - typeof GlobalIPCExtensionPostMessage === 'function' && + typeof __global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { let response = null try { - response = await GlobalIPCExtensionPostMessage(uri, buffer) + response = await __global_ipc_extension_handler(uri, buffer) } catch (err) { return Result.from(null, err) } @@ -1372,15 +1372,15 @@ export async function request (command, value, options) { await ready() const params = new IPCSearchParams(value, Date.now()) - const uri = `ipc://${command}` + const uri = `ipc://${command}?${params}` if ( - typeof GlobalIPCExtensionPostMessage === 'function' && + typeof __global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { let response = null try { - response = await GlobalIPCExtensionPostMessage(`${uri}?${params}`) + response = await __global_ipc_extension_handler(uri) } catch (err) { return Result.from(null, err) } @@ -1416,14 +1416,12 @@ export async function request (command, value, options) { }) } - const query = `?${params}` - request.responseType = options?.responseType ?? '' - request.open('GET', uri + query) + request.open('GET', uri) request.send(null) if (debug.enabled) { - debug.log('ipc.request:', uri + query) + debug.log('ipc.request:', uri) } return await new Promise((resolve) => { From 75f13f428cc2ee092a63ce85e1ff09d0c6808dd4 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Wed, 5 Jun 2024 13:45:40 +0200 Subject: [PATCH 0777/1178] fix(api/internal): fix document title to window title proxy --- api/internal/primitives.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 9eadf83a37..6ee9f294c7 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -406,23 +406,25 @@ export function init () { // create tag in document if it doesn't exist globalThis.document.title ||= '' // initial value - globalThis.document.addEventListener('DOMContentLoaded', () => { - const title = globalThis.document.title - if (title.length !== 0) { - const index = globalThis.__args.index - const o = new URLSearchParams({ value: title, index }).toString() - ipc.postMessage(`ipc://window.setTitle?${o}`) + globalThis.document.addEventListener('DOMContentLoaded', async () => { + const { title } = globalThis.document + if (title) { + const result = await ipc.request('window.setTitle', { + targetWindowIndex: globalThis.__args.index, + value: title + }) } }) // globalThis.document is unconfigurable property so we need to use MutationObserver here - const observer = new MutationObserver((mutationList) => { + const observer = new MutationObserver(async (mutationList) => { for (const mutation of mutationList) { if (mutation.type === 'childList') { - const index = globalThis.__args.index const title = mutation.addedNodes[0].textContent - const o = new URLSearchParams({ value: title, index }).toString() - ipc.postMessage(`ipc://window.setTitle?${o}`) + const result = await ipc.request('window.setTitle', { + targetWindowIndex: globalThis.__args.index, + value: title + }) } } }) From 50b20982b4ceac8d4ad7e532e62bfa0eeace1384 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 13:46:07 +0200 Subject: [PATCH 0778/1178] fix(core): improve timers --- src/core/core.cc | 23 +++++++---------- src/core/modules/timers.cc | 53 ++++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index a9246ed5f7..e9cfe7454a 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -184,10 +184,11 @@ namespace SSC { didLoopInit = true; Lock lock(this->loopMutex); - uv_loop_init(&eventLoop); - eventLoopAsync.data = (void *) this; + uv_loop_init(&this->eventLoop); + uv_loop_set_data(&this->eventLoop, reinterpret_cast<void*>(this)); + this->eventLoopAsync.data = reinterpret_cast<void*>(this); - uv_async_init(&eventLoop, &eventLoopAsync, [](uv_async_t *handle) { + uv_async_init(&this->eventLoop, &this->eventLoopAsync, [](uv_async_t *handle) { auto core = reinterpret_cast<Core*>(handle->data); while (true) { @@ -376,16 +377,19 @@ namespace SSC { } }; + #define RELEASE_STRONG_REFERENCE_SHARED_POINTER_BUFFERS_RESOLUTION 8 + static Timer releaseStrongReferenceSharedPointerBuffers = { .repeated = true, - .timeout = 16, // in milliseconds + .timeout = RELEASE_STRONG_REFERENCE_SHARED_POINTER_BUFFERS_RESOLUTION, // in milliseconds .invoke = [](uv_timer_t *handle) { auto core = reinterpret_cast<Core *>(handle->data); + static constexpr auto resolution = RELEASE_STRONG_REFERENCE_SHARED_POINTER_BUFFERS_RESOLUTION; Lock lock(core->mutex); for (int i = 0; i < core->sharedPointerBuffers.size(); ++i) { auto& entry = core->sharedPointerBuffers[i]; // expired - if (entry.ttl <= 16) { + if (entry.ttl <= resolution) { entry.pointer = nullptr; entry.ttl = 0; if (i == core->sharedPointerBuffers.size() - 1) { @@ -393,7 +397,7 @@ namespace SSC { break; } } else { - entry.ttl = entry.ttl - 16; + entry.ttl = entry.ttl - resolution; } } @@ -515,13 +519,6 @@ namespace SSC { } Lock lock(this->mutex); - for (auto& entry : this->sharedPointerBuffers) { - if (entry.ttl == 0 && entry.pointer == nullptr) { - entry.ttl = ttl; - entry.pointer = pointer; - return; - } - } this->sharedPointerBuffers.emplace_back(SharedPointerBuffer { pointer, diff --git a/src/core/modules/timers.cc b/src/core/modules/timers.cc index 5f11859333..028555518c 100644 --- a/src/core/modules/timers.cc +++ b/src/core/modules/timers.cc @@ -33,27 +33,46 @@ namespace SSC { Lock lock(this->mutex); if (this->handles.contains(id)) { auto handle = this->handles.at(id); - uv_handle_set_data((uv_handle_t*) &handle->timer, handle.get()); + uv_handle_set_data((uv_handle_t*) &handle->timer, (void*) id); uv_timer_init(loop, &handle->timer); uv_timer_start( &handle->timer, [](uv_timer_t* timer) { - auto handle = reinterpret_cast<Timer*>(uv_handle_get_data((uv_handle_t*) timer)); - if (handle != nullptr) { - handle->callback([handle] () { - handle->timers->core->dispatchEventLoop([handle]() { - handle->timers->cancelTimer(handle->id); - }); - }); - - do { - Lock lock(handle->timers->mutex); - if (!handle->repeat) { - if (handle->timers->handles.contains(handle->id)) { - handle->timers->handles.erase(handle->id); - } - } - } while (0); + auto loop = uv_handle_get_loop(reinterpret_cast<uv_handle_t*>(timer)); + auto core = reinterpret_cast<Core*>(uv_loop_get_data(loop)); + auto id = reinterpret_cast<ID>(uv_handle_get_data(reinterpret_cast<uv_handle_t*>(timer))); + + // bad state + if (core == nullptr) { + uv_timer_stop(timer); + return; + } + + Lock lock(core->timers.mutex); + + // cancelled (removed from 'handles') + if (!core->timers.handles.contains(id)) { + uv_timer_stop(timer); + return; + } + + auto handle = core->timers.handles.at(id); + + // bad ref + if (handle == nullptr) { + uv_timer_stop(timer); + return; + } + + // `callback` to timer callback is a "cancel" function + handle->callback([=] () { + core->timers.cancelTimer(id); + }); + + if (!handle->repeat) { + if (core->timers.handles.contains(id)) { + core->timers.handles.erase(id); + } } }, timeout, From bec333d4d7e5896051f80d753f4d65a7f198b8a4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 13:46:38 +0200 Subject: [PATCH 0779/1178] refctor(desktop): improve desktop startup and memory management --- src/desktop/extension/linux.cc | 8 ++--- src/desktop/main.cc | 57 ++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc index 0444c9fb6b..b75f78e688 100644 --- a/src/desktop/extension/linux.cc +++ b/src/desktop/extension/linux.cc @@ -138,9 +138,9 @@ extern "C" { ) { auto frame = webkit_web_page_get_main_frame(page); auto context = webkit_frame_get_js_context(frame); - auto GlobalIPCExtensionPostMessage = jsc_value_new_function( + auto __global_ipc_extension_handler = jsc_value_new_function( context, - "GlobalIPCExtensionPostMessage", + "__global_ipc_extension_handler", G_CALLBACK(onMessage), context, nullptr, @@ -152,8 +152,8 @@ extern "C" { jsc_context_set_value( context, - "GlobalIPCExtensionPostMessage", - GlobalIPCExtensionPostMessage + "__global_ipc_extension_handler", + __global_ipc_extension_handler ); } diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 919a6380f4..0b69300bb3 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -68,18 +68,20 @@ static Function<void(int)> shutdownHandler; static void defaultWindowSignalHandler (int signal) { auto app = App::sharedApplication(); if (app != nullptr && app->core->platform.wasFirstDOMContentLoadedEventDispatched) { - auto defaultWindow = app->windowManager.getWindow(0); - if (defaultWindow != nullptr) { - if (defaultWindow->status < WindowManager::WindowStatus::WINDOW_CLOSING) { - const auto json = JSON::Object { - JSON::Object::Entries { - {"signal", signal} - } - }; + app->dispatch([=] () { + auto defaultWindow = app->windowManager.getWindow(0); + if (defaultWindow != nullptr) { + if (defaultWindow->status < WindowManager::WindowStatus::WINDOW_CLOSING) { + const auto json = JSON::Object { + JSON::Object::Entries { + {"signal", signal} + } + }; - defaultWindow->eval(getEmitToRenderProcessJavaScript("signal", json.str())); + defaultWindow->eval(getEmitToRenderProcessJavaScript("signal", json.str())); + } } - } + }); } } @@ -116,7 +118,7 @@ static void handleApplicationURLEvent (const String url) { if (app_ptr != nullptr) { for (auto window : app_ptr->windowManager.windows) { if (window != nullptr) { - if (window->index == 0) { + if (window->index == 0 && window->window && window->webview) { gtk_widget_show_all(GTK_WIDGET(window->window)); gtk_widget_grab_focus(GTK_WIDGET(window->webview)); gtk_widget_grab_focus(GTK_WIDGET(window->window)); @@ -287,7 +289,7 @@ MAIN { auto appInstanceLockFd = open(appInstanceLock.c_str(), O_CREAT | O_EXCL, 0600); auto appProtocol = userConfig["meta_application_protocol"]; auto dbusError = DBusError {}; dbus_error_init(&dbusError); - auto connection = dbus_bus_get(DBUS_BUS_SESSION, &dbusError); + static auto connection = dbus_bus_get(DBUS_BUS_SESSION, &dbusError); auto dbusBundleIdentifier = replace(bundleIdentifier, "-", "_"); // instance is running if fd was acquired @@ -326,8 +328,8 @@ MAIN { dbus_connection_add_filter(connection, onDBusMessage, nullptr, nullptr); - static Function<void()> pollForMessage = [connection]() { - Thread thread([connection] () { + static Function<void()> pollForMessage = []() { + Thread thread([] () { while (dbus_connection_read_write_dispatch(connection, 256)); app_ptr->dispatch(pollForMessage); }); @@ -575,7 +577,7 @@ MAIN { static Process* process = nullptr; static Function<void(bool)> createProcess; - auto killProcess = [&](Process* processToKill) { + auto killProcess = [](Process* processToKill) { if (processToKill != nullptr) { processToKill->kill(); processToKill->wait(); @@ -588,7 +590,7 @@ MAIN { } }; - auto createProcessTemplate = [&]<class... Args>(Args... args) { + auto createProcessTemplate = [killProcess]<class... Args>(Args... args) { return [=](bool force) { if (process != nullptr && force) { killProcess(process); @@ -604,7 +606,7 @@ MAIN { cmd, argvForward.str(), cwd, - [&](String const &out) { + [&exitCode](String const &out) mutable { IPC::Message message(out); if (message.name == "exit") { @@ -625,7 +627,7 @@ MAIN { createProcess(true); - shutdownHandler = [&](int signum) { + shutdownHandler = [=](int signum) mutable { #if SOCKET_RUNTIME_PLATFORM_LINUX unlink(appInstanceLock.c_str()); #endif @@ -656,7 +658,7 @@ MAIN { ); #endif - const auto onStdErr = [&](const auto& output) { + const auto onStdErr = [](const auto& output) { #if SOCKET_RUNTIME_PLATFORM_APPLE os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", output.c_str()); #endif @@ -673,7 +675,7 @@ MAIN { // # "Backend" -> Main // Launch the backend process and connect callbacks to the stdio and stderr pipes. // - const auto onStdOut = [&](const auto& output) { + const auto onStdOut = [](const auto& output) { const auto message = IPC::Message(output); if (message.index > 0 && message.name.size() == 0) { @@ -882,7 +884,7 @@ MAIN { // When a window or the app wants to exit, // we clean up the windows and the backend process. // - shutdownHandler = [&](int code) { + shutdownHandler = [](int code) { #if SOCKET_RUNTIME_PLATFORM_LINUX unlink(appInstanceLock.c_str()); #endif @@ -1012,14 +1014,13 @@ MAIN { SET_DEFAULT_WINDOW_SIGNAL_HANDLER(SIGSYS) #endif - app.dispatch([=]() { Vector<String> properties = { "window_width", "window_height", "window_min_width", "window_min_height", "window_max_width", "window_max_height" }; - auto setDefaultValue = [&](String property) { + auto setDefaultValue = [](String property) { // for min values set 0 if (property.find("min") != -1) { return "0"; @@ -1045,7 +1046,7 @@ MAIN { } } - auto getProperty = [&](String property) { + auto getProperty = [](String property) { if (userConfig.count(property) > 0) { return userConfig[property]; } @@ -1097,7 +1098,8 @@ MAIN { auto screen = defaultWindow->getScreenSize(); serviceWorkerUserConfig["webview_watch_reload"] = "false"; - serviceWorkerWindowOptions.shouldExitApplicationOnClose = false; + // if the service worker window dies, then the app should too + serviceWorkerWindowOptions.shouldExitApplicationOnClose = true; serviceWorkerWindowOptions.minHeight = defaultWindow->getSizeInPixels("30%", screen.height); serviceWorkerWindowOptions.height = defaultWindow->getSizeInPixels("80%", screen.height); serviceWorkerWindowOptions.minWidth = defaultWindow->getSizeInPixels("40%", screen.width); @@ -1122,6 +1124,7 @@ MAIN { app.serviceWorkerContainer.init(&defaultWindow->bridge); } + msleep(256); defaultWindow->show(); if (devPort > 0) { @@ -1148,8 +1151,9 @@ MAIN { String value; std::getline(std::cin, value); - auto t = Thread([&](String value) { + auto t = Thread([](String value) { auto app = App::sharedApplication(); + auto defaultWindow = app->windowManager.getWindow(0); while (!app->core->platform.wasFirstDOMContentLoadedEventDispatched) { msleep(128); @@ -1169,7 +1173,6 @@ MAIN { t.detach(); } - }); // // # Event Loop From 853f0e694d96875980ca5fd4d434e7a87e49afbc Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 13:47:13 +0200 Subject: [PATCH 0780/1178] refactor(ipc): improve timing, memory management, and bad pointer ref --- src/ipc/bridge.cc | 37 +++++++++++++++++++++++++++++++------ src/ipc/message.hh | 2 +- src/ipc/navigator.cc | 10 +++++++++- src/ipc/preload.cc | 6 +++--- src/ipc/routes.cc | 2 +- src/ipc/scheme_handlers.cc | 27 ++++++++++++++++++++++++--- 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 734e128dd9..82bc64ac99 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -364,7 +364,9 @@ export default module)S"; return this->emit(name, json.str()); } - void Bridge::configureSchemeHandlers (const SchemeHandlers::Configuration& configuration) { + void Bridge::configureSchemeHandlers ( + const SchemeHandlers::Configuration& configuration + ) { this->schemeHandlers.configure(configuration); this->schemeHandlers.registerSchemeHandler("ipc", [this]( const auto request, @@ -540,6 +542,19 @@ export default module)S"; ) { auto userConfig = this->userConfig; auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + auto app = App::sharedApplication(); + auto window = app->windowManager.getWindowForBridge(bridge); + + // if there was no window, then this is a bad request as scheme + // handlers should only be handled directly in a window with + // a navigator and a connected IPC bridge + if (window == nullptr) { + auto response = SchemeHandlers::Response(request); + response.writeHead(400); + callback(response); + return; + } + // the location of static application resources const auto applicationResources = FileResource::getResourcesPath().string(); // default response is 404 @@ -655,7 +670,7 @@ export default module)S"; request->query, request->headers, ServiceWorkerContainer::FetchBody { request->body.size, request->body.bytes }, - ServiceWorkerContainer::Client { request->client.id, this->preload } + ServiceWorkerContainer::Client { request->client.id, window->index } }; const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { @@ -674,7 +689,7 @@ export default module)S"; }); if (fetched) { - this->core->setTimeout(32000, [request] () mutable { + this->core->setTimeout(32000, [=] () mutable { if (request->isActive()) { auto response = SchemeHandlers::Response(request, 408); response.fail("ServiceWorker request timed out."); @@ -915,11 +930,21 @@ export default module)S"; }); this->schemeHandlers.registerSchemeHandler(scheme, [this]( - const auto request, - const auto bridge, + auto request, + auto bridge, auto callbacks, auto callback ) { + auto app = App::sharedApplication(); + auto window = app->windowManager.getWindowForBridge(bridge); + + if (window == nullptr) { + auto response = SchemeHandlers::Response(request); + response.writeHead(400); + callback(response); + return; + } + if (this->navigator.serviceWorker.registrations.size() > 0) { auto hostname = request->hostname; auto pathname = request->pathname; @@ -942,7 +967,7 @@ export default module)S"; request->query, request->headers, ServiceWorkerContainer::FetchBody { request->body.size, request->body.bytes }, - ServiceWorkerContainer::Client { request->client.id, this->preload } + ServiceWorkerContainer::Client { request->client.id, window->index } }; const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { diff --git a/src/ipc/message.hh b/src/ipc/message.hh index 9042c99e36..a99d612d28 100644 --- a/src/ipc/message.hh +++ b/src/ipc/message.hh @@ -46,7 +46,7 @@ namespace SSC::IPC { String uri = ""; int index = -1; Seq seq = ""; - Map args; + Map args = {}; bool isHTTP = false; SharedPointer<MessageCancellation> cancel = nullptr; diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index cf85023ae7..3581efa1f7 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -221,7 +221,15 @@ namespace SSC::IPC { WebKitPolicyDecisionType decisionType, gpointer userData ) { - auto navigator = reinterpret_cast<Navigator*>(userData); + auto app = App::sharedApplication(); + auto window = app->windowManager.getWindowForWebView(webview); + + if (!window) { + webkit_policy_decision_ignore(decision); + return false; + } + + auto navigator = &window->bridge.navigator; if (decisionType != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { webkit_policy_decision_use(decision); diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 046cf82ec6..1f492fb02b 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -91,10 +91,10 @@ namespace SSC::IPC { {"argv", JSON::Array {}}, {"client", JSON::Object {}}, {"config", JSON::Object {}}, - {"debug", options.debug}, - {"headless", options.headless}, + {"debug", this->options.debug}, + {"headless", this->options.headless}, {"env", JSON::Object {}}, - {"index", options.index} + {"index", this->options.index} } }; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 7f0cf92fc0..835753e5bd 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2975,7 +2975,7 @@ static void mapIPCRoutes (Router *router) { reply(Result::Data { message, window->json() }); - App::sharedApplication()->core->setTimeout(16, [=] () { + app->core->setTimeout(16, [=] () { app->windowManager.destroyWindow(targetWindowIndex); }); }); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 51c00ff50c..f3e7cf29be 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -347,6 +347,10 @@ namespace SSC::IPC { } SchemeHandlers::~SchemeHandlers () { + Lock lock(this->mutex); + for (auto& entry : this->activeRequests) { + entry.second->handlers = nullptr; + } #if SOCKET_RUNTIME_PLATFORM_APPLE this->configuration.webview = nullptr; #endif @@ -911,11 +915,28 @@ namespace SSC::IPC { } bool SchemeHandlers::Request::isActive () const { - return this->handlers != nullptr && this->handlers->isRequestActive(this->id); + auto app = App::sharedApplication(); + auto window = app->windowManager.getWindowForBridge(this->bridge); + + // only a scheme handler owned by this bridge and attached to a + // window should be considered "active" + // scheme handlers SHOULD only work windows that have a navigator + if (window != nullptr && this->handlers != nullptr) { + return this->handlers->isRequestActive(this->id); + } + + return false; } bool SchemeHandlers::Request::isCancelled () const { - return this->handlers != nullptr && this->handlers->isRequestCancelled(this->id); + auto app = App::sharedApplication(); + auto window = app->windowManager.getWindowForBridge(this->bridge); + + if (window != nullptr && this->handlers != nullptr) { + return this->handlers->isRequestCancelled(this->id); + } + + return false; } JSON::Object SchemeHandlers::Request::json () const { @@ -1209,7 +1230,7 @@ namespace SSC::IPC { (gssize) size, nullptr ); - this->request->bridge->core->retainSharedPointerBuffer(bytes, 512); + this->request->bridge->core->retainSharedPointerBuffer(bytes, 256); span->end(); return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS From 3d38726facd2fff1bb5d26d35c597282f8231a85 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 13:47:27 +0200 Subject: [PATCH 0781/1178] fix(serviceworker): fix preload and client data --- src/serviceworker/container.cc | 6 ++++-- src/serviceworker/container.hh | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/serviceworker/container.cc b/src/serviceworker/container.cc index 9914158af5..76319dcb5e 100644 --- a/src/serviceworker/container.cc +++ b/src/serviceworker/container.cc @@ -430,7 +430,8 @@ namespace SSC { (html.find("<script") != String::npos || html.find("<SCRIPT") != String::npos) ) ) { - request.client.preload.metadata["runtime-frame-source"] = "serviceworker"; + auto preload = IPC::Preload(this->bridge->preload.options); + preload.metadata["runtime-frame-source"] = "serviceworker"; auto begin = String("<meta name=\"begin-runtime-preload\">"); auto end = String("<meta name=\"end-runtime-preload\">"); @@ -441,7 +442,8 @@ namespace SSC { html.erase(x, (y - x) + end.size()); } - html = request.client.preload.insertIntoHTML(html, { + preload.compile(); + html = preload.insertIntoHTML(html, { .protocolHandlerSchemes = this->protocols.getSchemes() }); diff --git a/src/serviceworker/container.hh b/src/serviceworker/container.hh index 5b1d7193f4..11ad8fff63 100644 --- a/src/serviceworker/container.hh +++ b/src/serviceworker/container.hh @@ -4,7 +4,6 @@ #include "../core/headers.hh" #include "../core/json.hh" #include "../ipc/client.hh" -#include "../ipc/preload.hh" #include "protocols.hh" @@ -16,7 +15,11 @@ namespace SSC { class ServiceWorkerContainer { public: using ID = IPC::Client::ID; - using Client = IPC::Client; + + struct Client { + ID id = 0; + int index = 0; + }; struct RegistrationOptions { enum class Type { Classic, Module }; From b2d1a68a55725c23d17f3a6b4957fbabdc6ad0f8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 13:48:13 +0200 Subject: [PATCH 0782/1178] fix(window): fix window lifecycles and memory management --- src/window/linux.cc | 268 +++++++++++++++++++++++++----------------- src/window/manager.cc | 67 ++++------- src/window/win.cc | 2 +- src/window/window.hh | 21 +--- 4 files changed, 182 insertions(+), 176 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 9d4eebdb39..0be074e2d3 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -89,7 +89,10 @@ namespace SSC { auto bytes = socket_runtime_init_get_user_config_bytes(); auto size = socket_runtime_init_get_user_config_bytes_size(); static auto data = String(reinterpret_cast<const char*>(bytes), size); - data += "[web-process-extension]\ncwd = " + cwd + "\n"; + data += "[web-process-extension]\n"; + data += "cwd = " + cwd + "\n"; + data += "host = " + getDevHost() + "\n"; + data += "port = " + std::to_string(getDevPort()) + "\n"; webkit_web_context_set_web_extensions_initialization_user_data( webContext, @@ -189,6 +192,9 @@ namespace SSC { userConfig["permissions_allow_data_access"] != "false" ); + this->accelGroup = gtk_accel_group_new(); + this->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + auto cookieManager = webkit_web_context_get_cookie_manager(webContext); webkit_cookie_manager_set_accept_policy(cookieManager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); @@ -218,8 +224,6 @@ namespace SSC { this->contextMenu = nullptr; this->contextMenuID = 0; - this->accelGroup = gtk_accel_group_new(); - this->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); this->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); this->bridge.navigateFunction = [this] (const auto url) { @@ -232,6 +236,7 @@ namespace SSC { this->bridge.preload = IPC::createPreload({ .clientId = this->bridge.id, + .index = options.index, .userScript = options.userScript }); @@ -260,29 +265,24 @@ namespace SSC { bool isDarkMode = false; auto isKDEDarkMode = []() -> bool { - std::string home = std::getenv("HOME") ? std::getenv("HOME") : ""; - std::string filepath = home + "/.config/kdeglobals"; - std::ifstream file(filepath); + static const auto paths = FileResource::getWellKnownPaths(); + static const auto kdeglobals = paths.home / ".config" / "kdeglobals"; - if (!file.is_open()) { - std::cerr << "Failed to open file: " << filepath << std::endl; + if (!FileResource::isFile(kdeglobals)) { return false; } - std::string line; + auto file = FileResource(kdeglobals); - while (getline(file, line)) { - std::string lower_line; + if (!file.exists() || !file.hasAccess()) { + return false; + } - std::transform( - line.begin(), - line.end(), - std::back_inserter(lower_line), - [](unsigned char c) { return std::tolower(c); } - ); + const auto bytes = file.read(); + const auto lines = split(bytes, '\n'); - if (lower_line.find("dark") != std::string::npos) { - std::cout << "Found dark setting in line: " << line << std::endl; + for (const auto& line : lines) { + if (toLowerCase(line).find("dark") != String::npos) { return true; } } @@ -290,10 +290,10 @@ namespace SSC { return false; }; - auto isGnomeDarkMode = [&]() -> bool { - GtkStyleContext* context = gtk_widget_get_style_context(window); - + auto isGnomeDarkMode = [this]() -> bool { + GtkStyleContext* context = gtk_widget_get_style_context(this->window); GdkRGBA background_color; + // FIXME(@jwerle): this is deprecated gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, &background_color); bool is_dark_theme = (0.299* background_color.red + @@ -320,7 +320,8 @@ namespace SSC { gdk_rgba_parse(&color, this->options.backgroundColorLight.c_str()); } - gtk_widget_override_background_color(window, GTK_STATE_FLAG_NORMAL, &color); + // FIXME(@jwerle): this is deprecated + gtk_widget_override_background_color(this->window, GTK_STATE_FLAG_NORMAL, &color); } webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(webview), &webviewBackground); @@ -834,51 +835,49 @@ namespace SSC { ); */ - /* - * FIXME(@jwerle): this can race - ideally, this is fixed in an abstraction - auto onDestroy = +[](GtkWidget*, gpointer arg) { - auto* w = static_cast<Window*>(arg); - auto* app = App::sharedApplication(); - app->windowManager.destroyWindow(w->index); + g_signal_connect( + G_OBJECT(this->window), + "destroy", + G_CALLBACK((+[](GtkWidget* object, gpointer arg) { + auto w = reinterpret_cast<Window*>(arg); + auto app = App::sharedApplication(); + int index = w != nullptr ? w->index : -1; + + for (auto& window : app->windowManager.windows) { + if (window == nullptr) { + continue; + } - for (auto window : app->windowManager.windows) { - if (window == nullptr || window.get() == w) { - continue; + if (window->window == object) { + index = window->index; + if (window->webview) { + window->webview = g_object_ref(window->webview); + } + window->window = nullptr; + window->vbox = nullptr; + break; + } } - JSON::Object json = JSON::Object::Entries { - {"data", w->index} - }; + if (index >= 0) { + for (auto window : app->windowManager.windows) { + if (window == nullptr || window->index == index) { + continue; + } - window->eval(getEmitToRenderProcessJavaScript("window-closed", json.str())); - } - return FALSE; - }; + JSON::Object json = JSON::Object::Entries { + {"data", index} + }; - g_signal_connect( - G_OBJECT(this->window), - "destroy", - G_CALLBACK(onDestroy), - this - ); - - g_signal_connect( - G_OBJECT(this->window), - "delete-event", - G_CALLBACK(+[](GtkWidget* widget, GdkEvent*, gpointer arg) { - auto* w = static_cast<Window*>(arg); + window->eval(getEmitToRenderProcessJavaScript("window-closed", json.str())); + } - if (w->options.shouldExitApplicationOnClose == false) { - w->eval(getEmitToRenderProcessJavaScript("windowHide", "{}")); - return gtk_widget_hide_on_delete(widget); + app->windowManager.destroyWindow(index); } - - w->close(0); return FALSE; - }), + })), this ); - */ g_signal_connect( G_OBJECT(this->window), @@ -943,26 +942,26 @@ namespace SSC { this->userContentManager = nullptr; } - if (this->webview) { - g_object_unref(GTK_WIDGET(this->webview)); - this->webview = nullptr; - } - - if (this->window) { - auto w = this->window; - this->window = nullptr; - // gtk_widget_destroy(w); - } - if (this->accelGroup) { g_object_unref(this->accelGroup); this->accelGroup = nullptr; } + if (this->webview) { + gtk_widget_set_can_focus(GTK_WIDGET(this->webview), false); + this->webview = nullptr; + } + if (this->vbox) { - g_object_unref(this->vbox); + gtk_container_remove(GTK_CONTAINER(this->window), this->vbox); this->vbox = nullptr; } + + if (this->window) { + auto window = this->window; + this->window = nullptr; + g_object_unref(window); + } } ScreenSize Window::getScreenSize () { @@ -1030,38 +1029,70 @@ namespace SSC { } void Window::eval (const String& source) { - if (this->webview) { - webkit_web_view_evaluate_javascript( - this->webview, - String(source).c_str(), - -1, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr - ); - } + auto app = App::sharedApplication(); + app->dispatch([=, this] () { + if (this->webview) { + webkit_web_view_evaluate_javascript( + this->webview, + source.c_str(), + source.size(), + nullptr, // world name + nullptr, // source URI + nullptr, // cancellable + +[]( // callback + GObject* object, + GAsyncResult* result, + gpointer userData + ) { + webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, nullptr); + }, + nullptr // user data + ); + } + }); } void Window::show () { - gtk_widget_realize(this->window); - - this->index = this->options.index; - if (this->options.headless == false) { - gtk_widget_show_all(this->window); - gtk_window_present(GTK_WINDOW(this->window)); - } + auto app = App::sharedApplication(); + app->dispatch([=, this] () { + if (this->window != nullptr) { + gtk_widget_realize(this->window); + + this->index = this->options.index; + if (this->options.headless == false) { + gtk_widget_show_all(this->window); + gtk_window_present(GTK_WINDOW(this->window)); + } + } + }); } void Window::hide () { gtk_widget_realize(this->window); gtk_widget_hide(this->window); - this->eval(getEmitToRenderProcessJavaScript("windowHide", "{}")); + this->eval(getEmitToRenderProcessJavaScript("window-hidden", "{}")); } void Window::setBackgroundColor (const String& rgba) { + const auto parts = split(trim(replace(replace(rgba, "rgba(", ""), ")", "")), ','); + int r = 0, g = 0, b = 0; + float a = 1.0; + + if (parts.size() == 4) { + try { r = std::stoi(trim(parts[0])); } + catch (...) {} + + try { g = std::stoi(trim(parts[1])); } + catch (...) {} + + try { b = std::stoi(trim(parts[2])); } + catch (...) {} + try { a = std::stof(trim(parts[3])); } + catch (...) {} + + return this->setBackgroundColor(r, g, b, a); + } } void Window::setBackgroundColor (int r, int g, int b, float a) { @@ -1071,51 +1102,60 @@ namespace SSC { color.blue = b / 255.0; color.alpha = a; - gtk_widget_realize(this->window); - // FIXME(@jwerle): this is deprecated - gtk_widget_override_background_color( - this->window, GTK_STATE_FLAG_NORMAL, &color - ); + if (this->window) { + gtk_widget_realize(this->window); + // FIXME(@jwerle): this is deprecated + gtk_widget_override_background_color( + this->window, GTK_STATE_FLAG_NORMAL, &color + ); + } } String Window::getBackgroundColor () { GtkStyleContext* context = gtk_widget_get_style_context(this->window); GdkRGBA color; + // FIXME(@jwerle): this is deprecated gtk_style_context_get_background_color(context, gtk_widget_get_state_flags(this->window), &color); - char str[100]; + char string[100]; snprintf( - str, - sizeof(str), + string, + sizeof(string), "rgba(%d, %d, %d, %f)", - (int)(color.red* 255), - (int)(color.green* 255), - (int)(color.blue* 255), + (int) (255 * color.red), + (int) (255 * color.green), + (int) (255 * color.blue), color.alpha ); - return String(str); + return string; } void Window::showInspector () { - auto inspector = webkit_web_view_get_inspector(this->webview); - if (inspector) { - webkit_web_inspector_show(inspector); + if (this->webview) { + const auto inspector = webkit_web_view_get_inspector(this->webview); + if (inspector) { + webkit_web_inspector_show(inspector); + } } } void Window::exit (int code) { - auto cb = this->onExit; + const auto callback = this->onExit; this->onExit = nullptr; - if (cb != nullptr) cb(code); + if (callback != nullptr) { + callback(code); + } } - void Window::kill () {} + void Window::kill () { + } void Window::close (int _) { if (this->window) { + g_object_ref(this->window); gtk_window_close(GTK_WINDOW(this->window)); } } @@ -1133,8 +1173,12 @@ namespace SSC { } void Window::navigate (const String& url) { - if (this->webview) { - webkit_web_view_load_uri(this->webview, url.c_str()); + static auto app = App::sharedApplication(); + auto webview = this->webview; + if (webview) { + app->dispatch([=] () { + webkit_web_view_load_uri(webview, url.c_str()); + }); } } @@ -1271,7 +1315,9 @@ namespace SSC { GList* children = gtk_container_get_children(GTK_CONTAINER(menu)); for (iter = children; iter != nullptr; iter = g_list_next(iter)) { - gtk_widget_destroy(GTK_WIDGET(iter->data)); + if (iter && iter->data) { + gtk_widget_destroy(GTK_WIDGET(iter->data)); + } } g_list_free(children); diff --git a/src/window/manager.cc b/src/window/manager.cc index 77eb6c1a44..7bca57b6aa 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -1,10 +1,6 @@ #include "../app/app.hh" #include "window.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" -#endif - namespace SSC { WindowManager::WindowManager (SharedPointer<Core> core) : core(core), @@ -60,10 +56,12 @@ namespace SSC { return this->getWindow(index, WindowStatus::WINDOW_EXITING); } - SharedPointer<WindowManager::ManagedWindow> WindowManager::getWindowForBridge (IPC::Bridge* bridge) { + SharedPointer<WindowManager::ManagedWindow> WindowManager::getWindowForBridge ( + const IPC::Bridge* bridge + ) { for (const auto& window : this->windows) { if (window != nullptr && &window->bridge == bridge) { - return window; + return this->getWindow(window->index); } } return nullptr; @@ -123,28 +121,29 @@ namespace SSC { auto window = this->windows[index]; if (window != nullptr) { - if (window->status < WINDOW_CLOSING) { - window->close(); - } - - if (window->status < WINDOW_KILLING) { - window->kill(); - } + window->close(); - Lock lock(this->mutex); this->windows[index] = nullptr; if (window->options.shouldExitApplicationOnClose) { App::sharedApplication()->dispatch([window]() { window->exit(0); }); + } else { + window->kill(); } } } - SharedPointer<WindowManager::ManagedWindow> WindowManager::createWindow (const WindowOptions& options) { + SharedPointer<WindowManager::ManagedWindow> WindowManager::createWindow ( + const WindowOptions& options + ) { Lock lock(this->mutex); - if (this->destroyed || options.index < 0 || options.index >= this->windows.size()) { + if ( + this->destroyed || + options.index < 0 || + options.index >= this->windows.size() + ) { return nullptr; } @@ -235,10 +234,6 @@ namespace SSC { windowOptions.userConfig[tuple.first] = tuple.second; } - if (options.debug || isDebugEnabled()) { - this->log("Creating Window#" + std::to_string(options.index)); - } - auto window = std::make_shared<ManagedWindow>(*this, this->core, windowOptions); window->status = WindowStatus::WINDOW_CREATED; @@ -280,8 +275,7 @@ namespace SSC { const auto window = this->createWindow(windowOptions); - if (isDebugEnabled()) { - this->lastDebugLogLine = std::chrono::system_clock::now(); + if (isDebugEnabled() && window->index == 0) { #if SOCKET_RUNTIME_PLATFORM_LINUX if (devHost.starts_with("http:")) { auto webContext = webkit_web_context_get_default(); @@ -321,8 +315,7 @@ namespace SSC { WindowManager::ManagedWindow::~ManagedWindow () {} void WindowManager::ManagedWindow::show () { - auto index = std::to_string(this->options.index); - manager.log("Showing Window#" + index); + auto index = std::to_string(this->index); status = WindowStatus::WINDOW_SHOWING; Window::show(); status = WindowStatus::WINDOW_SHOWN; @@ -333,8 +326,7 @@ namespace SSC { status > WindowStatus::WINDOW_HIDDEN && status < WindowStatus::WINDOW_EXITING ) { - auto index = std::to_string(this->options.index); - manager.log("Hiding Window#" + index); + auto index = std::to_string(this->index); status = WindowStatus::WINDOW_HIDING; Window::hide(); status = WindowStatus::WINDOW_HIDDEN; @@ -343,22 +335,16 @@ namespace SSC { void WindowManager::ManagedWindow::close (int code) { if (status < WindowStatus::WINDOW_CLOSING) { - auto index = std::to_string(this->options.index); - manager.log("Closing Window#" + index + " (code=" + std::to_string(code) + ")"); + auto index = std::to_string(this->index); status = WindowStatus::WINDOW_CLOSING; Window::close(code); - if (this->options.shouldExitApplicationOnClose) { - status = WindowStatus::WINDOW_EXITED; - } else { - status = WindowStatus::WINDOW_CLOSED; - } + status = WindowStatus::WINDOW_CLOSED; } } void WindowManager::ManagedWindow::exit (int code) { if (status < WindowStatus::WINDOW_EXITING) { - auto index = std::to_string(this->options.index); - manager.log("Exiting Window#" + index + " (code=" + std::to_string(code) + ")"); + auto index = std::to_string(this->index); status = WindowStatus::WINDOW_EXITING; Window::exit(code); status = WindowStatus::WINDOW_EXITED; @@ -367,17 +353,15 @@ namespace SSC { void WindowManager::ManagedWindow::kill () { if (status < WindowStatus::WINDOW_KILLING) { - auto index = std::to_string(this->options.index); - manager.log("Killing Window#" + index); + auto index = std::to_string(this->index); status = WindowStatus::WINDOW_KILLING; Window::kill(); status = WindowStatus::WINDOW_KILLED; - manager.destroyWindow(this->options.index); } } JSON::Object WindowManager::ManagedWindow::json () const { - const auto index = this->options.index; + const auto index = this->index; const auto size = this->getSize(); const auto id = this->bridge.id; @@ -395,8 +379,3 @@ namespace SSC { }; } } - -#if SOCKET_RUNTIME_PLATFORM_ANDROID -extern "C" { -} -#endif diff --git a/src/window/win.cc b/src/window/win.cc index 160d72b1d0..ec7c3dd724 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1577,7 +1577,7 @@ namespace SSC { void Window::hide () { ShowWindow(window, SW_HIDE); UpdateWindow(window); - this->eval(getEmitToRenderProcessJavaScript("windowHide", "{}")); + this->eval(getEmitToRenderProcessJavaScript("window-hidden", "{}")); } void Window::resize (HWND window) { diff --git a/src/window/window.hh b/src/window/window.hh index eaba52c2d8..4dcbe813aa 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -371,8 +371,6 @@ namespace SSC { JSON::Object json () const; }; - std::chrono::system_clock::time_point lastDebugLogLine = std::chrono::system_clock::now(); - Vector<SharedPointer<ManagedWindow>> windows; WindowManagerOptions options; SharedPointer<Core> core = nullptr; @@ -384,29 +382,12 @@ namespace SSC { WindowManager (const WindowManager&) = delete; ~WindowManager (); - void inline log (const String& line) { - using namespace std::chrono; - - if (this->destroyed || !isDebugEnabled()) { - return; - } - - const auto now = system_clock::now(); - const auto delta = duration_cast<milliseconds>(now - this->lastDebugLogLine).count(); - - std::cout << "• " << line; - std::cout << " \033[0;32m+" << delta << "ms\033[0m"; - std::cout << std::endl; - - this->lastDebugLogLine = now; - } - void destroy (); void configure (const WindowManagerOptions& configuration); SharedPointer<ManagedWindow> getWindow (int index, const WindowStatus status); SharedPointer<ManagedWindow> getWindow (int index); - SharedPointer<ManagedWindow> getWindowForBridge (IPC::Bridge* bridge); + SharedPointer<ManagedWindow> getWindowForBridge (const IPC::Bridge* bridge); SharedPointer<ManagedWindow> getWindowForWebView (WebView* webview);; SharedPointer<ManagedWindow> getOrCreateWindow (int index); SharedPointer<ManagedWindow> getOrCreateWindow (int index, const WindowOptions& options); From 2e7184896237e7d6a7fe48599b4d795617217ff3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 18:17:48 +0200 Subject: [PATCH 0783/1178] fix(src/core/modules/ai.hh): set 'nullptr' default values --- src/core/modules/ai.hh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh index ce598bd8ee..19da1828d7 100644 --- a/src/core/modules/ai.hh +++ b/src/core/modules/ai.hh @@ -77,14 +77,14 @@ namespace SSC { using Logger = std::function<void(ggml_log_level, const char*, void*)>; gpt_params params; - llama_model* model; - llama_context* ctx; + llama_model* model = nullptr; + llama_context* ctx = nullptr; llama_context* guidance = nullptr; struct llama_sampling_context* sampling; - std::vector<llama_token>* input_tokens; - std::ostringstream* output_ss; - std::vector<llama_token>* output_tokens; + std::vector<llama_token>* input_tokens = nullptr; + std::ostringstream* output_ss = nullptr; + std::vector<llama_token>* output_tokens = nullptr; std::vector<llama_token> session_tokens; std::vector<llama_token> embd_inp; std::vector<llama_token> guidance_inp; From 3105772cc73b95700a59425014aca01b823ce12a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 5 Jun 2024 18:18:10 +0200 Subject: [PATCH 0784/1178] fix(window): fix 'close()' on darwin --- src/window/apple.mm | 103 +++++++++++++++++++------------------------- src/window/linux.cc | 3 +- 2 files changed, 46 insertions(+), 60 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index cfd595155a..09478aa0d7 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -142,35 +142,22 @@ - (BOOL) windowShouldClose: (SSCWindow*) _ { auto window = (Window*) objc_getAssociatedObject(self, "window"); auto app = App::sharedApplication(); - if (!app || !window || !window->isExiting) { + if (!app || !window || window->isExiting) { return true; } + auto index = window->index; const JSON::Object json = JSON::Object::Entries { {"data", window->index} }; for (auto window : app->windowManager.windows) { - if (window != nullptr) { + if (window != nullptr && window->index != index) { window->eval(getEmitToRenderProcessJavaScript("window-closed", json.str())); } } -#if !__has_feature(objc_arc) - if (window->window) { - [window->window release]; - } -#endif - - window->window = nullptr; - if (window->options.shouldExitApplicationOnClose) { - window->isExiting = true; - window->exit(0); - return true; - } - - window->eval(getEmitToRenderProcessJavaScript("window-hide", "")); - window->hide(); + app->windowManager.destroyWindow(index); return true; } #elif SOCKET_RUNTIME_PLATFORM_IOS @@ -729,59 +716,59 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { void Window::exit (int code) { isExiting = true; - this->close(code);; - if (onExit != nullptr) onExit(code); + const auto callback = this->onExit; + this->onExit = nullptr; + if (callback != nullptr) { + callback(code); + } } - void Window::kill () { - } + void Window::kill () {} void Window::close (int code) { const auto app = App::sharedApplication(); - App::sharedApplication()->dispatch([this]() { - if (this->windowDelegate != nullptr) { - objc_removeAssociatedObjects(this->windowDelegate); - } + if (this->windowDelegate != nullptr) { + objc_removeAssociatedObjects(this->windowDelegate); + } - if (this->webview != nullptr) { - objc_removeAssociatedObjects(this->webview); - [this->webview stopLoading]; - [this->webview.configuration.userContentController removeAllScriptMessageHandlers]; - [this->webview removeFromSuperview]; - this->webview.navigationDelegate = nullptr; - this->webview.UIDelegate = nullptr; - } + if (this->webview != nullptr) { + objc_removeAssociatedObjects(this->webview); + [this->webview stopLoading]; + [this->webview.configuration.userContentController removeAllScriptMessageHandlers]; + [this->webview removeFromSuperview]; + this->webview.navigationDelegate = nullptr; + this->webview.UIDelegate = nullptr; + } - if (this->window != nullptr) { - #if SOCKET_RUNTIME_PLATFORM_MACOS - auto contentView = this->window.contentView; - auto subviews = NSMutableArray.array; + if (this->window != nullptr) { + #if SOCKET_RUNTIME_PLATFORM_MACOS + auto contentView = this->window.contentView; + auto subviews = NSMutableArray.array; - for (NSView* view in contentView.subviews) { - if (view == this->webview) { - this->webview = nullptr; - } - [view removeFromSuperview]; - [view release]; + for (NSView* view in contentView.subviews) { + if (view == this->webview) { + this->webview = nullptr; } + [view removeFromSuperview]; + [view release]; + } - [this->window performClose: nil]; - this->window = nullptr; - this->window.webview = nullptr; - this->window.delegate = nullptr; - this->window.contentView = nullptr; - - if (this->window.titleBarView) { - [this->window.titleBarView removeFromSuperview]; - #if !__has_feature(objc_arc) - [this->window.titleBarView release]; - #endif - } + [this->window performClose: nullptr]; + this->window = nullptr; + this->window.webview = nullptr; + this->window.delegate = nullptr; + this->window.contentView = nullptr; - this->window.titleBarView = nullptr; + if (this->window.titleBarView) { + [this->window.titleBarView removeFromSuperview]; + #if !__has_feature(objc_arc) + [this->window.titleBarView release]; #endif } - }); + + this->window.titleBarView = nullptr; + #endif + } } void Window::maximize () { @@ -812,7 +799,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { this->window.hidden = YES; } #endif - this->eval(getEmitToRenderProcessJavaScript("window-hide", "{}")); + this->eval(getEmitToRenderProcessJavaScript("window-hidden", "{}")); } void Window::eval (const String& source) { diff --git a/src/window/linux.cc b/src/window/linux.cc index 0be074e2d3..51500a9896 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1150,8 +1150,7 @@ namespace SSC { } } - void Window::kill () { - } + void Window::kill () {} void Window::close (int _) { if (this->window) { From 3bc93d457f7792fc1735e44d01cb37b9c4af7fe4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 13:56:37 +0200 Subject: [PATCH 0785/1178] refactor(bin): prebuild 'libllama' for android --- bin/build-runtime-library.sh | 6 ++-- bin/install.sh | 56 ++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index 7251d373ee..8739a4ccb0 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -335,8 +335,10 @@ function main () { fi fi - if [[ "$host" = "Linux" ]] && [[ "$platform" = "desktop" ]]; then - "$root/bin/generate-socket-runtime-pkg-config.sh" + if [[ "$platform" = "desktop" ]]; then + if [[ "$host" = "Linux" ]] || [[ "$host" = "Darwin" ]]; then + "$root/bin/generate-socket-runtime-pkg-config.sh" + fi fi if [[ "$platform" == "android" ]]; then diff --git a/bin/install.sh b/bin/install.sh index 2d5a097025..e018ac3216 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -593,7 +593,7 @@ function _install { echo "# copying pkgconfig to $SOCKET_HOME/pkgconfig" rm -rf "$SOCKET_HOME/pkgconfig" mkdir -p "$SOCKET_HOME/pkgconfig" - cp -rfp "$BUILD_DIR/$arch-$platform/pkgconfig"/* "$SOCKET_HOME/pkgconfig" + cp -rfp "$BUILD_DIR/$arch-desktop/pkgconfig"/* "$SOCKET_HOME/pkgconfig" fi if [ "$platform" == "desktop" ]; then @@ -794,7 +794,7 @@ function _compile_libuv_android { fi } -function _compile_metal { +function _compile_llama_metal { target=$1 hosttarget=$1 platform=$2 @@ -890,23 +890,35 @@ function _compile_llama { rm -f "$root/build/$(host_arch)-desktop/lib$d"/*.{so,la,dylib}* return - fi - - # https://github.com/ggerganov/llama.cpp/discussions/4508 - - declare ar=$(xcrun -sdk $sdk -find ar) - declare cc=$(xcrun -sdk $sdk -find clang) - declare cxx=$(xcrun -sdk $sdk -find clang++) - declare cflags="--target=$target-apple-ios -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION -DLLAMA_METAL_EMBED_LIBRARY=ON" + elif [ "$platform" == "iPhoneOS" ] || [ "$platform" == "iPhoneSimulator" ]; then + # https://github.com/ggerganov/llama.cpp/discussions/4508 - echo "AR: $ar" - echo "CFLAGS: $cflags" + local ar="$(xcrun -sdk $sdk -find ar)" + local cc="$(xcrun -sdk $sdk -find clang)" + local cxx="$(xcrun -sdk $sdk -find clang++)" + local cflags="--target=$target-apple-ios -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -m$sdk-version-min=$SDKMINVERSION -DLLAMA_METAL_EMBED_LIBRARY=ON" - AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make libllama.a + AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make libllama.a - if [ ! $? = 0 ]; then - die $? "not ok - iOS will not be enabled. Unable to compile libllama." - return + if [ ! $? = 0 ]; then + die $? "not ok - Unable to compile libllama for '$platform'" + return + fi + elif [ "$platform" == "android" ]; then + local android_includes=$(android_arch_includes "$1") + local host_arch="$(host_arch)" + local cc="$(android_clang "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" + local cxx="$cc" + local clang_target="$(android_clang_target "$target")" + local ar="$(android_ar "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" + local cflags=("$clang_target" -std=c++2a -g -pedantic "${android_includes[*]}") + + AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make UNAME_M="$1" UNAME_P="$1" LLAMA_FAST=1 libllama.a + + if [ ! $? = 0 ]; then + die $? "not ok - Unable to compile libllama for '$platform'" + return + fi fi cp libllama.a ../lib @@ -1065,9 +1077,14 @@ cd "$BUILD_DIR" || exit 1 trap onsignal INT TERM -_compile_metal arm64 iPhoneOS -_compile_metal x86_64 iPhoneSimulator -_compile_metal arm64 iPhoneSimulator +if [[ "$(uname -s)" == "Darwin" ]] && [[ -z "$NO_IOS" ]]; then + quiet xcode-select -p + die $? "not ok - xcode needs to be installed from the mac app store: https://apps.apple.com/us/app/xcode/id497799835" + + _compile_llama_metal arm64 iPhoneOS + _compile_llama_metal x86_64 iPhoneSimulator + _compile_llama_metal arm64 iPhoneSimulator +fi { _compile_llama @@ -1125,6 +1142,7 @@ fi if [[ -n "$BUILD_ANDROID" ]]; then for abi in $(android_supported_abis); do _compile_libuv_android "$abi" & pids+=($!) + _compile_llama "$abi" android & pids+=($!) done fi From 9ec348dd014e24b858ab0cad18f447519011f63c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 13:59:20 +0200 Subject: [PATCH 0786/1178] refactor(cli): build 'libllama.a' into runtime on android --- src/cli/cli.cc | 1 + src/cli/templates.hh | 34 +++++++++++++++++++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index c21d5272af..4afd419073 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -25,6 +25,7 @@ #pragma comment(lib, "userenv.lib") #pragma comment(lib, "uuid.lib") #pragma comment(lib, "libuv.lib") +#pragma comment(lib, "libllama.lib") #pragma comment(lib, "winspool.lib") #pragma comment(lib, "ws2_32.lib") #endif diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 5b4c1b13ce..de333d28bf 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1499,6 +1499,13 @@ LOCAL_MODULE := libuv LOCAL_SRC_FILES = ../libs/$(TARGET_ARCH_ABI)/libuv.a include $(PREBUILT_STATIC_LIBRARY) +## libllama.a +include $(CLEAR_VARS) +LOCAL_MODULE := libllama + +LOCAL_SRC_FILES = ../libs/$(TARGET_ARCH_ABI)/libllama.a +include $(PREBUILT_STATIC_LIBRARY) + ## libsocket-runtime.a include $(CLEAR_VARS) LOCAL_MODULE := libsocket-runtime-static @@ -1513,26 +1520,27 @@ include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := socket-runtime -LOCAL_CFLAGS += \ - -std=c++2a \ - -g \ - -I$(LOCAL_PATH)/include \ - -I$(LOCAL_PATH) \ - -pthreads \ - -fexceptions \ - -fPIC \ - -frtti \ - -fsigned-char \ +LOCAL_CFLAGS += \ + -std=c++2a \ + -g \ + -I$(LOCAL_PATH)/include \ + -I$(LOCAL_PATH) \ + -pthreads \ + -fexceptions \ + -fPIC \ + -frtti \ + -fsigned-char \ -O0 LOCAL_CFLAGS += {{cflags}} LOCAL_LDLIBS := -landroid -llog -LOCAL_SRC_FILES = \ +LOCAL_SRC_FILES = \ init.cc -LOCAL_WHOLE_STATIC_LIBRARIES := \ - libuv \ +LOCAL_WHOLE_STATIC_LIBRARIES := \ + libuv \ + libllama \ libsocket-runtime-static include $(BUILD_SHARED_LIBRARY) From 29bd522f19fd10e54f5da992b80740bbf16ed475 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 14:20:01 +0200 Subject: [PATCH 0787/1178] refactor(platform): include 'libllama.lib' for windows --- src/platform/platform.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/platform.hh b/src/platform/platform.hh index 48549fcd49..e1b2e59754 100644 --- a/src/platform/platform.hh +++ b/src/platform/platform.hh @@ -95,6 +95,7 @@ #pragma comment(lib, "Gdi32.lib") #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "libuv.lib") +#pragma comment(lib, "libllama.lib") #pragma comment(lib, "psapi.lib") #pragma comment(lib, "shell32.lib") #pragma comment(lib, "Shlwapi.lib") From 73489a28ab794ae33828906a0d91ca808f6f9ddc Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 6 Jun 2024 08:12:17 +0200 Subject: [PATCH 0788/1178] refactor(cli): make it possible for cli to listen to logs over udp --- src/cli/cli.cc | 255 +++++++++++++++---------------------------------- 1 file changed, 76 insertions(+), 179 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 4afd419073..3b4f6840f8 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -38,6 +38,7 @@ #include <filesystem> #include <iostream> #include <fstream> +#include <future> #include <regex> #include <span> #include <unordered_set> @@ -307,194 +308,83 @@ static std::atomic<int> appStatus = -1; static std::mutex appMutex; #if defined(__APPLE__) -void pollOSLogStream (bool isForDesktop, String bundleIdentifier, int processIdentifier) { - // It appears there is a bug with `:predicateWithFormat:` as the - // following does not appear to work: - // - // [NSPredicate - // predicateWithFormat: @"processIdentifier == %d AND subsystem == '%s'", - // app.processIdentifier, - // bundle.bundleIdentifier // or even a literal string "co.socketsupply.socket.tests" - // ]; - // - // We can build the predicate query string manually, instead. - auto queryStream = StringStream {}; - auto pid = std::to_string(processIdentifier); - auto bid = bundleIdentifier.c_str(); - queryStream - << "(" - << (isForDesktop - ? "category == 'socket.runtime.desktop'" - : "category == 'socket.runtime.mobile'") - << " OR category == 'socket.runtime.debug'" - << ") AND "; - - if (processIdentifier > 0) { - queryStream << "processIdentifier == " << pid << " AND "; - } - - queryStream << "subsystem == '" << bid << "'"; - // log store query and predicate for filtering logs based on the currently - // running application that was just launched and those of a subsystem - // directly related to the application's bundle identifier which allows us - // to just get logs that came from the application (not foundation/cocoa/webkit) - const auto query = [NSString stringWithUTF8String: queryStream.str().c_str()]; - const auto predicate = [NSPredicate predicateWithFormat: query]; - - // use the launch date as the initial marker - const auto now = [NSDate now]; - // and offset it by 1 second in the past as the initial position in the eumeration - auto offset = [now dateByAddingTimeInterval: -1]; - - // tracks the latest log entry date so we ignore older ones - NSDate* latest = nil; - NSError* error = nil; - - int pollsAfterTermination = 16; - int backoffIndex = 0; - - // lucas series of backoffs - static int backoffs[] = { - 16*2, - 16*1, - 16*3, - 16*4, - 16*7, - 16*11, - 16*18, - 16*29, - 32*2, - 32*1, - 32*3, - 32*4, - 32*7, - 32*11, - 32*18, - 32*29, - 64*2, - 64*1, - 64*3, - 64*4, - 64*7, - 64*11, - 64*18, - 64*29, - }; - - if (processIdentifier > 0) { - dispatch_async(queue, ^{ - while (kill(processIdentifier, 0) == 0) { - msleep(256); - } - }); - } - - while (appStatus < 0 || pollsAfterTermination > 0) { - if (appStatus >= 0) { - pollsAfterTermination = pollsAfterTermination - 1; - checkLogStore = true; - } - - if (!checkLogStore) { - auto backoff = backoffs[backoffIndex]; - backoffIndex = ( - (backoffIndex + 1) % - (sizeof(backoffs) / sizeof(backoffs[0])) - ); - - msleep(backoff); - if (processIdentifier > 0) { - continue; - } - } - - // this is may be set to `true` from a `SIGUSR1` signal - checkLogStore = false; - @autoreleasepool { - // We need a new `OSLogStore` in each so we can keep enumeratoring - // the logs until the application terminates. This is required because - // each `OSLogStore` instance is a snapshot of the system's universal - // log archive at the time of instantiation. - auto logs = [OSLogStore - storeWithScope: OSLogStoreSystem - error: &error - ]; +unsigned short createLogSocket() { + std::promise<int> p; + std::future<int> future = p.get_future(); + int port = 0; + + auto t = std::thread([](std::promise<int>&& p) { + uv_loop_t *loop = uv_default_loop(); + uv_udp_t socket; + + uv_udp_init(loop, &socket); + struct sockaddr_in addr; + int port; + + uv_ip4_addr("0.0.0.0", 0, &addr); + uv_udp_bind(&socket, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR); + + uv_udp_recv_start( + &socket, + [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { + *buf = uv_buf_init(new char[suggested_size], suggested_size); + }, + [](uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) { + if (nread > 0) { + String data = trim(String(buf->base, nread)); + if (data[0] != '+') return; + + @autoreleasepool { + NSError *err = nil; + auto logs = [OSLogStore storeWithScope: OSLogStoreSystem error: &err]; // get snapshot + + if (err) { + debug("ERROR: Failed to open logstore"); + return; + } - if (error) { - appStatus = 1; - debug( - "ERROR: OSLogStore: (code=%lu, domain=%@) %@", - error.code, - error.domain, - error.localizedDescription - ); - break; - } + auto offset = [[NSDate now] dateByAddingTimeInterval: -1]; // this may not be enough + auto position = [logs positionWithDate: offset]; + auto predicate = [NSPredicate predicateWithFormat: @"(category == 'socket.runtime')"]; + auto enumerator = [logs entriesEnumeratorWithOptions: 0 position: position predicate: predicate error: &err]; - auto position = [logs positionWithDate: offset]; - auto enumerator = [logs - entriesEnumeratorWithOptions: 0 - position: position - predicate: predicate - error: &error - ]; + if (err) { + debug("ERROR: Failed to open logstore"); + return; + } - if (error) { - appStatus = 1; - debug( - "ERROR: OSLogEnumerator: (code=%lu, domain=%@) %@", - error.code, - error.domain, - error.localizedDescription - ); - break; - } + int count = 0; + id logEntry; - // Enumerate all the logs in this loop and print unredacted and most - // recently log entries to stdout - for (OSLogEntryLog* entry in enumerator) { - if ( - entry.composedMessage && - (processIdentifier == 0 || entry.processIdentifier == processIdentifier) - ) { - // visit latest log - if (!latest || [latest compare: entry.date] == NSOrderedAscending) { - auto message = entry.composedMessage.UTF8String; - - // the OSLogStore may redact log messages the user does not - // have access to, filter them out - if (String(message) != "<private>") { - if (String(message).starts_with("__EXIT_SIGNAL__")) { - if (appStatus == -1) { - appStatus = std::stoi(replace(String(message), "__EXIT_SIGNAL__=", "")); - } - } else if ( - entry.level == OSLogEntryLogLevelDebug || - entry.level == OSLogEntryLogLevelError || - entry.level == OSLogEntryLogLevelFault - ) { - if (entry.level == OSLogEntryLogLevelDebug) { - if (flagDebugMode) { - std::cerr << message << std::endl; - } - } else { - std::cerr << message << std::endl; - } - } else { - std::cout << message << std::endl; - } + while ((logEntry = [enumerator nextObject]) != nil) { + count++; + OSLogEntryLog *entry = (OSLogEntryLog *)logEntry; + NSString *message = [entry composedMessage]; + std::cout << message.UTF8String << std::endl; } - - backoffIndex = 0; - latest = entry.date; - offset = latest; } } + + if (buf->base) delete[] buf->base; } + ); + + int len = sizeof(addr); + + if (uv_udp_getsockname(&socket, (struct sockaddr *)&addr, &len) == 0) { + auto port = ntohs(addr.sin_port); + debug("Listening on UDP port %d\n", port); + p.set_value(port); + } else { + debug("Failed to get socket name\n"); } - } - appMutex.unlock(); + uv_run(loop, UV_RUN_DEFAULT); + }, std::move(p)); + + port = future.get(); + t.detach(); + return port; } #endif @@ -536,10 +426,13 @@ void handleBuildPhaseForUser ( buildScript = "cmd.exe"; } + auto scriptPath = (cwd / targetPath).string(); + log("running build script (cmd='" + buildScript + "', args='" + scriptArgs + "', pwd='" + scriptPath + "')"); + auto process = new SSC::Process( buildScript, scriptArgs, - (cwd / targetPath).string(), + scriptPath, [](SSC::String const &out) { IO::write(out, false); }, [](SSC::String const &out) { IO::write(out, true); } ); @@ -981,6 +874,10 @@ int runApp (const Path& path, const String& args, bool headless) { for (auto const &envKey : parseStringList(settings["build_env"])) { auto cleanKey = trim(envKey); + + cleanKey.erase(0, cleanKey.find_first_not_of(",")); + cleanKey.erase(cleanKey.find_last_not_of(",") + 1); + auto envValue = Env::get(cleanKey.c_str()); auto key = [NSString stringWithUTF8String: cleanKey.c_str()]; auto value = [NSString stringWithUTF8String: envValue.c_str()]; From dedd331b180d8179c9ad067e778c6651ca6055b4 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 6 Jun 2024 10:26:33 +0200 Subject: [PATCH 0789/1178] refactor(core/child_process): ensure that child process takes env vars --- src/cli/cli.cc | 16 +- src/core/modules/child_process.cc | 2 + src/core/modules/child_process.hh | 2 + src/core/process.hh | 26 ++- src/core/process/unix.cc | 105 ++++++++---- src/desktop/main.cc | 6 +- src/extension/extension.cc | 29 ++-- src/extension/extension.hh | 2 + src/ipc/routes.cc | 275 +++++++++++++++++------------- 9 files changed, 271 insertions(+), 192 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 3b4f6840f8..3a8b0eddf1 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -894,6 +894,14 @@ int runApp (const Path& path, const String& args, bool headless) { [arguments addObject: @"--from-ssc"]; + auto port = std::to_string(createLogSocket()); + env[@"SSC_LOG_SOCKET"] = @(port.c_str()); + + auto parentLogSocket = Env::get("SSC_PARENT_LOG_SOCKET"); + if (parentLogSocket.size() > 0) { + env[@"SSC_PARENT_LOG_SOCKET"] = @(parentLogSocket.c_str()); + } + configuration.createsNewApplicationInstance = YES; configuration.promptsUserIfNeeded = YES; configuration.environment = env; @@ -923,16 +931,8 @@ int runApp (const Path& path, const String& args, bool headless) { error.domain, error.localizedDescription ); - return; } - appPid = app.processIdentifier; - - pollOSLogStream( - true, - String(bundle.bundleIdentifier.UTF8String), - app.processIdentifier - ); }]; // wait for `NSRunningApplication` to terminate diff --git a/src/core/modules/child_process.cc b/src/core/modules/child_process.cc index 388e71a7d7..4a993422ba 100644 --- a/src/core/modules/child_process.cc +++ b/src/core/modules/child_process.cc @@ -168,6 +168,7 @@ namespace SSC { process.reset(new Process( command, argv, + options.env, options.cwd, onStdout, onStderr, @@ -361,6 +362,7 @@ namespace SSC { process.reset(new Process( command, argv, + options.env, options.cwd, onStdout, onStderr, diff --git a/src/core/modules/child_process.hh b/src/core/modules/child_process.hh index 4d460583be..498547397e 100644 --- a/src/core/modules/child_process.hh +++ b/src/core/modules/child_process.hh @@ -13,6 +13,7 @@ namespace SSC { struct SpawnOptions { String cwd; + const Vector<String> env; bool allowStdin = true; bool allowStdout = true; bool allowStderr = true; @@ -20,6 +21,7 @@ namespace SSC { struct ExecOptions { String cwd; + const Vector<String> env; bool allowStdout = true; bool allowStderr = true; uint64_t timeout = 0; diff --git a/src/core/process.hh b/src/core/process.hh index b03aa4934f..4febb9478e 100644 --- a/src/core/process.hh +++ b/src/core/process.hh @@ -124,9 +124,11 @@ namespace SSC { String command; String argv; String path; + Vector<String> env; Atomic<bool> closed = true; Atomic<int> status = -1; Atomic<int> lastWriteStatus = 0; + bool detached = false; bool openStdin; PID id = 0; @@ -153,6 +155,7 @@ namespace SSC { Process( const String &command, const String &argv, + const SSC::Vector<SSC::String> &env, const String &path = String(""), MessageCallback readStdout = nullptr, MessageCallback readStderr = nullptr, @@ -161,18 +164,29 @@ namespace SSC { const ProcessConfig &config = {} ) noexcept; - #if !SOCKET_RUNTIME_PLATFORM_WINDOWS - // Starts a process with the environment of the calling process. - // Supported on Unix-like systems only. - Process ( - const std::function<int()> &function, + Process( + const String &command, + const String &argv, + const String &path = String(""), MessageCallback readStdout = nullptr, MessageCallback readStderr = nullptr, MessageCallback onExit = nullptr, bool openStdin = true, const ProcessConfig &config = {} ) noexcept; - #endif + + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS + // Starts a process with the environment of the calling process. + // Supported on Unix-like systems only. + Process ( + const std::function<int()> &function, + MessageCallback readStdout = nullptr, + MessageCallback readStderr = nullptr, + MessageCallback onExit = nullptr, + bool openStdin = true, + const ProcessConfig &config = {} + ) noexcept; + #endif ~Process () noexcept { closeFDs(); diff --git a/src/core/process/unix.cc b/src/core/process/unix.cc index a86a3339c4..307bb115cc 100644 --- a/src/core/process/unix.cc +++ b/src/core/process/unix.cc @@ -15,6 +15,8 @@ #include "../debug.hh" #include "../modules/timers.hh" +extern char **environ; + namespace SSC { static StringStream initial; @@ -22,6 +24,27 @@ namespace SSC { : id(-1) {} + Process::Process ( + const String &command, + const String &argv, + const Vector<String> &env, + const String &path, + MessageCallback readStdout, + MessageCallback readStderr, + MessageCallback onExit, + bool openStdin, + const ProcessConfig &config + ) noexcept + : openStdin(true), + readStdout(std::move(readStdout)), + readStderr(std::move(readStderr)), + onExit(std::move(onExit)), + env(env), + command(command), + argv(argv), + path(path) + {} + Process::Process ( const String &command, const String &argv, @@ -55,16 +78,17 @@ namespace SSC { openStdin(openStdin), config(config) { - #if !SOCKET_RUNTIME_PLATFORM_IOS - open(function); - read(); - #endif + #if !SOCKET_RUNTIME_PLATFORM_IOS + open(function); + read(); + #endif } Process::PID Process::open (const Function<int()> &function) noexcept { - #if SOCKET_RUNTIME_PLATFORM_IOS - return -1; // -EPERM - #else + #if SOCKET_RUNTIME_PLATFORM_IOS + return -1; // -EPERM + #else + if (openStdin) { stdinFD = UniquePointer<FD>(new FD); } @@ -218,37 +242,50 @@ namespace SSC { } Process::PID Process::open (const String &command, const String &path) noexcept { - #if SOCKET_RUNTIME_PLATFORM_IOS - return -1; // -EPERM - #else - return open([&command, &path, this] { - auto command_c_str = command.c_str(); - String cd_path_and_command; - - if (!path.empty()) { - auto path_escaped = path; - size_t pos = 0; - - // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 - while ((pos = path_escaped.find('\'', pos)) != String::npos) { - path_escaped.replace(pos, 1, "'\\''"); - pos += 4; - } + #if SOCKET_RUNTIME_PLATFORM_IOS + return -1; // -EPERM + #else - cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links - command_c_str = cd_path_and_command.c_str(); + std::vector<char*> newEnv; + + for (char** env = environ; *env != nullptr; ++env) { + newEnv.push_back(strdup(*env)); } - #if SOCKET_RUNTIME_PLATFORM_APPLE - setpgid(0, 0); - #endif - if (this->shell.size() > 0) { - return execl(this->shell.c_str(), this->shell.c_str(), "-c", command_c_str, nullptr); - } else { - return execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + for (const auto& str : this->env) { + newEnv.push_back(const_cast<char*>(str.c_str())); } - }); - #endif + + newEnv.push_back(nullptr); + + return open([&command, &path, &newEnv, this] { + auto command_c_str = command.c_str(); + String cd_path_and_command; + + if (!path.empty()) { + auto path_escaped = path; + size_t pos = 0; + + while ((pos = path_escaped.find('\'', pos)) != String::npos) { + path_escaped.replace(pos, 1, "'\\''"); + pos += 4; + } + + cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links + command_c_str = cd_path_and_command.c_str(); + } + + #if SOCKET_RUNTIME_PLATFORM_APPLE + setpgid(0, 0); + #endif + + if (this->shell.size() > 0) { + return execle(this->shell.c_str(), this->shell.c_str(), "-c", command_c_str, (char*)nullptr, newEnv.data()); + } else { + return execle("/bin/sh", "/bin/sh", "-c", command_c_str, (char*)nullptr, newEnv.data()); + } + }); + #endif } int Process::wait () { diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 0b69300bb3..5fd4b887f6 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -650,11 +650,7 @@ MAIN { #if SOCKET_RUNTIME_PLATFORM_APPLE static auto SOCKET_RUNTIME_OS_LOG_BUNDLE = os_log_create( bundleIdentifier.c_str(), - #if SOCKET_RUNTIME_PLATFORM_MOBILE - "socket.runtime.mobile" - #else - "socket.runtime.desktop" - #endif + "socket.runtime" ); #endif diff --git a/src/extension/extension.cc b/src/extension/extension.cc index 84abba3f8a..62276a53e1 100644 --- a/src/extension/extension.cc +++ b/src/extension/extension.cc @@ -570,24 +570,21 @@ void sapi_log (const sapi_context_t* ctx, const char* message) { output = message; } -#if SOCKET_RUNTIME_PLATFORM_ANDROID - __android_log_print(ANDROID_LOG_INFO, "Console", "%s", message); -#else - SSC::IO::write(output, false); -#endif - -#if SOCKET_RUNTIME_PLATFORM_APPLE - static auto userConfig = SSC::getUserConfig(); - static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - static auto SOCKET_RUNTIME_OS_LOG_INFO = os_log_create(bundleIdentifier.c_str(), - #if SOCKET_RUNTIME_PLATFORM_MOBILE - "socket.runtime.mobile" + #if SOCKET_RUNTIME_PLATFORM_ANDROID + __android_log_print(ANDROID_LOG_INFO, "Console", "%s", message); #else - "socket.runtime.desktop" + SSC::IO::write(output, false); + #endif + + #if SOCKET_RUNTIME_PLATFORM_APPLE + static auto userConfig = SSC::getUserConfig(); + static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + static auto SOCKET_RUNTIME_OS_LOG_INFO = os_log_create( + bundleIdentifier.c_str(), + "socket.runtime" + ); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_INFO, OS_LOG_TYPE_INFO, "%{public}s", output.c_str()); #endif - ); - os_log_with_type(SOCKET_RUNTIME_OS_LOG_INFO, OS_LOG_TYPE_INFO, "%{public}s", output.c_str()); -#endif } void sapi_debug (const sapi_context_t* ctx, const char* message) { diff --git a/src/extension/extension.hh b/src/extension/extension.hh index 7a1fd26e0f..670878a0c7 100644 --- a/src/extension/extension.hh +++ b/src/extension/extension.hh @@ -186,6 +186,7 @@ extern "C" { sapi_process_spawn ( const char* command, const char* argv, + SSC::Vector<SSC::String> env, const char* path, sapi_process_spawn_stderr_callback_t onstdout, sapi_process_spawn_stderr_callback_t onstderr, @@ -193,6 +194,7 @@ extern "C" { ) : SSC::Process( command, argv, + env, path, [this, onstdout] (auto output) { if (onstdout) { onstdout(this, output.c_str(), output.size()); }}, [this, onstderr] (auto output) { if (onstderr) { onstderr(this, output.c_str(), output.size()); }}, diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 835753e5bd..9beef2db37 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -39,16 +39,11 @@ static JSON::Any validateMessageParameters ( static void mapIPCRoutes (Router *router) { auto userConfig = router->bridge->userConfig; -#if SOCKET_RUNTIME_PLATFORM_APPLE - auto bundleIdentifier = userConfig["meta_bundle_identifier"]; - auto SOCKET_RUNTIME_OS_LOG_BUNDLE = os_log_create(bundleIdentifier.c_str(), - #if SOCKET_RUNTIME_PLATFORM_IOS - "socket.runtime.mobile" - #else - "socket.runtime.desktop" + + #if SOCKET_RUNTIME_PLATFORM_APPLE + auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + auto SOCKET_RUNTIME_OS_LOG_BUNDLE = os_log_create(bundleIdentifier.c_str(), "socket.runtime"); #endif - ); -#endif /** * AI @@ -468,115 +463,129 @@ static void mapIPCRoutes (Router *router) { * @param args (command, ...args) */ router->map("child_process.spawn", [=](auto message, auto router, auto reply) { - #if SOCKET_RUNTIME_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; - - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"args", "id"}); + #if SOCKET_RUNTIME_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; - if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); - } + #else + auto err = validateMessageParameters(message, {"args", "id"}); - auto args = split(message.get("args"), 0x0001); + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } - if (args.size() == 0 || args.at(0).size() == 0) { - auto json = JSON::Object::Entries { - {"source", "child_process.spawn"}, - {"err", JSON::Object::Entries { - {"message", "Spawn requires at least one argument with a length greater than zero"}, - }} - }; + auto args = split(message.get("args"), 0x0001); - return reply(Result { message.seq, message, json }); - } + if (args.size() == 0 || args.at(0).size() == 0) { + auto json = JSON::Object::Entries { + {"source", "child_process.spawn"}, + {"err", JSON::Object::Entries { + {"message", "Spawn requires at least one argument with a length greater than zero"}, + }} + }; - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + return reply(Result { message.seq, message, json }); + } - const auto options = Core::ChildProcess::SpawnOptions { - .cwd = message.get("cwd", getcwd()), - .allowStdin = message.get("stdin") != "false", - .allowStdout = message.get("stdout") != "false", - .allowStderr = message.get("stderr") != "false" - }; + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - router->bridge->core->childProcess.spawn( - message.seq, - id, - args, - options, - [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); + SSC::Vector<SSC::String> env{}; + + if (message.has("env")) { + env = split(message.get("env"), 0x0001); } - ); - #endif + + const auto options = Core::ChildProcess::SpawnOptions { + .cwd = message.get("cwd", getcwd()), + .env = env, + .allowStdin = message.get("stdin") != "false", + .allowStdout = message.get("stdout") != "false", + .allowStderr = message.get("stderr") != "false" + }; + + router->bridge->core->childProcess.spawn( + message.seq, + id, + args, + options, + [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + } + ); + #endif }); router->map("child_process.exec", [=](auto message, auto router, auto reply) { - #if SOCKET_RUNTIME_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; - - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"args", "id"}); + #if SOCKET_RUNTIME_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; - if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); - } + #else + auto err = validateMessageParameters(message, {"args", "id"}); - auto args = split(message.get("args"), 0x0001); + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } - if (args.size() == 0 || args.at(0).size() == 0) { - auto json = JSON::Object::Entries { - {"source", "child_process.exec"}, - {"err", JSON::Object::Entries { - {"message", "Spawn requires at least one argument with a length greater than zero"}, - }} - }; + auto args = split(message.get("args"), 0x0001); - return reply(Result { message.seq, message, json }); - } + if (args.size() == 0 || args.at(0).size() == 0) { + auto json = JSON::Object::Entries { + {"source", "child_process.exec"}, + {"err", JSON::Object::Entries { + {"message", "Spawn requires at least one argument with a length greater than zero"}, + }} + }; - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + return reply(Result { message.seq, message, json }); + } - uint64_t timeout = 0; - int killSignal = 0; + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - if (message.has("timeout")) { - REQUIRE_AND_GET_MESSAGE_VALUE(timeout, "timeout", std::stoull); - } + uint64_t timeout = 0; + int killSignal = 0; - if (message.has("killSignal")) { - REQUIRE_AND_GET_MESSAGE_VALUE(killSignal, "killSignal", std::stoi); - } + if (message.has("timeout")) { + REQUIRE_AND_GET_MESSAGE_VALUE(timeout, "timeout", std::stoull); + } - const auto options = Core::ChildProcess::ExecOptions { - .cwd = message.get("cwd", getcwd()), - .allowStdout = message.get("stdout") != "false", - .allowStderr = message.get("stderr") != "false", - .timeout = timeout, - .killSignal = killSignal - }; + if (message.has("killSignal")) { + REQUIRE_AND_GET_MESSAGE_VALUE(killSignal, "killSignal", std::stoi); + } - router->bridge->core->childProcess.exec( - message.seq, - id, - args, - options, - [message, reply](auto seq, auto json, auto post) { - reply(Result { seq, message, json, post }); + SSC::Vector<SSC::String> env{}; + + if (message.has("env")) { + env = split(message.get("env"), 0x0001); } - ); - #endif + + const auto options = Core::ChildProcess::ExecOptions { + .cwd = message.get("cwd", getcwd()), + .env = env, + .allowStdout = message.get("stdout") != "false", + .allowStderr = message.get("stderr") != "false", + .timeout = timeout, + .killSignal = killSignal + }; + + router->bridge->core->childProcess.exec( + message.seq, + id, + args, + options, + [message, reply](auto seq, auto json, auto post) { + reply(Result { seq, message, json, post }); + } + ); + #endif }); /** @@ -585,31 +594,31 @@ static void mapIPCRoutes (Router *router) { * @param id */ router->map("child_process.write", [=](auto message, auto router, auto reply) { - #if SOCKET_RUNTIME_PLATFORM_IOS - auto err = JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation is not supported on this platform"} - }; - - return reply(Result::Err { message, err }); - #else - auto err = validateMessageParameters(message, {"id"}); + #if SOCKET_RUNTIME_PLATFORM_IOS + auto err = JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation is not supported on this platform"} + }; - if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); - } + #else + auto err = validateMessageParameters(message, {"id"}); - uint64_t id; - REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } - router->bridge->core->childProcess.write( - message.seq, - id, - message.buffer.bytes, - message.buffer.size, - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - #endif + uint64_t id; + REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + + router->bridge->core->childProcess.write( + message.seq, + id, + message.buffer.bytes, + message.buffer.size, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + #endif }); /** @@ -1620,14 +1629,14 @@ static void mapIPCRoutes (Router *router) { */ router->map("log", [=](auto message, auto router, auto reply) { auto value = message.value.c_str(); - #if SOCKET_RUNTIME_PLATFORM_APPLE - NSLog(@"%s", value); - os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", value); - #elif SOCKET_RUNTIME_PLATFORM_ANDROID - __android_log_print(ANDROID_LOG_DEBUG, "", "%s", value); - #else - printf("%s\n", value); - #endif + #if SOCKET_RUNTIME_PLATFORM_APPLE + NSLog(@"%s", value); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", value); + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + __android_log_print(ANDROID_LOG_DEBUG, "", "%s", value); + #else + printf("%s\n", value); + #endif }); router->map("notification.show", [=](auto message, auto router, auto reply) { @@ -2262,6 +2271,16 @@ static void mapIPCRoutes (Router *router) { if (message.value.size() > 0) { #if SOCKET_RUNTIME_PLATFORM_APPLE os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", message.value.c_str()); + + if (Env::get("SSC_LOG_SOCKET").size() > 0) { + Core::UDP::SendOptions options; + options.size = 2; + options.bytes = SharedPointer<char[]>(new char[3]{ '+', 'N', '\0' }); + options.address = "0.0.0.0"; + options.port = std::stoi(Env::get("SSC_LOG_SOCKET")); + options.ephemeral = true; + router->bridge->core->udp.send("-1", 0, options, [](auto seq, auto json, auto post) {}); + } #endif IO::write(message.value, false); } else if (message.buffer.bytes != nullptr && message.buffer.size > 0) { @@ -2283,6 +2302,16 @@ static void mapIPCRoutes (Router *router) { } else if (message.value.size() > 0) { #if SOCKET_RUNTIME_PLATFORM_APPLE os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", message.value.c_str()); + + if (Env::get("SSC_LOG_SOCKET").size() > 0) { + Core::UDP::SendOptions options; + options.size = 2; + options.bytes = SharedPointer<char[]>(new char[3]{ '+', 'N', '\0' }); + options.address = "0.0.0.0"; + options.port = std::stoi(Env::get("SSC_LOG_SOCKET")); + options.ephemeral = true; + router->bridge->core->udp.send("-1", 0, options, [](auto seq, auto json, auto post) {}); + } #endif IO::write(message.value, true); } else if (message.buffer.bytes != nullptr && message.buffer.size > 0) { From e48b7ba07ae6b9533ab435427bf9edbb75744965 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 6 Jun 2024 15:26:11 +0200 Subject: [PATCH 0790/1178] refactor(cli): kill loop and exit on sighandler --- src/cli/cli.cc | 221 +++++++++++++++++++++++++++---------------------- 1 file changed, 121 insertions(+), 100 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 3a8b0eddf1..609982e1bc 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -306,6 +306,8 @@ static Process::PID appPid = 0; static SharedPointer<Process> appProcess = nullptr; static std::atomic<int> appStatus = -1; static std::mutex appMutex; +static uv_loop_t *loop = nullptr; +static uv_udp_t logsocket; #if defined(__APPLE__) unsigned short createLogSocket() { @@ -314,18 +316,17 @@ unsigned short createLogSocket() { int port = 0; auto t = std::thread([](std::promise<int>&& p) { - uv_loop_t *loop = uv_default_loop(); - uv_udp_t socket; + loop = uv_default_loop(); - uv_udp_init(loop, &socket); + uv_udp_init(loop, &logsocket); struct sockaddr_in addr; int port; uv_ip4_addr("0.0.0.0", 0, &addr); - uv_udp_bind(&socket, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR); + uv_udp_bind(&logsocket, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR); uv_udp_recv_start( - &socket, + &logsocket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { *buf = uv_buf_init(new char[suggested_size], suggested_size); }, @@ -371,7 +372,7 @@ unsigned short createLogSocket() { int len = sizeof(addr); - if (uv_udp_getsockname(&socket, (struct sockaddr *)&addr, &len) == 0) { + if (uv_udp_getsockname(&logsocket, (struct sockaddr *)&addr, &len) == 0) { auto port = ntohs(addr.sin_port); debug("Listening on UDP port %d\n", port); p.set_value(port); @@ -735,22 +736,22 @@ Vector<Path> handleBuildPhaseForCopyMappedFiles ( } void signalHandler (int signal) { -#if !defined(_WIN32) - if (signal == SIGUSR1) { - #if defined(__APPLE__) - checkLogStore = true; + #if !defined(_WIN32) + if (signal == SIGUSR1) { + #if defined(__APPLE__) + checkLogStore = true; + #endif + return; + } #endif - return; - } -#endif Lock lock(signalHandlerMutex); -#if !defined(_WIN32) - if (appPid > 0) { - kill(appPid, signal); - } -#endif + #if !defined(_WIN32) + if (appPid > 0) { + kill(appPid, signal); + } + #endif if (appProcess != nullptr) { appProcess->kill(); @@ -759,11 +760,11 @@ void signalHandler (int signal) { appPid = 0; -#if defined(__linux__) && !defined(__ANDROID__) - if (gtk_main_level() > 0) { - gtk_main_quit(); - } -#endif + #if defined(__linux__) && !defined(__ANDROID__) + if (gtk_main_level() > 0) { + gtk_main_quit(); + } + #endif if (sourcesWatcher != nullptr) { sourcesWatcher->stop(); @@ -788,7 +789,10 @@ void signalHandler (int signal) { if (appStatus == -1) { appStatus = signal; - log("App result: " + std::to_string(signal)); + log("App result (sighandler): " + std::to_string(signal)); + + if (loop && uv_loop_alive(loop)) uv_stop(loop); + exit(signal); } } @@ -856,105 +860,112 @@ int runApp (const Path& path, const String& args, bool headless) { } } -#if defined(__APPLE__) - if (platform.mac) { - auto sharedWorkspace = [NSWorkspace sharedWorkspace]; - auto configuration = [NSWorkspaceOpenConfiguration configuration]; - auto stringPath = path.string(); - auto slice = stringPath.substr(0, stringPath.rfind(".app") + 4); + #if defined(__APPLE__) + if (platform.mac) { + auto sharedWorkspace = [NSWorkspace sharedWorkspace]; + auto configuration = [NSWorkspaceOpenConfiguration configuration]; + auto stringPath = path.string(); + auto slice = stringPath.substr(0, stringPath.rfind(".app") + 4); - auto url = [NSURL - fileURLWithPath: [NSString stringWithUTF8String: slice.c_str()] - ]; + auto url = [NSURL + fileURLWithPath: [NSString stringWithUTF8String: slice.c_str()] + ]; - auto bundle = [NSBundle bundleWithURL: url]; - auto env = [[NSMutableDictionary alloc] init]; + auto bundle = [NSBundle bundleWithURL: url]; + auto env = [[NSMutableDictionary alloc] init]; - env[@"SSC_CLI_PID"] = [NSString stringWithFormat: @"%d", getpid()]; + env[@"SSC_CLI_PID"] = [NSString stringWithFormat: @"%d", getpid()]; - for (auto const &envKey : parseStringList(settings["build_env"])) { - auto cleanKey = trim(envKey); + for (auto const &envKey : parseStringList(settings["build_env"])) { + auto cleanKey = trim(envKey); - cleanKey.erase(0, cleanKey.find_first_not_of(",")); - cleanKey.erase(cleanKey.find_last_not_of(",") + 1); + cleanKey.erase(0, cleanKey.find_first_not_of(",")); + cleanKey.erase(cleanKey.find_last_not_of(",") + 1); - auto envValue = Env::get(cleanKey.c_str()); - auto key = [NSString stringWithUTF8String: cleanKey.c_str()]; - auto value = [NSString stringWithUTF8String: envValue.c_str()]; + auto envValue = Env::get(cleanKey.c_str()); + auto key = [NSString stringWithUTF8String: cleanKey.c_str()]; + auto value = [NSString stringWithUTF8String: envValue.c_str()]; - env[key] = value; - } + env[key] = value; + } - auto splitArgs = split(args, ' '); - auto arguments = [[NSMutableArray alloc] init]; + auto splitArgs = split(args, ' '); + auto arguments = [[NSMutableArray alloc] init]; - for (auto arg : splitArgs) { - [arguments addObject: [NSString stringWithUTF8String: arg.c_str()]]; - } + for (auto arg : splitArgs) { + [arguments addObject: [NSString stringWithUTF8String: arg.c_str()]]; + } - [arguments addObject: @"--from-ssc"]; + [arguments addObject: @"--from-ssc"]; - auto port = std::to_string(createLogSocket()); - env[@"SSC_LOG_SOCKET"] = @(port.c_str()); + auto port = std::to_string(createLogSocket()); + env[@"SSC_LOG_SOCKET"] = @(port.c_str()); - auto parentLogSocket = Env::get("SSC_PARENT_LOG_SOCKET"); - if (parentLogSocket.size() > 0) { - env[@"SSC_PARENT_LOG_SOCKET"] = @(parentLogSocket.c_str()); - } + auto parentLogSocket = Env::get("SSC_PARENT_LOG_SOCKET"); + if (parentLogSocket.size() > 0) { + env[@"SSC_PARENT_LOG_SOCKET"] = @(parentLogSocket.c_str()); + } - configuration.createsNewApplicationInstance = YES; - configuration.promptsUserIfNeeded = YES; - configuration.environment = env; - configuration.arguments = arguments; - configuration.activates = headless ? NO : YES; + configuration.createsNewApplicationInstance = YES; + configuration.promptsUserIfNeeded = YES; + configuration.environment = env; + configuration.arguments = arguments; + configuration.activates = headless ? NO : YES; - if (!bundle) { - log("Unable to find the application bundle"); - return 1; - } + if (!bundle) { + log("Unable to find the application bundle"); + return 1; + } - log(String("Running App: " + String(bundle.bundlePath.UTF8String))); + log(String("Running App: " + String(bundle.bundlePath.UTF8String))); - appMutex.lock(); + appMutex.lock(); - [sharedWorkspace - openApplicationAtURL: bundle.bundleURL - configuration: configuration - completionHandler: ^(NSRunningApplication* app, NSError* error) - { - if (error) { - appMutex.unlock(); - appStatus = 1; - debug( - "ERROR: NSWorkspace: (code=%lu, domain=%@) %@", - error.code, - error.domain, - error.localizedDescription - ); - } + [sharedWorkspace + openApplicationAtURL: bundle.bundleURL + configuration: configuration + completionHandler: ^(NSRunningApplication* app, NSError* error) + { + if (error) { + appMutex.unlock(); + appStatus = 1; + debug( + "ERROR: NSWorkspace: (code=%lu, domain=%@) %@", + error.code, + error.domain, + error.localizedDescription + ); + } + }]; - }]; + // wait for `NSRunningApplication` to terminate + std::lock_guard<std::mutex> lock(appMutex); - // wait for `NSRunningApplication` to terminate - std::lock_guard<std::mutex> lock(appMutex); - if (appStatus != -1) { - log("App result: " + std::to_string(appStatus.load())); - return appStatus.load(); - } + if (appStatus != -1) { + log("App result: " + std::to_string(appStatus.load())); + auto code = appStatus.load(); - return 0; - } -#endif + if (loop) { + std::cout << "KILLING LOOP" << std::endl; + uv_stop(loop); + } + + return code; + } + + return 0; + } + #endif log(String("Running App: " + headlessCommand + prefix + cmd + args + " --from-ssc")); -#if defined(__linux__) + #if defined(__linux__) // unlink lock file that may existk static const auto bundleIdentifier = settings["meta_bundle_identifier"]; static const auto TMPDIR = Env::get("TMPDIR", "/tmp"); static const auto appInstanceLock = fs::path(TMPDIR) / (bundleIdentifier + ".lock"); unlink(appInstanceLock.c_str()); -#endif + #endif appProcess = std::make_shared<Process>( headlessCommand + prefix + cmd, @@ -968,9 +979,15 @@ int runApp (const Path& path, const String& args, bool headless) { auto p = appProcess; appPid = p->open(); const auto status = p->wait(); + if (appStatus == -1) { appStatus = status; log("App result: " + std::to_string(appStatus.load())); + + if (loop) { + std::cout << "KILLING LOOP" << std::endl; + uv_stop(loop); + } } p = nullptr; @@ -1028,13 +1045,17 @@ void runIOSSimulator (const Path& path, Map& settings) { } StringStream listDevicesCommand; + listDevicesCommand << "xcrun" << " simctl" << " list devices available"; + auto rListDevices = exec(listDevicesCommand.str().c_str()); + if (rListDevices.exitCode != 0) { log("failed to list available devices using \"" + listDevicesCommand.str() + "\""); + if (rListDevices.output.size() > 0) { log(rListDevices.output); } @@ -1848,11 +1869,11 @@ int main (const int argc, const char* argv[]) { auto const subcommand = argv[1]; -#ifndef _WIN32 - signal(SIGHUP, signalHandler); - signal(SIGUSR1, signalHandler); - signal(SIGUSR2, signalHandler); -#endif + #ifndef _WIN32 + signal(SIGHUP, signalHandler); + signal(SIGUSR1, signalHandler); + signal(SIGUSR2, signalHandler); + #endif signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); From ba6cdb7db244996c1874af6376abda8f7b18b7a8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 16:45:06 +0200 Subject: [PATCH 0791/1178] refactor(cli): clean up --- src/cli/cli.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 609982e1bc..52f461080d 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -789,10 +789,7 @@ void signalHandler (int signal) { if (appStatus == -1) { appStatus = signal; - log("App result (sighandler): " + std::to_string(signal)); - - if (loop && uv_loop_alive(loop)) uv_stop(loop); - exit(signal); + log("App result: " + std::to_string(signal)); } } @@ -983,11 +980,6 @@ int runApp (const Path& path, const String& args, bool headless) { if (appStatus == -1) { appStatus = status; log("App result: " + std::to_string(appStatus.load())); - - if (loop) { - std::cout << "KILLING LOOP" << std::endl; - uv_stop(loop); - } } p = nullptr; From 04f57dd9dbf4a607f85152a74126bd21db885f9f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 16:45:34 +0200 Subject: [PATCH 0792/1178] refactor(extension/process): fix broken 'sapi_process_spawn' --- src/extension/process.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extension/process.cc b/src/extension/process.cc index 737d650103..c576f8ef05 100644 --- a/src/extension/process.cc +++ b/src/extension/process.cc @@ -60,6 +60,7 @@ sapi_process_spawn_t* sapi_process_spawn ( ctx, command, argv, + SSC::Vector<SSC::String>{}, path, onstdout, onstderr, From a4b5c9c4f6e60f92a2d393301a57978391f67d83 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 17:24:05 +0200 Subject: [PATCH 0793/1178] fix(cli): try to restore process setup before log store poll was dropped --- src/cli/cli.cc | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 52f461080d..f232f0be49 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -374,10 +374,7 @@ unsigned short createLogSocket() { if (uv_udp_getsockname(&logsocket, (struct sockaddr *)&addr, &len) == 0) { auto port = ntohs(addr.sin_port); - debug("Listening on UDP port %d\n", port); p.set_value(port); - } else { - debug("Failed to get socket name\n"); } uv_run(loop, UV_RUN_DEFAULT); @@ -735,9 +732,9 @@ Vector<Path> handleBuildPhaseForCopyMappedFiles ( return copyMapFiles; } -void signalHandler (int signal) { +void signalHandler (int signum) { #if !defined(_WIN32) - if (signal == SIGUSR1) { + if (signum == SIGUSR1) { #if defined(__APPLE__) checkLogStore = true; #endif @@ -749,7 +746,7 @@ void signalHandler (int signal) { #if !defined(_WIN32) if (appPid > 0) { - kill(appPid, signal); + kill(appPid, signum); } #endif @@ -788,8 +785,13 @@ void signalHandler (int signal) { } if (appStatus == -1) { - appStatus = signal; - log("App result: " + std::to_string(signal)); + appStatus = signum; + log("App result!: " + std::to_string(signum)); + } + + if (signum == SIGTERM || signum == SIGINT) { + signal(signum, SIG_DFL); + raise(signum); } } @@ -932,22 +934,16 @@ int runApp (const Path& path, const String& args, bool headless) { error.domain, error.localizedDescription ); + } else { + appPid = app.processIdentifier; } }]; - // wait for `NSRunningApplication` to terminate std::lock_guard<std::mutex> lock(appMutex); if (appStatus != -1) { log("App result: " + std::to_string(appStatus.load())); - auto code = appStatus.load(); - - if (loop) { - std::cout << "KILLING LOOP" << std::endl; - uv_stop(loop); - } - - return code; + return appStatus.load(); } return 0; From bc5fc2e70bbdf108400df413dfb989105c796dc0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 17:24:21 +0200 Subject: [PATCH 0794/1178] refactor(desktop): improve signal propagation --- src/desktop/main.cc | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 5fd4b887f6..0dbadc1797 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -85,7 +85,7 @@ static void defaultWindowSignalHandler (int signal) { } } -void signalHandler (int signal) { +void signalHandler (int signum) { static auto app = App::sharedApplication(); static auto userConfig = getUserConfig(); static const auto signalsDisabled = userConfig["application_signals"] == "false"; @@ -93,19 +93,24 @@ void signalHandler (int signal) { String name; #if SOCKET_RUNTIME_PLATFORM_APPLE - name = String(sys_signame[signal]); + name = String(sys_signame[signum]); #elif SOCKET_RUNTIME_PLATFORM_LINUX - name = strsignal(signal); + name = strsignal(signum); #endif if (!signalsDisabled || std::find(signals.begin(), signals.end(), name) != signals.end()) { - defaultWindowSignalHandler(signal); + defaultWindowSignalHandler(signum); } - if (shutdownHandler != nullptr) { - app->dispatch([signal] () { - shutdownHandler(signal); - }); + if (signum == SIGTERM || signum == SIGINT) { + signal(signum, SIG_DFL); + if (shutdownHandler != nullptr) { + app->dispatch([signum] () { + shutdownHandler(signum); + }); + } else { + raise(signum); + } } } @@ -791,7 +796,6 @@ MAIN { // main thread. // const auto onMessage = [&](const auto& output) { - // debug("onMessage %s", out.c_str()); const auto message = IPC::Message(output, true); auto window = app.windowManager.getWindow(message.index); From c2e6a6334dd9cf27ed83a52e30e1a3badc6e96df Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 17:28:07 +0200 Subject: [PATCH 0795/1178] fix(bin/install.sh): only copy pkyconfig files on desktop --- bin/install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/install.sh b/bin/install.sh index e018ac3216..95537b79ec 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -589,14 +589,14 @@ function _install { exit 1 fi - if [ "$host" == "Linux" ] || [ "$host" == "Darwin" ]; then - echo "# copying pkgconfig to $SOCKET_HOME/pkgconfig" - rm -rf "$SOCKET_HOME/pkgconfig" - mkdir -p "$SOCKET_HOME/pkgconfig" - cp -rfp "$BUILD_DIR/$arch-desktop/pkgconfig"/* "$SOCKET_HOME/pkgconfig" - fi - if [ "$platform" == "desktop" ]; then + if [ "$host" == "Linux" ] || [ "$host" == "Darwin" ]; then + echo "# copying pkgconfig to $SOCKET_HOME/pkgconfig" + rm -rf "$SOCKET_HOME/pkgconfig" + mkdir -p "$SOCKET_HOME/pkgconfig" + cp -rfp "$BUILD_DIR/$arch-desktop/pkgconfig"/* "$SOCKET_HOME/pkgconfig" + fi + echo "# copying js api to $SOCKET_HOME/api" mkdir -p "$SOCKET_HOME/api" cp -frp "$root"/api/* "$SOCKET_HOME/api" From dc79015d7de9c10f14b19b68ad71fbbb8dca0acf Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 17:28:49 +0200 Subject: [PATCH 0796/1178] fix(app): set default 'nullptr' values --- src/app/app.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/app.hh b/src/app/app.hh index e6e2ebf3a7..f501f8a9eb 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -81,9 +81,9 @@ namespace SSC { Android::BuildInformation androidBuildInformation; Android::Looper androidLooper; Android::JVMEnvironment jvm; - JNIEnv* jni; - jobject self; - jobject appActivity; + JNIEnv* jni = nullptr; + jobject self = nullptr; + jobject appActivity = nullptr; bool isAndroidEmulator = false; #endif From 0849716e72000c249d60dbb7032a05fc3d999546 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 17:29:45 +0200 Subject: [PATCH 0797/1178] refactor(cli): clean up --- src/cli/cli.cc | 10 ++++------ src/cli/templates.hh | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index f232f0be49..15bef6f4d2 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -1263,18 +1263,16 @@ bool setupAndroidAvd (AndroidCliState& state) { String package = state.quote + "system-images;" + state.platform + ";google_apis;" + replace(platform.arch, "arm64", "arm64-v8a") + state.quote; state.avdmanager << state.androidHome; - if (Env::get("ANDROID_SDK_MANAGER").size() > 0) - { + if (Env::get("ANDROID_SDK_MANAGER").size() > 0) { state.avdmanager << "/" << replace(Env::get("ANDROID_SDK_MANAGER"), "sdkmanager", "avdmanager"); - } - else { + } else { if (!platform.win) { if (std::system(("avdmanager list " + state.devNull).c_str()) != 0) { state.avdmanager << "/cmdline-tools/latest/bin/"; } - } - else + } else { state.avdmanager << "\\cmdline-tools\\latest\\bin\\"; + } } if (!fs::exists(state.avdmanager.str())) { diff --git a/src/cli/templates.hh b/src/cli/templates.hh index de333d28bf..5a543bce85 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1538,9 +1538,11 @@ LOCAL_LDLIBS := -landroid -llog LOCAL_SRC_FILES = \ init.cc +LOCAL_STATIC_LIBRARIES := \ + libllama \ + LOCAL_WHOLE_STATIC_LIBRARIES := \ libuv \ - libllama \ libsocket-runtime-static include $(BUILD_SHARED_LIBRARY) From 76ca14d8b8ee750287b2a12835808234abc46a42 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 6 Jun 2024 18:35:38 +0200 Subject: [PATCH 0798/1178] fix(bin/build-runtime-library.sh): fix mtime test tests and invalidation --- bin/build-runtime-library.sh | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index 8739a4ccb0..3b902d93d8 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash declare root="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" -declare clang="${CXX:-"$CLANG"}" +declare clang="${CXX:-"${CLANG:-$(which clang++)}"}" declare cache_path="$root/build/cache" source "$root/bin/functions.sh" @@ -10,13 +10,13 @@ export CPU_CORES=$(set_cpu_cores) declare args=() declare pids=() declare force=0 +declare d="" declare arch="$(host_arch)" declare host_arch=$arch declare host=$(host_os) declare platform="desktop" -declare d="" if [[ "$host" == "Win32" ]]; then # We have to differentiate release and debug for Win32 if [[ -n "$DEBUG" ]]; then @@ -88,11 +88,6 @@ while (( $# > 0 )); do continue fi - # Don't rebuild if header mtimes are newer than .o files - Be sure to manually delete affected assets as required - if [[ "$arg" == "--ignore-header-mtimes" ]]; then - ignore_header_mtimes=1; continue - fi - args+=("$arg") done @@ -115,7 +110,6 @@ declare sources=( "$root/src/window/hotkey.cc" ) -declare test_headers=() declare cflags if [[ "$platform" = "android" ]]; then @@ -181,10 +175,6 @@ done objects+=("$output_directory/llama/build-info.o") -if [[ -z "$ignore_header_mtimes" ]]; then - test_headers+="$(find "$root/src"/core/*.hh)" -fi - function generate_llama_build_info () { build_number="0" build_commit="unknown" @@ -240,8 +230,6 @@ function main () { local i=0 local max_concurrency=$CPU_CORES - local newest_mtime=0 - mkdir -p "$output_directory/include" cp -rf "$root/include"/* "$output_directory/include" rm -f "$output_directory/include/socket/_user-config-bytes.hh" @@ -260,31 +248,34 @@ function main () { { declare src_directory="$root/src" - declare object="${source/.cc/$d.o}" - object="${object/.cpp/$d.o}" - declare header="${source/.cc/.hh}" - header="${header/.cpp/.h}" - declare build_dir="$root/build" + object="${object/.cpp/$d.o}" + header="${header/.cpp/.h}" + if [[ "$object" =~ ^"$src_directory" ]]; then object="${object/$src_directory/$output_directory}" else object="${object/$build_dir/$output_directory}" fi - if (( force )) || + if + (( force )) || ! test -f "$object" || - (( newest_mtime > $(stat_mtime "$object") )) || (( $(stat_mtime "$source") > $(stat_mtime "$object") )) || (( $(stat_mtime "$header") > $(stat_mtime "$source") )); then mkdir -p "$(dirname "$object")" + echo "# compiling object ($arch-$platform) $(basename "$source")" quiet $clang "${cflags[@]}" -c "$source" -o "$object" || onsignal echo "ok - built ${source/$src_directory\//} -> ${object/$output_directory\//} ($arch-$platform)" + + if (( $(stat_mtime "$header") > $(stat_mtime "$source") )); then + touch "$source" + fi fi } & pids+=($!) done From 9304f5089e86908779e5e21d088b5bf7fdf99c2f Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 10 Jun 2024 14:32:41 +0200 Subject: [PATCH 0799/1178] fix(core): allow core to be used as a utility, turning off some features that cause assertion errors. feature(ai): allow more flags --- api/ai.js | 72 ++++++++++++++++++++--- api/latica/api.js | 11 ++-- api/latica/encryption.js | 2 +- api/latica/index.js | 7 ++- api/latica/packets.js | 2 +- api/latica/proxy.js | 6 +- api/latica/worker.js | 1 + bin/update-network-protocol.sh | 2 + src/cli/cli.cc | 12 ++-- src/cli/cli.hh | 4 ++ src/core/core.hh | 4 +- src/core/file_system_watcher.cc | 3 +- src/core/modules/ai.cc | 58 ++++++++++++++++--- src/core/modules/ai.hh | 23 +++++++- src/core/modules/notifications.cc | 94 ++++++++++++++++--------------- src/core/modules/notifications.hh | 4 +- src/ipc/routes.cc | 20 +++++++ 17 files changed, 243 insertions(+), 82 deletions(-) diff --git a/api/ai.js b/api/ai.js index b83e37aebe..e3af981784 100644 --- a/api/ai.js +++ b/api/ai.js @@ -43,13 +43,49 @@ import * as exports from './ai.js' */ export class LLM extends EventEmitter { /** - * Constructs an LLM instance. - * @param {Object} [options] - The options for initializing the LLM. - * @param {string} [options.path] - The path to a valid model (.gguf). - * @param {string} [options.prompt] - The query that guides the model to generate a relevant and coherent responses. - * @param {string} [options.id] - The optional ID for the LLM instance. - * @throws {Error} If the model path is not provided. + * Constructs an LLM instance. Each parameter is designed to configure and control + * the behavior of the underlying large language model provided by llama.cpp. + * @param {Object} options - Configuration options for the LLM instance. + * @param {string} options.path - The file path to the model in .gguf format. This model file contains + * the weights and configuration necessary for initializing the language model. + * @param {string} options.prompt - The initial input text to the model, setting the context or query + * for generating responses. The model uses this as a starting point for text generation. + * @param {string} [options.id] - An optional unique identifier for this specific instance of the model, + * useful for tracking or referencing the model in multi-model setups. + * @param {number} [options.n_ctx=1024] - Specifies the maximum number of tokens that the model can consider + * for a single query. This is crucial for managing memory and computational + * efficiency. Exceeding the model's configuration may lead to errors or truncated outputs. + * @param {number} [options.n_threads=8] - The number of threads allocated for the model's computation, + * affecting performance and speed of response generation. + * @param {number} [options.temp=1.1] - Sampling temperature controls the randomness of predictions. + * Higher values increase diversity, potentially at the cost of coherence. + * @param {number} [options.max_tokens=512] - The upper limit on the number of tokens that the model can generate + * in response to a single prompt. This prevents runaway generations. + * @param {number} [options.n_gpu_layers=32] - The number of GPU layers dedicated to the model processing. + * More layers can increase accuracy and complexity of the outputs. + * @param {number} [options.n_keep=0] - Determines how many of the top generated responses are retained after + * the initial generation phase. Useful for models that generate multiple outputs. + * @param {number} [options.n_batch=0] - The size of processing batches. Larger batch sizes can reduce + * the time per token generation by parallelizing computations. + * @param {number} [options.n_predict=0] - Specifies how many forward predictions the model should make + * from the current state. This can pre-generate responses or calculate probabilities. + * @param {number} [options.grp_attn_n=0] - Group attention parameter 'N' modifies how attention mechanisms + * within the model are grouped and interact, affecting the model’s focus and accuracy. + * @param {number} [options.grp_attn_w=0] - Group attention parameter 'W' adjusts the width of each attention group, + * influencing the breadth of context considered by each attention group. + * @param {number} [options.seed=0] - A seed for the random number generator used in the model. Setting this ensures + * consistent results in model outputs, important for reproducibility in experiments. + * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, + * reducing the risk of less likely, potentially nonsensical outputs. + * @param {float} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool + * to only those whose cumulative probability exceeds this value, enhancing output relevance. + * @param {float} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring + * that generated tokens have at least this likelihood of being relevant or coherent. + * @param {float} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how + * the model weights novel or unseen prompts during generation. + * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. */ + constructor (options = {}) { super() @@ -63,8 +99,27 @@ export class LLM extends EventEmitter { const opts = { id: this.id, + path: this.path, prompt: this.prompt, - path: this.path + antiprompt: options.antiprompt, + conversation: options.conversation === true, // Convert to boolean, more idiomatic than String(true/false) + chatml: options.chatml === true, + instruct: options.instruct === true, + n_ctx: options.n_ctx || 1024, // Simplified, assuming default value of 1024 if not specified + n_threads: options.n_threads || 8, + temp: options.temp || 1.1, // Assuming `temp` should be a number, not a string + max_tokens: options.max_tokens || 512, + n_gpu_layers: options.n_gpu_layers || 32, + n_keep: options.n_keep || 0, + n_batch: options.n_batch || 0, + n_predict: options.n_predict || 0, + grp_attn_n: options.grp_attn_n || 0, + grp_attn_w: options.grp_attn_w || 0, + seed: options.seed || 0, // Default seed if not specified + top_k: options.top_k || 0, // Default top_k if not specified + tok_p: options.tok_p || 0.0, // Default tok_p if not specified + min_p: options.min_p || 0.0, // Default min_p if not specified + tfs_z: options.tfs_z || 0.0 // Default tfs_z if not specified } globalThis.addEventListener('data', event => { @@ -92,7 +147,8 @@ export class LLM extends EventEmitter { } }) - const result = ipc.sendSync('ai.llm.create', opts) + console.log('NEW LLM', opts) + const result = ipc.request('ai.llm.create', opts) if (result.err) { throw result.err diff --git a/api/latica/api.js b/api/latica/api.js index 1c1210a4a2..37c94d601a 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -55,6 +55,7 @@ async function api (options = {}, events, dgram) { } _peer.onData = (...args) => bus._emit('#data', ...args) + _peer.onDebug = (...args) => bus._emit('#debug', ...args) _peer.onSend = (...args) => bus._emit('#send', ...args) _peer.onFirewall = (...args) => bus._emit('#firewall', ...args) _peer.onMulticast = (...args) => bus._emit('#multicast', ...args) @@ -126,6 +127,8 @@ async function api (options = {}, events, dgram) { bus.seal = (m, v = options.signingKeys) => _peer.seal(m, v) bus.open = (m, v = options.signingKeys) => _peer.open(m, v) + bus.send = (...args) => _peer.send(...args) + bus.query = (...args) => _peer.query(...args) const pack = async (eventName, value, opts = {}) => { @@ -163,10 +166,10 @@ async function api (options = {}, events, dgram) { const sub = bus.subclusters.get(scid) if (!sub) return {} - try { - opened = await _peer.open(packet.message, scid) - } catch (err) { - sub._emit('warning', err) + const { err: errOpen, data: dataOpened } = await _peer.open(packet.message, scid) + + if (errOpen) { + sub._emit('warning', errOpen) return {} } diff --git a/api/latica/encryption.js b/api/latica/encryption.js index 8b0ded7055..7946496d18 100644 --- a/api/latica/encryption.js +++ b/api/latica/encryption.js @@ -174,7 +174,7 @@ export class Encryption { throw new Error('ENOTVERIFIED') } - return Buffer.from(sodium.crypto_box_seal_open(ct, pk, sk)) + return sodium.crypto_box_seal_open(ct, pk, sk) } /** diff --git a/api/latica/index.js b/api/latica/index.js index f24af29817..a48059091f 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -572,7 +572,7 @@ export class Peer { await this.mcast(packet) debug(this.peerId, `-> RESEND (packetId=${packetId})`) - if (this.onState) await this.onState(this.getState()) + if (this.onState) this.onState(this.getState()) } } @@ -580,7 +580,7 @@ export class Peer { * Get the serializable state of the peer (can be passed to the constructor or create method) * @return {undefined} */ - async getState () { + getState () { this.config.clock = this.clock // save off the clock const peers = this.peers.map(p => { @@ -939,7 +939,7 @@ export class Peer { }) debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) - if (this.onState) await this.onState(this.getState()) + if (this.onState) this.onState(this.getState()) this.mcast(packet) this.gate.set(packet.packetId.toString('hex'), 1) @@ -1097,6 +1097,7 @@ export class Peer { this.closing = true this.socket.close() + this.probeSocket.close() if (this.onClose) this.onClose() } diff --git a/api/latica/packets.js b/api/latica/packets.js index c46ac43b50..0d5c178df1 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -320,7 +320,7 @@ export class Packet { p.message = String(p.message) } - if (p.message?.length > Packet.MESSAGE_BYTES) throw new Error('ETOOBIG') + if (p.message?.length > Packet.maxLength) throw new Error('ETOOBIG') // we only have p.nextId when we know ahead of time, if it's empty that's fine. if (p.packetId.length === 1 && p.packetId[0] === 0) { diff --git a/api/latica/proxy.js b/api/latica/proxy.js index dc6850e9c2..f6a764418f 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -10,7 +10,6 @@ * Protocol * */ -import { Deferred } from '../async.js' import path from '../path.js' const { pathname } = new URL(import.meta.url) @@ -236,7 +235,10 @@ class PeerWorkerProxy { } const seq = ++this.#index - const d = new Deferred() + let { promise, resolve, reject } = Promise.withResolvers(); + const d = promise + d.resolve = resolve + d.reject = reject this.#channel.port1.postMessage( { prop, data, seq }, diff --git a/api/latica/worker.js b/api/latica/worker.js index c4b1845b7e..fdd6f4f7ee 100644 --- a/api/latica/worker.js +++ b/api/latica/worker.js @@ -21,6 +21,7 @@ globalThis.addEventListener('message', ({ data: source }) => { case 'create': { peer = new Peer(data, dgram) + peer.onDebug = (...args) => this.callMainThread('onDebug', args) peer.onConnecting = (...args) => this.callMainThread('onConnecting', args) peer.onConnection = (...args) => this.callMainThread('onConnection', args) peer.onDisconnection = (...args) => this.callMainThread('onDisconnection', args) diff --git a/bin/update-network-protocol.sh b/bin/update-network-protocol.sh index 716b31885f..3767ad3fcf 100755 --- a/bin/update-network-protocol.sh +++ b/bin/update-network-protocol.sh @@ -1,5 +1,7 @@ #!/bin/sh +npm link @socketsupply/latica + version="${1:-"1.0.23-0"}" rm -rf api/latica.js || exit $? diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 15bef6f4d2..c39287d8a4 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -43,16 +43,20 @@ #include <span> #include <unordered_set> +#ifndef CMD_RUNNER +#define CMD_RUNNER +#endif + +#ifndef SSC_CLI +#define SSC_CLI 1 +#endif + #include "../extension/extension.hh" #include "../core/core.hh" #include "templates.hh" #include "cli.hh" -#ifndef CMD_RUNNER -#define CMD_RUNNER -#endif - #ifndef SOCKET_RUNTIME_BUILD_TIME #define SOCKET_RUNTIME_BUILD_TIME 0 #endif diff --git a/src/cli/cli.hh b/src/cli/cli.hh index 95f617a356..c298c74fad 100644 --- a/src/cli/cli.hh +++ b/src/cli/cli.hh @@ -6,6 +6,10 @@ #include <signal.h> +#ifndef SOCKET_CLI +#define SOCKET_CLI 1 +#endif + namespace SSC::CLI { inline void notify (int signal) { #if !defined(_WIN32) diff --git a/src/core/core.hh b/src/core/core.hh index 0d80fab659..e732a309ab 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -111,7 +111,7 @@ namespace SSC { Thread *eventLoopThread = nullptr; #endif - Core () : + Core (bool isUtility = false) : #if !SOCKET_RUNTIME_PLATFORM_IOS childProcess(this), #endif @@ -120,7 +120,7 @@ namespace SSC { fs(this), geolocation(this), networkStatus(this), - notifications(this), + notifications(this, isUtility), os(this), platform(this), timers(this), diff --git a/src/core/file_system_watcher.cc b/src/core/file_system_watcher.cc index fc2360480a..e3fbd80298 100644 --- a/src/core/file_system_watcher.cc +++ b/src/core/file_system_watcher.cc @@ -125,7 +125,8 @@ namespace SSC { // a loop may be configured for the instance already, perhaps here or // manually by the caller if (this->core == nullptr) { - this->core = new Core(); + const bool isUtility = true; + this->core = new Core(isUtility); this->ownsCore = true; } diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 1d3896cf5d..3131aaa778 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -225,15 +225,44 @@ namespace SSC { llama_numa_init(this->params.numa); llama_sampling_params& sparams = this->params.sparams; + + if (options.temp) sparams.temp = options.temp; + if (options.top_k) sparams.top_k = options.top_k; + if (options.top_p) sparams.top_p = options.top_p; + if (options.tfs_z) sparams.tfs_z = options.tfs_z; + if (options.min_p) sparams.min_p = options.min_p; + if (options.typical_p) sparams.typical_p = options.typical_p; + this->sampling = llama_sampling_init(sparams); if (!this->sampling) this->err = "failed to initialize sampling subsystem"; if (this->params.seed == LLAMA_DEFAULT_SEED) this->params.seed = time(nullptr); - this->params.chatml = true; + this->params.conversation = options.conversation; + this->params.chatml = options.chatml; + this->params.instruct = options.instruct; + + this->params.n_ctx = options.n_ctx; + + if (this->params.n_ctx != 0 && this->params.n_ctx < 8) { + this->params.n_ctx = 8; + } + + if (options.n_keep > 0) this->params.n_keep = options.n_keep; + if (options.n_batch > 0) this->params.n_batch = options.n_batch; + if (options.n_predict > 0) this->params.n_predict = options.n_predict; + if (options.grp_attn_n > 0) this->params.grp_attn_n = options.grp_attn_n; + if (options.grp_attn_w > 0) this->params.grp_attn_w = options.grp_attn_w; + this->params.verbose_prompt = false; - this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; - this->params.n_ctx = 2048; + + if (this->params.chatml) { + this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; + } + + if (options.antiprompt.size()) { + this->params.antiprompt = Vector<String> { options.antiprompt }; + } #if SOCKET_RUNTIME_PLATFORM_IOS this->params.use_mmap = false; @@ -426,7 +455,6 @@ namespace SSC { std::vector<llama_token> embd; std::vector<llama_token> embd_guidance; - const int n_ctx = this->n_ctx; const auto inp_pfx = ::llama_tokenize(ctx, "\n\n### Instruction:\n\n", true, true); const auto inp_sfx = ::llama_tokenize(ctx, "\n\n### Response:\n\n", false, true); @@ -436,9 +464,9 @@ namespace SSC { const auto cml_pfx = ::llama_tokenize(ctx, "\n<|im_start|>user\n", true, true); const auto cml_sfx = ::llama_tokenize(ctx, "<|im_end|>\n<|im_start|>assistant\n", false, true); - while ((n_remain != 0 && !is_antiprompt) || this->params.interactive) { + while (((n_remain != 0 && !is_antiprompt) || this->params.interactive) && this->stopped == false) { if (!embd.empty()) { - int max_embd_size = n_ctx - 4; + int max_embd_size = this->params.n_ctx - 4; if ((int) embd.size() > max_embd_size) { const int skipped_tokens = (int)embd.size() - max_embd_size; @@ -447,7 +475,7 @@ namespace SSC { } if (ga_n == 1) { - if (n_past + (int) embd.size() + std::max<int>(0, guidance_offset) >= n_ctx) { + if (n_past + (int) embd.size() + std::max<int>(0, guidance_offset) >= this->params.n_ctx) { if (this->params.n_predict == -2) { LOG("\n\ncontext full and n_predict == -%d => stopping\n", this->params.n_predict); break; @@ -457,7 +485,7 @@ namespace SSC { const int n_discard = n_left/2; LOG("context full, swapping: n_past = %d, n_left = %d, n_ctx = %d, n_keep = %d, n_discard = %d\n", - n_past, n_left, n_ctx, this->params.n_keep, n_discard); + n_past, n_left, this->params.n_ctx, this->params.n_keep, n_discard); llama_kv_cache_seq_rm (ctx, 0, this->params.n_keep, this->params.n_keep + n_discard); llama_kv_cache_seq_add(ctx, 0, this->params.n_keep + n_discard, n_past, -n_discard); @@ -475,6 +503,8 @@ namespace SSC { } } else { while (n_past >= ga_i + ga_w) { + if (this->stopped) return; + const int ib = (ga_n*ga_i)/ga_w; const int bd = (ga_w/ga_n)*(ga_n - 1); const int dd = (ga_w/ga_n) - ib*bd - ga_w; @@ -500,6 +530,8 @@ namespace SSC { size_t i = 0; for ( ; i < embd.size(); i++) { + if (this->stopped) return; + if (embd[i] != this->session_tokens[n_session_consumed]) { this->session_tokens.resize(n_session_consumed); break; @@ -540,6 +572,8 @@ namespace SSC { } for (int i = 0; i < input_size; i += this->params.n_batch) { + if (this->stopped) return; + int n_eval = std::min(input_size - i, this->params.n_batch); if (llama_decode(this->guidance, llama_batch_get_one(input_buf + i, n_eval, n_past_guidance, 0))) { @@ -552,6 +586,8 @@ namespace SSC { } for (int i = 0; i < (int) embd.size(); i += this->params.n_batch) { + if (this->stopped) return; + int n_eval = (int) embd.size() - i; if (n_eval > this->params.n_batch) { @@ -592,6 +628,8 @@ namespace SSC { LOG("embd_inp.size(): %d, n_consumed: %d\n", (int)this->embd_inp.size(), n_consumed); while ((int)this->embd_inp.size() > n_consumed) { + if (this->stopped) return; + embd.push_back(this->embd_inp[n_consumed]); llama_sampling_accept(this->sampling, this->ctx, this->embd_inp[n_consumed], false); @@ -636,6 +674,8 @@ namespace SSC { is_antiprompt = false; for (String & antiprompt : this->params.antiprompt) { + if (this->stopped) return; + size_t extra_padding = this->params.interactive ? 0 : 2; size_t search_start_pos = last_output.length() > static_cast<size_t>(antiprompt.length() + extra_padding) ? last_output.length() - static_cast<size_t>(antiprompt.length() + extra_padding) @@ -653,6 +693,8 @@ namespace SSC { llama_token last_token = llama_sampling_last(this->sampling); for (std::vector<llama_token> ids : antiprompt_ids) { + if (this->stopped) return; + if (ids.size() == 1 && last_token == ids[0]) { if (this->params.interactive) { this->interactive = true; diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh index 19da1828d7..034cf11c33 100644 --- a/src/core/modules/ai.hh +++ b/src/core/modules/ai.hh @@ -24,10 +24,29 @@ namespace SSC { class Core; struct LLMOptions { - int attentionCapacity; - int seed; + bool conversation = false; + bool chatml = false; + bool instruct = false; + int n_ctx = 0; + int n_keep = 0; + int n_batch = 0; + int n_threads = 0; + int n_gpu_layers = 0; + int n_predict = 0; + int grp_attn_n = 0; + int grp_attn_w = 0; + int seed = 0; + int max_tokens = 0; + int top_k = 0; + float top_p = 0.0; + float min_p = 0.0; + float tfs_z = 0.0; + float typical_p = 0.0; + float temp; + String path; String prompt; + String antiprompt; }; class CoreAI : public CoreModule { diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index 636678e7f5..46fbaff6a5 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -88,69 +88,73 @@ namespace SSC { }; } - CoreNotifications::CoreNotifications (Core* core) + CoreNotifications::CoreNotifications (Core* core, bool isUtility = false) : CoreModule(core), + isUtility(isUtility), permissionChangeObservers(), notificationResponseObservers(), notificationPresentedObservers() { - #if SOCKET_RUNTIME_PLATFORM_APPLE - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + #if SOCKET_RUNTIME_PLATFORM_APPLE + if (this->isUtility) return; + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; - this->userNotificationCenterDelegate.notifications = this; + this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; + this->userNotificationCenterDelegate.notifications = this; - if (!notificationCenter.delegate) { - notificationCenter.delegate = this->userNotificationCenterDelegate; - } + if (!notificationCenter.delegate) { + notificationCenter.delegate = this->userNotificationCenterDelegate; + } - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; - this->userNotificationCenterPollTimer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { - // look for authorization status changes - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - JSON::Object json; - if (this->currentUserNotificationAuthorizationStatus != settings.authorizationStatus) { - this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; - - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - json = JSON::Object::Entries {{"state", "denied"}}; - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - json = JSON::Object::Entries {{"state", "prompt"}}; - } else { - json = JSON::Object::Entries {{"state", "granted"}}; + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; + this->userNotificationCenterPollTimer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { + // look for authorization status changes + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + JSON::Object json; + if (this->currentUserNotificationAuthorizationStatus != settings.authorizationStatus) { + this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; + + if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + json = JSON::Object::Entries {{"state", "denied"}}; + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + json = JSON::Object::Entries {{"state", "prompt"}}; + } else { + json = JSON::Object::Entries {{"state", "granted"}}; + } + + this->permissionChangeObservers.dispatch(json); } - - this->permissionChangeObservers.dispatch(json); - } + }]; }]; - }]; - [NSRunLoop.mainRunLoop - addTimer: this->userNotificationCenterPollTimer - forMode: NSDefaultRunLoopMode - ]; - }]; - #endif + [NSRunLoop.mainRunLoop + addTimer: this->userNotificationCenterPollTimer + forMode: NSDefaultRunLoopMode + ]; + }]; + #endif } CoreNotifications::~CoreNotifications () { - #if SOCKET_RUNTIME_PLATFORM_APPLE - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + #if SOCKET_RUNTIME_PLATFORM_APPLE + if (this->isUtility) return; - if (notificationCenter.delegate == this->userNotificationCenterDelegate) { - notificationCenter.delegate = nullptr; - } + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [this->userNotificationCenterPollTimer invalidate]; + if (notificationCenter.delegate == this->userNotificationCenterDelegate) { + notificationCenter.delegate = nullptr; + } - #if !__has_feature(objc_arc) - [this->userNotificationCenterDelegate release]; - #endif + [this->userNotificationCenterPollTimer invalidate]; - this->userNotificationCenterDelegate = nullptr; - this->userNotificationCenterPollTimer = nullptr; - #endif + #if !__has_feature(objc_arc) + [this->userNotificationCenterDelegate release]; + #endif + + this->userNotificationCenterDelegate = nullptr; + this->userNotificationCenterPollTimer = nullptr; + #endif } bool CoreNotifications::addPermissionChangeObserver ( diff --git a/src/core/modules/notifications.hh b/src/core/modules/notifications.hh index 0169cc7e8c..32caa68a23 100644 --- a/src/core/modules/notifications.hh +++ b/src/core/modules/notifications.hh @@ -30,6 +30,8 @@ namespace SSC { using NotificationPresentedObserver = CoreModule::Observer<JSON::Object>; using NotificationPresentedObservers = CoreModule::Observers<NotificationPresentedObserver>; + bool isUtility = false; + struct Notification { String identifier; const JSON::Object json () const; @@ -66,7 +68,7 @@ namespace SSC { NotificationResponseObservers notificationResponseObservers; NotificationPresentedObservers notificationPresentedObservers; - CoreNotifications (Core* core); + CoreNotifications (Core* core, bool isUtility); ~CoreNotifications (); bool removePermissionChangeObserver (const PermissionChangeObserver& observer); diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 9beef2db37..c17f743b9f 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -58,10 +58,30 @@ static void mapIPCRoutes (Router *router) { SSC::LLMOptions options; options.path = message.get("path"); options.prompt = message.get("prompt"); + options.antiprompt = message.get("antiprompt"); uint64_t modelId = 0; REQUIRE_AND_GET_MESSAGE_VALUE(modelId, "id", std::stoull); + if (message.has("n_batch")) REQUIRE_AND_GET_MESSAGE_VALUE(options.n_batch, "n_batch", std::stoi); + if (message.has("n_ctx")) REQUIRE_AND_GET_MESSAGE_VALUE(options.n_ctx, "n_ctx", std::stoi); + if (message.has("n_gpu_layers")) REQUIRE_AND_GET_MESSAGE_VALUE(options.n_gpu_layers, "n_gpu_layers", std::stoi); + if (message.has("n_keep")) REQUIRE_AND_GET_MESSAGE_VALUE(options.n_keep, "n_keep", std::stoi); + if (message.has("n_threads")) REQUIRE_AND_GET_MESSAGE_VALUE(options.n_threads, "n_threads", std::stoi); + if (message.has("n_predict")) REQUIRE_AND_GET_MESSAGE_VALUE(options.n_predict, "n_predict", std::stoi); + if (message.has("grp_attn_n")) REQUIRE_AND_GET_MESSAGE_VALUE(options.grp_attn_n, "grp_attn_n", std::stoi); + if (message.has("grp_attn_w")) REQUIRE_AND_GET_MESSAGE_VALUE(options.grp_attn_w, "grp_attn_w", std::stoi); + if (message.has("max_tokens")) REQUIRE_AND_GET_MESSAGE_VALUE(options.max_tokens, "max_tokens", std::stoi); + if (message.has("seed")) REQUIRE_AND_GET_MESSAGE_VALUE(options.seed, "seed", std::stoi); + if (message.has("temp")) REQUIRE_AND_GET_MESSAGE_VALUE(options.temp, "temp", std::stof); + if (message.has("top_k")) REQUIRE_AND_GET_MESSAGE_VALUE(options.top_k, "top_k", std::stoi); + if (message.has("top_p")) REQUIRE_AND_GET_MESSAGE_VALUE(options.top_p, "top_p", std::stof); + if (message.has("min_p")) REQUIRE_AND_GET_MESSAGE_VALUE(options.min_p, "min_p", std::stof); + if (message.has("tfs_z")) REQUIRE_AND_GET_MESSAGE_VALUE(options.tfs_z, "tfs_z", std::stof); + if (message.has("conversation")) options.conversation = message.get("conversation") == "true"; + if (message.has("chatml")) options.chatml = message.get("chatml") == "true"; + if (message.has("instruct")) options.instruct = message.get("instruct") == "true"; + router->bridge->core->ai.createLLM(message.seq, modelId, options, RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply)); }); From fafc83956fc486802729cad41394b094747c551d Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 10 Jun 2024 14:34:18 +0200 Subject: [PATCH 0800/1178] chore(api): add files --- api/latica.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 api/latica.js diff --git a/api/latica.js b/api/latica.js new file mode 100644 index 0000000000..e033e4e0d0 --- /dev/null +++ b/api/latica.js @@ -0,0 +1,3 @@ +import def from './latica/index.js' +export * from './latica/index.js' +export default def From 9627a0816e4cd09d5b000e7d89f9e483d4e54aad Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 10 Jun 2024 14:44:13 +0200 Subject: [PATCH 0801/1178] chore(api): remove logs --- api/ai.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/ai.js b/api/ai.js index e3af981784..45e4baaaf4 100644 --- a/api/ai.js +++ b/api/ai.js @@ -147,7 +147,6 @@ export class LLM extends EventEmitter { } }) - console.log('NEW LLM', opts) const result = ipc.request('ai.llm.create', opts) if (result.err) { From e969beff4e409f81bb200482da390391f933dc7f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 11 Jun 2024 21:26:47 +0200 Subject: [PATCH 0802/1178] refactor(api): remove quirk android checks --- api/ipc.js | 8 +++++--- api/service-worker/container.js | 8 ++------ api/vm.js | 14 ++------------ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 6a8973a206..39ab939993 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1008,8 +1008,9 @@ export async function ready () { function loop () { // this can hang on android. Give it some time because emulators can be slow. if (Date.now() - startReady > 10000) { - reject(new Error('failed to resolve globalThis.__args')) + reject(new Error('Failed to resolve globalThis.__args')) } else if (globalThis.__args) { + // @ts-ignore queueMicrotask(() => resolve()) } else { queueMicrotask(loop) @@ -1090,7 +1091,7 @@ class IPCSearchParams extends URLSearchParams { * @return {Result} * @ignore */ -export function sendSync (command, value, options = {}, buffer) { +export function sendSync (command, value = '', options = {}, buffer = null) { if (!globalThis.XMLHttpRequest) { const err = new Error('XMLHttpRequest is not supported in environment') return Result.from(err) @@ -1199,7 +1200,7 @@ export async function resolve (seq, value) { * @param {boolean=} [options.bytes=false] * @return {Promise<Result>} */ -export async function send (command, value, options) { +export async function send (command, value, options = null) { await ready() if (options?.cache === true && cache[command]) { @@ -1560,6 +1561,7 @@ if ( } Object.freeze(primordials) + initializeXHRIntercept() if (typeof globalThis?.window !== 'undefined') { diff --git a/api/service-worker/container.js b/api/service-worker/container.js index c245854fc9..116587564d 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -110,8 +110,7 @@ async function preloadExistingRegistration (container) { if (registration) { if (registration.active) { if ( - application.config.webview_service_worker_mode === 'hybrid' || - /android/i.test(os.platform()) + application.config.webview_service_worker_mode === 'hybrid' ) { if ( !internal.get(container).isRegistered && @@ -309,10 +308,7 @@ export class ServiceWorkerContainer extends EventTarget { if ( String(application.config.webview_service_worker_frame) !== 'false' && - ( - application.config.webview_service_worker_mode === 'hybrid' || - /android/i.test(os.platform()) - ) + application.config.webview_service_worker_mode === 'hybrid' ) { await ServiceWorkerContainerRealm.init(this) } diff --git a/api/vm.js b/api/vm.js index 699c3f8250..4d8205436e 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1195,8 +1195,6 @@ export class Script extends EventTarget { /** * Gets the VM context window. * This function will create it if it does not already exist. - * The current window will be used on Android platforms as there can - * only be one window. * @return {Promise<import('./window.js').ApplicationWindow} */ export async function getContextWindow () { @@ -1207,11 +1205,7 @@ export async function getContextWindow () { const currentWindow = await application.getCurrentWindow() - // just return the current window for android as there can only ever be one - if ( - os.platform() === 'android' || - (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) - ) { + if (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) { contextWindow = currentWindow if (!contextWindow.frame) { @@ -1312,11 +1306,7 @@ export async function getContextWorker () { return await contextWorker.ready } - // just return the current window for android as there can only ever be one - if ( - os.platform() === 'android' || - (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) - ) { + if (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) { if (globalThis.window && globalThis.top === globalThis.window) { // inside global top window contextWorker = new ContextWorkerInterface() From 2e30180a52968a28285907ff9a5b90f5c6fccfd5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 11 Jun 2024 21:27:57 +0200 Subject: [PATCH 0803/1178] refactor(ipc): more android clean up --- src/ipc/bridge.cc | 23 ++++++----- src/ipc/bridge.kt | 22 ++++++++++- src/ipc/preload.hh | 4 +- src/ipc/scheme_handlers.cc | 63 +++++++++++++++++++----------- src/ipc/scheme_handlers.kt | 79 ++++++++++++++++++++++++++------------ 5 files changed, 129 insertions(+), 62 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 82bc64ac99..36cdb760ed 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -183,7 +183,11 @@ export default module)S"; }; this->dispatchFunction = [] (auto callback) { + #if SOCKET_RUNTIME_PLATFORM_ANDROID + callback(); + #else App::sharedApplication()->dispatch(callback); + #endif }; core->networkStatus.addObserver(this->networkStatusObserver, [this](auto json) { @@ -376,6 +380,11 @@ export default module)S"; ) { auto message = Message(request->url(), true); + if (request->method == "OPTIONS") { + auto response = SchemeHandlers::Response(request, 200); + return callback(response); + } + // handle special 'ipc://post' case if (message.name == "post") { uint64_t id = 0; @@ -390,8 +399,7 @@ export default module)S"; }} }); - callback(response); - return; + return callback(response); } if (!this->core->hasPost(id)) { @@ -403,8 +411,7 @@ export default module)S"; }} }); - callback(response); - return; + return callback(response); } auto response = SchemeHandlers::Response(request, 200); @@ -446,10 +453,6 @@ export default module)S"; auto response = SchemeHandlers::Response(request); response.setHeaders(result.headers); - response.setHeader("access-control-allow-origin", "*"); - response.setHeader("access-control-allow-methods", "GET, POST, PUT, DELETE"); - response.setHeader("access-control-allow-headers", "*"); - response.setHeader("access-control-allow-credentials", "true"); // handle event source streams if (result.post.eventStream != nullptr) { @@ -619,10 +622,6 @@ export default module)S"; } if (request->method == "OPTIONS") { - response.setHeader("access-control-allow-origin", "*"); - response.setHeader("access-control-allow-methods", "GET, HEAD"); - response.setHeader("access-control-allow-headers", "*"); - response.setHeader("access-control-allow-credentials", "true"); response.writeHead(200); } diff --git a/src/ipc/bridge.kt b/src/ipc/bridge.kt index 02578ac641..01090ded66 100644 --- a/src/ipc/bridge.kt +++ b/src/ipc/bridge.kt @@ -35,7 +35,10 @@ private fun isAndroidAssetsUri (uri: android.net.Uri): Boolean { return false } -open class Bridge (val index: Int, val activity: AppCompatActivity): WebViewClient() { +open class Bridge ( + val index: Int, + val activity: AppCompatActivity +): WebViewClient() { open val schemeHandlers = SchemeHandlers(this) open val navigator = Navigator(this) open val buffers = mutableMapOf<String, ByteArray>() @@ -44,6 +47,7 @@ open class Bridge (val index: Int, val activity: AppCompatActivity): WebViewClie view: WebView, request: WebResourceRequest ): Boolean { + console.log("request.url.scheme: ${request.url.scheme}") if (isAndroidAssetsUri(request.url)) { return false } @@ -55,13 +59,27 @@ open class Bridge (val index: Int, val activity: AppCompatActivity): WebViewClie return false } + if ( + request.url.scheme == "ipc" || + request.url.scheme == "node" || + request.url.scheme == "npm" || + request.url.scheme == "socket" + ) { + return false + } + + val scheme = request.url.scheme + if (scheme != null && schemeHandlers.hasHandlerForScheme(scheme)) { + return true + } + val allowed = this.navigator.isNavigationRequestAllowed( view.url ?: "", request.url.toString() ) if (allowed) { - return false + return true } val intent = Intent(Intent.ACTION_VIEW, request.url) diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh index 7f1921087c..f788fb9a11 100644 --- a/src/ipc/preload.hh +++ b/src/ipc/preload.hh @@ -67,7 +67,7 @@ namespace SSC::IPC { uint64_t clientId = 0; int index = 0; - Headers headers; // depends on 'features.useHTMLMarkup' + Headers headers = {}; // depends on 'features.useHTMLMarkup' Map metadata; // depends on 'features.useHTMLMarkup' Map env; Map userConfig; @@ -98,7 +98,7 @@ namespace SSC::IPC { * tags into the preload output. * This depends on the 'useHTMLMarkup' feature. */ - Headers headers; + Headers headers = {}; /** * A mapping of key-value metadata to be injected as HTML `<meta>` tags diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index f3e7cf29be..1bfde88334 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -236,6 +236,30 @@ extern "C" { return handled; } + + jboolean ANDROID_EXTERNAL(ipc, SchemeHandlers, hasHandlerForScheme) ( + JNIEnv* env, + jobject self, + jint index, + jstring schemeString + ) { + auto app = App::sharedApplication(); + + if (!app) { + ANDROID_THROW(env, "Missing 'App' in environment"); + return false; + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + ANDROID_THROW(env, "Invalid window requested"); + return false; + } + + const auto scheme = Android::StringWrap(env, schemeString).str(); + return window->bridge.schemeHandlers.hasHandlerForScheme(scheme); + } } #endif @@ -523,7 +547,6 @@ namespace SSC::IPC { this->bridge->dispatch([=, this] () mutable { Lock lock(this->mutex); - auto request = this->activeRequests[id]; if (request != nullptr && request->isActive() && !request->isCancelled()) { handler(request, this->bridge, &request->callbacks, [=, this](auto& response) mutable { // make sure the response was finished before @@ -978,7 +1001,6 @@ namespace SSC::IPC { this->setHeader("access-control-allow-origin", "*"); this->setHeader("access-control-allow-methods", "*"); this->setHeader("access-control-allow-headers", "*"); - this->setHeader("access-control-allow-credentials", "true"); for (const auto& entry : defaultHeaders) { const auto parts = split(trim(entry), ':'); @@ -1150,7 +1172,7 @@ namespace SSC::IPC { ); for (const auto& header : this->headers) { - const auto name = attachment.env->NewStringUTF(header.name.c_str()); + const auto name = attachment.env->NewStringUTF(toHeaderCase(header.name).c_str()); const auto value = attachment.env->NewStringUTF(header.value.c_str()); CallVoidClassMethodFromAndroidEnvironment( @@ -1243,7 +1265,7 @@ namespace SSC::IPC { byteArray, 0, size, - (jbyte *) bytes + (jbyte *) bytes.get() ); CallVoidClassMethodFromAndroidEnvironment( @@ -1373,21 +1395,18 @@ namespace SSC::IPC { #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #elif SOCKET_RUNTIME_PLATFORM_ANDROID if (this->platformResponse != nullptr) { - auto buffers = this->buffers; - this->request->bridge->dispatch([=, this] () { - auto app = App::sharedApplication(); - auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - - CallVoidClassMethodFromAndroidEnvironment( - attachment.env, - this->platformResponse, - "finish", - "()V" - ); + auto app = App::sharedApplication(); + auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - this->platformResponse = nullptr; - attachment.env->DeleteGlobalRef(this->platformResponse); - }); + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + this->platformResponse, + "finish", + "()V" + ); + + this->platformResponse = nullptr; + attachment.env->DeleteGlobalRef(this->platformResponse); } #else this->platformResponse = nullptr; @@ -1460,7 +1479,7 @@ namespace SSC::IPC { #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #else - return this->fail(String(error)); + //return this->fail(String(error)); #endif return this->fail("Request failed for an unknown reason"); @@ -1514,9 +1533,9 @@ namespace SSC::IPC { #endif // notify fail - if (this->request->callbacks.fail != nullptr) { - this->request->callbacks.fail(error); - } + //if (this->request->callbacks.fail != nullptr) { + //this->request->callbacks.fail(error); + //} this->finished = true; return true; diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index fdb4d966f5..bb8214c98c 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -84,11 +84,12 @@ open class SchemeHandlers (val bridge: Bridge) { val response = WebResourceResponse( mimeType, null, - null + PipedInputStream(this.stream) ) val headers = mutableMapOf<String, String>() var pendingWrites = 0 + var finished = false fun setStatus (statusCode: Int, statusText: String) { val headers = this.headers @@ -104,33 +105,39 @@ open class SchemeHandlers (val bridge: Bridge) { fun setHeader (name: String, value: String) { if (name.lowercase() == "content-type") { this.mimeType = value + this.response.setMimeType(value) } else { + this.headers.remove(name) + + if (this.response.responseHeaders != null) { + this.response.responseHeaders.remove(name) + } + this.headers += mapOf(name to value) + this.response.responseHeaders = this.headers } } fun write (bytes: ByteArray) { val stream = this.stream - console.log("begin response write") - if (this.response.data == null) { + val response = this + this.pendingWrites++ try { - this.response.data = PipedInputStream(this.stream) + console.log("before write") + stream.write(bytes, 0, bytes.size) } catch (err: Exception) { - console.log("stream.connect error: ${err.toString()}") + if (!err.message.toString().contains("closed")) { + console.error("socket.runtime.ipc.SchemeHandlers.Response: ${err.toString()}") + } } - } - try { - this.pendingWrites++ - stream.write(bytes, 0, bytes.size) - } catch (err: Exception) { - console.log("stream.write error: ${err.toString()}") - if (!err.message.toString().contains("closed")) { - console.error("socket.runtime.ipc.SchemeHandlers.Response: ${err.toString()}") - } - } - this.pendingWrites-- - console.log("end response write") + response.pendingWrites-- + console.log("after write pending=${response.pendingWrites}") + + if (response.pendingWrites == 0) { + response.finished = true + stream.close() + } } fun write (string: String) { @@ -139,14 +146,25 @@ open class SchemeHandlers (val bridge: Bridge) { fun finish () { val stream = this.stream + val response = this thread { - while (this.pendingWrites > 0) { - Thread.sleep(4) + if (!response.finished) { + console.log("waiting for writes to finish") + while (response.pendingWrites > 0) { + Thread.sleep(4) + } + + if (!response.finished) { + console.log("writes finished") + + for (entry in response.headers) { + console.log("${entry.key}: ${entry.value}") + } + + stream.flush() + stream.close() + } } - - stream.flush() - stream.close() - console.log("response closed") } } } @@ -155,12 +173,25 @@ open class SchemeHandlers (val bridge: Bridge) { val request = Request(this.bridge, webResourceRequest) if (this.handleRequest(this.bridge.index, request)) { - return request.getWebResourceResponse() + val response = request.getWebResourceResponse() + response?.responseHeaders = mapOf( + "Access-Control-Allow-Origin" to "*", + "Access-Control-Allow-Headers" to "*", + "Access-Control-Allow-Methods" to "*" + ) + return response } return null } + fun hasHandlerForScheme (scheme: String): Boolean { + return this.hasHandlerForScheme(this.bridge.index, scheme) + } + @Throws(Exception::class) external fun handleRequest (index: Int, request: Request): Boolean + + @Throws(Exception::class) + external fun hasHandlerForScheme (index: Int, scheme: String): Boolean } From 6b4f913f7cdbe98c2d9382eba73a80473cf38c1d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 11 Jun 2024 21:28:25 +0200 Subject: [PATCH 0804/1178] refactor(): clean up --- src/cli/cli.cc | 2 +- src/platform/android/looper.cc | 1 - src/window/android.cc | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index c39287d8a4..fd1ffbd11c 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -790,7 +790,7 @@ void signalHandler (int signum) { if (appStatus == -1) { appStatus = signum; - log("App result!: " + std::to_string(signum)); + log("App result: " + std::to_string(signum)); } if (signum == SIGTERM || signum == SIGINT) { diff --git a/src/platform/android/looper.cc b/src/platform/android/looper.cc index c0efe56002..838577e3b1 100644 --- a/src/platform/android/looper.cc +++ b/src/platform/android/looper.cc @@ -7,7 +7,6 @@ namespace SSC::Android { {} void Looper::dispatch (const DispatchCallback& callback) { - // TODO(@jwerle): timeout while (!this->isReady) { msleep(2); } diff --git a/src/window/android.cc b/src/window/android.cc index cd79bcef73..271cfdb150 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -16,7 +16,6 @@ namespace SSC { const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - this->index = this->options.index; this->bridge.userConfig = options.userConfig; this->bridge.configureNavigatorMounts(); @@ -30,6 +29,7 @@ namespace SSC { this->bridge.preload = IPC::createPreload({ .clientId = this->bridge.id, + .index = options.index, .userScript = options.userScript }); From dbd9cdcb6bf4a72927fc51351d16ae1bcb0e63e5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 11 Jun 2024 19:11:33 -0400 Subject: [PATCH 0805/1178] refactor(core,ipc,window,serviceworker): prempt for client refactor to fix user scripts --- src/core/core.hh | 1 + src/core/unique_client.hh | 13 +++++++++++++ src/ipc/bridge.cc | 4 ++-- src/ipc/bridge.hh | 2 ++ src/ipc/client.hh | 9 ++++----- src/ipc/preload.cc | 2 +- src/ipc/scheme_handlers.cc | 3 +-- src/serviceworker/container.cc | 6 +++--- src/serviceworker/container.hh | 6 +----- src/window/apple.mm | 1 + 10 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 src/core/unique_client.hh diff --git a/src/core/core.hh b/src/core/core.hh index e732a309ab..43bc0ffde8 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -18,6 +18,7 @@ #include "socket.hh" #include "post.hh" #include "resource.hh" +#include "unique_client.hh" #include "url.hh" #include "version.hh" #include "webview.hh" diff --git a/src/core/unique_client.hh b/src/core/unique_client.hh new file mode 100644 index 0000000000..8c6697278b --- /dev/null +++ b/src/core/unique_client.hh @@ -0,0 +1,13 @@ +#ifndef SOCKET_RUNTIME_UNIQUE_CLIENT_H +#define SOCKET_RUNTIME_UNIQUE_CLIENT_H + +#include "../platform/platform.hh" + +namespace SSC { + struct UniqueClient { + using ID = uint64_t; + ID id = 0; + int index = 0; + }; +} +#endif diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 36cdb760ed..2e372be81a 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -669,7 +669,7 @@ export default module)S"; request->query, request->headers, ServiceWorkerContainer::FetchBody { request->body.size, request->body.bytes }, - ServiceWorkerContainer::Client { request->client.id, window->index } + request->client }; const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback, response] (auto res) mutable { @@ -966,7 +966,7 @@ export default module)S"; request->query, request->headers, ServiceWorkerContainer::FetchBody { request->body.size, request->body.bytes }, - ServiceWorkerContainer::Client { request->client.id, window->index } + request->client }; const auto fetched = this->navigator.serviceWorker.fetch(fetch, [request, callback] (auto res) mutable { diff --git a/src/ipc/bridge.hh b/src/ipc/bridge.hh index a0eee75467..e6ce056776 100644 --- a/src/ipc/bridge.hh +++ b/src/ipc/bridge.hh @@ -4,6 +4,7 @@ #include "../core/core.hh" #include "../core/webview.hh" +#include "client.hh" #include "preload.hh" #include "navigator.hh" #include "router.hh" @@ -37,6 +38,7 @@ namespace SSC::IPC { Map userConfig; SharedPointer<Core> core = nullptr; + Client client = {}; uint64_t id = 0; #if SOCKET_RUNTIME_PLATFORM_ANDROID diff --git a/src/ipc/client.hh b/src/ipc/client.hh index 0fc25bc3ff..5a3ac96958 100644 --- a/src/ipc/client.hh +++ b/src/ipc/client.hh @@ -1,14 +1,13 @@ #ifndef SOCKET_RUNTIME_IPC_CLIENT_H #define SOCKET_RUNTIME_IPC_CLIENT_H -#include "../core/core.hh" +#include "../core/unique_client.hh" #include "preload.hh" namespace SSC::IPC { - struct Client { - using ID = uint64_t; - ID id = 0; - IPC::Preload preload; + struct Client : public UniqueClient { + using ID = Client::ID; + IPC::Preload preload = {}; }; } #endif diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 1f492fb02b..12ab4e8daa 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -399,7 +399,7 @@ namespace SSC::IPC { } } - // 13. compile "internal init" import + // 13. compile "internal init" import if (this->options.features.useHTMLMarkup && this->options.features.useESM) { buffers.push_back(tmpl( R"JAVASCRIPT( diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 1bfde88334..c7c3fe4e72 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -628,8 +628,7 @@ namespace SSC::IPC { } ); - // default client id, can be overloaded with 'runtime-client-id' header - this->request->client.id = handlers->bridge->id; + this->request->client = handlers->bridge->client; // build request URL components from parsed URL components this->request->originalURL = this->absoluteURL; diff --git a/src/serviceworker/container.cc b/src/serviceworker/container.cc index 76319dcb5e..e8b2907b16 100644 --- a/src/serviceworker/container.cc +++ b/src/serviceworker/container.cc @@ -430,9 +430,10 @@ namespace SSC { (html.find("<script") != String::npos || html.find("<SCRIPT") != String::npos) ) ) { - auto preload = IPC::Preload(this->bridge->preload.options); - preload.metadata["runtime-frame-source"] = "serviceworker"; + auto preloadOptions = this->bridge->preload.options; + preloadOptions.metadata["runtime-frame-source"] = "serviceworker"; + auto preload = IPC::createPreload(preloadOptions); auto begin = String("<meta name=\"begin-runtime-preload\">"); auto end = String("<meta name=\"end-runtime-preload\">"); auto x = html.find(begin); @@ -442,7 +443,6 @@ namespace SSC { html.erase(x, (y - x) + end.size()); } - preload.compile(); html = preload.insertIntoHTML(html, { .protocolHandlerSchemes = this->protocols.getSchemes() }); diff --git a/src/serviceworker/container.hh b/src/serviceworker/container.hh index 11ad8fff63..7194f707d5 100644 --- a/src/serviceworker/container.hh +++ b/src/serviceworker/container.hh @@ -15,11 +15,7 @@ namespace SSC { class ServiceWorkerContainer { public: using ID = IPC::Client::ID; - - struct Client { - ID id = 0; - int index = 0; - }; + using Client = IPC::Client; struct RegistrationOptions { enum class Type { Classic, Module }; diff --git a/src/window/apple.mm b/src/window/apple.mm index 09478aa0d7..540ee91c7a 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -215,6 +215,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { this->bridge.preload = IPC::createPreload({ .clientId = this->bridge.id, + .index = options.index, .userScript = options.userScript }); From 911dd377a273cb1b8d85bb940b7861d8ecac7630 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 12:24:14 +0200 Subject: [PATCH 0806/1178] refactor(core,ipc,window,serviceworker): simplify wrangling options --- src/app/app.cc | 8 +- src/app/app.hh | 6 - src/cli/cli.cc | 27 ++-- src/core/core.cc | 14 ++- src/core/core.hh | 3 +- src/core/options.hh | 14 +++ src/desktop/extension/linux.cc | 3 +- src/desktop/main.cc | 4 +- src/ipc/bridge.cc | 18 ++- src/ipc/bridge.hh | 19 ++- src/ipc/client.hh | 3 +- src/ipc/preload.cc | 18 +-- src/ipc/preload.hh | 220 +++++++++++++++++++-------------- src/ipc/routes.cc | 2 +- src/ipc/scheme_handlers.hh | 2 +- src/platform/types.hh | 1 + src/serviceworker/container.cc | 7 +- src/window/android.cc | 6 +- src/window/apple.mm | 2 +- src/window/linux.cc | 11 +- src/window/manager.cc | 16 +-- src/window/options.hh | 162 ------------------------ src/window/win.cc | 2 +- src/window/window.hh | 169 +++++++++++++++++++++++-- 24 files changed, 400 insertions(+), 337 deletions(-) create mode 100644 src/core/options.hh delete mode 100644 src/window/options.hh diff --git a/src/app/app.cc b/src/app/app.cc index acfd8437e2..745f0e73fc 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -205,7 +205,7 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType self.app->windowManager.configure(windowManagerOptions); - auto defaultWindow = self.app->windowManager.createDefaultWindow(WindowOptions { + auto defaultWindow = self.app->windowManager.createDefaultWindow(Window::Options { .shouldExitApplicationOnClose = true }); @@ -218,7 +218,7 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType self.app->userConfig["webview_service_worker_mode"] != "hybrid" && self.app->userConfig["permissions_allow_service_worker"] != "false" ) { - auto serviceWorkerWindowOptions = WindowOptions {}; + auto serviceWorkerWindowOptions = Window::Options {}; auto serviceWorkerUserConfig = self.app->userConfig; const auto screen = defaultWindow->getScreenSize(); @@ -1063,7 +1063,7 @@ extern "C" { app->windowManager.configure(windowManagerOptions); app->dispatch([=]() { - auto defaultWindow = app->windowManager.createDefaultWindow(WindowOptions { + auto defaultWindow = app->windowManager.createDefaultWindow(Window::Options { .shouldExitApplicationOnClose = true }); @@ -1074,7 +1074,7 @@ extern "C" { app->userConfig["permissions_allow_service_worker"] != "false" ) { if (app->windowManager.getWindowStatus(SOCKET_RUNTIME_SERVICE_WORKER_CONTAINER_WINDOW_INDEX) == WindowManager::WINDOW_NONE) { - auto serviceWorkerWindowOptions = WindowOptions {}; + auto serviceWorkerWindowOptions = Window::Options {}; auto serviceWorkerUserConfig = app->userConfig; auto screen = defaultWindow->getScreenSize(); diff --git a/src/app/app.hh b/src/app/app.hh index f501f8a9eb..a956ae035b 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -123,12 +123,6 @@ namespace SSC { void dispatch (Function<void()>); String getcwd (); bool hasRuntimePermission (const String& permission) const; - - /* - #if SOCKET_RUNTIME_PLATFORM_ANDROID - bool isAndroidPermissionAllowed (const String& permission); - #endif - */ }; } #endif diff --git a/src/cli/cli.cc b/src/cli/cli.cc index fd1ffbd11c..694680ab11 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -1692,22 +1692,23 @@ void run (const String& targetPlatform, Map& settings, const Paths& paths, const exit(1); } -struct Option { +struct CommandLineOption { std::vector<String> aliases; bool isOptional; bool shouldHaveValue; }; -using Options = std::vector<Option>; + +using CommandLineOptions = Vector<CommandLineOption>; struct optionsAndEnv { Map optionsWithValue; std::unordered_set<String> optionsWithoutValue; - std::vector<String> envs; + Vector<String> envs; }; optionsAndEnv parseCommandLineOptions ( const std::span<const char*>& options, - const Options& availableOptions, + const CommandLineOptions& availableOptions, const String& subcommand ) { optionsAndEnv result; @@ -1755,7 +1756,7 @@ optionsAndEnv parseCommandLineOptions ( } // find option - Option recognizedOption; + CommandLineOption recognizedOption; bool found = false; for (const auto option : availableOptions) { for (const auto alias : option.aliases) { @@ -1988,7 +1989,7 @@ int main (const int argc, const char* argv[]) { auto createSubcommand = [&]( const String& subcommand, - const Options& availableOptions, + const CommandLineOptions& availableOptions, const bool& needsConfig, std::function<void(Map, std::unordered_set<String>)> subcommandHandler ) -> void { @@ -2187,7 +2188,7 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value - Options initOptions = { + CommandLineOptions initOptions = { { { "--config", "-C" }, true, false }, { { "--name", "-n" }, true, true } }; @@ -2246,7 +2247,7 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value - Options listDevicesOptions = { + CommandLineOptions listDevicesOptions = { { { "--platform" }, false, true }, { { "--ecid" }, true, false }, { { "--udid" }, true, false }, @@ -2352,7 +2353,7 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value - Options installAppOptions = { + CommandLineOptions installAppOptions = { { { "--debug", "-D" }, true, false }, { { "--device" }, true, true }, { { "--platform" }, true, true }, @@ -2571,7 +2572,7 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value - Options printBuildDirOptions = { + CommandLineOptions printBuildDirOptions = { { { "--platform" }, true, true }, { { "--root" }, true, false} }; @@ -2592,7 +2593,7 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value - Options runOptions = { + CommandLineOptions runOptions = { { { "--platform" }, true, true }, { { "--prod", "-P" }, true, false }, { { "--test", "-t" }, true, true }, @@ -2604,7 +2605,7 @@ int main (const int argc, const char* argv[]) { { { "--host"}, true, true } }; - Options buildOptions = { + CommandLineOptions buildOptions = { { { "--quiet", "-q" }, true, false }, { { "--only-build", "-o" }, true, false }, { { "--run", "-r" }, true, false }, @@ -7125,7 +7126,7 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value - Options setupOptions = { + CommandLineOptions setupOptions = { { { "--platform" }, true, true }, { { "--yes", "-y" }, true, false }, { { "--quiet", "-q" }, true, false }, diff --git a/src/core/core.cc b/src/core/core.cc index e9cfe7454a..b03091b2d0 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -387,17 +387,21 @@ namespace SSC { static constexpr auto resolution = RELEASE_STRONG_REFERENCE_SHARED_POINTER_BUFFERS_RESOLUTION; Lock lock(core->mutex); for (int i = 0; i < core->sharedPointerBuffers.size(); ++i) { - auto& entry = core->sharedPointerBuffers[i]; + auto entry = &core->sharedPointerBuffers[i]; + if (entry == nullptr) { + continue; + } + // expired - if (entry.ttl <= resolution) { - entry.pointer = nullptr; - entry.ttl = 0; + if (entry->ttl <= resolution) { + entry->pointer = nullptr; + entry->ttl = 0; if (i == core->sharedPointerBuffers.size() - 1) { core->sharedPointerBuffers.pop_back(); break; } } else { - entry.ttl = entry.ttl - resolution; + entry->ttl = entry->ttl - resolution; } } diff --git a/src/core/core.hh b/src/core/core.hh index 43bc0ffde8..8219b14ebf 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -15,9 +15,10 @@ #include "ip.hh" #include "json.hh" #include "module.hh" -#include "socket.hh" +#include "options.hh" #include "post.hh" #include "resource.hh" +#include "socket.hh" #include "unique_client.hh" #include "url.hh" #include "version.hh" diff --git a/src/core/options.hh b/src/core/options.hh new file mode 100644 index 0000000000..37bf17ee8f --- /dev/null +++ b/src/core/options.hh @@ -0,0 +1,14 @@ +#ifndef SOCKET_RUNTIME_CORE_OPTIONS_H +#define SOCKET_RUNTIME_CORE_OPTIONS_H + +#include "../platform/types.hh" + +namespace SSC { + struct Options { + template <typename T> const T& as () const { + static_assert(std::is_base_of<Options, T>::value); + return *reinterpret_cast<const T*>(this); + } + }; +} +#endif diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc index b75f78e688..49640fe167 100644 --- a/src/desktop/extension/linux.cc +++ b/src/desktop/extension/linux.cc @@ -28,7 +28,8 @@ extern "C" { if (bridge == nullptr) { g_object_ref(context); - bridge = std::make_shared<IPC::Bridge>(app->core, app->userConfig); + auto options = IPC::Bridge::Options(app->userConfig); + bridge = std::make_shared<IPC::Bridge>(app->core, options); bridge->evaluateJavaScriptFunction = [context] (const auto source) { auto _ = jsc_context_evaluate(context, source.c_str(), source.size()); }; diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 0dbadc1797..961fe95f3d 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -1075,7 +1075,7 @@ MAIN { auto isMinimizable = getProperty("window_minimizable"); auto isClosable = getProperty("window_closable"); - auto defaultWindow = app.windowManager.createDefaultWindow(WindowOptions { + auto defaultWindow = app.windowManager.createDefaultWindow(Window::Options { .minimizable = (isMinimizable == "" || isMinimizable == "true") ? true : false, .maximizable = (isMaximizable == "" || isMaximizable == "true") ? true : false, .resizable = getProperty("window_resizable") == "false" ? false : true, @@ -1093,7 +1093,7 @@ MAIN { userConfig["webview_service_worker_mode"] != "hybrid" && userConfig["permissions_allow_service_worker"] != "false" ) { - auto serviceWorkerWindowOptions = WindowOptions {}; + auto serviceWorkerWindowOptions = Window::Options {}; auto serviceWorkerUserConfig = userConfig; auto screen = defaultWindow->getScreenSize(); diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 2e372be81a..ced88d0d64 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -154,10 +154,18 @@ export default module)S"; } } #endif - - Bridge::Bridge (SharedPointer<Core> core, Map userConfig) - : core(core), - userConfig(userConfig), + Bridge::Options::Options ( + const Map& userConfig, + const Preload::Options& preload + ) : userConfig(userConfig), + preload(preload) + {} + + Bridge::Bridge ( + SharedPointer<Core> core, + const Options& options + ) : core(core), + userConfig(options.userConfig), router(this), navigator(this), schemeHandlers(this) @@ -644,7 +652,7 @@ export default module)S"; if (resource.mimeType() != "text/html") { response.send(resource); } else { - const auto html = this->preload.insertIntoHTML(resource.str(), { + const auto html = this->client.preload.insertIntoHTML(resource.str(), { .protocolHandlerSchemes = this->navigator.serviceWorker.protocols.getSchemes() }); diff --git a/src/ipc/bridge.hh b/src/ipc/bridge.hh index e6ce056776..a4d395fd8e 100644 --- a/src/ipc/bridge.hh +++ b/src/ipc/bridge.hh @@ -2,6 +2,7 @@ #define SOCKET_RUNTIME_IPC_BRIDGE_H #include "../core/core.hh" +#include "../core/options.hh" #include "../core/webview.hh" #include "client.hh" @@ -18,6 +19,15 @@ namespace SSC::IPC { using DispatchCallback = Function<void()>; using DispatchFunction = Function<void(DispatchCallback)>; + struct Options : public SSC::Options { + Map userConfig = {}; + Preload::Options preload; + Options ( + const Map& userConfig = {}, + const Preload::Options& preload = {} + ); + }; + static Vector<Bridge*> getInstances(); const CoreNetworkStatus::Observer networkStatusObserver; @@ -31,24 +41,23 @@ namespace SSC::IPC { DispatchFunction dispatchFunction = nullptr; Bluetooth bluetooth; + Client client = {}; + Map userConfig; Navigator navigator; - SchemeHandlers schemeHandlers; - Preload preload; Router router; - Map userConfig; + SchemeHandlers schemeHandlers; SharedPointer<Core> core = nullptr; - Client client = {}; uint64_t id = 0; #if SOCKET_RUNTIME_PLATFORM_ANDROID bool isAndroidEmulator = false; #endif + Bridge (SharedPointer<Core>core, const Options& options); Bridge () = delete; Bridge (const Bridge&) = delete; Bridge (Bridge&&) = delete; - Bridge (SharedPointer<Core>core, Map userConfig); ~Bridge (); Bridge& operator = (const Bridge&) = delete; diff --git a/src/ipc/client.hh b/src/ipc/client.hh index 5a3ac96958..6db3f1b66d 100644 --- a/src/ipc/client.hh +++ b/src/ipc/client.hh @@ -6,7 +6,8 @@ namespace SSC::IPC { struct Client : public UniqueClient { - using ID = Client::ID; + using ID = UniqueClient::ID; + ID id = 0; IPC::Preload preload = {}; }; } diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 12ab4e8daa..b8e2d1a026 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -35,7 +35,13 @@ namespace SSC::IPC { R"HTML(</script>)HTML" ); - Preload::Preload (const PreloadOptions& options) + const Preload Preload::compile (const Options& options) { + auto preload = Preload(options); + preload.compile(); + return preload; + } + + Preload::Preload (const Options& options) : options(options) { this->configure(); @@ -48,7 +54,7 @@ namespace SSC::IPC { this->configure(this->options); } - void Preload::configure (const PreloadOptions& options) { + void Preload::configure (const Options& options) { this->options = options; this->headers = options.headers; this->metadata = options.metadata; @@ -236,7 +242,7 @@ namespace SSC::IPC { )JAVASCRIPT", Map { {"id", std::to_string(rand64())}, - {"clientId", std::to_string(this->options.clientId)} + {"clientId", std::to_string(this->options.client.id)} } ))); @@ -663,10 +669,4 @@ namespace SSC::IPC { return output; } - - const Preload createPreload (const PreloadOptions& options) { - auto preload = Preload(options); - preload.compile(); - return preload; - } } diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh index f788fb9a11..74a85362ee 100644 --- a/src/ipc/preload.hh +++ b/src/ipc/preload.hh @@ -2,125 +2,163 @@ #define SOCKET_RUNTIME_IPC_PRELOAD_H #include "../platform/platform.hh" +#include "../core/unique_client.hh" +#include "../core/options.hh" #include "../core/headers.hh" #include "../core/config.hh" namespace SSC::IPC { /** - * `PreloadFeatures` is a container for a variety of ways of configuring the - * compiler features for the compiled preload that is "injeced" into HTML - * documents. + * `Preload` is a container for state to compile a "preload script" attached + * to an IPC bridge that is injected into HTML documents loaded into a WebView */ - struct PreloadFeatures { - /** - * If `true`, the feature enables global CommonJS values such as `module`, - * `exports`, `require`, `__filename`, and `__dirname`. - */ - bool useGlobalCommonJS = true; + class Preload { + String compiled = ""; + public: + + /** + * `Options` is an input container for configuring `Preload` metadata, + * headers, environment variable values, preload compiler features, and more. + */ + struct Options : SSC::Options { + /** + * `Features` is a container for a variety of ways of configuring the + * compiler features for the compiled preload that is "injeced" into HTML + * documents. + */ + struct Features { + /** + * If `true`, the feature enables global CommonJS values such as `module`, + * `exports`, `require`, `__filename`, and `__dirname`. + */ + bool useGlobalCommonJS = true; + + /** + * If `true`, the feature enables global Node.js values like + * `process` and `global`. + */ + bool useGlobalNodeJS = true; + + /** + * If `true`, the feature enables the automatic import of a "test script" + * that is specified with the `--test <path_relative_to_resources>` command + * line argument. This is useful for running tests + */ + bool useTestScript = false; + + /** + * If `true`, the feature enables the compiled preload to use HTML markup + * such as `<script>` and `<meta>` tags for injecting JavaScript, metadata + * and HTTP headers. If this feature is `false`, then the preload compiler + * will not include metadata or HTTP headers in `<meta>` tags nor will the + * JavaScript compiled in the preload be "wrapped" by a `<script>` tag. + */ + bool useHTMLMarkup = true; + + /** + * If `true`, the feature enables the preload compiler to use ESM import + * syntax instead of "dynamic" import for importing the "internal init" + * module or running a "user script" + */ + bool useESM = true; + + /** + * If `true`, the feature enables the preload compiler to use include the + * a global `__args` object available on `globalThis`. + */ + bool useGlobalArgs = true; + }; + + bool headless = false; + bool debug = false; + + Features features; + + UniqueClient client; + int index = 0; + + Headers headers = {}; // depends on 'features.useHTMLMarkup' + Map metadata; // depends on 'features.useHTMLMarkup' + Map env; + Map userConfig; + Vector<String> argv; + + String userScript = ""; + + // only use if you know what you are doing + String RUNTIME_PRIMORDIAL_OVERRIDES = ""; + }; + + struct InsertIntoHTMLOptions : public Options { + Vector<String> protocolHandlerSchemes; + }; /** - * If `true`, the feature enables global Node.js values like - * `process` and `global`. + * Creates and compiles preload from given options. */ - bool useGlobalNodeJS = true; + static const Preload compile (const Options& options); /** - * If `true`, the feature enables the automatic import of a "test script" - * that is specified with the `--test <path_relative_to_resources>` command - * line argument. This is useful for running tests + * The options used to configure this preload. */ - bool useTestScript = false; + Options options; /** - * If `true`, the feature enables the compiled preload to use HTML markup - * such as `<script>` and `<meta>` tags for injecting JavaScript, metadata - * and HTTP headers. If this feature is `false`, then the preload compiler - * will not include metadata or HTTP headers in `<meta>` tags nor will the - * JavaScript compiled in the preload be "wrapped" by a `<script>` tag. + * The current preload buffer, prior to compilation. */ - bool useHTMLMarkup = true; + Vector<String> buffer; /** - * If `true`, the feature enables the preload compiler to use ESM import - * syntax instead of "dynamic" import for importing the "internal init" - * module or running a "user script" + * A mapping of key-value HTTP headers to be injected as HTML `<meta>` + * tags into the preload output. + * This depends on the 'useHTMLMarkup' feature. */ - bool useESM = true; + Headers headers = {}; /** - * If `true`, the feature enables the preload compiler to use include the - * a global `__args` object available on `globalThis`. + * A mapping of key-value metadata to be injected as HTML `<meta>` tags + * into the preload output. + * This depends on the 'useHTMLMarkup' feature. */ - bool useGlobalArgs = true; - }; + Map metadata; - /** - * `PreloadOptions` is an input container for configuring `Preload` metadata, - * headers, environment variable values, preload compiler features, and more. - */ - struct PreloadOptions { - bool headless = false; - bool debug = false; - - PreloadFeatures features; - - uint64_t clientId = 0; - int index = 0; - - Headers headers = {}; // depends on 'features.useHTMLMarkup' - Map metadata; // depends on 'features.useHTMLMarkup' - Map env; - Map userConfig; - Vector<String> argv; + Preload () = default; + Preload (const Options& options); - String userScript = ""; + /** + * Appends source to the preload, prior to compilation. + */ + Preload& append (const String& source); - // only use if you know what you are doing - String RUNTIME_PRIMORDIAL_OVERRIDES = ""; - }; + /** + * Configures the preload from default options. + */ + void configure (); - /** - * `Preload` is a container for state to compile a "preload script" attached - * to an IPC bridge that is injected into HTML documents loaded into a WebView - */ - class Preload { - String compiled = ""; - public: - struct InsertIntoHTMLOptions { - Vector<String> protocolHandlerSchemes; - }; + /** + * Configures the preload from given options. + */ + void configure (const Options& options); - PreloadOptions options; - Vector<String> buffer; + /** + * Compiles the preload and returns a reference to the + * compiled string. + */ + const String& compile (); - /** - * A mapping of key-value HTTP headers to be injected as HTML `<meta>` - * tags into the preload output. - * This depends on the 'useHTMLMarkup' feature. - */ - Headers headers = {}; + /** + * Get a reference to the currently compiled preload string + */ + const String& str () const; - /** - * A mapping of key-value metadata to be injected as HTML `<meta>` tags - * into the preload output. - * This depends on the 'useHTMLMarkup' feature. - */ - Map metadata; - - Preload () = default; - Preload (const PreloadOptions& options); - Preload& append (const String& source); - - void configure (); - void configure (const PreloadOptions& options); - const String& compile (); - const String& str () const; - const String insertIntoHTML ( - const String& html, - const InsertIntoHTMLOptions& options - ) const; + /** + * Inserts and returns compiled preload into an HTML string with + * insert options + */ + const String insertIntoHTML ( + const String& html, + const InsertIntoHTMLOptions& options + ) const; }; - - const Preload createPreload (const PreloadOptions& options); } #endif diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index c17f743b9f..f4c0832acf 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -3099,7 +3099,7 @@ static void mapIPCRoutes (Router *router) { const auto window = app->windowManager.getWindow(0); const auto screen = window->getScreenSize(); - auto options = WindowOptions {}; + auto options = Window::Options {}; options.shouldExitApplicationOnClose = message.get("shouldExitApplicationOnClose") == "true" ? true : false; options.headless = app->userConfig["build_headless"] == "true"; diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 694e40a343..a82e318ef7 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -114,7 +114,7 @@ namespace SSC::IPC { SharedPointer<Request> build (); }; - uint64_t id = rand64(); + Client::ID id = rand64(); String scheme = ""; String method = "GET"; String hostname = ""; diff --git a/src/platform/types.hh b/src/platform/types.hh index f2104ba612..f50c23174f 100644 --- a/src/platform/types.hh +++ b/src/platform/types.hh @@ -12,6 +12,7 @@ #include <sstream> #include <string> #include <thread> +#include <type_traits> #include <vector> #if defined(__APPLE__) diff --git a/src/serviceworker/container.cc b/src/serviceworker/container.cc index e8b2907b16..8f9ef0a599 100644 --- a/src/serviceworker/container.cc +++ b/src/serviceworker/container.cc @@ -385,10 +385,7 @@ namespace SSC { {"message", "Callback 'id' given in parameters does not have a 'FetchRequest'"} }}); } - } while (0); - do { - Lock lock(this->mutex); callback = this->fetchCallbacks.at(id); request = this->fetchRequests.at(id); } while (0); @@ -430,10 +427,10 @@ namespace SSC { (html.find("<script") != String::npos || html.find("<SCRIPT") != String::npos) ) ) { - auto preloadOptions = this->bridge->preload.options; + auto preloadOptions = request.client.preload.options; preloadOptions.metadata["runtime-frame-source"] = "serviceworker"; - auto preload = IPC::createPreload(preloadOptions); + auto preload = IPC::Preload::compile(preloadOptions); auto begin = String("<meta name=\"begin-runtime-preload\">"); auto end = String("<meta name=\"end-runtime-preload\">"); auto x = html.find(begin); diff --git a/src/window/android.cc b/src/window/android.cc index 271cfdb150..f3e41f6c9a 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -6,10 +6,10 @@ using namespace SSC; namespace SSC { - Window::Window (SharedPointer<Core> core, const WindowOptions& options) + Window::Window (SharedPointer<Core> core, const Window::Options& options) : options(options), core(core), - bridge(core, options.userConfig), + bridge(core, options.as<IPC::Bridge::Options>()), hotkey(this), dialog(this) { @@ -28,7 +28,7 @@ namespace SSC { }; this->bridge.preload = IPC::createPreload({ - .clientId = this->bridge.id, + .client = this->bridge.client, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/apple.mm b/src/window/apple.mm index 540ee91c7a..a79bf9f6e5 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -171,7 +171,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { @end namespace SSC { - Window::Window (SharedPointer<Core> core, const WindowOptions& options) + Window::Window (SharedPointer<Core> core, const Window::Options& options) : core(core), options(options), bridge(core, options.userConfig), diff --git a/src/window/linux.cc b/src/window/linux.cc index 51500a9896..e05fa1a38c 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -109,10 +109,13 @@ namespace SSC { isInitialized = true; } - Window::Window (SharedPointer<Core> core, const WindowOptions& options) + Window::Window (SharedPointer<Core> core, const Window::Options& options) : core(core), options(options), - bridge(core, options.userConfig), + bridge(core, IPC::Bridge::Options { + options.userConfig, + options.as<IPC::Preload::Options>() + }), hotkey(this), dialog(this) { @@ -234,8 +237,8 @@ namespace SSC { this->eval(source); }; - this->bridge.preload = IPC::createPreload({ - .clientId = this->bridge.id, + this->bridge.client.preload = IPC::Preload::compile({ + .client = this->bridge.client, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/manager.cc b/src/window/manager.cc index 7bca57b6aa..6224084d4e 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -77,19 +77,19 @@ namespace SSC { } SharedPointer<WindowManager::ManagedWindow> WindowManager::WindowManager::getOrCreateWindow (int index) { - return this->getOrCreateWindow(index, WindowOptions {}); + return this->getOrCreateWindow(index, Window::Options {}); } SharedPointer<WindowManager::ManagedWindow> WindowManager::WindowManager::getOrCreateWindow ( int index, - const WindowOptions& options + const Window::Options& options ) { if (this->destroyed || index < 0 || index >= this->windows.size()) { return nullptr; } if (this->getWindowStatus(index) == WindowStatus::WINDOW_NONE) { - WindowOptions optionsCopy = options; + Window::Options optionsCopy = options; optionsCopy.index = index; return this->createWindow(optionsCopy); } @@ -135,7 +135,7 @@ namespace SSC { } SharedPointer<WindowManager::ManagedWindow> WindowManager::createWindow ( - const WindowOptions& options + const Window::Options& options ) { Lock lock(this->mutex); @@ -172,7 +172,7 @@ namespace SSC { ? Window::getSizeInPixels(this->options.defaultMaxHeight, screen.height) : options.maxHeight; - WindowOptions windowOptions = { + Window::Options windowOptions = { .minimizable = options.minimizable, .maximizable = options.maximizable, .resizable = options.resizable, @@ -245,9 +245,9 @@ namespace SSC { return this->windows.at(options.index); } - SharedPointer<WindowManager::ManagedWindow> WindowManager::createDefaultWindow (const WindowOptions& options) { + SharedPointer<WindowManager::ManagedWindow> WindowManager::createDefaultWindow (const Window::Options& options) { static const auto devHost = SSC::getDevHost(); - auto windowOptions = WindowOptions { + auto windowOptions = Window::Options { .minimizable = options.minimizable, .maximizable = options.maximizable, .resizable = options.resizable, @@ -306,7 +306,7 @@ namespace SSC { WindowManager::ManagedWindow::ManagedWindow ( WindowManager &manager, SharedPointer<Core> core, - const WindowOptions& options + const Window::Options& options ) : index(options.index), Window(core, options), manager(manager) diff --git a/src/window/options.hh b/src/window/options.hh deleted file mode 100644 index 8f5b1d349c..0000000000 --- a/src/window/options.hh +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef SOCKET_RUNTIME_WINDOW_OPTIONS_H -#define SOCKET_RUNTIME_WINDOW_OPTIONS_H - -#include "../core/config.hh" -#include "../ipc/preload.hh" - -namespace SSC { - /** - * `WindowOptions` is an extended `IPC::PreloadOptions` container for - * configuring a new `Window`. - */ - struct WindowOptions : public IPC::PreloadOptions { - /** - * If `true`, the window can be minimized. - * This option value is only supported on desktop. - */ - bool minimizable = true; - - /** - * If `true`, the window can be maximized. - * This option value is only supported on desktop. - */ - bool maximizable = true; - - /** - * If `true`, the window can be resized. - * This option value is only supported on desktop. - */ - bool resizable = true; - - /** - * If `true`, the window can be closed. - * This option value is only supported on desktop. - */ - bool closable = true; - - /** - * If `true`, the window can be "frameless". - * This option value is only supported on desktop. - */ - bool frameless = false; - - /** - * If `true`, the window is considered a "utility" window. - * This option value is only supported on desktop. - */ - bool utility = false; - - /** - * If `true`, the window, when the window is "closed", it can - * exit the application. - * This option value is only supported on desktop. - */ - bool shouldExitApplicationOnClose = false; - - /** - * The maximum height in screen pixels the window can be. - */ - float maxHeight = 0.0; - - /** - * The minimum height in screen pixels the window can be. - */ - float minHeight = 0.0; - - /** - * The absolute height in screen pixels the window can be. - */ - float height = 0.0; - - /** - * The maximum width in screen pixels the window can be. - */ - float maxWidth = 0.0; - - /** - * The minimum width in screen pixels the window can be. - */ - float minWidth = 0.0; - - /** - * The absolute width in screen pixels the window can be. - */ - float width = 0.0; - - /** - * The window border/corner radius. - * This option value is only supported on macOS. - */ - float radius = 0.0; - - /** - * Thw window frame margin. - * This option value is only supported on macOS. - */ - float margin = 0.0; - - /** - * A string (split on ':') provides two float values which will - * set the window's aspect ratio. - * This option value is only supported on desktop. - */ - String aspectRatio = ""; - - /** - * A string that describes a style for the window title bar. - * Valid values are: - * - hidden - * - hiddenInset - * This option value is only supported on macOS and Windows. - */ - String titlebarStyle = ""; - - /** - * A string value (split on 'x') in the form of `XxY` where - * - `X` is the value in screen pixels offset from the left of the - * window frame - * - `Y` is the value in screen pixels offset from the top of the - * window frame - * The values control the offset of the "close", "minimize", and "maximize" - * button controls for a window. - * This option value is only supported on macOS. - */ - String windowControlOffsets = ""; - - /** - * A string value in the form of `rgba(r, g, b, a)` where - * - `r` is the "red" channel value, an integer between `0` and `255` - * - `g` is the "green" channel value, an integer between `0` and `255` - * - `b` is the "blue" channel value, an integer between `0` and `255` - * - `a` is the "alpha" channel value, a float between `0` and `1` - * The values represent the background color of a window when the platform - * system theme is in "light mode". This also be the "default" theme. - */ - String backgroundColorLight = ""; - - /** - * A string value in the form of `rgba(r, g, b, a)` where - * - `r` is the "red" channel value, an integer between `0` and `255` - * - `g` is the "green" channel value, an integer between `0` and `255` - * - `b` is the "blue" channel value, an integer between `0` and `255` - * - `a` is the "alpha" channel value, a float between `0` and `1` - * The values represent the background color of a window when the platform - * system theme is in "dark mode". - */ - String backgroundColorDark = ""; - - /** - * A callback function that is called when a "script message" is received - * from the WebVew. - */ - MessageCallback onMessage = [](const String) {}; - - /** - * A callback function that is called when the window wants to exit the - * application. This function is called _only_ when the - * `shouldExitApplicationOnClose` option is `true`. - */ - ExitCallback onExit = nullptr; - }; -} -#endif diff --git a/src/window/win.cc b/src/window/win.cc index ec7c3dd724..43cb0b9fc3 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -602,7 +602,7 @@ namespace SSC { }; }; - Window::Window (App& app, WindowOptions opts) + Window::Window (App& app, Window::Options opts) : app(app), opts(opts), hotkey(this) diff --git a/src/window/window.hh b/src/window/window.hh index 4dcbe813aa..d1aa86a4e7 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -11,7 +11,6 @@ #include "dialog.hh" #include "hotkey.hh" -#include "options.hh" #ifndef SOCKET_RUNTIME_MAX_WINDOWS #define SOCKET_RUNTIME_MAX_WINDOWS 32 @@ -102,10 +101,164 @@ namespace SSC { int height = 0; }; + /** + * `Window::Options` is an extended `IPC::Preload::Options` container for + * configuring a new `Window`. + */ + struct Options : public IPC::Preload::Options { + /** + * If `true`, the window can be minimized. + * This option value is only supported on desktop. + */ + bool minimizable = true; + + /** + * If `true`, the window can be maximized. + * This option value is only supported on desktop. + */ + bool maximizable = true; + + /** + * If `true`, the window can be resized. + * This option value is only supported on desktop. + */ + bool resizable = true; + + /** + * If `true`, the window can be closed. + * This option value is only supported on desktop. + */ + bool closable = true; + + /** + * If `true`, the window can be "frameless". + * This option value is only supported on desktop. + */ + bool frameless = false; + + /** + * If `true`, the window is considered a "utility" window. + * This option value is only supported on desktop. + */ + bool utility = false; + + /** + * If `true`, the window, when the window is "closed", it can + * exit the application. + * This option value is only supported on desktop. + */ + bool shouldExitApplicationOnClose = false; + + /** + * The maximum height in screen pixels the window can be. + */ + float maxHeight = 0.0; + + /** + * The minimum height in screen pixels the window can be. + */ + float minHeight = 0.0; + + /** + * The absolute height in screen pixels the window can be. + */ + float height = 0.0; + + /** + * The maximum width in screen pixels the window can be. + */ + float maxWidth = 0.0; + + /** + * The minimum width in screen pixels the window can be. + */ + float minWidth = 0.0; + + /** + * The absolute width in screen pixels the window can be. + */ + float width = 0.0; + + /** + * The window border/corner radius. + * This option value is only supported on macOS. + */ + float radius = 0.0; + + /** + * Thw window frame margin. + * This option value is only supported on macOS. + */ + float margin = 0.0; + + /** + * A string (split on ':') provides two float values which will + * set the window's aspect ratio. + * This option value is only supported on desktop. + */ + String aspectRatio = ""; + + /** + * A string that describes a style for the window title bar. + * Valid values are: + * - hidden + * - hiddenInset + * This option value is only supported on macOS and Windows. + */ + String titlebarStyle = ""; + + /** + * A string value (split on 'x') in the form of `XxY` where + * - `X` is the value in screen pixels offset from the left of the + * window frame + * - `Y` is the value in screen pixels offset from the top of the + * window frame + * The values control the offset of the "close", "minimize", and "maximize" + * button controls for a window. + * This option value is only supported on macOS. + */ + String windowControlOffsets = ""; + + /** + * A string value in the form of `rgba(r, g, b, a)` where + * - `r` is the "red" channel value, an integer between `0` and `255` + * - `g` is the "green" channel value, an integer between `0` and `255` + * - `b` is the "blue" channel value, an integer between `0` and `255` + * - `a` is the "alpha" channel value, a float between `0` and `1` + * The values represent the background color of a window when the platform + * system theme is in "light mode". This also be the "default" theme. + */ + String backgroundColorLight = ""; + + /** + * A string value in the form of `rgba(r, g, b, a)` where + * - `r` is the "red" channel value, an integer between `0` and `255` + * - `g` is the "green" channel value, an integer between `0` and `255` + * - `b` is the "blue" channel value, an integer between `0` and `255` + * - `a` is the "alpha" channel value, a float between `0` and `1` + * The values represent the background color of a window when the platform + * system theme is in "dark mode". + */ + String backgroundColorDark = ""; + + /** + * A callback function that is called when a "script message" is received + * from the WebVew. + */ + MessageCallback onMessage = [](const String) {}; + + /** + * A callback function that is called when the window wants to exit the + * application. This function is called _only_ when the + * `shouldExitApplicationOnClose` option is `true`. + */ + ExitCallback onExit = nullptr; + }; + /** * The options used to create this window. */ - const WindowOptions options; + const Window::Options options; /** * The "hot key" context for this window. @@ -246,7 +399,7 @@ namespace SSC { jobject androidWindowRef; #endif - Window (SharedPointer<Core> core, const WindowOptions&); + Window (SharedPointer<Core> core, const Window::Options&); ~Window (); static ScreenSize getScreenSize (); @@ -314,7 +467,7 @@ namespace SSC { } }; - struct WindowManagerOptions : WindowOptions { + struct WindowManagerOptions : Window::Options { String defaultHeight = "0"; String defaultWidth = "0"; String defaultMinWidth = "0"; @@ -357,7 +510,7 @@ namespace SSC { ManagedWindow ( WindowManager &manager, SharedPointer<Core> core, - const WindowOptions& options + const Window::Options& options ); ~ManagedWindow (); @@ -390,13 +543,13 @@ namespace SSC { SharedPointer<ManagedWindow> getWindowForBridge (const IPC::Bridge* bridge); SharedPointer<ManagedWindow> getWindowForWebView (WebView* webview);; SharedPointer<ManagedWindow> getOrCreateWindow (int index); - SharedPointer<ManagedWindow> getOrCreateWindow (int index, const WindowOptions& options); + SharedPointer<ManagedWindow> getOrCreateWindow (int index, const Window::Options& options); WindowStatus getWindowStatus (int index); void destroyWindow (int index); - SharedPointer<ManagedWindow> createWindow (const WindowOptions& options); - SharedPointer<ManagedWindow> createDefaultWindow (const WindowOptions& options); + SharedPointer<ManagedWindow> createWindow (const Window::Options& options); + SharedPointer<ManagedWindow> createDefaultWindow (const Window::Options& options); JSON::Array json (const Vector<int>& indices); }; From 97ddc2c5d376d26a8217f0ea1454ce6bd51b2242 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 12:29:24 +0200 Subject: [PATCH 0807/1178] fix(window): fix bridge init --- src/window/android.cc | 7 +++++-- src/window/apple.mm | 9 ++++++--- src/window/linux.cc | 2 -- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index f3e41f6c9a..de4780559d 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -9,7 +9,10 @@ namespace SSC { Window::Window (SharedPointer<Core> core, const Window::Options& options) : options(options), core(core), - bridge(core, options.as<IPC::Bridge::Options>()), + bridge(core, IPC::Bridge::Options { + options.userConfig, + options.as<IPC::Preload::Options>() + }), hotkey(this), dialog(this) { @@ -27,7 +30,7 @@ namespace SSC { this->eval(source); }; - this->bridge.preload = IPC::createPreload({ + this->bridge.client.preload = IPC::Preload::compile({ .client = this->bridge.client, .index = options.index, .userScript = options.userScript diff --git a/src/window/apple.mm b/src/window/apple.mm index a79bf9f6e5..d2dd2fa2e8 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -174,7 +174,10 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { Window::Window (SharedPointer<Core> core, const Window::Options& options) : core(core), options(options), - bridge(core, options.userConfig), + bridge(core, IPC::Bridge::Options { + options.userConfig, + options.as<IPC::Preload::Options>() + }), hotkey(this), dialog(this) { @@ -213,8 +216,8 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { }); }; - this->bridge.preload = IPC::createPreload({ - .clientId = this->bridge.id, + this->bridge.client.preload = IPC::Preload::compile({ + .client = this->bridge.client, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/linux.cc b/src/window/linux.cc index e05fa1a38c..256f4eca5d 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -124,8 +124,6 @@ namespace SSC { auto userConfig = options.userConfig; auto webContext = webkit_web_context_get_default(); - this->bridge.userConfig = userConfig; - if (options.index == 0) { initializeWebContextFromWindow(this); } From ac2caecb38c16ffe9088da5efd91f749ad0cb91e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 14:43:54 +0200 Subject: [PATCH 0808/1178] fix(window/dialog): fix default wildcard type for macos/ios --- src/window/dialog.cc | 18 ++++++++---------- src/window/dialog.hh | 1 - 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/window/dialog.cc b/src/window/dialog.cc index a5c462ee8d..034beded6d 100644 --- a/src/window/dialog.cc +++ b/src/window/dialog.cc @@ -175,7 +175,7 @@ namespace SSC { supertype = UTTypeVideo; prefersMedia = true; } else if (classes[0] == "*") { - supertype = UTTypeContent; + supertype = UTTypeData; } else { supertype = UTTypeCompositeContent; } @@ -200,13 +200,13 @@ namespace SSC { const auto extensions = split(parts[1], ","); for (const auto& extension : extensions) { - [contentTypes - addObjectsFromArray: [UTType - typesWithTag: @(extension.c_str()) - tagClass: UTTagClassFilenameExtension - conformingToType: supertype - ] + auto types = [UTType + typesWithTag: @(extension.c_str()) + tagClass: UTTagClassFilenameExtension + conformingToType: supertype ]; + + [contentTypes addObjectsFromArray: types]; } } } @@ -337,7 +337,7 @@ namespace SSC { const guint SELECT_RESPONSE = 0; GtkFileChooserAction action; GtkFileChooser *chooser; - GtkFileFilter *filter; // TODO(@jwerle): `gtk_file_filter_add_custom, gtk_file_chooser_add_filter` + GtkFileFilter *filter; GtkWidget *dialog; Vector<GtkFileFilter*> filters; @@ -451,9 +451,7 @@ namespace SSC { gtk_dialog_add_button(GTK_DIALOG(dialog), "Select", SELECT_RESPONSE); } - // if (FILE_DIALOG_OVERWRITE_CONFIRMATION) { gtk_file_chooser_set_do_overwrite_confirmation(chooser, true); - // } if ((!isSavePicker || allowDirectories) && allowMultiple) { gtk_file_chooser_set_select_multiple(chooser, true); diff --git a/src/window/dialog.hh b/src/window/dialog.hh index e876c53d87..14d493e89e 100644 --- a/src/window/dialog.hh +++ b/src/window/dialog.hh @@ -62,7 +62,6 @@ namespace SSC { Dialog& operator = (const Dialog&) = delete; Dialog& operator = (Dialog&&) = delete; - String showSaveFilePicker ( const FileSystemPickerOptions& options ); From b521b10e11668c295bca1febea0965e303fd49a7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 17:05:28 +0200 Subject: [PATCH 0809/1178] refactor(window): handle dark mode in dialog in extension context --- src/window/dialog.cc | 87 ++++++++++++++++++++++++++------------------ src/window/dialog.hh | 1 + src/window/linux.cc | 7 ++-- src/window/window.hh | 2 + 4 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/window/dialog.cc b/src/window/dialog.cc index 034beded6d..62e27dc14f 100644 --- a/src/window/dialog.cc +++ b/src/window/dialog.cc @@ -68,6 +68,7 @@ namespace SSC { const FileSystemPickerOptions& options ) { const auto results = this->showFileSystemPicker({ + .prefersDarkMode = options.prefersDarkMode, .directories = false, .multiple = false, .files = true, @@ -90,6 +91,7 @@ namespace SSC { const FileSystemPickerOptions& options ) { return this->showFileSystemPicker({ + .prefersDarkMode = options.prefersDarkMode, .directories = false, .multiple = options.multiple, .files = true, @@ -105,6 +107,7 @@ namespace SSC { const FileSystemPickerOptions& options ) { return this->showFileSystemPicker({ + .prefersDarkMode = options.prefersDarkMode, .directories = true, .multiple = options.multiple, .files = false, @@ -150,7 +153,7 @@ namespace SSC { // <mime>:<ext>,<ext>|<mime>:<ext>|... for (const auto& contentTypeSpec : split(options.contentTypes, "|")) { const auto parts = split(contentTypeSpec, ":"); - const auto mime = parts[0]; + const auto mime = trim(parts[0]); const auto classes = split(mime, "/"); UTType* supertype = nullptr; @@ -355,9 +358,40 @@ namespace SSC { return FALSE; }; + g_object_set( + gtk_settings_get_default(), + "gtk-application-prefer-dark-theme", + options.prefersDarkMode, + nullptr + ); + + if (isSavePicker) { + action = GTK_FILE_CHOOSER_ACTION_SAVE; + } else { + action = GTK_FILE_CHOOSER_ACTION_OPEN; + } + + if (!allowFiles && allowDirectories) { + action = (GtkFileChooserAction) (action | GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + } + + String dialogTitle = isSavePicker ? "Save File" : "Open File"; + if (title.size() > 0) { + dialogTitle = title; + } + + dialog = gtk_file_chooser_dialog_new( + dialogTitle.c_str(), + nullptr, + action, + "_Cancel", + GTK_RESPONSE_CANCEL, + nullptr + ); + for (const auto& contentTypeSpec : split(options.contentTypes, "|")) { const auto parts = split(contentTypeSpec, ":"); - const auto mime = parts[0]; + const auto mime = trim(parts[0]); const auto classes = split(mime, "/"); // malformed MIME @@ -366,6 +400,7 @@ namespace SSC { } auto filter = gtk_file_filter_new(); + Set<String> filterExtensionPatterns; #define MAKE_FILTER(userData) \ gtk_file_filter_add_custom( \ @@ -403,40 +438,21 @@ namespace SSC { extension ); + filterExtensionPatterns.insert(pattern); + gtk_file_filter_add_pattern(filter, pattern.c_str()); } } + gtk_file_filter_set_name( + filter, + join(filterExtensionPatterns, ", ").c_str() + ); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); filters.push_back(filter); } - if (isSavePicker) { - action = GTK_FILE_CHOOSER_ACTION_SAVE; - } else { - action = GTK_FILE_CHOOSER_ACTION_OPEN; - } - - if (!allowFiles && allowDirectories) { - action = (GtkFileChooserAction) (action | GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); - } - - gtk_init_check(nullptr, nullptr); - - String dialogTitle = isSavePicker ? "Save File" : "Open File"; - if (title.size() > 0) { - dialogTitle = title; - } - - dialog = gtk_file_chooser_dialog_new( - dialogTitle.c_str(), - nullptr, - action, - "_Cancel", - GTK_RESPONSE_CANCEL, - nullptr - ); - chooser = GTK_FILE_CHOOSER(dialog); if (!allowDirectories) { @@ -477,12 +493,10 @@ namespace SSC { } } + // FIXME(@jwerle, @heapwolf): hitting 'ESC' or cancelling the dialog + // causes 'assertion 'G_IS_OBJECT (object)' failed' messages guint response = gtk_dialog_run(GTK_DIALOG(dialog)); - for (const auto& filter : filters) { - g_object_unref(filter); - } - filters.clear(); if (response != GTK_RESPONSE_ACCEPT && response != SELECT_RESPONSE) { @@ -499,13 +513,14 @@ namespace SSC { GSList* filenames = gtk_file_chooser_get_filenames(chooser); for (int i = 0; filenames != nullptr; ++i) { - const auto file = (const char*) filenames->data; - paths.push_back(file); + const auto filename = (const char*) filenames->data; + paths.push_back(filename); + g_free(const_cast<char*>(reinterpret_cast<const char*>(filename))); filenames = filenames->next; } g_slist_free(filenames); - gtk_widget_destroy(dialog); + gtk_widget_destroy(GTK_WIDGET(dialog)); #elif defined(_WIN32) IShellItemArray *openResults; IShellItem *saveResult; @@ -772,7 +787,7 @@ namespace SSC { // <mime>:<ext>,<ext>|<mime>:<ext>|... for (const auto& contentTypeSpec : split(options.contentTypes, "|")) { const auto parts = split(contentTypeSpec, ":"); - const auto mime = parts[0]; + const auto mime = trim(parts[0]); const auto classes = split(mime, "/"); if (classes.size() == 2) { if (mimeTypes.size() == 0) { diff --git a/src/window/dialog.hh b/src/window/dialog.hh index 14d493e89e..65197b7d45 100644 --- a/src/window/dialog.hh +++ b/src/window/dialog.hh @@ -36,6 +36,7 @@ namespace SSC { public: struct FileSystemPickerOptions { enum class Type { Open, Save }; + bool prefersDarkMode = false; bool directories = false; bool multiple = false; bool files = false; diff --git a/src/window/linux.cc b/src/window/linux.cc index 256f4eca5d..79d767cb43 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -263,7 +263,6 @@ namespace SSC { GdkRGBA webviewBackground = {0.0, 0.0, 0.0, 0.0}; bool hasDarkValue = this->options.backgroundColorDark.size(); bool hasLightValue = this->options.backgroundColorLight.size(); - bool isDarkMode = false; auto isKDEDarkMode = []() -> bool { static const auto paths = FileResource::getWellKnownPaths(); @@ -310,12 +309,12 @@ namespace SSC { const gchar* desktop_env = getenv("XDG_CURRENT_DESKTOP"); if (desktop_env != NULL && g_str_has_prefix(desktop_env, "GNOME")) { - isDarkMode = isGnomeDarkMode(); + this->isDarkMode = isGnomeDarkMode(); } else { - isDarkMode = isKDEDarkMode(); + this->isDarkMode = isKDEDarkMode(); } - if (isDarkMode && hasDarkValue) { + if (this->isDarkMode && hasDarkValue) { gdk_rgba_parse(&color, this->options.backgroundColorDark.c_str()); } else if (hasLightValue) { gdk_rgba_parse(&color, this->options.backgroundColorLight.c_str()); diff --git a/src/window/window.hh b/src/window/window.hh index d1aa86a4e7..9f5341056a 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -351,6 +351,8 @@ namespace SSC { GtkWidget* menutray = nullptr; GtkWidget* contextMenu = nullptr; + bool isDarkMode = false; + #if SOCKET_RUNTIME_DESKTOP_EXTENSION void* userContentManager; void* policies; From ee67ed07345e5f3ff841f8326537af7fd790e7f4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 17:06:46 +0200 Subject: [PATCH 0810/1178] feat(platform): introduce 'join()' on 'Set' types --- src/platform/string.cc | 8 ++++++++ src/platform/string.hh | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/platform/string.cc b/src/platform/string.cc index 19555f4da8..25b4e141eb 100644 --- a/src/platform/string.cc +++ b/src/platform/string.cc @@ -164,6 +164,14 @@ namespace SSC { return join(vector, String(1, separator)); } + const String join (const Set<String>& set, const String& separator) { + return join(Vector<String>(set.begin(), set.end()), separator); + } + + const String join (const Set<String>& set, const char separator) { + return join(set, String(1, separator)); + } + Vector<String> parseStringList (const String& string, const Vector<char>& separators) { auto list = Vector<String>(); for (const auto& separator : separators) { diff --git a/src/platform/string.hh b/src/platform/string.hh index af7dfab301..578d61e58c 100644 --- a/src/platform/string.hh +++ b/src/platform/string.hh @@ -33,6 +33,8 @@ namespace SSC { const Vector<String> split (const String& source, const String& needle); const String join (const Vector<String>& vector, const String& separator); const String join (const Vector<String>& vector, const char separator); + const String join (const Set<String>& set, const String& separator); + const String join (const Set<String>& set, const char separator); Vector<String> parseStringList (const String& string, const Vector<char>& separators); Vector<String> parseStringList (const String& string, const char separator); Vector<String> parseStringList (const String& string); From f95596f4abb6f0bc154be17422831199cfa20f90 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 17:09:31 +0200 Subject: [PATCH 0811/1178] refactor(ipc): support 'Dialog' usage outside of 'Window' ownership --- src/ipc/routes.cc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index f4c0832acf..a223c48f73 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2948,7 +2948,16 @@ static void mapIPCRoutes (Router *router) { const auto app = App::sharedApplication(); const auto window = app->windowManager.getWindowForBridge(router->bridge); + Dialog* dialog = nullptr; + + if (window) { + dialog = &window->dialog; + } else { + dialog = new Dialog(); + } + const auto options = Dialog::FileSystemPickerOptions { + .prefersDarkMode = message.get("prefersDarkMode") == "true", .directories = allowDirs, .multiple = allowMultiple, .files = allowFiles, @@ -2959,7 +2968,7 @@ static void mapIPCRoutes (Router *router) { }; if (isSave) { - const auto result = window->dialog.showSaveFilePicker(options); + const auto result = dialog->showSaveFilePicker(options); if (result.size() == 0) { const auto err = JSON::Object::Entries {{"type", "AbortError"}}; @@ -2974,8 +2983,8 @@ static void mapIPCRoutes (Router *router) { JSON::Array paths; const auto results = ( allowFiles && !allowDirs - ? window->dialog.showOpenFilePicker(options) - : window->dialog.showDirectoryPicker(options) + ? dialog->showOpenFilePicker(options) + : dialog->showDirectoryPicker(options) ); for (const auto& result : results) { @@ -2988,6 +2997,10 @@ static void mapIPCRoutes (Router *router) { reply(Result::Data { message, data }); } + + if (!window) { + delete dialog; + } }); /** From 2a5ce96da604807727b4c4254dc02037acaa7319 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 17:11:13 +0200 Subject: [PATCH 0812/1178] refactor(api/window.js): set 'prefersDarkMode' in dialog methods --- api/window.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/window.js b/api/window.js index 83f4739fc6..0d977b86a8 100644 --- a/api/window.js +++ b/api/window.js @@ -352,6 +352,8 @@ export class ApplicationWindow { const result = await ipc.request('window.showFileSystemPicker', { type: 'open', ...options + }, { + useExtensionIPCIfAvailable: true }) if (result.err) { @@ -370,6 +372,8 @@ export class ApplicationWindow { const result = await ipc.request('window.showFileSystemPicker', { type: 'save', ...options + }, { + useExtensionIPCIfAvailable: true }) if (result.err) { @@ -389,6 +393,8 @@ export class ApplicationWindow { type: 'open', allowDirs: true, ...options + }, { + useExtensionIPCIfAvailable: true }) if (result.err) { From 83f9cc57d71fc1a9d932c5b88c4012e883ad1015 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 17:11:33 +0200 Subject: [PATCH 0813/1178] refactor(api/internal/pickers.js): prefer IPC extension when available --- api/internal/pickers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/internal/pickers.js b/api/internal/pickers.js index 7839441350..d06708dcf8 100644 --- a/api/internal/pickers.js +++ b/api/internal/pickers.js @@ -125,13 +125,12 @@ function normalizeShowFileSystemPickerOptions (options) { return { contentTypeSpecs: contentTypeSpecs.join('|'), + prefersDarkMode: globalThis?.matchMedia?.('(prefers-color-scheme: dark)')?.matches || false, allowMultiple: options?.multiple === true, defaultName: options?.suggestedName ?? '', defaultPath: resolveStartInPath(options?.startIn, options?.id) ?? '', - // eslint-disable-next-line - allowFiles: options?.files === true ? true : false, - // eslint-disable-next-line - allowDirs: options?.directories === true ? true : false + allowFiles: options?.files === true, + allowDirs: options?.directories === true } } @@ -141,6 +140,7 @@ function normalizeShowFileSystemPickerOptions (options) { * mode?: 'read' | 'readwrite' * startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos', * }} ShowDirectoryPickerOptions + * */ /** From 39c4759d73c16855c04c703b6062c2c2d5a03ac5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 21:25:24 +0200 Subject: [PATCH 0814/1178] refactor(core/resource): conslidate mount paths and logic --- src/core/resource.cc | 105 ++++++++++++++++++++++++++++++++++++++++++- src/core/resource.hh | 2 + 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/core/resource.cc b/src/core/resource.cc index c2cd8eaf2c..5ad3a37980 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -139,6 +139,101 @@ namespace SSC { return fs::is_directory(resourcePath); } + bool FileResource::isMountedPath (const Path& path) { + static auto userConfig = getUserConfig(); + static auto mounts = FileResource::getMountedPaths(); + for (const auto& entry : mounts) { + if (path.string().starts_with(entry.first)) { + return true; + } + } + return false; + } + + const Map FileResource::getMountedPaths () { + static auto userConfig = getUserConfig(); + static Map mounts = {}; + + if (mounts.size() > 0) { + return mounts; + } + + // determine HOME + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); + #elif SOCKET_RUNTIME_PLATFORM_IOS + static const auto HOME = String(NSHomeDirectory().UTF8String); + #else + static const auto uid = getuid(); + static const auto pwuid = getpwuid(uid); + static const auto HOME = pwuid != nullptr + ? String(pwuid->pw_dir) + : Env::get("HOME", getcwd()); + #endif + + static const Map mappings = { + {"\\$HOST_HOME", HOME}, + {"~", HOME}, + + {"\\$HOST_CONTAINER", + #if SOCKET_RUNTIME_PLATFORM_IOS + [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) objectAtIndex: 0].UTF8String + #elif SOCKET_RUNTIME_PLATFORM_MACOS + // `homeDirectoryForCurrentUser` resolves to sandboxed container + // directory when in "sandbox" mode, otherwise the user's HOME directory + NSFileManager.defaultManager.homeDirectoryForCurrentUser.absoluteString.UTF8String + #elif SOCKET_RUNTIME_PLATFORM_LINUX || SOCKET_RUNTIME_PLATFORM_ANDROID + // TODO(@jwerle): figure out `$HOST_CONTAINER` for Linux/Android + getcwd() + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + // TODO(@jwerle): figure out `$HOST_CONTAINER` for Windows + getcwd() + #else + getcwd() + #endif + }, + + {"\\$HOST_PROCESS_WORKING_DIRECTORY", + #if SOCKET_RUNTIME_PLATFORM_APPLE + NSBundle.mainBundle.resourcePath.UTF8String + #else + getcwd() + #endif + } + }; + + static const auto wellKnownPaths = FileResource::getWellKnownPaths(); + + for (const auto& entry : userConfig) { + if (entry.first.starts_with("webview_navigator_mounts_")) { + auto key = replace(entry.first, "webview_navigator_mounts_", ""); + + if (key.starts_with("android") && !platform.android) continue; + if (key.starts_with("ios") && !platform.ios) continue; + if (key.starts_with("linux") && !platform.linux) continue; + if (key.starts_with("mac") && !platform.mac) continue; + if (key.starts_with("win") && !platform.win) continue; + + key = replace(key, "android_", ""); + key = replace(key, "ios_", ""); + key = replace(key, "linux_", ""); + key = replace(key, "mac_", ""); + key = replace(key, "win_", ""); + + String path = key; + + for (const auto& map : mappings) { + path = replace(path, map.first, map.second); + } + + const auto& value = entry.second; + mounts.insert_or_assign(path, value); + } + } + + return mounts; + } + Path FileResource::getResourcesPath () { static String value; @@ -453,13 +548,19 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_APPLE if (this->url == nullptr) { - #if SOCKET_RUNTIME_PLATFORM_APPLE this->url = [NSURL fileURLWithPath: @(this->path.string().c_str())]; - #endif } + #endif + if (FileResource::isMountedPath(this->path)) { + this->accessing = true; + return true; + } + + #if SOCKET_RUNTIME_PLATFORM_APPLE if (!this->path.string().starts_with(resourcesPath.string())) { if (![this->url startAccessingSecurityScopedResource]) { + this->url = nullptr; return false; } } diff --git a/src/core/resource.hh b/src/core/resource.hh index 395099aa0f..19fe046e12 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -127,7 +127,9 @@ namespace SSC { static bool isFile (const Path& resourcePath); static bool isDirectory (const String& resourcePath); static bool isDirectory (const Path& resourcePath); + static bool isMountedPath (const Path& path); static const WellKnownPaths& getWellKnownPaths (); + static const Map getMountedPaths (); #if SOCKET_RUNTIME_PLATFORM_ANDROID static void setSharedAndroidAssetManager (Android::AssetManager*); From b25277e02bcca4752a14cfa1870c414cd6b71db9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 21:25:43 +0200 Subject: [PATCH 0815/1178] fix(ipc): fix navigator mount resolution --- src/ipc/bridge.cc | 2 +- src/ipc/navigator.cc | 72 ++------------------------------------------ 2 files changed, 4 insertions(+), 70 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index ced88d0d64..59746f8108 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -598,7 +598,7 @@ export default module)S"; } else if (resolved.isResource()) { resourcePath = applicationResources + resolved.pathname; } else if (resolved.isMount()) { - resourcePath = applicationResources + resolved.mount.filename; + resourcePath = resolved.mount.filename; } else if (request->pathname == "" || request->pathname == "/") { if (userConfig.contains("webview_default_index")) { resourcePath = userConfig["webview_default_index"]; diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index 3581efa1f7..905c0a363c 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -399,83 +399,17 @@ namespace SSC::IPC { } void Navigator::configureMounts () { - // determine HOME - #if SOCKET_RUNTIME_PLATFORM_WINDOWS - static const auto HOME = Env::get("HOMEPATH", Env::get("USERPROFILE", Env::get("HOME"))); - #elif SOCKET_RUNTIME_PLATFORM_IOS - static const auto HOME = String(NSHomeDirectory().UTF8String); - #else - static const auto uid = getuid(); - static const auto pwuid = getpwuid(uid); - static const auto HOME = pwuid != nullptr - ? String(pwuid->pw_dir) - : Env::get("HOME", getcwd()); - #endif - - static const Map mappings = { - {"\\$HOST_HOME", HOME}, - {"~", HOME}, - - {"\\$HOST_CONTAINER", - #if SOCKET_RUNTIME_PLATFORM_IOS - [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) objectAtIndex: 0].UTF8String - #elif SOCKET_RUNTIME_PLATFORM_MACOS - // `homeDirectoryForCurrentUser` resolves to sandboxed container - // directory when in "sandbox" mode, otherwise the user's HOME directory - NSFileManager.defaultManager.homeDirectoryForCurrentUser.absoluteString.UTF8String - #elif SOCKET_RUNTIME_PLATFORM_LINUX || SOCKET_RUNTIME_PLATFORM_ANDROID - // TODO(@jwerle): figure out `$HOST_CONTAINER` for Linux/Android - getcwd() - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - // TODO(@jwerle): figure out `$HOST_CONTAINER` for Windows - getcwd() - #else - getcwd() - #endif - }, - - {"\\$HOST_PROCESS_WORKING_DIRECTORY", - #if SOCKET_RUNTIME_PLATFORM_APPLE - NSBundle.mainBundle.resourcePath.UTF8String - #else - getcwd() - #endif - } - }; - static const auto wellKnownPaths = FileResource::getWellKnownPaths(); + this->location.mounts = FileResource::getMountedPaths(); - for (const auto& entry : this->bridge->userConfig) { - if (entry.first.starts_with("webview_navigator_mounts_")) { - auto key = replace(entry.first, "webview_navigator_mounts_", ""); - - if (key.starts_with("android") && !platform.android) continue; - if (key.starts_with("ios") && !platform.ios) continue; - if (key.starts_with("linux") && !platform.linux) continue; - if (key.starts_with("mac") && !platform.mac) continue; - if (key.starts_with("win") && !platform.win) continue; - - key = replace(key, "android_", ""); - key = replace(key, "ios_", ""); - key = replace(key, "linux_", ""); - key = replace(key, "mac_", ""); - key = replace(key, "win_", ""); - - String path = key; - - for (const auto& map : mappings) { - path = replace(path, map.first, map.second); - } - - const auto& value = entry.second; - this->location.mounts.insert_or_assign(path, value); + for (const auto& entry : this->location.mounts) { + const auto& path = entry.first; #if SOCKET_RUNTIME_PLATFORM_LINUX auto webContext = webkit_web_context_get_default(); if (path != wellKnownPaths.home.string()) { webkit_web_context_add_path_to_sandbox(webContext, path.c_str(), false); } #endif - } } #if SOCKET_RUNTIME_PLATFORM_LINUX From ad61c7f8ab359aedeae5dd92d4e4df0068e76e2e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 13 Jun 2024 21:26:10 +0200 Subject: [PATCH 0816/1178] fix(window): set window position values --- src/window/apple.mm | 7 ++++++- src/window/window.hh | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index d2dd2fa2e8..0b3f94aea5 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -647,8 +647,10 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { this->window.rootViewController = this->viewController; this->window.rootViewController.view.frame = frame; - #endif + + this->position.x = this->window.frame.origin.x; + this->position.y = this->window.frame.origin.y; } Window::~Window () { @@ -705,6 +707,9 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { [this->window makeKeyAndOrderFront: nil]; [NSApp activateIgnoringOtherApps: YES]; } + + this->position.x = this->window.frame.origin.x; + this->position.y = this->window.frame.origin.y; #elif SOCKET_RUNTIME_PLATFORM_IOS [this->webview becomeFirstResponder]; [this->window makeKeyAndVisible]; diff --git a/src/window/window.hh b/src/window/window.hh index 9f5341056a..8e85b9e34f 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -92,8 +92,8 @@ namespace SSC { class Window { public: struct Position { - float x; - float y; + float x = 0.0f; + float y = 0.0f; }; struct Size { From 7b2f1ff7d726befa67a64add672673a1f0af20fe Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 14 Jun 2024 00:31:13 +0200 Subject: [PATCH 0817/1178] fix(window): remove 'dispatchFunction' configuration --- src/window/apple.mm | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 0b3f94aea5..1615eb55c8 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -197,19 +197,6 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { this->navigate(url); }; - this->bridge.dispatchFunction = [this] (auto callback) { - #if SOCKET_RUNTIME_PLATFORM_MACOS - const auto app = App::sharedApplication(); - if (app != nullptr) { - app->dispatch(callback); - } - #elif SOCKET_RUNTIME_PLATFORM_IOS - dispatch_async(queue, ^{ - callback(); - }); - #endif - }; - this->bridge.evaluateJavaScriptFunction = [this](auto source) { dispatch_async(dispatch_get_main_queue(), ^{ this->eval(source); From 73309ff53d3fe1040ce5047f66b0f7d048b8a435 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 14 Jun 2024 11:51:13 +0200 Subject: [PATCH 0818/1178] refactor(p2p): makes debug a method to call so debug data can be passed though the worker context; fix(p2p): fix resolveMainThread impl --- api/latica/api.js | 13 ++--- api/latica/index.js | 125 ++++++++++++++++++++--------------------- api/latica/packets.js | 5 +- api/latica/proxy.js | 17 ++++-- src/core/modules/ai.cc | 8 +-- 5 files changed, 84 insertions(+), 84 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index 37c94d601a..abd47b1f22 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -160,31 +160,30 @@ async function api (options = {}, events, dgram) { } const unpack = async packet => { - let opened let verified const scid = Buffer.from(packet.subclusterId).toString('base64') const sub = bus.subclusters.get(scid) if (!sub) return {} - const { err: errOpen, data: dataOpened } = await _peer.open(packet.message, scid) + const opened = await _peer.open(packet.message, scid) - if (errOpen) { - sub._emit('warning', errOpen) + if (!opened) { + sub._emit('unopened', { packet }) return {} } if (packet.sig) { try { - if (Encryption.verify(opened.data || opened, packet.sig, packet.usr2)) { + if (Encryption.verify(opened, packet.sig, packet.usr2)) { verified = true } } catch (err) { - sub._emit('warning', err) + sub._emit('unverified', packet) return {} } } - return { opened, verified } + return { opened: Buffer.from(opened), verified } } /** diff --git a/api/latica/index.js b/api/latica/index.js index a48059091f..714c4a0768 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -32,15 +32,6 @@ const COLOR_GRAY = '\x1b[90m' const COLOR_WHITE = '\x1b[37m' const COLOR_RESET = '\x1b[0m' -export const debug = (pid, ...args) => { - if (typeof process === 'undefined' || !process.env.DEBUG) return - const output = COLOR_GRAY + - String(logcount++).padStart(6) + ' │ ' + COLOR_WHITE + - pid.slice(0, 4) + ' ' + args.join(' ') + COLOR_RESET - - if (new RegExp(process.env.DEBUG).test(output)) console.log(output) -} - export { Packet, sha256, Cache, Encryption, NAT } /** @@ -197,13 +188,13 @@ export class RemotePeer { const packets = await this.localPeer._message2packets(PacketStream, args.message, args) if (this.proxy) { - debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) + this.localPeer.debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) } for (const packet of packets) { const from = this.localPeer.peerId.slice(0, 6) const to = this.peerId.slice(0, 6) - debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) + this.localPeer.debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) this.localPeer.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) @@ -366,6 +357,10 @@ export class Peer { return setTimeout(fn, t) } + _debug (pid, ...args) { + if (this.onDebug) this.onDebug(pid, ...args) + } + /** * A method that encapsulates the listing procedure * @return {undefined} @@ -401,7 +396,7 @@ export class Peer { if (this.onListening) this.onListening() this.isListening = true - debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) + this._debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) } /* @@ -462,7 +457,7 @@ export class Peer { if (deadline <= ts) { if (p.hops < this.maxHops) this.mcast(p) this.cache.delete(k) - debug(this.peerId, '-- DELETE', k, this.cache.size) + this._debug(this.peerId, '-- DELETE', k, this.cache.size) if (this.onDelete) this.onDelete(p) } } @@ -552,7 +547,7 @@ export class Peer { this.metrics.o[packet.type]++ delete this.unpublished[packet.packetId.toString('hex')] if (this.onSend && packet.type) this.onSend(packet, port, address) - debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) + this._debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) }) } @@ -571,7 +566,7 @@ export class Peer { } await this.mcast(packet) - debug(this.peerId, `-> RESEND (packetId=${packetId})`) + this._debug(this.peerId, `-> RESEND (packetId=${packetId})`) if (this.onState) this.onState(this.getState()) } } @@ -721,16 +716,16 @@ export class Peer { */ async requestReflection () { if (this.closing || this.indexed || this.reflectionId) { - debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) + this._debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) return } if (this.natType && (this.lastUpdate > 0 && (Date.now() - this.config.keepalive) < this.lastUpdate)) { - debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) + this._debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) return } - debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) + this._debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) if (this.onConnecting) this.onConnecting({ code: -1, status: `Entering reflection (lastUpdate ${Date.now() - this.lastUpdate}ms)` }) const peers = [...this.peers] @@ -739,7 +734,7 @@ export class Peer { if (peers.length < 2) { if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) - debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') + this._debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') if (++this.reflectionRetry > 16) this.reflectionRetry = 1 return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) @@ -766,7 +761,7 @@ export class Peer { if (this.reflectionTimeout) this._clearTimeout(this.reflectionTimeout) this.reflectionStage = 1 - debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) + this._debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) const list = peers.filter(p => p.probed).sort(() => Math.random() - 0.5) const peer = list.length ? list[0] : peers[0] peer.probed = Date.now() // mark this peer as being used to provide port info @@ -776,7 +771,7 @@ export class Peer { this.probeReflectionTimeout = this._setTimeout(() => { this.probeReflectionTimeout = null if (this.reflectionStage !== 1) return - debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) + this._debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) if (this.onConnecting) this.onConnecting({ code: 1, status: 'Timeout' }) this.reflectionStage = 1 @@ -784,7 +779,7 @@ export class Peer { this.requestReflection() }, 1024) - debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) + this._debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) return } @@ -814,12 +809,12 @@ export class Peer { const peer2 = peers.filter(p => !p.probed).sort(() => Math.random() - 0.5)[0] if (!peer1 || !peer2) { - debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') + this._debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Insufficent Peers' }) return this._setTimeout(() => this.requestReflection(), 256) } - debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) + this._debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) // reset reflection variables to defaults this.nextNatType = NAT.UNKNOWN @@ -844,7 +839,7 @@ export class Peer { if (this.onConnecting) this.onConnecting({ code: 2, status: 'Timeout' }) this.reflectionStage = 1 this.reflectionId = null - debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) + this._debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) return this.requestReflection() }, 2048) } @@ -938,7 +933,7 @@ export class Peer { } }) - debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) + this._debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) if (this.onState) this.onState(this.getState()) this.mcast(packet) @@ -1079,7 +1074,7 @@ export class Peer { this.lastSync = Date.now() const summary = await this.cache.summarize('', this.cachePredicate) - debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) + this._debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) // if we are out of sync send our cache summary @@ -1123,7 +1118,7 @@ export class Peer { this.returnRoutes.set(p.usr3.toString('hex'), {}) this.gate.set(pid, 1) // don't accidentally spam - debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) + this._debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) await this.mcast(p) } @@ -1187,7 +1182,7 @@ export class Peer { if (!peer.localPeer) peer.localPeer = this if (!this.connections) this.connections = new Map() - debug(this.peerId, '<- CONNECTION ( ' + + this._debug(this.peerId, '<- CONNECTION ( ' + `peerId=${peer.peerId.slice(0, 6)}, ` + `address=${address}:${port}, ` + `type=${packet.type}, ` + @@ -1232,7 +1227,7 @@ export class Peer { if (this.onSync) this.onSync(packet, port, address, { remote, local }) const remoteBuckets = remote.buckets.filter(Boolean).length - debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) + this._debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) for (let i = 0; i < local.buckets.length; i++) { // @@ -1251,7 +1246,7 @@ export class Peer { if (!this.cachePredicate(packet)) continue const pid = packet.packetId.toString('hex') - debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) + this._debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) this.send(await Packet.encode(packet), port, address) } @@ -1304,7 +1299,7 @@ export class Peer { if (queryTimestamp < (Date.now() - 2048)) return const type = queryType === 1 ? 'question' : 'answer' - debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) + this._debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) let rinfo = { port, address } @@ -1365,7 +1360,7 @@ export class Peer { } if (packet.hops >= this.maxHops) return - debug(this.peerId, '>> QUERY RELAY', port, address) + this._debug(this.peerId, '>> QUERY RELAY', port, address) return await this.mcast(packet) } @@ -1454,7 +1449,7 @@ export class Peer { const { reflectionId, pingId, isReflection, responderPeerId } = packet.message - debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) + this._debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) const peer = this.getPeer(packet.message.responderPeerId) if (packet.message.isConnection) { @@ -1473,10 +1468,10 @@ export class Peer { if (!this.reflectionFirstResponder) { this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) + this._debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) } else { if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) + this._debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) if (packet.message.address !== this.address) return this.nextNatType |= ( @@ -1485,7 +1480,7 @@ export class Peer { ? NAT.MAPPING_ENDPOINT_INDEPENDENT : NAT.MAPPING_ENDPOINT_DEPENDENT - debug( + this._debug( this.peerId, `++ NAT REFLECT - STATE UPDATE (natType=${this.natType}, nextType=${this.nextNatType})`, packet.message.port, @@ -1515,7 +1510,7 @@ export class Peer { for (const peer of this.peers) { peer.lastRequest = Date.now() - debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) + this._debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) await this.ping(peer, false, { message: { @@ -1529,7 +1524,7 @@ export class Peer { if (this.onNat) this.onNat(this.natType) - debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) + this._debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) this.sendUnpublished() // } @@ -1544,7 +1539,7 @@ export class Peer { this.address = packet.message.address this.port = packet.message.port - debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) + this._debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) } } @@ -1597,7 +1592,7 @@ export class Peer { const cid = clusterId.toString('base64') const scid = subclusterId.toString('base64') - debug(this.peerId, '<- INTRO (' + + this._debug(this.peerId, '<- INTRO (' + `isRendezvous=${packet.message.isRendezvous}, ` + `from=${address}:${port}, ` + `to=${packet.message.address}:${packet.message.port}, ` + @@ -1636,13 +1631,13 @@ export class Peer { }, 1024 * 2) if (packet.message.isRendezvous) { - debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) + this._debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) } - debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) + this._debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { - debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) + this._debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) let i = 0 if (!this.socketPool) { @@ -1746,7 +1741,7 @@ export class Peer { if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { // TODO could allow multiple proxies this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) - debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') + this._debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') } if (strategy === NAT.STRATEGY_TRAVERSAL_OPEN) { @@ -1766,11 +1761,11 @@ export class Peer { } if (strategy === NAT.STRATEGY_DIRECT_CONNECT) { - debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') + this._debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') } if (strategy === NAT.STRATEGY_DEFER) { - debug(this.peerId, '++ NAT STRATEGY_DEFER') + this._debug(this.peerId, '++ NAT STRATEGY_DEFER') } this.ping(peer, true, props) @@ -1808,7 +1803,7 @@ export class Peer { const cid = clusterId.toString('base64') const scid = subclusterId.toString('base64') - debug(this.peerId, '<- JOIN (' + + this._debug(this.peerId, '<- JOIN (' + `peerId=${peerId.slice(0, 6)}, ` + `clock=${packet.clock}, ` + `hops=${packet.hops}, ` + @@ -1831,7 +1826,7 @@ export class Peer { // TODO it would tighten up the transition time between dropped peers // if we check strategy from (packet.message.natType, this.natType) and // make introductions that create more mutually known peers. - debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) + this._debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) const data = await Packet.encode(new PacketJoin({ clock: packet.clock, @@ -1873,14 +1868,14 @@ export class Peer { const peer = this.peers.find(p => p.peerId === packet.message.rendezvousRequesterPeerId) if (!peer) { - debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) + this._debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) return } // peer.natType = packet.message.rendezvousType peers = [peer] - debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + this._debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) } for (const peer of peers) { @@ -1916,8 +1911,8 @@ export class Peer { // Send intro1 to the peer described in the message // Send intro2 to the peer in this loop // - debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) - debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + this._debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) + this._debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) peer.lastRequest = Date.now() @@ -1941,7 +1936,7 @@ export class Peer { packet.message.rendezvousDeadline = Date.now() + this.config.keepalive } - debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) + this._debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) if (packet.hops <= 1) { @@ -1963,11 +1958,11 @@ export class Peer { const pid = packet.packetId.toString('hex') if (this.cache.has(pid)) { - debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) + this._debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) return } - debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) + this._debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) this.cacheInsert(packet) const ignorelist = [{ address, port }] @@ -2040,16 +2035,16 @@ export class Peer { // stream message is for another peer const peerTo = this.peers.find(p => p.peerId === streamTo) if (!peerTo) { - debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) + this._debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) return } if (packet.hops >= this.maxHops) { - debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) + this._debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) return } - debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) + this._debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) this.send(await Packet.encode(packet), peerTo.port, peerTo.address) if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) } @@ -2072,7 +2067,7 @@ export class Peer { this.gate.set(pid, 1) const { reflectionId } = packet.message - debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) + this._debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) if (this.onProbe) this.onProbe(data, port, address) if (this.reflectionId !== reflectionId || !this.reflectionId) return @@ -2081,7 +2076,7 @@ export class Peer { // const reflectionStage = reflectionId ? parseInt(reflectionId.slice(-1), 16) : 0 if (this.reflectionStage === 1) { - debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) + this._debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) if (!packet.message?.port) return // message must include a port number // successfully discovered the probe socket external port @@ -2095,16 +2090,16 @@ export class Peer { } if (this.reflectionStage === 2) { - debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) + this._debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) // if we have previously sent an outbount message to this peer on the probe port // then our NAT will have a mapping for their IP, but not their IP+Port. if (!NAT.isFirewallDefined(this.nextNatType)) { this.nextNatType |= NAT.FIREWALL_ALLOW_KNOWN_IP - debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) + this._debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) } else { this.nextNatType |= NAT.FIREWALL_ALLOW_ANY - debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) + this._debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) } // wait for all messages to arrive @@ -2135,7 +2130,7 @@ export class Peer { if (!this.config.limitExempt) { if (rateLimit(this.rates, packet.type, port, address, subcluster)) { - debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) + this._debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) this.metrics.i.REJECTED++ return } diff --git a/api/latica/packets.js b/api/latica/packets.js index 0d5c178df1..609eb2418b 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -1,7 +1,6 @@ import { randomBytes } from '../crypto.js' import { isBufferLike } from '../util.js' import { Buffer } from '../buffer.js' -import { debug } from './index.js' /** * Hash function factory. @@ -153,13 +152,13 @@ export const validatePacket = (o, constraints) => { for (const [key, con] of Object.entries(constraints)) { if (con.required && !o[key]) { - debug(new Error(`${key} is required (${JSON.stringify(o, null, 2)})`), JSON.stringify(o)) + // console.warn(new Error(`${key} is required (${JSON.stringify(o, null, 2)})`), JSON.stringify(o)) } const type = ({}).toString.call(o[key]).slice(8, -1).toLowerCase() if (o[key] && type !== con.type) { - debug(`expected .${key} to be of type ${con.type}, got ${type} in packet.. ` + JSON.stringify(o)) + // console.warn(`expected .${key} to be of type ${con.type}, got ${type} in packet.. ` + JSON.stringify(o)) } } } diff --git a/api/latica/proxy.js b/api/latica/proxy.js index f6a764418f..9ba06b0ab6 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -19,12 +19,14 @@ function deepClone (object, map = new Map()) { const isNull = object === null const isNotObject = typeof object !== 'object' const isArrayBuffer = object instanceof ArrayBuffer + const isNodeBuffer = object?.constructor?.name === 'Buffer' const isArray = Array.isArray(object) const isUint8Array = object instanceof Uint8Array const isMessagePort = object instanceof MessagePort if (isMessagePort || isNotObject || isNull || isArrayBuffer) return object if (isUint8Array) return new Uint8Array(object) + if (isNodeBuffer) return Uint8Array.from(object) if (isArrayBuffer) return object.slice(0) if (isArray) { @@ -128,6 +130,7 @@ class PeerWorkerProxy { if (err) { p.reject(err) } else { + if (prop === 'open') console.log('<<<', data) p.resolve(data) } @@ -235,7 +238,7 @@ class PeerWorkerProxy { } const seq = ++this.#index - let { promise, resolve, reject } = Promise.withResolvers(); + const { promise, resolve, reject } = Promise.withResolvers() const d = promise d.resolve = resolve d.reject = reject @@ -267,13 +270,17 @@ class PeerWorkerProxy { } } - resolveMainThread (seq, data) { + resolveMainThread (seq, result) { + if (result.err) { // err result of the worker try/catch + return this.#port.postMessage({ data: { err: result.err.message } }) + } + try { this.#port.postMessage( - { data: deepClone(data), seq }, - { transfer: transferOwnership(data) } + { data: deepClone(result.data), seq }, + { transfer: transferOwnership(result.data) } ) - } catch (err) { + } catch (err) { // can't post the data this.#port.postMessage({ data: { err: err.message } }) } } diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 3131aaa778..9ebe453d5b 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -117,7 +117,7 @@ namespace SSC { auto llm = this->getLLM(id); - llm->chat(message, [=](auto self, auto token, auto isComplete) { + llm->chat(message, [&](auto self, auto token, auto isComplete) { const auto json = JSON::Object::Entries { {"source", "ai.llm.chat"}, {"data", JSON::Object::Entries { @@ -264,9 +264,9 @@ namespace SSC { this->params.antiprompt = Vector<String> { options.antiprompt }; } - #if SOCKET_RUNTIME_PLATFORM_IOS - this->params.use_mmap = false; - #endif + // #if SOCKET_RUNTIME_PLATFORM_IOS + // this->params.use_mmap = false; + // #endif FileResource modelResource(options.path); FileResource metalResource(String("ggml-metal")); From e3e97f30e97366e9bab06ded6fdd8708d5b4bfe1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 14 Jun 2024 16:29:33 +0200 Subject: [PATCH 0819/1178] refactor(core): allow 'Core' to be configured --- src/core/core.cc | 48 ++++++++++++++++++++----------- src/core/core.hh | 39 +++++++++++++++++++++++-- src/core/file_system_watcher.cc | 5 ++-- src/core/modules/notifications.cc | 8 +++--- src/core/modules/notifications.hh | 7 +++-- 5 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index b03091b2d0..6ed5b253bc 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -170,7 +170,6 @@ namespace SSC { ) -> gboolean { auto core = reinterpret_cast<UVSource *>(source)->core; auto loop = core->getEventLoop(); - Lock lock(core->loopMutex); uv_run(loop, UV_RUN_NOWAIT); return G_SOURCE_CONTINUE; } @@ -196,10 +195,10 @@ namespace SSC { do { Lock lock(core->loopMutex); - if (core->eventLoopDispatchQueue.size() == 0) break; - - dispatch = core->eventLoopDispatchQueue.front(); - core->eventLoopDispatchQueue.pop(); + if (core->eventLoopDispatchQueue.size() > 0) { + dispatch = core->eventLoopDispatchQueue.front(); + core->eventLoopDispatchQueue.pop(); + } } while (0); if (dispatch == nullptr) { @@ -211,17 +210,19 @@ namespace SSC { }); #if SOCKET_RUNTIME_PLATFORM_LINUX - GSource *source = g_source_new(&loopSourceFunctions, sizeof(UVSource)); - UVSource *uvSource = (UVSource *) source; - uvSource->core = this; - uvSource->tag = g_source_add_unix_fd( - source, - uv_backend_fd(&eventLoop), - (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR) - ); + if (!this->options.dedicatedLoopThread) { + GSource *source = g_source_new(&loopSourceFunctions, sizeof(UVSource)); + UVSource *uvSource = (UVSource *) source; + uvSource->core = this; + uvSource->tag = g_source_add_unix_fd( + source, + uv_backend_fd(&eventLoop), + (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR) + ); - g_source_set_priority(source, G_PRIORITY_HIGH); - g_source_attach(source, nullptr); + g_source_set_priority(source, G_PRIORITY_HIGH); + g_source_attach(source, nullptr); + } #endif } @@ -243,7 +244,10 @@ namespace SSC { void Core::stopEventLoop() { isLoopRunning = false; uv_stop(&eventLoop); - #if SOCKET_RUNTIME_PLATFORM_ANDROID || SOCKET_RUNTIME_PLATFORM_WINDOWS + #if !SOCKET_RUNTIME_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (this->options.dedicatedLoopThread) { + #endif if (eventLoopThread != nullptr) { if (eventLoopThread->joinable()) { eventLoopThread->join(); @@ -252,6 +256,9 @@ namespace SSC { delete eventLoopThread; eventLoopThread = nullptr; } + #if SOCKET_RUNTIME_PLATFORM_LINUX + } + #endif #endif } @@ -270,6 +277,7 @@ namespace SSC { void Core::signalDispatchEventLoop () { initEventLoop(); runEventLoop(); + Lock lock(this->loopMutex); uv_async_send(&eventLoopAsync); } @@ -312,7 +320,10 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_APPLE Lock lock(this->loopMutex); dispatch_async(eventLoopQueue, ^{ pollEventLoop(this); }); - #elif SOCKET_RUNTIME_PLATFORM_ANDROID || SOCKET_RUNTIME_PLATFORM_WINDOWS + #else + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (this->options.dedicatedLoopThread) { + #endif Lock lock(this->loopMutex); // clean up old thread if still running if (eventLoopThread != nullptr) { @@ -325,6 +336,9 @@ namespace SSC { } eventLoopThread = new std::thread(&pollEventLoop, this); + #if SOCKET_RUNTIME_PLATFORM_LINUX + } + #endif #endif } diff --git a/src/core/core.hh b/src/core/core.hh index 8219b14ebf..e631113f10 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -61,6 +61,35 @@ namespace SSC { using UDP = CoreUDP; using AI = CoreAI; + struct Options : SSC::Options { + struct Features { + #if !SOCKET_RUNTIME_PLATFORM_IOS + bool useChildProcess = true; + #endif + + bool useDNS = true; + bool useFS = true; + bool useGeolocation = true; + bool useNetworkStatus = true; + bool useNotifications = true; + bool useOS = true; + bool usePlatform = true; + bool useTimers = true; + bool useUDP = true; + bool useAI = true; + }; + + Features features; + + #if SOCKET_RUNTIME_PLATFORM_LINUX + // this is turned on in the WebKitWebProcess extension to avoid + // deadlocking the GTK loop AND WebKit WebView thread as they + // are shared and we typically "interpolate" loop execution + // with the GTK thread on the main runtime process + bool dedicatedLoopThread = false; + #endif + }; + struct SharedPointerBuffer { SharedPointer<char[]> pointer; unsigned int ttl = 0; @@ -81,6 +110,7 @@ namespace SSC { AI ai; Vector<SharedPointerBuffer> sharedPointerBuffers; + Options options = {}; Posts posts; Mutex mutex; @@ -113,7 +143,8 @@ namespace SSC { Thread *eventLoopThread = nullptr; #endif - Core (bool isUtility = false) : + Core (const Options& options) : + options(options), #if !SOCKET_RUNTIME_PLATFORM_IOS childProcess(this), #endif @@ -122,7 +153,7 @@ namespace SSC { fs(this), geolocation(this), networkStatus(this), - notifications(this, isUtility), + notifications(this, { options.features.useNotifications }), os(this), platform(this), timers(this), @@ -131,6 +162,10 @@ namespace SSC { initEventLoop(); } + Core () + : Core(Options {}) + {} + ~Core () { this->shutdown(); } diff --git a/src/core/file_system_watcher.cc b/src/core/file_system_watcher.cc index e3fbd80298..d12a68076f 100644 --- a/src/core/file_system_watcher.cc +++ b/src/core/file_system_watcher.cc @@ -125,8 +125,9 @@ namespace SSC { // a loop may be configured for the instance already, perhaps here or // manually by the caller if (this->core == nullptr) { - const bool isUtility = true; - this->core = new Core(isUtility); + Core::Options options; + options.features.useNotifications = false; + this->core = new Core(options); this->ownsCore = true; } diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index 46fbaff6a5..4e0a7c1dc4 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -88,15 +88,15 @@ namespace SSC { }; } - CoreNotifications::CoreNotifications (Core* core, bool isUtility = false) + CoreNotifications::CoreNotifications (Core* core, const Options& options) : CoreModule(core), - isUtility(isUtility), + options(options), permissionChangeObservers(), notificationResponseObservers(), notificationPresentedObservers() { #if SOCKET_RUNTIME_PLATFORM_APPLE - if (this->isUtility) return; + if (!this->options.enabled) return; auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; @@ -138,7 +138,7 @@ namespace SSC { CoreNotifications::~CoreNotifications () { #if SOCKET_RUNTIME_PLATFORM_APPLE - if (this->isUtility) return; + if (!this->options.enabled) return; auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; diff --git a/src/core/modules/notifications.hh b/src/core/modules/notifications.hh index 32caa68a23..2032ccada5 100644 --- a/src/core/modules/notifications.hh +++ b/src/core/modules/notifications.hh @@ -30,7 +30,9 @@ namespace SSC { using NotificationPresentedObserver = CoreModule::Observer<JSON::Object>; using NotificationPresentedObservers = CoreModule::Observers<NotificationPresentedObserver>; - bool isUtility = false; + struct Options { + bool enabled = true; + }; struct Notification { String identifier; @@ -62,13 +64,14 @@ namespace SSC { UNAuthorizationStatus __block currentUserNotificationAuthorizationStatus; #endif + Options options; Mutex mutex; PermissionChangeObservers permissionChangeObservers; NotificationResponseObservers notificationResponseObservers; NotificationPresentedObservers notificationPresentedObservers; - CoreNotifications (Core* core, bool isUtility); + CoreNotifications (Core* core, const Options& options); ~CoreNotifications (); bool removePermissionChangeObserver (const PermissionChangeObserver& observer); From 0e1b99e41687edb829d5aad87edd25bd82ec50bd Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 14 Jun 2024 16:41:30 +0200 Subject: [PATCH 0820/1178] refactor(desktop/extension/linux): configure core, support sync calls, clean up --- src/desktop/extension/linux.cc | 171 +++++++++++++++++++++++---------- 1 file changed, 122 insertions(+), 49 deletions(-) diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc index 49640fe167..9ce75959d7 100644 --- a/src/desktop/extension/linux.cc +++ b/src/desktop/extension/linux.cc @@ -14,9 +14,32 @@ using namespace SSC; #if defined(__cplusplus) extern "C" { #endif - static WebExtensionContext context; - static SharedPointer<IPC::Bridge> bridge = nullptr; + static SharedPointer<IPC::Bridge> sharedBridge = nullptr; static bool isMainApplicationDebugEnabled = false; + static WebExtensionContext context; + static Mutex sharedMutex; + + static SharedPointer<IPC::Bridge> getSharedBridge (JSCContext* context) { + static auto app = App::sharedApplication(); + Lock lock(sharedMutex); + + if (sharedBridge == nullptr) { + g_object_ref(context); + auto options = IPC::Bridge::Options(app->userConfig); + sharedBridge = std::make_shared<IPC::Bridge>(app->core, options); + sharedBridge->dispatchFunction = [](auto callback) { + callback(); + }; + sharedBridge->evaluateJavaScriptFunction = [context] (const auto source) { + app->dispatch([=] () { + auto _ = jsc_context_evaluate(context, source.c_str(), source.size()); + }); + }; + sharedBridge->init(); + } + + return sharedBridge; + } static void onMessageResolver ( JSCValue* resolve, @@ -24,51 +47,44 @@ extern "C" { IPC::Message* message ) { auto context = jsc_value_get_context(resolve); + auto bridge = getSharedBridge(context); auto app = App::sharedApplication(); - if (bridge == nullptr) { - g_object_ref(context); - auto options = IPC::Bridge::Options(app->userConfig); - bridge = std::make_shared<IPC::Bridge>(app->core, options); - bridge->evaluateJavaScriptFunction = [context] (const auto source) { - auto _ = jsc_context_evaluate(context, source.c_str(), source.size()); - }; - bridge->init(); - } - auto routed = bridge->route(message->str(), message->buffer.bytes, message->buffer.size, [=](auto result) { - if (result.post.body != nullptr) { - auto array = jsc_value_new_typed_array( - context, - JSC_TYPED_ARRAY_UINT8, - result.post.length - ); + app->dispatch([=] () { + if (result.post.body != nullptr) { + auto array = jsc_value_new_typed_array( + context, + JSC_TYPED_ARRAY_UINT8, + result.post.length + ); - gsize size = 0; - auto bytes = jsc_value_typed_array_get_data(array, &size); - memcpy(bytes, result.post.body.get(), size); + gsize size = 0; + auto bytes = jsc_value_typed_array_get_data(array, &size); + memcpy(bytes, result.post.body.get(), size); - auto _ = jsc_value_function_call( - resolve, - JSC_TYPE_VALUE, - array, - G_TYPE_NONE - ); - } else { - auto json = result.json().str(); - if (json.size() > 0) { - auto _ = jsc_value_function_call( + const auto _ = jsc_value_function_call( resolve, JSC_TYPE_VALUE, - jsc_value_new_string(context, json.c_str()), + array, G_TYPE_NONE ); + } else { + const auto json = result.json().str(); + if (json.size() > 0) { + auto _ = jsc_value_function_call( + resolve, + JSC_TYPE_VALUE, + jsc_value_new_string(context, json.c_str()), + G_TYPE_NONE + ); + } } - } - g_object_unref(context); - g_object_unref(resolve); - g_object_unref(reject); + g_object_unref(context); + g_object_unref(resolve); + g_object_unref(reject); + }); }); if (routed) { @@ -76,7 +92,7 @@ extern "C" { g_object_ref(resolve); g_object_ref(reject); } else { - auto json = JSON::Object::Entries { + const auto json = JSON::Object::Entries { {"err", JSON::Object::Entries { {"message", "Not found"}, {"type", "NotFoundError"}, @@ -84,7 +100,7 @@ extern "C" { }} }; - auto _ = jsc_value_function_call( + const auto _ = jsc_value_function_call( resolve, JSC_TYPE_VALUE, jsc_value_new_string(context, JSON::Object(json).str().c_str()), @@ -99,6 +115,70 @@ extern "C" { auto context = reinterpret_cast<JSCContext*>(userData); auto Promise = jsc_context_get_value(context, "Promise"); auto message = new IPC::Message(source); + + if (jsc_value_is_typed_array(value)) { + auto bytes = jsc_value_typed_array_get_data(value, &message->buffer.size); + message->buffer.bytes = std::make_shared<char[]>(message->buffer.size); + memcpy( + message->buffer.bytes.get(), + bytes, + message->buffer.size + ); + } + + if (message->get("__sync__") == "true") { + auto bridge = getSharedBridge(context); + auto app = App::sharedApplication(); + auto semaphore = new std::binary_semaphore{0}; + + IPC::Result returnResult; + + auto routed = bridge->route( + message->str(), + message->buffer.bytes, + message->buffer.size, + [&returnResult, &semaphore] (auto result) mutable { + returnResult = std::move(result); + semaphore->release(); + } + ); + + semaphore->acquire(); + + delete semaphore; + delete message; + + if (routed) { + if (returnResult.post.body != nullptr) { + auto array = jsc_value_new_typed_array( + context, + JSC_TYPED_ARRAY_UINT8, + returnResult.post.length + ); + + gsize size = 0; + auto bytes = jsc_value_typed_array_get_data(array, &size); + memcpy(bytes, returnResult.post.body.get(), size); + return array; + } else { + auto json = returnResult.json().str(); + if (json.size() > 0) { + return jsc_value_new_string(context, json.c_str()); + } + } + } + + const auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", "Not found"}, + {"type", "NotFoundError"}, + {"source", source} + }} + }; + + return jsc_value_new_string(context, JSON::Object(json).str().c_str()); + } + auto resolver = jsc_value_new_function( context, nullptr, @@ -111,16 +191,6 @@ extern "C" { JSC_TYPE_VALUE ); - if (jsc_value_is_typed_array(value)) { - auto bytes = jsc_value_typed_array_get_data(value, &message->buffer.size); - message->buffer.bytes = std::make_shared<char[]>(message->buffer.size); - memcpy( - message->buffer.bytes.get(), - bytes, - message->buffer.size - ); - } - auto promise = jsc_value_constructor_call( Promise, JSC_TYPE_VALUE, @@ -196,9 +266,12 @@ extern "C" { } } - static App app(0); + Core::Options options; + options.dedicatedLoopThread = true; + static App app(std::move(std::make_shared<Core>(options))); auto userConfig = getUserConfig(); auto cwd = userConfig["web-process-extension_cwd"]; + if (cwd.size() > 0) { setcwd(cwd); uv_chdir(cwd.c_str()); From 18cbd4635c9b76ea6a90f04c92449fa782fa01c8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 14 Jun 2024 16:41:56 +0200 Subject: [PATCH 0821/1178] refactor(api/fs): more sync APIs --- api/fs/dir.js | 78 ++++++++++++++++- api/fs/handle.js | 5 +- api/fs/index.js | 216 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 285 insertions(+), 14 deletions(-) diff --git a/api/fs/dir.js b/api/fs/dir.js index 198ef8c37e..83cc73c84c 100644 --- a/api/fs/dir.js +++ b/api/fs/dir.js @@ -1,5 +1,6 @@ import { DirectoryHandle } from './handle.js' import { Buffer } from '../buffer.js' +import { clamp } from '../util.js' import { UV_DIRENT_UNKNOWN, UV_DIRENT_FILE, @@ -10,6 +11,9 @@ import { UV_DIRENT_CHAR, UV_DIRENT_BLOCK } from './constants.js' +import fds from './fds.js' + +import ipc from '../ipc.js' import * as exports from './dir.js' @@ -83,7 +87,7 @@ export class Dir { * @param {object|function} options * @param {function=} callback */ - async close (options = null, callback) { + async close (options = null, callback = null) { if (typeof options === 'function') { callback = options options = {} @@ -102,11 +106,26 @@ export class Dir { return await this.handle?.close(options) } + /** + * Closes container and underlying handle + * synchronously. + * @param {object=} [options] + */ + closeSync (options = null) { + const { id } = this.handle + const result = ipc.sendSync('fs.closedir', { id }, options) + if (result.err) { + throw result.err + } + + fds.release(id, false) + } + /** * Reads and returns directory entry. * @param {object|function} options * @param {function=} callback - * @return {Dirent|string} + * @return {Promise<Dirent[]|string[]>} */ async read (options, callback) { if (typeof options === 'function') { @@ -161,6 +180,61 @@ export class Dir { return results } + /** + * Reads and returns directory entry synchronously. + * @param {object|function} options + * @return {Dirent[]|string[]} + */ + readSync (options = null) { + const { encoding } = this + const { id } = this.handle + const entries = clamp( + options?.entries || DirectoryHandle.MAX_ENTRIES, + 1, // MIN_ENTRIES + DirectoryHandle.MAX_ENTRIES + ) + + const result = ipc.sendSync('fs.readdir', { id, entries }, options) + + if (result.err) { + throw result.err + } + + const results = result.data + .map((entry) => ({ + type: entry.type, + name: decodeURIComponent(entry.name) + })) + .map((result) => { + const { name } = result + + if (this.withFileTypes) { + result = Dirent.from(result) + + if (encoding === 'buffer') { + result.name = Buffer.from(name) + } else { + result.name = Buffer.from(name).toString(encoding) + } + return result + } + + if (encoding === 'buffer') { + return Buffer.from(name) + } else { + return Buffer.from(name).toString(encoding) + } + }) + + if (results.length === 1) { + return results[0] + } else if (results.length === 0) { + return null + } + + return results + } + /** * AsyncGenerator which yields directory entries. * @param {object=} options diff --git a/api/fs/handle.js b/api/fs/handle.js index 67cae637da..31317e99c9 100644 --- a/api/fs/handle.js +++ b/api/fs/handle.js @@ -871,9 +871,10 @@ export class DirectoryHandle extends EventEmitter { /** * Creates a `FileHandle` from a given `id` or `fd` * @param {string|number|DirectoryHandle|object} id + * @param {object} options * @return {DirectoryHandle} */ - static from (id) { + static from (id, options) { if (id?.id) { return this.from(id.id) } else if (id?.fd) { @@ -884,7 +885,7 @@ export class DirectoryHandle extends EventEmitter { throw new Error('Invalid file descriptor for directory handle.') } - return new this({ id }) + return new this({ id, ...options }) } /** diff --git a/api/fs/index.js b/api/fs/index.js index c072d0eb10..a61ae091ab 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -306,6 +306,19 @@ export function close (fd, callback) { } } +/** + * Synchronously close a file descriptor. + * @param {number} fd - fd + */ +export function closeSync (fd) { + const id = fds.get(fd) || fd + const result = ipc.sendSync('fs.close', { id }) + if (result.err) { + throw result.err + } + fds.release(id) +} + /** * Asynchronously copies `src` to `dest` calling `callback` upon success or error. * @param {string} src - The source file path. @@ -709,6 +722,51 @@ export function open (path, flags = 'r', mode = 0o666, options = null, callback) .catch((err) => callback(err)) } +/** + * Synchronously open a file. + * @param {string | Buffer | URL} path + * @param {string?} [flags = 'r'] + * @param {string?} [mode = 0o666] + * @param {object?|function?} [options] + */ +export function openSync (path, flags = 'r', mode = 0o666, options = null) { + if (typeof flags === 'object') { + options = flags + flags = FileHandle.DEFAULT_OPEN_FLAGS + mode = FileHandle.DEFAULT_OPEN_MODE + } + + if (typeof mode === 'object') { + options = mode + flags = FileHandle.DEFAULT_OPEN_FLAGS + mode = FileHandle.DEFAULT_OPEN_MODE + } + + path = normalizePath(path) + + const id = String(options?.id || rand64()) + const result = ipc.sendSync('fs.open', { + id, + mode, + path, + flags + }, { + ...options + }) + + if (result.err) { + throw result.err + } + + if (result.data?.fd) { + fds.set(id, result.data.fd, 'file') + } else { + fds.set(id, id, 'file') + } + + return result.data?.fd || id +} + /** * Asynchronously open a directory calling `callback` upon success or error. * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} @@ -736,6 +794,32 @@ export function opendir (path, options = {}, callback) { .catch((err) => callback(err)) } +/** + * Synchronously open a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL} path + * @param {object?|function(Error?, Dir?)} [options] + * @param {string?} [options.encoding = 'utf8'] + * @param {boolean?} [options.withFileTypes = false] + * @return {Dir} + */ +export function opendirSync (path, options = {}) { + path = normalizePath(path) + // @ts-ignore + const id = String(options?.id || rand64()) + const result = ipc.sendSync('fs.opendir', { id, path }, options) + + if (result.err) { + throw result.err + } + + fds.set(id, id, 'directory') + + // @ts-ignore + const handle = new DirectoryHandle({ id, path }) + return new Dir(handle, options) +} + /** * Asynchronously read from an open file descriptor. * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} @@ -859,6 +943,36 @@ export function readdir (path, options = {}, callback) { .catch((err) => callback(err)) } +/** + * Synchronously read all entries in a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL } path + * @param {object?|function(Error?, object[])} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {boolean?} [options.withFileTypes ? false] + */ +export function readdirSync (path, options = {}) { + options = { + entries: DirectoryHandle.MAX_ENTRIES, + withFileTypes: false, + ...options + } + const dir = opendirSync(path, options) + const entries = [] + + do { + const entry = dir.readSync(options) + if (!entry || entry.length === 0) { + break + } + + entries.push(...[].concat(entry)) + } while (true) + + dir.closeSync() + return entries +} + /** * @param {string | Buffer | URL | number } path * @param {object?|function(Error?, Buffer?)} [options] @@ -904,30 +1018,36 @@ export function readFile (path, options = {}, callback) { } /** - * @param {string | Buffer | URL | number } path + * @param {string|Buffer|URL|number} path + * @param {{ encoding?: string = 'utf8', flags?: string = 'r'}} [options] * @param {object?|function(Error?, Buffer?)} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] * @param {AbortSignal?} [options.signal] */ -export function readFileSync (path, options = {}) { +export function readFileSync (path, options = null) { if (typeof options === 'string') { options = { encoding: options } } path = normalizePath(path) - options = { flags: 'r', ...options } + options = { + flags: 'r', + encoding: options?.encoding ?? 'utf8', + ...options + } let result = null - const id = String(options?.id || rand64()) const stats = statSync(path) + const flags = normalizeFlags(options.flags) + const mode = FileHandle.DEFAULT_OPEN_MODE + // @ts-ignore + const id = String(options?.id || rand64()) result = ipc.sendSync('fs.open', { + mode, + flags, id, - mode: FileHandle.DEFAULT_OPEN_MODE, - path, - flags: normalizeFlags(options.flags) + path }, options) if (result.err) { @@ -1000,6 +1120,22 @@ export function realpath (path, callback) { }).catch(callback) } +/** + * Computes real path for `path` + * @param {string} path + */ +export function realpathSync (path) { + if (typeof path !== 'string') { + throw new TypeError('The argument \'path\' must be a string') + } + + const result = ipc.sendSync('fs.realpath', { path }) + + if (result.err) { + throw result.err + } +} + /** * Renames file or directory at `src` to `dest`. * @param {string} src @@ -1027,6 +1163,30 @@ export function rename (src, dest, callback) { }) } +/** + * Renames file or directory at `src` to `dest`, synchronously. + * @param {string} src + * @param {string} dest + */ +export function renameSync (src, dest) { + src = normalizePath(src) + dest = normalizePath(dest) + + if (typeof src !== 'string') { + throw new TypeError('The argument \'path\' must be a string') + } + + if (typeof dest !== 'string') { + throw new TypeError('The argument \'dest\' must be a string') + } + + const result = ipc.sendSync('fs.rename', { src, dest }) + + if (result.err) { + throw result.err + } +} + /** * Removes directory at `path`. * @param {string} path @@ -1048,6 +1208,24 @@ export function rmdir (path, callback) { }) } +/** + * Removes directory at `path`, synchronously. + * @param {string} path + */ +export function rmdirSync (path) { + path = normalizePath(path) + + if (typeof path !== 'string') { + throw new TypeError('The argument \'path\' must be a string') + } + + const result = ipc.sendSync('fs.rmdir', { path }) + + if (result.err) { + throw result.err + } +} + /** * Synchronously get the stats of a file * @param {string | Buffer | URL | number } path - filename or file descriptor @@ -1055,7 +1233,7 @@ export function rmdir (path, callback) { * @param {string?} [options.encoding ? 'utf8'] * @param {string?} [options.flag ? 'r'] */ -export function statSync (path, options) { +export function statSync (path, options = null) { path = normalizePath(path) const result = ipc.sendSync('fs.stat', { path }) @@ -1206,6 +1384,24 @@ export function unlink (path, callback) { }).catch(callback) } +/** + * Unlinks (removes) file at `path`, synchronously. + * @param {string} path + */ +export function unlinkSync (path) { + path = normalizePath(path) + + if (typeof path !== 'string') { + throw new TypeError('The argument \'path\' must be a string') + } + + const result = ipc.sendSync('fs.unlink', { path }) + + if (result.err) { + throw result.err + } +} + /** * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} * @param {string | Buffer | URL | number } path - filename or file descriptor From 4f4a048cc7312a1c6c149bc6d41845efca66cc36 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 14 Jun 2024 16:42:30 +0200 Subject: [PATCH 0822/1178] refactor(api): clean up and docs --- api/README.md | 169 +- api/ai.js | 56 +- api/async/deferred.js | 1 - api/index.d.ts | 3423 ++++++++++++++++--------------- api/internal/primitives.js | 8 + api/ipc.js | 30 +- api/latica/index.js | 5 + api/service-worker/container.js | 5 +- 8 files changed, 1966 insertions(+), 1731 deletions(-) diff --git a/api/README.md b/api/README.md index e9daf2f16e..6e2ab536aa 100644 --- a/api/README.md +++ b/api/README.md @@ -908,7 +908,15 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L317) +## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L313) + +Synchronously close a file descriptor. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| fd | number | | false | fd | + +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L330) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -920,7 +928,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L349) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L362) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -931,7 +939,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L378) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L391) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -945,7 +953,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L423) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L436) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -959,7 +967,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L471) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L484) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -973,7 +981,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L498) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L511) Request that all data for the open file descriptor is flushed to the storage device. @@ -983,7 +991,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L520) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L533) Truncates the file up to `offset` bytes. @@ -993,7 +1001,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L548) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L561) Chages ownership of link at `path` with `uid` and `gid. @@ -1004,7 +1012,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L578) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L591) Creates a link to `dest` from `src`. @@ -1014,7 +1022,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L666) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L679) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1027,7 +1035,18 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L721) +## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L732) + +Synchronously open a file. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| flags | string? | r | true | | +| mode | string? | 0o666 | true | | +| options | object? \| function? | | true | | + +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L779) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1040,7 +1059,23 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L749) +## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L806) + +External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback +Synchronously open a directory. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| options | object? \| function(Error?, Dir?) | | true | | +| options.encoding | string? | utf8 | true | | +| options.withFileTypes | boolean? | false | true | | + +| Return Value | Type | Description | +| :--- | :--- | :--- | +| Not specified | Dir | | + +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L833) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1054,7 +1089,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L784) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L868) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1068,7 +1103,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L818) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L902) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1081,7 +1116,19 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L870) +## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L954) + +External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback +Synchronously read all entries in a directory. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string \| Buffer \| URL | | false | | +| options | object? \| function(Error?, object) | | true | | +| options.encoding ? utf8 | string? | | true | | +| options.withFileTypes ? false | boolean? | | true | | + +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L984) @@ -1094,19 +1141,18 @@ Asynchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L913) +## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1026) | Argument | Type | Default | Optional | Description | | :--- | :--- | :---: | :---: | :--- | -| path | string \| Buffer \| URL \| number | | false | | +| path | string \| Buffer \| URL \| number | | false | | +| } options | { encoding?: string = 'utf8', flags?: string = 'r' | | false | | | options | object? \| function(Error?, Buffer?) | | true | | -| options.encoding ? utf8 | string? | | true | | -| options.flag ? r | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L969) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1089) Reads link at `path` @@ -1115,7 +1161,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L989) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1109) Computes real path for `path` @@ -1124,7 +1170,15 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1009) +## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1127) + +Computes real path for `path` + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string | | false | | + +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1145) Renames file or directory at `src` to `dest`. @@ -1134,7 +1188,16 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1035) +## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1171) + +Renames file or directory at `src` to `dest`, synchronously. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| src | string | | false | | +| dest | string | | false | | + +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1195) Removes directory at `path`. @@ -1143,7 +1206,15 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1058) +## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1215) + +Removes directory at `path`, synchronously. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string | | false | | + +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1236) Synchronously get the stats of a file @@ -1154,7 +1225,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1078) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1256) Get the stats of a file @@ -1167,7 +1238,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1116) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1294) Get the stats of a symbolic link @@ -1180,7 +1251,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1150) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1328) Creates a symlink of `src` at `dest`. @@ -1189,7 +1260,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1193) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1371) Unlinks (removes) file at `path`. @@ -1198,7 +1269,15 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1220) +## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1391) + +Unlinks (removes) file at `path`, synchronously. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| path | string | | false | | + +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1416) @@ -1213,7 +1292,7 @@ Unlinks (removes) file at `path`. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1265) +## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1461) External docs: https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options Writes data to a file synchronously. @@ -1228,7 +1307,7 @@ Writes data to a file synchronously. | options.flag ? w | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1300) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1496) Watch for changes at `path` calling `callback` @@ -1609,7 +1688,7 @@ Watch for changes at `path` calling `callback` This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1143) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1166) Emit event to be dispatched on `window` object. @@ -1620,7 +1699,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1202) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1225) Sends an async IPC command request with parameters. @@ -2844,7 +2923,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L369) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L371) Shows a native save file dialog. @@ -2856,7 +2935,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L387) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L391) Shows a native directory dialog. @@ -2868,7 +2947,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L412) +### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L418) This is a high-level API that you should use instead of `ipc.request` when you want to send a message to another window or to the backend. @@ -2882,7 +2961,7 @@ This is a high-level API that you should use instead of `ipc.request` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L453) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L459) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -2895,7 +2974,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L472) +### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L478) Opens an URL in the default application associated with the URL protocol, such as 'https:' for the default web browser. @@ -2908,7 +2987,7 @@ Opens an URL in the default application associated with the URL protocol, | :--- | :--- | :--- | | Not specified | Promise<{ url: string | >} | -### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L487) +### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L493) Opens a file in the default file explorer. @@ -2920,7 +2999,7 @@ Opens a file in the default file explorer. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L502) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L508) Adds a listener to the window. @@ -2929,7 +3008,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L520) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L526) Adds a listener to the window. An alias for `addListener`. @@ -2938,7 +3017,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L537) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L543) Adds a listener to the window. The listener is removed after the first call. @@ -2947,7 +3026,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L553) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L559) Removes a listener from the window. @@ -2956,7 +3035,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L566) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L572) Removes all listeners from the window. @@ -2964,7 +3043,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L582) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L588) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/ai.js b/api/ai.js index 45e4baaaf4..f2661c4ad9 100644 --- a/api/ai.js +++ b/api/ai.js @@ -30,16 +30,16 @@ * }) * ``` */ -import ipc from './ipc.js' -import process from './process.js' -import gc from './gc.js' import { EventEmitter } from './events.js' import { rand64 } from './crypto.js' +import process from './process.js' +import ipc from './ipc.js' +import gc from './gc.js' + import * as exports from './ai.js' /** * A class to interact with large language models (using llama.cpp) - * @extends EventEmitter */ export class LLM extends EventEmitter { /** @@ -77,18 +77,19 @@ export class LLM extends EventEmitter { * consistent results in model outputs, important for reproducibility in experiments. * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, * reducing the risk of less likely, potentially nonsensical outputs. - * @param {float} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool + * @param {number} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool * to only those whose cumulative probability exceeds this value, enhancing output relevance. - * @param {float} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring + * @param {number} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring * that generated tokens have at least this likelihood of being relevant or coherent. - * @param {float} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how + * @param {number} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how * the model weights novel or unseen prompts during generation. * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. */ - constructor (options = {}) { + constructor (options = null) { super() + options = { ...options } if (!options.path) { throw new Error('expected a path to a valid model (.gguf)') } @@ -101,13 +102,17 @@ export class LLM extends EventEmitter { id: this.id, path: this.path, prompt: this.prompt, + // @ts-ignore antiprompt: options.antiprompt, - conversation: options.conversation === true, // Convert to boolean, more idiomatic than String(true/false) + // @ts-ignore + conversation: options.conversation === true, + // @ts-ignore chatml: options.chatml === true, + // @ts-ignore instruct: options.instruct === true, - n_ctx: options.n_ctx || 1024, // Simplified, assuming default value of 1024 if not specified + n_ctx: options.n_ctx || 1024, // simplified, assuming default value of 1024 if not specified n_threads: options.n_threads || 8, - temp: options.temp || 1.1, // Assuming `temp` should be a number, not a string + temp: options.temp || 1.1, // assuming `temp` should be a number, not a string max_tokens: options.max_tokens || 512, n_gpu_layers: options.n_gpu_layers || 32, n_keep: options.n_keep || 0, @@ -115,11 +120,11 @@ export class LLM extends EventEmitter { n_predict: options.n_predict || 0, grp_attn_n: options.grp_attn_n || 0, grp_attn_w: options.grp_attn_w || 0, - seed: options.seed || 0, // Default seed if not specified - top_k: options.top_k || 0, // Default top_k if not specified - tok_p: options.tok_p || 0.0, // Default tok_p if not specified - min_p: options.min_p || 0.0, // Default min_p if not specified - tfs_z: options.tfs_z || 0.0 // Default tfs_z if not specified + seed: options.seed || 0, + top_k: options.top_k || 0, + tok_p: options.tok_p || 0.0, + min_p: options.min_p || 0.0, + tfs_z: options.tfs_z || 0.0 } globalThis.addEventListener('data', event => { @@ -147,11 +152,14 @@ export class LLM extends EventEmitter { } }) - const result = ipc.request('ai.llm.create', opts) - - if (result.err) { - throw result.err - } + ipc.request('ai.llm.create', opts) + .then((result) => { + if (result.err) { + this.emit('error', result.err) + } + }, (err) => { + this.emit('error', err) + }) } /** @@ -159,7 +167,7 @@ export class LLM extends EventEmitter { * @returns {Promise<void>} A promise that resolves when the LLM stops. */ async stop () { - return ipc.request('ai.llm.stop', { id: this.id }) + return await ipc.request('ai.llm.stop', { id: this.id }) } /** @@ -170,7 +178,7 @@ export class LLM extends EventEmitter { args: [this.id, options], async handle (id) { if (process.env.DEBUG) { - console.warn('Closing Socket on garbage collection') + console.warn('Closing LLM on garbage collection') } await ipc.request('ai.llm.destroy', { id }, options) @@ -184,7 +192,7 @@ export class LLM extends EventEmitter { * @returns {Promise<any>} A promise that resolves with the response from the chat. */ async chat (message) { - return ipc.request('ai.llm.chat', { id: this.id, message }) + return await ipc.request('ai.llm.chat', { id: this.id, message }) } } diff --git a/api/async/deferred.js b/api/async/deferred.js index 25a447325d..e9b52b64f7 100644 --- a/api/async/deferred.js +++ b/api/async/deferred.js @@ -1,4 +1,3 @@ -/* global ErrorEvent */ import { ErrorEvent } from '../events.js' /** diff --git a/api/index.d.ts b/api/index.d.ts index 702d6327d6..e745d6a96e 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -1,477 +1,405 @@ -declare module "socket:os/constants" { - export type errno = number; - /** - * @typedef {number} errno - * @typedef {number} signal - */ - /** - * A container for all known "errno" constant values. - * Unsupported values have a default value of `0`. - */ - export const errno: any; - export type signal = number; - /** - * A container for all known "signal" constant values. - * Unsupported values have a default value of `0`. - */ - export const signal: any; - namespace _default { - export { errno }; - export { signal }; - } - export default _default; -} - -declare module "socket:errno" { - /** - * Converts an `errno` code to its corresponding string message. - * @param {import('./os/constants.js').errno} {code} - * @return {string} - */ - export function toString(code: any): string; - /** - * Gets the code for a given 'errno' name. - * @param {string|number} name - * @return {errno} - */ - export function getCode(name: string | number): errno; +declare module "socket:async/context" { /** - * Gets the name for a given 'errno' code - * @return {string} - * @param {string|number} code + * @module Async.AsyncContext + * + * Async Context for JavaScript based on the TC39 proposal. + * + * Example usage: + * ```js + * // `AsyncContext` is also globally available as `globalThis.AsyncContext` + * import AsyncContext from 'socket:async/context' + * + * const var = new AsyncContext.Variable() + * var.run('top', () => { + * console.log(var.get()) // 'top' + * queueMicrotask(() => { + * var.run('nested', () => { + * console.log(var.get()) // 'nested' + * }) + * }) + * }) + * ``` + * + * @see {@link https://tc39.es/proposal-async-context} + * @see {@link https://github.com/tc39/proposal-async-context} */ - export function getName(code: string | number): string; /** - * Gets the message for a 'errno' code. - * @param {number|string} code - * @return {string} + * @template T + * @typedef {{ + * name?: string, + * defaultValue?: T + * }} VariableOptions */ - export function getMessage(code: number | string): string; /** - * @typedef {import('./os/constants.js').errno} errno + * @callback AnyFunc + * @template T + * @this T + * @param {...any} args + * @returns {any} */ - export const E2BIG: any; - export const EACCES: any; - export const EADDRINUSE: any; - export const EADDRNOTAVAIL: any; - export const EAFNOSUPPORT: any; - export const EAGAIN: any; - export const EALREADY: any; - export const EBADF: any; - export const EBADMSG: any; - export const EBUSY: any; - export const ECANCELED: any; - export const ECHILD: any; - export const ECONNABORTED: any; - export const ECONNREFUSED: any; - export const ECONNRESET: any; - export const EDEADLK: any; - export const EDESTADDRREQ: any; - export const EDOM: any; - export const EDQUOT: any; - export const EEXIST: any; - export const EFAULT: any; - export const EFBIG: any; - export const EHOSTUNREACH: any; - export const EIDRM: any; - export const EILSEQ: any; - export const EINPROGRESS: any; - export const EINTR: any; - export const EINVAL: any; - export const EIO: any; - export const EISCONN: any; - export const EISDIR: any; - export const ELOOP: any; - export const EMFILE: any; - export const EMLINK: any; - export const EMSGSIZE: any; - export const EMULTIHOP: any; - export const ENAMETOOLONG: any; - export const ENETDOWN: any; - export const ENETRESET: any; - export const ENETUNREACH: any; - export const ENFILE: any; - export const ENOBUFS: any; - export const ENODATA: any; - export const ENODEV: any; - export const ENOENT: any; - export const ENOEXEC: any; - export const ENOLCK: any; - export const ENOLINK: any; - export const ENOMEM: any; - export const ENOMSG: any; - export const ENOPROTOOPT: any; - export const ENOSPC: any; - export const ENOSR: any; - export const ENOSTR: any; - export const ENOSYS: any; - export const ENOTCONN: any; - export const ENOTDIR: any; - export const ENOTEMPTY: any; - export const ENOTSOCK: any; - export const ENOTSUP: any; - export const ENOTTY: any; - export const ENXIO: any; - export const EOPNOTSUPP: any; - export const EOVERFLOW: any; - export const EPERM: any; - export const EPIPE: any; - export const EPROTO: any; - export const EPROTONOSUPPORT: any; - export const EPROTOTYPE: any; - export const ERANGE: any; - export const EROFS: any; - export const ESPIPE: any; - export const ESRCH: any; - export const ESTALE: any; - export const ETIME: any; - export const ETIMEDOUT: any; - export const ETXTBSY: any; - export const EWOULDBLOCK: any; - export const EXDEV: any; - export const strings: any; - export { constants }; - namespace _default { - export { constants }; - export { strings }; - export { toString }; - export { getCode }; - export { getMessage }; - } - export default _default; - export type errno = import("socket:os/constants").errno; - import { errno as constants } from "socket:os/constants"; -} - -declare module "socket:errors" { - export default exports; - export const ABORT_ERR: any; - export const ENCODING_ERR: any; - export const INVALID_ACCESS_ERR: any; - export const INDEX_SIZE_ERR: any; - export const NETWORK_ERR: any; - export const NOT_ALLOWED_ERR: any; - export const NOT_FOUND_ERR: any; - export const NOT_SUPPORTED_ERR: any; - export const OPERATION_ERR: any; - export const SECURITY_ERR: any; - export const TIMEOUT_ERR: any; /** - * An `AbortError` is an error type thrown in an `onabort()` level 0 - * event handler on an `AbortSignal` instance. + * `FrozenRevert` holds a frozen Mapping that will be simply restored + * when the revert is run. + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} */ - export class AbortError extends Error { + export class FrozenRevert { /** - * The code given to an `ABORT_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * `FrozenRevert` class constructor. + * @param {Mapping} mapping */ - static get code(): any; + constructor(mapping: Mapping); /** - * `AbortError` class constructor. - * @param {AbortSignal|string} reasonOrSignal - * @param {AbortSignal=} [signal] + * Restores (unchaged) mapping from this `FrozenRevert`. This function is + * called by `AsyncContext.Storage` when it reverts a current mapping to the + * previous state before a "fork". + * @param {Mapping=} [unused] + * @return {Mapping} */ - constructor(reason: any, signal?: AbortSignal | undefined, ...args: any[]); - signal: AbortSignal; - get name(): string; - get code(): string; + restore(unused?: Mapping | undefined): Mapping; + #private; } /** - * An `BadRequestError` is an error type thrown in an `onabort()` level 0 - * event handler on an `BadRequestSignal` instance. + * Revert holds the state on how to revert a change to the + * `AsyncContext.Storage` current `Mapping` + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} + * @template T */ - export class BadRequestError extends Error { + export class Revert<T> { /** - * The default code given to a `BadRequestError` + * `Revert` class constructor. + * @param {Mapping} mapping + * @param {Variable<T>} key */ - static get code(): number; + constructor(mapping: Mapping, key: Variable<T>); /** - * `BadRequestError` class constructor. - * @param {string} message - * @param {number} [code] + * @type {T|undefined} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + get previousVariable(): T; + /** + * Restores a mapping from this `Revert`. This function is called by + * `AsyncContext.Storage` when it reverts a current mapping to the + * previous state before a "fork". + * @param {Mapping} current + * @return {Mapping} + */ + restore(current: Mapping): Mapping; + #private; } /** - * An `EncodingError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * A container for all `AsyncContext.Variable` instances and snapshot state. + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/mapping.ts} */ - export class EncodingError extends Error { + export class Mapping { /** - * The code given to an `ENCODING_ERR` `DOMException`. + * `Mapping` class constructor. + * @param {Map<Variable<any>, any>} data */ - static get code(): any; + constructor(data: Map<Variable<any>, any>); /** - * `EncodingError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An error type derived from an `errno` code. - */ - export class ErrnoError extends Error { - static get code(): string; - static errno: any; - /** - * `ErrnoError` class constructor. - * @param {import('./errno').errno|string} code + * Freezes the `Mapping` preventing `AsyncContext.Variable` modifications with + * `set()` and `delete()`. */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); - get name(): string; - get code(): number; - #private; - } - /** - * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class FinalizationRegistryCallbackError extends Error { + freeze(): void; /** - * The default code given to an `FinalizationRegistryCallbackError` + * Returns `true` if the `Mapping` is frozen, otherwise `false`. + * @return {boolean} */ - static get code(): number; + isFrozen(): boolean; /** - * `FinalizationRegistryCallbackError` class constructor. - * @param {string} message - * @param {number} [code] + * Optionally returns a new `Mapping` if the current one is "frozen", + * otherwise it just returns the current instance. + * @return {Mapping} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `IllegalConstructorError` is an error type thrown when a constructor is - * called for a class constructor when it shouldn't be. - */ - export class IllegalConstructorError extends TypeError { + fork(): Mapping; /** - * The default code given to an `IllegalConstructorError` + * Returns `true` if the `Mapping` has a `AsyncContext.Variable` at `key`, + * otherwise `false. + * @template T + * @param {Variable<T>} key + * @return {boolean} */ - static get code(): number; + has<T>(key: Variable<T>): boolean; /** - * `IllegalConstructorError` class constructor. - * @param {string} message - * @param {number} [code] + * Gets an `AsyncContext.Variable` value at `key`. If not set, this function + * returns `undefined`. + * @template T + * @param {Variable<T>} key + * @return {boolean} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `IndexSizeError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class IndexSizeError extends Error { + get<T_1>(key: Variable<T_1>): boolean; /** - * The code given to an `INDEX_SIZE_ERR` `DOMException` + * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, + * then a "forked" (new) instance with the value set on it is returned, + * otherwise the current instance. + * @template T + * @param {Variable<T>} key + * @param {T} value + * @return {Mapping} */ - static get code(): any; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** - * `IndexSizeError` class constructor. - * @param {string} message - * @param {number} [code] + * Delete an `AsyncContext.Variable` value at `key`. + * If the `Mapping` is frozen, then a "forked" (new) instance is returned, + * otherwise the current instance. + * @template T + * @param {Variable<T>} key + * @param {T} value + * @return {Mapping} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + delete<T_3>(key: Variable<T_3>): Mapping; + #private; } - export const kInternalErrorCode: unique symbol; /** - * An `InternalError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * A container of all `AsyncContext.Variable` data. + * @ignore + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/storage.ts} */ - export class InternalError extends Error { + export class Storage { /** - * The default code given to an `InternalError` + * The current `Mapping` for this `AsyncContext`. + * @type {Mapping} */ - static get code(): number; + static "__#4@#current": Mapping; /** - * `InternalError` class constructor. - * @param {string} message - * @param {number} [code] + * Returns `true` if the current `Mapping` has a + * `AsyncContext.Variable` at `key`, + * otherwise `false. + * @template T + * @param {Variable<T>} key + * @return {boolean} */ - constructor(message: string, code?: number, ...args: any[]); - get name(): string; + static has<T>(key: Variable<T>): boolean; /** - * @param {number|string} + * Gets an `AsyncContext.Variable` value at `key` for the current `Mapping`. + * If not set, this function returns `undefined`. + * @template T + * @param {Variable<T>} key + * @return {T|undefined} */ - set code(code: string | number); + static get<T_1>(key: Variable<T_1>): T_1; /** - * @type {number|string} + * Set updates the `AsyncContext.Variable` with a new value and returns a + * revert action that allows the modification to be reversed in the future. + * @template T + * @param {Variable<T>} key + * @param {T} value + * @return {Revert<T>|FrozenRevert} */ - get code(): string | number; - [exports.kInternalErrorCode]: number; - } - /** - * An `InvalidAccessError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class InvalidAccessError extends Error { + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** - * The code given to an `INVALID_ACCESS_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` + * or `Revert` which can restore the storage state to the state at + * the time of the snapshot. + * @return {FrozenRevert} */ - static get code(): any; + static snapshot(): FrozenRevert; /** - * `InvalidAccessError` class constructor. - * @param {string} message - * @param {number} [code] + * Restores the storage `Mapping` state to state at the time the + * "revert" (`FrozenRevert` or `Revert`) was created. + * @template T + * @param {Revert<T>|FrozenRevert} revert */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + /** + * Switches storage `Mapping` state to the state at the time of a + * "snapshot". + * @param {FrozenRevert} snapshot + * @return {FrozenRevert} + */ + static switch(snapshot: FrozenRevert): FrozenRevert; } /** - * An `NetworkError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * `AsyncContext.Variable` is a container for a value that is associated with + * the current execution flow. The value is propagated through async execution + * flows, and can be snapshot and restored with Snapshot. + * @template T + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} */ - export class NetworkError extends Error { + export class Variable<T> { /** - * The code given to an `NETWORK_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * `Variable` class constructor. + * @param {VariableOptions<T>=} [options] */ - static get code(): any; + constructor(options?: VariableOptions<T> | undefined); + set defaultValue(defaultValue: T); /** - * `NetworkError` class constructor. - * @param {string} message - * @param {number} [code] + * @ignore */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `NotAllowedError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class NotAllowedError extends Error { + get defaultValue(): T; /** - * The code given to an `NOT_ALLOWED_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * @ignore */ - static get code(): any; + get revert(): FrozenRevert | Revert<T>; /** - * `NotAllowedError` class constructor. - * @param {string} message - * @param {number} [code] + * The name of this async context variable. + * @type {string} */ - constructor(message: string, ...args: any[]); get name(): string; - get code(): string; - } - /** - * An `NotFoundError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class NotFoundError extends Error { /** - * The code given to an `NOT_FOUND_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + * Executes a function `fn` with specified arguments, + * setting a new value to the current context before the call, + * and ensuring the environment is reverted back afterwards. + * The function allows for the modification of a specific context's + * state in a controlled manner, ensuring that any changes can be undone. + * @template T, F extends AnyFunc<null> + * @param {T} value + * @param {F} fn + * @param {...Parameters<F>} args + * @returns {ReturnType<F>} */ - static get code(): any; + run<T_1, F>(value: T_1, fn: F, ...args: Parameters<F>[]): ReturnType<F>; /** - * `NotFoundError` class constructor. - * @param {string} message - * @param {number} [code] + * Get the `AsyncContext.Variable` value. + * @template T + * @return {T|undefined} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + get<T_2>(): T_2; + #private; } /** - * An `NotSupportedError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * `AsyncContext.Snapshot` allows you to opaquely capture the current values of + * all `AsyncContext.Variable` instances and execute a function at a later time + * as if those values were still the current values (a snapshot and restore). + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} */ - export class NotSupportedError extends Error { + export class Snapshot { /** - * The code given to an `NOT_SUPPORTED_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `NotSupportedError` class constructor. - * @param {string} message - * @param {number} [code] + * Wraps a given function `fn` with additional logic to take a snapshot of + * `Storage` before invoking `fn`. Returns a new function with the same + * signature as `fn` that when called, will invoke `fn` with the current + * `this` context and provided arguments, after restoring the `Storage` + * snapshot. + * + * `AsyncContext.Snapshot.wrap` is a helper which captures the current values + * of all Variables and returns a wrapped function. When invoked, this + * wrapped function restores the state of all Variables and executes the + * inner function. + * + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshotwrap} + * + * @template F + * @param {F} fn + * @returns {F} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; - } - /** - * An `ModuleNotFoundError` is an error type thrown when an imported or - * required module is not found. - */ - export class ModuleNotFoundError extends exports.NotFoundError { + static wrap<F_1>(fn: F_1): F_1; /** - * `ModuleNotFoundError` class constructor. - * @param {string} message - * @param {string[]=} [requireStack] + * Runs the given function `fn` with arguments `args`, using a `null` + * context and the current snapshot. + * + * @template F extends AnyFunc<null> + * @param {F} fn + * @param {...Parameters<F>} args + * @returns {ReturnType<F>} */ - constructor(message: string, requireStack?: string[] | undefined); - requireStack: string[]; + run<F>(fn: F, ...args: Parameters<F>[]): ReturnType<F>; + #private; } /** - * An `OperationError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. + * `AsyncContext` container. */ - export class OperationError extends Error { + export class AsyncContext { /** - * The code given to an `OPERATION_ERR` `DOMException` + * `AsyncContext.Variable` is a container for a value that is associated with + * the current execution flow. The value is propagated through async execution + * flows, and can be snapshot and restored with Snapshot. + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} + * @type {typeof Variable} */ - static get code(): any; + static Variable: typeof Variable; /** - * `OperationError` class constructor. - * @param {string} message - * @param {number} [code] + * `AsyncContext.Snapshot` allows you to opaquely capture the current values of + * all `AsyncContext.Variable` instances and execute a function at a later time + * as if those values were still the current values (a snapshot and restore). + * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} + * @type {typeof Snapshot} */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + static Snapshot: typeof Snapshot; } - /** - * An `SecurityError` is an error type thrown when an internal exception - * has occurred, such as in the native IPC layer. - */ - export class SecurityError extends Error { - /** - * The code given to an `SECURITY_ERR` `DOMException` - */ - static get code(): any; - /** - * `SecurityError` class constructor. - * @param {string} message - * @param {number} [code] - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + export default AsyncContext; + export type VariableOptions<T> = { + name?: string; + defaultValue?: T; + }; + export type AnyFunc = () => any; +} + +declare module "socket:events" { + export const Event: { + new (type: string, eventInitDict?: EventInit): Event; + prototype: Event; + readonly NONE: 0; + readonly CAPTURING_PHASE: 1; + readonly AT_TARGET: 2; + readonly BUBBLING_PHASE: 3; + } | { + new (): {}; + }; + export const EventTarget: { + new (): {}; + }; + export const CustomEvent: { + new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; + prototype: CustomEvent<any>; + } | { + new (type: any, options: any): { + "__#7@#detail": any; + readonly detail: any; + }; + }; + export const MessageEvent: { + new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; + prototype: MessageEvent<any>; + } | { + new (type: any, options: any): { + "__#8@#detail": any; + "__#8@#data": any; + readonly detail: any; + readonly data: any; + }; + }; + export const ErrorEvent: { + new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; + prototype: ErrorEvent; + } | { + new (type: any, options: any): { + "__#9@#detail": any; + "__#9@#error": any; + readonly detail: any; + readonly error: any; + }; + }; + export default EventEmitter; + export function EventEmitter(): void; + export class EventEmitter { + _events: any; + _contexts: any; + _eventsCount: number; + _maxListeners: number; + setMaxListeners(n: any): this; + getMaxListeners(): any; + emit(type: any, ...args: any[]): boolean; + addListener(type: any, listener: any): any; + on(arg0: any, arg1: any): any; + prependListener(type: any, listener: any): any; + once(type: any, listener: any): this; + prependOnceListener(type: any, listener: any): this; + removeListener(type: any, listener: any): this; + off(type: any, listener: any): this; + removeAllListeners(type: any, ...args: any[]): this; + listeners(type: any): any[]; + rawListeners(type: any): any[]; + listenerCount(type: any): any; + eventNames(): (string | symbol)[]; } - /** - * An `TimeoutError` is an error type thrown when an operation timesout. - */ - export class TimeoutError extends Error { - /** - * The code given to an `TIMEOUT_ERR` `DOMException` - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} - */ - static get code(): any; - /** - * `TimeoutError` class constructor. - * @param {string} message - */ - constructor(message: string, ...args: any[]); - get name(): string; - get code(): string; + export namespace EventEmitter { + export { EventEmitter }; + export let defaultMaxListeners: number; + export function init(): void; + export function listenerCount(emitter: any, type: any): any; + export { once }; } - import * as exports from "socket:errors"; - + export function once(emitter: any, name: any): Promise<any>; } declare module "socket:buffer" { @@ -641,12 +569,12 @@ declare module "socket:url/urlpattern/urlpattern" { export { me as URLPattern }; var me: { new (t: {}, r: any, n: any): { - "__#3@#i": any; - "__#3@#n": {}; - "__#3@#t": {}; - "__#3@#e": {}; - "__#3@#s": {}; - "__#3@#l": boolean; + "__#11@#i": any; + "__#11@#n": {}; + "__#11@#t": {}; + "__#11@#e": {}; + "__#11@#s": {}; + "__#11@#l": boolean; test(t: {}, r: any): boolean; exec(t: {}, r: any): { inputs: any[] | {}[]; @@ -711,12 +639,12 @@ declare module "socket:url/index" { export function fileURLToPath(url: any): any; const URLPattern_base: { new (t: {}, r: any, n: any): { - "__#3@#i": any; - "__#3@#n": {}; - "__#3@#t": {}; - "__#3@#e": {}; - "__#3@#s": {}; - "__#3@#l": boolean; + "__#11@#i": any; + "__#11@#n": {}; + "__#11@#t": {}; + "__#11@#e": {}; + "__#11@#s": {}; + "__#11@#l": boolean; test(t: {}, r: any): boolean; exec(t: {}, r: any): { inputs: any[] | {}[]; @@ -750,988 +678,1447 @@ declare module "socket:url" { import URL from "socket:url/index"; } -declare module "socket:util/types" { - /** - * Returns `true` if input is a plan `Object` instance. - * @param {any} input - * @return {boolean} - */ - export function isPlainObject(input: any): boolean; +declare module "socket:ipc" { + export function maybeMakeError(error: any, caller: any): any; /** - * Returns `true` if input is an `AsyncFunction` - * @param {any} input - * @return {boolean} + * Parses `seq` as integer value + * @param {string|number} seq + * @param {object=} [options] + * @param {boolean} [options.bigint = false] + * @ignore */ - export function isAsyncFunction(input: any): boolean; + export function parseSeq(seq: string | number, options?: object | undefined): number | bigint; /** - * Returns `true` if input is an `Function` - * @param {any} input + * If `debug.enabled === true`, then debug output will be printed to console. + * @param {(boolean)} [enable] * @return {boolean} + * @ignore */ - export function isFunction(input: any): boolean; + export function debug(enable?: (boolean)): boolean; + export namespace debug { + let enabled: any; + function log(...args: any[]): any; + } /** - * Returns `true` if input is an `AsyncFunction` object. - * @param {any} input - * @return {boolean} + * Find transfers for an in worker global `postMessage` + * that is proxied to the main thread. + * @ignore */ - export function isAsyncFunctionObject(input: any): boolean; + export function findMessageTransfers(transfers: any, object: any): any; /** - * Returns `true` if input is an `Function` object. - * @param {any} input - * @return {boolean} + * @ignore */ - export function isFunctionObject(input: any): boolean; + export function postMessage(message: any, ...args: any[]): any; /** - * Always returns `false`. - * @param {any} input - * @return {boolean} + * Waits for the native IPC layer to be ready and exposed on the + * global window object. + * @ignore */ - export function isExternal(input: any): boolean; + export function ready(): Promise<any>; /** - * Returns `true` if input is a `Date` instance. - * @param {any} input - * @return {boolean} + * Sends a synchronous IPC command over XHR returning a `Result` + * upon success or error. + * @param {string} command + * @param {any?} [value] + * @param {object?} [options] + * @return {Result} + * @ignore */ - export function isDate(input: any): boolean; + export function sendSync(command: string, value?: any | null, options?: object | null, buffer?: any): Result; /** - * Returns `true` if input is an `arguments` object. - * @param {any} input - * @return {boolean} + * Emit event to be dispatched on `window` object. + * @param {string} name + * @param {any} value + * @param {EventTarget=} [target = window] + * @param {Object=} options */ - export function isArgumentsObject(input: any): boolean; + export function emit(name: string, value: any, target?: EventTarget | undefined, options?: any | undefined): Promise<void>; /** - * Returns `true` if input is a `BigInt` object. - * @param {any} input - * @return {boolean} + * Resolves a request by `seq` with possible value. + * @param {string} seq + * @param {any} value + * @ignore */ - export function isBigIntObject(input: any): boolean; + export function resolve(seq: string, value: any): Promise<void>; /** - * Returns `true` if input is a `Boolean` object. - * @param {any} input - * @return {boolean} + * Sends an async IPC command request with parameters. + * @param {string} command + * @param {any=} value + * @param {object=} [options] + * @param {boolean=} [options.cache=false] + * @param {boolean=} [options.bytes=false] + * @return {Promise<Result>} */ - export function isBooleanObject(input: any): boolean; + export function send(command: string, value?: any | undefined, options?: object | undefined): Promise<Result>; /** - * Returns `true` if input is a `Number` object. - * @param {any} input - * @return {boolean} + * Sends an async IPC command request with parameters and buffered bytes. + * @param {string} command + * @param {any=} value + * @param {(Buffer|Uint8Array|ArrayBuffer|string|Array)=} buffer + * @param {object=} options + * @ignore */ - export function isNumberObject(input: any): boolean; + export function write(command: string, value?: any | undefined, buffer?: (Buffer | Uint8Array | ArrayBuffer | string | any[]) | undefined, options?: object | undefined): Promise<any>; /** - * Returns `true` if input is a `String` object. - * @param {any} input - * @return {boolean} + * Sends an async IPC command request with parameters requesting a response + * with buffered bytes. + * @param {string} command + * @param {any=} value + * @param {object=} options + * @ignore */ - export function isStringObject(input: any): boolean; + export function request(command: string, value?: any | undefined, options?: object | undefined): Promise<any>; /** - * Returns `true` if input is a `Symbol` object. - * @param {any} input - * @return {boolean} + * Factory for creating a proxy based IPC API. + * @param {string} domain + * @param {(function|object)=} ctx + * @param {string=} [ctx.default] + * @return {Proxy} + * @ignore */ - export function isSymbolObject(input: any): boolean; + export function createBinding(domain: string, ctx?: (Function | object) | undefined): ProxyConstructor; /** - * Returns `true` if input is native `Error` instance. - * @param {any} input - * @return {boolean} + * Represents an OK IPC status. + * @ignore */ - export function isNativeError(input: any): boolean; + export const OK: 0; /** - * Returns `true` if input is a `RegExp` instance. - * @param {any} input - * @return {boolean} + * Represents an ERROR IPC status. + * @ignore */ - export function isRegExp(input: any): boolean; + export const ERROR: 1; /** - * Returns `true` if input is a `GeneratorFunction`. - * @param {any} input - * @return {boolean} + * Timeout in milliseconds for IPC requests. + * @ignore */ - export function isGeneratorFunction(input: any): boolean; + export const TIMEOUT: number; /** - * Returns `true` if input is an `AsyncGeneratorFunction`. - * @param {any} input - * @return {boolean} + * Symbol for the `ipc.debug.enabled` property + * @ignore */ - export function isAsyncGeneratorFunction(input: any): boolean; + export const kDebugEnabled: unique symbol; /** - * Returns `true` if input is an instance of a `Generator`. - * @param {any} input - * @return {boolean} + * @ignore */ - export function isGeneratorObject(input: any): boolean; + export class Headers extends globalThis.Headers { + /** + * @ignore + */ + static from(input: any): any; + /** + * @ignore + */ + get length(): number; + /** + * @ignore + */ + toJSON(): { + [k: string]: string; + }; + } + const Message_base: any; /** - * Returns `true` if input is a `Promise` instance. - * @param {any} input - * @return {boolean} + * A container for a IPC message based on a `ipc://` URI scheme. + * @ignore */ - export function isPromise(input: any): boolean; + export class Message extends Message_base { + [x: string]: any; + /** + * The expected protocol for an IPC message. + * @ignore + */ + static get PROTOCOL(): string; + /** + * Creates a `Message` instance from a variety of input. + * @param {string|URL|Message|Buffer|object} input + * @param {(object|string|URLSearchParams)=} [params] + * @param {(ArrayBuffer|Uint8Array|string)?} [bytes] + * @return {Message} + * @ignore + */ + static from(input: string | URL | Message | Buffer | object, params?: (object | string | URLSearchParams) | undefined, bytes?: (ArrayBuffer | Uint8Array | string) | null): Message; + /** + * Predicate to determine if `input` is valid for constructing + * a new `Message` instance. + * @param {string|URL|Message|Buffer|object} input + * @return {boolean} + * @ignore + */ + static isValidInput(input: string | URL | Message | Buffer | object): boolean; + /** + * `Message` class constructor. + * @protected + * @param {string|URL} input + * @param {(object|Uint8Array)?} [bytes] + * @ignore + */ + protected constructor(); + /** + * @type {Uint8Array?} + * @ignore + */ + bytes: Uint8Array | null; + /** + * Computed IPC message name. + * @type {string} + * @ignore + */ + get command(): string; + /** + * Computed IPC message name. + * @type {string} + * @ignore + */ + get name(): string; + /** + * Computed `id` value for the command. + * @type {string} + * @ignore + */ + get id(): string; + /** + * Computed `seq` (sequence) value for the command. + * @type {string} + * @ignore + */ + get seq(): string; + /** + * Computed message value potentially given in message parameters. + * This value is automatically decoded, but not treated as JSON. + * @type {string} + * @ignore + */ + get value(): string; + /** + * Computed `index` value for the command potentially referring to + * the window index the command is scoped to or originating from. If not + * specified in the message parameters, then this value defaults to `-1`. + * @type {number} + * @ignore + */ + get index(): number; + /** + * Computed value parsed as JSON. This value is `null` if the value is not present + * or it is invalid JSON. + * @type {object?} + * @ignore + */ + get json(): any; + /** + * Computed readonly object of message parameters. + * @type {object} + * @ignore + */ + get params(): any; + /** + * Gets unparsed message parameters. + * @type {Array<Array<string>>} + * @ignore + */ + get rawParams(): string[][]; + /** + * Returns computed parameters as entries + * @return {Array<Array<any>>} + * @ignore + */ + entries(): Array<Array<any>>; + /** + * Set a parameter `value` by `key`. + * @param {string} key + * @param {any} value + * @ignore + */ + set(key: string, value: any): any; + /** + * Get a parameter value by `key`. + * @param {string} key + * @param {any} defaultValue + * @return {any} + * @ignore + */ + get(key: string, defaultValue: any): any; + /** + * Delete a parameter by `key`. + * @param {string} key + * @return {boolean} + * @ignore + */ + delete(key: string): boolean; + /** + * Computed parameter keys. + * @return {Array<string>} + * @ignore + */ + keys(): Array<string>; + /** + * Computed parameter values. + * @return {Array<any>} + * @ignore + */ + values(): Array<any>; + /** + * Predicate to determine if parameter `key` is present in parameters. + * @param {string} key + * @return {boolean} + * @ignore + */ + has(key: string): boolean; + } /** - * Returns `true` if input is a `Map` instance. - * @param {any} input - * @return {boolean} - */ - export function isMap(input: any): boolean; - /** - * Returns `true` if input is a `Set` instance. - * @param {any} input - * @return {boolean} - */ - export function isSet(input: any): boolean; - /** - * Returns `true` if input is an instance of an `Iterator`. - * @param {any} input - * @return {boolean} - */ - export function isIterator(input: any): boolean; - /** - * Returns `true` if input is an instance of an `AsyncIterator`. - * @param {any} input - * @return {boolean} - */ - export function isAsyncIterator(input: any): boolean; - /** - * Returns `true` if input is an instance of a `MapIterator`. - * @param {any} input - * @return {boolean} - */ - export function isMapIterator(input: any): boolean; - /** - * Returns `true` if input is an instance of a `SetIterator`. - * @param {any} input - * @return {boolean} - */ - export function isSetIterator(input: any): boolean; - /** - * Returns `true` if input is a `WeakMap` instance. - * @param {any} input - * @return {boolean} - */ - export function isWeakMap(input: any): boolean; - /** - * Returns `true` if input is a `WeakSet` instance. - * @param {any} input - * @return {boolean} - */ - export function isWeakSet(input: any): boolean; - /** - * Returns `true` if input is an `ArrayBuffer` instance. - * @param {any} input - * @return {boolean} - */ - export function isArrayBuffer(input: any): boolean; - /** - * Returns `true` if input is an `DataView` instance. - * @param {any} input - * @return {boolean} - */ - export function isDataView(input: any): boolean; - /** - * Returns `true` if input is a `SharedArrayBuffer`. - * This will always return `false` if a `SharedArrayBuffer` - * type is not available. - * @param {any} input - * @return {boolean} - */ - export function isSharedArrayBuffer(input: any): boolean; - /** - * Not supported. This function will return `false` always. - * @param {any} input - * @return {boolean} - */ - export function isProxy(input: any): boolean; - /** - * Returns `true` if input looks like a module namespace object. - * @param {any} input - * @return {boolean} - */ - export function isModuleNamespaceObject(input: any): boolean; - /** - * Returns `true` if input is an `ArrayBuffer` of `SharedArrayBuffer`. - * @param {any} input - * @return {boolean} - */ - export function isAnyArrayBuffer(input: any): boolean; - /** - * Returns `true` if input is a "boxed" primitive. - * @param {any} input - * @return {boolean} + * A result type used internally for handling + * IPC result values from the native layer that are in the form + * of `{ err?, data? }`. The `data` and `err` properties on this + * type of object are in tuple form and be accessed at `[data?,err?]` + * @ignore */ - export function isBoxedPrimitive(input: any): boolean; + export class Result { + /** + * Creates a `Result` instance from input that may be an object + * like `{ err?, data? }`, an `Error` instance, or just `data`. + * @param {(object|Error|any)?} result + * @param {Error|object} [maybeError] + * @param {string} [maybeSource] + * @param {object|string|Headers} [maybeHeaders] + * @return {Result} + * @ignore + */ + static from(result: (object | Error | any) | null, maybeError?: Error | object, maybeSource?: string, maybeHeaders?: object | string | Headers): Result; + /** + * `Result` class constructor. + * @private + * @param {string?} [id = null] + * @param {Error?} [err = null] + * @param {object?} [data = null] + * @param {string?} [source = null] + * @param {(object|string|Headers)?} [headers = null] + * @ignore + */ + private constructor(); + /** + * The unique ID for this result. + * @type {string} + * @ignore + */ + id: string; + /** + * An optional error in the result. + * @type {Error?} + * @ignore + */ + err: Error | null; + /** + * Result data if given. + * @type {(string|object|Uint8Array)?} + * @ignore + */ + data: (string | object | Uint8Array) | null; + /** + * The source of this result. + * @type {string?} + * @ignore + */ + source: string | null; + /** + * Result headers, if given. + * @type {Headers?} + * @ignore + */ + headers: Headers | null; + /** + * Computed result length. + * @ignore + */ + get length(): any; + /** + * @ignore + */ + toJSON(): { + headers: { + [k: string]: string; + }; + source: string; + data: any; + err: { + name: string; + message: string; + stack?: string; + cause?: unknown; + type: any; + code: any; + }; + }; + /** + * Generator for an `Iterable` interface over this instance. + * @ignore + */ + [Symbol.iterator](): Generator<any, void, unknown>; + } /** - * Returns `true` if input is an `ArrayBuffer` view. - * @param {any} input - * @return {boolean} + * @ignore */ - export function isArrayBufferView(input: any): boolean; + export const primordials: any; + export default exports; + import { Buffer } from "socket:buffer"; + import { URL } from "socket:url/index"; + import * as exports from "socket:ipc"; + +} + +declare module "socket:os/constants" { + export type errno = number; /** - * Returns `true` if input is a `TypedArray` instance. - * @param {any} input - * @return {boolean} + * @typedef {number} errno + * @typedef {number} signal */ - export function isTypedArray(input: any): boolean; /** - * Returns `true` if input is an `Uint8Array` instance. - * @param {any} input - * @return {boolean} + * A container for all known "errno" constant values. + * Unsupported values have a default value of `0`. */ - export function isUint8Array(input: any): boolean; + export const errno: any; + export type signal = number; /** - * Returns `true` if input is an `Uint8ClampedArray` instance. - * @param {any} input - * @return {boolean} + * A container for all known "signal" constant values. + * Unsupported values have a default value of `0`. */ - export function isUint8ClampedArray(input: any): boolean; + export const signal: any; + namespace _default { + export { errno }; + export { signal }; + } + export default _default; +} + +declare module "socket:errno" { /** - * Returns `true` if input is an `Uint16Array` instance. - * @param {any} input - * @return {boolean} + * Converts an `errno` code to its corresponding string message. + * @param {import('./os/constants.js').errno} {code} + * @return {string} */ - export function isUint16Array(input: any): boolean; + export function toString(code: any): string; /** - * Returns `true` if input is an `Uint32Array` instance. - * @param {any} input - * @return {boolean} + * Gets the code for a given 'errno' name. + * @param {string|number} name + * @return {errno} */ - export function isUint32Array(input: any): boolean; + export function getCode(name: string | number): errno; /** - * Returns `true` if input is an Int8Array`` instance. - * @param {any} input - * @return {boolean} + * Gets the name for a given 'errno' code + * @return {string} + * @param {string|number} code */ - export function isInt8Array(input: any): boolean; + export function getName(code: string | number): string; /** - * Returns `true` if input is an `Int16Array` instance. - * @param {any} input - * @return {boolean} + * Gets the message for a 'errno' code. + * @param {number|string} code + * @return {string} */ - export function isInt16Array(input: any): boolean; + export function getMessage(code: number | string): string; /** - * Returns `true` if input is an `Int32Array` instance. - * @param {any} input - * @return {boolean} + * @typedef {import('./os/constants.js').errno} errno */ - export function isInt32Array(input: any): boolean; - /** - * Returns `true` if input is an `Float32Array` instance. - * @param {any} input - * @return {boolean} - */ - export function isFloat32Array(input: any): boolean; - /** - * Returns `true` if input is an `Float64Array` instance. - * @param {any} input - * @return {boolean} - */ - export function isFloat64Array(input: any): boolean; - /** - * Returns `true` if input is an `BigInt64Array` instance. - * @param {any} input - * @return {boolean} - */ - export function isBigInt64Array(input: any): boolean; - /** - * Returns `true` if input is an `BigUint64Array` instance. - * @param {any} input - * @return {boolean} - */ - export function isBigUint64Array(input: any): boolean; - /** - * @ignore - * @param {any} input - * @return {boolean} - */ - export function isKeyObject(input: any): boolean; - /** - * Returns `true` if input is a `CryptoKey` instance. - * @param {any} input - * @return {boolean} - */ - export function isCryptoKey(input: any): boolean; - /** - * Returns `true` if input is an `Array`. - * @param {any} input - * @return {boolean} - */ - export const isArray: any; - export default exports; - import * as exports from "socket:util/types"; - + export const E2BIG: any; + export const EACCES: any; + export const EADDRINUSE: any; + export const EADDRNOTAVAIL: any; + export const EAFNOSUPPORT: any; + export const EAGAIN: any; + export const EALREADY: any; + export const EBADF: any; + export const EBADMSG: any; + export const EBUSY: any; + export const ECANCELED: any; + export const ECHILD: any; + export const ECONNABORTED: any; + export const ECONNREFUSED: any; + export const ECONNRESET: any; + export const EDEADLK: any; + export const EDESTADDRREQ: any; + export const EDOM: any; + export const EDQUOT: any; + export const EEXIST: any; + export const EFAULT: any; + export const EFBIG: any; + export const EHOSTUNREACH: any; + export const EIDRM: any; + export const EILSEQ: any; + export const EINPROGRESS: any; + export const EINTR: any; + export const EINVAL: any; + export const EIO: any; + export const EISCONN: any; + export const EISDIR: any; + export const ELOOP: any; + export const EMFILE: any; + export const EMLINK: any; + export const EMSGSIZE: any; + export const EMULTIHOP: any; + export const ENAMETOOLONG: any; + export const ENETDOWN: any; + export const ENETRESET: any; + export const ENETUNREACH: any; + export const ENFILE: any; + export const ENOBUFS: any; + export const ENODATA: any; + export const ENODEV: any; + export const ENOENT: any; + export const ENOEXEC: any; + export const ENOLCK: any; + export const ENOLINK: any; + export const ENOMEM: any; + export const ENOMSG: any; + export const ENOPROTOOPT: any; + export const ENOSPC: any; + export const ENOSR: any; + export const ENOSTR: any; + export const ENOSYS: any; + export const ENOTCONN: any; + export const ENOTDIR: any; + export const ENOTEMPTY: any; + export const ENOTSOCK: any; + export const ENOTSUP: any; + export const ENOTTY: any; + export const ENXIO: any; + export const EOPNOTSUPP: any; + export const EOVERFLOW: any; + export const EPERM: any; + export const EPIPE: any; + export const EPROTO: any; + export const EPROTONOSUPPORT: any; + export const EPROTOTYPE: any; + export const ERANGE: any; + export const EROFS: any; + export const ESPIPE: any; + export const ESRCH: any; + export const ESTALE: any; + export const ETIME: any; + export const ETIMEDOUT: any; + export const ETXTBSY: any; + export const EWOULDBLOCK: any; + export const EXDEV: any; + export const strings: any; + export { constants }; + namespace _default { + export { constants }; + export { strings }; + export { toString }; + export { getCode }; + export { getMessage }; + } + export default _default; + export type errno = import("socket:os/constants").errno; + import { errno as constants } from "socket:os/constants"; } -declare module "socket:mime/index" { - /** - * Look up a MIME type in various MIME databases. - * @param {string} query - * @return {Promise<DatabaseQueryResult[]>} - */ - export function lookup(query: string): Promise<DatabaseQueryResult[]>; - /** - * Look up a MIME type in various MIME databases synchronously. - * @param {string} query - * @return {DatabaseQueryResult[]} - */ - export function lookupSync(query: string): DatabaseQueryResult[]; +declare module "socket:errors" { + export default exports; + export const ABORT_ERR: any; + export const ENCODING_ERR: any; + export const INVALID_ACCESS_ERR: any; + export const INDEX_SIZE_ERR: any; + export const NETWORK_ERR: any; + export const NOT_ALLOWED_ERR: any; + export const NOT_FOUND_ERR: any; + export const NOT_SUPPORTED_ERR: any; + export const OPERATION_ERR: any; + export const SECURITY_ERR: any; + export const TIMEOUT_ERR: any; /** - * A container for a database lookup query. + * An `AbortError` is an error type thrown in an `onabort()` level 0 + * event handler on an `AbortSignal` instance. */ - export class DatabaseQueryResult { - /** - * `DatabaseQueryResult` class constructor. - * @ignore - * @param {Database} database - * @param {string} name - * @param {string} mime - */ - constructor(database: Database, name: string, mime: string); + export class AbortError extends Error { /** - * @type {string} + * The code given to an `ABORT_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} */ - name: string; + static get code(): any; /** - * @type {string} + * `AbortError` class constructor. + * @param {AbortSignal|string} reasonOrSignal + * @param {AbortSignal=} [signal] */ - mime: string; - database: Database; + constructor(reason: any, signal?: AbortSignal | undefined, ...args: any[]); + signal: AbortSignal; + get name(): string; + get code(): string; } /** - * A container for MIME types by class (audio, video, text, etc) - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} + * An `BadRequestError` is an error type thrown in an `onabort()` level 0 + * event handler on an `BadRequestSignal` instance. */ - export class Database { + export class BadRequestError extends Error { /** - * `Database` class constructor. - * @param {string} name + * The default code given to a `BadRequestError` */ - constructor(name: string); + static get code(): number; /** - * The name of the MIME database. - * @type {string} + * `BadRequestError` class constructor. + * @param {string} message + * @param {number} [code] */ - name: string; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `EncodingError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class EncodingError extends Error { /** - * The URL of the MIME database. - * @type {URL} + * The code given to an `ENCODING_ERR` `DOMException`. */ - url: URL; + static get code(): any; /** - * The mapping of MIME name to the MIME "content type" - * @type {Map} + * `EncodingError` class constructor. + * @param {string} message + * @param {number} [code] */ - map: Map<any, any>; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An error type derived from an `errno` code. + */ + export class ErrnoError extends Error { + static get code(): string; + static errno: any; /** - * An index of MIME "content type" to the MIME name. - * @type {Map} + * `ErrnoError` class constructor. + * @param {import('./errno').errno|string} code */ - index: Map<any, any>; + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + get name(): string; + get code(): number; + #private; + } + /** + * An `FinalizationRegistryCallbackError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class FinalizationRegistryCallbackError extends Error { /** - * An enumeration of all database entries. - * @return {Array<Array<string>>} + * The default code given to an `FinalizationRegistryCallbackError` */ - entries(): Array<Array<string>>; + static get code(): number; /** - * Loads database MIME entries into internal map. - * @return {Promise} + * `FinalizationRegistryCallbackError` class constructor. + * @param {string} message + * @param {number} [code] */ - load(): Promise<any>; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `IllegalConstructorError` is an error type thrown when a constructor is + * called for a class constructor when it shouldn't be. + */ + export class IllegalConstructorError extends TypeError { /** - * Loads database MIME entries synchronously into internal map. + * The default code given to an `IllegalConstructorError` */ - loadSync(): void; + static get code(): number; /** - * Lookup MIME type by name or content type - * @param {string} query - * @return {Promise<DatabaseQueryResult[]>} + * `IllegalConstructorError` class constructor. + * @param {string} message + * @param {number} [code] */ - lookup(query: string): Promise<DatabaseQueryResult[]>; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } + /** + * An `IndexSizeError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. + */ + export class IndexSizeError extends Error { /** - * Lookup MIME type by name or content type synchronously. - * @param {string} query - * @return {Promise<DatabaseQueryResult[]>} + * The code given to an `INDEX_SIZE_ERR` `DOMException` */ - lookupSync(query: string): Promise<DatabaseQueryResult[]>; + static get code(): any; /** - * Queries database map and returns an array of results - * @param {string} query - * @return {DatabaseQueryResult[]} + * `IndexSizeError` class constructor. + * @param {string} message + * @param {number} [code] */ - query(query: string): DatabaseQueryResult[]; + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } + export const kInternalErrorCode: unique symbol; /** - * A database of MIME types for 'application/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} + * An `InternalError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const application: Database; + export class InternalError extends Error { + /** + * The default code given to an `InternalError` + */ + static get code(): number; + /** + * `InternalError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, code?: number, ...args: any[]); + get name(): string; + /** + * @param {number|string} + */ + set code(code: string | number); + /** + * @type {number|string} + */ + get code(): string | number; + [exports.kInternalErrorCode]: number; + } /** - * A database of MIME types for 'audio/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} + * An `InvalidAccessError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const audio: Database; + export class InvalidAccessError extends Error { + /** + * The code given to an `INVALID_ACCESS_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `InvalidAccessError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * A database of MIME types for 'font/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} + * An `NetworkError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const font: Database; + export class NetworkError extends Error { + /** + * The code given to an `NETWORK_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `NetworkError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * A database of MIME types for 'image/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} + * An `NotAllowedError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const image: Database; + export class NotAllowedError extends Error { + /** + * The code given to an `NOT_ALLOWED_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `NotAllowedError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * A database of MIME types for 'model/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} + * An `NotFoundError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const model: Database; + export class NotFoundError extends Error { + /** + * The code given to an `NOT_FOUND_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `NotFoundError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * A database of MIME types for 'multipart/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} + * An `NotSupportedError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const multipart: Database; + export class NotSupportedError extends Error { + /** + * The code given to an `NOT_SUPPORTED_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `NotSupportedError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * A database of MIME types for 'text/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} + * An `ModuleNotFoundError` is an error type thrown when an imported or + * required module is not found. */ - export const text: Database; + export class ModuleNotFoundError extends exports.NotFoundError { + /** + * `ModuleNotFoundError` class constructor. + * @param {string} message + * @param {string[]=} [requireStack] + */ + constructor(message: string, requireStack?: string[] | undefined); + requireStack: string[]; + } /** - * A database of MIME types for 'video/' content types - * @type {Database} - * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} + * An `OperationError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const video: Database; + export class OperationError extends Error { + /** + * The code given to an `OPERATION_ERR` `DOMException` + */ + static get code(): any; + /** + * `OperationError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; + } /** - * An array of known MIME databases. Custom databases can be added to this - * array in userspace for lookup with `mime.lookup()` - * @type {Database[]} + * An `SecurityError` is an error type thrown when an internal exception + * has occurred, such as in the native IPC layer. */ - export const databases: Database[]; - export class MIMEParams extends Map<any, any> { - constructor(); - constructor(entries?: readonly (readonly [any, any])[]); - constructor(); - constructor(iterable?: Iterable<readonly [any, any]>); - } - export class MIMEType { - constructor(input: any); - set type(value: any); - get type(): any; - set subtype(value: any); - get subtype(): any; - get essence(): string; - get params(): any; - toString(): string; - toJSON(): string; - #private; + export class SecurityError extends Error { + /** + * The code given to an `SECURITY_ERR` `DOMException` + */ + static get code(): any; + /** + * `SecurityError` class constructor. + * @param {string} message + * @param {number} [code] + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } - namespace _default { - export { Database }; - export { databases }; - export { lookup }; - export { lookupSync }; - export { MIMEParams }; - export { MIMEType }; - export { application }; - export { audio }; - export { font }; - export { image }; - export { model }; - export { multipart }; - export { text }; - export { video }; - } - export default _default; -} - -declare module "socket:mime" { - export * from "socket:mime/index"; - export default exports; - import * as exports from "socket:mime/index"; -} - -declare module "socket:util" { - export function debug(section: any): { - (...args: any[]): void; - enabled: boolean; - }; - export function hasOwnProperty(object: any, property: any): any; - export function isDate(object: any): boolean; - export function isTypedArray(object: any): boolean; - export function isArrayLike(input: any): boolean; - export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; - export function isNumber(value: any): boolean; - export function isBoolean(value: any): boolean; - export function isArrayBufferView(buf: any): boolean; - export function isAsyncFunction(object: any): boolean; - export function isArgumentsObject(object: any): boolean; - export function isEmptyObject(object: any): boolean; - export function isObject(object: any): boolean; - export function isUndefined(value: any): boolean; - export function isNull(value: any): boolean; - export function isNullOrUndefined(value: any): boolean; - export function isPrimitive(value: any): boolean; - export function isRegExp(value: any): boolean; - export function isPlainObject(object: any): boolean; - export function isArrayBuffer(object: any): boolean; - export function isBufferLike(object: any): boolean; - export function isFunction(value: any): boolean; - export function isErrorLike(error: any): boolean; - export function isClass(value: any): boolean; - export function isBuffer(value: any): boolean; - export function isPromiseLike(object: any): boolean; - export function toString(object: any): any; - export function toBuffer(object: any, encoding?: any): any; - export function toProperCase(string: any): any; - export function splitBuffer(buffer: any, highWaterMark: any): any[]; - export function clamp(value: any, min: any, max: any): number; - export function promisify(original: any): any; - export function inspect(value: any, options: any): any; - export namespace inspect { - let ignore: symbol; - let custom: symbol; - } - export function format(format: any, ...args: any[]): string; - export function parseJSON(string: any): any; - export function parseHeaders(headers: any): string[][]; - export function noop(): void; - export function isValidPercentageValue(input: any): boolean; - export function compareBuffers(a: any, b: any): any; - export function inherits(Constructor: any, Super: any): void; /** - * @ignore - * @param {string} source - * @return {boolean} + * An `TimeoutError` is an error type thrown when an operation timesout. */ - export function isESMSource(source: string): boolean; - export function deprecate(...args: any[]): void; - export { types }; - export const TextDecoder: { - new (label?: string, options?: TextDecoderOptions): TextDecoder; - prototype: TextDecoder; - }; - export const TextEncoder: { - new (): TextEncoder; - prototype: TextEncoder; - }; - export const isArray: any; - export const inspectSymbols: symbol[]; - export class IllegalConstructor { + export class TimeoutError extends Error { + /** + * The code given to an `TIMEOUT_ERR` `DOMException` + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMException} + */ + static get code(): any; + /** + * `TimeoutError` class constructor. + * @param {string} message + */ + constructor(message: string, ...args: any[]); + get name(): string; + get code(): string; } - export const MIMEType: typeof mime.MIMEType; - export const MIMEParams: typeof mime.MIMEParams; - export default exports; - import types from "socket:util/types"; - import mime from "socket:mime"; - import * as exports from "socket:util"; + import * as exports from "socket:errors"; } -declare module "socket:async/context" { +declare module "socket:util/types" { /** - * @module Async.AsyncContext - * - * Async Context for JavaScript based on the TC39 proposal. - * - * Example usage: - * ```js - * // `AsyncContext` is also globally available as `globalThis.AsyncContext` - * import AsyncContext from 'socket:async/context' - * - * const var = new AsyncContext.Variable() - * var.run('top', () => { - * console.log(var.get()) // 'top' - * queueMicrotask(() => { - * var.run('nested', () => { - * console.log(var.get()) // 'nested' - * }) - * }) - * }) - * ``` - * - * @see {@link https://tc39.es/proposal-async-context} - * @see {@link https://github.com/tc39/proposal-async-context} + * Returns `true` if input is a plan `Object` instance. + * @param {any} input + * @return {boolean} */ + export function isPlainObject(input: any): boolean; /** - * @template T - * @typedef {{ - * name?: string, - * defaultValue?: T - * }} VariableOptions + * Returns `true` if input is an `AsyncFunction` + * @param {any} input + * @return {boolean} */ + export function isAsyncFunction(input: any): boolean; /** - * @callback AnyFunc - * @template T - * @this T - * @param {...any} args - * @returns {any} + * Returns `true` if input is an `Function` + * @param {any} input + * @return {boolean} */ + export function isFunction(input: any): boolean; /** - * `FrozenRevert` holds a frozen Mapping that will be simply restored - * when the revert is run. - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} + * Returns `true` if input is an `AsyncFunction` object. + * @param {any} input + * @return {boolean} */ - export class FrozenRevert { - /** - * `FrozenRevert` class constructor. - * @param {Mapping} mapping - */ - constructor(mapping: Mapping); - /** - * Restores (unchaged) mapping from this `FrozenRevert`. This function is - * called by `AsyncContext.Storage` when it reverts a current mapping to the - * previous state before a "fork". - * @param {Mapping=} [unused] - * @return {Mapping} - */ - restore(unused?: Mapping | undefined): Mapping; - #private; - } + export function isAsyncFunctionObject(input: any): boolean; /** - * Revert holds the state on how to revert a change to the - * `AsyncContext.Storage` current `Mapping` - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/fork.ts} - * @template T + * Returns `true` if input is an `Function` object. + * @param {any} input + * @return {boolean} */ - export class Revert<T> { - /** - * `Revert` class constructor. - * @param {Mapping} mapping - * @param {Variable<T>} key - */ - constructor(mapping: Mapping, key: Variable<T>); - /** - * @type {T|undefined} - */ - get previousVariable(): T; - /** - * Restores a mapping from this `Revert`. This function is called by - * `AsyncContext.Storage` when it reverts a current mapping to the - * previous state before a "fork". - * @param {Mapping} current - * @return {Mapping} - */ - restore(current: Mapping): Mapping; - #private; - } + export function isFunctionObject(input: any): boolean; /** - * A container for all `AsyncContext.Variable` instances and snapshot state. - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/mapping.ts} + * Always returns `false`. + * @param {any} input + * @return {boolean} */ - export class Mapping { - /** - * `Mapping` class constructor. - * @param {Map<Variable<any>, any>} data - */ - constructor(data: Map<Variable<any>, any>); - /** - * Freezes the `Mapping` preventing `AsyncContext.Variable` modifications with - * `set()` and `delete()`. - */ - freeze(): void; - /** - * Returns `true` if the `Mapping` is frozen, otherwise `false`. - * @return {boolean} - */ - isFrozen(): boolean; - /** - * Optionally returns a new `Mapping` if the current one is "frozen", - * otherwise it just returns the current instance. - * @return {Mapping} - */ - fork(): Mapping; - /** - * Returns `true` if the `Mapping` has a `AsyncContext.Variable` at `key`, - * otherwise `false. - * @template T - * @param {Variable<T>} key - * @return {boolean} - */ - has<T>(key: Variable<T>): boolean; - /** - * Gets an `AsyncContext.Variable` value at `key`. If not set, this function - * returns `undefined`. - * @template T - * @param {Variable<T>} key - * @return {boolean} - */ - get<T_1>(key: Variable<T_1>): boolean; - /** - * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, - * then a "forked" (new) instance with the value set on it is returned, - * otherwise the current instance. - * @template T - * @param {Variable<T>} key - * @param {T} value - * @return {Mapping} - */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; - /** - * Delete an `AsyncContext.Variable` value at `key`. - * If the `Mapping` is frozen, then a "forked" (new) instance is returned, - * otherwise the current instance. - * @template T - * @param {Variable<T>} key - * @param {T} value - * @return {Mapping} - */ - delete<T_3>(key: Variable<T_3>): Mapping; - #private; - } + export function isExternal(input: any): boolean; + /** + * Returns `true` if input is a `Date` instance. + * @param {any} input + * @return {boolean} + */ + export function isDate(input: any): boolean; + /** + * Returns `true` if input is an `arguments` object. + * @param {any} input + * @return {boolean} + */ + export function isArgumentsObject(input: any): boolean; + /** + * Returns `true` if input is a `BigInt` object. + * @param {any} input + * @return {boolean} + */ + export function isBigIntObject(input: any): boolean; + /** + * Returns `true` if input is a `Boolean` object. + * @param {any} input + * @return {boolean} + */ + export function isBooleanObject(input: any): boolean; + /** + * Returns `true` if input is a `Number` object. + * @param {any} input + * @return {boolean} + */ + export function isNumberObject(input: any): boolean; + /** + * Returns `true` if input is a `String` object. + * @param {any} input + * @return {boolean} + */ + export function isStringObject(input: any): boolean; + /** + * Returns `true` if input is a `Symbol` object. + * @param {any} input + * @return {boolean} + */ + export function isSymbolObject(input: any): boolean; + /** + * Returns `true` if input is native `Error` instance. + * @param {any} input + * @return {boolean} + */ + export function isNativeError(input: any): boolean; + /** + * Returns `true` if input is a `RegExp` instance. + * @param {any} input + * @return {boolean} + */ + export function isRegExp(input: any): boolean; + /** + * Returns `true` if input is a `GeneratorFunction`. + * @param {any} input + * @return {boolean} + */ + export function isGeneratorFunction(input: any): boolean; + /** + * Returns `true` if input is an `AsyncGeneratorFunction`. + * @param {any} input + * @return {boolean} + */ + export function isAsyncGeneratorFunction(input: any): boolean; + /** + * Returns `true` if input is an instance of a `Generator`. + * @param {any} input + * @return {boolean} + */ + export function isGeneratorObject(input: any): boolean; + /** + * Returns `true` if input is a `Promise` instance. + * @param {any} input + * @return {boolean} + */ + export function isPromise(input: any): boolean; + /** + * Returns `true` if input is a `Map` instance. + * @param {any} input + * @return {boolean} + */ + export function isMap(input: any): boolean; + /** + * Returns `true` if input is a `Set` instance. + * @param {any} input + * @return {boolean} + */ + export function isSet(input: any): boolean; + /** + * Returns `true` if input is an instance of an `Iterator`. + * @param {any} input + * @return {boolean} + */ + export function isIterator(input: any): boolean; + /** + * Returns `true` if input is an instance of an `AsyncIterator`. + * @param {any} input + * @return {boolean} + */ + export function isAsyncIterator(input: any): boolean; + /** + * Returns `true` if input is an instance of a `MapIterator`. + * @param {any} input + * @return {boolean} + */ + export function isMapIterator(input: any): boolean; + /** + * Returns `true` if input is an instance of a `SetIterator`. + * @param {any} input + * @return {boolean} + */ + export function isSetIterator(input: any): boolean; + /** + * Returns `true` if input is a `WeakMap` instance. + * @param {any} input + * @return {boolean} + */ + export function isWeakMap(input: any): boolean; + /** + * Returns `true` if input is a `WeakSet` instance. + * @param {any} input + * @return {boolean} + */ + export function isWeakSet(input: any): boolean; + /** + * Returns `true` if input is an `ArrayBuffer` instance. + * @param {any} input + * @return {boolean} + */ + export function isArrayBuffer(input: any): boolean; + /** + * Returns `true` if input is an `DataView` instance. + * @param {any} input + * @return {boolean} + */ + export function isDataView(input: any): boolean; + /** + * Returns `true` if input is a `SharedArrayBuffer`. + * This will always return `false` if a `SharedArrayBuffer` + * type is not available. + * @param {any} input + * @return {boolean} + */ + export function isSharedArrayBuffer(input: any): boolean; + /** + * Not supported. This function will return `false` always. + * @param {any} input + * @return {boolean} + */ + export function isProxy(input: any): boolean; + /** + * Returns `true` if input looks like a module namespace object. + * @param {any} input + * @return {boolean} + */ + export function isModuleNamespaceObject(input: any): boolean; + /** + * Returns `true` if input is an `ArrayBuffer` of `SharedArrayBuffer`. + * @param {any} input + * @return {boolean} + */ + export function isAnyArrayBuffer(input: any): boolean; + /** + * Returns `true` if input is a "boxed" primitive. + * @param {any} input + * @return {boolean} + */ + export function isBoxedPrimitive(input: any): boolean; + /** + * Returns `true` if input is an `ArrayBuffer` view. + * @param {any} input + * @return {boolean} + */ + export function isArrayBufferView(input: any): boolean; + /** + * Returns `true` if input is a `TypedArray` instance. + * @param {any} input + * @return {boolean} + */ + export function isTypedArray(input: any): boolean; + /** + * Returns `true` if input is an `Uint8Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint8Array(input: any): boolean; + /** + * Returns `true` if input is an `Uint8ClampedArray` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint8ClampedArray(input: any): boolean; + /** + * Returns `true` if input is an `Uint16Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint16Array(input: any): boolean; + /** + * Returns `true` if input is an `Uint32Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isUint32Array(input: any): boolean; + /** + * Returns `true` if input is an Int8Array`` instance. + * @param {any} input + * @return {boolean} + */ + export function isInt8Array(input: any): boolean; + /** + * Returns `true` if input is an `Int16Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isInt16Array(input: any): boolean; + /** + * Returns `true` if input is an `Int32Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isInt32Array(input: any): boolean; + /** + * Returns `true` if input is an `Float32Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isFloat32Array(input: any): boolean; + /** + * Returns `true` if input is an `Float64Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isFloat64Array(input: any): boolean; + /** + * Returns `true` if input is an `BigInt64Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isBigInt64Array(input: any): boolean; + /** + * Returns `true` if input is an `BigUint64Array` instance. + * @param {any} input + * @return {boolean} + */ + export function isBigUint64Array(input: any): boolean; /** - * A container of all `AsyncContext.Variable` data. * @ignore - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/src/storage.ts} + * @param {any} input + * @return {boolean} */ - export class Storage { - /** - * The current `Mapping` for this `AsyncContext`. - * @type {Mapping} - */ - static "__#8@#current": Mapping; - /** - * Returns `true` if the current `Mapping` has a - * `AsyncContext.Variable` at `key`, - * otherwise `false. - * @template T - * @param {Variable<T>} key - * @return {boolean} - */ - static has<T>(key: Variable<T>): boolean; - /** - * Gets an `AsyncContext.Variable` value at `key` for the current `Mapping`. - * If not set, this function returns `undefined`. - * @template T - * @param {Variable<T>} key - * @return {T|undefined} - */ - static get<T_1>(key: Variable<T_1>): T_1; - /** - * Set updates the `AsyncContext.Variable` with a new value and returns a - * revert action that allows the modification to be reversed in the future. - * @template T - * @param {Variable<T>} key - * @param {T} value - * @return {Revert<T>|FrozenRevert} - */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + export function isKeyObject(input: any): boolean; + /** + * Returns `true` if input is a `CryptoKey` instance. + * @param {any} input + * @return {boolean} + */ + export function isCryptoKey(input: any): boolean; + /** + * Returns `true` if input is an `Array`. + * @param {any} input + * @return {boolean} + */ + export const isArray: any; + export default exports; + import * as exports from "socket:util/types"; + +} + +declare module "socket:mime/index" { + /** + * Look up a MIME type in various MIME databases. + * @param {string} query + * @return {Promise<DatabaseQueryResult[]>} + */ + export function lookup(query: string): Promise<DatabaseQueryResult[]>; + /** + * Look up a MIME type in various MIME databases synchronously. + * @param {string} query + * @return {DatabaseQueryResult[]} + */ + export function lookupSync(query: string): DatabaseQueryResult[]; + /** + * A container for a database lookup query. + */ + export class DatabaseQueryResult { /** - * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` - * or `Revert` which can restore the storage state to the state at - * the time of the snapshot. - * @return {FrozenRevert} + * `DatabaseQueryResult` class constructor. + * @ignore + * @param {Database} database + * @param {string} name + * @param {string} mime */ - static snapshot(): FrozenRevert; + constructor(database: Database, name: string, mime: string); /** - * Restores the storage `Mapping` state to state at the time the - * "revert" (`FrozenRevert` or `Revert`) was created. - * @template T - * @param {Revert<T>|FrozenRevert} revert + * @type {string} */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + name: string; /** - * Switches storage `Mapping` state to the state at the time of a - * "snapshot". - * @param {FrozenRevert} snapshot - * @return {FrozenRevert} + * @type {string} */ - static switch(snapshot: FrozenRevert): FrozenRevert; + mime: string; + database: Database; } /** - * `AsyncContext.Variable` is a container for a value that is associated with - * the current execution flow. The value is propagated through async execution - * flows, and can be snapshot and restored with Snapshot. - * @template T - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} + * A container for MIME types by class (audio, video, text, etc) + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml} */ - export class Variable<T> { + export class Database { /** - * `Variable` class constructor. - * @param {VariableOptions<T>=} [options] + * `Database` class constructor. + * @param {string} name */ - constructor(options?: VariableOptions<T> | undefined); - set defaultValue(defaultValue: T); + constructor(name: string); /** - * @ignore + * The name of the MIME database. + * @type {string} */ - get defaultValue(): T; + name: string; /** - * @ignore + * The URL of the MIME database. + * @type {URL} */ - get revert(): FrozenRevert | Revert<T>; + url: URL; /** - * The name of this async context variable. - * @type {string} + * The mapping of MIME name to the MIME "content type" + * @type {Map} */ - get name(): string; + map: Map<any, any>; /** - * Executes a function `fn` with specified arguments, - * setting a new value to the current context before the call, - * and ensuring the environment is reverted back afterwards. - * The function allows for the modification of a specific context's - * state in a controlled manner, ensuring that any changes can be undone. - * @template T, F extends AnyFunc<null> - * @param {T} value - * @param {F} fn - * @param {...Parameters<F>} args - * @returns {ReturnType<F>} + * An index of MIME "content type" to the MIME name. + * @type {Map} */ - run<T_1, F>(value: T_1, fn: F, ...args: Parameters<F>[]): ReturnType<F>; + index: Map<any, any>; /** - * Get the `AsyncContext.Variable` value. - * @template T - * @return {T|undefined} + * An enumeration of all database entries. + * @return {Array<Array<string>>} */ - get<T_2>(): T_2; - #private; - } - /** - * `AsyncContext.Snapshot` allows you to opaquely capture the current values of - * all `AsyncContext.Variable` instances and execute a function at a later time - * as if those values were still the current values (a snapshot and restore). - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} - */ - export class Snapshot { + entries(): Array<Array<string>>; + /** + * Loads database MIME entries into internal map. + * @return {Promise} + */ + load(): Promise<any>; /** - * Wraps a given function `fn` with additional logic to take a snapshot of - * `Storage` before invoking `fn`. Returns a new function with the same - * signature as `fn` that when called, will invoke `fn` with the current - * `this` context and provided arguments, after restoring the `Storage` - * snapshot. - * - * `AsyncContext.Snapshot.wrap` is a helper which captures the current values - * of all Variables and returns a wrapped function. When invoked, this - * wrapped function restores the state of all Variables and executes the - * inner function. - * - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshotwrap} - * - * @template F - * @param {F} fn - * @returns {F} + * Loads database MIME entries synchronously into internal map. */ - static wrap<F_1>(fn: F_1): F_1; + loadSync(): void; /** - * Runs the given function `fn` with arguments `args`, using a `null` - * context and the current snapshot. - * - * @template F extends AnyFunc<null> - * @param {F} fn - * @param {...Parameters<F>} args - * @returns {ReturnType<F>} + * Lookup MIME type by name or content type + * @param {string} query + * @return {Promise<DatabaseQueryResult[]>} */ - run<F>(fn: F, ...args: Parameters<F>[]): ReturnType<F>; - #private; - } - /** - * `AsyncContext` container. - */ - export class AsyncContext { + lookup(query: string): Promise<DatabaseQueryResult[]>; /** - * `AsyncContext.Variable` is a container for a value that is associated with - * the current execution flow. The value is propagated through async execution - * flows, and can be snapshot and restored with Snapshot. - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextvariable} - * @type {typeof Variable} + * Lookup MIME type by name or content type synchronously. + * @param {string} query + * @return {Promise<DatabaseQueryResult[]>} */ - static Variable: typeof Variable; + lookupSync(query: string): Promise<DatabaseQueryResult[]>; /** - * `AsyncContext.Snapshot` allows you to opaquely capture the current values of - * all `AsyncContext.Variable` instances and execute a function at a later time - * as if those values were still the current values (a snapshot and restore). - * @see {@link https://github.com/tc39/proposal-async-context/blob/master/README.md#asynccontextsnapshot} - * @type {typeof Snapshot} + * Queries database map and returns an array of results + * @param {string} query + * @return {DatabaseQueryResult[]} */ - static Snapshot: typeof Snapshot; + query(query: string): DatabaseQueryResult[]; } - export default AsyncContext; - export type VariableOptions<T> = { - name?: string; - defaultValue?: T; - }; - export type AnyFunc = () => any; + /** + * A database of MIME types for 'application/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#application} + */ + export const application: Database; + /** + * A database of MIME types for 'audio/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#audio} + */ + export const audio: Database; + /** + * A database of MIME types for 'font/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#font} + */ + export const font: Database; + /** + * A database of MIME types for 'image/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#image} + */ + export const image: Database; + /** + * A database of MIME types for 'model/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#model} + */ + export const model: Database; + /** + * A database of MIME types for 'multipart/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#multipart} + */ + export const multipart: Database; + /** + * A database of MIME types for 'text/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#text} + */ + export const text: Database; + /** + * A database of MIME types for 'video/' content types + * @type {Database} + * @see {@link https://www.iana.org/assignments/media-types/media-types.xhtml#video} + */ + export const video: Database; + /** + * An array of known MIME databases. Custom databases can be added to this + * array in userspace for lookup with `mime.lookup()` + * @type {Database[]} + */ + export const databases: Database[]; + export class MIMEParams extends Map<any, any> { + constructor(); + constructor(entries?: readonly (readonly [any, any])[]); + constructor(); + constructor(iterable?: Iterable<readonly [any, any]>); + } + export class MIMEType { + constructor(input: any); + set type(value: any); + get type(): any; + set subtype(value: any); + get subtype(): any; + get essence(): string; + get params(): any; + toString(): string; + toJSON(): string; + #private; + } + namespace _default { + export { Database }; + export { databases }; + export { lookup }; + export { lookupSync }; + export { MIMEParams }; + export { MIMEType }; + export { application }; + export { audio }; + export { font }; + export { image }; + export { model }; + export { multipart }; + export { text }; + export { video }; + } + export default _default; } -declare module "socket:events" { - export const Event: { - new (type: string, eventInitDict?: EventInit): Event; - prototype: Event; - readonly NONE: 0; - readonly CAPTURING_PHASE: 1; - readonly AT_TARGET: 2; - readonly BUBBLING_PHASE: 3; - } | { - new (): {}; - }; - export const EventTarget: { - new (): {}; - }; - export const CustomEvent: { - new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; - } | { - new (type: any, options: any): { - "__#11@#detail": any; - readonly detail: any; - }; - }; - export const MessageEvent: { - new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; - } | { - new (type: any, options: any): { - "__#12@#detail": any; - "__#12@#data": any; - readonly detail: any; - readonly data: any; - }; - }; - export const ErrorEvent: { - new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent; - prototype: ErrorEvent; - } | { - new (type: any, options: any): { - "__#13@#detail": any; - "__#13@#error": any; - readonly detail: any; - readonly error: any; - }; +declare module "socket:mime" { + export * from "socket:mime/index"; + export default exports; + import * as exports from "socket:mime/index"; +} + +declare module "socket:util" { + export function debug(section: any): { + (...args: any[]): void; + enabled: boolean; }; - export default EventEmitter; - export function EventEmitter(): void; - export class EventEmitter { - _events: any; - _contexts: any; - _eventsCount: number; - _maxListeners: number; - setMaxListeners(n: any): this; - getMaxListeners(): any; - emit(type: any, ...args: any[]): boolean; - addListener(type: any, listener: any): any; - on(arg0: any, arg1: any): any; - prependListener(type: any, listener: any): any; - once(type: any, listener: any): this; - prependOnceListener(type: any, listener: any): this; - removeListener(type: any, listener: any): this; - off(type: any, listener: any): this; - removeAllListeners(type: any, ...args: any[]): this; - listeners(type: any): any[]; - rawListeners(type: any): any[]; - listenerCount(type: any): any; - eventNames(): any; + export function hasOwnProperty(object: any, property: any): any; + export function isDate(object: any): boolean; + export function isTypedArray(object: any): boolean; + export function isArrayLike(input: any): boolean; + export function isError(object: any): boolean; + export function isSymbol(value: any): boolean; + export function isNumber(value: any): boolean; + export function isBoolean(value: any): boolean; + export function isArrayBufferView(buf: any): boolean; + export function isAsyncFunction(object: any): boolean; + export function isArgumentsObject(object: any): boolean; + export function isEmptyObject(object: any): boolean; + export function isObject(object: any): boolean; + export function isUndefined(value: any): boolean; + export function isNull(value: any): boolean; + export function isNullOrUndefined(value: any): boolean; + export function isPrimitive(value: any): boolean; + export function isRegExp(value: any): boolean; + export function isPlainObject(object: any): boolean; + export function isArrayBuffer(object: any): boolean; + export function isBufferLike(object: any): boolean; + export function isFunction(value: any): boolean; + export function isErrorLike(error: any): boolean; + export function isClass(value: any): boolean; + export function isBuffer(value: any): boolean; + export function isPromiseLike(object: any): boolean; + export function toString(object: any): any; + export function toBuffer(object: any, encoding?: any): any; + export function toProperCase(string: any): any; + export function splitBuffer(buffer: any, highWaterMark: any): any[]; + export function clamp(value: any, min: any, max: any): number; + export function promisify(original: any): any; + export function inspect(value: any, options: any): any; + export namespace inspect { + let ignore: symbol; + let custom: symbol; } - export namespace EventEmitter { - export { EventEmitter }; - export let defaultMaxListeners: number; - export function init(): void; - export function listenerCount(emitter: any, type: any): any; - export { once }; + export function format(format: any, ...args: any[]): string; + export function parseJSON(string: any): any; + export function parseHeaders(headers: any): string[][]; + export function noop(): void; + export function isValidPercentageValue(input: any): boolean; + export function compareBuffers(a: any, b: any): any; + export function inherits(Constructor: any, Super: any): void; + /** + * @ignore + * @param {string} source + * @return {boolean} + */ + export function isESMSource(source: string): boolean; + export function deprecate(...args: any[]): void; + export { types }; + export const TextDecoder: { + new (label?: string, options?: TextDecoderOptions): TextDecoder; + prototype: TextDecoder; + }; + export const TextEncoder: { + new (): TextEncoder; + prototype: TextEncoder; + }; + export const isArray: any; + export const inspectSymbols: symbol[]; + export class IllegalConstructor { } - export function once(emitter: any, name: any): Promise<any>; + export const ESM_TEST_REGEX: RegExp; + export const MIMEType: typeof mime.MIMEType; + export const MIMEParams: typeof mime.MIMEParams; + export default exports; + import types from "socket:util/types"; + import mime from "socket:mime"; + import * as exports from "socket:util"; + } declare module "socket:async/wrap" { @@ -4102,6 +4489,7 @@ declare module "socket:fs/constants" { export const UV_DIRENT_BLOCK: any; export const UV_FS_SYMLINK_DIR: any; export const UV_FS_SYMLINK_JUNCTION: any; + export const UV_FS_O_FILEMAP: any; export const O_RDONLY: any; export const O_WRONLY: any; export const O_RDWR: any; @@ -4461,9 +4849,10 @@ declare module "socket:fs/handle" { /** * Creates a `FileHandle` from a given `id` or `fd` * @param {string|number|DirectoryHandle|object} id + * @param {object} options * @return {DirectoryHandle} */ - static from(id: string | number | DirectoryHandle | object): DirectoryHandle; + static from(id: string | number | DirectoryHandle | object, options: object): DirectoryHandle; /** * Asynchronously open a directory. * @param {string | Buffer | URL} path @@ -4581,22 +4970,34 @@ declare module "socket:fs/dir" { * @param {function=} callback */ close(options?: object | Function, callback?: Function | undefined): Promise<any>; + /** + * Closes container and underlying handle + * synchronously. + * @param {object=} [options] + */ + closeSync(options?: object | undefined): void; /** * Reads and returns directory entry. * @param {object|function} options * @param {function=} callback - * @return {Dirent|string} + * @return {Promise<Dirent[]|string[]>} + */ + read(options: object | Function, callback?: Function | undefined): Promise<Dirent[] | string[]>; + /** + * Reads and returns directory entry synchronously. + * @param {object|function} options + * @return {Dirent[]|string[]} */ - read(options: object | Function, callback?: Function | undefined): Dirent | string; + readSync(options?: object | Function): Dirent[] | string[]; /** * AsyncGenerator which yields directory entries. * @param {object=} options */ - entries(options?: object | undefined): AsyncGenerator<any, void, unknown>; + entries(options?: object | undefined): AsyncGenerator<string | exports.Dirent, void, unknown>; /** * `for await (...)` AsyncGenerator support. */ - get [Symbol.asyncIterator](): (options?: object | undefined) => AsyncGenerator<any, void, unknown>; + get [Symbol.asyncIterator](): (options?: object | undefined) => AsyncGenerator<string | exports.Dirent, void, unknown>; } /** * A container for a directory entry. @@ -5129,7 +5530,7 @@ declare module "socket:fs/promises" { * @param {string?} [options.encoding = 'utf8'] * @param {boolean?} [options.withFileTypes = false] */ - export function readdir(path: string | Buffer | URL, options: object | null): Promise<any[]>; + export function readdir(path: string | Buffer | URL, options: object | null): Promise<(string | Dirent)[]>; /** * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options} * @param {string} path @@ -5223,11 +5624,11 @@ declare module "socket:fs/promises" { export type TypedArray = Uint8Array | Int8Array; import { FileHandle } from "socket:fs/handle"; import { Dir } from "socket:fs/dir"; + import { Dirent } from "socket:fs/dir"; import { Stats } from "socket:fs/stats"; import { Watcher } from "socket:fs/watcher"; import * as constants from "socket:fs/constants"; import { DirectoryHandle } from "socket:fs/handle"; - import { Dirent } from "socket:fs/dir"; import fds from "socket:fs/fds"; import { ReadStream } from "socket:fs/stream"; import { WriteStream } from "socket:fs/stream"; @@ -5307,6 +5708,11 @@ declare module "socket:fs/index" { * @param {function(Error?)?} [callback] */ export function close(fd: number, callback?: ((arg0: Error | null) => any) | null): void; + /** + * Synchronously close a file descriptor. + * @param {number} fd - fd + */ + export function closeSync(fd: number): void; /** * Asynchronously copies `src` to `dest` calling `callback` upon success or error. * @param {string} src - The source file path. @@ -5398,6 +5804,14 @@ declare module "socket:fs/index" { * @param {function(Error?, number?)?} [callback] */ export function open(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any, callback?: ((arg0: Error | null, arg1: number | null) => any) | null): void; + /** + * Synchronously open a file. + * @param {string | Buffer | URL} path + * @param {string?} [flags = 'r'] + * @param {string?} [mode = 0o666] + * @param {object?|function?} [options] + */ + export function openSync(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any): any; /** * Asynchronously open a directory calling `callback` upon success or error. * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} @@ -5408,6 +5822,16 @@ declare module "socket:fs/index" { * @param {function(Error?, Dir?)?} callback */ export function opendir(path: string | Buffer | URL, options: {}, callback: ((arg0: Error | null, arg1: Dir | null) => any) | null): void; + /** + * Synchronously open a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL} path + * @param {object?|function(Error?, Dir?)} [options] + * @param {string?} [options.encoding = 'utf8'] + * @param {boolean?} [options.withFileTypes = false] + * @return {Dir} + */ + export function opendirSync(path: string | Buffer | URL, options?: {}): Dir; /** * Asynchronously read from an open file descriptor. * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} @@ -5440,6 +5864,15 @@ declare module "socket:fs/index" { * @param {function(Error?, object[])} callback */ export function readdir(path: string | Buffer | URL, options: {}, callback: (arg0: Error | null, arg1: object[]) => any): void; + /** + * Synchronously read all entries in a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL } path + * @param {object?|function(Error?, object[])} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {boolean?} [options.withFileTypes ? false] + */ + export function readdirSync(path: string | Buffer | URL, options?: {}): any[]; /** * @param {string | Buffer | URL | number } path * @param {object?|function(Error?, Buffer?)} [options] @@ -5450,13 +5883,15 @@ declare module "socket:fs/index" { */ export function readFile(path: string | Buffer | URL | number, options: {}, callback: (arg0: Error | null, arg1: Buffer | null) => any): void; /** - * @param {string | Buffer | URL | number } path + * @param {string|Buffer|URL|number} path + * @param {{ encoding?: string = 'utf8', flags?: string = 'r'}} [options] * @param {object?|function(Error?, Buffer?)} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] * @param {AbortSignal?} [options.signal] */ - export function readFileSync(path: string | Buffer | URL | number, options?: {}): any; + export function readFileSync(path: string | Buffer | URL | number, options?: { + encoding?: string; + flags?: string; + }): any; /** * Reads link at `path` * @param {string} path @@ -5469,6 +5904,11 @@ declare module "socket:fs/index" { * @param {function(err, string)} callback */ export function realpath(path: string, callback: (arg0: err, arg1: string) => any): void; + /** + * Computes real path for `path` + * @param {string} path + */ + export function realpathSync(path: string): void; /** * Renames file or directory at `src` to `dest`. * @param {string} src @@ -5476,12 +5916,23 @@ declare module "socket:fs/index" { * @param {function} callback */ export function rename(src: string, dest: string, callback: Function): void; + /** + * Renames file or directory at `src` to `dest`, synchronously. + * @param {string} src + * @param {string} dest + */ + export function renameSync(src: string, dest: string): void; /** * Removes directory at `path`. * @param {string} path * @param {function} callback */ export function rmdir(path: string, callback: Function): void; + /** + * Removes directory at `path`, synchronously. + * @param {string} path + */ + export function rmdirSync(path: string): void; /** * Synchronously get the stats of a file * @param {string | Buffer | URL | number } path - filename or file descriptor @@ -5489,7 +5940,7 @@ declare module "socket:fs/index" { * @param {string?} [options.encoding ? 'utf8'] * @param {string?} [options.flag ? 'r'] */ - export function statSync(path: string | Buffer | URL | number, options: object | null): promises.Stats; + export function statSync(path: string | Buffer | URL | number, options?: object | null): promises.Stats; /** * Get the stats of a file * @param {string | Buffer | URL | number } path - filename or file descriptor @@ -5522,6 +5973,11 @@ declare module "socket:fs/index" { * @param {function} callback */ export function unlink(path: string, callback: Function): void; + /** + * Unlinks (removes) file at `path`, synchronously. + * @param {string} path + */ + export function unlinkSync(path: string): void; /** * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} * @param {string | Buffer | URL | number } path - filename or file descriptor @@ -5632,436 +6088,99 @@ declare module "socket:crypto" { */ export let webcrypto: any; /** - * A promise that resolves when all internals to be loaded/ready. - * @type {Promise} - */ - export const ready: Promise<any>; - /** - * Maximum total size of random bytes per page - */ - export const RANDOM_BYTES_QUOTA: number; - /** - * Maximum total size for random bytes. - */ - export const MAX_RANDOM_BYTES: 281474976710655; - /** - * Maximum total amount of allocated per page of bytes (max/quota) - */ - export const MAX_RANDOM_BYTES_PAGES: number; - export default exports; - export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - export namespace sodium { - let ready: Promise<any>; - } - import * as exports from "socket:crypto"; - -} - -declare module "socket:ipc" { - export function maybeMakeError(error: any, caller: any): any; - /** - * Parses `seq` as integer value - * @param {string|number} seq - * @param {object=} [options] - * @param {boolean} [options.bigint = false] - * @ignore - */ - export function parseSeq(seq: string | number, options?: object | undefined): number | bigint; - /** - * If `debug.enabled === true`, then debug output will be printed to console. - * @param {(boolean)} [enable] - * @return {boolean} - * @ignore - */ - export function debug(enable?: (boolean)): boolean; - export namespace debug { - let enabled: any; - function log(...args: any[]): any; - } - /** - * Find transfers for an in worker global `postMessage` - * that is proxied to the main thread. - * @ignore - */ - export function findMessageTransfers(transfers: any, object: any): any; - /** - * @ignore - */ - export function postMessage(message: any, ...args: any[]): any; - /** - * Waits for the native IPC layer to be ready and exposed on the - * global window object. - * @ignore - */ - export function ready(): Promise<any>; - /** - * Sends a synchronous IPC command over XHR returning a `Result` - * upon success or error. - * @param {string} command - * @param {any?} [value] - * @param {object?} [options] - * @return {Result} - * @ignore - */ - export function sendSync(command: string, value?: any | null, options?: object | null, buffer: any): Result; - /** - * Emit event to be dispatched on `window` object. - * @param {string} name - * @param {any} value - * @param {EventTarget=} [target = window] - * @param {Object=} options - */ - export function emit(name: string, value: any, target?: EventTarget | undefined, options?: any | undefined): Promise<void>; - /** - * Resolves a request by `seq` with possible value. - * @param {string} seq - * @param {any} value - * @ignore - */ - export function resolve(seq: string, value: any): Promise<void>; - /** - * Sends an async IPC command request with parameters. - * @param {string} command - * @param {any=} value - * @param {object=} [options] - * @param {boolean=} [options.cache=false] - * @param {boolean=} [options.bytes=false] - * @return {Promise<Result>} - */ - export function send(command: string, value?: any | undefined, options?: object | undefined): Promise<Result>; - /** - * Sends an async IPC command request with parameters and buffered bytes. - * @param {string} command - * @param {any=} value - * @param {(Buffer|Uint8Array|ArrayBuffer|string|Array)=} buffer - * @param {object=} options - * @ignore - */ - export function write(command: string, value?: any | undefined, buffer?: (Buffer | Uint8Array | ArrayBuffer | string | any[]) | undefined, options?: object | undefined): Promise<any>; - /** - * Sends an async IPC command request with parameters requesting a response - * with buffered bytes. - * @param {string} command - * @param {any=} value - * @param {object=} options - * @ignore - */ - export function request(command: string, value?: any | undefined, options?: object | undefined): Promise<any>; - /** - * Factory for creating a proxy based IPC API. - * @param {string} domain - * @param {(function|object)=} ctx - * @param {string=} [ctx.default] - * @return {Proxy} - * @ignore - */ - export function createBinding(domain: string, ctx?: (Function | object) | undefined): ProxyConstructor; - /** - * Represents an OK IPC status. - * @ignore - */ - export const OK: 0; - /** - * Represents an ERROR IPC status. - * @ignore - */ - export const ERROR: 1; - /** - * Timeout in milliseconds for IPC requests. - * @ignore - */ - export const TIMEOUT: number; - /** - * Symbol for the `ipc.debug.enabled` property - * @ignore - */ - export const kDebugEnabled: unique symbol; - /** - * @ignore - */ - export class Headers extends globalThis.Headers { - /** - * @ignore - */ - static from(input: any): any; - /** - * @ignore - */ - get length(): number; - /** - * @ignore - */ - toJSON(): { - [k: string]: string; - }; - } - const Message_base: any; + * A promise that resolves when all internals to be loaded/ready. + * @type {Promise} + */ + export const ready: Promise<any>; /** - * A container for a IPC message based on a `ipc://` URI scheme. - * @ignore + * Maximum total size of random bytes per page */ - export class Message extends Message_base { - [x: string]: any; - /** - * The expected protocol for an IPC message. - * @ignore - */ - static get PROTOCOL(): string; - /** - * Creates a `Message` instance from a variety of input. - * @param {string|URL|Message|Buffer|object} input - * @param {(object|string|URLSearchParams)=} [params] - * @param {(ArrayBuffer|Uint8Array|string)?} [bytes] - * @return {Message} - * @ignore - */ - static from(input: string | URL | Message | Buffer | object, params?: (object | string | URLSearchParams) | undefined, bytes?: (ArrayBuffer | Uint8Array | string) | null): Message; - /** - * Predicate to determine if `input` is valid for constructing - * a new `Message` instance. - * @param {string|URL|Message|Buffer|object} input - * @return {boolean} - * @ignore - */ - static isValidInput(input: string | URL | Message | Buffer | object): boolean; - /** - * `Message` class constructor. - * @protected - * @param {string|URL} input - * @param {(object|Uint8Array)?} [bytes] - * @ignore - */ - protected constructor(); - /** - * @type {Uint8Array?} - * @ignore - */ - bytes: Uint8Array | null; - /** - * Computed IPC message name. - * @type {string} - * @ignore - */ - get command(): string; - /** - * Computed IPC message name. - * @type {string} - * @ignore - */ - get name(): string; - /** - * Computed `id` value for the command. - * @type {string} - * @ignore - */ - get id(): string; - /** - * Computed `seq` (sequence) value for the command. - * @type {string} - * @ignore - */ - get seq(): string; - /** - * Computed message value potentially given in message parameters. - * This value is automatically decoded, but not treated as JSON. - * @type {string} - * @ignore - */ - get value(): string; - /** - * Computed `index` value for the command potentially referring to - * the window index the command is scoped to or originating from. If not - * specified in the message parameters, then this value defaults to `-1`. - * @type {number} - * @ignore - */ - get index(): number; - /** - * Computed value parsed as JSON. This value is `null` if the value is not present - * or it is invalid JSON. - * @type {object?} - * @ignore - */ - get json(): any; - /** - * Computed readonly object of message parameters. - * @type {object} - * @ignore - */ - get params(): any; - /** - * Gets unparsed message parameters. - * @type {Array<Array<string>>} - * @ignore - */ - get rawParams(): string[][]; - /** - * Returns computed parameters as entries - * @return {Array<Array<any>>} - * @ignore - */ - entries(): Array<Array<any>>; - /** - * Set a parameter `value` by `key`. - * @param {string} key - * @param {any} value - * @ignore - */ - set(key: string, value: any): any; - /** - * Get a parameter value by `key`. - * @param {string} key - * @param {any} defaultValue - * @return {any} - * @ignore - */ - get(key: string, defaultValue: any): any; - /** - * Delete a parameter by `key`. - * @param {string} key - * @return {boolean} - * @ignore - */ - delete(key: string): boolean; - /** - * Computed parameter keys. - * @return {Array<string>} - * @ignore - */ - keys(): Array<string>; - /** - * Computed parameter values. - * @return {Array<any>} - * @ignore - */ - values(): Array<any>; - /** - * Predicate to determine if parameter `key` is present in parameters. - * @param {string} key - * @return {boolean} - * @ignore - */ - has(key: string): boolean; - } + export const RANDOM_BYTES_QUOTA: number; /** - * A result type used internally for handling - * IPC result values from the native layer that are in the form - * of `{ err?, data? }`. The `data` and `err` properties on this - * type of object are in tuple form and be accessed at `[data?,err?]` - * @ignore + * Maximum total size for random bytes. */ - export class Result { - /** - * Creates a `Result` instance from input that may be an object - * like `{ err?, data? }`, an `Error` instance, or just `data`. - * @param {(object|Error|any)?} result - * @param {Error|object} [maybeError] - * @param {string} [maybeSource] - * @param {object|string|Headers} [maybeHeaders] - * @return {Result} - * @ignore - */ - static from(result: (object | Error | any) | null, maybeError?: Error | object, maybeSource?: string, maybeHeaders?: object | string | Headers): Result; - /** - * `Result` class constructor. - * @private - * @param {string?} [id = null] - * @param {Error?} [err = null] - * @param {object?} [data = null] - * @param {string?} [source = null] - * @param {(object|string|Headers)?} [headers = null] - * @ignore - */ - private constructor(); - /** - * The unique ID for this result. - * @type {string} - * @ignore - */ - id: string; - /** - * An optional error in the result. - * @type {Error?} - * @ignore - */ - err: Error | null; - /** - * Result data if given. - * @type {(string|object|Uint8Array)?} - * @ignore - */ - data: (string | object | Uint8Array) | null; - /** - * The source of this result. - * @type {string?} - * @ignore - */ - source: string | null; - /** - * Result headers, if given. - * @type {Headers?} - * @ignore - */ - headers: Headers | null; - /** - * Computed result length. - * @ignore - */ - get length(): any; - /** - * @ignore - */ - toJSON(): { - headers: { - [k: string]: string; - }; - source: string; - data: any; - err: { - name: string; - message: string; - stack?: string; - cause?: unknown; - type: any; - code: any; - }; - }; - /** - * Generator for an `Iterable` interface over this instance. - * @ignore - */ - [Symbol.iterator](): Generator<any, void, unknown>; - } + export const MAX_RANDOM_BYTES: 281474976710655; /** - * @ignore + * Maximum total amount of allocated per page of bytes (max/quota) */ - export const primordials: any; + export const MAX_RANDOM_BYTES_PAGES: number; export default exports; + export type TypedArray = Uint8Array | Int8Array; import { Buffer } from "socket:buffer"; - import { URL } from "socket:url/index"; - import * as exports from "socket:ipc"; + export namespace sodium { + let ready: Promise<any>; + } + import * as exports from "socket:crypto"; } declare module "socket:ai" { /** * A class to interact with large language models (using llama.cpp) - * @extends EventEmitter */ export class LLM extends EventEmitter { /** - * Constructs an LLM instance. - * @param {Object} [options] - The options for initializing the LLM. - * @param {string} [options.path] - The path to a valid model (.gguf). - * @param {string} [options.prompt] - The query that guides the model to generate a relevant and coherent responses. - * @param {string} [options.id] - The optional ID for the LLM instance. - * @throws {Error} If the model path is not provided. + * Constructs an LLM instance. Each parameter is designed to configure and control + * the behavior of the underlying large language model provided by llama.cpp. + * @param {Object} options - Configuration options for the LLM instance. + * @param {string} options.path - The file path to the model in .gguf format. This model file contains + * the weights and configuration necessary for initializing the language model. + * @param {string} options.prompt - The initial input text to the model, setting the context or query + * for generating responses. The model uses this as a starting point for text generation. + * @param {string} [options.id] - An optional unique identifier for this specific instance of the model, + * useful for tracking or referencing the model in multi-model setups. + * @param {number} [options.n_ctx=1024] - Specifies the maximum number of tokens that the model can consider + * for a single query. This is crucial for managing memory and computational + * efficiency. Exceeding the model's configuration may lead to errors or truncated outputs. + * @param {number} [options.n_threads=8] - The number of threads allocated for the model's computation, + * affecting performance and speed of response generation. + * @param {number} [options.temp=1.1] - Sampling temperature controls the randomness of predictions. + * Higher values increase diversity, potentially at the cost of coherence. + * @param {number} [options.max_tokens=512] - The upper limit on the number of tokens that the model can generate + * in response to a single prompt. This prevents runaway generations. + * @param {number} [options.n_gpu_layers=32] - The number of GPU layers dedicated to the model processing. + * More layers can increase accuracy and complexity of the outputs. + * @param {number} [options.n_keep=0] - Determines how many of the top generated responses are retained after + * the initial generation phase. Useful for models that generate multiple outputs. + * @param {number} [options.n_batch=0] - The size of processing batches. Larger batch sizes can reduce + * the time per token generation by parallelizing computations. + * @param {number} [options.n_predict=0] - Specifies how many forward predictions the model should make + * from the current state. This can pre-generate responses or calculate probabilities. + * @param {number} [options.grp_attn_n=0] - Group attention parameter 'N' modifies how attention mechanisms + * within the model are grouped and interact, affecting the model’s focus and accuracy. + * @param {number} [options.grp_attn_w=0] - Group attention parameter 'W' adjusts the width of each attention group, + * influencing the breadth of context considered by each attention group. + * @param {number} [options.seed=0] - A seed for the random number generator used in the model. Setting this ensures + * consistent results in model outputs, important for reproducibility in experiments. + * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, + * reducing the risk of less likely, potentially nonsensical outputs. + * @param {number} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool + * to only those whose cumulative probability exceeds this value, enhancing output relevance. + * @param {number} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring + * that generated tokens have at least this likelihood of being relevant or coherent. + * @param {number} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how + * the model weights novel or unseen prompts during generation. + * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. */ constructor(options?: { - path?: string; - prompt?: string; + path: string; + prompt: string; id?: string; + n_ctx?: number; + n_threads?: number; + temp?: number; + max_tokens?: number; + n_gpu_layers?: number; + n_keep?: number; + n_batch?: number; + n_predict?: number; + grp_attn_n?: number; + grp_attn_w?: number; + seed?: number; + top_k?: number; + tok_p?: number; + min_p?: number; + tfs_z?: number; }); path: string; prompt: string; @@ -7191,8 +7310,6 @@ declare module "socket:vm" { /** * Gets the VM context window. * This function will create it if it does not already exist. - * The current window will be used on Android platforms as there can - * only be one window. * @return {Promise<import('./window.js').ApplicationWindow} */ export function getContextWindow(): Promise<import("socket:window").ApplicationWindow>; @@ -7544,7 +7661,6 @@ declare module "socket:vm" { filename?: string; context?: object; }; - import { SharedWorker } from "socket:internal/shared-worker"; } declare module "socket:worker_threads/init" { @@ -7583,7 +7699,7 @@ declare module "socket:worker_threads" { * A pool of known worker threads. * @type {<Map<string, Worker>} */ - export const workers: <Map_1>() => <string, Worker_1>() => any; + export const workers: <Map>() => <string, Worker>() => any; /** * `true` if this is the "main" thread, otherwise `false` * The "main" thread is the top level webview window. @@ -7740,7 +7856,6 @@ declare module "socket:worker_threads" { import { Readable } from "socket:stream"; import { SHARE_ENV } from "socket:worker_threads/init"; import init from "socket:worker_threads/init"; - import { env } from "socket:process"; export { SHARE_ENV, init }; } @@ -7928,7 +8043,6 @@ declare module "socket:child_process" { import { AsyncResource } from "socket:async/resource"; import { EventEmitter } from "socket:events"; import { Worker } from "socket:worker_threads"; - import signal from "socket:signal"; } declare module "socket:constants" { @@ -8443,7 +8557,7 @@ declare module "socket:fs/web" { export function createFile(filename: string, options?: { fd: fs.FileHandle; highWaterMark?: number; - }): File; + } | undefined): File; /** * Creates a `FileSystemWritableFileStream` instance backed * by `socket:fs:` module from a given `FileSystemFileHandle` instance. @@ -8739,7 +8853,6 @@ declare module "socket:extension" { * @typedef {number} Pointer */ const $loaded: unique symbol; - import path from "socket:path"; } declare module "socket:fetch/fetch" { @@ -11503,7 +11616,6 @@ declare module "socket:latica/index" { * @return {boolean} */ export function rateLimit(rates: Map<any, any>, type: number, port: number, address: string, subclusterIdQuota: any): boolean; - export function debug(pid: any, ...args: any[]): void; /** * Retry delay in milliseconds for ping. * @type {number} @@ -11687,6 +11799,7 @@ declare module "socket:latica/index" { * @ignore */ _setTimeout(fn: any, t: any): number; + _debug(pid: any, ...args: any[]): void; /** * A method that encapsulates the listing procedure * @return {undefined} @@ -11974,12 +12087,11 @@ declare module "socket:latica/proxy" { close(...args: any[]): Promise<any>; query(...args: any[]): Promise<any>; compileCachePredicate(src: any): Promise<any>; - callWorkerThread(prop: any, data: any): Deferred; + callWorkerThread(prop: any, data: any): any; callMainThread(prop: any, args: any): void; - resolveMainThread(seq: any, data: any): void; + resolveMainThread(seq: any, result: any): any; #private; } - import { Deferred } from "socket:async"; } declare module "socket:latica/api" { @@ -12018,6 +12130,12 @@ declare module "socket:index" { export { network, Cache, sha256, Encryption, Packet, NAT }; } +declare module "socket:latica" { + export * from "socket:latica/index"; + export default def; + import def from "socket:latica/index"; +} + declare module "socket:network" { export default network; export function network(options: any): Promise<events.EventEmitter>; @@ -13748,7 +13866,7 @@ declare module "socket:commonjs/package" { static parse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - }): ParsedPackageName | null; + } | undefined): ParsedPackageName | null; /** * Returns `true` if the given `input` can be parsed by `Name.parse` or given * as input to the `Name` class constructor. @@ -13759,7 +13877,7 @@ declare module "socket:commonjs/package" { static canParse(input: string | URL, options?: { origin?: string | URL; manifest?: string; - }): boolean; + } | undefined): boolean; /** * Creates a new `Name` from input. * @param {string|URL} input @@ -14528,7 +14646,6 @@ declare module "socket:commonjs/module" { import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import builtins from "socket:commonjs/builtins"; } declare module "socket:module" { @@ -15799,6 +15916,7 @@ declare module "socket:internal/pickers" { * mode?: 'read' | 'readwrite' * startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos', * }} ShowDirectoryPickerOptions + * */ /** * Shows a directory picker which allows the user to select a directory. @@ -15884,7 +16002,6 @@ declare module "socket:internal/pickers" { [keyof]; }>; }; - import { FileSystemHandle } from "socket:fs/web"; } declare module "socket:internal/primitives" { diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 6ee9f294c7..e546f1ce15 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -413,6 +413,10 @@ export function init () { targetWindowIndex: globalThis.__args.index, value: title }) + + if (result.err) { + console.warn(result.err) + } } }) @@ -425,6 +429,10 @@ export function init () { targetWindowIndex: globalThis.__args.index, value: title }) + + if (result.err) { + console.warn(result.err) + } } } }) diff --git a/api/ipc.js b/api/ipc.js index 39ab939993..b373fca8a2 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -33,7 +33,7 @@ * ``` */ -/* global webkit, chrome, external, reportError */ +/* global webkit, chrome, external, reportError, __global_ipc_extension_handler */ import { AbortError, InternalError, @@ -1101,10 +1101,32 @@ export function sendSync (command, value = '', options = {}, buffer = null) { return cache[command] } - const request = new globalThis.XMLHttpRequest() const params = new IPCSearchParams(value, Date.now()) + params.set('__sync__', 'true') const uri = `ipc://${command}?${params}` + if ( + typeof __global_ipc_extension_handler === 'function' && + (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) + ) { + let response = null + try { + response = __global_ipc_extension_handler(uri) + } catch (err) { + return Result.from(null, err) + } + + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch {} + } + + return Result.from(response, null, command) + } + + const request = new globalThis.XMLHttpRequest() + if (debug.enabled) { debug.log('ipc.sendSync: %s', uri) } @@ -1389,9 +1411,7 @@ export async function request (command, value, options) { if (typeof response === 'string') { try { response = JSON.parse(response) - } catch (err) { - console.log({err, response}) - } + } catch {} } return Result.from(response, null, command) diff --git a/api/latica/index.js b/api/latica/index.js index 714c4a0768..b306dae435 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -26,10 +26,15 @@ import { VERSION } from './packets.js' +// eslint-disable-next-line let logcount = 0 +// eslint-disable-next-line const process = globalThis.process || globalThis.window?.__args +// eslint-disable-next-line const COLOR_GRAY = '\x1b[90m' +// eslint-disable-next-line const COLOR_WHITE = '\x1b[37m' +// eslint-disable-next-line const COLOR_RESET = '\x1b[0m' export { Packet, sha256, Cache, Encryption, NAT } diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 116587564d..b33668596b 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -7,9 +7,8 @@ import application from '../application.js' import location from '../location.js' import state from './state.js' import ipc from '../ipc.js' -import os from '../os.js' -const SERVICE_WINDOW_PATH = `${globalThis.origin}/socket/service-worker/index.html` +const SERVICE_WORKER_WINDOW_PATH = `${location.origin}/socket/service-worker/index.html` class ServiceWorkerContainerInternalStateMap extends Map { define (container, property, descriptor) { @@ -87,7 +86,7 @@ class ServiceWorkerContainerRealm { this.frame = globalThis.top.document.createElement('iframe') this.frame.id = frameId - this.frame.src = SERVICE_WINDOW_PATH + this.frame.src = SERVICE_WORKER_WINDOW_PATH this.frame.setAttribute('loading', 'eager') this.frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') From c55b2bfc8bc8e98880185e5bfd7ca7e058158a52 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 15 Jun 2024 12:28:30 +0200 Subject: [PATCH 0823/1178] refactor(api/app): Use js to respond to keyboard events instead of obj-c selector, giving the UI more control over how the UI changes --- api/internal/init.js | 73 ++++++++++++++++++++++++++++ src/app/app.cc | 110 +++++++------------------------------------ 2 files changed, 91 insertions(+), 92 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 87d86313fa..579d82b5d7 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -25,6 +25,7 @@ import { rand64 } from '../crypto.js' import location from '../location.js' import mime from '../mime.js' import path from '../path.js' +import process from '../process.js' import fs from '../fs/promises.js' import { createFileSystemDirectoryHandle, @@ -209,6 +210,78 @@ if ((globalThis.window) === globalThis) { })) } }) + + if (process.platform === 'ios') { + let keyboardHeight + let isKeyboardOpen = false + let initialHeight = 0 + const duration = 346 + + globalThis.window.addEventListener('keyboard', ({ detail }) => { + if (initialHeight === 0) { + initialHeight = document.body.offsetHeight + } + + if (detail.value.event === 'will-show') { + if (isKeyboardOpen) { + document.body.style.height = initialHeight + } + + keyboardHeight = detail.value.height + let start = null + + const bezier = t => { + const p1 = 0.9 + const p2 = 0.95 + return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + } + + const animate = (timestamp) => { + if (!start) start = timestamp + const elapsed = timestamp - start + const progress = Math.min(elapsed / duration, 1) + const easeProgress = bezier(progress) + const currentHeight = initialHeight - (easeProgress * keyboardHeight) + + document.body.style.height = `${currentHeight}px` + + if (progress < 1) { + isKeyboardOpen = true + window.requestAnimationFrame(animate) + } + } + window.requestAnimationFrame(animate) + } + + if (detail.value.event === 'will-hide') { + let start = null + const initialHeight = document.body.offsetHeight + + const bezier = t => { + const p1 = 0.86 + const p2 = 0.95 + return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + } + + const animate = (timestamp) => { + if (!start) start = timestamp + const elapsed = timestamp - start + const progress = Math.min(elapsed / duration, 1) + const easeProgress = bezier(progress) + const currentHeight = initialHeight + (easeProgress * keyboardHeight) + + document.body.style.height = `${currentHeight}px` + + if (progress < 1) { + window.requestAnimationFrame(animate) + } else { + isKeyboardOpen = false + } + } + window.requestAnimationFrame(animate) + } + }) + } } class RuntimeWorker extends GlobalWorker { diff --git a/src/app/app.cc b/src/app/app.cc index 745f0e73fc..7619c15918 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -369,24 +369,16 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType } - (void) keyboardWillHide: (NSNotification*) notification { - NSDictionary *userInfo = notification.userInfo; - CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - CGFloat keyboardHeight = keyboardFrame.size.height; + const auto info = notification.userInfo; + const auto keyboardFrameBegin = (NSValue*) [info valueForKey: UIKeyboardFrameEndUserInfoKey]; + const auto rect = [keyboardFrameBegin CGRectValue]; + const auto height = rect.size.height; - self.keyboardHeight = keyboardFrame.size.height; - self.animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - - self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(keyboardHide:)]; - self.displayLink.preferredFramesPerSecond = 120; - [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + const auto window = self.app->windowManager.getWindow(0); + window->webview.scrollView.scrollEnabled = YES; for (const auto window : self.app->windowManager.windows) { if (window) { - const auto info = notification.userInfo; - const auto keyboardFrameBegin = (NSValue*) [info valueForKey: UIKeyboardFrameEndUserInfoKey]; - const auto rect = [keyboardFrameBegin CGRectValue]; - const auto height = rect.size.height; - window->bridge.emit("keyboard", JSON::Object::Entries { {"value", JSON::Object::Entries { {"event", "will-hide"}, @@ -410,24 +402,16 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType } - (void)keyboardWillShow: (NSNotification*) notification { - NSDictionary *userInfo = notification.userInfo; - CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - CGFloat keyboardHeight = keyboardFrame.size.height; - - self.keyboardHeight = keyboardFrame.size.height; - self.animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + const auto info = notification.userInfo; + const auto keyboardFrameBegin = (NSValue*) [info valueForKey: UIKeyboardFrameEndUserInfoKey]; + const auto rect = [keyboardFrameBegin CGRectValue]; + const auto height = rect.size.height; - self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(keyboardShow:)]; - self.displayLink.preferredFramesPerSecond = 120; - [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + const auto window = self.app->windowManager.getWindow(0); + window->webview.scrollView.scrollEnabled = NO; for (const auto window : self.app->windowManager.windows) { if (window && !window->window.isHidden) { - const auto info = notification.userInfo; - const auto keyboardFrameBegin = (NSValue*) [info valueForKey: UIKeyboardFrameEndUserInfoKey]; - const auto rect = [keyboardFrameBegin CGRectValue]; - const auto height = rect.size.height; - window->bridge.emit("keyboard", JSON::Object::Entries { {"value", JSON::Object::Entries { {"event", "will-show"}, @@ -438,64 +422,6 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType } } -- (void) keyboardShow: (CADisplayLink*) displayLink { - if (self.inMotion) return; - self.inMotion = true; - - const auto window = self.app->windowManager.getWindow(0); - - auto timer = [NSTimer scheduledTimerWithTimeInterval: 0.0008 repeats: YES block:^(NSTimer * _Nonnull timer) { - CGRect newFrame = window->webview.frame; - - CGFloat p = self.progress / self.keyboardHeight; - p = p > 1.0 ? 1.0 : p; - - CGFloat easedHeightChange = 0.5 + pow(1.4 - p, 8); - newFrame.size.height -= easedHeightChange; - - window->webview.frame = newFrame; - - self.progress += easedHeightChange > 0 ? easedHeightChange : 0; - - if (self.progress >= self.keyboardHeight) { - [displayLink invalidate]; - [timer invalidate]; - self.inMotion = false; - } - }]; - - [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; -} - -- (void) keyboardHide: (CADisplayLink*) displayLink { - if (self.inMotion) return; - self.inMotion = true; - - const auto window = self.app->windowManager.getWindow(0); - const auto stepDuration = self.animationDuration / 60.0; - - auto timer = [NSTimer scheduledTimerWithTimeInterval:stepDuration repeats:YES block:^(NSTimer * _Nonnull timer) { - CGRect newFrame = window->webview.frame; - - CGFloat p = (self.progress / self.keyboardHeight || 1); - CGFloat easedHeightChange = 8 + pow(--p, 4); - newFrame.size.height += easedHeightChange; // Adjusted to increase the height - - window->webview.frame = newFrame; - - CGFloat progressIncrement = easedHeightChange > 0 ? easedHeightChange : 0; - self.progress -= progressIncrement; // Decrease the progress - - if (self.progress <= 0) { // Check if progress is zero or less - [displayLink invalidate]; - [timer invalidate]; - self.inMotion = false; - } - }]; - - [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; -} - - (void) keyboardDidShow: (NSNotification*) notification { for (const auto window : self.app->windowManager.windows) { if (window && !window->window.isHidden) { @@ -509,14 +435,14 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType } - (void) keyboardWillChange: (NSNotification*) notification { + const auto keyboardInfo = notification.userInfo; + const auto keyboardFrameBegin = (NSValue* )[keyboardInfo valueForKey: UIKeyboardFrameEndUserInfoKey]; + const auto rect = [keyboardFrameBegin CGRectValue]; + const auto width = rect.size.width; + const auto height = rect.size.height; + for (const auto window : self.app->windowManager.windows) { if (window && !window->window.isHidden) { - const auto keyboardInfo = notification.userInfo; - const auto keyboardFrameBegin = (NSValue* )[keyboardInfo valueForKey: UIKeyboardFrameEndUserInfoKey]; - const auto rect = [keyboardFrameBegin CGRectValue]; - const auto width = rect.size.width; - const auto height = rect.size.height; - window->bridge.emit("keyboard", JSON::Object::Entries { {"value", JSON::Object::Entries { {"event", "will-change"}, From c7a05dc5edb1da89346918ad191d51f3c6ee02f4 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 18 Jun 2024 16:15:45 +0200 Subject: [PATCH 0824/1178] update(protocol): updates the p2p protocol, refactors the ai stuff --- api/internal/init.js | 34 +++++----- api/latica/api.js | 10 +++ api/latica/cache.js | 4 +- api/latica/index.js | 137 ++++++++++++++++++++++------------------- api/latica/proxy.js | 4 ++ src/core/core.hh | 4 ++ src/core/modules/ai.cc | 70 +++------------------ src/ipc/bridge.cc | 6 +- 8 files changed, 122 insertions(+), 147 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 579d82b5d7..5a5da79ad8 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -217,30 +217,34 @@ if ((globalThis.window) === globalThis) { let initialHeight = 0 const duration = 346 + const bezierShow = t => { + const p1 = 0.9 + const p2 = 0.95 + return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + } + + const bezierHide = t => { + const p1 = 0.86 + const p2 = 0.95 + return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + } + globalThis.window.addEventListener('keyboard', ({ detail }) => { if (initialHeight === 0) { initialHeight = document.body.offsetHeight } if (detail.value.event === 'will-show') { - if (isKeyboardOpen) { - document.body.style.height = initialHeight - } + if (isKeyboardOpen) return keyboardHeight = detail.value.height let start = null - const bezier = t => { - const p1 = 0.9 - const p2 = 0.95 - return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t - } - const animate = (timestamp) => { if (!start) start = timestamp const elapsed = timestamp - start const progress = Math.min(elapsed / duration, 1) - const easeProgress = bezier(progress) + const easeProgress = bezierShow(progress) const currentHeight = initialHeight - (easeProgress * keyboardHeight) document.body.style.height = `${currentHeight}px` @@ -254,21 +258,17 @@ if ((globalThis.window) === globalThis) { } if (detail.value.event === 'will-hide') { + isKeyboardOpen = false let start = null const initialHeight = document.body.offsetHeight - const bezier = t => { - const p1 = 0.86 - const p2 = 0.95 - return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t - } - const animate = (timestamp) => { if (!start) start = timestamp const elapsed = timestamp - start const progress = Math.min(elapsed / duration, 1) - const easeProgress = bezier(progress) + const easeProgress = bezierHide(progress) const currentHeight = initialHeight + (easeProgress * keyboardHeight) + if (currentHeight <= 0) progress = 1 document.body.style.height = `${currentHeight}px` diff --git a/api/latica/api.js b/api/latica/api.js index abd47b1f22..349e279d3f 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -93,6 +93,7 @@ async function api (options = {}, events, dgram) { * @returns {object} - The general information. */ bus.getInfo = () => _peer.getInfo() + bus.getMetrics = () => _peer.getMetrics() /** * Gets the read only state of the network peer. @@ -257,6 +258,15 @@ async function api (options = {}, events, dgram) { _peer.mcast(packet) } + + const head = packets.find(p => p.index === 0) + + if (_peer.onPacket && head) { // try to emit a single packet + const p = await _peer.cache.compose(head) + _peer.onPacket(p, _peer.port, _peer.address, true) + return [p] + } + return packets } else { const packets = await _peer.publish(sub.sharedKey, args) diff --git a/api/latica/cache.js b/api/latica/cache.js index 1893b72a99..ced5e70b0c 100644 --- a/api/latica/cache.js +++ b/api/latica/cache.js @@ -196,7 +196,7 @@ export class Cache { // follow the chain to get the buffers in order const bufs = [...source.values()] - .filter(p => Buffer.from(p.previousId).toString('hex') === Buffer.from(previous.packetId).toString('hex')) + .filter(p => Buffer.from(p.previousId || '').toString('hex') === Buffer.from(previous.packetId).toString('hex')) .sort((a, b) => a.index - b.index) if (!indexes || bufs.length < indexes) return null @@ -208,7 +208,7 @@ export class Cache { if (!meta.ts) meta.ts = ts // generate a new packet ID - const packetId = await sha256(Buffer.concat([packet.previousId, message]), { bytes: true }) + const packetId = await sha256(Buffer.concat([Buffer.from(packet.previousId || ''), message]), { bytes: true }) return Packet.from({ ...packet, diff --git a/api/latica/index.js b/api/latica/index.js index b306dae435..fa2510d4d8 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -26,17 +26,6 @@ import { VERSION } from './packets.js' -// eslint-disable-next-line -let logcount = 0 -// eslint-disable-next-line -const process = globalThis.process || globalThis.window?.__args -// eslint-disable-next-line -const COLOR_GRAY = '\x1b[90m' -// eslint-disable-next-line -const COLOR_WHITE = '\x1b[37m' -// eslint-disable-next-line -const COLOR_RESET = '\x1b[0m' - export { Packet, sha256, Cache, Encryption, NAT } /** @@ -193,13 +182,13 @@ export class RemotePeer { const packets = await this.localPeer._message2packets(PacketStream, args.message, args) if (this.proxy) { - this.localPeer.debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) + this.localPeer._debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) } for (const packet of packets) { const from = this.localPeer.peerId.slice(0, 6) const to = this.peerId.slice(0, 6) - this.localPeer.debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) + this.localPeer._debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) this.localPeer.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) @@ -278,7 +267,7 @@ export class Peer { this.encryption = new Encryption() if (!config.peerId) throw new Error('constructor expected .peerId') - if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error('invalid .peerId') + if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error(`invalid .peerId (${config.peerId})`) // // The purpose of this.config is to seperate transitioned state from initial state. @@ -413,12 +402,6 @@ export class Peer { if (!this.isListening) await this._listen() if (cb) this.onReady = cb - // tell all well-known peers that we would like to hear from them, if - // we hear from any we can ask for the reflection information we need. - for (const peer of this.peers.filter(p => p.indexed)) { - await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) - } - this._mainLoop(Date.now()) this.mainLoopTimer = this._setInterval(ts => this._mainLoop(ts), this.config.keepalive) @@ -550,7 +533,7 @@ export class Peer { if (!packet) return this.metrics.o[packet.type]++ - delete this.unpublished[packet.packetId.toString('hex')] + delete this.unpublished[Buffer.from(packet.packetId).toString('hex')] if (this.onSend && packet.type) this.onSend(packet, port, address) this._debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) }) @@ -610,7 +593,7 @@ export class Peer { } async cacheInsert (packet) { - this.cache.insert(packet.packetId.toString('hex'), Packet.from(packet)) + this.cache.insert(Buffer.from(packet.packetId).toString('hex'), Packet.from(packet)) } async addIndexedPeer (info) { @@ -700,13 +683,18 @@ export class Peer { * @ignore */ async mcast (packet, ignorelist = []) { - const peers = this.getPeers(packet, this.peers, ignorelist) - const pid = packet.packetId.toString('hex') + const peers = this.getPeers(packet, this.peers, ignorelist, p => { + if (this.indexed && p.lastSend > Date.now() - 6000) return false + return p + }) + + const pid = Buffer.from(packet.packetId).toString('hex') packet.hops += 1 for (const peer of peers) { this.send(await Packet.encode(packet), peer.port, peer.address) + peer.lastSend = Date.now() } if (this.onMulticast) this.onMulticast(packet) @@ -739,10 +727,16 @@ export class Peer { if (peers.length < 2) { if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) - this._debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING') + this._debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING', peers) + + // tell all well-known peers that we would like to hear from them, if + // we hear from any we can ask for the reflection information we need. + for (const peer of this.peers.filter(p => p.indexed).sort(() => Math.random() - 0.5).slice(0, 3)) { + await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) + } if (++this.reflectionRetry > 16) this.reflectionRetry = 1 - return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) + return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 512) } this.reflectionRetry = 1 @@ -915,7 +909,7 @@ export class Peer { args.sharedKey = sharedKey - const clusterId = args.clusterId || this.config.clusterId + const clusterId = Buffer.from(args.clusterId || this.config.clusterId || '') const subclusterId = Buffer.from(keys.publicKey) const cid = clusterId?.toString('base64') @@ -942,7 +936,7 @@ export class Peer { if (this.onState) this.onState(this.getState()) this.mcast(packet) - this.gate.set(packet.packetId.toString('hex'), 1) + this.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) } /** @@ -997,8 +991,8 @@ export class Peer { sig })) - if (packet) packets[0].previousId = packet.packetId - if (nextId) packets[packets.length - 1].nextId = nextId + if (packet) packets[0].previousId = Buffer.from(packet.packetId) + if (nextId) packets[packets.length - 1].nextId = Buffer.from(nextId) // set the .packetId (any maybe the .previousId and .nextId) for (let i = 0; i < packets.length; i++) { @@ -1009,7 +1003,7 @@ export class Peer { } else { // all fragments will have the same previous packetId // the index is used to stitch them back together in order. - packets[i].previousId = packets[0].packetId + packets[i].previousId = Buffer.from(packets[0].packetId) } if (packets[i + 1]) { @@ -1051,18 +1045,29 @@ export class Peer { const message = this.encryption.seal(args.message, keys) const packets = await this._message2packets(PacketPublish, message, args) + const head = packets.find(p => p.index === 0) // has a head, should compose for (const packet of packets) { this.cacheInsert(packet) - if (this.onPacket) this.onPacket(packet, this.port, this.address, true) + if (this.onPacket && packet.index === -1) { + this.onPacket(packet, this.port, this.address, true) + } - this.unpublished[packet.packetId.toString('hex')] = Date.now() + this.unpublished[Buffer.from(packet.packetId).toString('hex')] = Date.now() if (globalThis.navigator && !globalThis.navigator.onLine) continue this.mcast(packet) } + // if there is a head, we can recompose the packets, this gives this + // peer a consistent view of the data as it has been published. + if (this.onPacket && head) { + const p = await this.cache.compose(head) + this.onPacket(p, this.port, this.address, true) + return [p] + } + return packets } @@ -1117,7 +1122,7 @@ export class Peer { const data = await Packet.encode(packet) const p = Packet.decode(data) // finalize a packet - const pid = p.packetId.toString('hex') + const pid = Buffer.from(p.packetId).toString('hex') if (this.gate.has(pid)) return this.returnRoutes.set(p.usr3.toString('hex'), {}) @@ -1174,8 +1179,8 @@ export class Peer { if (proxy) peer.proxy = proxy if (socket) peer.socket = socket - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') + const cid = Buffer.from(clusterId).toString('base64') + const scid = Buffer.from(subclusterId).toString('base64') if (cid) peer.clusters[cid] ??= {} @@ -1214,7 +1219,7 @@ export class Peer { this.metrics.i[packet.type]++ this.lastSync = Date.now() - const pid = packet.packetId?.toString('hex') + const pid = Buffer.from(packet.packetId)?.toString('hex') if (!isBufferLike(packet.message)) return if (this.gate.has(pid)) return @@ -1250,7 +1255,7 @@ export class Peer { const packet = Packet.from(p) if (!this.cachePredicate(packet)) continue - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') this._debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) this.send(await Packet.encode(packet), port, address) @@ -1292,7 +1297,7 @@ export class Peer { async _onQuery (packet, port, address) { this.metrics.i[packet.type]++ - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 1) @@ -1557,7 +1562,7 @@ export class Peer { this.metrics.i[packet.type]++ if (this.closing) return - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') // the packet needs to be gated, but should allow for attempt // recursion so that the fallback can still be selected. if (this.gate.has(pid) && opts.attempts === 0) return @@ -1594,8 +1599,8 @@ export class Peer { if (this.gate.has('CONN' + peer.peerId) && opts.attempts === 0) return this.gate.set('CONN' + peer.peerId, 1) - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') + const cid = Buffer.from(clusterId).toString('base64') + const scid = Buffer.from(subclusterId).toString('base64') this._debug(this.peerId, '<- INTRO (' + `isRendezvous=${packet.message.isRendezvous}, ` + @@ -1784,7 +1789,7 @@ export class Peer { async _onJoin (packet, port, address, data) { this.metrics.i[packet.type]++ - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') if (packet.message.requesterPeerId === this.peerId) return if (this.gate.has(pid)) return if (packet.clusterId.length !== 32) return @@ -1805,8 +1810,8 @@ export class Peer { // a rendezvous isn't relevant if it's too old, just drop the packet if (rendezvousDeadline && rendezvousDeadline < Date.now()) return - const cid = clusterId.toString('base64') - const scid = subclusterId.toString('base64') + const cid = Buffer.from(clusterId).toString('base64') + const scid = Buffer.from(subclusterId).toString('base64') this._debug(this.peerId, '<- JOIN (' + `peerId=${peerId.slice(0, 6)}, ` + @@ -1825,7 +1830,7 @@ export class Peer { // if (rendezvousDeadline && !this.indexed && this.clusters[cid]) { if (!packet.message.rendezvousRequesterPeerId) { - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') this.gate.set(pid, 2) // TODO it would tighten up the transition time between dropped peers @@ -1924,11 +1929,11 @@ export class Peer { this.send(intro2, peer.port, peer.address) this.send(intro1, packet.message.port, packet.message.address) - this.gate.set(Packet.decode(intro1).packetId.toString('hex'), 2) - this.gate.set(Packet.decode(intro2).packetId.toString('hex'), 2) + this.gate.set(Buffer.from(Packet.decode(intro1).packetId).toString('hex'), 2) + this.gate.set(Buffer.from(Packet.decode(intro2).packetId).toString('hex'), 2) } - this.gate.set(packet.packetId.toString('hex'), 2) + this.gate.set(Buffer.from(packet.packetId).toString('hex'), 2) if (packet.hops >= this.maxHops) return if (this.indexed && !packet.clusterId) return @@ -1961,13 +1966,16 @@ export class Peer { // const cluster = this.clusters[packet.clusterId] // if (cluster && cluster[packet.subclusterId]) { - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') if (this.cache.has(pid)) { - this._debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, from=${address}:${port})`) + this.metrics.i.REJECTED++ + const cid = Buffer.from(packet.clusterId).toString('base64') + const scid = Buffer.from(packet.subclusterId).toString('base64') + this._debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, clusterId=${cid}, subclueterId=${scid}, from=${address}:${port}, hops=${packet.hops})`) return } - this._debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) + this._debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${Buffer.from(packet.usr4).toString() === 'SYNC'})`) this.cacheInsert(packet) const ignorelist = [{ address, port }] @@ -1980,6 +1988,7 @@ export class Peer { } if (packet.hops >= this.maxHops) return + this.mcast(packet, ignorelist) // } @@ -1993,40 +2002,40 @@ export class Peer { async _onStream (packet, port, address, data) { this.metrics.i[packet.type]++ - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 1) - const streamTo = packet.usr3.toString('hex') - const streamFrom = packet.usr4.toString('hex') + const streamTo = Buffer.from(packet.usr3).toString('hex') + const streamFrom = Buffer.from(packet.usr4).toString('hex') // only help packets with a higher hop count if they are in our cluster // if (packet.hops > 2 && !this.clusters[packet.cluster]) return - const peerFrom = this.peers.find(p => p.peerId.toString('hex') === streamFrom.toString('hex')) + const peerFrom = this.peers.find(p => p.peerId === streamFrom) if (!peerFrom) return // stream message is for this peer - if (streamTo.toString('hex') === this.peerId.toString('hex')) { - const scid = packet.subclusterId.toString('base64') + if (streamTo === this.peerId) { + const scid = Buffer.from(packet.subclusterId).toString('base64') if (this.encryption.has(scid)) { let p = packet.copy() // clone the packet so it's not modified if (packet.index > -1) { // if it needs to be composed... p.timestamp = Date.now() - this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial + this.streamBuffer.set(Buffer.from(p.packetId).toString('hex'), p) // cache the partial p = await this.cache.compose(p, this.streamBuffer) // try to compose if (!p) return // could not compose if (p) { // if successful, delete the artifacts - const previousId = p.index === 0 ? p.packetId : p.previousId + const previousId = Buffer.from(p.index === 0 ? p.packetId : p.previousId) const pid = previousId.toString('hex') this.streamBuffer.forEach((v, k) => { if (k === pid) this.streamBuffer.delete(k) - if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) + if (Buffer.from(v.previousId).toString('hex') === pid) this.streamBuffer.delete(k) }) } } @@ -2067,7 +2076,7 @@ export class Peer { if (!packet || packet.version !== VERSION) return if (packet?.type !== 2) return - const pid = packet.packetId.toString('hex') + const pid = Buffer.from(packet.packetId).toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 1) @@ -2126,8 +2135,8 @@ export class Peer { const peer = this.peers.find(p => p.address === address && p.port === port) if (peer) peer.lastUpdate = Date.now() - const cid = packet.clusterId.toString('base64') - const scid = packet.subclusterId.toString('base64') + const cid = Buffer.from(packet.clusterId).toString('base64') + const scid = Buffer.from(packet.subclusterId).toString('base64') // debug('<- PACKET', packet.type, port, address) const clusters = this.clusters[cid] diff --git a/api/latica/proxy.js b/api/latica/proxy.js index 9ba06b0ab6..a0a8b8d2b3 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -161,6 +161,10 @@ class PeerWorkerProxy { return await this.callWorkerThread('getInfo') } + async getMetrics () { + return await this.callWorkerThread('metrics') + } + async getState () { return await this.callWorkerThread('getState') } diff --git a/src/core/core.hh b/src/core/core.hh index e631113f10..b8ffa81d87 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -160,6 +160,10 @@ namespace SSC { udp(this) { initEventLoop(); + + if (options.features.useNetworkStatus) { + this->networkStatus.start(); + } } Core () diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 9ebe453d5b..8fbc9f691d 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -88,6 +88,7 @@ namespace SSC { if (llm->err.size()) { auto json = ERR_AI_LLM_MESSAGE("ai.llm.create", id, llm->err); return callback(seq, json, Post{}); + return; } const auto json = JSON::Object::Entries { @@ -225,64 +226,24 @@ namespace SSC { llama_numa_init(this->params.numa); llama_sampling_params& sparams = this->params.sparams; - - if (options.temp) sparams.temp = options.temp; - if (options.top_k) sparams.top_k = options.top_k; - if (options.top_p) sparams.top_p = options.top_p; - if (options.tfs_z) sparams.tfs_z = options.tfs_z; - if (options.min_p) sparams.min_p = options.min_p; - if (options.typical_p) sparams.typical_p = options.typical_p; - this->sampling = llama_sampling_init(sparams); if (!this->sampling) this->err = "failed to initialize sampling subsystem"; if (this->params.seed == LLAMA_DEFAULT_SEED) this->params.seed = time(nullptr); - this->params.conversation = options.conversation; - this->params.chatml = options.chatml; - this->params.instruct = options.instruct; - - this->params.n_ctx = options.n_ctx; - - if (this->params.n_ctx != 0 && this->params.n_ctx < 8) { - this->params.n_ctx = 8; - } - - if (options.n_keep > 0) this->params.n_keep = options.n_keep; - if (options.n_batch > 0) this->params.n_batch = options.n_batch; - if (options.n_predict > 0) this->params.n_predict = options.n_predict; - if (options.grp_attn_n > 0) this->params.grp_attn_n = options.grp_attn_n; - if (options.grp_attn_w > 0) this->params.grp_attn_w = options.grp_attn_w; - + this->params.chatml = true; this->params.verbose_prompt = false; if (this->params.chatml) { this->params.prompt = "<|im_start|>system\n" + options.prompt + "<|im_end|>\n\n"; } - if (options.antiprompt.size()) { - this->params.antiprompt = Vector<String> { options.antiprompt }; - } - - // #if SOCKET_RUNTIME_PLATFORM_IOS - // this->params.use_mmap = false; - // #endif + this->params.n_ctx = 2048; FileResource modelResource(options.path); - FileResource metalResource(String("ggml-metal")); - - if (!modelResource.hasAccess()) { - this->err = "Unable to access the model file due to permissions"; - return; - } if (!modelResource.exists()) { - this->err = "The model file does not exist"; - return; - } - - if (metalResource.exists() && !metalResource.hasAccess()) { - this->err = "Unable to access the GGML metal file due to permissions"; + this->err = "Unable to access the model file due to permissions"; return; } @@ -455,6 +416,7 @@ namespace SSC { std::vector<llama_token> embd; std::vector<llama_token> embd_guidance; + const int n_ctx = this->n_ctx; const auto inp_pfx = ::llama_tokenize(ctx, "\n\n### Instruction:\n\n", true, true); const auto inp_sfx = ::llama_tokenize(ctx, "\n\n### Response:\n\n", false, true); @@ -464,9 +426,9 @@ namespace SSC { const auto cml_pfx = ::llama_tokenize(ctx, "\n<|im_start|>user\n", true, true); const auto cml_sfx = ::llama_tokenize(ctx, "<|im_end|>\n<|im_start|>assistant\n", false, true); - while (((n_remain != 0 && !is_antiprompt) || this->params.interactive) && this->stopped == false) { + while ((n_remain != 0 && !is_antiprompt) || this->params.interactive) { if (!embd.empty()) { - int max_embd_size = this->params.n_ctx - 4; + int max_embd_size = n_ctx - 4; if ((int) embd.size() > max_embd_size) { const int skipped_tokens = (int)embd.size() - max_embd_size; @@ -475,7 +437,7 @@ namespace SSC { } if (ga_n == 1) { - if (n_past + (int) embd.size() + std::max<int>(0, guidance_offset) >= this->params.n_ctx) { + if (n_past + (int) embd.size() + std::max<int>(0, guidance_offset) >= n_ctx) { if (this->params.n_predict == -2) { LOG("\n\ncontext full and n_predict == -%d => stopping\n", this->params.n_predict); break; @@ -485,7 +447,7 @@ namespace SSC { const int n_discard = n_left/2; LOG("context full, swapping: n_past = %d, n_left = %d, n_ctx = %d, n_keep = %d, n_discard = %d\n", - n_past, n_left, this->params.n_ctx, this->params.n_keep, n_discard); + n_past, n_left, n_ctx, this->params.n_keep, n_discard); llama_kv_cache_seq_rm (ctx, 0, this->params.n_keep, this->params.n_keep + n_discard); llama_kv_cache_seq_add(ctx, 0, this->params.n_keep + n_discard, n_past, -n_discard); @@ -503,8 +465,6 @@ namespace SSC { } } else { while (n_past >= ga_i + ga_w) { - if (this->stopped) return; - const int ib = (ga_n*ga_i)/ga_w; const int bd = (ga_w/ga_n)*(ga_n - 1); const int dd = (ga_w/ga_n) - ib*bd - ga_w; @@ -530,8 +490,6 @@ namespace SSC { size_t i = 0; for ( ; i < embd.size(); i++) { - if (this->stopped) return; - if (embd[i] != this->session_tokens[n_session_consumed]) { this->session_tokens.resize(n_session_consumed); break; @@ -572,8 +530,6 @@ namespace SSC { } for (int i = 0; i < input_size; i += this->params.n_batch) { - if (this->stopped) return; - int n_eval = std::min(input_size - i, this->params.n_batch); if (llama_decode(this->guidance, llama_batch_get_one(input_buf + i, n_eval, n_past_guidance, 0))) { @@ -586,8 +542,6 @@ namespace SSC { } for (int i = 0; i < (int) embd.size(); i += this->params.n_batch) { - if (this->stopped) return; - int n_eval = (int) embd.size() - i; if (n_eval > this->params.n_batch) { @@ -628,8 +582,6 @@ namespace SSC { LOG("embd_inp.size(): %d, n_consumed: %d\n", (int)this->embd_inp.size(), n_consumed); while ((int)this->embd_inp.size() > n_consumed) { - if (this->stopped) return; - embd.push_back(this->embd_inp[n_consumed]); llama_sampling_accept(this->sampling, this->ctx, this->embd_inp[n_consumed], false); @@ -674,8 +626,6 @@ namespace SSC { is_antiprompt = false; for (String & antiprompt : this->params.antiprompt) { - if (this->stopped) return; - size_t extra_padding = this->params.interactive ? 0 : 2; size_t search_start_pos = last_output.length() > static_cast<size_t>(antiprompt.length() + extra_padding) ? last_output.length() - static_cast<size_t>(antiprompt.length() + extra_padding) @@ -693,8 +643,6 @@ namespace SSC { llama_token last_token = llama_sampling_last(this->sampling); for (std::vector<llama_token> ids : antiprompt_ids) { - if (this->stopped) return; - if (ids.size() == 1 && last_token == ids[0]) { if (this->params.interactive) { this->interactive = true; diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 59746f8108..e4b34646d3 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -199,11 +199,11 @@ export default module)S"; }; core->networkStatus.addObserver(this->networkStatusObserver, [this](auto json) { - if (json.has("name")) { - this->emit(json["name"].str(), json.str()); - } + if (json.has("name")) this->emit(json["name"].str(), json.str()); }); + core->networkStatus.start(); + core->geolocation.addPermissionChangeObserver(this->geolocationPermissionChangeObserver, [this] (auto json) { JSON::Object event = JSON::Object::Entries { {"name", "geolocation"}, From 1726f60057b8ff8e3b24ee9fd146434b59044464 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 22 Jun 2024 16:53:10 +0200 Subject: [PATCH 0825/1178] fix(cli): ensure duplicate logs aren't printed, chore(api): update protocol --- api/latica/index.js | 165 ++++++++++++++++++++++---------------------- src/cli/cli.cc | 4 ++ 2 files changed, 88 insertions(+), 81 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index fa2510d4d8..89d42ff092 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -182,13 +182,13 @@ export class RemotePeer { const packets = await this.localPeer._message2packets(PacketStream, args.message, args) if (this.proxy) { - this.localPeer._debug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) + this.localPeer._onDebug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) } for (const packet of packets) { const from = this.localPeer.peerId.slice(0, 6) const to = this.peerId.slice(0, 6) - this.localPeer._debug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) + this.localPeer._onDebug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) this.localPeer.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) @@ -226,7 +226,7 @@ export class Peer { maxHops = 16 bdpCache = /** @type {number[]} */ ([]) - dgram = () => { throw new Error('dgram implementation required in constructor as second argument') } + dgram = null onListening = null onDelete = null @@ -239,7 +239,7 @@ export class Peer { returnRoutes = new Map() metrics = { - i: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, REJECTED: 0 }, + i: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, DROPPED: 0 }, o: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 } } @@ -262,6 +262,12 @@ export class Peer { * @param {object} dgram - A nodejs compatible implementation of the dgram module (sans multicast). */ constructor (persistedState = {}, dgram) { + if (!dgram) { + throw new Error('dgram implementation required in constructor as second argument') + } + + this.dgram = dgram + const config = persistedState?.config ?? persistedState ?? {} this.encryption = new Encryption() @@ -277,8 +283,6 @@ export class Peer { ...config } - this.dgram = () => dgram - let cacheData if (persistedState?.data?.length > 0) { @@ -300,8 +304,8 @@ export class Peer { this.natType = config.natType || null this.address = config.address || null - this.socket = this.dgram().createSocket('udp4', null, this) - this.probeSocket = this.dgram().createSocket('udp4', null, this).unref() + this.socket = this.dgram.createSocket('udp4', null, this) + this.probeSocket = this.dgram.createSocket('udp4', null, this).unref() const isRecoverable = err => err.code === 'ECONNRESET' || @@ -351,8 +355,8 @@ export class Peer { return setTimeout(fn, t) } - _debug (pid, ...args) { - if (this.onDebug) this.onDebug(pid, ...args) + _onDebug (...args) { + if (this.onDebug) this.onDebug(this.peerId, ...args) } /** @@ -390,7 +394,7 @@ export class Peer { if (this.onListening) this.onListening() this.isListening = true - this._debug(this.peerId, `++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) + this._onDebug(`++ INIT (config.internalPort=${this.config.port}, config.probeInternalPort=${this.config.probeInternalPort})`) } /* @@ -445,7 +449,7 @@ export class Peer { if (deadline <= ts) { if (p.hops < this.maxHops) this.mcast(p) this.cache.delete(k) - this._debug(this.peerId, '-- DELETE', k, this.cache.size) + this._onDebug('-- DELETE', k, this.cache.size) if (this.onDelete) this.onDelete(p) } } @@ -535,7 +539,7 @@ export class Peer { this.metrics.o[packet.type]++ delete this.unpublished[Buffer.from(packet.packetId).toString('hex')] if (this.onSend && packet.type) this.onSend(packet, port, address) - this._debug(this.peerId, `>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) + this._onDebug(`>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) }) } @@ -554,7 +558,7 @@ export class Peer { } await this.mcast(packet) - this._debug(this.peerId, `-> RESEND (packetId=${packetId})`) + this._onDebug(`-> RESEND (packetId=${packetId})`) if (this.onState) this.onState(this.getState()) } } @@ -683,18 +687,13 @@ export class Peer { * @ignore */ async mcast (packet, ignorelist = []) { - const peers = this.getPeers(packet, this.peers, ignorelist, p => { - if (this.indexed && p.lastSend > Date.now() - 6000) return false - return p - }) - + const peers = this.getPeers(packet, this.peers, ignorelist) const pid = Buffer.from(packet.packetId).toString('hex') packet.hops += 1 for (const peer of peers) { this.send(await Packet.encode(packet), peer.port, peer.address) - peer.lastSend = Date.now() } if (this.onMulticast) this.onMulticast(packet) @@ -709,16 +708,16 @@ export class Peer { */ async requestReflection () { if (this.closing || this.indexed || this.reflectionId) { - this._debug(this.peerId, '<> REFLECT ABORTED', this.reflectionId) + this._onDebug('<> REFLECT ABORTED', this.reflectionId) return } if (this.natType && (this.lastUpdate > 0 && (Date.now() - this.config.keepalive) < this.lastUpdate)) { - this._debug(this.peerId, `<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) + this._onDebug(`<> REFLECT NOT NEEDED (last-recv=${Date.now() - this.lastUpdate}ms)`) return } - this._debug(this.peerId, '-> REQ REFLECT', this.reflectionId, this.reflectionStage) + this._onDebug('-> REQ REFLECT', this.reflectionId, this.reflectionStage) if (this.onConnecting) this.onConnecting({ code: -1, status: `Entering reflection (lastUpdate ${Date.now() - this.lastUpdate}ms)` }) const peers = [...this.peers] @@ -727,7 +726,7 @@ export class Peer { if (peers.length < 2) { if (this.onConnecting) this.onConnecting({ code: -1, status: 'Not enough pingable peers' }) - this._debug(this.peerId, 'XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING', peers) + this._onDebug('XX REFLECT NOT ENOUGH PINGABLE PEERS - RETRYING', peers) // tell all well-known peers that we would like to hear from them, if // we hear from any we can ask for the reflection information we need. @@ -760,7 +759,7 @@ export class Peer { if (this.reflectionTimeout) this._clearTimeout(this.reflectionTimeout) this.reflectionStage = 1 - this._debug(this.peerId, '-> NAT REFLECT - STAGE1: A', this.reflectionId) + this._onDebug('-> NAT REFLECT - STAGE1: A', this.reflectionId) const list = peers.filter(p => p.probed).sort(() => Math.random() - 0.5) const peer = list.length ? list[0] : peers[0] peer.probed = Date.now() // mark this peer as being used to provide port info @@ -770,7 +769,7 @@ export class Peer { this.probeReflectionTimeout = this._setTimeout(() => { this.probeReflectionTimeout = null if (this.reflectionStage !== 1) return - this._debug(this.peerId, 'XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) + this._onDebug('XX NAT REFLECT - STAGE1: C - TIMEOUT', this.reflectionId) if (this.onConnecting) this.onConnecting({ code: 1, status: 'Timeout' }) this.reflectionStage = 1 @@ -778,7 +777,7 @@ export class Peer { this.requestReflection() }, 1024) - this._debug(this.peerId, '-> NAT REFLECT - STAGE1: B', this.reflectionId) + this._onDebug('-> NAT REFLECT - STAGE1: B', this.reflectionId) return } @@ -808,12 +807,12 @@ export class Peer { const peer2 = peers.filter(p => !p.probed).sort(() => Math.random() - 0.5)[0] if (!peer1 || !peer2) { - this._debug(this.peerId, 'XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') + this._onDebug('XX NAT REFLECT - STAGE2: INSUFFICENT PEERS - RETRYING') if (this.onConnecting) this.onConnecting({ code: 1.5, status: 'Insufficent Peers' }) return this._setTimeout(() => this.requestReflection(), 256) } - this._debug(this.peerId, '-> NAT REFLECT - STAGE2: START', this.reflectionId) + this._onDebug('-> NAT REFLECT - STAGE2: START', this.reflectionId) // reset reflection variables to defaults this.nextNatType = NAT.UNKNOWN @@ -838,7 +837,7 @@ export class Peer { if (this.onConnecting) this.onConnecting({ code: 2, status: 'Timeout' }) this.reflectionStage = 1 this.reflectionId = null - this._debug(this.peerId, 'XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) + this._onDebug('XX NAT REFLECT - STAGE2: TIMEOUT', this.reflectionId) return this.requestReflection() }, 2048) } @@ -909,11 +908,11 @@ export class Peer { args.sharedKey = sharedKey - const clusterId = Buffer.from(args.clusterId || this.config.clusterId || '') - const subclusterId = Buffer.from(keys.publicKey) + const clusterId = args.clusterId || this.config.clusterId + const subclusterId = keys.publicKey - const cid = clusterId?.toString('base64') - const scid = subclusterId?.toString('base64') + const cid = Buffer.from(clusterId || '').toString('base64') + const scid = Buffer.from(subclusterId || '').toString('base64') this.clusters[cid] ??= {} this.clusters[cid][scid] = args @@ -932,7 +931,7 @@ export class Peer { } }) - this._debug(this.peerId, `-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) + this._onDebug(`-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) if (this.onState) this.onState(this.getState()) this.mcast(packet) @@ -1041,7 +1040,7 @@ export class Peer { const keys = await Encryption.createKeyPair(sharedKey) args.subclusterId = Buffer.from(keys.publicKey) - args.clusterId = args.clusterId || this.config.clusterId + args.clusterId = Buffer.from(args.clusterId || this.config.clusterId) const message = this.encryption.seal(args.message, keys) const packets = await this._message2packets(PacketPublish, message, args) @@ -1084,7 +1083,7 @@ export class Peer { this.lastSync = Date.now() const summary = await this.cache.summarize('', this.cachePredicate) - this._debug(this.peerId, `-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) + this._onDebug(`-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) // if we are out of sync send our cache summary @@ -1128,7 +1127,7 @@ export class Peer { this.returnRoutes.set(p.usr3.toString('hex'), {}) this.gate.set(pid, 1) // don't accidentally spam - this._debug(this.peerId, `-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) + this._onDebug(`-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) await this.mcast(p) } @@ -1192,7 +1191,7 @@ export class Peer { if (!peer.localPeer) peer.localPeer = this if (!this.connections) this.connections = new Map() - this._debug(this.peerId, '<- CONNECTION ( ' + + this._onDebug('<- CONNECTION ( ' + `peerId=${peer.peerId.slice(0, 6)}, ` + `address=${address}:${port}, ` + `type=${packet.type}, ` + @@ -1219,7 +1218,7 @@ export class Peer { this.metrics.i[packet.type]++ this.lastSync = Date.now() - const pid = Buffer.from(packet.packetId)?.toString('hex') + const pid = Buffer.from(packet.packetId || '').toString('hex') if (!isBufferLike(packet.message)) return if (this.gate.has(pid)) return @@ -1237,7 +1236,7 @@ export class Peer { if (this.onSync) this.onSync(packet, port, address, { remote, local }) const remoteBuckets = remote.buckets.filter(Boolean).length - this._debug(this.peerId, `<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) + this._onDebug(`<- ON SYNC (from=${address}:${port}, local=${local.hash.slice(0, 8)}, remote=${remote.hash.slice(0, 8)} remote-buckets=${remoteBuckets})`) for (let i = 0; i < local.buckets.length; i++) { // @@ -1256,7 +1255,7 @@ export class Peer { if (!this.cachePredicate(packet)) continue const pid = Buffer.from(packet.packetId).toString('hex') - this._debug(this.peerId, `-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) + this._onDebug(`-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) this.send(await Packet.encode(packet), port, address) } @@ -1309,7 +1308,7 @@ export class Peer { if (queryTimestamp < (Date.now() - 2048)) return const type = queryType === 1 ? 'question' : 'answer' - this._debug(this.peerId, `<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) + this._onDebug(`<- QUERY (type=${type}, from=${address}:${port}, packet=${pid.slice(0, 8)})`) let rinfo = { port, address } @@ -1370,7 +1369,7 @@ export class Peer { } if (packet.hops >= this.maxHops) return - this._debug(this.peerId, '>> QUERY RELAY', port, address) + this._onDebug('>> QUERY RELAY', port, address) return await this.mcast(packet) } @@ -1459,7 +1458,7 @@ export class Peer { const { reflectionId, pingId, isReflection, responderPeerId } = packet.message - this._debug(this.peerId, `<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) + this._onDebug(`<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) const peer = this.getPeer(packet.message.responderPeerId) if (packet.message.isConnection) { @@ -1478,10 +1477,10 @@ export class Peer { if (!this.reflectionFirstResponder) { this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - this._debug(this.peerId, '<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) + this._onDebug('<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) } else { if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) - this._debug(this.peerId, '<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) + this._onDebug('<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) if (packet.message.address !== this.address) return this.nextNatType |= ( @@ -1490,7 +1489,7 @@ export class Peer { ? NAT.MAPPING_ENDPOINT_INDEPENDENT : NAT.MAPPING_ENDPOINT_DEPENDENT - this._debug( + this._onDebug( this.peerId, `++ NAT REFLECT - STATE UPDATE (natType=${this.natType}, nextType=${this.nextNatType})`, packet.message.port, @@ -1520,7 +1519,7 @@ export class Peer { for (const peer of this.peers) { peer.lastRequest = Date.now() - this._debug(this.peerId, `-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) + this._onDebug(`-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) await this.ping(peer, false, { message: { @@ -1534,7 +1533,7 @@ export class Peer { if (this.onNat) this.onNat(this.natType) - this._debug(this.peerId, `++ NAT (type=${NAT.toString(this.natType)})`) + this._onDebug(`++ NAT (type=${NAT.toString(this.natType)})`) this.sendUnpublished() // } @@ -1549,7 +1548,7 @@ export class Peer { this.address = packet.message.address this.port = packet.message.port - this._debug(this.peerId, `++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) + this._onDebug(`++ NAT UPDATE STATE (address=${this.address}, port=${this.port})`) } } @@ -1602,7 +1601,7 @@ export class Peer { const cid = Buffer.from(clusterId).toString('base64') const scid = Buffer.from(subclusterId).toString('base64') - this._debug(this.peerId, '<- INTRO (' + + this._onDebug('<- INTRO (' + `isRendezvous=${packet.message.isRendezvous}, ` + `from=${address}:${port}, ` + `to=${packet.message.address}:${packet.message.port}, ` + @@ -1641,18 +1640,18 @@ export class Peer { }, 1024 * 2) if (packet.message.isRendezvous) { - this._debug(this.peerId, `<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) + this._onDebug(`<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) } - this._debug(this.peerId, `++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) + this._onDebug(`++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { - this._debug(this.peerId, `## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) + this._onDebug(`## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) let i = 0 if (!this.socketPool) { this.socketPool = Array.from({ length: 256 }, (_, index) => { - return this.dgram().createSocket('udp4', null, this, index).unref() + return this.dgram.createSocket('udp4', null, this, index).unref() }) } @@ -1733,7 +1732,7 @@ export class Peer { // create a new socket to replace it in the pool const oldIndex = this.socketPool.findIndex(s => s === pooledSocket) - this.socketPool[oldIndex] = this.dgram().createSocket('udp4', null, this).unref() + this.socketPool[oldIndex] = this.dgram.createSocket('udp4', null, this).unref() this._onMessage(msg, rinfo) }) @@ -1751,7 +1750,7 @@ export class Peer { if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { // TODO could allow multiple proxies this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) - this._debug(this.peerId, '++ INTRO CHOSE PROXY STRATEGY') + this._onDebug('++ INTRO CHOSE PROXY STRATEGY') } if (strategy === NAT.STRATEGY_TRAVERSAL_OPEN) { @@ -1771,11 +1770,11 @@ export class Peer { } if (strategy === NAT.STRATEGY_DIRECT_CONNECT) { - this._debug(this.peerId, '++ NAT STRATEGY_DIRECT_CONNECT') + this._onDebug('++ NAT STRATEGY_DIRECT_CONNECT') } if (strategy === NAT.STRATEGY_DEFER) { - this._debug(this.peerId, '++ NAT STRATEGY_DEFER') + this._onDebug('++ NAT STRATEGY_DEFER') } this.ping(peer, true, props) @@ -1813,7 +1812,7 @@ export class Peer { const cid = Buffer.from(clusterId).toString('base64') const scid = Buffer.from(subclusterId).toString('base64') - this._debug(this.peerId, '<- JOIN (' + + this._onDebug('<- JOIN (' + `peerId=${peerId.slice(0, 6)}, ` + `clock=${packet.clock}, ` + `hops=${packet.hops}, ` + @@ -1836,7 +1835,7 @@ export class Peer { // TODO it would tighten up the transition time between dropped peers // if we check strategy from (packet.message.natType, this.natType) and // make introductions that create more mutually known peers. - this._debug(this.peerId, `<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) + this._onDebug(`<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) const data = await Packet.encode(new PacketJoin({ clock: packet.clock, @@ -1878,14 +1877,14 @@ export class Peer { const peer = this.peers.find(p => p.peerId === packet.message.rendezvousRequesterPeerId) if (!peer) { - this._debug(this.peerId, '<- INTRO FROM RENDEZVOUS FAILED', packet) + this._onDebug('<- INTRO FROM RENDEZVOUS FAILED', packet) return } // peer.natType = packet.message.rendezvousType peers = [peer] - this._debug(this.peerId, `<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + this._onDebug(`<- JOIN EXECUTING RENDEZVOUS (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) } for (const peer of peers) { @@ -1921,8 +1920,8 @@ export class Peer { // Send intro1 to the peer described in the message // Send intro2 to the peer in this loop // - this._debug(this.peerId, `>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) - this._debug(this.peerId, `>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) + this._onDebug(`>> INTRO SEND (from=${peer.address}:${peer.port}, to=${packet.message.address}:${packet.message.port})`) + this._onDebug(`>> INTRO SEND (from=${packet.message.address}:${packet.message.port}, to=${peer.address}:${peer.port})`) peer.lastRequest = Date.now() @@ -1946,7 +1945,7 @@ export class Peer { packet.message.rendezvousDeadline = Date.now() + this.config.keepalive } - this._debug(this.peerId, `-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) + this._onDebug(`-> JOIN RELAY (peerId=${peerId.slice(0, 6)}, from=${peerAddress}:${peerPort})`) this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) if (packet.hops <= 1) { @@ -1967,15 +1966,19 @@ export class Peer { // if (cluster && cluster[packet.subclusterId]) { const pid = Buffer.from(packet.packetId).toString('hex') + + if (this.gate.has(pid)) return + this.gate.set(pid, 6) + if (this.cache.has(pid)) { - this.metrics.i.REJECTED++ + this.metrics.i.DROPPED++ const cid = Buffer.from(packet.clusterId).toString('base64') const scid = Buffer.from(packet.subclusterId).toString('base64') - this._debug(this.peerId, `<- PUBLISH DUPE (packetId=${pid.slice(0, 8)}, clusterId=${cid}, subclueterId=${scid}, from=${address}:${port}, hops=${packet.hops})`) + this._onDebug(`<- PUBLISH DROP (packetId=${pid.slice(0, 8)}, clusterId=${cid}, subclueterId=${scid}, from=${address}:${port}, hops=${packet.hops})`) return } - this._debug(this.peerId, `<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${Buffer.from(packet.usr4).toString() === 'SYNC'})`) + this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${Buffer.from(packet.usr4).toString() === 'SYNC'})`) this.cacheInsert(packet) const ignorelist = [{ address, port }] @@ -2049,16 +2052,16 @@ export class Peer { // stream message is for another peer const peerTo = this.peers.find(p => p.peerId === streamTo) if (!peerTo) { - this._debug(this.peerId, `XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) + this._onDebug(`XX STREAM RELAY FORWARD DESTINATION NOT REACHABLE (to=${streamTo})`) return } if (packet.hops >= this.maxHops) { - this._debug(this.peerId, `XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) + this._onDebug(`XX STREAM RELAY MAX HOPS EXCEEDED (to=${streamTo})`) return } - this._debug(this.peerId, `>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) + this._onDebug(`>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) this.send(await Packet.encode(packet), peerTo.port, peerTo.address) if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) } @@ -2081,7 +2084,7 @@ export class Peer { this.gate.set(pid, 1) const { reflectionId } = packet.message - this._debug(this.peerId, `<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) + this._onDebug(`<- NAT PROBE (from=${address}:${port}, stage=${this.reflectionStage}, id=${reflectionId})`) if (this.onProbe) this.onProbe(data, port, address) if (this.reflectionId !== reflectionId || !this.reflectionId) return @@ -2090,7 +2093,7 @@ export class Peer { // const reflectionStage = reflectionId ? parseInt(reflectionId.slice(-1), 16) : 0 if (this.reflectionStage === 1) { - this._debug(this.peerId, '<- NAT REFLECT - STAGE1: probe received', reflectionId) + this._onDebug('<- NAT REFLECT - STAGE1: probe received', reflectionId) if (!packet.message?.port) return // message must include a port number // successfully discovered the probe socket external port @@ -2104,16 +2107,16 @@ export class Peer { } if (this.reflectionStage === 2) { - this._debug(this.peerId, '<- NAT REFLECT - STAGE2: probe received', reflectionId) + this._onDebug('<- NAT REFLECT - STAGE2: probe received', reflectionId) // if we have previously sent an outbount message to this peer on the probe port // then our NAT will have a mapping for their IP, but not their IP+Port. if (!NAT.isFirewallDefined(this.nextNatType)) { this.nextNatType |= NAT.FIREWALL_ALLOW_KNOWN_IP - this._debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) + this._onDebug(`<> PROBE STATUS: NAT.FIREWALL_ALLOW_KNOWN_IP (${packet.message.port} -> ${this.nextNatType})`) } else { this.nextNatType |= NAT.FIREWALL_ALLOW_ANY - this._debug(this.peerId, `<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) + this._onDebug(`<> PROBE STATUS: NAT.FIREWALL_ALLOW_ANY (${packet.message.port} -> ${this.nextNatType})`) } // wait for all messages to arrive @@ -2138,14 +2141,14 @@ export class Peer { const cid = Buffer.from(packet.clusterId).toString('base64') const scid = Buffer.from(packet.subclusterId).toString('base64') - // debug('<- PACKET', packet.type, port, address) + // onDebug('<- PACKET', packet.type, port, address) const clusters = this.clusters[cid] const subcluster = clusters && clusters[scid] if (!this.config.limitExempt) { if (rateLimit(this.rates, packet.type, port, address, subcluster)) { - this._debug(this.peerId, `XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) - this.metrics.i.REJECTED++ + this._onDebug(`XX RATE LIMIT HIT (from=${address}, type=${packet.type})`) + this.metrics.i.DROPPED++ return } if (this.onLimit && !this.onLimit(packet, port, address)) return diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 694680ab11..3c9bbe687f 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -361,10 +361,14 @@ unsigned short createLogSocket() { int count = 0; id logEntry; + NSString *lastMessage; while ((logEntry = [enumerator nextObject]) != nil) { count++; OSLogEntryLog *entry = (OSLogEntryLog *)logEntry; NSString *message = [entry composedMessage]; + if ([message isEqualToString:lastMessage]) continue; + + lastMessage = message; std::cout << message.UTF8String << std::endl; } } From 27b37bad3985fc308857f3c534f6bafb5c482578 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 22 Jun 2024 17:13:59 +0200 Subject: [PATCH 0826/1178] refactor(cli): improve log deduplication --- src/cli/cli.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 3c9bbe687f..0e26a39cda 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -361,14 +361,14 @@ unsigned short createLogSocket() { int count = 0; id logEntry; - NSString *lastMessage; + NSDate *timestamp = [NSDate date]; while ((logEntry = [enumerator nextObject]) != nil) { count++; OSLogEntryLog *entry = (OSLogEntryLog *)logEntry; - NSString *message = [entry composedMessage]; - if ([message isEqualToString:lastMessage]) continue; + if ([entry.timestamp isEqualToDate:timestamp]) continue; - lastMessage = message; + NSString *message = [entry composedMessage]; + timestamp = entry.timestamp; std::cout << message.UTF8String << std::endl; } } From 262d8bd927fb6e45bddb69366f9d184ccecff1bb Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 22 Jun 2024 17:34:08 +0200 Subject: [PATCH 0827/1178] fix(cli): corrected property names on logging api --- src/cli/cli.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 0e26a39cda..7dae54d210 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -365,10 +365,10 @@ unsigned short createLogSocket() { while ((logEntry = [enumerator nextObject]) != nil) { count++; OSLogEntryLog *entry = (OSLogEntryLog *)logEntry; - if ([entry.timestamp isEqualToDate:timestamp]) continue; + if ([entry.date isEqualToDate:timestamp]) continue; NSString *message = [entry composedMessage]; - timestamp = entry.timestamp; + timestamp = entry.date; std::cout << message.UTF8String << std::endl; } } From 5b6672f34f64c7f0371dc6d3bd4ce90243e1193b Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 22 Jun 2024 18:41:55 +0200 Subject: [PATCH 0828/1178] fix(cli): move position period outside of recv to preserve last time-point --- src/cli/cli.cc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 7dae54d210..f2f7ba8473 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -314,6 +314,7 @@ static uv_loop_t *loop = nullptr; static uv_udp_t logsocket; #if defined(__APPLE__) + unsigned short createLogSocket() { std::promise<int> p; std::future<int> future = p.get_future(); @@ -326,6 +327,9 @@ unsigned short createLogSocket() { struct sockaddr_in addr; int port; + NSDate* lastLogTime = [NSDate now]; + logsocket.data = (void*) lastLogTime; + uv_ip4_addr("0.0.0.0", 0, &addr); uv_udp_bind(&logsocket, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR); @@ -336,6 +340,8 @@ unsigned short createLogSocket() { }, [](uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) { if (nread > 0) { + NSDate* lastLogTime = (NSDate*)req->data; + String data = trim(String(buf->base, nread)); if (data[0] != '+') return; @@ -348,8 +354,7 @@ unsigned short createLogSocket() { return; } - auto offset = [[NSDate now] dateByAddingTimeInterval: -1]; // this may not be enough - auto position = [logs positionWithDate: offset]; + auto position = [logs positionWithDate: lastLogTime]; auto predicate = [NSPredicate predicateWithFormat: @"(category == 'socket.runtime')"]; auto enumerator = [logs entriesEnumeratorWithOptions: 0 position: position predicate: predicate error: &err]; @@ -361,15 +366,14 @@ unsigned short createLogSocket() { int count = 0; id logEntry; - NSDate *timestamp = [NSDate date]; while ((logEntry = [enumerator nextObject]) != nil) { - count++; OSLogEntryLog *entry = (OSLogEntryLog *)logEntry; - if ([entry.date isEqualToDate:timestamp]) continue; + + count++; NSString *message = [entry composedMessage]; - timestamp = entry.date; std::cout << message.UTF8String << std::endl; + req->data = (void*) [NSDate now]; } } } From 5d2f4cc445c94c8faf3516d8bc6b4db5cd867fb6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 16 Jun 2024 09:34:20 -0400 Subject: [PATCH 0829/1178] fix(src): fix compile errors for windows --- src/core/json.cc | 2 +- src/core/json.hh | 2 +- src/core/modules/ai.cc | 7 +++++++ src/core/modules/ai.hh | 8 -------- src/core/webview.hh | 2 +- src/ipc/scheme_handlers.cc | 2 +- src/ipc/scheme_handlers.hh | 4 ++-- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/core/json.cc b/src/core/json.cc index 3aa7c791bf..eba6280bf6 100644 --- a/src/core/json.cc +++ b/src/core/json.cc @@ -158,7 +158,7 @@ namespace SSC::JSON { this->pointer = SharedPointer<void>(new Number((double) number)); this->type = Type::Number; } -#else +#elif !SOCKET_RUNTIME_PLATFORM_WINDOWS Any::Any (long long number) { this->pointer = SharedPointer<void>(new Number((double) number)); this->type = Type::Number; diff --git a/src/core/json.hh b/src/core/json.hh index 75a0ea5439..e3ded25443 100644 --- a/src/core/json.hh +++ b/src/core/json.hh @@ -180,7 +180,7 @@ namespace SSC::JSON { #if SOCKET_RUNTIME_PLATFORM_APPLE Any (size_t); Any (ssize_t); - #else + #elif !SOCKET_RUNTIME_PLATFORM_WINDOWS Any (long long); #endif Any (const Number); diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index 8fbc9f691d..c8889a446f 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -2,6 +2,13 @@ #include "../resource.hh" #include "ai.hh" +#if defined (_WIN32) + #ifndef NOMINMAX + #define NOMINMAX + #endif +#endif + + namespace SSC { static JSON::Object::Entries ERR_AI_LLM_NOEXISTS ( const String& source, diff --git a/src/core/modules/ai.hh b/src/core/modules/ai.hh index 034cf11c33..21561891ff 100644 --- a/src/core/modules/ai.hh +++ b/src/core/modules/ai.hh @@ -11,14 +11,6 @@ // #include <cmath> // #include <ctime> -#if defined (_WIN32) - #define WIN32_LEAN_AND_MEAN - - #ifndef NOMINMAX - #define NOMINMAX - #endif -#endif - namespace SSC { class LLM; class Core; diff --git a/src/core/webview.hh b/src/core/webview.hh index dac6a0d507..f20f95e98f 100644 --- a/src/core/webview.hh +++ b/src/core/webview.hh @@ -84,7 +84,7 @@ namespace SSC { using WebViewSettings = WebKitSettings; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS using WebView = ICoreWebView2; - using WebViewSettings = ComPtr<CoreWebView2EnvironmentOptions>; + using WebViewSettings = Microsoft::WRL::ComPtr<CoreWebView2EnvironmentOptions>; #elif SOCKET_RUNTIME_PLATFORM_ANDROID using WebView = CoreAndroidWebView; using WebViewSettings = CoreAndroidWebViewSettings; diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index c7c3fe4e72..cb754b3398 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -1444,7 +1444,7 @@ namespace SSC::IPC { this->setHeader(name, std::to_string(value)); } -#if !SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_PLATFORM_ANDROID +#if !SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_PLATFORM_ANDROID && !SOCKET_RUNTIME_PLATFORM_WINDOWS void SchemeHandlers::Response::setHeader (const String& name, uint64_t value) { this->setHeader(name, std::to_string(value)); } diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index a82e318ef7..481188c13b 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -217,7 +217,7 @@ namespace SSC::IPC { void setHeader (const String& name, const Headers::Value& value); void setHeader (const String& name, size_t value); void setHeader (const String& name, int64_t value); - #if !SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_PLATFORM_ANDROID + #if !SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_PLATFORM_ANDROID && !SOCKET_RUNTIME_PLATFORM_WINDOWS void setHeader (const String& name, uint64_t value); #endif void setHeader (const Headers::Header& header); @@ -256,7 +256,7 @@ namespace SSC::IPC { RequestMap activeRequests; #if SOCKET_RUNTIME_PLATFORM_WINDOWS - Set<ComPtr<CoreWebView2CustomSchemeRegistration>> coreWebView2CustomSchemeRegistrations; + Set<Microsoft::WRL::ComPtr<CoreWebView2CustomSchemeRegistration>> coreWebView2CustomSchemeRegistrations; #endif SchemeHandlers (Bridge* bridge); From d4f2300d2337aeca41b05fc213c3916c0b208e73 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 16 Jun 2024 09:34:37 -0400 Subject: [PATCH 0830/1178] fix(bin): fixes for windows --- bin/build-runtime-library.sh | 14 +++++++------- bin/install.sh | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index 3b902d93d8..b8130ee7f0 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash declare root="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" -declare clang="${CXX:-"${CLANG:-$(which clang++)}"}" +declare clang="${CXX:-"${CLANG:-"$(which clang++)"}"}" declare cache_path="$root/build/cache" source "$root/bin/functions.sh" @@ -190,12 +190,12 @@ function generate_llama_build_info () { build_commit=$(printf '%s' "$out" | tr -d '\n') fi - if out=$($clang --version | head -1); then - build_compiler=$out + if out=$("$clang" --version | head -1); then + build_compiler="$out" fi - if out=$($clang -dumpmachine); then - build_target=$out + if out=$("$clang" -dumpmachine); then + build_target="$out" fi echo "# generating llama build info" @@ -208,7 +208,7 @@ function generate_llama_build_info () { char const *LLAMA_BUILD_TARGET = "$build_target"; LLAMA_BUILD_INFO - quiet $clang "${cflags[@]}" -c $source -o ${source/cpp/o} || onsignal + quiet "$clang" "${cflags[@]}" -c $source -o ${source/cpp/o} || onsignal } function build_linux_desktop_extension_object () { @@ -270,7 +270,7 @@ function main () { mkdir -p "$(dirname "$object")" echo "# compiling object ($arch-$platform) $(basename "$source")" - quiet $clang "${cflags[@]}" -c "$source" -o "$object" || onsignal + quiet "$clang" "${cflags[@]}" -c "$source" -o "$object" || onsignal echo "ok - built ${source/$src_directory\//} -> ${object/$output_directory\//} ($arch-$platform)" if (( $(stat_mtime "$header") > $(stat_mtime "$source") )); then diff --git a/bin/install.sh b/bin/install.sh index 95537b79ec..4e8264c128 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -871,7 +871,7 @@ function _compile_llama { quiet cmake --install build die $? "not ok - libllama.a (desktop)" else - if ! test -f "$BUILD_DIR/$target-$platform/lib$d/libllama.lib"; then + if ! test -f "$BUILD_DIR/$target-$platform/lib$d/llama.lib"; then local config="Release" if [[ -n "$DEBUG" ]]; then config="Debug" @@ -880,8 +880,8 @@ function _compile_llama { quiet cmake -S .. -B . ${cmake_args[@]} quiet cmake --build . --config $config mkdir -p "$BUILD_DIR/$target-$platform/lib$d" - quiet echo "cp -up $STAGING_DIR/build/$config/libllama.lib "$BUILD_DIR/$target-$platform/lib$d/libllama.lib"" - cp -up "$STAGING_DIR/build/$config/libllama.lib" "$BUILD_DIR/$target-$platform/lib$d/libllama.lib" + quiet echo "cp -up $STAGING_DIR/build/$config/llama.lib "$BUILD_DIR/$target-$platform/lib$d/llama.lib"" + cp -up "$STAGING_DIR/build/$config/llama.lib" "$BUILD_DIR/$target-$platform/lib$d/llama.lib" if [[ -n "$DEBUG" ]]; then cp -up "$STAGING_DIR"/build/$config/llama_a.pdb "$BUILD_DIR/$target-$platform/lib$d/llama_a.pdb" fi; From 1fd5681950975f469e8d989e8156b70e74c41cc2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:33:10 +0200 Subject: [PATCH 0831/1178] refactor(app): consolidate windows app and 'WndProc' logic --- src/app/app.cc | 317 ++++++++++++++++++++++++++++++++++++++----------- src/app/app.hh | 46 ++++--- 2 files changed, 280 insertions(+), 83 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 7619c15918..cb25b84b7f 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -477,6 +477,7 @@ namespace SSC { static App* applicationInstance = nullptr; #if SOCKET_RUNTIME_PLATFORM_WINDOWS + static Atomic<bool> isConsoleVisible = false; static FILE* console = nullptr; static inline void alert (const WString &ws) { @@ -490,6 +491,201 @@ namespace SSC { static inline void alert (const char* s) { MessageBoxA(nullptr, s, _TEXT("Alert"), MB_OK | MB_ICONSTOP); } + + static void showWindowsConsole () { + if (!isConsoleVisible) { + isConsoleVisible = true; + AllocConsole(); + freopen_s(&console, "CONOUT$", "w", stdout); + } + } + + static void hideWindowsConsole () { + if (isConsoleVisible) { + isConsoleVisible = false; + fclose(console); + FreeConsole(); + } + } + + // message is defined in WinUser.h + // https://raw.githubusercontent.com/tpn/winsdk-10/master/Include/10.0.10240.0/um/WinUser.h + static LRESULT CALLBACK onWindowProcMessage ( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam + ) { + auto app = SSC::App::sharedApplication(); + + if (app == nullptr) { + return 0; + } + + auto window = reinterpret_cast<WindowManager::ManagedWindow*>( + GetWindowLongPtr(hWnd, GWLP_USERDATA) + ); + + // invalidate `window` pointer that potentially is leaked + if (window != nullptr && app->windowManager.getWindow(window) != window) { + window = nullptr; + } + + auto userConfig = window != nullptr + ? reinterpret_cast<Window*>(window)->options.userConfig + : getUserConfig(); + + if (message == WM_COPYDATA) { + auto copyData = reinterpret_cast<PCOPYDATASTRUCT>(lParam); + message = (UINT) copyData->dwData; + wParam = (WPARAM) copyData->cbData; + lParam = (LPARAM) copyData->lpData; + } + + switch (message) { + case WM_SIZE: { + if (window == nullptr || window->controller == nullptr) { + break; + } + + RECT bounds; + GetClientRect(hWnd, &bounds); + window->size.height = bounds.bottom - bounds.top; + window->size.width = bounds.right - bounds.left; + window->controller->put_Bounds(bounds); + break; + } + + case WM_SOCKET_TRAY: { + // XXX(@jwerle, @heapwolf): is this a correct for an `isAgent` predicate? + auto isAgent = userConfig.count("tray_icon") != 0; + + if (lParam == WM_LBUTTONDOWN) { + SetForegroundWindow(hWnd); + if (isAgent) { + POINT point; + GetCursorPos(&point); + TrackPopupMenu( + window->menutray, + TPM_BOTTOMALIGN | TPM_LEFTALIGN, + point.x, + point.y, + 0, + hWnd, + NULL + ); + } + + PostMessage(hWnd, WM_NULL, 0, 0); + + // broadcast an event to all the windows that the tray icon was clicked + for (auto window : app->windowManager->windows) { + if (window != nullptr) { + window->bridge->router.emit("tray", "true"); + } + } + } + + // XXX: falls through to `WM_COMMAND` below + } + + case WM_COMMAND: { + if (window == nullptr) { + break; + } + + if (window->menuMap.contains(wParam)) { + String meta(window->menuMap[wParam]); + auto parts = split(meta, '\t'); + + if (parts.size() > 1) { + auto title = parts[0]; + auto parent = parts[1]; + + if (title.find("About") == 0) { + reinterpret_cast<Window*>(window)->about(); + break; + } + + if (title.find("Quit") == 0) { + window->exit(0); + break; + } + + window->eval(getResolveMenuSelectionJavaScript("0", title, parent, "system")); + } + } else if (window->menuTrayMap.contains(wParam)) { + String meta(window->menuTrayMap[wParam]); + auto parts = split(meta, ':'); + + if (parts.size() > 0) { + auto title = trim(parts[0]); + auto tag = parts.size() > 1 ? trim(parts[1]) : ""; + window->eval(getResolveMenuSelectionJavaScript("0", title, tag, "tray")); + } + } + + break; + } + + case WM_SETTINGCHANGE: { + // TODO(heapwolf): Dark mode + break; + } + + case WM_CREATE: { + // TODO(heapwolf): Dark mode + SetWindowTheme(hWnd, L"Explorer", NULL); + SetMenu(hWnd, CreateMenu()); + break; + } + + case WM_CLOSE: { + if (!window->options.closable) { + break; + } + + auto index = window->index; + const JSON::Object json = JSON::Object::Entries { + {"data", window->index} + }; + + for (auto window : app->windowManager->windows) { + if (window != nullptr && window->index != index) { + window->eval(getEmitToRenderProcessJavaScript("window-closed", json.str())); + } + } + + app->windowManager->destroyWindow(index); + break; + } + + case WM_HOTKEY: { + window->hotkey.onHotKeyBindingCallback((HotKeyBinding::ID) wParam); + break; + } + + case WM_HANDLE_DEEP_LINK: { + auto url = SSC::String(reinterpret_cast<const char*>(lParam), wParam); + const JSON::Object json = JSON::Object::Entries { + {"url", url} + }; + + for (auto window : app->windowManager->windows) { + if (window != nullptr) { + window->bridge->router.emit("applicationurl", json.str()); + } + } + break; + } + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + break; + } + + return 0; + } #endif App* App::sharedApplication () { @@ -514,11 +710,7 @@ namespace SSC { this->init(); } #else - App::App (int _, SharedPointer<Core> core) - : App(core) - {} - - App::App (SharedPointer<Core> core) + App::App (int instanceId, SharedPointer<Core> core) : userConfig(getUserConfig()), core(core), windowManager(core), @@ -528,6 +720,49 @@ namespace SSC { applicationInstance = this; } + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + this->hInstance = reinterpret_cast<HINSTANCE>(instanceId); + + // this fixes bad default quality DPI. + SetProcessDPIAware(); + + if (userConfig["win_logo"].size() == 0 && userConfig["win_icon"].size() > 0) { + userConfig["win_logo"] = fs::path(userConfig["win_icon"]).filename().string(); + } + + auto iconPath = fs::path { getcwd() / fs::path { userConfig["win_logo"] } }; + + HICON icon = (HICON) LoadImageA( + NULL, + iconPath.string().c_str(), + IMAGE_ICON, + GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CXICON), + LR_LOADFROMFILE + ); + + auto windowClassName = userConfig["meta_bundle_identifier"]; + + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = windowClassName.c_str(); + wcex.hIconSm = icon; // ico doesn't auto scale, needs 16x16 icon lol fuck you bill + wcex.hIcon = icon; + wcex.lpfnWndProc = onWindowProcMessage; + + if (!RegisterClassEx(&wcex)) { + alert("Application could not launch, possible missing resources."); + } + + #endif + #if !SOCKET_RUNTIME_DESKTOP_EXTENSION const auto cwd = getcwd(); uv_chdir(cwd.c_str()); @@ -549,6 +784,8 @@ namespace SSC { this->applicationDelegate = [SSCApplicationDelegate new]; this->applicationDelegate.app = this; NSApplication.sharedApplication.delegate = this->applicationDelegate; + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + OleInitialize(nullptr); #endif } @@ -606,7 +843,6 @@ namespace SSC { void App::kill () { this->killed = true; - this->core->shuttingDown = true; this->core->shutdown(); // Distinguish window closing with app exiting shouldExit = true; @@ -619,11 +855,6 @@ namespace SSC { [NSApp terminate: nil]; } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - if (isDebugEnabled()) { - if (w32ShowConsole) { - HideConsole(); - } - } PostQuitMessage(0); #endif } @@ -739,64 +970,14 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_WINDOWS - void App::ShowConsole () { - if (!isConsoleVisible) { - isConsoleVisible = true; - AllocConsole(); - freopen_s(&console, "CONOUT$", "w", stdout); - } - } - - void App::HideConsole () { - if (isConsoleVisible) { - isConsoleVisible = false; - fclose(console); - FreeConsole(); - } + LRESULT App::forwardWindowProcMessage ( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam + ) { + return onWindowProcMessage(hWnd, message, wParam, lParam); } - - App::App (void* h) : App() { - static auto userConfig = getUserConfig(); - this->hInstance = (HINSTANCE) h; - - // this fixes bad default quality DPI. - SetProcessDPIAware(); - - if (userConfig["win_logo"].size() == 0 && userConfig["win_icon"].size() > 0) { - userConfig["win_logo"] = fs::path(userConfig["win_icon"]).filename().string(); - } - - auto iconPath = fs::path { getcwd() / fs::path { userConfig["win_logo"] } }; - - HICON icon = (HICON) LoadImageA( - NULL, - iconPath.string().c_str(), - IMAGE_ICON, - GetSystemMetrics(SM_CXICON), - GetSystemMetrics(SM_CXICON), - LR_LOADFROMFILE - ); - - auto windowClassName = userConfig["meta_bundle_identifier"]; - - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION); - wcex.hCursor = LoadCursor(NULL, IDC_ARROW); - wcex.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); - wcex.lpszMenuName = NULL; - wcex.lpszClassName = windowClassName.c_str(); - wcex.hIconSm = icon; // ico doesn't auto scale, needs 16x16 icon lol fuck you bill - wcex.hIcon = icon; - wcex.lpfnWndProc = Window::WndProc; - - if (!RegisterClassEx(&wcex)) { - alert("Application could not launch, possible missing resources."); - } - }; #endif } diff --git a/src/app/app.hh b/src/app/app.hh index a956ae035b..7cd65a80b5 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -68,12 +68,10 @@ namespace SSC { // created and set in `App::App()` on macOS or // created by `UIApplicationMain` and set in `application:didFinishLaunchingWithOptions:` on iOS SSCApplicationDelegate* applicationDelegate = nullptr; - #endif - - #if SOCKET_RUNTIME_PLATFORM_MACOS - NSAutoreleasePool* pool = [NSAutoreleasePool new]; + #if SOCKET_RUNTIME_PLATFORM_MACOS + NSAutoreleasePool* pool = [NSAutoreleasePool new]; + #endif #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - Atomic<bool> isConsoleVisible = false; _In_ HINSTANCE hInstance; WNDCLASSEX wcex; MSG msg; @@ -91,30 +89,44 @@ namespace SSC { AtomicBool shouldExit = false; AtomicBool killed = false; bool wasLaunchedFromCli = false; - bool w32ShowConsole = false; WindowManager windowManager; ServiceWorkerContainer serviceWorkerContainer; SharedPointer<Core> core = nullptr; Map userConfig; - #if SOCKET_RUNTIME_PLATFORM_WINDOWS - App (void *); - void ShowConsole (); - void HideConsole (); - #endif - #if SOCKET_RUNTIME_PLATFORM_ANDROID - App (JNIEnv* env, jobject self, SharedPointer<Core> core = SharedPointer<Core>(new Core())); + /** + * `App` class constructor for Android. + * The `App` instance is constructed from the context of + * the shared `Application` singleton on Android. This is a + * special case constructor. + */ + App ( + JNIEnv* env, + jobject self, + SharedPointer<Core> core = SharedPointer<Core>(new Core()) + ); #else - App (int instanceId, SharedPointer<Core> core = SharedPointer<Core>(new Core())); - App (SharedPointer<Core> core = SharedPointer<Core>(new Core())); + /** + * `App` class constructor for desktop (Linux, macOS, Windows) and + * iOS (iPhoneOS, iPhoneSimulator, iPad) where `instanceId` can be + * a `HINSTANCE` on Windows or an empty value (`0`) on other platforms. + */ + App ( + int instanceId = 0, + SharedPointer<Core> core = SharedPointer<Core>(new Core()) + ); #endif + App () = delete; App (const App&) = delete; App (App&&) = delete; ~App (); + App& operator = (App&) = delete; + App& operator = (App&&) = delete; + int run (int argc = 0, char** argv = nullptr); void init (); void kill (); @@ -123,6 +135,10 @@ namespace SSC { void dispatch (Function<void()>); String getcwd (); bool hasRuntimePermission (const String& permission) const; + + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + LRESULT forwardWindowProcMessage (HWND, UINT, WPARAM, LPARAM); + #endif }; } #endif From bc28c409f88373c6cd01292796b192710b61e4d8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:33:42 +0200 Subject: [PATCH 0832/1178] refactor(desktop): clean up for 'App' usage --- src/desktop/main.cc | 60 ++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 961fe95f3d..55ad3b0af2 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -22,17 +22,18 @@ // magically gives us argc and argv. // #if SOCKET_RUNTIME_PLATFORM_WINDOWS -#define MAIN \ - static const int argc = __argc; \ - static char** argv = __argv; \ - int CALLBACK WinMain ( \ - _In_ HINSTANCE instanceId, \ - _In_ HINSTANCE hPrevInstance, \ - _In_ LPSTR lpCmdLine, \ - _In_ int nCmdShow) +#define MAIN \ + static const int argc = __argc; \ + static char** argv = __argv; \ + int CALLBACK WinMain ( \ + _In_ HINSTANCE instanceId, \ + _In_ HINSTANCE hPrevInstance, \ + _In_ LPSTR lpCmdLine, \ + _In_ int nCmdShow \ + ) #else -#define MAIN \ - const int instanceId = 0; \ +#define MAIN \ + static const int instanceId = 0; \ int main (int argc, char** argv) #endif @@ -58,8 +59,6 @@ static void installSignalHandler (int signum, void (*handler)(int)) { using namespace SSC; -static App *app_ptr = nullptr; - static Function<void(int)> shutdownHandler; // propagate signals to the default window which will use the @@ -116,12 +115,14 @@ void signalHandler (int signum) { #if SOCKET_RUNTIME_PLATFORM_LINUX static void handleApplicationURLEvent (const String url) { + auto app = App::sharedApplication(); + JSON::Object json = JSON::Object::Entries {{ "url", url }}; - if (app_ptr != nullptr) { - for (auto window : app_ptr->windowManager.windows) { + if (app != nullptr) { + for (auto window : app->windowManager.windows) { if (window != nullptr) { if (window->index == 0 && window->window && window->webview) { gtk_widget_show_all(GTK_WIDGET(window->window)); @@ -254,13 +255,6 @@ MAIN { static App app(instanceId); static auto userConfig = getUserConfig(); - // TODO(trevnorris): Since App is a singleton, follow the CppCoreGuidelines - // better in how it's handled in the future. - // For now make a pointer reference since there is some member variable name - // collision in the call to shutdownHandler when it's being called from the - // windowManager instance. - app_ptr = &app; - const String devHost = getDevHost(); const auto devPort = getDevPort(); @@ -335,18 +329,19 @@ MAIN { static Function<void()> pollForMessage = []() { Thread thread([] () { + auto app = App::sharedApplication(); while (dbus_connection_read_write_dispatch(connection, 256)); - app_ptr->dispatch(pollForMessage); + app->dispatch(pollForMessage); }); thread.detach(); }; - app_ptr->dispatch(pollForMessage); + app.dispatch(pollForMessage); if (appProtocol.size() > 0 && argc > 1 && String(argv[1]).starts_with(appProtocol + ":")) { const auto uri = String(argv[1]); - app_ptr->dispatch([uri]() { - app_ptr->core->dispatchEventLoop([uri]() { + app.dispatch([uri]() { + app.core->dispatchEventLoop([uri]() { handleApplicationURLEvent(uri); }); }); @@ -447,8 +442,8 @@ MAIN { reinterpret_cast<LPARAM>(&data) ); } else { - app_ptr->dispatch([hWnd, lpCmdLine]() { - Window::WndProc( + app.dispatch([hWnd, lpCmdLine]() { + app.forwardWindowProcMessage( hWnd, WM_HANDLE_DEEP_LINK, (WPARAM) strlen(lpCmdLine), @@ -509,13 +504,6 @@ MAIN { // launched from the `ssc` cli app.wasLaunchedFromCli = s.find("--from-ssc") == 0 ? true : false; - #if SOCKET_RUNTIME_PLATFORM_WINDOWS - if (!app.w32ShowConsole && s.find("--w32-console") == 0) { - app.w32ShowConsole = true; - app.ShowConsole(); - } - #endif - if (s.find("--test") == 0) { suffix = "-test"; isTest = true; @@ -896,13 +884,13 @@ MAIN { app.windowManager.destroy(); #if SOCKET_RUNTIME_PLATFORM_APPLE - if (app_ptr->wasLaunchedFromCli) { + if (app.wasLaunchedFromCli) { debug("__EXIT_SIGNAL__=%d", 0); CLI::notify(); } #endif - app_ptr->kill(); + app.kill(); exit(code); }; From dd80fc08be0095d2a1845f4398013e611b0b8d45 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:36:14 +0200 Subject: [PATCH 0833/1178] refactor(platform): improve windows on linux DX, clean up --- src/platform/platform.hh | 23 ++++++++++++++--------- src/platform/types.hh | 3 ++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/platform/platform.hh b/src/platform/platform.hh index e1b2e59754..b70aaf6519 100644 --- a/src/platform/platform.hh +++ b/src/platform/platform.hh @@ -1,6 +1,10 @@ #ifndef SOCKET_RUNTIME_PLATFORM_PLATFORM_H #define SOCKET_RUNTIME_PLATFORM_PLATFORM_H +#if SOCKET_RUNTIME_PLATFORM_WANTS_MINGW +#include <_mingw.h> +#endif + // All Platforms #include <errno.h> #include <math.h> @@ -66,7 +70,7 @@ #undef _WINSOCKAPI_ #define _WINSOCKAPI_ -#include <WinSock2.h> +#include <winsock2.h> #include <windows.h> #include <dwmapi.h> @@ -75,7 +79,7 @@ #include <objidl.h> #include <signal.h> #include <shellapi.h> -#include <shlobj_core.h> +#include <shlobj.h> #include <shlwapi.h> #include <shobjidl.h> #include <tchar.h> @@ -120,20 +124,22 @@ #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include <dlfcn.h> +#else +#endif + +#if SOCKET_RUNTIME_CROSS_COMPILED_HOST +#include <windows.foundation.h> #endif #include <socket/platform.h> #include "string.hh" #include "types.hh" -#if !SOCKET_RUNTIME_PLATFORM_WINDOWS -#include <dlfcn.h> -#endif - namespace SSC { struct RuntimePlatform { - const String arch = ""; - const String os = ""; + const String arch; + const String os; bool mac = false; bool ios = false; bool win = false; @@ -146,5 +152,4 @@ namespace SSC { void msleep (uint64_t ms); uint64_t rand64 (); } - #endif diff --git a/src/platform/types.hh b/src/platform/types.hh index f50c23174f..27ab83d748 100644 --- a/src/platform/types.hh +++ b/src/platform/types.hh @@ -5,6 +5,7 @@ #include <atomic> #include <filesystem> #include <functional> +#include <future> #include <map> #include <mutex> #include <queue> @@ -44,9 +45,9 @@ namespace SSC { template <typename T = String> using Set = std::set<T>; template <typename T> using SharedPointer = std::shared_ptr<T>; template <typename T> using UniquePointer = std::unique_ptr<T>; + template <typename T> using Promise = std::promise<T>; using ExitCallback = Function<void(int code)>; using MessageCallback = Function<void(const String)>; } - #endif From 341807e62ed509e1dd94e030dea5f64691a273cb Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:36:40 +0200 Subject: [PATCH 0834/1178] refactor(ipc): improve scheme handlers impl on windows --- src/ipc/scheme_handlers.cc | 67 +++++++++++++++++++++++++++++--------- src/ipc/scheme_handlers.hh | 14 ++++++++ 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index cb754b3398..64c46dc0e3 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -143,7 +143,6 @@ static void onURISchemeRequest (WebKitURISchemeRequest* schemeRequest, gpointer if (!handled) { auto response = IPC::SchemeHandlers::Response(request, 404); response.finish(); - return; } } #elif SOCKET_RUNTIME_PLATFORM_ANDROID @@ -452,6 +451,7 @@ namespace SSC::IPC { #elif SOCKET_RUNTIME_PLATFORM_WINDOWS static const int MAX_ALLOWED_SCHEME_ORIGINS = 64; static const int MAX_CUSTOM_SCHEME_REGISTRATIONS = 64; + int registrationsCount = 0; auto registration = Microsoft::WRL::Make<CoreWebView2CustomSchemeRegistration>( convertStringToWString(scheme).c_str() @@ -466,12 +466,12 @@ namespace SSC::IPC { allowedOrigins[i++] = convertStringToWString(entry.first + "://*").c_str(); } - registration->put_HasAuthorityComponent(TRUE); + registration->put_HasAuthorityComponent(true); registration->SetAllowedOrigins(this->handlers.size(), allowedOrigins); this->coreWebView2CustomSchemeRegistrations.insert(registration); - if (this->configuration.webview.As(&options) != S_OK) { + if (this->configuration.webview->As(&options) != S_OK) { return false; } @@ -590,8 +590,14 @@ namespace SSC::IPC { } SchemeHandlers::Request::Builder::Builder ( + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + SchemeHandlers* handlers, + PlatformRequest platformRequest, + ICoreWebView2Environment* env + #else SchemeHandlers* handlers, PlatformRequest platformRequest + #endif ) { #if SOCKET_RUNTIME_PLATFORM_APPLE this->absoluteURL = platformRequest.request.URL.absoluteString.UTF8String; @@ -628,6 +634,10 @@ namespace SSC::IPC { } ); + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + this->request->env = env; + #endif + this->request->client = handlers->bridge->client; // build request URL components from parsed URL components @@ -850,6 +860,10 @@ namespace SSC::IPC { destination->tracer = source.tracer; destination->handlers = source.handlers; destination->platformRequest = source.platformRequest; + + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + destination->env = source.env; + #endif } SchemeHandlers::Request::Request (const Request& request) noexcept @@ -1156,6 +1170,19 @@ namespace SSC::IPC { return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + const auto statusText = String( + STATUS_CODES.contains(this->statusCode) + ? STATUS_CODES.at(this->statusCode) + : "" + ); + this->platformResponseStream = SHCreateMemStream(nullptr, 0); + return S_OK == this->request->env->CreateWebResourceResponse( + this->platformResponseStream, + this->statusCode, + convertStringToWString(statusText).c_str(), + convertStringToWString(this->headers.str()).c_str(), + &this->platformResponse + ); #elif SOCKET_RUNTIME_PLATFORM_ANDROID const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); @@ -1244,7 +1271,6 @@ namespace SSC::IPC { } return true; #elif SOCKET_RUNTIME_PLATFORM_LINUX - auto span = this->tracer.span("write"); g_memory_input_stream_add_data( reinterpret_cast<GMemoryInputStream*>(this->platformResponseStream), reinterpret_cast<const void*>(bytes.get()), @@ -1252,9 +1278,13 @@ namespace SSC::IPC { nullptr ); this->request->bridge->core->retainSharedPointerBuffer(bytes, 256); - span->end(); return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + return S_OK == this->platformResponseStream->Write( + reinterpret_cast<const void*>(bytes.get()), + (ULONG) size, + nullptr + ); #elif SOCKET_RUNTIME_PLATFORM_ANDROID const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); @@ -1392,6 +1422,8 @@ namespace SSC::IPC { g_object_unref(platformResponseStream); } #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + this->platformResponseStream = nullptr; + // TODO(@jwerle): move more `WebResourceRequested` logic to here #elif SOCKET_RUNTIME_PLATFORM_ANDROID if (this->platformResponse != nullptr) { auto app = App::sharedApplication(); @@ -1475,10 +1507,6 @@ namespace SSC::IPC { if (error != nullptr && error->message != nullptr) { return this->fail(error->message); } - - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - #else - //return this->fail(String(error)); #endif return this->fail("Request failed for an unknown reason"); @@ -1510,6 +1538,11 @@ namespace SSC::IPC { // ignore possible 'NSInternalInconsistencyException' return false; } + + // notify fail callback + if (this->request->callbacks.fail != nullptr) { + this->request->callbacks.fail(error); + } #elif SOCKET_RUNTIME_PLATFORM_LINUX const auto quark = g_quark_from_string(bundleIdentifier.c_str()); if (!quark) { @@ -1527,14 +1560,16 @@ namespace SSC::IPC { } else { return false; } - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - #elif SOCKET_RUNTIME_PLATFORM_ANDROID - #endif - // notify fail - //if (this->request->callbacks.fail != nullptr) { - //this->request->callbacks.fail(error); - //} + // notify fail callback + if (this->request->callbacks.fail != nullptr) { + this->request->callbacks.fail(error); + } + #else + // XXX(@jwerle): there doesn't appear to be a way to notify a failure for all platforms + this->finished = true; + return false; + #endif this->finished = true; return true; diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 481188c13b..a5f5d71d4a 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -85,10 +85,18 @@ namespace SSC::IPC { Error* error = nullptr; SharedPointer<Request> request = nullptr; + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + Builder ( + SchemeHandlers* handlers, + PlatformRequest platformRequest, + ICoreWebView2Environment* env + ); + #else Builder ( SchemeHandlers* handlers, PlatformRequest platformRequest ); + #endif Builder& setScheme (const String& scheme); Builder& setMethod (const String& method); @@ -140,6 +148,10 @@ namespace SSC::IPC { SchemeHandlers* handlers = nullptr; PlatformRequest platformRequest; + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + ICoreWebView2Environment* env = nullptr; + #endif + Request () = delete; Request ( SchemeHandlers* handlers, @@ -189,6 +201,8 @@ namespace SSC::IPC { #if SOCKET_RUNTIME_PLATFORM_LINUX GInputStream* platformResponseStream = nullptr; + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + IStream* platformResponseStream = nullptr; #endif Response ( From 5b60d49e88bdb9756f0a390121ba1042f9a6cfb8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:37:13 +0200 Subject: [PATCH 0835/1178] refactor(window): clean up Windows implementation --- src/window/win.cc | 1585 +++++++++++++++--------------------------- src/window/window.hh | 56 +- 2 files changed, 553 insertions(+), 1088 deletions(-) diff --git a/src/window/win.cc b/src/window/win.cc index 43cb0b9fc3..0fef2252ee 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1,3 +1,4 @@ +#include "../app/app.hh" #include "window.hh" #ifndef CHECK_FAILURE @@ -7,11 +8,11 @@ using namespace Microsoft::WRL; namespace SSC { - static inline void alert (const SSC::WString &ws) { - MessageBoxA(nullptr, SSC::convertWStringToString(ws).c_str(), _TEXT("Alert"), MB_OK | MB_ICONSTOP); + static inline void alert (const WString &ws) { + MessageBoxA(nullptr, convertWStringToString(ws).c_str(), _TEXT("Alert"), MB_OK | MB_ICONSTOP); } - static inline void alert (const SSC::String &s) { + static inline void alert (const String &s) { MessageBoxA(nullptr, s.c_str(), _TEXT("Alert"), MB_OK | MB_ICONSTOP); } @@ -142,7 +143,7 @@ namespace SSC { } HRESULT __stdcall CDataObject::GetCanonicalFormatEtc (FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut) { - pFormatEtcOut->ptd = NULL; + pFormatEtcOut->ptd = nullptr; return E_NOTIMPL; } @@ -255,7 +256,7 @@ namespace SSC { } class DragDrop : public IDropTarget { - SSC::Vector<SSC::String> draggablePayload; + Vector<String> draggablePayload; unsigned int refCount; public: @@ -277,7 +278,7 @@ namespace SSC { AddRef(); return S_OK; } - *ppv = NULL; + *ppv = nullptr; return E_NOINTERFACE; }; @@ -310,7 +311,7 @@ namespace SSC { *dragEffect = DROPEFFECT_MOVE; format.cfFormat = CF_TEXT; - format.ptd = NULL; + format.ptd = nullptr; format.dwAspect = DVASPECT_CONTENT; format.lindex = -1; format.tymed = TYMED_HGLOBAL; @@ -323,10 +324,10 @@ namespace SSC { this->draggablePayload.clear(); if (list != 0) { - draggablePayload = SSC::split(SSC::String(list), ';'); + draggablePayload = split(String(list), ';'); - SSC::String json = ( + String json = ( "{" " \"count\":" + std::to_string(this->draggablePayload.size()) + "," " \"inbound\": true," @@ -335,7 +336,7 @@ namespace SSC { "}" ); - auto payload = SSC::getEmitToRenderProcessJavaScript("drag", json); + auto payload = getEmitToRenderProcessJavaScript("drag", json); this->window->eval(payload); } @@ -381,7 +382,7 @@ namespace SSC { point.x = dragPoint.x - position.x; point.y = dragPoint.y - position.y; - SSC::String json = ( + String json = ( "{" " \"count\":" + std::to_string(this->draggablePayload.size()) + "," " \"inbound\": false," @@ -390,7 +391,7 @@ namespace SSC { "}" ); - auto payload = SSC::getEmitToRenderProcessJavaScript("drag", json); + auto payload = getEmitToRenderProcessJavaScript("drag", json); this->window->eval(payload); return S_OK; @@ -411,7 +412,7 @@ namespace SSC { STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 }; UINT len = 0; - SSC::Vector<SSC::String> files = this->draggablePayload; + Vector<String> files = this->draggablePayload; for (auto &file : files) { file = file.substr(12); @@ -421,14 +422,14 @@ namespace SSC { globalMemory = GlobalAlloc(GHND, sizeof(DROPFILES) + len + 1); if (!globalMemory) { - return NULL; + return nullptr; } dropFiles = (DROPFILES*) GlobalLock(globalMemory); if (!dropFiles) { GlobalFree(globalMemory); - return NULL; + return nullptr; } dropFiles->fNC = TRUE; @@ -437,8 +438,8 @@ namespace SSC { GetCursorPos(&(dropFiles->pt)); char *dropFilePtr = (char *) &dropFiles[1]; - for (SSC::Vector<SSC::String>::size_type i = 0; i < files.size(); ++i) { - SSC::String &file = files[i]; + for (Vector<String>::size_type i = 0; i < files.size(); ++i) { + String &file = files[i]; len = (file.length() + 1); @@ -507,7 +508,7 @@ namespace SSC { HDROP drop; int count; - SSC::StringStream filesStringArray; + StringStream filesStringArray; GetClientRect(child, &rect); position = { rect.left, rect.top }; @@ -520,7 +521,7 @@ namespace SSC { format.cfFormat = CF_HDROP; format.lindex = -1; format.tymed = TYMED_HGLOBAL; - format.ptd = NULL; + format.ptd = nullptr; if ( SUCCEEDED(dataObject->QueryGetData(&format)) && @@ -529,10 +530,10 @@ namespace SSC { *dragEffect = DROPEFFECT_COPY; drop = (HDROP) GlobalLock(medium.hGlobal); - count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); + count = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); for (int i = 0; i < count; i++) { - int size = DragQueryFile(drop, i, NULL, 0); + int size = DragQueryFile(drop, i, nullptr, 0); TCHAR* buf = new TCHAR[size + 1]; DragQueryFile(drop, i, buf, size + 1); @@ -540,7 +541,7 @@ namespace SSC { // append escaped file path with wrapped quotes ('"') filesStringArray << '"' - << SSC::replace(SSC::String(buf), "\\\\", "\\\\") + << replace(String(buf), "\\\\", "\\\\") << '"'; if (i < count - 1) { @@ -571,7 +572,7 @@ namespace SSC { ) { *dragEffect = DROPEFFECT_MOVE; for (auto &src : this->draggablePayload) { - SSC::String json = ( + String json = ( "{" " \"src\": \"" + src + "\"," " \"x\":" + std::to_string(point.x) + "," @@ -579,17 +580,17 @@ namespace SSC { "}" ); - this->window->eval(SSC::getEmitToRenderProcessJavaScript("drop", json)); + this->window->eval(getEmitToRenderProcessJavaScript("drop", json)); } - SSC::String json = ( + String json = ( "{" " \"x\":" + std::to_string(point.x) + "," " \"y\":" + std::to_string(point.y) + "" "}" ); - this->window->eval(SSC::getEmitToRenderProcessJavaScript("dragend", json)); + this->window->eval(getEmitToRenderProcessJavaScript("dragend", json)); } } @@ -602,53 +603,88 @@ namespace SSC { }; }; - Window::Window (App& app, Window::Options opts) - : app(app), - opts(opts), - hotkey(this) + Window::Window (SharedPointer<Core> core, const Window::Options& options) + : core(core), + options(options), + bridge(core, IPC::Bridge::Options { + options.userConfig, + options.as<IPC::Preload::Options>() + }), + hotkey(this), + dialog(this) { - static auto userConfig = SSC::getUserConfig(); - const bool isAgent = userConfig["application_agent"] == "true" && opts.index == 0; + // this may be an "empty" path if not available + static const auto edgeRuntimePath = FileResource::getMicrosoftEdgeRuntimePath(); + static auto app = App::sharedApplication(); + + if (!edgeRuntimePath.empty()) { + const auto string = convertWStringToString(edgeRuntimePath.string()); + const auto value = replace(string, "\\\\", "\\\\") + // inject the `EDGE_RUNTIME_DIRECTORY` environment variable directly into + // the userConfig so it is available as an env var in the webview runtime + this->bridge.userConfig["env_EDGE_RUNTIME_DIRECTORY"] = value; + this->options.userConfig["env_EDGE_RUNTIME_DIRECTORY"] = value; + debug("Microsoft Edge Runtime directory set to '%ls'", edgeRuntimePath.c_str()); + } + + auto userConfig = this->bridge.userConfig; + auto webviewEnvironmentOptions = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>(); + webviewEnvironmentOptions->put_AdditionalBrowserArguments(L"--enable-features=msWebView2EnableDraggableRegions"); + + Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions4> webviewEnvironmentOptions4; + if (webviewEnvironmentOptions.As(&webviewEnvironmentOptions4) != S_OK) { + throw std::runtime_error( + "Unable to resolve 'ICoreWebView2EnvironmentOptions4'" + ); + } - app.isReady = false; + // only the root window can handle "agent" tasks + const bool isAgent = ( + userConfig["application_agent"] == "true" && + options.index == 0 + ); - this->index = opts.index; - if (isAgent && opts.index == 0) { - window = CreateWindowEx( + if (isAgent) { + this->window = CreateWindowEx( WS_EX_TOOLWINDOW, userConfig["meta_bundle_identifier"].c_str(), userConfig["meta_title"].c_str(), WS_OVERLAPPEDWINDOW, 100000, 100000, - opts.width, - opts.height, - NULL, - NULL, - app.hInstance, - NULL + options.width, + options.height, + nullptr, + nullptr, + app->hInstance, + nullptr ); } else { DWORD style = WS_THICKFRAME; - if (!opts.frameless) { + if (options.frameless) { + style |= WS_POPUP; + } else { style |= WS_OVERLAPPED; + // Windows does not have the ability to reposition the decorations + // In this case, we can assume that the user will draw their own controls. + if (options.titlebarStyle != "hidden" && options.titlebarStyle != "hiddenInset") { + if (options.closable) { + style |= WS_CAPTION | WS_SYSMENU; + + if (options.minimizable) { + style |= WS_MINIMIZEBOX; + } - if (opts.titlebarStyle == "hidden" || opts.titlebarStyle == "hiddenInset") { - // Windows does not have the ability to reposition the decorations - // In this case, we can assume that the user will draw their own controls. - } else if (opts.closable) { - style |= WS_CAPTION | WS_SYSMENU; - - if (opts.minimizable) style |= WS_MINIMIZEBOX; - if (opts.maximizable) style |= WS_MAXIMIZEBOX; + if (options.maximizable) { + style |= WS_MAXIMIZEBOX; + } + } } - } else { - style |= WS_POPUP; } - window = CreateWindowEx( - opts.headless + this->window = CreateWindowEx( + options.headless ? WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE : WS_EX_APPWINDOW | WS_EX_ACCEPTFILES, userConfig["meta_bundle_identifier"].c_str(), @@ -656,26 +692,32 @@ namespace SSC { style, 100000, 100000, - opts.width, - opts.height, - NULL, - NULL, - app.hInstance, - NULL + options.width, + options.height, + nullptr, + nullptr, + app->hInstance, + nullptr ); } - HRESULT initResult = OleInitialize(NULL); - - this->drop = new DragDrop(this); + this->drop = std::make_shared<DragDrop>(this); + this->bridge.navigateFunction = [this] (const auto url) { + this->navigate(url); + }; - this->bridge = new IPC::Bridge(app.core, opts.userConfig); - opts.clientId = this->bridge->id; + this->bridge.evaluateJavaScriptFunction = [this] (const auto source) { + this->eval(source); + }; - this->hotkey.init(this->bridge); + this->bridge.client.preload = IPC::Preload::compile({ + .client = this->bridge.client, + .index = options.index, + .userScript = options.userScript + }); - if (this->opts.aspectRatio.size() > 0) { - auto parts = split(this->opts.aspectRatio, ':'); + if (options.aspectRatio.size() > 0) { + auto parts = split(options.aspectRatio, ':'); double aspectRatio = 0; try { @@ -686,782 +728,382 @@ namespace SSC { if (aspectRatio > 0) { RECT rect; - GetClientRect(window, &rect); - // SetWindowAspectRatio(window, MAKELONG((long)(rect.bottom * aspectRatio), rect.bottom), NULL); + GetClientRect(this->window, &rect); + // SetWindowAspectRatio(window, MAKELONG((long)(rect.bottom * aspectRatio), rect.bottom), nullptr); } } - this->bridge->router.dispatchFunction = [&app] (auto callback) { - app.dispatch([callback] { callback(); }); - }; + // in theory these allow you to do drop files in elevated mode + ChangeWindowMessageFilterEx(this->window, WM_DROPFILES, MSGFLT_ALLOW, nullptr); + ChangeWindowMessageFilterEx(this->window, WM_COPYDATA, MSGFLT_ALLOW, nullptr); + ChangeWindowMessageFilterEx(this->window, 0x0049, MSGFLT_ALLOW, nullptr); - this->bridge->router.evaluateJavaScriptFunction = [this] (auto js) { - this->eval(js); - }; + UpdateWindow(this->window); + ShowWindow(this->window, isAgent ? SW_HIDE : SW_SHOWNORMAL); - // - // In theory these allow you to do drop files in elevated mode - // - ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD); - ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD); - ChangeWindowMessageFilter(0x0049, MSGFLT_ADD); + // make this `Window` instance as `GWLP_USERDATA` + SetWindowLongPtr(this->window, GWLP_USERDATA, (LONG_PTR) this); - UpdateWindow(window); - ShowWindow(window, isAgent ? SW_HIDE : SW_SHOWNORMAL); - SetWindowLongPtr(window, GWLP_USERDATA, (LONG_PTR) this); + this->hotkey.init(); + this->bridge.init(); + this->bridge.configureWebView(this->webview); - // this is something like "C:\\Users\\josep\\AppData\\Local\\Microsoft\\Edge SxS\\Application\\123.0.2386.0" - auto EDGE_RUNTIME_DIRECTORY = convertStringToWString(trim(Env::get("SOCKET_EDGE_RUNTIME_DIRECTORY"))); + static const auto APPDATA = Path(convertStringToWString(Env::get("APPDATA"))); - if (EDGE_RUNTIME_DIRECTORY.size() > 0 && fs::exists(EDGE_RUNTIME_DIRECTORY)) { - usingCustomEdgeRuntimeDirectory = true; - opts.userConfig["env_EDGE_RUNTIME_DIRECTORY"] = replace(convertWStringToString(EDGE_RUNTIME_DIRECTORY), "\\\\", "\\\\"); - debug("Using Edge Runtime Directory: %ls", EDGE_RUNTIME_DIRECTORY.c_str()); - } else { - EDGE_RUNTIME_DIRECTORY = L""; + if (APPDATA.empty() || !fs::exists(APPDATA)) { + throw std::runtime_error( + "Environment is in an invalid state: Could not determine 'APPDATA' path" + ); } - wchar_t modulefile[MAX_PATH]; - GetModuleFileNameW(NULL, modulefile, MAX_PATH); - auto file = (fs::path { modulefile }).filename(); - auto filename = SSC::convertStringToWString(file.string()); - auto path = SSC::convertStringToWString(Env::get("APPDATA")); - this->modulePath = fs::path(modulefile); + static const auto edgeRuntimeUserDataPath = ({ + wchar_t modulefile[MAX_PATH]; + GetModuleFileNameW(nullptr, modulefile, MAX_PATH); + auto file = (fs::path { modulefile }).filename(); + auto filename = convertStringToWString(file.string()); + APPDATA / filename; + }); - auto options = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>(); - options->put_AdditionalBrowserArguments(L"--enable-features=msWebView2EnableDraggableRegions"); + this->bridge.configureSchemeHandlers({ + .webview = webviewEnvironmentOptions + }); - Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions4> options4; - HRESULT oeResult = options.As(&options4); - if (oeResult != S_OK) { - // UNREACHABLE - cannot continue - } + CreateCoreWebView2EnvironmentWithOptions( + edgeRuntimePath.empty() ? nullptr : edgeRuntimePath.string(), + edgeRuntimeUserDataPath, + webviewEnvironmentOptions.Get(), + Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>([=, this]( + HRESULT result, + ICoreWebView2Environment* webviewEnvironment + ) -> HRESULT { + return env->CreateCoreWebView2Controller( + this->window, + Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>([=, this]( + HRESULT result, + ICoreWebView2Controller* controller + ) -> HRESULT { + const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + + if (result != S_OK) { + return result; + } - this->bridge->router.configureHandlers({ - options - }); + if (controller == nullptr) { + return E_HANDLE; + } - auto init = [&, opts]() -> HRESULT { - return CreateCoreWebView2EnvironmentWithOptions( - EDGE_RUNTIME_DIRECTORY.size() > 0 ? EDGE_RUNTIME_DIRECTORY.c_str() : nullptr, - (path + L"\\" + filename).c_str(), - options.Get(), - Microsoft::WRL::Callback<IEnvHandler>( - [&, opts](HRESULT result, ICoreWebView2Environment* env) -> HRESULT { - env->CreateCoreWebView2Controller( - window, - Microsoft::WRL::Callback<IConHandler>( - [&, opts](HRESULT result, ICoreWebView2Controller* c) -> HRESULT { - static auto userConfig = SSC::getUserConfig(); - if (c != nullptr) { - controller = c; - controller->get_CoreWebView2(&webview); - - RECT bounds; - GetClientRect(window, &bounds); - controller->put_Bounds(bounds); - controller->AddRef(); - controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); + // configure the webview controller + do { + RECT bounds; + GetClientRect(this->window, &bounds); + this->controller = controller; + this->controller->get_CoreWebView2(&this->webview); + this->controller->put_Bounds(bounds); + this->controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); + this->controller->AddRef(); + } while (0); + + // configure the webview settings + do { + ICoreWebView2Settings* settings = nullptr; + ICoreWebView2Settings3* settings3 = nullptr; + ICoreWebView2Settings6* settings6 = nullptr; + + this->webview->get_Settings(&settings); + + settings3 = reinterpret_cast<ICoreWebView2Settings3*>(settings); + settings6 = reinterpret_cast<ICoreWebView2Settings6*>(settings); + + settings->put_IsScriptEnabled(true); + settings->put_IsStatusBarEnabled(false); + settings->put_IsWebMessageEnabled(true); + settings->put_AreHostObjectsAllowed(true); + settings->put_IsZoomControlEnabled(false); + settings->put_IsBuiltInErrorPageEnabled(false); + settings->put_AreDefaultContextMenusEnabled(true); + settings->put_AreDefaultScriptDialogsEnabled(true); + + settings6->put_IsPinchZoomEnabled(false); + settings6->put_IsSwipeNavigationEnabled(false); + + if (this->options.debug || isDebugEnabled()) { + settings->put_AreDevToolsEnabled(true); + settings3->put_AreBrowserAcceleratorKeysEnabled(true); + } else { + settings->put_AreDevToolsEnabled(false); + settings3->put_AreBrowserAcceleratorKeysEnabled(false); + } + } while (0); + + // enumerate all child windows to re-register drag/drop + EnumChildWindows( + this->window, + [](HWND handle, LPARAM param) -> BOOL { + const auto length = GetWindowTextLengthW(handle); + const auto pointer = GetWindowLongPtr(reinterpret_cast<HWND>(param), GWLP_USERDATA); + auto window = reinterpret_cast<Window*>(pointer); + + if (length > 0) { + auto buffer = std::make_shared<wchar_t[]>(length + 1); + auto text = convertWStringToString(buffer.get()); + GetWindowTextW(handle, buffer.get(), length + 1); + + if (text.find("Chrome") != String::npos) { + RevokeDragDrop(handle); + RegisterDragDrop(handle, window->drop); + window->drop->childWindow = handle; } + } - ICoreWebView2Settings* Settings; - webview->get_Settings(&Settings); - Settings->put_IsScriptEnabled(TRUE); - Settings->put_AreDefaultScriptDialogsEnabled(TRUE); - Settings->put_IsWebMessageEnabled(TRUE); - Settings->put_AreHostObjectsAllowed(TRUE); - Settings->put_IsStatusBarEnabled(FALSE); - - Settings->put_AreDefaultContextMenusEnabled(TRUE); - if (opts.debug || isDebugEnabled()) { - Settings->put_AreDevToolsEnabled(TRUE); - } else { - Settings->put_AreDevToolsEnabled(FALSE); - } + return true; + }, + reinterpret_cast<LPARAM>(this->window) + ); - Settings->put_IsBuiltInErrorPageEnabled(FALSE); - Settings->put_IsZoomControlEnabled(FALSE); + // configure webview + do { + ICoreWebView2_22* webview22 = nullptr; + ICoreWebView2_3* webview3 = reinterpret_cast<ICoreWebView2_3*>(this->webview); - auto settings3 = (ICoreWebView2Settings3*) Settings; - if (!isDebugEnabled()) { - settings3->put_AreBrowserAcceleratorKeysEnabled(FALSE); - } + this->webview->QueryInterface(IID_PPV_ARGS(&webview22)); + this->webview->AddWebResourceRequestedFilter(L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL); - auto settings6 = (ICoreWebView2Settings6*) Settings; - settings6->put_IsPinchZoomEnabled(FALSE); - settings6->put_IsSwipeNavigationEnabled(FALSE); + if (webview22 != nullptr) { + this->bridge.userConfig["env_COREWEBVIEW2_22_AVAILABLE"] = "true"; + this->options.userConfig["env_COREWEBVIEW2_22_AVAILABLE"] = "true"; - EnumChildWindows(window, [](HWND hWnd, LPARAM window) -> BOOL { - int l = GetWindowTextLengthW(hWnd); + webview22->AddWebResourceRequestedFilterWithRequestSourceKinds( + L"*", + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL, + COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL + ); - if (l > 0) { - wchar_t* buf = new wchar_t[l+1]; - GetWindowTextW(hWnd, buf, l+1); + debug("Configured CoreWebView2 (ICoreWebView2_22) request filter with all request source kinds"); + } - if (SSC::convertWStringToString(buf).find("Chrome") != SSC::String::npos) { - RevokeDragDrop(hWnd); - Window* w = reinterpret_cast<Window*>(GetWindowLongPtr((HWND)window, GWLP_USERDATA)); - w->drop->childWindow = hWnd; - RegisterDragDrop(hWnd, w->drop); - } + webview3->SetVirtualHostNameToFolderMapping( + convertStringToWString(bundleIdentifier).c_str(), + FileResource::getResourcesPath().c_str(), + COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW + ); + } while (0); + + // configure webview permission request handler + do { + EventRegistrationToken token; + this->webview->add_PermissionRequested( + Microsoft::WRL::Callback<ICoreWebView2PermissionRequestedEventHandler>([=, this]( + ICoreWebView2 *webview, + ICoreWebView2PermissionRequestedEventArgs *args + ) -> HRESULT { + COREWEBVIEW2_PERMISSION_KIND kind; + args->get_PermissionKind(&kind); + + if (kind == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE) { + if ( + userConfig["permissions_allow_microphone"] == "false" || + userConfig["permissions_allow_user_media"] == "false" + ) { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); } - return TRUE; - }, (LPARAM)window); - - reinterpret_cast<ICoreWebView2_3*>(webview)->SetVirtualHostNameToFolderMapping( - convertStringToWString(userConfig["meta_bundle_identifier"]).c_str(), - this->modulePath.parent_path().c_str(), - COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW - ); + } - EventRegistrationToken tokenSchemaFilter; - webview->AddWebResourceRequestedFilter(L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL); + if (kind == COREWEBVIEW2_PERMISSION_KIND_CAMERA) { + if ( + userConfig["permissions_allow_camera"] == "false" || + userConfig["permissions_allow_user_media"] == "false" + ) { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } - ICoreWebView2_22* webview22 = nullptr; - webview->QueryInterface(IID_PPV_ARGS(&webview22)); + if (kind == COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION) { + if (userConfig["permissions_allow_geolocation"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } - if (webview22 != nullptr) { - webview22->AddWebResourceRequestedFilterWithRequestSourceKinds( - L"*", - COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL, - COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL - ); + if (kind == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS) { + if (userConfig["permissions_allow_notifications"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } - debug("Configured CoreWebView2 (ICoreWebView2_22) request filter with all request source kinds"); + if (kind == COREWEBVIEW2_PERMISSION_KIND_OTHER_SENSORS) { + if (userConfig["permissions_allow_sensors"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } } - webview->add_WebResourceRequested( - Microsoft::WRL::Callback<ICoreWebView2WebResourceRequestedEventHandler>( - [&, opts](ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* event) { - ICoreWebView2WebResourceRequest* platformRequest = nullptr; - ICoreWebView2HttpRequestHeaders* headers = nullptr; - ICoreWebView2Environment* env = nullptr; - ICoreWebView2_2* webview2 = nullptr; - - - LPWSTR method; - LPWSTR uri; - - webview->QueryInterface(IID_PPV_ARGS(&webview2)); - webview2->get_Environment(&env); - event->get_Request(&platformRequest); - - platformRequest->get_Headers(&headers); - platformRequest->get_Method(&method); - platformRequest->get_Uri(&uri); - - auto request = IPC::SchemeHandlers::Request::Builder(this->bridge->router->schemeHandlers, platformRequest); - - request.setMethod(convertWStringToString(method)); - - do { - ComPtr<ICoreWebView2HttpHeadersCollectionIterator> iterator; - BOOL hasCurrent = FALSE; - CHECK_FAILURE(headers->GetIterator(&iterator)); - while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { - LPWSTR name; - LPWSTR value; - - if (iterator->GetCurrentHeader(&name, &value) == S_OK) { - request.setHeader(convertWStringToString(name), convertWStringToString(value)); - } - } - } while (0); - - - String method; - String uri; - - webview->QueryInterface(IID_PPV_ARGS(&webview2)); - webview2->get_Environment(&env); - args->get_Request(&req); - - req->get_Uri(&req_uri); - uri = convertWStringToString(req_uri); - CoTaskMemFree(req_uri); - - req->get_Method(&req_method); - method = convertWStringToString(req_method); - CoTaskMemFree(req_method); - - bool ipc_scheme = false; - bool socket_scheme = false; - bool handled = false; - - if (uri.compare(0, 4, "ipc:") == 0) { - ipc_scheme = true; - } else if (uri.compare(0, 7, "socket:") == 0) { - socket_scheme = true; - } else { - return S_OK; - } - - // Handle CORS preflight request. - if (method.compare("OPTIONS") == 0) { - ICoreWebView2WebResourceResponse* res = nullptr; - env->CreateWebResourceResponse( - nullptr, - 204, - L"OK", - L"Connection: keep-alive\n" - L"Cache-Control: no-cache\n" - L"Access-Control-Allow-Headers: *\n" - L"Access-Control-Allow-Origin: *\n" - L"Access-Control-Allow-Methods: GET, POST, PUT, HEAD\n", - &res - ); - args->put_Response(res); - - return S_OK; - } - - - ICoreWebView2Deferral* deferral; - HRESULT hr = args->GetDeferral(&deferral); - - char* body_ptr = nullptr; - size_t body_length = 0; - - if (ipc_scheme) { - if (method.compare("POST") == 0 || method.compare("PUT") == 0) { - IStream* body_data; - DWORD actual; - HRESULT r; - auto msg = IPC::Message(uri); - msg.isHTTP = true; - // TODO(heapwolf): Make sure index and seq are set. - if (w->bridge->router.hasMappedBuffer(msg.index, msg.seq)) { - IPC::MessageBuffer buf = w->bridge->router.getMappedBuffer(msg.index, msg.seq); - ICoreWebView2SharedBuffer* shared_buf = buf.shared_buf; - size_t size = buf.size; - char* data = new char[size]; - w->bridge->router.removeMappedBuffer(msg.index, msg.seq); - shared_buf->OpenStream(&body_data); - r = body_data->Read(data, size, &actual); - if (r == S_OK || r == S_FALSE) { - body_ptr = data; - body_length = actual; - } else { - delete[] data; - } - shared_buf->Close(); - } - } - - handled = w->bridge->route(uri, body_ptr, body_length, [&, args, deferral, env, body_ptr](auto result) { - String headers; - char* body; - size_t length; - - if (body_ptr != nullptr) { - delete[] body_ptr; - } - - if (result.post.body != nullptr) { - length = result.post.length; - body = new char[length]; - memcpy(body, result.post.body, length); - headers = "Content-Type: application/octet-stream\n"; - } else { - length = result.str().size(); - body = new char[length]; - memcpy(body, result.str().c_str(), length); - headers = "Content-Type: application/json\n"; - } - - headers += "Connection: keep-alive\n"; - headers += "Cache-Control: no-cache\n"; - headers += "Access-Control-Allow-Headers: *\n"; - headers += "Access-Control-Allow-Origin: *\n"; - headers += "Content-Length: "; - headers += std::to_string(length); - headers += "\n"; - - // Completing the response in the call to dispatch because the - // put_Response() must be called from the same thread that made - // the request. This assumes that the request was made from the - // main thread, since that's where dispatch() will call its cb. - app.dispatch([&, body, length, headers, args, deferral, env] { - ICoreWebView2WebResourceResponse* res = nullptr; - IStream* bytes = SHCreateMemStream((const BYTE*)body, length); - env->CreateWebResourceResponse( - bytes, - 200, - L"OK", - convertStringToWString(headers).c_str(), - &res - ); - args->put_Response(res); - deferral->Complete(); - delete[] body; - }); - }); - } - - if (socket_scheme) { - if (method.compare("GET") == 0 || method.compare("HEAD") == 0) { - if (uri.starts_with("socket:///")) { - uri = uri.substr(10); - } else if (uri.starts_with("socket://")) { - uri = uri.substr(9); - } else if (uri.starts_with("socket:")) { - uri = uri.substr(7); - } - - auto path = String( - uri.starts_with(bundleIdentifier) - ? uri.substr(bundleIdentifier.size()) - : "socket/" + uri - ); - - const auto parts = split(path, '?'); - const auto query = parts.size() > 1 ? String("?") + parts[1] : ""; - path = parts[0]; - - auto ext = fs::path(path).extension().string(); - - if (ext.size() > 0 && !ext.starts_with(".")) { - ext = "." + ext; - } - - if (!uri.starts_with(bundleIdentifier)) { - if (path.ends_with("/")) { - path = path.substr(0, path.size() - 1); - } - - if (ext.size() == 0 && !path.ends_with(".js")) { - path += ".js"; - } - - if (path == "/") { - uri = "socket://" + bundleIdentifier + "/"; - } else { - uri = "socket://" + bundleIdentifier + "/" + path; - } - - String headers; - - auto moduleUri = replace(uri, "\\\\", "/"); - auto moduleSource = trim(tmpl( - moduleTemplate, - Map { {"url", String(moduleUri)} } - )); - - auto length = moduleSource.size(); - - headers = "Content-Type: text/javascript\n"; - headers += "Cache-Control: no-cache\n"; - headers += "Connection: keep-alive\n"; - headers += "Access-Control-Allow-Headers: *\n"; - headers += "Access-Control-Allow-Origin: *\n"; - headers += "Content-Length: "; - headers += std::to_string(length); - headers += "\n"; - headers += userConfig["webview_headers"]; - - handled = true; - - if (method.compare("HEAD") == 0) { - ICoreWebView2WebResourceResponse* res = nullptr; - env->CreateWebResourceResponse( - nullptr, - 200, - L"OK", - convertStringToWString(headers).c_str(), - &res - ); - args->put_Response(res); - deferral->Complete(); - } else { - auto body = new char[length]; - memcpy(body, moduleSource.c_str(), length); - - app.dispatch([&, body, length, headers, args, deferral, env] { - ICoreWebView2WebResourceResponse* res = nullptr; - IStream* bytes = SHCreateMemStream((const BYTE*)body, length); - env->CreateWebResourceResponse( - bytes, - 200, - L"OK", - convertStringToWString(headers).c_str(), - &res - ); - args->put_Response(res); - deferral->Complete(); - delete[] body; - }); - } - } else { - if (path.ends_with("//")) { - path = path.substr(0, path.size() - 2); - } - - auto parsedPath = IPC::Router::parseURLComponents(path); - auto rootPath = this->modulePath.parent_path(); - auto resolved = IPC::Router::resolveURLPathForWebView(parsedPath.path, rootPath.string()); - auto mount = IPC::Router::resolveNavigatorMountForWebView(parsedPath.path); - path = resolved.path; - - if (mount.path.size() > 0) { - if (mount.resolution.redirect) { - auto redirectURL = mount.resolution.path; - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - ICoreWebView2WebResourceResponse* res = nullptr; - auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - env->CreateWebResourceResponse( - nullptr, - 301, - L"Moved Permanently", - WString( - convertStringToWString("Location: ") + convertStringToWString(redirectURL) + L"\n" + - convertStringToWString("Content-Location: ") + convertStringToWString(contentLocation) + L"\n" - ).c_str(), - &res - ); - - args->put_Response(res); - deferral->Complete(); - return S_OK; - } - } else if (path.size() == 0 && userConfig.contains("webview_default_index")) { - path = userConfig["webview_default_index"]; - } else if (resolved.redirect) { - auto redirectURL = resolved.path; - if (parsedPath.queryString.size() > 0) { - redirectURL += "?" + parsedPath.queryString; - } - - if (parsedPath.fragment.size() > 0) { - redirectURL += "#" + parsedPath.fragment; - } - - auto contentLocation = replace(redirectURL, "socket://" + bundleIdentifier, ""); - ICoreWebView2WebResourceResponse* res = nullptr; - env->CreateWebResourceResponse( - nullptr, - 301, - L"Moved Permanently", - WString( - convertStringToWString("Location: ") + convertStringToWString(redirectURL) + L"\n" + - convertStringToWString("Content-Location: ") + convertStringToWString(contentLocation) + L"\n" - ).c_str(), - &res - ); - - args->put_Response(res); - deferral->Complete(); - return S_OK; - } - - if (mount.path.size() > 0) { - path = mount.path; - } else if (path.size() > 0) { - path = fs::absolute(rootPath / path.substr(1)).string(); - } - - LARGE_INTEGER fileSize; - auto handle = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); - auto getSizeResult = GetFileSizeEx(handle, &fileSize); - - if (handle) { - CloseHandle(handle); - } - - if (getSizeResult) { - handled = true; - app.dispatch([&, path, args, deferral, env] { - ICoreWebView2WebResourceResponse* res = nullptr; - LPWSTR mimeType = (wchar_t*) L"application/octet-stream"; - IStream* stream = nullptr; - String headers = ""; - - if (path.ends_with(".js") || path.ends_with(".mjs") || path.ends_with(".cjs")) { - mimeType = (wchar_t*) L"text/javascript"; - } else if (path.ends_with(".wasm")) { - mimeType = (wchar_t*) L"application/wasm"; - } else if (path.ends_with(".ts")) { - mimeType = (wchar_t*) L"application/typescript"; - } else if (path.ends_with(".html")) { - mimeType = (wchar_t*) L"text/html"; - } else if (path.ends_with(".css")) { - mimeType = (wchar_t*) L"text/css"; - } else if (path.ends_with(".png")) { - mimeType = (wchar_t*) L"image/png"; - } else if (path.ends_with(".jpg") || path.ends_with(".jpeg")) { - mimeType = (wchar_t*) L"image/jpeg"; - } else if (path.ends_with(".json")) { - mimeType = (wchar_t*) L"application/json"; - } else if (path.ends_with(".jsonld")) { - mimeType = (wchar_t*) L"application/ld+json"; - } else if (path.ends_with(".opus")) { - mimeType = (wchar_t*) L"audio/opus"; - } else if (path.ends_with(".oga")) { - mimeType = (wchar_t*) L"audio/ogg"; - } else if (path.ends_with(".mp3")) { - mimeType = (wchar_t*) L"audio/mp3"; - } else if (path.ends_with(".mp4")) { - mimeType = (wchar_t*) L"video/mp4"; - } else if (path.ends_with(".mpeg")) { - mimeType = (wchar_t*) L"video/mpeg"; - } else if (path.ends_with(".ogv")) { - mimeType = (wchar_t*) L"video/ogg"; - } else { - FindMimeFromData(0, convertStringToWString(path).c_str(), 0, 0, 0, 0, &mimeType, 0); - } - - headers = "Content-Type: "; - headers += convertWStringToString(mimeType) + "\n"; - headers += "Connection: keep-alive\n"; - headers += "Cache-Control: no-cache\n"; - headers += "Access-Control-Allow-Headers: *\n"; - headers += "Access-Control-Allow-Origin: *\n"; - headers += "Content-Length: "; - headers += std::to_string(fileSize.QuadPart); - headers += "\n"; - headers += userConfig["webview_headers"]; - - if (SHCreateStreamOnFileA(path.c_str(), STGM_READ, &stream) == S_OK) { - env->CreateWebResourceResponse( - stream, - 200, - L"OK", - convertStringToWString(headers).c_str(), - &res - ); - } else { - env->CreateWebResourceResponse( - nullptr, - 404, - L"Not Found", - L"Access-Control-Allow-Origin: *", - &res - ); - } - args->put_Response(res); - deferral->Complete(); - }); - } - } - } - } - - if (!handled) { - ICoreWebView2WebResourceResponse* res = nullptr; - env->CreateWebResourceResponse( - nullptr, - 404, - L"Not Found", - L"Access-Control-Allow-Origin: *", - &res - ); - args->put_Response(res); - deferral->Complete(); - } - - return S_OK; - } - ).Get(), - &tokenSchemaFilter - ); - - EventRegistrationToken tokenNewWindow; - - webview->add_NewWindowRequested( - Microsoft::WRL::Callback<ICoreWebView2NewWindowRequestedEventHandler>( - [&](ICoreWebView2* wv, ICoreWebView2NewWindowRequestedEventArgs* e) { - // TODO(heapwolf): Called when window.open() is called in JS, but the new - // window won't have all the setup and request interception. This setup should - // be moved to another location where it can be run for any new window. Right - // now ipc won't work for any new window. - e->put_Handled(true); - return S_OK; - } - ).Get(), - &tokenNewWindow - ); - - webview->QueryInterface(IID_PPV_ARGS(&webview22)); - this->bridge->userConfig["env_COREWEBVIEW2_22_AVAILABLE"] = webview22 != nullptr ? "true" : ""; - this->bridge->preload = createPreload(opts, { - .module = true, - .wrap = true, - .userScript = opts.userScript - }); + if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { + if (userConfig["permissions_allow_clipboard"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } - EventRegistrationToken tokenMessage; - - webview->add_WebMessageReceived( - Microsoft::WRL::Callback<IRecHandler>([&](ICoreWebView2* webview, IArgs* args) -> HRESULT { - LPWSTR messageRaw; - args->TryGetWebMessageAsString(&messageRaw); - SSC::WString message_w(messageRaw); - CoTaskMemFree(messageRaw); - if (onMessage != nullptr) { - SSC::String message = SSC::convertWStringToString(message_w); - auto msg = IPC::Message{message}; - Window* w = reinterpret_cast<Window*>(GetWindowLongPtr((HWND)window, GWLP_USERDATA)); - ICoreWebView2_2* webview2 = nullptr; - ICoreWebView2Environment* env = nullptr; - ICoreWebView2_18* webView18 = nullptr; - ICoreWebView2Environment12* environment = nullptr; - - webview->QueryInterface(IID_PPV_ARGS(&webview2)); - webview2->get_Environment(&env); - env->QueryInterface(IID_PPV_ARGS(&environment)); - - webview->QueryInterface(IID_PPV_ARGS(&webView18)); - - // this should only come from `postMessage()` - if (msg.name == "buffer.create") { - auto seq = msg.seq; - auto size = std::stoull(msg.get("size", "0")); - auto index = msg.index; - ICoreWebView2SharedBuffer* sharedBuffer = nullptr; - // TODO(heapwolf): What to do if creation fails, or size == 0? - HRESULT cshr = environment->CreateSharedBuffer(size, &sharedBuffer); - String additionalData = "{\"seq\":\""; - additionalData += seq; - additionalData += "\",\"index\":"; - additionalData += std::to_string(index); - additionalData += "}"; - cshr = webView18->PostSharedBufferToScript( - sharedBuffer, - COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_WRITE, - convertStringToWString(additionalData).c_str() - ); - IPC::MessageBuffer msg_buf(sharedBuffer, size); - // TODO(heapwolf): This will leak memory if the buffer is created and - // placed on the map then never removed. Since there's no Window cleanup - // that will remove unused buffers when the window is closed. - w->bridge->router.setMappedBuffer(index, seq, msg_buf); - return S_OK; - } - - if (!w->bridge->route(message, nullptr, 0)) { - onMessage(message); - } - } + if (kind == COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY) { + if (userConfig["permissions_allow_autoplay"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } - return S_OK; - }).Get(), - &tokenMessage - ); - - EventRegistrationToken tokenPermissionRequested; - webview->add_PermissionRequested( - Microsoft::WRL::Callback<ICoreWebView2PermissionRequestedEventHandler>([&]( - ICoreWebView2 *webview, - ICoreWebView2PermissionRequestedEventArgs *args - ) -> HRESULT { - static auto userConfig = SSC::getUserConfig(); - COREWEBVIEW2_PERMISSION_KIND kind; - args->get_PermissionKind(&kind); - - if (kind == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE) { - if ( - userConfig["permissions_allow_microphone"] == "false" || - userConfig["permissions_allow_user_media"] == "false" - ) { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + if (kind == COREWEBVIEW2_PERMISSION_KIND_LOCAL_FONTS) { + if (userConfig["permissions_allow_local_fonts"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } - if (kind == COREWEBVIEW2_PERMISSION_KIND_CAMERA) { - if ( - userConfig["permissions_allow_camera"] == "false" || - userConfig["permissions_allow_user_media"] == "false" - ) { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + return S_OK; + }).Get(), + &token + ); + } while (0); + + // configure webview callback for `window.open()` + do { + EventRegistrationToken token; + this->webview->add_NewWindowRequested( + Microsoft::WRL::Callback<ICoreWebView2NewWindowRequestedEventHandler>( + [&](ICoreWebView2* webview, ICoreWebView2NewWindowRequestedEventArgs* args) { + // TODO(@jwerle): handle 'window.open()' + args->put_Handled(true); + return S_OK; + } + ).Get(), + &token + ); + } while (0); + + // configure webview message handler + do { + EventRegistrationToken token; + this->webview->add_WebMessageReceived( + Microsoft::WRL::Callback<ICoreWebView2WebMessageReceivedEventHandler>([=]( + ICoreWebView2* webview, + ICoreWebView2WebMessageReceivedEventArgs* args + ) -> HRESULT { + if (this->onMessage == nullptr) { + return S_OK; + } - if (kind == COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION) { - if (userConfig["permissions_allow_geolocation"] == "false") { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + ICoreWebView2Environment12* environment12 = nullptr; + ICoreWebView2Environment* environment = nullptr; + ICoreWebView2_18* webview18 = nullptr; + ICoreWebView2_2* webview2 = nullptr; - if (kind == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS) { - if (userConfig["permissions_allow_notifications"] == "false") { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + LPWSTR string; + args->TryGetWebMessageAsString(&string); + const auto message = IPC::Message(convertWStringToString(string)); + CoTaskMemFree(string); - if (kind == COREWEBVIEW2_PERMISSION_KIND_OTHER_SENSORS) { - if (userConfig["permissions_allow_sensors"] == "false") { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + webview2->get_Environment(&environment); + environment->QueryInterface(IID_PPV_ARGS(&environment12)); + this->webview->QueryInterface(IID_PPV_ARGS(&webview2)); + this->webview->QueryInterface(IID_PPV_ARGS(&webview18)); - if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { - if (userConfig["permissions_allow_clipboard"] == "false") { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + if (!this->bridge.route(message, nullptr, 0)) { + onMessage(message); + } - if (kind == COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY) { - if (userConfig["permissions_allow_autoplay"] == "false") { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - } + return S_OK; + }).Get(), + &token + ); + } while (0); + + // configure webview web resource requested callback + do { + EventRegistrationToken token; + webview->add_WebResourceRequested( + Microsoft::WRL::Callback<ICoreWebView2WebResourceRequestedEventHandler>([=, this]( + ICoreWebView2* webview, + ICoreWebView2WebResourceRequestedEventArgs* args + ) { + ICoreWebView2WebResourceRequest* platformRequest = nullptr; + ICoreWebView2Environment* env = nullptr; + ICoreWebView2Deferral* deferral = nullptr; + + if (auto result = args->GetDeferral(&deferral)) { + return result; + } - if (kind == COREWEBVIEW2_PERMISSION_KIND_LOCAL_FONTS) { - if (userConfig["permissions_allow_local_fonts"] == "false") { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); - } else { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } + // get platform request and environment from event args + do { + ICoreWebView2_2* webview2 = nullptr; + webview->QueryInterface(IID_PPV_ARGS(&webview2)); + webview2->get_Environment(&env); + args->get_Request(&platformRequest); + } while (0); + + auto request = IPC::SchemeHandlers::Request::Builder(&this->bridge.schemeHandlers, platformRequest); + + // get and set HTTP method + do { + LPWSTR method; + platformRequest->get_Method(&method); + request.setMethod(convertWStringToString(method)); + } while (0); + + // iterator all HTTP headers and set them + do { + ICoreWebView2HttpRequestHeaders* headers = nullptr; + platformRequest->get_Headers(&headers); + ComPtr<ICoreWebView2HttpHeadersCollectionIterator> iterator; + BOOL hasCurrent = FALSE; + CHECK_FAILURE(headers->GetIterator(&iterator)); + while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { + LPWSTR name; + LPWSTR value; + if (iterator->GetCurrentHeader(&name, &value) == S_OK) { + request.setHeader(convertWStringToString(name), convertWStringToString(value)); } + } + } while (0); - return S_OK; - }).Get(), - &tokenPermissionRequested - ); + const auto handled = this->bridge.schemeHandlers.handleRequest(request.build(), [=](const auto& response) mutable { + args->put_Response(response.platformResponse); + deferral->Complete(); + }); - app.isReady = true; + if (!handled) { + auto response = IPC::SchemeHandlers::Response(request, 404); + response.finish(); + args->put_Response(response.platformResponse); + deferral->Complete(); + } return S_OK; - } - ).Get() - ); + }).Get(), + &token + ); + } while (0); + // notify app is ready + app->isReady = true; return S_OK; - } - ).Get() - ); - }; - - auto res = init(); - - if (!SUCCEEDED(res)) { - std::cerr << "Webview2 failed to initialize: " << std::to_string(res) << std::endl; - } + }).Get() + ); + }).Get() + ); } Window::~Window () { - delete this->drop; - delete this->bridge; } ScreenSize Window::getScreenSize () { @@ -1472,22 +1114,23 @@ namespace SSC { } void Window::about () { - auto text = SSC::String( - app.userConfig["build_name"] + " " + - "v" + app.userConfig["meta_version"] + "\n" + - "Built with ssc v" + SSC::VERSION_FULL_STRING + "\n" + - app.userConfig["meta_copyright"] + auto app = App::sharedApplication(); + auto text = String( + this->options.userConfig["build_name"] + " " + + "v" + this->options.userConfig["meta_version"] + "\n" + + "Built with ssc v" + VERSION_FULL_STRING + "\n" + + this->options.userConfig["meta_copyright"] ); MSGBOXPARAMS mbp; mbp.cbSize = sizeof(MSGBOXPARAMS); - mbp.hwndOwner = window; - mbp.hInstance = app.hInstance; + mbp.hwndOwner = this->window; + mbp.hInstance = app->hInstance; mbp.lpszText = text.c_str(); - mbp.lpszCaption = app.userConfig["build_name"].c_str(); + mbp.lpszCaption = this->options.userConfig["build_name"].c_str(); mbp.dwStyle = MB_USERICON; mbp.dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); - mbp.lpfnMsgBoxCallback = NULL; + mbp.lpfnMsgBoxCallback = nullptr; mbp.dwContextHelpId = 0; MessageBoxIndirect(&mbp); @@ -1496,8 +1139,8 @@ namespace SSC { void Window::kill () { if (this->controller != nullptr) this->controller->Close(); if (this->window != nullptr) { - if (menubar != NULL) DestroyMenu(menubar); - if (menutray != NULL) DestroyMenu(menutray); + if (menubar != nullptr) DestroyMenu(menubar); + if (menutray != nullptr) DestroyMenu(menutray); DestroyWindow(this->window); } } @@ -1509,8 +1152,8 @@ namespace SSC { void Window::exit (int code) { if (this->onExit != nullptr) { std::cerr << "WARNING: Window#" << index << " exiting with code " << code << std::endl; - if (menubar != NULL) DestroyMenu(menubar); - if (menutray != NULL) DestroyMenu(menutray); + if (menubar != nullptr) DestroyMenu(menubar); + if (menutray != nullptr) DestroyMenu(menutray); this->onExit(code); } else { @@ -1519,7 +1162,7 @@ namespace SSC { } void Window::close (int code) { - if (opts.shouldExitApplicationOnClose) { + if (options.shouldExitApplicationOnClose) { this->exit(0); DestroyWindow(window); } else { @@ -1540,12 +1183,12 @@ namespace SSC { } void Window::show () { - static auto userConfig = SSC::getUserConfig(); + static auto userConfig = getUserConfig(); auto isAgent = userConfig.count("application_agent") != 0; - if (isAgent && this->opts.index == 0) return; + if (isAgent && this->options.index == 0) return; - if (this->opts.headless == false) { + if (this->options.headless == false) { ShowWindow(window, SW_SHOWNORMAL); UpdateWindow(window); @@ -1570,7 +1213,7 @@ namespace SSC { rc.right = 0; InvalidateRect(this->window, &rc, true); DrawMenuBar(this->window); - RedrawWindow(this->window, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); + RedrawWindow(this->window, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE); } } @@ -1590,14 +1233,14 @@ namespace SSC { controller->put_Bounds(bounds); } - void Window::eval (const SSC::String& s) { + void Window::eval (const String& s) { app.dispatch([&, this, s] { if (this->webview == nullptr) { return; } this->webview->ExecuteScript( - SSC::convertStringToWString(s).c_str(), + convertStringToWString(s).c_str(), nullptr ); }); @@ -1607,15 +1250,15 @@ namespace SSC { return this->navigate("", url); } - void Window::navigate (const SSC::String& seq, const SSC::String& value) { - auto index = std::to_string(this->opts.index); + void Window::navigate (const String& seq, const String& value) { + auto index = std::to_string(this->options.index); app.dispatch([&, this, seq, value, index] { EventRegistrationToken token; this->webview->add_NavigationCompleted( Microsoft::WRL::Callback<ICoreWebView2NavigationCompletedEventHandler>( [&, this, seq, index, token](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { - SSC::String state = "1"; + String state = "1"; BOOL success; args->get_IsSuccess(&success); @@ -1633,7 +1276,7 @@ namespace SSC { &token ); - webview->Navigate(SSC::convertStringToWString(value).c_str()); + webview->Navigate(convertStringToWString(value).c_str()); }); } @@ -1652,7 +1295,7 @@ namespace SSC { return ""; } - void Window::setTitle (const SSC::String& title) { + void Window::setTitle (const String& title) { SetWindowText(window, title.c_str()); } @@ -1702,7 +1345,7 @@ namespace SSC { AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); SetWindowPos( window, - NULL, + nullptr, r.left, r.top, r.right - r.left, @@ -1727,7 +1370,7 @@ namespace SSC { AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); SetWindowPos( window, - NULL, + nullptr, r.left, r.top, r.right - r.left, @@ -1740,16 +1383,16 @@ namespace SSC { this->position.y = y; } - void Window::setTrayMenu (const SSC::String& seq, const SSC::String& value) { + void Window::setTrayMenu (const String& seq, const String& value) { setMenu(seq, value, true); } - void Window::setSystemMenu (const SSC::String& seq, const SSC::String& value) { + void Window::setSystemMenu (const String& seq, const String& value) { setMenu(seq, value, false); } - void Window::setMenu (const SSC::String& seq, const SSC::String& menuSource, const bool& isTrayMenu) { - static auto userConfig = SSC::getUserConfig(); + void Window::setMenu (const String& seq, const String& menuSource, const bool& isTrayMenu) { + static auto userConfig = getUserConfig(); if (menuSource.empty()) return void(0); NOTIFYICONDATA nid; @@ -1775,7 +1418,7 @@ namespace SSC { HICON icon; if (trayIconPath.size() > 0) { icon = (HICON) LoadImageA( - NULL, + nullptr, trayIconPath.c_str(), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), @@ -1783,7 +1426,10 @@ namespace SSC { LR_LOADFROMFILE ); } else { - icon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_APPLICATION)); + icon = LoadIcon( + GetModuleHandle(nullptr), + reinterpret_cast<LPCSTR>(MAKEINTRESOURCE(IDI_APPLICATION)) + ); } menutray = CreatePopupMenu(); @@ -1828,9 +1474,9 @@ namespace SSC { if (line.find("---") != -1) { if (isTrayMenu) { - AppendMenuW(menutray, MF_SEPARATOR, 0, NULL); + AppendMenuW(menutray, MF_SEPARATOR, 0, nullptr); } else { - AppendMenuW(subMenu, MF_SEPARATOR, 0, NULL); + AppendMenuW(subMenu, MF_SEPARATOR, 0, nullptr); } continue; } @@ -1838,19 +1484,19 @@ namespace SSC { auto parts = split(line, ':'); auto title = parts[0]; int mask = 0; - SSC::String key = ""; + String key = ""; auto accelerators = split(parts[1], '+'); - auto accl = SSC::String(""); + auto accl = String(""); key = trim(parts[1]) == "_" ? "" : trim(accelerators[0]); if (key.size() > 0) { - bool isShift = SSC::String("ABCDEFGHIJKLMNOPQRSTUVWXYZ").find(key) != -1; + bool isShift = String("ABCDEFGHIJKLMNOPQRSTUVWXYZ").find(key) != -1; accl = key; if (accelerators.size() > 1) { - accl = SSC::String(trim(accelerators[1]) + "+" + key); + accl = String(trim(accelerators[1]) + "+" + key); accl = replace(accl, "CommandOrControl", "Ctrl"); accl = replace(accl, "Command", "Ctrl"); accl = replace(accl, "Control", "Ctrl"); @@ -1858,18 +1504,18 @@ namespace SSC { } if (isShift) { - accl = SSC::String("Shift+" + accl); + accl = String("Shift+" + accl); } } - auto display = SSC::String(title + "\t" + accl); + auto display = String(title + "\t" + accl); if (isTrayMenu) { AppendMenuA(menutray, MF_STRING, itemId, display.c_str()); - menuTrayMap[itemId] = SSC::String(title + ":" +(parts.size() > 1 ? parts[1] : "")); + menuTrayMap[itemId] = String(title + ":" +(parts.size() > 1 ? parts[1] : "")); } else { AppendMenuA(subMenu, MF_STRING, itemId, display.c_str()); - menuMap[itemId] = SSC::String(title + "\t" + menuTitle); + menuMap[itemId] = String(title + "\t" + menuTitle); } itemId++; @@ -1896,247 +1542,116 @@ namespace SSC { rc.right = 0; InvalidateRect(this->window, &rc, true); DrawMenuBar(this->window); - RedrawWindow(this->window, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); + RedrawWindow(this->window, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE); } if (seq.size() > 0) { - auto index = std::to_string(this->opts.index); + auto index = std::to_string(this->options.index); this->resolvePromise(seq, "0", index); } } void Window::setSystemMenuItemEnabled (bool enabled, int barPos, int menuPos) { - // @TODO(): provide impl + // TODO } void Window::closeContextMenu() { - // @TODO(jwerle) + // TODO } - void Window::closeContextMenu(const SSC::String &seq) { - // @TODO(jwerle) + void Window::closeContextMenu (const String &seq) { + // TODO } - void Window::setContextMenu (const SSC::String& seq, const SSC::String& menuSource) { - if (menuSource.empty()) return void(0); - - HMENU hPopupMenu = CreatePopupMenu(); + void Window::setContextMenu (const String& seq, const String& menuSource) { + if (menuSource.empty()) { + return; + } - auto menuItems = split(menuSource, '\n'); + const auto menuItems = split(menuSource, '\n'); + auto menu = CreatePopupMenu(); + Vector<String> lookup; int index = 1; - std::vector<SSC::String> lookup; - lookup.push_back(""); - for (auto item : menuItems) { - auto pair = split(trim(item), ':'); - auto key = SSC::String(""); + lookup.push_back(""); - if (pair.size() > 1) { - key = pair[1]; - } + for (const auto& item : menuItems) { + const auto pair = split(trim(item), ':'); if (pair[0].find("---") != -1) { - InsertMenu(hPopupMenu, 0, MF_SEPARATOR, 0, NULL); + InsertMenu(menu, 0, MF_SEPARATOR, 0, nullptr); } else { lookup.push_back(pair[0]); - InsertMenu(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, index++, pair[0].c_str()); + InsertMenu(menu, 0, MF_BYPOSITION | MF_STRING, index++, pair[0].c_str()); } } - SetForegroundWindow(window); + SetForegroundWindow(this->window); - POINT p; - GetCursorPos(&p); + POINT point; + GetCursorPos(&point); - auto selection = TrackPopupMenu( - hPopupMenu, + const auto selection = TrackPopupMenu( + menu, TPM_RETURNCMD | TPM_NONOTIFY, - p.x, - p.y, + point.x, + point.y, 0, - window, + this->window, nullptr ); - DestroyMenu(hPopupMenu); - if (selection == 0) return; - this->eval(getResolveMenuSelectionJavaScript(seq, lookup.at(selection), "contextMenu", "context")); + DestroyMenu(menu); + + if (selection != 0) { + this->eval(getResolveMenuSelectionJavaScript(seq, lookup.at(selection), "contextMenu", "context")); + } } void Window::setBackgroundColor (const String& rgba) { + const auto parts = split(trim(replace(replace(rgba, "rgba(", ""), ")", "")), ','); + int r = 0, g = 0, b = 0; - } + if (parts.size() >= 3) { + try { r = std::stoi(trim(parts[0])); } + catch (...) {} - void Window::setBackgroundColor (int r, int g, int b, float a) { - SetBkColor(GetDC(window), RGB(r, g, b)); - app.wcex.hbrBackground = CreateSolidBrush(RGB(r, g, b)); - } + try { g = std::stoi(trim(parts[1])); } + catch (...) {} - String Window::getBackgroundColor () { - LOGBRUSH lb; - GetObject(app.wcex.hbrBackground, sizeof(LOGBRUSH), &lb); + try { b = std::stoi(trim(parts[2])); } + catch (...) {} - int r = GetRValue(lb.lbColor); - int g = GetGValue(lb.lbColor); - int b = GetBValue(lb.lbColor); + return this->setBackgroundColor(r, g, b, 1.0); + } + } - std::stringstream ss; - ss << "R:" << r << ", G:" << g << ", B:" << b; - return ss.str(); + void Window::setBackgroundColor (int r, int g, int b, float a) { + SetBkColor(GetDC(this->window), RGB(r, g, b)); } - // message is defined in WinUser.h - // https://raw.githubusercontent.com/tpn/winsdk-10/master/Include/10.0.10240.0/um/WinUser.h - LRESULT CALLBACK Window::WndProc( - HWND hWnd, - UINT message, - WPARAM wParam, - LPARAM lParam - ) { - static auto app = SSC::App::sharedApplication(); - Window* w = reinterpret_cast<Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); - - if (message == WM_COPYDATA) { - auto copyData = reinterpret_cast<PCOPYDATASTRUCT>(lParam); - message = (UINT) copyData->dwData; - wParam = (WPARAM) copyData->cbData; - lParam = (LPARAM) copyData->lpData; + String Window::getBackgroundColor () { + auto color = GetBkColor(GetDC(this->window)); + if (color == CLR_INVALID) { + return ""; } - switch (message) { - case WM_SIZE: { - if (w == nullptr || w->webview == nullptr) { - break; - } - - RECT bounds; - GetClientRect(hWnd, &bounds); - w->controller->put_Bounds(bounds); - break; - } - - case WM_SOCKET_TRAY: { - static auto userConfig = SSC::getUserConfig(); - auto isAgent = userConfig.count("tray_icon") != 0; - - if (lParam == WM_LBUTTONDOWN) { - SetForegroundWindow(hWnd); - if (isAgent) { - POINT pt; - GetCursorPos(&pt); - TrackPopupMenu(w->menutray, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL); - } - - PostMessage(hWnd, WM_NULL, 0, 0); + const auto r = GetRValue(color); + const auto g = GetGValue(color); + const auto b = GetBValue(color); - // broadcast an event to all the windows that the tray icon was clicked - if (app != nullptr && app->windowManager != nullptr) { - for (auto window : app->windowManager->windows) { - if (window != nullptr) { - window->bridge->router.emit("tray", "true"); - } - } - } - } - // fall through to WM_COMMAND!! - } - - case WM_COMMAND: { - if (w == nullptr) break; - - if (w->menuMap.contains(wParam)) { - String meta(w->menuMap[wParam]); - auto parts = split(meta, '\t'); - - if (parts.size() > 1) { - auto title = parts[0]; - auto parent = parts[1]; - - if (String(title).find("About") == 0) { - w->about(); - break; - } + char string[100]; - if (String(title).find("Quit") == 0) { - w->exit(0); - break; - } - - w->eval(getResolveMenuSelectionJavaScript("0", title, parent, "system")); - } - } else if (w->menuTrayMap.contains(wParam)) { - String meta(w->menuTrayMap[wParam]); - auto parts = split(meta, ':'); - - if (parts.size() > 0) { - auto title = trim(parts[0]); - auto tag = parts.size() > 1 ? trim(parts[1]) : ""; - w->eval(getResolveMenuSelectionJavaScript("0", title, tag, "tray")); - } - } - - break; - } - - case WM_SETTINGCHANGE: { - // TODO(heapwolf): Dark mode - break; - } - - case WM_CREATE: { - // TODO(heapwolf): Dark mode - SetWindowTheme(hWnd, L"Explorer", NULL); - SetMenu(hWnd, CreateMenu()); - break; - } - - case WM_CLOSE: { - if (!w->opts.closable) break; - - SSC::JSON::Object json = SSC::JSON::Object::Entries { - {"data", w->index} - }; - - auto app = App::sharedApplication(); - app->windowManager->destroyWindow(w->index); - - for (auto window : app->windowManager->windows) { - if (window != nullptr) { - window->eval(getEmitToRenderProcessJavaScript("window-closed", json.str())); - } - } - - w->close(0); - break; - } - - case WM_HOTKEY: { - w->hotkey.onHotKeyBindingCallback((HotKeyBinding::ID) wParam); - break; - } - - case WM_HANDLE_DEEP_LINK: { - auto url = SSC::String((const char*) lParam, wParam); - SSC::JSON::Object json = SSC::JSON::Object::Entries {{ - "url", url - }}; - - if (app != nullptr && app->windowManager != nullptr) { - for (auto window : app->windowManager->windows) { - if (window != nullptr) { - window->bridge->router.emit("applicationurl", json.str()); - } - } - } - break; - } - - default: - return DefWindowProc(hWnd, message, wParam, lParam); - break; - } + snprintf( + string, + sizeof(string), + "rgba(%d, %d, %d, %f)", + r, + g, + b, + 1.0f + ); - return 0; + return string; } - -} // namespace SSC +} diff --git a/src/window/window.hh b/src/window/window.hh index 8e85b9e34f..8852fb6b1f 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -1,8 +1,6 @@ #ifndef SOCKET_RUNTIME_WINDOW_WINDOW_H #define SOCKET_RUNTIME_WINDOW_WINDOW_H -#include <iostream> - #include "../core/env.hh" #include "../core/config.hh" #include "../core/webview.hh" @@ -372,8 +370,6 @@ namespace SSC { bool isDragInvokedInsideWindow; GdkPoint initialLocation; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); - bool usingCustomEdgeRuntimeDirectory = false; ICoreWebView2Controller *controller = nullptr; HMENU menubar; HMENU menutray; @@ -382,7 +378,7 @@ namespace SSC { double dragLastX = 0; double dragLastY = 0; bool shouldDrag; - DragDrop* drop; + SharedPointer<DragDrop> drop; POINT minimumSize = POINT {0, 0}; POINT maximumSize = POINT {0, 0}; @@ -393,7 +389,6 @@ namespace SSC { HWND window; std::map<int, String> menuMap; std::map<int, String> menuTrayMap; - Path modulePath; void resize (HWND window); #elif SOCKET_RUNTIME_PLATFORM_ANDROID @@ -548,57 +543,12 @@ namespace SSC { SharedPointer<ManagedWindow> getOrCreateWindow (int index, const Window::Options& options); WindowStatus getWindowStatus (int index); - void destroyWindow (int index); - SharedPointer<ManagedWindow> createWindow (const Window::Options& options); SharedPointer<ManagedWindow> createDefaultWindow (const Window::Options& options); - JSON::Array json (const Vector<int>& indices); - }; - -#if SOCKET_RUNTIME_PLATFORM_WINDOWS - using IEnvHandler = ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler; - using IConHandler = ICoreWebView2CreateCoreWebView2ControllerCompletedHandler; - using INavHandler = ICoreWebView2NavigationCompletedEventHandler; - using IRecHandler = ICoreWebView2WebMessageReceivedEventHandler; - using IArgs = ICoreWebView2WebMessageReceivedEventArgs; - - enum WINDOWCOMPOSITIONATTRIB { - WCA_UNDEFINED = 0, - WCA_NCRENDERING_ENABLED = 1, - WCA_NCRENDERING_POLICY = 2, - WCA_TRANSITIONS_FORCEDISABLED = 3, - WCA_ALLOW_NCPAINT = 4, - WCA_CAPTION_BUTTON_BOUNDS = 5, - WCA_NONCLIENT_RTL_LAYOUT = 6, - WCA_FORCE_ICONIC_REPRESENTATION = 7, - WCA_EXTENDED_FRAME_BOUNDS = 8, - WCA_HAS_ICONIC_BITMAP = 9, - WCA_THEME_ATTRIBUTES = 10, - WCA_NCRENDERING_EXILED = 11, - WCA_NCADORNMENTINFO = 12, - WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, - WCA_VIDEO_OVERLAY_ACTIVE = 14, - WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, - WCA_DISALLOW_PEEK = 16, - WCA_CLOAK = 17, - WCA_CLOAKED = 18, - WCA_ACCENT_POLICY = 19, - WCA_FREEZE_REPRESENTATION = 20, - WCA_EVER_UNCLOAKED = 21, - WCA_VISUAL_OWNER = 22, - WCA_HOLOGRAPHIC = 23, - WCA_EXCLUDED_FROM_DDA = 24, - WCA_PASSIVEUPDATEMODE = 25, - WCA_USEDARKMODECOLORS = 26, - WCA_LAST = 27 - }; + void destroyWindow (int index); - struct WINDOWCOMPOSITIONATTRIBDATA { - WINDOWCOMPOSITIONATTRIB Attrib; - PVOID pvData; - SIZE_T cbData; + JSON::Array json (const Vector<int>& indices); }; -#endif } #endif From a76c72365a2b2b135040423e181e00a42cce146b Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:38:26 +0200 Subject: [PATCH 0836/1178] refactor(core): clean up and consolidate windows edge runtime resource path --- src/core/core.cc | 8 +++++++- src/core/core.hh | 3 +-- src/core/modules/notifications.cc | 6 +++--- src/core/modules/notifications.hh | 7 +------ src/core/resource.cc | 21 ++++++++++++++++++--- src/core/resource.hh | 4 ++++ src/core/webview.cc | 1 - 7 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 6ed5b253bc..f927aaf885 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -11,10 +11,16 @@ namespace SSC { } void Core::shutdown () { + if (this->shuttingDown) { + return; + } + + this->shuttingDown = true; #if SOCKET_RUNTIME_PLATFORM_DESKTOP this->childProcess.shutdown(); #endif this->stopEventLoop(); + this->shuttingDown = false; } bool Core::hasPost (uint64_t id) { @@ -266,7 +272,7 @@ namespace SSC { if (ms > 0) { auto timeout = getEventLoopTimeout(); ms = timeout > ms ? timeout : ms; - std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + msleep(ms); } } diff --git a/src/core/core.hh b/src/core/core.hh index b8ffa81d87..0daea09a3c 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -153,14 +153,13 @@ namespace SSC { fs(this), geolocation(this), networkStatus(this), - notifications(this, { options.features.useNotifications }), + notifications(this), os(this), platform(this), timers(this), udp(this) { initEventLoop(); - if (options.features.useNetworkStatus) { this->networkStatus.start(); } diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index 4e0a7c1dc4..4e06c3a4a2 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -96,7 +96,7 @@ namespace SSC { notificationPresentedObservers() { #if SOCKET_RUNTIME_PLATFORM_APPLE - if (!this->options.enabled) return; + if (!core->options.features.useNotifications) return; auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; @@ -130,7 +130,7 @@ namespace SSC { [NSRunLoop.mainRunLoop addTimer: this->userNotificationCenterPollTimer - forMode: NSDefaultRunLoopMode + forMode: NSDefaultRunLoopMode ]; }]; #endif @@ -138,7 +138,7 @@ namespace SSC { CoreNotifications::~CoreNotifications () { #if SOCKET_RUNTIME_PLATFORM_APPLE - if (!this->options.enabled) return; + if (!core->options.features.useNotifications) return; auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; diff --git a/src/core/modules/notifications.hh b/src/core/modules/notifications.hh index 2032ccada5..0169cc7e8c 100644 --- a/src/core/modules/notifications.hh +++ b/src/core/modules/notifications.hh @@ -30,10 +30,6 @@ namespace SSC { using NotificationPresentedObserver = CoreModule::Observer<JSON::Object>; using NotificationPresentedObservers = CoreModule::Observers<NotificationPresentedObserver>; - struct Options { - bool enabled = true; - }; - struct Notification { String identifier; const JSON::Object json () const; @@ -64,14 +60,13 @@ namespace SSC { UNAuthorizationStatus __block currentUserNotificationAuthorizationStatus; #endif - Options options; Mutex mutex; PermissionChangeObservers permissionChangeObservers; NotificationResponseObservers notificationResponseObservers; NotificationPresentedObservers notificationPresentedObservers; - CoreNotifications (Core* core, const Options& options); + CoreNotifications (Core* core); ~CoreNotifications (); bool removePermissionChangeObserver (const PermissionChangeObserver& observer); diff --git a/src/core/resource.cc b/src/core/resource.cc index 5ad3a37980..b55a0decd7 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -3,6 +3,8 @@ #include "debug.hh" #include "core.hh" +#include "../platform/platform.hh" + #if SOCKET_RUNTIME_PLATFORM_ANDROID #include "../platform/android.hh" #include <fstream> @@ -254,10 +256,10 @@ namespace SSC { static wchar_t filename[MAX_PATH]; GetModuleFileNameW(NULL, filename, MAX_PATH); const auto self = Path(filename).remove_filename(); - value = path.string(); + value = self.string(); size_t offset = 0; // escape - while ((offset = value.find('\\', offset)) != Sstring::npos) { + while ((offset = value.find('\\', offset)) != String::npos) { value.replace(offset, 1, "\\\\"); offset += 2; } @@ -266,7 +268,7 @@ namespace SSC { #endif if (value.size() > 0) { - #if !PLATFORM_WINDOWS + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS std::replace( value.begin(), value.end(), @@ -462,6 +464,19 @@ namespace SSC { return entries; } +#if SOCKET_RUNTIME_PLATFORM_WINDOWS + const Path FileResource::getMicrosoftEdgeRuntimePath () { + // this is something like "C:\\Users\\jwerle\\AppData\\Local\\Microsoft\\Edge SxS\\Application\\123.0.2386.0" + static const auto EDGE_RUNTIME_DIRECTORY = convertStringToWString(trim(Env::get("SOCKET_EDGE_RUNTIME_DIRECTORY"))); + + return Path( + EDGE_RUNTIME_DIRECTORY.size() > 0 && fs::exists(EDGE_RUNTIME_DIRECTORY) + ? EDGE_RUNTIME_DIRECTORY + : "" + ); + } +#endif + FileResource::FileResource ( const Path& resourcePath, const Options& options diff --git a/src/core/resource.hh b/src/core/resource.hh index 19fe046e12..61f670ecbd 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -131,6 +131,10 @@ namespace SSC { static const WellKnownPaths& getWellKnownPaths (); static const Map getMountedPaths (); + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + static const Path getMicrosoftEdgeRuntimePath (); + #endif + #if SOCKET_RUNTIME_PLATFORM_ANDROID static void setSharedAndroidAssetManager (Android::AssetManager*); static Android::AssetManager* getSharedAndroidAssetManager (); diff --git a/src/core/webview.cc b/src/core/webview.cc index b727a02b1b..d8adef2f84 100644 --- a/src/core/webview.cc +++ b/src/core/webview.cc @@ -695,5 +695,4 @@ int lastY = 0; @implementation SSCWebViewController @end #endif - #endif From 499d4383d2e2f4663132fbda188e9307a90d2e7a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:38:50 +0200 Subject: [PATCH 0837/1178] refactor(bin): improve windows on linux DX --- bin/cflags.sh | 32 +++++++++++++++++++++++++++++--- bin/install.sh | 4 ++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/bin/cflags.sh b/bin/cflags.sh index 369320cf5a..5ace39c49c 100755 --- a/bin/cflags.sh +++ b/bin/cflags.sh @@ -8,7 +8,7 @@ declare IOS_SIMULATOR_VERSION_MIN="${IOS_SIMULATOR_VERSION_MIN:-$IPHONEOS_VERSIO declare cflags=() declare arch="$(uname -m | sed 's/aarch64/arm64/g')" arch=${ARCH:-$arch} -declare host="$(uname -s)" +declare host="${TARGET_HOST:-"$(uname -s)"}" declare platform="desktop" declare ios_sdk_path="" @@ -47,7 +47,7 @@ else fi cflags+=( - $CFLAG + $CFLAGS $CXXFLAGS -std=c++2a -ferror-limit=6 @@ -99,12 +99,38 @@ if (( !TARGET_OS_ANDROID && !TARGET_ANDROID_EMULATOR )); then -D_DLL -DWIN32 -DWIN32_LEAN_AND_MEAN - -Xlinker /NODEFAULTLIB:libcmt + "-Xlinker /NODEFAULTLIB:libcmt" -Wno-nonportable-include-path ) if [[ -n "$DEBUG" ]]; then cflags+=("-D_DEBUG") fi + + ## TODO(@jwerle): figure this out for macOS + if [[ "$(uname -s)" == "Linux" ]]; then + cflags+=( + "-fdeclspec" + "-I/usr/share/mingw-w64/include" + "-I/usr/include/x86_64-linux-gnu/c++/12/" + "-I/usr/lib/gcc/x86_64-w64-mingw32/10-win32/include/" + "-I/usr/lib/gcc/x86_64-w64-mingw32/10-posix/include/c++" + "-I/usr/lib/gcc/x86_64-w64-mingw32/10-win32/include/c++" + "-I/usr/lib/gcc/x86_64-w64-mingw32/10-win32/include/c++/x86_64-w64-mingw32" + "-I/usr/lib/gcc/x86_64-w64-mingw32/10-win32/include/c++/backward" + "-DWIN32" + "-DWINVER=0x0A00" + "-D_WIN32_WINNT=0x0A00" + "-D_WIN32" + "-D_WIN64" + "-D_MSC_VER=1940" + "-D_MSC_FULL_VER=193933519" + "-D_MSC_BUILD=0" + "-D_GLIBCXX_HAS_GTHREADS=1" + "-DSOCKET_RUNTIME_CROSS_COMPILED_HOST=1" + "-DSOCKET_RUNTIME_PLATFORM_WANTS_MINGW=1" + $(pkg-config gthread-2.0 --cflags) + ) + fi fi fi diff --git a/bin/install.sh b/bin/install.sh index 4e8264c128..7a4a461bdd 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -343,14 +343,14 @@ function _build_runtime_library() { } function _get_web_view2() { - if [[ "$(uname -s)" != *"_NT"* ]]; then + if [[ "$(uname -s)" != *"_NT"* ]] && [ -z "$FORCE_WEBVIEW2_DOWNLOAD" ]; then return fi local arch="$(host_arch)" local platform="desktop" - if test -f "$BUILD_DIR/$arch-$platform/lib$d/WebView2LoaderStatic.lib"; then + if [ -z "$FORCE_WEBVIEW2_DOWNLOAD" ] && test -f "$BUILD_DIR/$arch-$platform/lib$d/WebView2LoaderStatic.lib"; then echo "$BUILD_DIR/$arch-$platform/lib$d/WebView2LoaderStatic.lib exists." return fi From fa4d60bcc6579b6a50ef05b9284670fa71b89588 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 20 Jun 2024 12:46:38 +0200 Subject: [PATCH 0838/1178] fix(core/modules/notifications.cc): fix broken constructor --- src/core/modules/notifications.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index 4e06c3a4a2..adbb4cec8b 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -88,9 +88,8 @@ namespace SSC { }; } - CoreNotifications::CoreNotifications (Core* core, const Options& options) + CoreNotifications::CoreNotifications (Core* core) : CoreModule(core), - options(options), permissionChangeObservers(), notificationResponseObservers(), notificationPresentedObservers() From b103ff0b676cb8cebd57386765aab4790552129a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 11:53:58 -0400 Subject: [PATCH 0839/1178] refactor(api): fixes for windows and CORS --- api/internal/primitives.js | 83 +++----------------------------------- api/ipc.js | 34 +--------------- api/npm/module.js | 2 +- api/npm/service-worker.js | 6 ++- api/url/index.js | 17 +------- 5 files changed, 14 insertions(+), 128 deletions(-) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index e546f1ce15..e9cabfd522 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -159,6 +159,11 @@ export function init () { } } else { descriptors[key] = { ...nativeDescriptor, writable: true, configurable: true } + if (typeof implementation.prototype[key] === 'function') { + descriptors[key].value = implementation.prototype[key] + delete descriptors[key].get + delete descriptors[key].set + } } if (descriptors[key] && typeof descriptors[key] === 'object') { @@ -287,84 +292,6 @@ export function init () { }) } - // globals - install({ - // url - URL, - URLPattern, - URLSearchParams, - - // Promise - Promise, - - // fetch - fetch, - Headers, - Request, - Response, - - // notifications - Notification, - - // pickers - showDirectoryPicker, - showOpenFilePicker, - showSaveFilePicker, - - // events - ApplicationURLEvent, - MenuItemEvent, - SignalEvent, - HotKeyEvent, - - // file - File, - FileSystemHandle, - FileSystemFileHandle, - FileSystemDirectoryHandle, - FileSystemWritableFileStream, - - // buffer - Buffer, - - // workers - SharedWorker, - ServiceWorker, - - // timers - setTimeout, - setInterval, - setImmediate, - clearTimeout, - clearInterval, - clearImmediate, - - // streams - ReadableStream, - ReadableStreamBYOBReader, - ReadableByteStreamController, - ReadableStreamBYOBRequest, - ReadableStreamDefaultController, - ReadableStreamDefaultReader, - WritableStream, - WritableStreamDefaultController, - WritableStreamDefaultWriter, - TransformStream, - TransformStreamDefaultController, - ByteLengthQueuingStrategy, - CountQueuingStrategy, - - // async - AsyncContext, - AsyncResource, - AsyncHook, - AsyncLocalStorage, - Deferred, - - // platform detection - isSocketRuntime: true - }) - if (globalThis.scheduler) { install(scheduler, globalThis.scheduler) } diff --git a/api/ipc.js b/api/ipc.js index b373fca8a2..352cf585bd 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -103,39 +103,6 @@ function initializeXHRIntercept () { } body = null } - - if (/win32/i.test(primordials.platform) && body) { - // 1. send `ipc://buffer.create` - // - The native side should create a shared buffer for `index` and `seq` pair of `size` bytes - // - `index` is the target window - // - `seq` is the sequence is used to know how to return the value to the sender - // 2. wait for 'sharedbufferreceived' event - // - The webview will wait for this event on `window` - // - The event should include "additional data" that is JSON and includes the `index` and `seq` values - // 3. filter on `index` and `seq` for this request - // - The webview will filter on the `index` and `seq` values before calling `getBuffer()` - // 4. write `body` to _shared_ `buffer` - // - The webview should write all bytes to the buffer - // 5. resolve promise - // - After promise resolution, the XHR request will continue - // - The native side should look up the shared buffer for the `index` and `seq` values and use it - // as the bytes for the request when routing the IPC request through the bridge router - // - The native side should release the shared buffer - // size here assumes latin1 encoding. - await postMessage(`ipc://buffer.create?index=${index}&seq=${seq}&size=${body.length}`) - await new Promise((resolve) => { - globalThis.chrome.webview - .addEventListener('sharedbufferreceived', function onSharedBufferReceived (event) { - const { additionalData } = event - if (additionalData.index === index && additionalData.seq === seq) { - const buffer = new Uint8Array(event.getBuffer()) - buffer.set(body) - globalThis.chrome.webview.removeEventListener('sharedbufferreceived', onSharedBufferReceived) - resolve() - } - }) - }) - } } } @@ -1582,6 +1549,7 @@ if ( Object.freeze(primordials) + initializeXHRIntercept() if (typeof globalThis?.window !== 'undefined') { diff --git a/api/npm/module.js b/api/npm/module.js index 8a59dc33a2..021983225e 100644 --- a/api/npm/module.js +++ b/api/npm/module.js @@ -50,7 +50,7 @@ export async function resolve (specifier, origin = null, options = null) { const pathname = name.pathname.replace(name.value, '.') || '.' try { - pkg.load() + pkg.load({ type }) const url = pkg.type === type ? pkg.resolve(pathname, { prefix, type }) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index ffd7e01198..892f8c2f15 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -30,7 +30,7 @@ export async function onRequest (request, env, ctx) { if (typeof specifier === 'string') { try { - specifier = (new URL(require.resolve(specifier))).toString() + specifier = (new URL(require.resolve(specifier, { type: 'module' }))).toString() } catch {} } @@ -173,6 +173,10 @@ export async function onRequest (request, env, ctx) { * @return {Response?} */ export default async function (request, env, ctx) { + if (request.method === 'OPTIONS') { + return new Response('OK', { status: 204 }) + } + if (request.method !== 'GET') { return new Response('Invalid HTTP method', { status: http.BAD_REQUEST diff --git a/api/url/index.js b/api/url/index.js index e012470cfd..3d6009ea49 100644 --- a/api/url/index.js +++ b/api/url/index.js @@ -1,4 +1,4 @@ -import { URLPattern as URLPatternImplementation } from './urlpattern/urlpattern.js' +import { URLPattern } from './urlpattern/urlpattern.js' import url from './url/url.js' import qs from '../querystring.js' @@ -20,19 +20,6 @@ URL.resolve = resolve URL.parse = parse URL.format = format -export class URLPattern extends URLPatternImplementation {} - -const URLPatternDescriptors = Object.getOwnPropertyDescriptors(URLPattern.prototype) -Object.defineProperties(URLPatternDescriptors, { - hash: { ...URLPatternDescriptors.hash, enumerable: true }, - hostname: { ...URLPatternDescriptors.hostname, enumerable: true }, - password: { ...URLPatternDescriptors.password, enumerable: true }, - pathname: { ...URLPatternDescriptors.pathname, enumerable: true }, - protocol: { ...URLPatternDescriptors.protocol, enumerable: true }, - username: { ...URLPatternDescriptors.username, enumerable: true }, - search: { ...URLPatternDescriptors.search, enumerable: true } -}) - URL.prototype[Symbol.for('socket.runtime.util.inspect.custom')] = function () { return [ 'URL {', @@ -325,4 +312,4 @@ Object.defineProperties(URL.prototype, { }) export default URL -export { URL, URLSearchParams, parseURL } +export { URL, URLSearchParams, parseURL, URLPattern } From 158b47b78790da9ace102aa0bea0c35428fd4814 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 11:54:27 -0400 Subject: [PATCH 0840/1178] chore(bin/install.sh): fix build llama.cpp on windows --- bin/install.sh | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/bin/install.sh b/bin/install.sh index 7a4a461bdd..38797e1c36 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -360,7 +360,7 @@ function _get_web_view2() { echo "# Downloading Webview2" - curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/1.0.2420.47 --output "$tmp/webview2.zip" + curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/1.0.2592.51 --output "$tmp/webview2.zip" cd "$tmp" || exit 1 unzip -q "$tmp/webview2.zip" mkdir -p "$BUILD_DIR/include" @@ -889,7 +889,6 @@ function _compile_llama { fi rm -f "$root/build/$(host_arch)-desktop/lib$d"/*.{so,la,dylib}* - return elif [ "$platform" == "iPhoneOS" ] || [ "$platform" == "iPhoneSimulator" ]; then # https://github.com/ggerganov/llama.cpp/discussions/4508 @@ -905,23 +904,30 @@ function _compile_llama { return fi elif [ "$platform" == "android" ]; then - local android_includes=$(android_arch_includes "$1") - local host_arch="$(host_arch)" - local cc="$(android_clang "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" - local cxx="$cc" - local clang_target="$(android_clang_target "$target")" - local ar="$(android_ar "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" - local cflags=("$clang_target" -std=c++2a -g -pedantic "${android_includes[*]}") - - AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make UNAME_M="$1" UNAME_P="$1" LLAMA_FAST=1 libllama.a - - if [ ! $? = 0 ]; then - die $? "not ok - Unable to compile libllama for '$platform'" + if [[ "$host" == "Win32" ]]; then + echo "WARN - Building libllama for Android on Windows is not yet supported" return + else + local android_includes=$(android_arch_includes "$1") + local host_arch="$(host_arch)" + local cc="$(android_clang "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" + local cxx="$cc" + local clang_target="$(android_clang_target "$target")" + local ar="$(android_ar "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" + local cflags=("$clang_target" -std=c++2a -g -pedantic "${android_includes[*]}") + + AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make UNAME_M="$1" UNAME_P="$1" LLAMA_FAST=1 libllama.a + + if [ ! $? = 0 ]; then + die $? "not ok - Unable to compile libllama for '$platform'" + return + fi fi fi - cp libllama.a ../lib + if [[ "$host" != "Win32" ]]; then + cp libllama.a ../lib + fi cd "$BUILD_DIR" || exit 1 rm -f "$root/build/$target-$platform/lib$d"/*.{so,la,dylib}* From f9731843b6b3151416d56535e029fcaab3544a93 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 11:55:06 -0400 Subject: [PATCH 0841/1178] refactor(app): various fixes for 'App' on windows --- src/app/app.cc | 48 +++++++++++++++++++++++++++++------------------- src/app/app.hh | 4 ++++ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index cb25b84b7f..96438f3c2d 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -47,7 +47,7 @@ static dispatch_queue_t queue = dispatch_queue_create( for (auto& window : self.app->windowManager.windows) { if (window != nullptr) { - window->bridge.emit("applicationurl", json.str()); + window->bridge.emit("applicationurl", json); } } } @@ -527,12 +527,12 @@ namespace SSC { ); // invalidate `window` pointer that potentially is leaked - if (window != nullptr && app->windowManager.getWindow(window) != window) { + if (window != nullptr && app->windowManager.getWindow(window->index).get() != window) { window = nullptr; } auto userConfig = window != nullptr - ? reinterpret_cast<Window*>(window)->options.userConfig + ? reinterpret_cast<Window*>(window)->bridge.userConfig : getUserConfig(); if (message == WM_COPYDATA) { @@ -560,7 +560,7 @@ namespace SSC { // XXX(@jwerle, @heapwolf): is this a correct for an `isAgent` predicate? auto isAgent = userConfig.count("tray_icon") != 0; - if (lParam == WM_LBUTTONDOWN) { + if (window != nullptr && lParam == WM_LBUTTONDOWN) { SetForegroundWindow(hWnd); if (isAgent) { POINT point; @@ -579,9 +579,9 @@ namespace SSC { PostMessage(hWnd, WM_NULL, 0, 0); // broadcast an event to all the windows that the tray icon was clicked - for (auto window : app->windowManager->windows) { + for (auto window : app->windowManager.windows) { if (window != nullptr) { - window->bridge->router.emit("tray", "true"); + window->bridge.emit("tray", JSON::Object {}); } } } @@ -641,27 +641,29 @@ namespace SSC { } case WM_CLOSE: { - if (!window->options.closable) { + if (!window || !window->options.closable) { break; } auto index = window->index; const JSON::Object json = JSON::Object::Entries { - {"data", window->index} + {"data", index} }; - for (auto window : app->windowManager->windows) { + for (auto window : app->windowManager.windows) { if (window != nullptr && window->index != index) { window->eval(getEmitToRenderProcessJavaScript("window-closed", json.str())); } } - app->windowManager->destroyWindow(index); + app->windowManager.destroyWindow(index); break; } case WM_HOTKEY: { - window->hotkey.onHotKeyBindingCallback((HotKeyBinding::ID) wParam); + if (window != nullptr) { + window->hotkey.onHotKeyBindingCallback((HotKeyBinding::ID) wParam); + } break; } @@ -671,9 +673,9 @@ namespace SSC { {"url", url} }; - for (auto window : app->windowManager->windows) { + for (auto window : app->windowManager.windows) { if (window != nullptr) { - window->bridge->router.emit("applicationurl", json.str()); + window->bridge.emit("applicationurl", json); } } break; @@ -681,7 +683,6 @@ namespace SSC { default: return DefWindowProc(hWnd, message, wParam, lParam); - break; } return 0; @@ -693,7 +694,11 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_ANDROID - App::App (JNIEnv* env, jobject self, SharedPointer<Core> core) + App::App ( + JNIEnv* env, + jobject self, + SharedPointer<Core> core + ) : userConfig(getUserConfig()), core(core), windowManager(core), @@ -710,8 +715,14 @@ namespace SSC { this->init(); } #else - App::App (int instanceId, SharedPointer<Core> core) - : userConfig(getUserConfig()), + App::App ( + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + HINSTANCE instanceId, + #else + int instanceId, + #endif + SharedPointer<Core> core + ) : userConfig(getUserConfig()), core(core), windowManager(core), serviceWorkerContainer(core) @@ -721,7 +732,7 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_WINDOWS - this->hInstance = reinterpret_cast<HINSTANCE>(instanceId); + this->hInstance = instanceId; // this fixes bad default quality DPI. SetProcessDPIAware(); @@ -760,7 +771,6 @@ namespace SSC { if (!RegisterClassEx(&wcex)) { alert("Application could not launch, possible missing resources."); } - #endif #if !SOCKET_RUNTIME_DESKTOP_EXTENSION diff --git a/src/app/app.hh b/src/app/app.hh index 7cd65a80b5..5d3e47e044 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -114,7 +114,11 @@ namespace SSC { * a `HINSTANCE` on Windows or an empty value (`0`) on other platforms. */ App ( + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + HINSTANCE instanceId = 0, + #else int instanceId = 0, + #endif SharedPointer<Core> core = SharedPointer<Core>(new Core()) ); #endif From efbf6b5c2a9cc121a764f40b87d6a5ad15b6d3b4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 11:55:38 -0400 Subject: [PATCH 0842/1178] refactor(cli): include static llama.lib in desktop build for linkage --- src/cli/cli.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index f2f7ba8473..e049751031 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -25,7 +25,7 @@ #pragma comment(lib, "userenv.lib") #pragma comment(lib, "uuid.lib") #pragma comment(lib, "libuv.lib") -#pragma comment(lib, "libllama.lib") +#pragma comment(lib, "llama.lib") #pragma comment(lib, "winspool.lib") #pragma comment(lib, "ws2_32.lib") #endif @@ -557,7 +557,7 @@ Vector<Path> handleBuildPhaseForCopyMappedFiles ( dst = fs::absolute(dst); if (!fs::exists(fs::status(src))) { - log("WARNING: [build.copy-map] entry '" + src.string() + "' does not exist"); + log("WARNING: [build.copy-map] entry '" + convertWStringToString(src.string()) + "' does not exist"); continue; } @@ -4876,7 +4876,7 @@ int main (const int argc, const char* argv[]) { } if (debugBuild) { - flags += " -D_DEBUG"; + flags += " -D_DEBUG -g"; } auto d = String(debugBuild ? "d" : "" ); @@ -5981,11 +5981,12 @@ int main (const int argc, const char* argv[]) { #if defined(_WIN32) auto d = String(debugBuild ? "d" : ""); auto static_uv = prefixFile("lib" + d + "\\" + platform.arch + "-desktop\\libuv.lib"); + auto static_llama = prefixFile("lib" + d + "\\" + platform.arch + "-desktop\\llama.lib"); auto static_runtime = trim(prefixFile("lib" + d + "\\" + platform.arch + "-desktop\\libsocket-runtime" + d + ".a")); - // TODO(@heapwolf): llama for win32 #else auto d = ""; auto static_uv = ""; + auto static_llama = ""; auto static_runtime = ""; #endif @@ -5996,6 +5997,7 @@ int main (const int argc, const char* argv[]) { << Env::get("CXX") << quote // win32 - quote the binary path << " " << static_runtime + << " " << static_llama << " " << static_uv << " " << objects.str() #if defined(_WIN32) From 22d16f92e27c6a86644414aade758f206e0d2bc8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 11:56:36 -0400 Subject: [PATCH 0843/1178] refactor(core): fix broken modules and process impl for windows --- src/core/modules/ai.cc | 7 ++++++- src/core/modules/fs.cc | 14 ++++++-------- src/core/modules/notifications.cc | 2 ++ src/core/modules/os.cc | 14 ++++++-------- src/core/modules/platform.cc | 12 ++++++------ src/core/process.hh | 4 ++++ src/core/process/win.cc | 27 ++++++++++++++++++++++++--- src/core/resource.cc | 16 ++++++++-------- 8 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index c8889a446f..d802b8031a 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -537,8 +537,13 @@ namespace SSC { } for (int i = 0; i < input_size; i += this->params.n_batch) { - int n_eval = std::min(input_size - i, this->params.n_batch); + if (this->stopped) return; + + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + #undef min + #endif + int n_eval = std::min(input_size - i, this->params.n_batch); if (llama_decode(this->guidance, llama_batch_get_one(input_buf + i, n_eval, n_past_guidance, 0))) { LOG("failed to eval\n"); return; diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index 35fe6f6c38..e9b6e22e9c 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -2051,14 +2051,12 @@ namespace SSC { const String& seq, const CoreModule::Callback& callback ) const { - this->core->dispatchEventLoop([=, this]() { - static const auto data = JSON::Object(FS_CONSTANTS); - static const auto json = JSON::Object::Entries { - {"source", "fs.constants"}, - {"data", data} - }; + static const auto data = JSON::Object(FS_CONSTANTS); + static const auto json = JSON::Object::Entries { + {"source", "fs.constants"}, + {"data", data} + }; - callback(seq, json, Post {}); - }); + callback(seq, json, Post {}); } } diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index adbb4cec8b..910bce2031 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -380,6 +380,8 @@ namespace SSC { callback(ShowResult { "", options.id }); }); }]; + + return true; #endif return false; } diff --git a/src/core/modules/os.cc b/src/core/modules/os.cc index 5fc47f0592..b1c3d90f22 100644 --- a/src/core/modules/os.cc +++ b/src/core/modules/os.cc @@ -659,14 +659,12 @@ namespace SSC { const String& seq, const CoreModule::Callback& callback ) const { - this->core->dispatchEventLoop([=, this]() { - static const auto data = JSON::Object(OS_CONSTANTS); - static const auto json = JSON::Object::Entries { - {"source", "os.constants"}, - {"data", data} - }; + static const auto data = JSON::Object(OS_CONSTANTS); + static const auto json = JSON::Object::Entries { + {"source", "os.constants"}, + {"data", data} + }; - callback(seq, json, Post {}); - }); + callback(seq, json, Post {}); } } diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index 2abbf6e26c..e298210fb9 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -20,14 +20,14 @@ namespace SSC { this->wasFirstDOMContentLoadedEventDispatched = true; } + }); - const auto json = JSON::Object::Entries { - {"source", "platform.event"}, - {"data", JSON::Object {}} - }; + const auto json = JSON::Object::Entries { + {"source", "platform.event"}, + {"data", JSON::Object {}} + }; - callback(seq, json, Post{}); - }); + callback(seq, json, Post{}); } void CorePlatform::notify ( diff --git a/src/core/process.hh b/src/core/process.hh index 4febb9478e..f7f925bf42 100644 --- a/src/core/process.hh +++ b/src/core/process.hh @@ -3,6 +3,10 @@ #include "../platform/platform.hh" +#if SOCKET_RUNTIME_PLATFORM_WINDOWS +#include <tlhelp32.h> +#endif + #if !SOCKET_RUNTIME_PLATFORM_IOS #include <iostream> #endif diff --git a/src/core/process/win.cc b/src/core/process/win.cc index f4d026c8fe..ec8ea5c454 100644 --- a/src/core/process/win.cc +++ b/src/core/process/win.cc @@ -1,10 +1,10 @@ #include <cstring> #include <iostream> #include <stdexcept> -#include <tlhelp32.h> #include <limits.h> #include "../process.hh" +#include "../env.hh" namespace SSC { @@ -32,6 +32,27 @@ Process::Process( this->path = path; } +Process::Process ( + const String &command, + const String &argv, + const Vector<String> &env, + const String &path, + MessageCallback readStdout, + MessageCallback readStderr, + MessageCallback onExit, + bool openStdin, + const ProcessConfig &config + ) noexcept + : openStdin(true), + readStdout(std::move(readStdout)), + readStderr(std::move(readStderr)), + onExit(std::move(onExit)), + env(env), + command(command), + argv(argv), + path(path) +{} + int Process::wait () { do { msleep(Process::PROCESS_WAIT_TIMEOUT); @@ -69,7 +90,7 @@ class Handle { }; //Based on the discussion thread: https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxq1wsj -std::mutex create_processMutex; +std::recursive_mutex create_processMutex; //Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx. Process::PID Process::open (const String &command, const String &path) noexcept { @@ -283,7 +304,7 @@ void Process::read() noexcept { if (stderrFD) { stderrThread = Thread([this]() { DWORD n; - UniquePointer<char*> buffer(new char[config.bufferSize]); + auto buffer = std::make_unique<char[]>(config.bufferSize); for (;;) { BOOL bSuccess = ReadFile(*stderrFD, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.bufferSize), &n, nullptr); diff --git a/src/core/resource.cc b/src/core/resource.cc index b55a0decd7..5a364e9428 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -467,7 +467,7 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_WINDOWS const Path FileResource::getMicrosoftEdgeRuntimePath () { // this is something like "C:\\Users\\jwerle\\AppData\\Local\\Microsoft\\Edge SxS\\Application\\123.0.2386.0" - static const auto EDGE_RUNTIME_DIRECTORY = convertStringToWString(trim(Env::get("SOCKET_EDGE_RUNTIME_DIRECTORY"))); + static const auto EDGE_RUNTIME_DIRECTORY = trim(Env::get("SOCKET_EDGE_RUNTIME_DIRECTORY")); return Path( EDGE_RUNTIME_DIRECTORY.size() > 0 && fs::exists(EDGE_RUNTIME_DIRECTORY) @@ -699,12 +699,12 @@ namespace SSC { return mimeType; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS LPWSTR mimeData; - const auto bytes = this->read(true) - const auto size = this->size(true); + const auto bytes = this->read(); + const auto size = this->size(); const auto result = FindMimeFromData( nullptr, // (LPBC) ignored (unsused) convertStringToWString(path).c_str(), // filename - reinterpret_cast<const void*>(bytes), // detected buffer data + const_cast<void*>(reinterpret_cast<const void*>(bytes)), // detected buffer data (DWORD) size, // detected buffer size nullptr, // mime suggestion 0, // flags (unsused) @@ -757,12 +757,12 @@ namespace SSC { #elif SOCKET_RUNTIME_PLATFORM_WINDOWS LARGE_INTEGER fileSize; auto handle = CreateFile( - this->path.c_str(), + convertWStringToString(this->path.string()).c_str(), GENERIC_READ, // access FILE_SHARE_READ, // share mode nullptr, // security attribues (unused) OPEN_EXISTING, // creation disposition - nullptr, // flags and attribues (unused) + 0, // flags and attribues (unused) nullptr // templte file (unused) ); @@ -849,12 +849,12 @@ namespace SSC { span->end(); #elif SOCKET_RUNTIME_PLATFORM_WINDOWS auto handle = CreateFile( - this->path.c_str(), + convertWStringToString(this->path.string()).c_str(), GENERIC_READ, // access FILE_SHARE_READ, // share mode nullptr, // security attribues (unused) OPEN_EXISTING, // creation disposition - nullptr, // flags and attribues (unused) + 0, // flags and attribues (unused) nullptr // templte file (unused) ); From 80028434857125b81212d8b657f3b625157b6a19 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 11:59:48 -0400 Subject: [PATCH 0844/1178] refactor(ipc): improve IPC on windows --- src/ipc/bridge.cc | 125 +++++++++++++++++++++++++++++-------- src/ipc/navigator.cc | 14 ++--- src/ipc/preload.cc | 8 ++- src/ipc/router.cc | 8 ++- src/ipc/scheme_handlers.cc | 65 +++++++------------ src/ipc/scheme_handlers.hh | 8 +-- 6 files changed, 147 insertions(+), 81 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index e4b34646d3..c9c3a986c8 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -16,8 +16,8 @@ namespace SSC::IPC { // a proxy to a canonical URL for a module so `{{protocol}}:{{specifier}}` and // `{{protocol}}://{{bundle_identifier}}/socket/{{pathname}}` resolve to the exact // same module - static constexpr auto ESM_IMPORT_PROXY_TEMPLATE = -R"S(/** + static constexpr auto ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT = R"S( +/** * This module exists to provide a proxy to a canonical URL for a module * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` * resolve to the exact same module instance. @@ -25,7 +25,18 @@ R"S(/** */ import module from '{{url}}' export * from '{{url}}' -export default module)S"; +export default module +)S"; + + static constexpr auto ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT = R"S( +/** + * This module exists to provide a proxy to a canonical URL for a module + * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` + * resolve to the exact same module instance. + * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} + */ +export * from '{{url}}' +)S"; static const Vector<String> allowedNodeCoreModules = { "async_hooks", @@ -171,6 +182,7 @@ export default module)S"; schemeHandlers(this) { this->id = rand64(); + this->client.id = this->id; #if SOCKET_RUNTIME_PLATFORM_ANDROID this->isAndroidEmulator = App::sharedApplication()->isAndroidEmulator; #endif @@ -389,7 +401,7 @@ export default module)S"; auto message = Message(request->url(), true); if (request->method == "OPTIONS") { - auto response = SchemeHandlers::Response(request, 200); + auto response = SchemeHandlers::Response(request, 204); return callback(response); } @@ -618,7 +630,9 @@ export default module)S"; // handle HEAD and GET requests for a file resource if (resourcePath.size() > 0) { - contentLocation = replace(resourcePath, applicationResources, ""); + if (resourcePath.starts_with(applicationResources)) { + contentLocation = resourcePath.substr(applicationResources.size() -1, resourcePath.size()); + } auto resource = FileResource(resourcePath); @@ -630,7 +644,7 @@ export default module)S"; } if (request->method == "OPTIONS") { - response.writeHead(200); + response.writeHead(204); } if (request->method == "HEAD") { @@ -713,8 +727,11 @@ export default module)S"; // module or stdlib import/fetch `socket:<module>/<path>` which will just // proxy an import into a normal resource request above if (request->hostname.size() == 0) { - const auto specifier = request->pathname.substr(1); auto pathname = request->pathname; + if (pathname.ends_with("/")) { + pathname = pathname.substr(0, pathname.size() - 1); + } + const auto specifier = pathname.substr(1); if (!pathname.ends_with(".js")) { pathname += ".js"; @@ -737,14 +754,19 @@ export default module)S"; (request->query.size() > 0 ? "?" + request->query : "") ); - const auto moduleImportProxy = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map { - {"url", url}, - {"commit", VERSION_HASH_STRING}, - {"protocol", "socket"}, - {"pathname", pathname}, - {"specifier", specifier}, - {"bundle_identifier", bundleIdentifier} - }); + const auto moduleImportProxy = tmpl( + String(resource.read()).find("export default") != String::npos + ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT + : ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT, + Map { + {"url", url}, + {"commit", VERSION_HASH_STRING}, + {"protocol", "socket"}, + {"pathname", pathname}, + {"specifier", specifier}, + {"bundle_identifier", bundleIdentifier} + } + ); const auto contentType = resource.mimeType(); @@ -760,9 +782,9 @@ export default module)S"; response.writeHead(200); response.write(moduleImportProxy); + return callback(response); } - - return callback(response); + response.setHeader("content-type", "text/javascript"); } response.writeHead(404); @@ -838,14 +860,19 @@ export default module)S"; if (resource.exists()) { const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; - const auto moduleImportProxy = tmpl(ESM_IMPORT_PROXY_TEMPLATE, Map { - {"url", url}, - {"commit", VERSION_HASH_STRING}, - {"protocol", "node"}, - {"pathname", pathname}, - {"specifier", pathname.substr(1)}, - {"bundle_identifier", bundleIdentifier} - }); + const auto moduleImportProxy = tmpl( + String(resource.read()).find("export default") != String::npos + ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT + : ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT, + Map { + {"url", url}, + {"commit", VERSION_HASH_STRING}, + {"protocol", "node"}, + {"pathname", pathname}, + {"specifier", pathname.substr(1)}, + {"bundle_identifier", bundleIdentifier} + } + ); const auto contentType = resource.mimeType(); @@ -957,6 +984,9 @@ export default module)S"; auto pathname = request->pathname; if (request->scheme == "npm") { + if (hostname.size() > 0) { + pathname = "/" + hostname; + } hostname = this->userConfig["meta_bundle_identifier"]; } @@ -1010,6 +1040,51 @@ export default module)S"; callback(response); }); } + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + static const int MAX_ALLOWED_SCHEME_ORIGINS = 64; + static const int MAX_CUSTOM_SCHEME_REGISTRATIONS = 64; + Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions4> options; + Vector<SharedPointer<WString>> schemes; + Vector<SharedPointer<WString>> origins; + + if (this->schemeHandlers.configuration.webview.As(&options) == S_OK) { + ICoreWebView2CustomSchemeRegistration* registrations[MAX_CUSTOM_SCHEME_REGISTRATIONS] = {}; + const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {}; + + int registrationsCount = 0; + int allowedOriginsCount = 0; + + allowedOrigins[allowedOriginsCount++] = L"about://*"; + allowedOrigins[allowedOriginsCount++] = L"https://*"; + + for (const auto& entry : this->schemeHandlers.handlers) { + const auto origin = entry.first + "://*"; + origins.push_back(std::make_shared<WString>(convertStringToWString(origin))); + allowedOrigins[allowedOriginsCount++] = origins.back()->c_str(); + } + + // store registratino refs here + Set<Microsoft::WRL::ComPtr<CoreWebView2CustomSchemeRegistration>> registrationsSet; + + for (const auto& entry : this->schemeHandlers.handlers) { + schemes.push_back(std::make_shared<WString>(convertStringToWString(entry.first))); + auto registration = Microsoft::WRL::Make<CoreWebView2CustomSchemeRegistration>( + schemes.back()->c_str() + ); + + registration->SetAllowedOrigins(allowedOriginsCount, allowedOrigins); + registration->put_HasAuthorityComponent(true); + registration->put_TreatAsSecure(true); + registrations[registrationsCount++] = registration.Get(); + registrationsSet.insert(registration); + } + + options->SetCustomSchemeRegistrations( + registrationsCount, + static_cast<ICoreWebView2CustomSchemeRegistration**>(registrations) + ); + } +#endif } void Bridge::configureNavigatorMounts () { diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index 905c0a363c..11df655a35 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -149,7 +149,7 @@ namespace SSC::IPC { if (resolution.pathname.size() > 0) { const auto filename = Path(entry.first) / resolution.pathname.substr(1); resolution.type = Navigator::Location::Resolution::Type::Mount; - resolution.mount.filename = filename; + resolution.mount.filename = filename.string(); return resolution; } } @@ -252,27 +252,27 @@ namespace SSC::IPC { this ); #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - EventRegistrationToken tokenNavigation; + EventRegistrationToken token; webview->add_NavigationStarting( Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>( - [this, &](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs *event) { + [=, this](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) { PWSTR source; PWSTR uri; - event->get_Uri(&uri); + args->get_Uri(&uri); webview->get_Source(&source); if (uri == nullptr || source == nullptr) { if (uri) CoTaskMemFree(uri); if (source) CoTaskMemFree(source); - return E_POIINTER; + return E_POINTER; } const auto requestedURL = convertWStringToString(uri); const auto currentURL = convertWStringToString(source); if (!this->handleNavigationRequest(currentURL, requestedURL)) { - event->put_Cancel(true); + args->put_Cancel(true); } CoTaskMemFree(uri); @@ -280,7 +280,7 @@ namespace SSC::IPC { return S_OK; } ).Get(), - &tokenNavigation + &token ); #endif } diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index b8e2d1a026..36ca3eba4a 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -79,7 +79,9 @@ namespace SSC::IPC { } if (!this->headers.has("referrer-policy")) { - this->headers.set("referrer-policy", this->metadata["referrer"]); + if (this->metadata["referrer"].size() > 0) { + this->headers.set("referrer-policy", this->metadata["referrer"]); + } } } } @@ -111,6 +113,10 @@ namespace SSC::IPC { // 1. compile metadata if `options.features.useHTMLMarkup == true`, otherwise skip if (this->options.features.useHTMLMarkup) { for (const auto &entry : this->metadata) { + if (entry.second.size() == 0) { + continue; + } + buffers.push_back(tmpl( R"HTML(<meta name="{{name}}" content="{{content}}">)HTML", Map { diff --git a/src/ipc/router.cc b/src/ipc/router.cc index 93bfa0a018..94e6433e33 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -158,7 +158,9 @@ namespace SSC::IPC { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { - callback(result); + this->bridge->dispatch([=, this]() { + callback(result); + }); } }); }); @@ -168,7 +170,9 @@ namespace SSC::IPC { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { - callback(result); + this->bridge->dispatch([=, this]() { + callback(result); + }); } }); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 64c46dc0e3..a4d749dce5 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -448,41 +448,6 @@ namespace SSC::IPC { nullptr ); } - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - static const int MAX_ALLOWED_SCHEME_ORIGINS = 64; - static const int MAX_CUSTOM_SCHEME_REGISTRATIONS = 64; - int registrationsCount = 0; - - auto registration = Microsoft::WRL::Make<CoreWebView2CustomSchemeRegistration>( - convertStringToWString(scheme).c_str() - ); - - Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions4> options; - ICoreWebView2CustomSchemeRegistration* registrations[MAX_CUSTOM_SCHEME_REGISTRATIONS] = {}; - const WCHAR* allowedOrigins[MAX_ALLOWED_SCHEME_ORIGINS] = {}; - int i = 0; - - for (const auto& entry : this->handlers) { - allowedOrigins[i++] = convertStringToWString(entry.first + "://*").c_str(); - } - - registration->put_HasAuthorityComponent(true); - registration->SetAllowedOrigins(this->handlers.size(), allowedOrigins); - - this->coreWebView2CustomSchemeRegistrations.insert(registration); - - if (this->configuration.webview->As(&options) != S_OK) { - return false; - } - - for (const auto& registration : this->coreWebView2CustomSchemeRegistrations) { - registrations[registrationsCount++] = registration.Get(); - } - - options->SetCustomSchemeRegistrations( - registrationsCount, - static_cast<ICoreWebView2CustomSchemeRegistration**>(registrations) - ); #endif this->handlers.insert_or_assign(scheme, handler); @@ -599,6 +564,11 @@ namespace SSC::IPC { PlatformRequest platformRequest #endif ) { + const auto userConfig = handlers->bridge->userConfig; + const auto bundleIdentifier = userConfig.contains("meta_bundle_identifier") + ? userConfig.at("meta_bundle_identifier") + : ""; + #if SOCKET_RUNTIME_PLATFORM_APPLE this->absoluteURL = platformRequest.request.URL.absoluteString.UTF8String; #elif SOCKET_RUNTIME_PLATFORM_LINUX @@ -607,6 +577,15 @@ namespace SSC::IPC { LPWSTR requestURI; platformRequest->get_Uri(&requestURI); this->absoluteURL = convertWStringToString(requestURI); + if ( + this->absoluteURL.starts_with("socket://") && + !this->absoluteURL.starts_with("socket://" + bundleIdentifier) + ) { + this->absoluteURL = String("socket://") + this->absoluteURL.substr(8); + if (this->absoluteURL.ends_with("/")) { + this->absoluteURL = this->absoluteURL.substr(0, this->absoluteURL.size() - 1); + } + } CoTaskMemFree(requestURI); #elif SOCKET_RUNTIME_PLATFORM_ANDROID auto app = App::sharedApplication(); @@ -620,11 +599,7 @@ namespace SSC::IPC { )).str(); #endif - const auto userConfig = handlers->bridge->userConfig; const auto url = URL::Components::parse(this->absoluteURL); - const auto bundleIdentifier = userConfig.contains("meta_bundle_identifier") - ? userConfig.at("meta_bundle_identifier") - : ""; this->request = std::make_shared<Request>( handlers, @@ -1011,9 +986,10 @@ namespace SSC::IPC { this->setHeader("cache-control", "no-cache"); } + this->setHeader("connection", "keep-alive"); this->setHeader("access-control-allow-origin", "*"); - this->setHeader("access-control-allow-methods", "*"); this->setHeader("access-control-allow-headers", "*"); + this->setHeader("access-control-allow-methods", "*"); for (const auto& entry : defaultHeaders) { const auto parts = split(trim(entry), ':'); @@ -1176,13 +1152,15 @@ namespace SSC::IPC { : "" ); this->platformResponseStream = SHCreateMemStream(nullptr, 0); - return S_OK == this->request->env->CreateWebResourceResponse( + const auto result = this->request->env->CreateWebResourceResponse( this->platformResponseStream, this->statusCode, convertStringToWString(statusText).c_str(), convertStringToWString(this->headers.str()).c_str(), &this->platformResponse ); + + return result == S_OK; #elif SOCKET_RUNTIME_PLATFORM_ANDROID const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); @@ -1256,7 +1234,10 @@ namespace SSC::IPC { // set 'content-length' header if response was not created this->setHeader("content-length", size); if (!this->writeHead()) { - debug("IPC::SchemeHandlers::Response: Failed to write head"); + debug( + "IPC::SchemeHandlers::Response: Failed to write head for %s", + this->request->str().c_str() + ); return false; } } diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index a5f5d71d4a..dee8f81b94 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -259,7 +259,11 @@ namespace SSC::IPC { using RequestMap = std::map<uint64_t, SharedPointer<Request>>; struct Configuration { + #if SOCKET_RUNTIME_PLATFORM_WINDOWS + WebViewSettings webview; + #else WebViewSettings* webview; + #endif }; Configuration configuration; @@ -269,10 +273,6 @@ namespace SSC::IPC { Bridge* bridge = nullptr; RequestMap activeRequests; - #if SOCKET_RUNTIME_PLATFORM_WINDOWS - Set<Microsoft::WRL::ComPtr<CoreWebView2CustomSchemeRegistration>> coreWebView2CustomSchemeRegistrations; - #endif - SchemeHandlers (Bridge* bridge); ~SchemeHandlers (); SchemeHandlers (const SchemeHandlers&) = delete; From 6a0a10742e0299a6fa9e5a0336d42ffba51c4bca Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 12:00:02 -0400 Subject: [PATCH 0845/1178] fix(platform): fix typos --- src/platform/platform.hh | 2 +- src/platform/string.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/platform.hh b/src/platform/platform.hh index b70aaf6519..2f3d6998ba 100644 --- a/src/platform/platform.hh +++ b/src/platform/platform.hh @@ -99,7 +99,7 @@ #pragma comment(lib, "Gdi32.lib") #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "libuv.lib") -#pragma comment(lib, "libllama.lib") +#pragma comment(lib, "llama.lib") #pragma comment(lib, "psapi.lib") #pragma comment(lib, "shell32.lib") #pragma comment(lib, "Shlwapi.lib") diff --git a/src/platform/string.cc b/src/platform/string.cc index 25b4e141eb..266f6c2689 100644 --- a/src/platform/string.cc +++ b/src/platform/string.cc @@ -215,7 +215,7 @@ namespace SSC { << " in " << source << ": " << (LPTSTR) errorMessage; - LocalFree(lpMsgBuf); + LocalFree(errorMessage); return message.str(); } From ed043d097ba72e28219ea42dd5938a0b110e1d35 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 12:00:51 -0400 Subject: [PATCH 0846/1178] fix(window): fix Windows winding impl --- src/window/win.cc | 275 +++++++++++++++++++++++++--------------------- 1 file changed, 148 insertions(+), 127 deletions(-) diff --git a/src/window/win.cc b/src/window/win.cc index 0fef2252ee..a8930773d2 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1,25 +1,17 @@ #include "../app/app.hh" #include "window.hh" - -#ifndef CHECK_FAILURE -#define CHECK_FAILURE(...) -#endif +#include <winuser.h> using namespace Microsoft::WRL; -namespace SSC { - static inline void alert (const WString &ws) { - MessageBoxA(nullptr, convertWStringToString(ws).c_str(), _TEXT("Alert"), MB_OK | MB_ICONSTOP); - } - - static inline void alert (const String &s) { - MessageBoxA(nullptr, s.c_str(), _TEXT("Alert"), MB_OK | MB_ICONSTOP); - } - - static inline void alert (const char* s) { - MessageBoxA(nullptr, s, _TEXT("Alert"), MB_OK | MB_ICONSTOP); - } +extern BOOL ChangeWindowMessageFilterEx ( + HWND hwnd, + UINT message, + DWORD action, + void* unused +); +namespace SSC { class CDataObject : public IDataObject { public: HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject); @@ -422,14 +414,14 @@ namespace SSC { globalMemory = GlobalAlloc(GHND, sizeof(DROPFILES) + len + 1); if (!globalMemory) { - return nullptr; + return E_POINTER; } dropFiles = (DROPFILES*) GlobalLock(globalMemory); if (!dropFiles) { GlobalFree(globalMemory); - return nullptr; + return E_POINTER; } dropFiles->fNC = TRUE; @@ -616,28 +608,18 @@ namespace SSC { // this may be an "empty" path if not available static const auto edgeRuntimePath = FileResource::getMicrosoftEdgeRuntimePath(); static auto app = App::sharedApplication(); + app->isReady = false; if (!edgeRuntimePath.empty()) { const auto string = convertWStringToString(edgeRuntimePath.string()); - const auto value = replace(string, "\\\\", "\\\\") + const auto value = replace(string, "\\\\", "\\\\"); // inject the `EDGE_RUNTIME_DIRECTORY` environment variable directly into // the userConfig so it is available as an env var in the webview runtime this->bridge.userConfig["env_EDGE_RUNTIME_DIRECTORY"] = value; - this->options.userConfig["env_EDGE_RUNTIME_DIRECTORY"] = value; debug("Microsoft Edge Runtime directory set to '%ls'", edgeRuntimePath.c_str()); } auto userConfig = this->bridge.userConfig; - auto webviewEnvironmentOptions = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>(); - webviewEnvironmentOptions->put_AdditionalBrowserArguments(L"--enable-features=msWebView2EnableDraggableRegions"); - - Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions4> webviewEnvironmentOptions4; - if (webviewEnvironmentOptions.As(&webviewEnvironmentOptions4) != S_OK) { - throw std::runtime_error( - "Unable to resolve 'ICoreWebView2EnvironmentOptions4'" - ); - } - // only the root window can handle "agent" tasks const bool isAgent = ( userConfig["application_agent"] == "true" && @@ -701,6 +683,9 @@ namespace SSC { ); } + auto webviewEnvironmentOptions = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>(); + webviewEnvironmentOptions->put_AdditionalBrowserArguments(L"--enable-features=msWebView2EnableDraggableRegions"); + this->drop = std::make_shared<DragDrop>(this); this->bridge.navigateFunction = [this] (const auto url) { this->navigate(url); @@ -711,7 +696,10 @@ namespace SSC { }; this->bridge.client.preload = IPC::Preload::compile({ - .client = this->bridge.client, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, .index = options.index, .userScript = options.userScript }); @@ -734,9 +722,14 @@ namespace SSC { } // in theory these allow you to do drop files in elevated mode - ChangeWindowMessageFilterEx(this->window, WM_DROPFILES, MSGFLT_ALLOW, nullptr); - ChangeWindowMessageFilterEx(this->window, WM_COPYDATA, MSGFLT_ALLOW, nullptr); - ChangeWindowMessageFilterEx(this->window, 0x0049, MSGFLT_ALLOW, nullptr); + // FIXME(@jwerle): `ChangeWindowMessageFilter` will be deprecated and potentially removed + // but `ChangeWindowMessageFilterEx` doesn't seem to be available for linkage + ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD); + ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD); + ChangeWindowMessageFilter(0x0049, MSGFLT_ADD); + //ChangeWindowMessageFilterEx(this->window, WM_DROPFILES, /* MSGFLT_ALLOW */ 1, nullptr); + //ChangeWindowMessageFilterEx(this->window, WM_COPYDATA, /* MSGFLT_ALLOW*/ 1, nullptr); + //ChangeWindowMessageFilterEx(this->window, 0x0049, /* MSGFLT_ALLOW */ 1, nullptr); UpdateWindow(this->window); ShowWindow(this->window, isAgent ? SW_HIDE : SW_SHOWNORMAL); @@ -746,7 +739,6 @@ namespace SSC { this->hotkey.init(); this->bridge.init(); - this->bridge.configureWebView(this->webview); static const auto APPDATA = Path(convertStringToWString(Env::get("APPDATA"))); @@ -756,6 +748,8 @@ namespace SSC { ); } + // XXX(@jwerle): is this path correct? maybe we should use the bundle identifier here + // instead of the executable filename static const auto edgeRuntimeUserDataPath = ({ wchar_t modulefile[MAX_PATH]; GetModuleFileNameW(nullptr, modulefile, MAX_PATH); @@ -769,19 +763,19 @@ namespace SSC { }); CreateCoreWebView2EnvironmentWithOptions( - edgeRuntimePath.empty() ? nullptr : edgeRuntimePath.string(), - edgeRuntimeUserDataPath, + edgeRuntimePath.empty() ? nullptr : convertStringToWString(edgeRuntimePath.string()).c_str(), + convertStringToWString(edgeRuntimeUserDataPath.string()).c_str(), webviewEnvironmentOptions.Get(), Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>([=, this]( HRESULT result, - ICoreWebView2Environment* webviewEnvironment - ) -> HRESULT { + ICoreWebView2Environment* env + ) mutable { return env->CreateCoreWebView2Controller( this->window, Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>([=, this]( HRESULT result, ICoreWebView2Controller* controller - ) -> HRESULT { + ) mutable { const auto bundleIdentifier = userConfig["meta_bundle_identifier"]; if (result != S_OK) { @@ -801,38 +795,45 @@ namespace SSC { this->controller->put_Bounds(bounds); this->controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); this->controller->AddRef(); + this->bridge.configureWebView(this->webview); } while (0); // configure the webview settings do { ICoreWebView2Settings* settings = nullptr; + ICoreWebView2Settings2* settings2 = nullptr; ICoreWebView2Settings3* settings3 = nullptr; ICoreWebView2Settings6* settings6 = nullptr; + ICoreWebView2Settings9* settings9 = nullptr; + + const auto wantsDebugMode = this->options.debug || isDebugEnabled(); this->webview->get_Settings(&settings); + settings2 = reinterpret_cast<ICoreWebView2Settings2*>(settings); settings3 = reinterpret_cast<ICoreWebView2Settings3*>(settings); settings6 = reinterpret_cast<ICoreWebView2Settings6*>(settings); + settings9 = reinterpret_cast<ICoreWebView2Settings9*>(settings); settings->put_IsScriptEnabled(true); settings->put_IsStatusBarEnabled(false); settings->put_IsWebMessageEnabled(true); + settings->put_AreDevToolsEnabled(wantsDebugMode); settings->put_AreHostObjectsAllowed(true); settings->put_IsZoomControlEnabled(false); settings->put_IsBuiltInErrorPageEnabled(false); settings->put_AreDefaultContextMenusEnabled(true); settings->put_AreDefaultScriptDialogsEnabled(true); + // TODO(@jwerle): set user agent for runtime + // settings2->put_UserAgent(); + + settings3->put_AreBrowserAcceleratorKeysEnabled(wantsDebugMode); + settings6->put_IsPinchZoomEnabled(false); settings6->put_IsSwipeNavigationEnabled(false); - if (this->options.debug || isDebugEnabled()) { - settings->put_AreDevToolsEnabled(true); - settings3->put_AreBrowserAcceleratorKeysEnabled(true); - } else { - settings->put_AreDevToolsEnabled(false); - settings3->put_AreBrowserAcceleratorKeysEnabled(false); - } + settings9->put_IsNonClientRegionSupportEnabled(true); } while (0); // enumerate all child windows to re-register drag/drop @@ -850,7 +851,7 @@ namespace SSC { if (text.find("Chrome") != String::npos) { RevokeDragDrop(handle); - RegisterDragDrop(handle, window->drop); + RegisterDragDrop(handle, window->drop.get()); window->drop->childWindow = handle; } } @@ -870,15 +871,12 @@ namespace SSC { if (webview22 != nullptr) { this->bridge.userConfig["env_COREWEBVIEW2_22_AVAILABLE"] = "true"; - this->options.userConfig["env_COREWEBVIEW2_22_AVAILABLE"] = "true"; webview22->AddWebResourceRequestedFilterWithRequestSourceKinds( L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL, COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL ); - - debug("Configured CoreWebView2 (ICoreWebView2_22) request filter with all request source kinds"); } webview3->SetVirtualHostNameToFolderMapping( @@ -893,9 +891,9 @@ namespace SSC { EventRegistrationToken token; this->webview->add_PermissionRequested( Microsoft::WRL::Callback<ICoreWebView2PermissionRequestedEventHandler>([=, this]( - ICoreWebView2 *webview, + ICoreWebView2 *_, ICoreWebView2PermissionRequestedEventArgs *args - ) -> HRESULT { + ) mutable { COREWEBVIEW2_PERMISSION_KIND kind; args->get_PermissionKind(&kind); @@ -994,7 +992,7 @@ namespace SSC { do { EventRegistrationToken token; this->webview->add_WebMessageReceived( - Microsoft::WRL::Callback<ICoreWebView2WebMessageReceivedEventHandler>([=]( + Microsoft::WRL::Callback<ICoreWebView2WebMessageReceivedEventHandler>([=, this]( ICoreWebView2* webview, ICoreWebView2WebMessageReceivedEventArgs* args ) -> HRESULT { @@ -1012,13 +1010,13 @@ namespace SSC { const auto message = IPC::Message(convertWStringToString(string)); CoTaskMemFree(string); - webview2->get_Environment(&environment); - environment->QueryInterface(IID_PPV_ARGS(&environment12)); this->webview->QueryInterface(IID_PPV_ARGS(&webview2)); this->webview->QueryInterface(IID_PPV_ARGS(&webview18)); + webview2->get_Environment(&environment); + environment->QueryInterface(IID_PPV_ARGS(&environment12)); - if (!this->bridge.route(message, nullptr, 0)) { - onMessage(message); + if (!this->bridge.route(message.str(), nullptr, 0)) { + onMessage(message.str()); } return S_OK; @@ -1039,19 +1037,27 @@ namespace SSC { ICoreWebView2Environment* env = nullptr; ICoreWebView2Deferral* deferral = nullptr; - if (auto result = args->GetDeferral(&deferral)) { - return result; - } - // get platform request and environment from event args do { ICoreWebView2_2* webview2 = nullptr; - webview->QueryInterface(IID_PPV_ARGS(&webview2)); - webview2->get_Environment(&env); - args->get_Request(&platformRequest); + if (webview->QueryInterface(IID_PPV_ARGS(&webview2)) != S_OK) { + return E_FAIL; + } + + if (webview2->get_Environment(&env) != S_OK) { + return E_FAIL; + } + + if (args->get_Request(&platformRequest) != S_OK) { + return E_FAIL; + } } while (0); - auto request = IPC::SchemeHandlers::Request::Builder(&this->bridge.schemeHandlers, platformRequest); + auto request = IPC::SchemeHandlers::Request::Builder( + &this->bridge.schemeHandlers, + platformRequest, + env + ); // get and set HTTP method do { @@ -1063,26 +1069,58 @@ namespace SSC { // iterator all HTTP headers and set them do { ICoreWebView2HttpRequestHeaders* headers = nullptr; - platformRequest->get_Headers(&headers); ComPtr<ICoreWebView2HttpHeadersCollectionIterator> iterator; - BOOL hasCurrent = FALSE; - CHECK_FAILURE(headers->GetIterator(&iterator)); - while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { - LPWSTR name; - LPWSTR value; - if (iterator->GetCurrentHeader(&name, &value) == S_OK) { + BOOL hasCurrent = false; + BOOL hasNext = false; + + if (platformRequest->get_Headers(&headers) == S_OK && headers->GetIterator(&iterator) == S_OK) { + while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { + LPWSTR name; + LPWSTR value; + + if (iterator->GetCurrentHeader(&name, &value) != S_OK) { + break; + } + request.setHeader(convertWStringToString(name), convertWStringToString(value)); - } - } + if (iterator->MoveNext(&hasNext) != S_OK || !hasNext) { + break; + } + } + } } while (0); - const auto handled = this->bridge.schemeHandlers.handleRequest(request.build(), [=](const auto& response) mutable { + // get request body + if (request.request->method == "POST" || request.request->method == "PUT" || request.request->method == "PATCH") { + IStream* content = nullptr; + if (platformRequest->get_Content(&content) == S_OK && content != nullptr) { + STATSTG stat; + content->Stat(&stat, 0); + size_t size = stat.cbSize.QuadPart; + if (size > 0) { + auto buffer = std::make_shared<char[]>(size); + if (content->Read(buffer.get(), size, nullptr) == S_OK) { + request.setBody(IPC::SchemeHandlers::Body { + size, + std::move(buffer) + }); + } + } + } + } + + auto req = request.build(); + if (args->GetDeferral(&deferral) != S_OK) { + return E_FAIL; + } + + const auto handled = this->bridge.schemeHandlers.handleRequest(req, [=](const auto& response) mutable { args->put_Response(response.platformResponse); deferral->Complete(); }); if (!handled) { - auto response = IPC::SchemeHandlers::Response(request, 404); + auto response = IPC::SchemeHandlers::Response(req, 404); response.finish(); args->put_Response(response.platformResponse); deferral->Complete(); @@ -1116,10 +1154,10 @@ namespace SSC { void Window::about () { auto app = App::sharedApplication(); auto text = String( - this->options.userConfig["build_name"] + " " + - "v" + this->options.userConfig["meta_version"] + "\n" + + this->bridge.userConfig["build_name"] + " " + + "v" + this->bridge.userConfig["meta_version"] + "\n" + "Built with ssc v" + VERSION_FULL_STRING + "\n" + - this->options.userConfig["meta_copyright"] + this->bridge.userConfig["meta_copyright"] ); MSGBOXPARAMS mbp; @@ -1127,7 +1165,7 @@ namespace SSC { mbp.hwndOwner = this->window; mbp.hInstance = app->hInstance; mbp.lpszText = text.c_str(); - mbp.lpszCaption = this->options.userConfig["build_name"].c_str(); + mbp.lpszCaption = this->bridge.userConfig["build_name"].c_str(); mbp.dwStyle = MB_USERICON; mbp.dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); mbp.lpfnMsgBoxCallback = nullptr; @@ -1151,14 +1189,10 @@ namespace SSC { void Window::exit (int code) { if (this->onExit != nullptr) { - std::cerr << "WARNING: Window#" << index << " exiting with code " << code << std::endl; if (menubar != nullptr) DestroyMenu(menubar); if (menutray != nullptr) DestroyMenu(menutray); this->onExit(code); } - else { - std::cerr << "WARNING: Window#" << index << " window->onExit is null in Window::exit()" << std::endl; - } } void Window::close (int code) { @@ -1233,50 +1267,39 @@ namespace SSC { controller->put_Bounds(bounds); } - void Window::eval (const String& s) { - app.dispatch([&, this, s] { + void Window::eval (const String& source) { + auto app = App::sharedApplication(); + app->dispatch([=, this] { if (this->webview == nullptr) { return; } this->webview->ExecuteScript( - convertStringToWString(s).c_str(), + convertStringToWString(source).c_str(), nullptr ); }); } void Window::navigate (const String& url) { - return this->navigate("", url); - } - - void Window::navigate (const String& seq, const String& value) { + auto app = App::sharedApplication(); auto index = std::to_string(this->options.index); - app.dispatch([&, this, seq, value, index] { + app->dispatch([=, this] { EventRegistrationToken token; this->webview->add_NavigationCompleted( Microsoft::WRL::Callback<ICoreWebView2NavigationCompletedEventHandler>( - [&, this, seq, index, token](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { - String state = "1"; - + [=, this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { BOOL success; args->get_IsSuccess(&success); - - if (success) { - state = "0"; - } - - this->resolvePromise(seq, state, index); webview->remove_NavigationCompleted(token); - return S_OK; }) .Get(), &token ); - webview->Navigate(convertStringToWString(value).c_str()); + webview->Navigate(convertStringToWString(url).c_str()); }); } @@ -1299,7 +1322,7 @@ namespace SSC { SetWindowText(window, title.c_str()); } - ScreenSize Window::getSize () { + Window::Size Window::getSize () { // 100 are the min width/height that can be returned. Keep defaults in case // the function call fail. UINT32 height = 100; @@ -1308,15 +1331,15 @@ namespace SSC { // Make sure controller exists, and the call to get window bounds succeeds. if (controller != nullptr && controller->get_Bounds(&rect) >= 0) { - this->height = rect.bottom - rect.top; - this->width = rect.right - rect.left; + this->size.height = rect.bottom - rect.top; + this->size.width = rect.right - rect.left; } - return { static_cast<int>(this->height), static_cast<int>(this->width) }; + return this->size; } - ScreenSize Window::getSize () const { - return { static_cast<int>(this->height), static_cast<int>(this->width) }; + const Window::Size Window::getSize () const { + return this->size; } void Window::setSize (int width, int height, int hints) { @@ -1356,16 +1379,16 @@ namespace SSC { resize(window); } - this->width = width; - this->height = height; + this->size.width = width; + this->size.height = height; } void Window::setPosition (float x, float y) { RECT r; r.left = x; r.top = y; - r.right = this->width; - r.bottom = this->height; + r.right = this->size.width; + r.bottom = this->size.height; AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); SetWindowPos( @@ -1383,22 +1406,25 @@ namespace SSC { this->position.y = y; } - void Window::setTrayMenu (const String& seq, const String& value) { - setMenu(seq, value, true); + void Window::setTrayMenu (const String& value) { + return this->setMenu(value, true); } - void Window::setSystemMenu (const String& seq, const String& value) { - setMenu(seq, value, false); + void Window::setSystemMenu (const String& value) { + return this->setMenu(value, false); } - void Window::setMenu (const String& seq, const String& menuSource, const bool& isTrayMenu) { - static auto userConfig = getUserConfig(); - if (menuSource.empty()) return void(0); + void Window::setMenu (const String& menuSource, const bool& isTrayMenu) { + auto app = App::sharedApplication(); + auto userConfig = this->options.userConfig; + + if (menuSource.empty()) { + return; + } NOTIFYICONDATA nid; if (isTrayMenu) { - static auto app = App::sharedApplication(); auto cwd = app->getcwd(); auto trayIconPath = String("application_tray_icon"); @@ -1544,11 +1570,6 @@ namespace SSC { DrawMenuBar(this->window); RedrawWindow(this->window, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE); } - - if (seq.size() > 0) { - auto index = std::to_string(this->options.index); - this->resolvePromise(seq, "0", index); - } } void Window::setSystemMenuItemEnabled (bool enabled, int barPos, int menuPos) { From dea743f3cc18cebdd2312acf554fa27add69aa78 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 23:31:35 +0200 Subject: [PATCH 0847/1178] refactor(bin): upgrade android ndk, clean up builds --- bin/android-functions.sh | 10 +++++----- bin/build-runtime-library.sh | 4 ++-- bin/cflags.sh | 7 +++++-- bin/functions.sh | 2 +- bin/generate-gradle-files.sh | 2 +- bin/install.sh | 11 ++++++----- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/bin/android-functions.sh b/bin/android-functions.sh index 11cf8c4137..dc38dbb1a2 100755 --- a/bin/android-functions.sh +++ b/bin/android-functions.sh @@ -433,7 +433,7 @@ function android_clang () { function android_clang_target () { local arch=$1 - echo "--target=$(android_arch "$arch")-linux-android$(android_eabi "$arch")" + echo "-target $(android_arch "$arch")-linux-android$(android_eabi "$arch")" } @@ -450,9 +450,9 @@ function android_arch_includes() { #get abi specific includes and sysroot local arch=$1 local include=( - "-I$ANDROID_HOME/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/$(android_arch "$arch")-linux-android$(android_eabi "$arch")" - "-I$ANDROID_HOME/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include" - "--sysroot=$ANDROID_HOME/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/$(android_arch "$arch")-linux-android$(android_eabi "$arch")" + "-I$ANDROID_HOME/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/$(android_host_platform "$(host_os)")-x86_64/sysroot/usr/include/$(android_arch "$arch")-linux-android$(android_eabi "$arch")" + "-I$ANDROID_HOME/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/$(android_host_platform "$(host_os)")-x86_64/sysroot/usr/include" + "--sysroot=$ANDROID_HOME/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/$(android_host_platform "$(host_os)")-x86_64/sysroot/usr/lib/$(android_arch "$arch")-linux-android$(android_eabi "$arch")" ) echo "${include[@]}" @@ -488,7 +488,7 @@ function android_supported_abis() { export ANDROID_DEPS_ERROR declare ANDROID_PLATFORM="34" -declare NDK_VERSION="26.0.10792818" +declare NDK_VERSION="26.1.10909125" export BUILD_ANDROID diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index b8130ee7f0..1554d44efc 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -190,11 +190,11 @@ function generate_llama_build_info () { build_commit=$(printf '%s' "$out" | tr -d '\n') fi - if out=$("$clang" --version | head -1); then + if out=$(eval "$clang" --version | head -1); then build_compiler="$out" fi - if out=$("$clang" -dumpmachine); then + if out=$(eval "$clang" -dumpmachine); then build_target="$out" fi diff --git a/bin/cflags.sh b/bin/cflags.sh index 5ace39c49c..05b347bfd6 100755 --- a/bin/cflags.sh +++ b/bin/cflags.sh @@ -13,6 +13,11 @@ declare platform="desktop" declare ios_sdk_path="" +cflags+=( + $CFLAGS + $CXXFLAGS +) + if [[ "$host" = "Linux" ]]; then if [ -n "$WSL_DISTRO_NAME" ] || uname -r | grep 'Microsoft'; then host="Win32" @@ -47,8 +52,6 @@ else fi cflags+=( - $CFLAGS - $CXXFLAGS -std=c++2a -ferror-limit=6 -I"$root/include" diff --git a/bin/functions.sh b/bin/functions.sh index df19a567b7..cef4cd9b6a 100755 --- a/bin/functions.sh +++ b/bin/functions.sh @@ -98,7 +98,7 @@ function quiet () { declare command="$1"; shift if [ -n "$VERBOSE" ]; then echo "$command" "$@" - "$command" "$@" + eval "$command $@" else "$command" "$@" > /dev/null 2>&1 fi diff --git a/bin/generate-gradle-files.sh b/bin/generate-gradle-files.sh index 146f987d4f..9f080a5b52 100755 --- a/bin/generate-gradle-files.sh +++ b/bin/generate-gradle-files.sh @@ -40,7 +40,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 34 - ndkVersion "26.0.10792818" + ndkVersion "26.1.10909125" flavorDimensions "default" defaultConfig { diff --git a/bin/install.sh b/bin/install.sh index 38797e1c36..6a52db6ee0 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -889,6 +889,7 @@ function _compile_llama { fi rm -f "$root/build/$(host_arch)-desktop/lib$d"/*.{so,la,dylib}* + return elif [ "$platform" == "iPhoneOS" ] || [ "$platform" == "iPhoneSimulator" ]; then # https://github.com/ggerganov/llama.cpp/discussions/4508 @@ -911,27 +912,28 @@ function _compile_llama { local android_includes=$(android_arch_includes "$1") local host_arch="$(host_arch)" local cc="$(android_clang "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" - local cxx="$cc" + local cxx="$(android_clang "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch" "++")" local clang_target="$(android_clang_target "$target")" local ar="$(android_ar "$ANDROID_HOME" "$NDK_VERSION" "$host" "$host_arch")" local cflags=("$clang_target" -std=c++2a -g -pedantic "${android_includes[*]}") - AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make UNAME_M="$1" UNAME_P="$1" LLAMA_FAST=1 libllama.a + AR="$ar" CFLAGS="$cflags" CXXFLAGS="$cflags" CXX="$cxx" CC="$cc" make UNAME_S="Android" UNAME_M=".." UNAME_P="$1" LLAMA_FAST=1 libllama.a if [ ! $? = 0 ]; then die $? "not ok - Unable to compile libllama for '$platform'" - return + return fi fi fi if [[ "$host" != "Win32" ]]; then cp libllama.a ../lib + die $? "not ok - Unable to compile libllama for '$platform'" fi cd "$BUILD_DIR" || exit 1 rm -f "$root/build/$target-$platform/lib$d"/*.{so,la,dylib}* - echo "ok - built llama for $target" + echo "ok - built llama for $target-$platform" } function _compile_libuv { @@ -1122,7 +1124,6 @@ if [[ "$(uname -s)" == "Darwin" ]] && [[ -z "$NO_IOS" ]]; then _compile_llama x86_64 iPhoneSimulator & pids+=($!) if [[ "$arch" = "arm64" ]]; then - echo "lol" _compile_libuv arm64 iPhoneSimulator & pids+=($!) _compile_llama arm64 iPhoneSimulator & pids+=($!) fi From 63299a2dee70e39cc7f3477cb83422b65ab3b32a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 22 Jun 2024 23:31:48 +0200 Subject: [PATCH 0848/1178] chore(cli): upgrade ndk version --- src/cli/cli.cc | 2 +- src/cli/templates.hh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index e049751031..58a3d60514 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -5204,7 +5204,7 @@ int main (const int argc, const char* argv[]) { StringStream sdkmanager; StringStream packages; StringStream gradlew; - String ndkVersion = "26.0.10792818"; + String ndkVersion = "26.1.10909125"; String androidPlatform = "android-34"; if (platform.unix) { diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 5a543bce85..3e96c55855 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -1388,7 +1388,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 34 - ndkVersion "26.0.10792818" + ndkVersion "26.1.10909125" flavorDimensions "default" namespace '{{android_bundle_identifier}}' From 98fc55d545d151a869ec39c65f882e4c4ee459e1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 23 Jun 2024 17:27:58 +0200 Subject: [PATCH 0849/1178] refactor(window): make 'Dialog::showFileSystemPicker' async --- src/window/dialog.cc | 200 ++++++++++++++++++++++++------------------- src/window/dialog.hh | 24 ++++-- 2 files changed, 126 insertions(+), 98 deletions(-) diff --git a/src/window/dialog.cc b/src/window/dialog.cc index 62e27dc14f..9e39a0af4b 100644 --- a/src/window/dialog.cc +++ b/src/window/dialog.cc @@ -6,21 +6,26 @@ #include "../platform/android.hh" #endif +using namespace SSC; + #if SOCKET_RUNTIME_PLATFORM_IOS @implementation SSCUIPickerDelegate : NSObject - (void) documentPicker: (UIDocumentPickerViewController*) controller didPickDocumentsAtURLs: (NSArray<NSURL*>*) urls { + Vector<String> paths; for (NSURL* url in urls) { if (url.isFileURL) { - self.dialog->delegatedResults.push_back(url.path.UTF8String); + paths.push_back(url.path.UTF8String); } } - self.dialog->delegateMutex.unlock(); + debug("before callback"); + self.dialog->callback(paths); + debug("after callback"); } - (void) documentPickerWasCancelled: (UIDocumentPickerViewController*) controller { - self.dialog->delegateMutex.unlock(); + self.dialog->callback(Vector<String>()); } - (void) imagePickerController: (UIImagePickerController*) picker @@ -28,19 +33,20 @@ { NSURL* mediaURL = info[UIImagePickerControllerMediaURL]; NSURL* imageURL = info[UIImagePickerControllerImageURL]; + Vector<String> paths; if (mediaURL != nullptr) { - self.dialog->delegatedResults.push_back(mediaURL.path.UTF8String); + paths.push_back(mediaURL.path.UTF8String); } else { - self.dialog->delegatedResults.push_back(imageURL.path.UTF8String); + paths.push_back(imageURL.path.UTF8String); } - self.dialog->delegateMutex.unlock(); [picker dismissViewControllerAnimated: YES completion: nullptr]; + self.dialog->callback(paths); } - (void) imagePickerControllerDidCancel: (UIImagePickerController*) picker { - self.dialog->delegateMutex.unlock(); + self.dialog->callback(Vector<String>()); } @end #endif @@ -49,14 +55,14 @@ namespace SSC { Dialog::Dialog (Window* window) : window(window) { - #if defined(__APPLE__) && TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #if SOCKET_RUNTIME_PLATFORM_IOS this->uiPickerDelegate = [SSCUIPickerDelegate new]; this->uiPickerDelegate.dialog = this; #endif } Dialog::~Dialog () { - #if defined(__APPLE__) && TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #if SOCKET_RUNTIME_PLATFORM_IOS #if !__has_feature(objc_arc) [this->uiPickerDelegate release]; #endif @@ -64,10 +70,11 @@ namespace SSC { #endif } - String Dialog::showSaveFilePicker ( - const FileSystemPickerOptions& options + bool Dialog::showSaveFilePicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ) { - const auto results = this->showFileSystemPicker({ + return this->showFileSystemPicker({ .prefersDarkMode = options.prefersDarkMode, .directories = false, .multiple = false, @@ -77,18 +84,12 @@ namespace SSC { .defaultName = options.defaultName, .defaultPath = options.defaultPath, .title = options.title - }); - - if (results.size() == 1) { - return results[0]; - } - - return ""; + }, callback); } - - Vector<String> Dialog::showOpenFilePicker ( - const FileSystemPickerOptions& options + bool Dialog::showOpenFilePicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ) { return this->showFileSystemPicker({ .prefersDarkMode = options.prefersDarkMode, @@ -100,11 +101,12 @@ namespace SSC { .defaultName = options.defaultName, .defaultPath = options.defaultPath, .title = options.title - }); + }, callback); } - Vector<String> Dialog::showDirectoryPicker ( - const FileSystemPickerOptions& options + bool Dialog::showDirectoryPicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ) { return this->showFileSystemPicker({ .prefersDarkMode = options.prefersDarkMode, @@ -116,11 +118,12 @@ namespace SSC { .defaultName = options.defaultName, .defaultPath = options.defaultPath, .title = options.title - }); + }, callback); } - Vector<String> Dialog::showFileSystemPicker ( - const FileSystemPickerOptions& options + bool Dialog::showFileSystemPicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ) { const auto isSavePicker = options.type == FileSystemPickerOptions::Type::Save; const auto allowDirectories = options.directories; @@ -129,10 +132,11 @@ namespace SSC { const auto defaultName = options.defaultName; const auto defaultPath = options.defaultPath; const auto title = options.title; + const auto app = App::sharedApplication(); Vector<String> paths; - #if defined(__APPLE__) + #if SOCKET_RUNTIME_PLATFORM_APPLE // state NSMutableArray<UTType *>* contentTypes = [NSMutableArray new]; NSString* suggestedFilename = nullptr; @@ -219,50 +223,48 @@ namespace SSC { } } - #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR - this->delegateMutex.lock(); - dispatch_async(dispatch_get_main_queue(), ^{ - UIWindow* window = nullptr; + #if SOCKET_RUNTIME_PLATFORM_IOS + UIWindow* window = nullptr; + this->callback = callback; - if (@available(iOS 15.0, *)) { - auto scene = (UIWindowScene*) UIApplication.sharedApplication.connectedScenes.allObjects.firstObject; - window = scene.windows.lastObject; - } else { - window = UIApplication.sharedApplication.windows.lastObject; - } + if (this->window) { + window = this->window->window; + } else if (@available(iOS 15.0, *)) { + auto scene = (UIWindowScene*) UIApplication.sharedApplication.connectedScenes.allObjects.firstObject; + window = scene.windows.lastObject; + } else { + window = UIApplication.sharedApplication.windows.lastObject; + } - if (prefersMedia) { - auto picker = [UIImagePickerController new]; - NSMutableArray<NSString*>* mediaTypes = [NSMutableArray new]; + if (prefersMedia) { + auto picker = [UIImagePickerController new]; + NSMutableArray<NSString*>* mediaTypes = [NSMutableArray new]; - picker.delegate = this->uiPickerDelegate; + picker.delegate = this->uiPickerDelegate; - [window.rootViewController - presentViewController: picker - animated: YES - completion: nullptr - ]; - } else { - auto picker = [UIDocumentPickerViewController.alloc - initForOpeningContentTypes: contentTypes - ]; + [window.rootViewController + presentViewController: picker + animated: YES + completion: nullptr + ]; + } else { + auto picker = [UIDocumentPickerViewController.alloc + initForOpeningContentTypes: contentTypes + ]; - picker.allowsMultipleSelection = allowMultiple ? YES : NO; - picker.modalPresentationStyle = UIModalPresentationFormSheet; - picker.directoryURL = directoryURL; - picker.delegate = this->uiPickerDelegate; + picker.allowsMultipleSelection = allowMultiple ? YES : NO; + picker.modalPresentationStyle = UIModalPresentationFormSheet; + picker.directoryURL = directoryURL; + picker.delegate = this->uiPickerDelegate; - [window.rootViewController - presentViewController: picker - animated: YES - completion: nullptr - ]; - } - }); + [window.rootViewController + presentViewController: picker + animated: YES + completion: nullptr + ]; + } - std::lock_guard<std::mutex> lock(this->delegateMutex); - paths = this->delegatedResults; - this->delegatedResults.clear(); + return true; #else NSAutoreleasePool* pool = [NSAutoreleasePool new]; @@ -335,6 +337,10 @@ namespace SSC { } [pool release]; + app->dispatch([=, this] () { + callback(paths); + }); + return true; #endif #elif defined(__linux__) && !defined(__ANDROID__) const guint SELECT_RESPONSE = 0; @@ -355,7 +361,7 @@ namespace SSC { } } - return FALSE; + return false; }; g_object_set( @@ -501,7 +507,7 @@ namespace SSC { if (response != GTK_RESPONSE_ACCEPT && response != SELECT_RESPONSE) { gtk_widget_destroy(dialog); - return paths; + return false; } // TODO (@heapwolf): validate multi-select @@ -521,7 +527,11 @@ namespace SSC { g_slist_free(filenames); gtk_widget_destroy(GTK_WIDGET(dialog)); - #elif defined(_WIN32) + app->dispatch([=]() { + callback(paths); + }); + return true; + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS IShellItemArray *openResults; IShellItem *saveResult; DWORD dialogOptions; @@ -541,7 +551,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: CoInitializeEx() failed in 'showFileSystemPicker()'"); - return paths; + return false; } // create IFileDialog instance (IFileOpenDialog or IFileSaveDialog) @@ -556,7 +566,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: CoCreateInstance() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } else { result = CoCreateInstance( @@ -569,7 +579,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: CoCreateInstance() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } @@ -582,7 +592,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::GetOptions() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } if (allowDirectories == true && allowFiles == false) { @@ -595,7 +605,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::SetOptions(FOS_PICKFOLDERS) failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } @@ -605,7 +615,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::SetOptions(FOS_ALLOWMULTISELECT) failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } @@ -623,7 +633,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: SHCreateItemFromParsingName() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } if (isSavePicker) { @@ -635,7 +645,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::SetDefaultFolder() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } @@ -653,7 +663,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::SetTitle() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } @@ -671,7 +681,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::SetFileName() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } @@ -684,7 +694,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::Show() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } if (isSavePicker) { @@ -693,7 +703,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::GetResult() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } else { result = dialog.open->GetResults(&openResults); @@ -701,14 +711,15 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IFileDialog::GetResults() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } } if (FAILED(result)) { debug("ERR: IFileDialog::Show() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + callback(paths); + return false; } if (isSavePicker) { @@ -719,10 +730,10 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IShellItem::GetDisplayName() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } - paths.push_back(SSC::convertWStringToString(WString(buf))); + paths.push_back(convertWStringToString(WString(buf))); saveResult->Release(); CoTaskMemFree(buf); @@ -732,7 +743,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IShellItemArray::GetCount() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } for (DWORD i = 0; i < totalResults; i++) { @@ -744,7 +755,7 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IShellItemArray::GetItemAt() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } result = path->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &buf); @@ -752,10 +763,10 @@ namespace SSC { if (FAILED(result)) { debug("ERR: IShellItem::GetDisplayName() failed in 'showFileSystemPicker()'"); CoUninitialize(); - return paths; + return false; } - paths.push_back(SSC::convertWStringToString(WString(buf))); + paths.push_back(convertWStringToString(WString(buf))); path->Release(); CoTaskMemFree(buf); } @@ -772,6 +783,10 @@ namespace SSC { } CoUninitialize(); + app->dispatch([=]() { + callback(paths); + }); + return true; #elif SOCKET_RUNTIME_PLATFORM_ANDROID if (this->window->androidWindowRef) { const auto app = App::sharedApplication(); @@ -824,9 +839,14 @@ namespace SSC { paths.push_back(string); } } + + app->dispatch([=]() { + callback(paths); + }); + return true; } #endif - return paths; + return false; } } diff --git a/src/window/dialog.hh b/src/window/dialog.hh index 65197b7d45..334151d7ec 100644 --- a/src/window/dialog.hh +++ b/src/window/dialog.hh @@ -47,13 +47,17 @@ namespace SSC { String title; }; + using ShowCallback = Function<void(Vector<String>)>; + #if SOCKET_RUNTIME_PLATFORM_IOS SSCUIPickerDelegate* uiPickerDelegate = nullptr; Vector<String> delegatedResults; std::mutex delegateMutex; #endif + ShowCallback callback = nullptr; Window* window = nullptr; + Dialog (Window* window); Dialog () = default; Dialog (const Dialog&) = delete; @@ -63,20 +67,24 @@ namespace SSC { Dialog& operator = (const Dialog&) = delete; Dialog& operator = (Dialog&&) = delete; - String showSaveFilePicker ( - const FileSystemPickerOptions& options + bool showSaveFilePicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ); - Vector<String> showOpenFilePicker ( - const FileSystemPickerOptions& options + bool showOpenFilePicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ); - Vector<String> showDirectoryPicker ( - const FileSystemPickerOptions& options + bool showDirectoryPicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ); - Vector<String> showFileSystemPicker ( - const FileSystemPickerOptions& options + bool showFileSystemPicker ( + const FileSystemPickerOptions& options, + const ShowCallback& callback ); }; } From 0cd604e78c216fb1f9df5f0e60cddbabaf6440b5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 23 Jun 2024 17:28:25 +0200 Subject: [PATCH 0850/1178] refactor(ipc): use async 'Dialog' methods --- src/ipc/router.cc | 2 +- src/ipc/routes.cc | 96 +++++++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/ipc/router.cc b/src/ipc/router.cc index 94e6433e33..c1f249145e 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -158,7 +158,7 @@ namespace SSC::IPC { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { - this->bridge->dispatch([=, this]() { + this->bridge->dispatch([=, this]() { callback(result); }); } diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index a223c48f73..031c09aa0d 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2948,59 +2948,67 @@ static void mapIPCRoutes (Router *router) { const auto app = App::sharedApplication(); const auto window = app->windowManager.getWindowForBridge(router->bridge); - Dialog* dialog = nullptr; + app->dispatch([=]() { + Dialog* dialog = nullptr; - if (window) { - dialog = &window->dialog; - } else { - dialog = new Dialog(); - } - - const auto options = Dialog::FileSystemPickerOptions { - .prefersDarkMode = message.get("prefersDarkMode") == "true", - .directories = allowDirs, - .multiple = allowMultiple, - .files = allowFiles, - .contentTypes = contentTypeSpecs, - .defaultName = defaultName, - .defaultPath = defaultPath, - .title = title - }; + if (window) { + dialog = &window->dialog; + } else { + dialog = new Dialog(); + } + + const auto options = Dialog::FileSystemPickerOptions { + .prefersDarkMode = message.get("prefersDarkMode") == "true", + .directories = allowDirs, + .multiple = allowMultiple, + .files = allowFiles, + .contentTypes = contentTypeSpecs, + .defaultName = defaultName, + .defaultPath = defaultPath, + .title = title + }; - if (isSave) { - const auto result = dialog->showSaveFilePicker(options); + const auto callback = [=](Vector<String> results) { + JSON::Array paths; + + if (results.size() == 0) { + const auto err = JSON::Object::Entries {{"type", "AbortError"}}; + return reply(Result::Err { message, err }); + } + + for (const auto& result : results) { + paths.push(result); + } - if (result.size() == 0) { - const auto err = JSON::Object::Entries {{"type", "AbortError"}}; - reply(Result::Err { message, err }); - } else { const auto data = JSON::Object::Entries { - {"paths", JSON::Array::Entries{result}} + {"paths", paths} }; - reply(Result::Data { message, data }); - } - } else { - JSON::Array paths; - const auto results = ( - allowFiles && !allowDirs - ? dialog->showOpenFilePicker(options) - : dialog->showDirectoryPicker(options) - ); - - for (const auto& result : results) { - paths.push(result); - } - const auto data = JSON::Object::Entries { - {"paths", paths} + reply(Result::Data { message, data }); }; - reply(Result::Data { message, data }); - } + if (isSave) { + if (!dialog->showSaveFilePicker(options, callback)) { + const auto err = JSON::Object::Entries {{"type", "AbortError"}}; + reply(Result::Err { message, err }); + } + } else { + const auto result = ( + allowFiles && !allowDirs + ? dialog->showOpenFilePicker(options, callback) + : dialog->showDirectoryPicker(options, callback) + ); - if (!window) { - delete dialog; - } + if (!result) { + const auto err = JSON::Object::Entries {{"type", "AbortError"}}; + reply(Result::Err { message, err }); + } + } + + if (!window) { + delete dialog; + } + }); }); /** From 1582ac8e798762f2040fa20ec31735d641f0d315 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 23 Jun 2024 17:28:47 +0200 Subject: [PATCH 0851/1178] refactor(core/webview): use async Dialog --- src/core/webview.cc | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/core/webview.cc b/src/core/webview.cc index d8adef2f84..8dfe2eeb0b 100644 --- a/src/core/webview.cc +++ b/src/core/webview.cc @@ -456,6 +456,7 @@ int lastY = 0; { const auto acceptedFileExtensions = parameters._acceptedFileExtensions; const auto acceptedMIMETypes = parameters._acceptedMIMETypes; + const auto window = (Window*) objc_getAssociatedObject(self, "window"); StringStream contentTypesSpec; for (NSString* acceptedMIMEType in acceptedMIMETypes) { @@ -499,21 +500,26 @@ int lastY = 0; .title = "Choose a File" }; - Dialog dialog; - const auto results = dialog.showOpenFilePicker(options); + Dialog dialog(window); + const auto success = dialog.showOpenFilePicker(options, [=](const auto results) { + if (results.size() == 0) { + completionHandler(nullptr); + return; + } - if (results.size() == 0) { - completionHandler(nullptr); - return; - } + auto urls = [NSMutableArray array]; - auto urls = [NSMutableArray array]; + for (const auto& result : results) { + [urls addObject: [NSURL URLWithString: @(result.c_str())]]; + } - for (const auto& result : results) { - [urls addObject: [NSURL URLWithString: @(result.c_str())]]; - } + completionHandler(urls); + }); - completionHandler(urls); + if (!success) { + completionHandler(nullptr); + return; + } } #endif @@ -619,7 +625,7 @@ int lastY = 0; } #if SOCKET_RUNTIME_PLATFORM_IOS -- (void)traitCollectionDidChange:(UITraitCollection *) previousTraitCollection { +- (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; static auto userConfig = getUserConfig(); From e1f203e37c300f0599cdde594c03e08dbdaed680 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 23 Jun 2024 17:47:04 +0200 Subject: [PATCH 0852/1178] chore(core): clean up --- src/core/core.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index f927aaf885..910476b5b9 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -72,10 +72,9 @@ namespace SSC { void Core::removePost (uint64_t id) { Lock lock(this->postsMutex); - if (this->posts.find(id) == this->posts.end()) { - return; + if (this->posts.find(id) != this->posts.end()) { + posts.erase(id); } - posts.erase(id); } String Core::createPost (String seq, String params, Post post) { From ebfd3fe0f8a80f32086b1db69781910aa19ea01e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 25 Jun 2024 19:12:16 +0200 Subject: [PATCH 0853/1178] feat(core/modules/diagnostics): introduce initial runtime diagnostics --- src/core/core.cc | 8 ++ src/core/core.hh | 4 + src/core/modules/diagnostics.cc | 216 ++++++++++++++++++++++++++++++++ src/core/modules/diagnostics.hh | 120 ++++++++++++++++++ src/core/modules/timers.cc | 29 ++++- src/core/modules/timers.hh | 3 + src/core/modules/udp.cc | 2 +- 7 files changed, 378 insertions(+), 4 deletions(-) create mode 100644 src/core/modules/diagnostics.cc create mode 100644 src/core/modules/diagnostics.hh diff --git a/src/core/core.cc b/src/core/core.cc index 910476b5b9..2b7e86ec73 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -73,6 +73,7 @@ namespace SSC { Lock lock(this->postsMutex); if (this->posts.find(id) != this->posts.end()) { + // debug("remove post %ld", this->posts.at(id).body.use_count()); posts.erase(id); } } @@ -424,6 +425,13 @@ namespace SSC { } } + while ( + core->sharedPointerBuffers.size() > 0 && + core->sharedPointerBuffers.back().pointer == nullptr + ) { + core->sharedPointerBuffers.pop_back(); + } + if (core->sharedPointerBuffers.size() == 0) { uv_timer_stop(&releaseStrongReferenceSharedPointerBuffers.handle); } diff --git a/src/core/core.hh b/src/core/core.hh index 0daea09a3c..ba0df50892 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -26,6 +26,7 @@ #include "modules/ai.hh" #include "modules/child_process.hh" +#include "modules/diagnostics.hh" #include "modules/dns.hh" #include "modules/fs.hh" #include "modules/geolocation.hh" @@ -51,6 +52,7 @@ namespace SSC { using ChildProcess = CoreChildProcess; #endif using DNS = CoreDNS; + using Diagnostics = CoreDiagnostics; using FS = CoreFS; using Geolocation = CoreGeolocation; using NetworkStatus = CoreNetworkStatus; @@ -98,6 +100,7 @@ namespace SSC { #if !SOCKET_RUNTIME_PLATFORM_IOS ChildProcess childProcess; #endif + Diagnostics diagnostics; DNS dns; FS fs; Geolocation geolocation; @@ -149,6 +152,7 @@ namespace SSC { childProcess(this), #endif ai(this), + diagnostics(this), dns(this), fs(this), geolocation(this), diff --git a/src/core/modules/diagnostics.cc b/src/core/modules/diagnostics.cc new file mode 100644 index 0000000000..bd757e1cda --- /dev/null +++ b/src/core/modules/diagnostics.cc @@ -0,0 +1,216 @@ +#include "../core.hh" +#include "diagnostics.hh" + +namespace SSC { + JSON::Object CoreDiagnostics::Diagnostic::Handles::json () const { + auto ids = JSON::Array {}; + for (const auto id : this->ids) { + ids.push(std::to_string(id)); + } + return JSON::Object::Entries { + {"count", this->count}, + {"ids", ids} + }; + } + + void CoreDiagnostics::query (const QueryCallback& callback) const { + this->core->dispatchEventLoop([=, this] () mutable { + auto query = QueryDiagnostic {}; + + // posts diagnostics + do { + Lock lock(this->core->postsMutex); + query.posts.handles.count = this->core->posts.size(); + for (const auto& entry : this->core->posts) { + query.posts.handles.ids.push_back(entry.first); + } + } while (0); + + // `childProcess` diagnostics + do { + Lock lock(this->core->childProcess.mutex); + query.childProcess.handles.count = this->core->childProcess.handles.size(); + for (const auto& entry : this->core->childProcess.handles) { + query.childProcess.handles.ids.push_back(entry.first); + } + } while (0); + + // ai diagnostics + do { + Lock lock(this->core->ai.mutex); + query.ai.llm.handles.count = this->core->ai.llms.size(); + for (const auto& entry : this->core->ai.llms) { + query.ai.llm.handles.ids.push_back(entry.first); + } + } while (0); + + // fs diagnostics + do { + Lock lock(this->core->fs.mutex); + query.fs.descriptors.handles.count = this->core->fs.descriptors.size(); + query.fs.watchers.handles.count = this->core->fs.watchers.size(); + + for (const auto& entry : this->core->fs.descriptors) { + query.fs.descriptors.handles.ids.push_back(entry.first); + } + + for (const auto& entry : this->core->fs.watchers) { + query.fs.watchers.handles.ids.push_back(entry.first); + } + } while (0); + + // timers diagnostics + do { + Lock lock(this->core->timers.mutex); + for (const auto& entry : this->core->timers.handles) { + const auto id = entry.first; + const auto& timer = entry.second; + if (timer->type == CoreTimers::Timer::Type::Timeout) { + query.timers.timeout.handles.count++; + query.timers.timeout.handles.ids.push_back(entry.first); + } else if (timer->type == CoreTimers::Timer::Type::Interval) { + query.timers.interval.handles.count++; + query.timers.interval.handles.ids.push_back(entry.first); + } else if (timer->type == CoreTimers::Timer::Type::Immediate) { + query.timers.immediate.handles.count++; + query.timers.immediate.handles.ids.push_back(entry.first); + } + } + } while (0); + + // udp + do { + Lock lock(this->core->udp.mutex); + query.udp.handles.count = this->core->udp.sockets.size(); + for (const auto& entry : this->core->udp.sockets) { + query.udp.handles.ids.push_back(entry.first); + } + } while (0); + + // uv + do { + Lock lock(this->core->loopMutex); + uv_metrics_info(&this->core->eventLoop, &query.uv.metrics); + query.uv.idleTime = uv_metrics_idle_time(&this->core->eventLoop); + query.uv.handles.count = this->core->eventLoop.active_handles; + query.uv.activeRequests = this->core->eventLoop.active_reqs.count; + } while (0); + + callback(query); + }); + } + + void CoreDiagnostics::query ( + const String& seq, + const CoreModule::Callback& callback + ) const { + this->core->dispatchEventLoop([=, this] () { + this->query([=] (const auto query) { + auto json = JSON::Object::Entries { + {"source", "diagnostics.query"}, + {"data", query.json()} + }; + callback(seq, json, Post {}); + }); + }); + } + + JSON::Object CoreDiagnostics::UVDiagnostic::json () const { + return JSON::Object::Entries { + {"metrics", JSON::Object::Entries { + {"loopCount", this->metrics.loop_count}, + {"events", this->metrics.events}, + {"eventsWaiting", this->metrics.events_waiting}, + }}, + {"idleTime", this->idleTime}, + {"activeRequests", this->activeRequests} + }; + } + + JSON::Object CoreDiagnostics::PostsDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::ChildProcessDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::AIDiagnostic::json () const { + return JSON::Object::Entries { + {"llm", this->llm.json()} + }; + } + + JSON::Object CoreDiagnostics::AIDiagnostic::LLMDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::FSDiagnostic::json () const { + return JSON::Object::Entries { + {"watchers", this->watchers.json()}, + {"descriptors", this->descriptors.json()} + }; + } + + JSON::Object CoreDiagnostics::FSDiagnostic::WatchersDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::FSDiagnostic::DescriptorsDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::TimersDiagnostic::json () const { + return JSON::Object::Entries { + {"timeout", this->timeout.json()}, + {"interval", this->interval.json()}, + {"immediate", this->immediate.json()} + }; + } + + JSON::Object CoreDiagnostics::TimersDiagnostic::TimeoutDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::TimersDiagnostic::IntervalDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::TimersDiagnostic::ImmediateDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::UDPDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + + JSON::Object CoreDiagnostics::QueryDiagnostic::json () const { + return JSON::Object::Entries { + {"posts", this->posts.json()}, + {"childProcess", this->childProcess.json()}, + {"ai", this->ai.json()}, + {"fs", this->fs.json()}, + {"timers", this->timers.json()}, + {"udp", this->udp.json()}, + {"uv", this->uv.json()} + }; + } +} diff --git a/src/core/modules/diagnostics.hh b/src/core/modules/diagnostics.hh new file mode 100644 index 0000000000..fad12f231d --- /dev/null +++ b/src/core/modules/diagnostics.hh @@ -0,0 +1,120 @@ +#ifndef SOCKET_RUNTIME_CORE_MODULE_DIAGNOSTICS_H +#define SOCKET_RUNTIME_CORE_MODULE_DIAGNOSTICS_H + +#include "../json.hh" +#include "../module.hh" + +namespace SSC { + class Core; + class CoreDiagnostics : public CoreModule { + public: + struct Diagnostic { + using ID = uint64_t; + struct Handles { + size_t count = 0; + Vector<ID> ids; + JSON::Object json () const; + }; + + virtual JSON::Object json () const = 0; + }; + + struct UVDiagnostic : public Diagnostic { + uv_metrics_t metrics; // various uv metrics + Handles handles; // active uv loop handles + uint64_t idleTime = 0; + uint64_t activeRequests = 0; + JSON::Object json () const override; + }; + + struct PostsDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + struct ChildProcessDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + struct AIDiagnostic : public Diagnostic { + struct LLMDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + LLMDiagnostic llm; + + JSON::Object json () const override; + }; + + struct FSDiagnostic : public Diagnostic { + struct WatchersDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + struct DescriptorsDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + WatchersDiagnostic watchers; + DescriptorsDiagnostic descriptors; + JSON::Object json () const override; + }; + + struct TimersDiagnostic : public Diagnostic { + struct TimeoutDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + struct IntervalDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + struct ImmediateDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + TimeoutDiagnostic timeout; + IntervalDiagnostic interval; + ImmediateDiagnostic immediate; + + JSON::Object json () const override; + }; + + struct UDPDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + + struct QueryDiagnostic : public Diagnostic { + PostsDiagnostic posts; + ChildProcessDiagnostic childProcess; + AIDiagnostic ai; + FSDiagnostic fs; + TimersDiagnostic timers; + UDPDiagnostic udp; + UVDiagnostic uv; + + JSON::Object json () const override; + }; + + using QueryCallback = Function<void(const QueryDiagnostic&)>; + + CoreDiagnostics (Core* core) + : CoreModule(core) + {} + + void query (const QueryCallback& callback) const; + void query ( + const String& seq, + const CoreModule::Callback& callback + ) const; + }; +} +#endif diff --git a/src/core/modules/timers.cc b/src/core/modules/timers.cc index 028555518c..739cbc02b9 100644 --- a/src/core/modules/timers.cc +++ b/src/core/modules/timers.cc @@ -102,9 +102,16 @@ namespace SSC { uint64_t timeout, const TimeoutCallback& callback ) { - return this->createTimer(timeout, 0, [callback] (auto _) { + Lock lock(this->mutex); + const auto id = this->createTimer(timeout, 0, [callback] (auto _) { callback(); }); + + if (this->handles.contains(id)) { + this->handles.at(id)->type = Timer::Type::Timeout; + } + + return id; } bool CoreTimers::clearTimeout (const ID id) { @@ -115,7 +122,19 @@ namespace SSC { uint64_t interval, const IntervalCallback& callback ) { - return this->createTimer(interval, interval, callback); + Lock lock(this->mutex); + + const auto id = this->createTimer(interval, interval, callback); + + if (this->handles.contains(id)) { + this->handles.at(id)->type = Timer::Type::Interval; + } + + if (this->handles.contains(id)) { + this->handles.at(id)->type = Timer::Type::Interval; + } + + return id; } bool CoreTimers::clearInterval (const ID id) { @@ -123,9 +142,13 @@ namespace SSC { } const CoreTimers::ID CoreTimers::setImmediate (const ImmediateCallback& callback) { - return this->createTimer(0, 0, [callback] (auto _) { + Lock lock(this->mutex); + + const auto id = this->createTimer(0, 0, [callback] (auto _) { callback(); }); + + return id; } bool CoreTimers::clearImmediate (const ID id) { diff --git a/src/core/modules/timers.hh b/src/core/modules/timers.hh index bec2513d2a..ce4f4bf0c2 100644 --- a/src/core/modules/timers.hh +++ b/src/core/modules/timers.hh @@ -15,12 +15,15 @@ namespace SSC { using ImmediateCallback = TimeoutCallback; struct Timer { + enum class Type { Timeout, Interval, Immediate }; + CoreTimers* timers = nullptr; ID id = 0; Callback callback = nullptr; bool repeat = false; bool cancelled = false; uv_timer_t timer; + Type type; Timer (CoreTimers* timers, ID id, Callback callback); }; diff --git a/src/core/modules/udp.cc b/src/core/modules/udp.cc index 84d485c896..25903b5356 100644 --- a/src/core/modules/udp.cc +++ b/src/core/modules/udp.cc @@ -522,7 +522,7 @@ namespace SSC { }; callback("-1", json, Post{}); - } else if (nread > 0) { + } else if (nread > 0 && buf && buf->base) { char address[17] = {0}; Post post; int port; From c19928b7181c9d63347ff9c09268106c413f31d9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 25 Jun 2024 19:13:02 +0200 Subject: [PATCH 0854/1178] refactor(ipc): introduce 'diagnostics.query' route --- src/ipc/bridge.cc | 75 +++++++------------------------------- src/ipc/router.cc | 8 ++-- src/ipc/routes.cc | 32 +++++++++++++++- src/ipc/scheme_handlers.cc | 2 +- 4 files changed, 48 insertions(+), 69 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index c9c3a986c8..084c9a3ac5 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -405,55 +405,6 @@ export * from '{{url}}' return callback(response); } - // handle special 'ipc://post' case - if (message.name == "post") { - uint64_t id = 0; - - try { - id = std::stoull(message.get("id")); - } catch (...) { - auto response = SchemeHandlers::Response(request, 400); - response.send(JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", "Invalid 'id' given in parameters"} - }} - }); - - return callback(response); - } - - if (!this->core->hasPost(id)) { - auto response = SchemeHandlers::Response(request, 404); - response.send(JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", "A 'Post' was not found for the given 'id' in parameters"}, - {"type", "NotFoundError"} - }} - }); - - return callback(response); - } - - auto response = SchemeHandlers::Response(request, 200); - const auto post = this->core->getPost(id); - - // handle raw headers in 'Post' object - if (post.headers.size() > 0) { - const auto lines = split(trim(post.headers), '\n'); - for (const auto& line : lines) { - const auto pair = split(trim(line), ':'); - const auto key = trim(pair[0]); - const auto value = trim(pair[1]); - response.setHeader(key, value); - } - } - - response.write(post.length, post.body); - callback(response); - this->core->removePost(id); - return; - } - message.isHTTP = true; message.cancel = std::make_shared<MessageCancellation>(); @@ -465,7 +416,7 @@ export * from '{{url}}' const auto size = request->body.size; const auto bytes = request->body.bytes; - const auto invoked = this->router.invoke(message, request->body.bytes, size, [=](Result result) { + const auto invoked = this->router.invoke(message.str(), request->body.bytes, size, [=](Result result) { if (!request->isActive()) { return; } @@ -710,7 +661,7 @@ export * from '{{url}}' }); if (fetched) { - this->core->setTimeout(32000, [=] () mutable { + this->core->setTimeout(32000, [request] () mutable { if (request->isActive()) { auto response = SchemeHandlers::Response(request, 408); response.fail("ServiceWorker request timed out."); @@ -728,9 +679,11 @@ export * from '{{url}}' // proxy an import into a normal resource request above if (request->hostname.size() == 0) { auto pathname = request->pathname; - if (pathname.ends_with("/")) { + + if (pathname.ends_with("/")) { pathname = pathname.substr(0, pathname.size() - 1); - } + } + const auto specifier = pathname.substr(1); if (!pathname.ends_with(".js")) { @@ -756,9 +709,9 @@ export * from '{{url}}' const auto moduleImportProxy = tmpl( String(resource.read()).find("export default") != String::npos - ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT - : ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT, - Map { + ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT + : ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT, + Map { {"url", url}, {"commit", VERSION_HASH_STRING}, {"protocol", "socket"}, @@ -766,7 +719,7 @@ export * from '{{url}}' {"specifier", specifier}, {"bundle_identifier", bundleIdentifier} } - ); + ); const auto contentType = resource.mimeType(); @@ -862,9 +815,9 @@ export * from '{{url}}' const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; const auto moduleImportProxy = tmpl( String(resource.read()).find("export default") != String::npos - ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT - : ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT, - Map { + ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT + : ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT, + Map { {"url", url}, {"commit", VERSION_HASH_STRING}, {"protocol", "node"}, @@ -872,7 +825,7 @@ export * from '{{url}}' {"specifier", pathname.substr(1)}, {"bundle_identifier", bundleIdentifier} } - ); + ); const auto contentType = resource.mimeType(); diff --git a/src/ipc/router.cc b/src/ipc/router.cc index c1f249145e..340f27e557 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -154,11 +154,11 @@ namespace SSC::IPC { if (context.async) { return this->bridge->dispatch([=, this]() mutable { - context.callback(msg, this, [=, this](const auto result) mutable { + context.callback(msg, this, [callback, this](const auto result) mutable { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { - this->bridge->dispatch([=, this]() { + this->bridge->dispatch([result, callback, this]() { callback(result); }); } @@ -170,9 +170,7 @@ namespace SSC::IPC { if (result.seq == "-1") { this->bridge->send(result.seq, result.str(), result.post); } else { - this->bridge->dispatch([=, this]() { - callback(result); - }); + callback(result); } }); diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 031c09aa0d..2dc3b2c909 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -5,6 +5,8 @@ #include "../window/window.hh" #include "ipc.hh" +extern int LLAMA_BUILD_NUMBER; + #define REQUIRE_AND_GET_MESSAGE_VALUE(var, name, parse, ...) \ try { \ var = parse(message.get(name, ##__VA_ARGS__)); \ @@ -641,6 +643,16 @@ static void mapIPCRoutes (Router *router) { #endif }); + /** + * Query diagnostics information about the runtime core. + */ + router->map("diagnostics.query", [=](auto message, auto router, auto reply) { + router->bridge->core->diagnostics.query( + message.seq, + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); + }); + /** * Look up an IP address by `hostname`. * @param hostname Host name to lookup @@ -2131,6 +2143,12 @@ static void mapIPCRoutes (Router *router) { {"short", SSC::VERSION_STRING}, {"hash", SSC::VERSION_HASH_STRING}} }, + {"uv", JSON::Object::Entries { + {"version", uv_version_string()} + }}, + {"llama", JSON::Object::Entries { + {"version", String("0.0.") + std::to_string(LLAMA_BUILD_NUMBER)} + }}, {"host-operating-system", #if SOCKET_RUNTIME_PLATFORM_APPLE #if SOCKET_RUNTIME_PLATFORM_IOS_SIMULATOR @@ -2162,7 +2180,7 @@ static void mapIPCRoutes (Router *router) { * `ipc://post` IPC call intercepted by an XHR request. * @param id The id of the post data. */ - router->map("post", [=](auto message, auto router, auto reply) { + router->map("post", false, [=](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, {"id"}); if (err.type != JSON::Type::Null) { @@ -2175,12 +2193,22 @@ static void mapIPCRoutes (Router *router) { if (!router->bridge->core->hasPost(id)) { return reply(Result::Err { message, JSON::Object::Entries { {"id", std::to_string(id)}, - {"message", "Post not found for given 'id'"} + {"type", "NotFoundError"}, + {"message", "A 'Post' was not found for the given 'id' in parameters"} }}); } auto result = Result { message.seq, message }; result.post = router->bridge->core->getPost(id); + if (result.post.headers.size() > 0) { + const auto lines = split(trim(result.post.headers), '\n'); + for (const auto& line : lines) { + const auto pair = split(trim(line), ':'); + const auto key = trim(pair[0]); + const auto value = trim(pair[1]); + result.headers.set(key, value); + } + } reply(result); router->bridge->core->removePost(id); }); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index a4d749dce5..83f6e9fb24 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -1258,7 +1258,7 @@ namespace SSC::IPC { (gssize) size, nullptr ); - this->request->bridge->core->retainSharedPointerBuffer(bytes, 256); + this->request->bridge->core->retainSharedPointerBuffer(std::move(bytes), 256); return true; #elif SOCKET_RUNTIME_PLATFORM_WINDOWS return S_OK == this->platformResponseStream->Write( From 5c48c67d787fea2074ddd60cc78610940a78b292 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 25 Jun 2024 19:13:42 +0200 Subject: [PATCH 0855/1178] refactor(app): introduce 'App:DEFAULT_INSTANCE_ID' --- src/app/app.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/app.hh b/src/app/app.hh index 5d3e47e044..79529fc574 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -63,6 +63,7 @@ namespace SSC { public: static inline Atomic<bool> isReady = false; static App* sharedApplication (); + static constexpr int DEFAULT_INSTANCE_ID = 0; #if SOCKET_RUNTIME_PLATFORM_APPLE // created and set in `App::App()` on macOS or From e15fc621620e1d479e020426bd0393ed377edcf8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 25 Jun 2024 19:14:04 +0200 Subject: [PATCH 0856/1178] refactor(desktop/extension/linux): use 'App::DEFAULT_INSTANCE_ID' --- src/desktop/extension/linux.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc index 9ce75959d7..27a25d0f92 100644 --- a/src/desktop/extension/linux.cc +++ b/src/desktop/extension/linux.cc @@ -268,10 +268,11 @@ extern "C" { Core::Options options; options.dedicatedLoopThread = true; - static App app(std::move(std::make_shared<Core>(options))); auto userConfig = getUserConfig(); auto cwd = userConfig["web-process-extension_cwd"]; + static App app(App::DEFAULT_INSTANCE_ID, std::move(std::make_shared<Core>(options))); + if (cwd.size() > 0) { setcwd(cwd); uv_chdir(cwd.c_str()); From 26f801a73dec21c032db007324246d0860103ef0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 25 Jun 2024 19:14:46 +0200 Subject: [PATCH 0857/1178] feat(api/diagnostics): introduce 'diagnostics.runtime' API --- api/diagnostics/index.js | 3 +- api/diagnostics/runtime.js | 234 +++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 api/diagnostics/runtime.js diff --git a/api/diagnostics/index.js b/api/diagnostics/index.js index a4c3f16af0..599919167c 100644 --- a/api/diagnostics/index.js +++ b/api/diagnostics/index.js @@ -1,10 +1,11 @@ import channels from './channels.js' import window from './window.js' +import runtime from './runtime.js' import * as exports from './index.js' export default exports -export { channels, window } +export { channels, window, runtime } /** * @param {string} name diff --git a/api/diagnostics/runtime.js b/api/diagnostics/runtime.js new file mode 100644 index 0000000000..5b42a301d8 --- /dev/null +++ b/api/diagnostics/runtime.js @@ -0,0 +1,234 @@ +import ipc from '../ipc.js' + +/** + * A base container class for diagnostic information. + */ +export class Diagnostic { + /** + * A container for handles related to the diagnostics + */ + static Handles = class Handles { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count = 0 + + /** + * A set of known handle IDs + * @type {string[]} + */ + ids = [] + + /** + * `Diagnostic.Handles` class constructor. + * @private + */ + constructor () { + Object.seal(this) + } + } + + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles = new Diagnostic.Handles() +} + +/** + * A container for libuv diagnostics + */ +export class UVDiagnostic extends Diagnostic { + /** + * A container for libuv metrics. + */ + static Metrics = class Metrics { + /** + * The number of event loop iterations. + * @type {number} + */ + loopCount = 0 + + /** + * Number of events that have been processed by the event handler. + * @type {number} + */ + events = 0 + + /** + * Number of events that were waiting to be processed when the + * event provider was called. + * @type {number} + */ + eventsWaiting = 0 + } + + /** + * Known libuv metrics for this diagnostic. + * @type {UVDiagnostic.Metrics} + */ + metrics = new UVDiagnostic.Metrics() + + /** + * The current idle time of the libuv loop + * @type {number} + */ + idleTime = 0 + + /** + * The number of active requests in the libuv loop + * @type {number} + */ + activeRequests = 0 +} + +/** + * A container for Core Post diagnostics. + */ +export class PostsDiagnostic extends Diagnostic {} + +/** + * A container for child process diagnostics. + */ +export class ChildProcessDiagnostic extends Diagnostic {} + +/** + * A container for AI diagnostics. + */ +export class AIDiagnostic extends Diagnostic { + /** + * A container for AI LLM diagnostics. + */ + static LLMDiagnostic = class LLMDiagnostic extends Diagnostic {} + + /** + * Known AI LLM diagnostics. + * @type {AIDiagnostic.LLMDiagnostic} + */ + llm = new AIDiagnostic.LLMDiagnostic() +} + +/** + * A container for various filesystem diagnostics. + */ +export class FSDiagnostic extends Diagnostic { + /** + * A container for filesystem watcher diagnostics. + */ + static WatchersDiagnostic = class WatchersDiagnostic extends Diagnostic {} + + /** + * A container for filesystem descriptors diagnostics. + */ + static DescriptorsDiagnostic = class DescriptorsDiagnostic extends Diagnostic {} + + /** + * Known FS watcher diagnostics. + * @type {FSDiagnostic.WatchersDiagnostic} + */ + watchers = new FSDiagnostic.WatchersDiagnostic() + + /** + * @type {FSDiagnostic.DescriptorsDiagnostic} + */ + descriptors = new FSDiagnostic.DescriptorsDiagnostic() +} + +/** + * A container for various timers diagnostics. + */ +export class TimersDiagnostic extends Diagnostic { + /** + * A container for core timeout timer diagnostics. + */ + static TimeoutDiagnostic = class TimeoutDiagnostic extends Diagnostic {} + + /** + * A container for core interval timer diagnostics. + */ + static IntervalDiagnostic = class IntervalDiagnostic extends Diagnostic {} + + /** + * A container for core immediate timer diagnostics. + */ + static ImmediateDiagnostic = class ImmediateDiagnostic extends Diagnostic {} + + /** + * @type {TimersDiagnostic.TimeoutDiagnostic} + */ + timeout = new TimersDiagnostic.TimeoutDiagnostic() + + /** + * @type {TimersDiagnostic.IntervalDiagnostic} + */ + interval = new TimersDiagnostic.IntervalDiagnostic() + + /** + * @type {TimersDiagnostic.ImmediateDiagnostic} + */ + immediate = new TimersDiagnostic.ImmediateDiagnostic() +} + +/** + * A container for UDP diagnostics. + */ +export class UDPDiagnostic extends Diagnostic {} + +/** + */ +export class QueryDiagnostic { + posts = new PostsDiagnostic() + childProcess = new ChildProcessDiagnostic() + ai = new AIDiagnostic() + fs = new FSDiagnostic() + timers = new TimersDiagnostic() + udp = new UDPDiagnostic() + uv = new UVDiagnostic() +} + +/** + * Queries runtime diagnostics. + * @return {Promise<QueryDiagnostic>} + */ +export async function query () { + const result = await ipc.request('diagnostics.query') + + if (result.err) { + throw result.err + } + + const query = Object.assign(new QueryDiagnostic(), result.data) + + if (typeof globalThis.__global_ipc_extension_handler === 'function') { + const result = await ipc.request('diagnostics.query', {}, { + useExtensionIPCIfAvailable: true + }) + + if (result.data) { + extend(query, Object.assign(new QueryDiagnostic(), result.data)) + } + + function extend (left, right) { + for (const key in right) { + if (Array.isArray(left[key]) && Array.isArray(right[key])) { + left[key].push(...right[key]) + } else if (left[key] && typeof left[key] === 'object') { + if (right[key] && typeof right[key] === 'object') { + extend(left[key], right[key]) + } + } else if (typeof left[key] === 'number' && typeof right[key] === 'number') { + left[key] += right[key] + } else { + left[key] = right[key] + } + } + } + } + + return query +} + +export default { + query +} From cd508be3f280ee2c7ec79abe7d2d9ad02e3273fa Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:26:51 +0200 Subject: [PATCH 0858/1178] refactor(desktop/main.cc): improve timing during shutdown --- src/desktop/main.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 55ad3b0af2..ec803547b4 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -101,6 +101,7 @@ void signalHandler (int signum) { defaultWindowSignalHandler(signum); } + msleep(32); if (signum == SIGTERM || signum == SIGINT) { signal(signum, SIG_DFL); if (shutdownHandler != nullptr) { @@ -621,6 +622,8 @@ MAIN { createProcess(true); shutdownHandler = [=](int signum) mutable { + msleep(32); + #if SOCKET_RUNTIME_PLATFORM_LINUX unlink(appInstanceLock.c_str()); #endif From e260ccdbefb2dcdda58934aa93b63694477f4977 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:29:46 +0200 Subject: [PATCH 0859/1178] refactor(ipc): clean up, introduce 'client.parent' in preload --- src/ipc/bridge.cc | 16 ++++++++-------- src/ipc/preload.cc | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 084c9a3ac5..648bd286fd 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -583,7 +583,7 @@ export * from '{{url}}' if (resourcePath.size() > 0) { if (resourcePath.starts_with(applicationResources)) { contentLocation = resourcePath.substr(applicationResources.size() -1, resourcePath.size()); - } + } auto resource = FileResource(resourcePath); @@ -938,8 +938,8 @@ export * from '{{url}}' if (request->scheme == "npm") { if (hostname.size() > 0) { - pathname = "/" + hostname; - } + pathname = "/" + hostname; + } hostname = this->userConfig["meta_bundle_identifier"]; } @@ -1012,8 +1012,8 @@ export * from '{{url}}' for (const auto& entry : this->schemeHandlers.handlers) { const auto origin = entry.first + "://*"; - origins.push_back(std::make_shared<WString>(convertStringToWString(origin))); - allowedOrigins[allowedOriginsCount++] = origins.back()->c_str(); + origins.push_back(std::make_shared<WString>(convertStringToWString(origin))); + allowedOrigins[allowedOriginsCount++] = origins.back()->c_str(); } // store registratino refs here @@ -1021,9 +1021,9 @@ export * from '{{url}}' for (const auto& entry : this->schemeHandlers.handlers) { schemes.push_back(std::make_shared<WString>(convertStringToWString(entry.first))); - auto registration = Microsoft::WRL::Make<CoreWebView2CustomSchemeRegistration>( + auto registration = Microsoft::WRL::Make<CoreWebView2CustomSchemeRegistration>( schemes.back()->c_str() - ); + ); registration->SetAllowedOrigins(allowedOriginsCount, allowedOrigins); registration->put_HasAuthorityComponent(true); @@ -1037,7 +1037,7 @@ export * from '{{url}}' static_cast<ICoreWebView2CustomSchemeRegistration**>(registrations) ); } -#endif + #endif } void Bridge::configureNavigatorMounts () { diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 36ca3eba4a..592a63a7a0 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -234,6 +234,12 @@ namespace SSC::IPC { writable: true, value: globalThis.window ? 'window' : 'worker' }, + parent: { + configurable: false, + enumerable: true, + writable: false, + value: globalThis?.parent?.__args?.client ?? {} + }, frameType: { configurable: false, enumerable: true, From a60806015aa31e61f8d6a7e504045712e0f6fdf2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:30:01 +0200 Subject: [PATCH 0860/1178] fix(window): fix bad client state --- src/window/android.cc | 5 ++++- src/window/apple.mm | 11 +++++++++-- src/window/linux.cc | 5 ++++- src/window/win.cc | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index de4780559d..72606b5416 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -31,7 +31,10 @@ namespace SSC { }; this->bridge.client.preload = IPC::Preload::compile({ - .client = this->bridge.client, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/apple.mm b/src/window/apple.mm index 1615eb55c8..77eefcfb46 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -204,7 +204,10 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { }; this->bridge.client.preload = IPC::Preload::compile({ - .client = this->bridge.client, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, .index = options.index, .userScript = options.userScript }); @@ -289,11 +292,13 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } if (userConfig["permissions_allow_notifications"] == "false") { + #if SOCKET_RUNTIME_PLATFORM_MACOS @try { [preferences setValue: @NO forKey: @"appBadgeEnabled"]; } @catch (NSException *error) { - debug("Failed to set preference: 'deviceOrientationEventEnabled': %@", error); + debug("Failed to set preference: 'appBadgeEnabled': %@", error); } + #endif @try { [preferences setValue: @NO forKey: @"notificationsEnabled"]; @@ -307,11 +312,13 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { debug("Failed to set preference: 'notificationEventEnabled': %@", error); } } else { + #if SOCKET_RUNTIME_PLATFORM_MACOS @try { [preferences setValue: @YES forKey: @"appBadgeEnabled"]; } @catch (NSException *error) { debug("Failed to set preference: 'appBadgeEnabled': %@", error); } + #endif } #if SOCKET_RUNTIME_PLATFORM_MACOS diff --git a/src/window/linux.cc b/src/window/linux.cc index 79d767cb43..845b16ca93 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -236,7 +236,10 @@ namespace SSC { }; this->bridge.client.preload = IPC::Preload::compile({ - .client = this->bridge.client, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/win.cc b/src/window/win.cc index a8930773d2..a65bf04f7c 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -698,7 +698,7 @@ namespace SSC { this->bridge.client.preload = IPC::Preload::compile({ .client = UniqueClient { .id = this->bridge.client.id, - .index = this->bridge.client.index + .index = this->bridge.client.index }, .index = options.index, .userScript = options.userScript From e078815c139bbd449ec2e5457bee9a674ef620d6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:30:12 +0200 Subject: [PATCH 0861/1178] chore(package.json): upgrade deps --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ac888988c9..4892f63ee0 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ }, "private": true, "devDependencies": { - "acorn": "8.10.0", - "acorn-walk": "8.2.0", + "acorn": "8.12.0", + "acorn-walk": "8.3.3", "standard": "^17.1.0", - "typescript": "5.4.4", + "typescript": "5.5.2", "urlpattern-polyfill": "^10.0.0", "web-streams-polyfill": "^4.0.0", "whatwg-fetch": "^3.6.20", From 1c1521ab4486e100a835e212a4e858a8fbd8dd21 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:30:28 +0200 Subject: [PATCH 0862/1178] refactor(api/npm): clean up output --- api/npm/service-worker.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index 892f8c2f15..d5b0364b90 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -138,7 +138,12 @@ export async function onRequest (request, env, ctx) { export default module ` - return new Response(proxy, { + const source = proxy + .trim() + .split('\n') + .map((line) => line.trim()) + .join('\n') + return new Response(source, { headers: { 'content-type': 'text/javascript' } @@ -148,16 +153,18 @@ export async function onRequest (request, env, ctx) { if (resolved.type === 'commonjs') { const proxy = ` import { createRequire } from 'socket:module' - const require = createRequire('${resolved.origin}', { - headers: { - 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' - } - }) + const headers = { 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' } + const require = createRequire('${resolved.origin}', { headers }) const exports = require('${resolved.url}') export default exports?.default ?? exports ?? null - ` - - return new Response(proxy, { + `.trim() + + const source = proxy + .trim() + .split('\n') + .map((line) => line.trim()) + .join('\n') + return new Response(source, { headers: { 'content-type': 'text/javascript' } From 33791ae25edf3d6a24248bf4c349fd378ad8bb0e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:30:58 +0200 Subject: [PATCH 0863/1178] refactor(api/window.js): pickers should use normal runtime We should not prefer the webkit native extension for the file system dialog pickers as they should have full access to the user file system --- api/window.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/api/window.js b/api/window.js index 0d977b86a8..83f4739fc6 100644 --- a/api/window.js +++ b/api/window.js @@ -352,8 +352,6 @@ export class ApplicationWindow { const result = await ipc.request('window.showFileSystemPicker', { type: 'open', ...options - }, { - useExtensionIPCIfAvailable: true }) if (result.err) { @@ -372,8 +370,6 @@ export class ApplicationWindow { const result = await ipc.request('window.showFileSystemPicker', { type: 'save', ...options - }, { - useExtensionIPCIfAvailable: true }) if (result.err) { @@ -393,8 +389,6 @@ export class ApplicationWindow { type: 'open', allowDirs: true, ...options - }, { - useExtensionIPCIfAvailable: true }) if (result.err) { From 873b032af7c5481d74a57ce9454d040f099c473f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 12:32:56 +0200 Subject: [PATCH 0864/1178] refactor(api/internal/init.js): clean up, propagate 'client.parent' to workers --- api/internal/init.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 5a5da79ad8..3a10fa3c64 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -262,10 +262,10 @@ if ((globalThis.window) === globalThis) { let start = null const initialHeight = document.body.offsetHeight - const animate = (timestamp) => { + window.requestAnimationFrame(function animate (timestamp) { if (!start) start = timestamp const elapsed = timestamp - start - const progress = Math.min(elapsed / duration, 1) + let progress = Math.min(elapsed / duration, 1) const easeProgress = bezierHide(progress) const currentHeight = initialHeight + (easeProgress * keyboardHeight) if (currentHeight <= 0) progress = 1 @@ -277,8 +277,7 @@ if ((globalThis.window) === globalThis) { } else { isKeyboardOpen = false } - } - window.requestAnimationFrame(animate) + }) } }) } @@ -326,6 +325,7 @@ class RuntimeWorker extends GlobalWorker { globalThis.__args.client.id = '${id}' globalThis.__args.client.type = 'worker' globalThis.__args.client.frameType = 'none' + globalThis.__args.client.parent = ${JSON.stringify(globalThis.__args.client)} Object.defineProperty(globalThis, 'isWorkerScope', { configurable: false, From 958e6aa34e30d3448feee4cd8cdab678f83c5a25 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 13:01:57 +0200 Subject: [PATCH 0865/1178] feat(api/fs/bookmarks.js): initial fs bookmarking --- api/fs/bookmarks.js | 12 ++++++++++++ api/fs/index.js | 2 ++ api/fs/promises.js | 2 ++ api/fs/web.js | 21 ++++++++++++--------- 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 api/fs/bookmarks.js diff --git a/api/fs/bookmarks.js b/api/fs/bookmarks.js new file mode 100644 index 0000000000..46af490ccb --- /dev/null +++ b/api/fs/bookmarks.js @@ -0,0 +1,12 @@ +/** + * A map of known absolute file paths to file IDs that + * have been granted access outside of the sandbox. + * XXX(@jwerle): this is currently only used on linux, but valaues may + * be added for all platforms, likely from a file system picker dialog. + * @type {Map<string, string>} + */ +export const temporary = new Map() + +export default { + temporary +} diff --git a/api/fs/index.js b/api/fs/index.js index a61ae091ab..797e42d866 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -37,6 +37,7 @@ import * as constants from './constants.js' import * as promises from './promises.js' import { Watcher } from './watcher.js' import { Stats } from './stats.js' +import bookmarks from './bookmarks.js' import fds from './fds.js' import * as exports from './index.js' @@ -1506,6 +1507,7 @@ export function watch (path, options, callback = null) { // re-exports export { + bookmarks, constants, Dir, DirectoryHandle, diff --git a/api/fs/promises.js b/api/fs/promises.js index bd581d036f..9d08cd3003 100644 --- a/api/fs/promises.js +++ b/api/fs/promises.js @@ -30,12 +30,14 @@ import { ReadStream, WriteStream } from './stream.js' import * as constants from './constants.js' import { Watcher } from './watcher.js' import { Stats } from './stats.js' +import bookmarks from './bookmarks.js' import fds from './fds.js' import * as exports from './promises.js' // re-exports export { + bookmarks, constants, Dir, DirectoryHandle, diff --git a/api/fs/web.js b/api/fs/web.js index c63ec8fb42..cf9dde1fe5 100644 --- a/api/fs/web.js +++ b/api/fs/web.js @@ -6,8 +6,9 @@ import mime from '../mime.js' import path from '../path.js' import fs from './promises.js' -const kFileSystemHandleFullName = Symbol('kFileSystemHandleFullName') -const kFileFullName = Symbol('kFileFullName') +export const kFileSystemHandleFullName = Symbol('kFileSystemHandleFullName') +export const kFileDescriptor = Symbol('kFileDescriptor') +export const kFileFullName = Symbol('kFileFullName') // @ts-ignore export const File = globalThis.File ?? @@ -109,8 +110,11 @@ export async function createFile (filename, options = null) { ? options.highWaterMark : Math.min(stats.size, DEFAULT_STREAM_HIGH_WATER_MARK) + let fd = options?.fd ?? null + return create(File, class File { get [kFileFullName] () { return filename } + get [kFileDescriptor] () { return fd } get lastModifiedDate () { return new Date(stats.mtimeMs) } get lastModified () { return stats.mtimeMs } @@ -151,7 +155,6 @@ export async function createFile (filename, options = null) { stream () { let buffer = null let offset = 0 - let fd = null const stream = new ReadableStream({ async start (controller) { @@ -256,6 +259,8 @@ export async function createFileSystemWritableFileStream (handle, options) { // @ts-ignore return create(FileSystemWritableFileStream, class FileSystemWritableFileStream { + get [kFileDescriptor] () { return fd } + async seek (position) { offset = position } @@ -338,9 +343,8 @@ export async function createFileSystemFileHandle (file, options = null) { } return create(FileSystemFileHandle, class FileSystemFileHandle { - get [kFileSystemHandleFullName] () { - return file[kFileFullName] - } + get [kFileSystemHandleFullName] () { return file[kFileFullName] } + get [kFileDescriptor] () { return file[kFileDescriptor] } get name () { return file.name @@ -447,9 +451,8 @@ export async function createFileSystemDirectoryHandle (dirname, options = null) let fd = null return create(FileSystemDirectoryHandle, class FileSystemDirectoryHandle { - get [kFileSystemHandleFullName] () { - return dirname - } + get [kFileSystemHandleFullName] () { return dirname } + get [kFileDescriptor] () { return fd } get name () { return path.basename(dirname) From a1fea0026bfe1e15e04e154b1cc7e48bbdfbd07c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 13:02:56 +0200 Subject: [PATCH 0866/1178] refactor(api/internal/pickers.js): store fs bookmarks from pickers --- api/internal/pickers.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/internal/pickers.js b/api/internal/pickers.js index d06708dcf8..280987a05d 100644 --- a/api/internal/pickers.js +++ b/api/internal/pickers.js @@ -8,6 +8,8 @@ import { FileSystemHandle } from '../fs/web.js' +import bookmarks from '../fs/bookmarks.js' + /** * Key-value store for general usage by the file pickers" * @ignore @@ -168,7 +170,10 @@ export async function showDirectoryPicker (options = null) { db.set(id, dirname) } - return await createFileSystemDirectoryHandle(dirname) + bookmarks.temporary.set(dirname, null) // placehold `null` + + const result = await createFileSystemDirectoryHandle(dirname) + return result } /** @@ -215,7 +220,11 @@ export async function showOpenFilePicker (options = null) { const handles = [] for (const filename of filenames) { - handles.push(await createFileSystemFileHandle(filename, { writable: false })) + bookmarks.temporary.set(filename, null) + } + + for (const filename of filenames) { + hnameandles.push(await createFileSystemFileHandle(filename, { writable: false })) } return handles @@ -263,6 +272,8 @@ export async function showSaveFilePicker (options = null) { db.set(id, filename) } + bookmarks.temporary.set(filename, null) + return await createFileSystemFileHandle(filename) } From 01c4654b4f289c93f18abf23a8dfafcfe5fc4bc0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 13:03:22 +0200 Subject: [PATCH 0867/1178] refactor(api/ipc.js): hook into fs bookmarks when applicable --- api/ipc.js | 186 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 150 insertions(+), 36 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 352cf585bd..acacbbb39e 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -53,6 +53,7 @@ import { import * as errors from './errors.js' import { Buffer } from './buffer.js' import { rand64 } from './crypto.js' +import bookmarks from './fs/bookmarks.js' import { URL } from './url.js' let nextSeq = 1 @@ -84,7 +85,6 @@ function initializeXHRIntercept () { async send (body) { const { method, seq, url } = this - const index = globalThis.__args?.index || 0 if (url?.protocol === 'ipc:') { if ( @@ -236,6 +236,35 @@ function getRequestResponse (request, options) { return response } +function getFileSystemBookmarkName (options) { + const names = [ + options.params.get('src'), + options.params.get('path'), + options.params.get('value') + ] + return names.find(Boolean) ?? null +} + +function isFileSystemBookmark (options) { + const names = [ + options.params.get('src'), + options.params.get('path'), + options.params.get('value') + ].filter(Boolean) + + if (names.some((name) => bookmarks.temporary.has(name))) { + return true + } + + for (const [, fd] of bookmarks.temporary.entries()) { + if (fd === options.params.get('id') || fd === options.params.get('fd')) { + return true + } + } + + return false +} + export function maybeMakeError (error, caller) { const errors = { AbortError: getErrorClass('AbortError'), @@ -988,7 +1017,7 @@ export async function ready () { const { toString } = Object.prototype -class IPCSearchParams extends URLSearchParams { +export class IPCSearchParams extends URLSearchParams { constructor (params, nonce) { let value if (params !== undefined && toString.call(params) !== '[object Object]') { @@ -1076,20 +1105,29 @@ export function sendSync (command, value = '', options = {}, buffer = null) { typeof __global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { - let response = null - try { - response = __global_ipc_extension_handler(uri) - } catch (err) { - return Result.from(null, err) - } + // eslint-disable-next-line + do { + if (command.startsWith('fs.')) { + if (isFileSystemBookmark({ params })) { + break + } + } - if (typeof response === 'string') { + let response = null try { - response = JSON.parse(response) - } catch {} - } + response = __global_ipc_extension_handler(uri) + } catch (err) { + return Result.from(null, err) + } - return Result.from(response, null, command) + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch {} + } + + return Result.from(response, null, command) + } while (0) } const request = new globalThis.XMLHttpRequest() @@ -1120,6 +1158,16 @@ export function sendSync (command, value = '', options = {}, buffer = null) { cache[command] = result } + if (command.startsWith('fs.') && isFileSystemBookmark({ params })) { + if (!result.err) { + const id = result.data?.id ?? result.data?.fd + const name = getFileSystemBookmarkName({ params }) + if (id && name) { + bookmarks.temporary.set(name, id) + } + } + } + return result } @@ -1203,6 +1251,35 @@ export async function send (command, value, options = null) { const params = new IPCSearchParams(value) const uri = `ipc://${command}?${params}` + if ( + typeof __global_ipc_extension_handler === 'function' && + (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) + ) { + // eslint-disable-next-line + do { + if (command.startsWith('fs.')) { + if (isFileSystemBookmark({ params })) { + break + } + } + + let response = null + try { + response = await __global_ipc_extension_handler(uri) + } catch (err) { + return Result.from(null, err) + } + + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch {} + } + + return Result.from(response, null, command) + } while (0) + } + if (options?.bytes) { postMessage(uri, options.bytes) } else { @@ -1222,6 +1299,16 @@ export async function send (command, value, options = null) { cache[command] = result } + if (command.startsWith('fs.') && isFileSystemBookmark({ params })) { + if (!result.err) { + const id = result.data?.id ?? result.data?.fd + const name = getFileSystemBookmarkName({ params }) + if (id && name) { + bookmarks.temporary.set(name, id) + } + } + } + resolve(result) } }) @@ -1250,20 +1337,29 @@ export async function write (command, value, buffer, options) { typeof __global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { - let response = null - try { - response = await __global_ipc_extension_handler(uri, buffer) - } catch (err) { - return Result.from(null, err) - } + // eslint-disable-next-line + do { + if (command.startsWith('fs.')) { + if (isFileSystemBookmark({ params })) { + break + } + } - if (typeof response === 'string') { + let response = null try { - response = JSON.parse(response) - } catch {} - } + response = await __global_ipc_extension_handler(uri, buffer) + } catch (err) { + return Result.from(null, err) + } + + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch {} + } - return Result.from(response, null, command) + return Result.from(response, null, command) + } while (0) } const signal = options?.signal @@ -1368,20 +1464,29 @@ export async function request (command, value, options) { typeof __global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { - let response = null - try { - response = await __global_ipc_extension_handler(uri) - } catch (err) { - return Result.from(null, err) - } + // eslint-disable-next-line + do { + if (command.startsWith('fs.')) { + if (isFileSystemBookmark({ params })) { + break + } + } - if (typeof response === 'string') { + let response = null try { - response = JSON.parse(response) - } catch {} - } + response = await __global_ipc_extension_handler(uri) + } catch (err) { + return Result.from(null, err) + } - return Result.from(response, null, command) + if (typeof response === 'string') { + try { + response = JSON.parse(response) + } catch {} + } + + return Result.from(response, null, command) + } while (0) } const signal = options?.signal @@ -1450,6 +1555,16 @@ export async function request (command, value, options) { cache[command] = result } + if (command.startsWith('fs.') && isFileSystemBookmark({ params })) { + if (!result.err) { + const id = result.data?.id ?? result.data?.fd + const name = getFileSystemBookmarkName({ params }) + if (id && name) { + bookmarks.temporary.set(name, id) + } + } + } + return resolve(result) } } @@ -1549,7 +1664,6 @@ if ( Object.freeze(primordials) - initializeXHRIntercept() if (typeof globalThis?.window !== 'undefined') { From 82ae40e2ccfabf951d472d18cdb64bf1472b8910 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 13:06:01 +0200 Subject: [PATCH 0868/1178] doc(api): generate docs --- api/README.md | 167 +++++++++++++++++++------------------ api/diagnostics/runtime.js | 1 + 2 files changed, 87 insertions(+), 81 deletions(-) diff --git a/api/README.md b/api/README.md index 6e2ab536aa..e380fd4d5b 100644 --- a/api/README.md +++ b/api/README.md @@ -811,7 +811,7 @@ External docs: https://nodejs.org/api/events.html import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L108) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L109) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -823,7 +823,7 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L133) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L134) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` @@ -834,7 +834,7 @@ Synchronously check access a file for a given mode calling `callback` | path | string \| Buffer \| URL | | false | | | mode | string? | F_OK(0) | true | | -## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L150) +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L151) Checks if a path exists @@ -843,7 +843,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L167) +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L168) Checks if a path exists @@ -852,7 +852,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L187) +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L188) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -866,7 +866,7 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L213) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L214) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Synchronously changes the permissions of a file. @@ -877,7 +877,7 @@ Synchronously changes the permissions of a file. | path | string \| Buffer \| URL | | false | | | mode | number | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L237) +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L238) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -888,7 +888,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L266) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L267) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -898,7 +898,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | uid | number | | false | | | gid | number | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L293) +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L294) External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -908,7 +908,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L313) +## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L314) Synchronously close a file descriptor. @@ -916,7 +916,7 @@ Synchronously close a file descriptor. | :--- | :--- | :---: | :---: | :--- | | fd | number | | false | fd | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L330) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L331) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -928,7 +928,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L362) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L363) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -939,7 +939,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L391) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L392) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -953,7 +953,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L436) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L437) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -967,7 +967,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L484) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L485) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -981,7 +981,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L511) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L512) Request that all data for the open file descriptor is flushed to the storage device. @@ -991,7 +991,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L533) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L534) Truncates the file up to `offset` bytes. @@ -1001,7 +1001,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L561) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L562) Chages ownership of link at `path` with `uid` and `gid. @@ -1012,7 +1012,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L591) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L592) Creates a link to `dest` from `src`. @@ -1022,7 +1022,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L679) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L680) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1035,7 +1035,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L732) +## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L733) Synchronously open a file. @@ -1046,7 +1046,7 @@ Synchronously open a file. | mode | string? | 0o666 | true | | | options | object? \| function? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L779) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L780) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1059,7 +1059,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L806) +## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L807) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously open a directory. @@ -1075,7 +1075,7 @@ Synchronously open a directory. | :--- | :--- | :--- | | Not specified | Dir | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L833) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L834) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1089,7 +1089,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L868) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L869) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1103,7 +1103,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L902) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L903) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1116,7 +1116,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L954) +## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L955) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously read all entries in a directory. @@ -1128,7 +1128,7 @@ Synchronously read all entries in a directory. | options.encoding ? utf8 | string? | | true | | | options.withFileTypes ? false | boolean? | | true | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L984) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L985) @@ -1141,7 +1141,7 @@ Synchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1026) +## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1027) @@ -1152,7 +1152,7 @@ Synchronously read all entries in a directory. | options | object? \| function(Error?, Buffer?) | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1089) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1090) Reads link at `path` @@ -1161,7 +1161,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1109) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1110) Computes real path for `path` @@ -1170,7 +1170,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1127) +## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1128) Computes real path for `path` @@ -1178,7 +1178,7 @@ Computes real path for `path` | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1145) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1146) Renames file or directory at `src` to `dest`. @@ -1188,7 +1188,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1171) +## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1172) Renames file or directory at `src` to `dest`, synchronously. @@ -1197,7 +1197,7 @@ Renames file or directory at `src` to `dest`, synchronously. | src | string | | false | | | dest | string | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1195) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1196) Removes directory at `path`. @@ -1206,7 +1206,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1215) +## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1216) Removes directory at `path`, synchronously. @@ -1214,7 +1214,7 @@ Removes directory at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1236) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1237) Synchronously get the stats of a file @@ -1225,7 +1225,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1256) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1257) Get the stats of a file @@ -1238,7 +1238,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1294) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1295) Get the stats of a symbolic link @@ -1251,7 +1251,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1328) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1329) Creates a symlink of `src` at `dest`. @@ -1260,7 +1260,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1371) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1372) Unlinks (removes) file at `path`. @@ -1269,7 +1269,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1391) +## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1392) Unlinks (removes) file at `path`, synchronously. @@ -1277,7 +1277,7 @@ Unlinks (removes) file at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1416) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1417) @@ -1292,7 +1292,7 @@ Unlinks (removes) file at `path`, synchronously. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1461) +## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1462) External docs: https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options Writes data to a file synchronously. @@ -1307,7 +1307,7 @@ Writes data to a file synchronously. | options.flag ? w | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1496) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1497) Watch for changes at `path` calling `callback` @@ -1350,7 +1350,7 @@ Watch for changes at `path` calling `callback` import fs from 'socket:fs/promises' ``` -## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L105) +## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L107) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode Asynchronously check access a file. @@ -1361,7 +1361,7 @@ Asynchronously check access a file. | mode | string? | | true | | | options | object? | | true | | -## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L116) +## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L118) External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode @@ -1375,7 +1375,7 @@ External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L141) +## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L143) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -1389,7 +1389,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L170) +## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L172) Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -1403,7 +1403,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | :--- | :--- | :--- | | Not specified | Promise | | -## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L200) +## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L202) Chages ownership of link at `path` with `uid` and `gid. @@ -1417,7 +1417,7 @@ Chages ownership of link at `path` with `uid` and `gid. | :--- | :--- | :--- | | Not specified | Promise | | -## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L228) +## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L230) Creates a link to `dest` from `dest`. @@ -1430,7 +1430,7 @@ Creates a link to `dest` from `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L256) +## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L258) Asynchronously creates a directory. @@ -1446,7 +1446,7 @@ Asynchronously creates a directory. | :--- | :--- | :--- | | Not specified | Promise<any> | Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. | -## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L286) +## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L288) External docs: https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode Asynchronously open a file. @@ -1462,7 +1462,7 @@ Asynchronously open a file. | :--- | :--- | :--- | | Not specified | Promise<FileHandle> | | -## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L299) +## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L301) External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options @@ -1478,7 +1478,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options | :--- | :--- | :--- | | Not specified | Promise<Dir> | | -## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L312) +## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L314) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options @@ -1490,7 +1490,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | options.encoding | string? | utf8 | true | | | options.withFileTypes | boolean? | false | true | | -## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L350) +## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L352) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options @@ -1507,7 +1507,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | :--- | :--- | :--- | | Not specified | Promise<Buffer \| string> | | -## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L368) +## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L370) Reads link at `path` @@ -1519,7 +1519,7 @@ Reads link at `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L389) +## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L391) Computes real path for `path` @@ -1531,7 +1531,7 @@ Computes real path for `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L411) +## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L413) Renames file or directory at `src` to `dest`. @@ -1544,7 +1544,7 @@ Renames file or directory at `src` to `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L435) +## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L437) Removes directory at `path`. @@ -1556,7 +1556,7 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L457) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L459) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options Get the stats of a file @@ -1571,7 +1571,7 @@ Get the stats of a file | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L472) +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L474) External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options Get the stats of a symbolic link. @@ -1586,7 +1586,7 @@ Get the stats of a symbolic link. | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L485) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L487) Creates a symlink of `src` at `dest`. @@ -1599,7 +1599,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L521) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L523) Unlinks (removes) file at `path`. @@ -1611,7 +1611,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L546) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L548) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1630,7 +1630,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L567) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L569) Watch for changes at `path` calling `callback` @@ -1683,12 +1683,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L272) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L268) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1166) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1020) + +This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. + + +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1181) Emit event to be dispatched on `window` object. @@ -1699,7 +1704,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1225) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1240) Sends an async IPC command request with parameters. @@ -2923,7 +2928,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L371) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L369) Shows a native save file dialog. @@ -2935,7 +2940,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L391) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L387) Shows a native directory dialog. @@ -2947,7 +2952,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L418) +### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L412) This is a high-level API that you should use instead of `ipc.request` when you want to send a message to another window or to the backend. @@ -2961,7 +2966,7 @@ This is a high-level API that you should use instead of `ipc.request` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L459) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L453) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -2974,7 +2979,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L478) +### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L472) Opens an URL in the default application associated with the URL protocol, such as 'https:' for the default web browser. @@ -2987,7 +2992,7 @@ Opens an URL in the default application associated with the URL protocol, | :--- | :--- | :--- | | Not specified | Promise<{ url: string | >} | -### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L493) +### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L487) Opens a file in the default file explorer. @@ -2999,7 +3004,7 @@ Opens a file in the default file explorer. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L508) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L502) Adds a listener to the window. @@ -3008,7 +3013,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L526) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L520) Adds a listener to the window. An alias for `addListener`. @@ -3017,7 +3022,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L543) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L537) Adds a listener to the window. The listener is removed after the first call. @@ -3026,7 +3031,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L559) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L553) Removes a listener from the window. @@ -3035,7 +3040,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L572) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L566) Removes all listeners from the window. @@ -3043,7 +3048,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L588) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L582) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/diagnostics/runtime.js b/api/diagnostics/runtime.js index 5b42a301d8..f1d9ef0844 100644 --- a/api/diagnostics/runtime.js +++ b/api/diagnostics/runtime.js @@ -176,6 +176,7 @@ export class TimersDiagnostic extends Diagnostic { export class UDPDiagnostic extends Diagnostic {} /** + * A container for various queried runtime diagnostics. */ export class QueryDiagnostic { posts = new PostsDiagnostic() From f47610af1fdff8f199866520ab1dba7aac2fc290 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 13:37:23 +0200 Subject: [PATCH 0869/1178] refactor(api/internal): clean up --- api/internal/init.js | 90 ++++++++++++++++++++++------------------- api/internal/pickers.js | 6 ++- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 3a10fa3c64..4952e4633a 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -1,4 +1,4 @@ -/* global Blob, DataTransfer, DragEvent, FileList, MessageEvent, reportError */ +/* global requestAnimationFrame, Blob, DataTransfer, DragEvent, FileList, MessageEvent, reportError */ /* eslint-disable import/first */ // mark when runtime did init console.assert( @@ -25,7 +25,7 @@ import { rand64 } from '../crypto.js' import location from '../location.js' import mime from '../mime.js' import path from '../path.js' -import process from '../process.js' +import os from '../process.js' import fs from '../fs/promises.js' import { createFileSystemDirectoryHandle, @@ -211,71 +211,79 @@ if ((globalThis.window) === globalThis) { } }) - if (process.platform === 'ios') { - let keyboardHeight - let isKeyboardOpen = false - let initialHeight = 0 - const duration = 346 - - const bezierShow = t => { - const p1 = 0.9 - const p2 = 0.95 - return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + // TODO: move this somewhere more appropriate + if (os.platform() === 'ios') { + const timing = { + duration: 346 // TODO: document this } - const bezierHide = t => { - const p1 = 0.86 - const p2 = 0.95 - return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + const keyboard = { + opened: false, + offset: 0, + height: 0 } - globalThis.window.addEventListener('keyboard', ({ detail }) => { - if (initialHeight === 0) { - initialHeight = document.body.offsetHeight + const bezier = { + show (t) { + const p1 = 0.9 + const p2 = 0.95 + return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t + }, + + hide (t) { + const p1 = 0.86 + const p2 = 0.95 + return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t } + } - if (detail.value.event === 'will-show') { - if (isKeyboardOpen) return + globalThis.addEventListener('keyboard', function (event) { + const { detail } = event - keyboardHeight = detail.value.height + if (keyboard.offset === 0) { + keyboard.offset = document.body.offsetHeight + } + + if (detail.value.event === 'will-show' && !keyboard.opened) { + keyboard.height = detail.value.height let start = null - const animate = (timestamp) => { + requestAnimationFrame(function animate (timestamp) { if (!start) start = timestamp const elapsed = timestamp - start - const progress = Math.min(elapsed / duration, 1) - const easeProgress = bezierShow(progress) - const currentHeight = initialHeight - (easeProgress * keyboardHeight) + const progress = Math.min(elapsed / timing.duration, 1) + const easeProgress = bezier.show(progress) + const currentHeight = keyboard.offset - (easeProgress * keyboard.height) - document.body.style.height = `${currentHeight}px` + globalThis.document.body.style.height = `${currentHeight}px` if (progress < 1) { - isKeyboardOpen = true - window.requestAnimationFrame(animate) + keyboard.opened = true + requestAnimationFrame(animate) } - } - window.requestAnimationFrame(animate) + }) } - if (detail.value.event === 'will-hide') { - isKeyboardOpen = false + if (detail.value.event === 'will-hide' && keyboard.opened) { + keyboard.opened = false + keyboard.offset = globalThis.document.body.offsetHeight + let start = null - const initialHeight = document.body.offsetHeight - window.requestAnimationFrame(function animate (timestamp) { + requestAnimationFrame(function animate (timestamp) { if (!start) start = timestamp const elapsed = timestamp - start - let progress = Math.min(elapsed / duration, 1) - const easeProgress = bezierHide(progress) - const currentHeight = initialHeight + (easeProgress * keyboardHeight) + let progress = Math.min(elapsed / timing.duration, 1) + const easeProgress = bezier.hide(progress) + const currentHeight = keyboard.offset + (easeProgress * keyboard.height) if (currentHeight <= 0) progress = 1 - document.body.style.height = `${currentHeight}px` + globalThis.document.body.style.height = `${currentHeight}px` if (progress < 1) { - window.requestAnimationFrame(animate) + requestAnimationFrame(animate) } else { - isKeyboardOpen = false + keyboard.opened = false } }) } diff --git a/api/internal/pickers.js b/api/internal/pickers.js index 280987a05d..43c1a4b85b 100644 --- a/api/internal/pickers.js +++ b/api/internal/pickers.js @@ -224,7 +224,11 @@ export async function showOpenFilePicker (options = null) { } for (const filename of filenames) { - hnameandles.push(await createFileSystemFileHandle(filename, { writable: false })) + const handle = await createFileSystemFileHandle(filename, { + writable: false + }) + + handles.push(handle) } return handles From 689b58c4c35213656bcd07478ab441a5c3fd75cf Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 13:37:30 +0200 Subject: [PATCH 0870/1178] chore(api/index.d.ts): generate typings --- api/index.d.ts | 881 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 782 insertions(+), 99 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index e745d6a96e..4d4b2bb6cb 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -565,6 +565,21 @@ declare module "socket:buffer" { function byteLength(string: any, encoding: any, ...args: any[]): any; } +declare module "socket:fs/bookmarks" { + /** + * A map of known absolute file paths to file IDs that + * have been granted access outside of the sandbox. + * XXX(@jwerle): this is currently only used on linux, but valaues may + * be added for all platforms, likely from a file system picker dialog. + * @type {Map<string, string>} + */ + export const temporary: Map<string, string>; + namespace _default { + export { temporary }; + } + export default _default; +} + declare module "socket:url/urlpattern/urlpattern" { export { me as URLPattern }; var me: { @@ -637,32 +652,6 @@ declare module "socket:url/index" { export function resolve(from: any, to: any): any; export function format(input: any): any; export function fileURLToPath(url: any): any; - const URLPattern_base: { - new (t: {}, r: any, n: any): { - "__#11@#i": any; - "__#11@#n": {}; - "__#11@#t": {}; - "__#11@#e": {}; - "__#11@#s": {}; - "__#11@#l": boolean; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - readonly hasRegExpGroups: boolean; - }; - compareComponent(t: any, r: any, n: any): number; - }; - export class URLPattern extends URLPattern_base { - } export const protocols: Set<string>; export default URL; export class URL { @@ -670,6 +659,8 @@ declare module "socket:url/index" { } export const URLSearchParams: any; export const parseURL: any; + import { URLPattern } from "socket:url/urlpattern/urlpattern"; + export { URLPattern }; } declare module "socket:url" { @@ -1053,6 +1044,9 @@ declare module "socket:ipc" { */ [Symbol.iterator](): Generator<any, void, unknown>; } + export class IPCSearchParams extends URLSearchParams { + constructor(params: any, nonce: any); + } /** * @ignore */ @@ -1288,7 +1282,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2052,7 +2046,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2412,9 +2406,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -2448,6 +2442,668 @@ declare module "socket:diagnostics/window" { import { Metric } from "socket:diagnostics/metric"; } +declare module "socket:diagnostics/runtime" { + /** + * Queries runtime diagnostics. + * @return {Promise<QueryDiagnostic>} + */ + export function query(): Promise<QueryDiagnostic>; + /** + * A base container class for diagnostic information. + */ + export class Diagnostic { + /** + * A container for handles related to the diagnostics + */ + static Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + } + /** + * A container for libuv diagnostics + */ + export class UVDiagnostic extends Diagnostic { + /** + * A container for libuv metrics. + */ + static Metrics: { + new (): { + /** + * The number of event loop iterations. + * @type {number} + */ + loopCount: number; + /** + * Number of events that have been processed by the event handler. + * @type {number} + */ + events: number; + /** + * Number of events that were waiting to be processed when the + * event provider was called. + * @type {number} + */ + eventsWaiting: number; + }; + }; + /** + * Known libuv metrics for this diagnostic. + * @type {UVDiagnostic.Metrics} + */ + metrics: { + new (): { + /** + * The number of event loop iterations. + * @type {number} + */ + loopCount: number; + /** + * Number of events that have been processed by the event handler. + * @type {number} + */ + events: number; + /** + * Number of events that were waiting to be processed when the + * event provider was called. + * @type {number} + */ + eventsWaiting: number; + }; + }; + /** + * The current idle time of the libuv loop + * @type {number} + */ + idleTime: number; + /** + * The number of active requests in the libuv loop + * @type {number} + */ + activeRequests: number; + } + /** + * A container for Core Post diagnostics. + */ + export class PostsDiagnostic extends Diagnostic { + } + /** + * A container for child process diagnostics. + */ + export class ChildProcessDiagnostic extends Diagnostic { + } + /** + * A container for AI diagnostics. + */ + export class AIDiagnostic extends Diagnostic { + /** + * A container for AI LLM diagnostics. + */ + static LLMDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * Known AI LLM diagnostics. + * @type {AIDiagnostic.LLMDiagnostic} + */ + llm: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + } + /** + * A container for various filesystem diagnostics. + */ + export class FSDiagnostic extends Diagnostic { + /** + * A container for filesystem watcher diagnostics. + */ + static WatchersDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for filesystem descriptors diagnostics. + */ + static DescriptorsDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * Known FS watcher diagnostics. + * @type {FSDiagnostic.WatchersDiagnostic} + */ + watchers: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {FSDiagnostic.DescriptorsDiagnostic} + */ + descriptors: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + } + /** + * A container for various timers diagnostics. + */ + export class TimersDiagnostic extends Diagnostic { + /** + * A container for core timeout timer diagnostics. + */ + static TimeoutDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for core interval timer diagnostics. + */ + static IntervalDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for core immediate timer diagnostics. + */ + static ImmediateDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {TimersDiagnostic.TimeoutDiagnostic} + */ + timeout: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {TimersDiagnostic.IntervalDiagnostic} + */ + interval: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {TimersDiagnostic.ImmediateDiagnostic} + */ + immediate: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + } + /** + * A container for UDP diagnostics. + */ + export class UDPDiagnostic extends Diagnostic { + } + /** + * A container for various queried runtime diagnostics. + */ + export class QueryDiagnostic { + posts: PostsDiagnostic; + childProcess: ChildProcessDiagnostic; + ai: AIDiagnostic; + fs: FSDiagnostic; + timers: TimersDiagnostic; + udp: UDPDiagnostic; + uv: UVDiagnostic; + } + namespace _default { + export { query }; + } + export default _default; +} + declare module "socket:diagnostics/index" { /** * @param {string} name @@ -2458,8 +3114,9 @@ declare module "socket:diagnostics/index" { import * as exports from "socket:diagnostics/index"; import channels from "socket:diagnostics/channels"; import window from "socket:diagnostics/window"; + import runtime from "socket:diagnostics/runtime"; - export { channels, window }; + export { channels, window, runtime }; } declare module "socket:diagnostics" { @@ -3130,7 +3787,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3365,7 +4022,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3539,7 +4196,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3920,7 +4577,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } @@ -4039,7 +4696,27 @@ declare module "socket:path/path" { * @param {string} [cwd = Path.cwd()] */ protected constructor(); - pattern: URLPattern; + pattern: { + "__#11@#i": any; + "__#11@#n": {}; + "__#11@#t": {}; + "__#11@#e": {}; + "__#11@#s": {}; + "__#11@#l": boolean; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + readonly hasRegExpGroups: boolean; + }; url: any; get pathname(): any; get protocol(): any; @@ -4124,7 +4801,6 @@ declare module "socket:path/path" { } | { url: string; }); - import { URLPattern } from "socket:url/index"; import { URL } from "socket:url/index"; } @@ -4558,7 +5234,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -5407,7 +6083,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -5627,6 +6303,7 @@ declare module "socket:fs/promises" { import { Dirent } from "socket:fs/dir"; import { Stats } from "socket:fs/stats"; import { Watcher } from "socket:fs/watcher"; + import bookmarks from "socket:fs/bookmarks"; import * as constants from "socket:fs/constants"; import { DirectoryHandle } from "socket:fs/handle"; import fds from "socket:fs/fds"; @@ -5634,7 +6311,7 @@ declare module "socket:fs/promises" { import { WriteStream } from "socket:fs/stream"; import * as exports from "socket:fs/promises"; - export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, ReadStream, Watcher, WriteStream }; + export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, ReadStream, Watcher, WriteStream }; } declare module "socket:fs/index" { @@ -6021,6 +6698,7 @@ declare module "socket:fs/index" { import * as promises from "socket:fs/promises"; import { Stats } from "socket:fs/stats"; import { Watcher } from "socket:fs/watcher"; + import bookmarks from "socket:fs/bookmarks"; import * as constants from "socket:fs/constants"; import { DirectoryHandle } from "socket:fs/handle"; import { Dirent } from "socket:fs/dir"; @@ -6028,7 +6706,7 @@ declare module "socket:fs/index" { import { FileHandle } from "socket:fs/handle"; import * as exports from "socket:fs/index"; - export { constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; + export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; } declare module "socket:fs" { @@ -6293,7 +6971,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -6506,6 +7184,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -7793,7 +8472,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -8580,6 +9259,9 @@ declare module "socket:fs/web" { * @return {Promise<FileSystemFileHandle>} */ export function createFileSystemDirectoryHandle(dirname: string, options?: any): Promise<FileSystemFileHandle>; + export const kFileSystemHandleFullName: unique symbol; + export const kFileDescriptor: unique symbol; + export const kFileFullName: unique symbol; export const File: { new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; prototype: File; @@ -8700,7 +9382,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -8775,7 +9457,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -9271,7 +9953,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -9335,7 +10017,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -9462,7 +10144,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -9503,7 +10185,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -9686,7 +10368,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -9721,13 +10403,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -11733,7 +12415,7 @@ declare module "socket:latica/index" { uptime: number; maxHops: number; bdpCache: number[]; - dgram: () => never; + dgram: any; onListening: any; onDelete: any; sendQueue: any[]; @@ -11753,7 +12435,7 @@ declare module "socket:latica/index" { 6: number; 7: number; 8: number; - REJECTED: number; + DROPPED: number; }; o: { 0: number; @@ -11799,7 +12481,7 @@ declare module "socket:latica/index" { * @ignore */ _setTimeout(fn: any, t: any): number; - _debug(pid: any, ...args: any[]): void; + _onDebug(...args: any[]): void; /** * A method that encapsulates the listing procedure * @return {undefined} @@ -12070,6 +12752,7 @@ declare module "socket:latica/proxy" { reconnect(): Promise<any>; disconnect(): Promise<any>; getInfo(): Promise<any>; + getMetrics(): Promise<any>; getState(): Promise<any>; open(...args: any[]): Promise<any>; seal(...args: any[]): Promise<any>; @@ -12439,7 +13122,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -12447,7 +13130,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -12696,7 +13379,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -12707,7 +13390,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -14101,7 +14784,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -14112,13 +14795,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -14201,7 +14884,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -14245,7 +14928,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -14605,7 +15288,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -14643,9 +15326,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:module" { @@ -14985,7 +15668,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -15153,9 +15836,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -15823,7 +16506,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -15869,9 +16552,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; }; } export namespace Promise { @@ -15943,7 +16626,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -15963,7 +16646,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -15985,8 +16668,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -15996,10 +16679,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -16100,7 +16783,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -16109,7 +16792,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -16190,8 +16873,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -16205,7 +16888,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -16578,12 +17261,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -16636,7 +17319,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -16653,8 +17336,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From b9e5165e987813e9ee62dc0c11045e47620f6826 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 14:04:55 +0200 Subject: [PATCH 0871/1178] fix(core/modules/diagnostics.cc): guard ios for child process info --- src/core/modules/diagnostics.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/modules/diagnostics.cc b/src/core/modules/diagnostics.cc index bd757e1cda..6a7b57bfa1 100644 --- a/src/core/modules/diagnostics.cc +++ b/src/core/modules/diagnostics.cc @@ -26,6 +26,7 @@ namespace SSC { } } while (0); + #if !SOCKET_RUNTIME_PLATFORM_IOS // `childProcess` diagnostics do { Lock lock(this->core->childProcess.mutex); @@ -34,6 +35,7 @@ namespace SSC { query.childProcess.handles.ids.push_back(entry.first); } } while (0); + #endif // ai diagnostics do { From 7d38f830a16528ffca9e0573a3b97a8ba19f4cdf Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 08:31:05 -0400 Subject: [PATCH 0872/1178] fix(bin): various fixes for building android on windows --- bin/android-functions.sh | 2 +- bin/build-runtime-library.sh | 21 ++++++++++++++++----- bin/functions.sh | 12 ++++++++++-- bin/install.sh | 4 ++-- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/bin/android-functions.sh b/bin/android-functions.sh index dc38dbb1a2..d39e4c41fc 100755 --- a/bin/android-functions.sh +++ b/bin/android-functions.sh @@ -433,7 +433,7 @@ function android_clang () { function android_clang_target () { local arch=$1 - echo "-target $(android_arch "$arch")-linux-android$(android_eabi "$arch")" + echo "--target=$(android_arch "$arch")-linux-android$(android_eabi "$arch")" } diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index 1554d44efc..2a31559a4f 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -80,6 +80,7 @@ while (( $# > 0 )); do export TARGET_IPHONE_SIMULATOR=1 elif [[ "$1" = "android" ]]; then platform="$1"; + d="" export TARGET_OS_ANDROID=1 else platform="$1"; @@ -190,12 +191,22 @@ function generate_llama_build_info () { build_commit=$(printf '%s' "$out" | tr -d '\n') fi - if out=$(eval "$clang" --version | head -1); then - build_compiler="$out" - fi + if [[ "$(host_os)" == "Win32" ]]; then + if out=$("$clang" --version | head -1); then + build_compiler="$out" + fi - if out=$(eval "$clang" -dumpmachine); then - build_target="$out" + if out=$("$clang" -dumpmachine); then + build_target="$out" + fi + else + if out=$(eval "$clang" --version | head -1); then + build_compiler="$out" + fi + + if out=$(eval "$clang" -dumpmachine); then + build_target="$out" + fi fi echo "# generating llama build info" diff --git a/bin/functions.sh b/bin/functions.sh index cef4cd9b6a..1f2fd808f1 100755 --- a/bin/functions.sh +++ b/bin/functions.sh @@ -98,9 +98,17 @@ function quiet () { declare command="$1"; shift if [ -n "$VERBOSE" ]; then echo "$command" "$@" - eval "$command $@" + if [[ "$(host_os)" != "Win32" ]]; then + eval "$command $@" + else + "$command" "$@" + fi else - "$command" "$@" > /dev/null 2>&1 + if [[ "$(host_os)" != "Win32" ]]; then + eval "$command $@" > /dev/null 2>&1 + else + "$command" "$@" > /dev/null 2>&1 + fi fi return $? diff --git a/bin/install.sh b/bin/install.sh index 6a52db6ee0..5022bacf26 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -729,7 +729,7 @@ function _compile_libuv_android { local max_concurrency=$CPU_CORES build_static=0 declare base_lib="libuv" - declare static_library="$root/build/$arch-$platform/lib$d/$base_lib$d.a" + declare static_library="$root/build/$arch-$platform/lib/$base_lib.a" for source in "${sources[@]}"; do if (( i++ > max_concurrency )); then @@ -739,7 +739,7 @@ function _compile_libuv_android { i=0 fi - declare object="${source/.c/$d.o}" + declare object="${source/.c/.o}" object="$(basename "$object")" objects+=("$output_directory/$object") From 87c76cc0ebb5e27ddf25ed9c90f7f8da47b8d695 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 08:31:21 -0400 Subject: [PATCH 0873/1178] fix(api/internal/init.js): fix import typo --- api/internal/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/init.js b/api/internal/init.js index 4952e4633a..446a6c253b 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -25,7 +25,7 @@ import { rand64 } from '../crypto.js' import location from '../location.js' import mime from '../mime.js' import path from '../path.js' -import os from '../process.js' +import os from '../os.js' import fs from '../fs/promises.js' import { createFileSystemDirectoryHandle, From e8ebaa1c242555b5a1d5154218c5cf27e5a4fc5e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 08:31:36 -0400 Subject: [PATCH 0874/1178] fix(ipc/preload.cc): fix 'client.parent' cycle --- src/ipc/preload.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 592a63a7a0..14faf186ed 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -238,7 +238,9 @@ namespace SSC::IPC { configurable: false, enumerable: true, writable: false, - value: globalThis?.parent?.__args?.client ?? {} + value: globalThis?.parent !== globalThis + ? globalThis?.parent?.__args?.client ?? {} + : {} }, frameType: { configurable: false, From 97b31de038bd01d94ae81587b2c50780654da500 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 14:50:35 +0200 Subject: [PATCH 0875/1178] fix(bin/install.sh): ignore 'metallib' files for desktop on darwin --- bin/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/install.sh b/bin/install.sh index 5022bacf26..461b61f130 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -573,7 +573,7 @@ function _install { if [[ "$platform" != "android" ]]; then cp -rfp "$BUILD_DIR/$arch-$platform"/lib$_d/*.a "$SOCKET_HOME/lib$_d/$arch-$platform" - if [[ "$host" == "Darwin" ]]; then + if [[ "$host" == "Darwin" ]] && [[ "$platform" != "desktop" ]]; then cp -rfp "$BUILD_DIR/$arch-$platform"/lib/*.metallib "$SOCKET_HOME/lib/$arch-$platform" fi fi From 35ad94e445d1681304993e56d2edb1e826ff76de Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 14:50:58 +0200 Subject: [PATCH 0876/1178] fix(api/internal/init.js): fix typos --- api/internal/init.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 446a6c253b..e39f6cd2a2 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -240,12 +240,13 @@ if ((globalThis.window) === globalThis) { globalThis.addEventListener('keyboard', function (event) { const { detail } = event + keyboard.height = detail.value.height + if (keyboard.offset === 0) { keyboard.offset = document.body.offsetHeight } if (detail.value.event === 'will-show' && !keyboard.opened) { - keyboard.height = detail.value.height let start = null requestAnimationFrame(function animate (timestamp) { @@ -266,7 +267,7 @@ if ((globalThis.window) === globalThis) { if (detail.value.event === 'will-hide' && keyboard.opened) { keyboard.opened = false - keyboard.offset = globalThis.document.body.offsetHeight + const { offsetHeight } = globalThis.document.body let start = null @@ -275,7 +276,7 @@ if ((globalThis.window) === globalThis) { const elapsed = timestamp - start let progress = Math.min(elapsed / timing.duration, 1) const easeProgress = bezier.hide(progress) - const currentHeight = keyboard.offset + (easeProgress * keyboard.height) + const currentHeight = offsetHeight + (easeProgress * keyboard.height) if (currentHeight <= 0) progress = 1 globalThis.document.body.style.height = `${currentHeight}px` From 86972f1ecfc34bd4ac60eff12e8a51324d8d94d3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 14:52:21 +0200 Subject: [PATCH 0877/1178] chore(api/index.d.ts): generate types --- api/index.d.ts | 131 ++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 4d4b2bb6cb..c2f19587c7 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T>(key: Variable<T>): boolean; + get<T_1>(key: Variable<T_1>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T>(key: Variable<T>, value: T): Mapping; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T>(key: Variable<T>): Mapping; + delete<T_3>(key: Variable<T_3>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T>(key: Variable<T>): T | undefined; + static get<T_1>(key: Variable<T_1>): T_1; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T>(revert: Revert<T> | FrozenRevert): void; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_1>(): T_1 | undefined; + get<T_2>(): T_2; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F>(fn: F): F; + static wrap<F_1>(fn: F_1): F_1; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent; + prototype: CustomEvent<any>; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent; + prototype: MessageEvent<any>; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1282,7 +1282,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2046,7 +2046,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): value is symbol; + export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2406,9 +2406,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + send: (body?: Document | XMLHttpRequestBodyInit) => void; }; } export class WorkerMetric extends Metric { @@ -3787,7 +3787,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -4022,7 +4022,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -4196,7 +4196,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -4577,7 +4577,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: ProcessEnvironment; + export const env: any; export default process; const process: any; } @@ -5234,7 +5234,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; + static from(stat?: object | Stats, fromBigInt?: any): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6083,7 +6083,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: "utf8" | "buffer"; + encoding: 'utf8' | 'buffer'; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6971,7 +6971,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7184,7 +7184,6 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; - import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8472,7 +8471,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9382,7 +9381,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9457,7 +9456,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: "shared" | "wasm32"; + type?: 'shared' | 'wasm32'; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -9953,7 +9952,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: "strict" | "relaxed" | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10017,7 +10016,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: "set" | "delete", entry?: object | undefined); + constructor(type: 'set' | 'delete', entry?: object | undefined); entry: any; } /** @@ -10144,7 +10143,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import("socket:service-worker/events").ExtendableEvent); + constructor(event: import('./events.js').ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10185,7 +10184,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import("socket:service-worker/clients").Client>; + client(): Promise<import('./clients.js').Client>; #private; } namespace _default { @@ -10368,7 +10367,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10403,13 +10402,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13122,7 +13121,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T>(actual: T, expected: T, msg?: string): void; + notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; /** * @template T * @param {T} actual @@ -13130,7 +13129,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T>(actual: T, expected: T, msg?: string): void; + equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13379,7 +13378,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: { + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { /** * - The text to wait for */ @@ -13390,7 +13389,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; + }, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -14784,7 +14783,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } export default Package; @@ -14795,13 +14794,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; prefix?: string; }; export type ParsedPackageName = { @@ -14884,7 +14883,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import("socket:commonjs/module").Module); + constructor(module: import('./module.js').Module); /** * The referrer (parent) of this module. * @type {string} @@ -14928,7 +14927,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15288,7 +15287,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions | undefined): any; + require(url: any, options?: RequireOptions): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15326,9 +15325,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import process from "socket:process"; } declare module "socket:module" { @@ -15668,7 +15667,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; + static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; /** * `Notification` class constructor. * @param {string} title @@ -15836,9 +15835,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; export default createServiceWorker; @@ -16506,7 +16505,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -16552,9 +16551,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): CoreAsyncResource; - bind(fn: Function, thisArg?: object | undefined): Function; - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; }; } export namespace Promise { @@ -16626,7 +16625,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16646,7 +16645,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -16668,8 +16667,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: "read" | "readwrite"; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + mode?: 'read' | 'readwrite'; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; }; /** * ]?: string[] @@ -16679,10 +16678,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; types?: Array<{ description?: string; - [keyof]: any; + [keyof]; }>; }; } @@ -16783,7 +16782,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -16792,7 +16791,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: "commonjs" | "module"; + type: 'commonjs' | 'module'; url: string; }; import { Package } from "socket:commonjs/package"; @@ -16873,8 +16872,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -16888,7 +16887,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17261,12 +17260,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; + export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends Harness> { + export class TapeHarness<T extends exports.Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17319,7 +17318,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends Harness> = { + export type TapeTestFn<T extends exports.Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17336,8 +17335,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 228f2f3ab44fc691545f4328e484b8c5a8a38100 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 15:13:19 +0200 Subject: [PATCH 0878/1178] refactor(window): clean up, remove 'appBadgeEnabled' pref on apple --- src/window/apple.mm | 16 ----------- src/window/win.cc | 70 ++++++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 77eefcfb46..e6d6a28e68 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -292,14 +292,6 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } if (userConfig["permissions_allow_notifications"] == "false") { - #if SOCKET_RUNTIME_PLATFORM_MACOS - @try { - [preferences setValue: @NO forKey: @"appBadgeEnabled"]; - } @catch (NSException *error) { - debug("Failed to set preference: 'appBadgeEnabled': %@", error); - } - #endif - @try { [preferences setValue: @NO forKey: @"notificationsEnabled"]; } @catch (NSException *error) { @@ -311,14 +303,6 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } @catch (NSException *error) { debug("Failed to set preference: 'notificationEventEnabled': %@", error); } - } else { - #if SOCKET_RUNTIME_PLATFORM_MACOS - @try { - [preferences setValue: @YES forKey: @"appBadgeEnabled"]; - } @catch (NSException *error) { - debug("Failed to set preference: 'appBadgeEnabled': %@", error); - } - #endif } #if SOCKET_RUNTIME_PLATFORM_MACOS diff --git a/src/window/win.cc b/src/window/win.cc index a65bf04f7c..dca6dc230a 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -795,7 +795,7 @@ namespace SSC { this->controller->put_Bounds(bounds); this->controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); this->controller->AddRef(); - this->bridge.configureWebView(this->webview); + this->bridge.configureWebView(this->webview); } while (0); // configure the webview settings @@ -806,7 +806,7 @@ namespace SSC { ICoreWebView2Settings6* settings6 = nullptr; ICoreWebView2Settings9* settings9 = nullptr; - const auto wantsDebugMode = this->options.debug || isDebugEnabled(); + const auto wantsDebugMode = this->options.debug || isDebugEnabled(); this->webview->get_Settings(&settings); @@ -825,15 +825,15 @@ namespace SSC { settings->put_AreDefaultContextMenusEnabled(true); settings->put_AreDefaultScriptDialogsEnabled(true); - // TODO(@jwerle): set user agent for runtime - // settings2->put_UserAgent(); + // TODO(@jwerle): set user agent for runtime + // settings2->put_UserAgent(); settings3->put_AreBrowserAcceleratorKeysEnabled(wantsDebugMode); settings6->put_IsPinchZoomEnabled(false); settings6->put_IsSwipeNavigationEnabled(false); - settings9->put_IsNonClientRegionSupportEnabled(true); + settings9->put_IsNonClientRegionSupportEnabled(true); } while (0); // enumerate all child windows to re-register drag/drop @@ -1042,22 +1042,22 @@ namespace SSC { ICoreWebView2_2* webview2 = nullptr; if (webview->QueryInterface(IID_PPV_ARGS(&webview2)) != S_OK) { return E_FAIL; - } + } if (webview2->get_Environment(&env) != S_OK) { return E_FAIL; - } + } if (args->get_Request(&platformRequest) != S_OK) { return E_FAIL; - } + } } while (0); auto request = IPC::SchemeHandlers::Request::Builder( &this->bridge.schemeHandlers, - platformRequest, - env - ); + platformRequest, + env + ); // get and set HTTP method do { @@ -1072,44 +1072,48 @@ namespace SSC { ComPtr<ICoreWebView2HttpHeadersCollectionIterator> iterator; BOOL hasCurrent = false; BOOL hasNext = false; - + if (platformRequest->get_Headers(&headers) == S_OK && headers->GetIterator(&iterator) == S_OK) { while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) { LPWSTR name; - LPWSTR value; + LPWSTR value; - if (iterator->GetCurrentHeader(&name, &value) != S_OK) { + if (iterator->GetCurrentHeader(&name, &value) != S_OK) { break; } request.setHeader(convertWStringToString(name), convertWStringToString(value)); - if (iterator->MoveNext(&hasNext) != S_OK || !hasNext) { + if (iterator->MoveNext(&hasNext) != S_OK || !hasNext) { break; } - } - } + } + } } while (0); - // get request body - if (request.request->method == "POST" || request.request->method == "PUT" || request.request->method == "PATCH") { + // get request body + if ( + request.request->method == "POST" || + request.request->method == "PUT" || + request.request->method == "PATCH" + ) { IStream* content = nullptr; if (platformRequest->get_Content(&content) == S_OK && content != nullptr) { - STATSTG stat; - content->Stat(&stat, 0); - size_t size = stat.cbSize.QuadPart; - if (size > 0) { - auto buffer = std::make_shared<char[]>(size); + STATSTG stat; + content->Stat(&stat, 0); + size_t size = stat.cbSize.QuadPart; + if (size > 0) { + auto buffer = std::make_shared<char[]>(size); if (content->Read(buffer.get(), size, nullptr) == S_OK) { - request.setBody(IPC::SchemeHandlers::Body { + request.setBody(IPC::SchemeHandlers::Body { size, - std::move(buffer) - }); - } - } - } - } - - auto req = request.build(); + std::move(buffer) + }); + } + } + } + } + + auto req = request.build(); if (args->GetDeferral(&deferral) != S_OK) { return E_FAIL; } From 0256b0a83a6704a85f8c9ce17369c071c3f3d6ff Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 15:13:36 +0200 Subject: [PATCH 0879/1178] refactor(ipc): clean up --- src/ipc/message.cc | 6 ++++-- src/ipc/scheme_handlers.cc | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ipc/message.cc b/src/ipc/message.cc index f806bf9949..ee6de2043d 100644 --- a/src/ipc/message.cc +++ b/src/ipc/message.cc @@ -72,7 +72,7 @@ namespace SSC::IPC { bool Message::has (const String& key) const { return ( - this->args.find(key) != this->args.end() && + this->args.contains(key) && this->args.at(key).size() > 0 ); } @@ -83,6 +83,8 @@ namespace SSC::IPC { String Message::get (const String& key, const String &fallback) const { if (key == "value") return this->value; - return args.count(key) ? decodeURIComponent(args.at(key)) : fallback; + return this->args.contains(key) + ? decodeURIComponent(args.at(key)) + : fallback; } } diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 83f6e9fb24..9e3c83335f 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -406,7 +406,9 @@ namespace SSC::IPC { SchemeHandlers::Handler SchemeHandlers::getHandlerForScheme (const String& scheme) { Lock lock(this->mutex); - return this->handlers.at(scheme); + return this->handlers.contains(scheme) + ? this->handlers.at(scheme) + : SchemeHandlers::Handler {}; } bool SchemeHandlers::registerSchemeHandler (const String& scheme, const Handler& handler) { @@ -550,6 +552,7 @@ namespace SSC::IPC { return ( id > 0 && this->activeRequests.contains(id) && + this->activeRequests.at(id) != nullptr && this->activeRequests.at(id)->cancelled ); } From 79ded0ddfce6707a993d456397b1df6ab225cf5a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 15:13:50 +0200 Subject: [PATCH 0880/1178] refactor(core): clean up locks --- src/core/core.cc | 4 ++++ src/core/modules/ai.cc | 8 ++++++-- src/core/modules/fs.cc | 3 ++- src/core/modules/fs.hh | 2 +- src/core/modules/timers.cc | 8 ++++---- src/core/trace.cc | 2 +- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 2b7e86ec73..9ac2c17a62 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -374,6 +374,10 @@ namespace SSC { for (auto const id : ids) { Lock lock(core->fs.mutex); + if (!core->fs.descriptors.contains(id)) { + continue; + } + auto desc = core->fs.descriptors.at(id); if (desc == nullptr) { diff --git a/src/core/modules/ai.cc b/src/core/modules/ai.cc index d802b8031a..c6f121211a 100644 --- a/src/core/modules/ai.cc +++ b/src/core/modules/ai.cc @@ -57,9 +57,13 @@ namespace SSC { } SharedPointer<LLM> CoreAI::getLLM (ID id) { - if (!this->hasLLM(id)) return nullptr; Lock lock(this->mutex); - return this->llms.at(id); + + if (this->llms.contains(id)) { + return this->llms.at(id); + } + + return nullptr; } bool CoreAI::hasLLM (ID id) { diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index e9b6e22e9c..da8f6bc1d1 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -236,7 +236,8 @@ namespace SSC { return this->stale; } - SharedPointer<CoreFS::Descriptor> CoreFS::getDescriptor (ID id) const { + SharedPointer<CoreFS::Descriptor> CoreFS::getDescriptor (ID id) { + Lock lock(this->mutex); if (descriptors.find(id) != descriptors.end()) { return descriptors.at(id); } diff --git a/src/core/modules/fs.hh b/src/core/modules/fs.hh index 9e8c15f377..0bcd40b53a 100644 --- a/src/core/modules/fs.hh +++ b/src/core/modules/fs.hh @@ -83,7 +83,7 @@ namespace SSC { : CoreModule(core) {} - SharedPointer<Descriptor> getDescriptor (ID id) const; + SharedPointer<Descriptor> getDescriptor (ID id); void removeDescriptor (ID id); bool hasDescriptor (ID id) const; diff --git a/src/core/modules/timers.cc b/src/core/modules/timers.cc index 739cbc02b9..66e861d3c2 100644 --- a/src/core/modules/timers.cc +++ b/src/core/modules/timers.cc @@ -130,10 +130,6 @@ namespace SSC { this->handles.at(id)->type = Timer::Type::Interval; } - if (this->handles.contains(id)) { - this->handles.at(id)->type = Timer::Type::Interval; - } - return id; } @@ -148,6 +144,10 @@ namespace SSC { callback(); }); + if (this->handles.contains(id)) { + this->handles.at(id)->type = Timer::Type::Immediate; + } + return id; } diff --git a/src/core/trace.cc b/src/core/trace.cc index adcdab7713..390d65eff1 100644 --- a/src/core/trace.cc +++ b/src/core/trace.cc @@ -54,7 +54,7 @@ namespace SSC { if (this->index.contains(id)) { const auto i = this->index[id]; if (i < this->spans->size()) { - return this->spans->at(id); + return this->spans->at(i); } } From 015cddb2389427b56e7e1db2c6f377ab7c12a988 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 27 Jun 2024 15:16:31 +0200 Subject: [PATCH 0881/1178] fix(core/modules/fs): restore 'const' 'CoreFS::getDescriptor' --- src/core/modules/fs.cc | 7 +++++++ src/core/modules/fs.hh | 1 + 2 files changed, 8 insertions(+) diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index da8f6bc1d1..0daef20a39 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -244,6 +244,13 @@ namespace SSC { return nullptr; } + SharedPointer<CoreFS::Descriptor> CoreFS::getDescriptor (ID id) const { + if (descriptors.find(id) != descriptors.end()) { + return descriptors.at(id); + } + return nullptr; + } + void CoreFS::removeDescriptor (ID id) { Lock lock(this->mutex); if (descriptors.find(id) != descriptors.end()) { diff --git a/src/core/modules/fs.hh b/src/core/modules/fs.hh index 0bcd40b53a..ce3a9b9e75 100644 --- a/src/core/modules/fs.hh +++ b/src/core/modules/fs.hh @@ -83,6 +83,7 @@ namespace SSC { : CoreModule(core) {} + SharedPointer<Descriptor> getDescriptor (ID id) const; SharedPointer<Descriptor> getDescriptor (ID id); void removeDescriptor (ID id); bool hasDescriptor (ID id) const; From 7f9c96ad3bc391ab82931edcda5058364121e833 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 28 Jun 2024 17:33:08 +0200 Subject: [PATCH 0882/1178] fix(core/modules/fs): clean up 'uv_fs_t' if is set --- src/core/modules/fs.hh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/modules/fs.hh b/src/core/modules/fs.hh index ce3a9b9e75..0a51ec43b2 100644 --- a/src/core/modules/fs.hh +++ b/src/core/modules/fs.hh @@ -66,10 +66,13 @@ namespace SSC { this->callback = callback; this->descriptor = descriptor; this->recursive = false; + this->req.loop = nullptr; } ~RequestContext () { - uv_fs_req_cleanup(&this->req); + if (this->req.loop) { + uv_fs_req_cleanup(&this->req); + } } void setBuffer (SharedPointer<char[]> base, uint32_t size); From 65da59d08152cca1cb41cea6b1abda7b09ac20ec Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 28 Jun 2024 18:17:25 +0200 Subject: [PATCH 0883/1178] feat(core/url): introduce 'URL::Builder' --- src/core/url.cc | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ src/core/url.hh | 31 +++++++++++++ 2 files changed, 144 insertions(+) diff --git a/src/core/url.cc b/src/core/url.cc index a0b1425eeb..b1b58533dd 100644 --- a/src/core/url.cc +++ b/src/core/url.cc @@ -77,6 +77,119 @@ namespace SSC { return components; } + URL::Builder& URL::Builder::setProtocol (const String& protocol) { + this->protocol = protocol; + return *this; + } + + URL::Builder& URL::Builder::setUsername (const String& username) { + this->username = username; + return *this; + } + + URL::Builder& URL::Builder::setPassword (const String& password) { + this->password = password; + return *this; + } + + URL::Builder& URL::Builder::setHostname (const String& hostname) { + this->hostname = hostname; + return *this; + } + + URL::Builder& URL::Builder::setPort (const String& port) { + this->port = port; + return *this; + } + + URL::Builder& URL::Builder::setPort (const int port) { + this->port = std::to_string(port); + return *this; + } + + URL::Builder& URL::Builder::setPathname (const String& pathname) { + this->pathname = pathname; + return *this; + } + + URL::Builder& URL::Builder::setQuery (const String& query) { + this->search = "?" + query; + return *this; + } + + URL::Builder& URL::Builder::setSearch (const String& search) { + this->search = search; + return *this; + } + + URL::Builder& URL::Builder::setHash (const String& hash) { + this->hash = hash; + return *this; + } + + URL::Builder& URL::Builder::setFragment (const String& fragment) { + this->hash = "#" + fragment; + return *this; + } + + URL::Builder& URL::Builder::setSearchParam (const String& key, const String& value) { + this->searchParams[key] = value; + return *this; + } + + URL::Builder& URL::Builder::setSearchParam (const String& key, const JSON::Any& value) { + if (JSON::typeof(value) == "string" || JSON::typeof(value) == "number" || JSON::typeof(value) == "boolean") { + return this->setSearchParam(key, value.str()); + } + + return *this; + } + + URL::Builder& URL::Builder::setSearchParams (const Map& params) { + for (const auto& entry : params) { + this->searchParams.insert_or_assign(entry.first, entry.second); + } + return *this; + } + + URL URL::Builder::build () const { + StringStream stream; + + if (this->protocol.size() == 0) { + return String(""); + } + + stream << this->protocol << ":"; + + if ( + (this->username.size() > 0 || this->password.size() > 0) && + this->hostname.size() > 0 + ) { + stream << "//"; + if (this->username.size() > 0) { + stream << this->username; + if (this->password.size() > 0) { + stream << ":" << this->password; + } + + stream << "@" << this->hostname; + if (this->port.size() > 0) { + stream << ":" << this->port; + } + } + } + + if (this->hostname.size() > 0 && this->pathname.size() > 0) { + if (!this->pathname.starts_with("/")) { + stream << "/"; + } + } + + stream << this->pathname << this->search << this->hash; + + return stream.str(); + } + URL::URL (const JSON::Object& json) : URL(json["href"].str()) {} diff --git a/src/core/url.hh b/src/core/url.hh index 6fe2cf6319..3edb0595d7 100644 --- a/src/core/url.hh +++ b/src/core/url.hh @@ -16,6 +16,36 @@ namespace SSC { static const Components parse (const String& url); }; + struct Builder { + String protocol = ""; + String username = ""; + String password = ""; + String hostname = ""; + String port = ""; + String pathname = ""; + String search = ""; // includes '?' and 'query' if 'query' is not empty + String hash = ""; // include '#' and 'fragment' if 'fragment' is not empty + + Map searchParams; + + Builder& setProtocol (const String& protocol); + Builder& setUsername (const String& username); + Builder& setPassword (const String& password); + Builder& setHostname (const String& hostname); + Builder& setPort (const String& port); + Builder& setPort (const int port); + Builder& setPathname (const String& pathname); + Builder& setQuery (const String& query); + Builder& setSearch (const String& search); + Builder& setHash (const String& hash); + Builder& setFragment (const String& fragment); + Builder& setSearchParam (const String& key, const String& value); + Builder& setSearchParam (const String& key, const JSON::Any& value); + Builder& setSearchParams (const Map& params); + + URL build () const; + }; + // core properties String href = ""; String origin = ""; @@ -37,6 +67,7 @@ namespace SSC { URL () = default; URL (const String& href); URL (const JSON::Object& json); + void set (const String& href); void set (const JSON::Object& json); const String str () const; From a8eea6e77a146e1feff8c2b84db81d5a04e7a7bd Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:11:11 +0200 Subject: [PATCH 0884/1178] feat(api/window/client.js): introduce window client info --- api/window/client.js | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 api/window/client.js diff --git a/api/window/client.js b/api/window/client.js new file mode 100644 index 0000000000..7b354c6afb --- /dev/null +++ b/api/window/client.js @@ -0,0 +1,50 @@ +/** + * @typedef {{ + * id?: string | null, + * type?: 'window' | 'worker', + * parent?: object | null, + * top?: object | null, + * frameType?: 'top-level' | 'nested' | 'none' + * }} ClientState + */ + +export class Client { + /** + * @type {ClientState} + */ + #state = null + + /** + * `Client` class constructor + * @param {ClientState} state + */ + constructor (state) { + this.#state = state + } + + get id () { + return this.#state?.id ?? null + } + + get frameType () { + return this.#state?.frameType ?? 'none' + } + + get type () { + return this.#state?.type ?? '' + } + + get parent () { + return this.#state?.parent ? + new Client(this.#state.parent) + : null + } + + get top () { + return this.#state?.top + ? new Client(this.#state.top) + : null + } +} + +export default new Client (globalThis.__args?.client ?? {}) From 2f0d22358d30c2c905a71281887ae9da9c963bb9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:12:07 +0200 Subject: [PATCH 0885/1178] refactor(api/internal/init.js): improve '__args.client' in worker --- api/internal/init.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index e39f6cd2a2..4d74adaecc 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -324,17 +324,35 @@ class RuntimeWorker extends GlobalWorker { const url = encodeURIComponent(new URL(filename, globalThis.location.href).toString()) const id = String(rand64()) + const topClient = globalThis.__args.client.top || globalThis.__args.client + + const __args = { ...globalThis.__args, client: {} } const preload = ` Object.defineProperty(globalThis, '__args', { configurable: false, enumerable: false, - value: ${JSON.stringify(globalThis.__args)} + value: ${JSON.stringify(__args)} }) globalThis.__args.client.id = '${id}' globalThis.__args.client.type = 'worker' globalThis.__args.client.frameType = 'none' - globalThis.__args.client.parent = ${JSON.stringify(globalThis.__args.client)} + globalThis.__args.client.parent = ${JSON.stringify({ + id: globalThis.__args?.client?.id, + top: null, + type: globalThis.__args?.client?.type, + parent: null, + frameType: globalThis.__args?.client?.frameType + })} + globalThis.__args.client.top = ${JSON.stringify({ + id: topClient?.id, + top: null, + type: topClient?.type, + parent: null, + frameType: topClient?.frameType + })} + + globalThis.__args.client.parent.top = globalThis.__args.client.top Object.defineProperty(globalThis, 'isWorkerScope', { configurable: false, From dd355ed52ed5b541a3d56d20b5699a8eaa7e4821 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:12:23 +0200 Subject: [PATCH 0886/1178] fix(core/url): improve query param builder --- src/core/url.cc | 20 ++++++++++++++++---- src/core/url.hh | 2 -- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/core/url.cc b/src/core/url.cc index b1b58533dd..6042f878ed 100644 --- a/src/core/url.cc +++ b/src/core/url.cc @@ -133,8 +133,7 @@ namespace SSC { } URL::Builder& URL::Builder::setSearchParam (const String& key, const String& value) { - this->searchParams[key] = value; - return *this; + return this->setSearchParams(Map {{ key, value }}); } URL::Builder& URL::Builder::setSearchParam (const String& key, const JSON::Any& value) { @@ -146,9 +145,22 @@ namespace SSC { } URL::Builder& URL::Builder::setSearchParams (const Map& params) { - for (const auto& entry : params) { - this->searchParams.insert_or_assign(entry.first, entry.second); + if (params.size() > 0) { + if (!this->search.starts_with("?")) { + this->search = "?"; + } else if (this->search.size() > 0) { + this->search += "&"; + } + + for (const auto& entry : params) { + this->search = entry.first + "=" + entry.second + "&"; + } } + + if (this->search.ends_with("&")) { + this->search = this->search.substr(0, this->search.size() - 1); + } + return *this; } diff --git a/src/core/url.hh b/src/core/url.hh index 3edb0595d7..bca1d1196d 100644 --- a/src/core/url.hh +++ b/src/core/url.hh @@ -26,8 +26,6 @@ namespace SSC { String search = ""; // includes '?' and 'query' if 'query' is not empty String hash = ""; // include '#' and 'fragment' if 'fragment' is not empty - Map searchParams; - Builder& setProtocol (const String& protocol); Builder& setUsername (const String& username); Builder& setPassword (const String& password); From 78280d5e6e633831685ea31e4a6c9c86550eaaf5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:12:40 +0200 Subject: [PATCH 0887/1178] refactor(ipc/preload): introduce 'top' client --- src/ipc/preload.cc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 14faf186ed..55dd7283ea 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -238,9 +238,16 @@ namespace SSC::IPC { configurable: false, enumerable: true, writable: false, - value: globalThis?.parent !== globalThis - ? globalThis?.parent?.__args?.client ?? {} - : {} + value: globalThis.parent !== globalThis + ? globalThis.parent?.__args?.client ?? null + : null + }, + top: { + configurable: false, + enumerable: true, + get: () => globalThis.top + ? globalThis.top.__args?.client ?? null + : globalThis.__args.client }, frameType: { configurable: false, From 630c26c6ba852eb3c2b58a88fd76c6a64d94298c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:13:00 +0200 Subject: [PATCH 0888/1178] feat(window): introduce 'WindowManager::getWindowForClient' --- src/window/manager.cc | 10 ++++++++++ src/window/window.hh | 1 + 2 files changed, 11 insertions(+) diff --git a/src/window/manager.cc b/src/window/manager.cc index 6224084d4e..74d7d13ff2 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -76,6 +76,16 @@ namespace SSC { return nullptr; } + SharedPointer<WindowManager::ManagedWindow> WindowManager::getWindowForClient (const IPC::Client& client) { + for (const auto& window : this->windows) { + if (window != nullptr && window->bridge.client.id == client.id) { + return this->getWindow(window->index); + } + } + + return nullptr; + } + SharedPointer<WindowManager::ManagedWindow> WindowManager::WindowManager::getOrCreateWindow (int index) { return this->getOrCreateWindow(index, Window::Options {}); } diff --git a/src/window/window.hh b/src/window/window.hh index 8852fb6b1f..87687d2de7 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -537,6 +537,7 @@ namespace SSC { SharedPointer<ManagedWindow> getWindow (int index, const WindowStatus status); SharedPointer<ManagedWindow> getWindow (int index); + SharedPointer<ManagedWindow> getWindowForClient (const IPC::Client& client); SharedPointer<ManagedWindow> getWindowForBridge (const IPC::Bridge* bridge); SharedPointer<ManagedWindow> getWindowForWebView (WebView* webview);; SharedPointer<ManagedWindow> getOrCreateWindow (int index); From 3750e872e40a02a4ed63a6bf34e229f262deb15e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:17:18 +0200 Subject: [PATCH 0889/1178] refactor(api/window): clean up, docs, and export 'client' --- api/window.js | 3 ++- api/window/client.js | 28 +++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/api/window.js b/api/window.js index 83f4739fc6..b2a86c3ccf 100644 --- a/api/window.js +++ b/api/window.js @@ -13,6 +13,7 @@ import { isValidPercentageValue } from './util.js' import * as statuses from './window/constants.js' import location from './location.js' import { URL } from './url.js' +import client from './window/client.js' import hotkey from './window/hotkey.js' import menu from './application/menu.js' import ipc from './ipc.js' @@ -588,7 +589,7 @@ export class ApplicationWindow { } } -export { hotkey } +export { client, hotkey } export default ApplicationWindow diff --git a/api/window/client.js b/api/window/client.js index 7b354c6afb..95c2764798 100644 --- a/api/window/client.js +++ b/api/window/client.js @@ -16,30 +16,51 @@ export class Client { /** * `Client` class constructor + * @private * @param {ClientState} state */ constructor (state) { this.#state = state } + /** + * The unique ID of the client. + * @type {string|null} + */ get id () { return this.#state?.id ?? null } + /** + * The frame type of the client. + * @type {'top-level'|'nested'|'none'} + */ get frameType () { return this.#state?.frameType ?? 'none' } + /** + * The type of the client. + * @type {'window'|'worker'} + */ get type () { return this.#state?.type ?? '' } + /** + * The parent client of the client. + * @type {Client|null} + */ get parent () { - return this.#state?.parent ? - new Client(this.#state.parent) + return this.#state?.parent + ? new Client(this.#state.parent) : null } + /** + * The top client of the client. + * @type {Client|null} + */ get top () { return this.#state?.top ? new Client(this.#state.top) @@ -47,4 +68,5 @@ export class Client { } } -export default new Client (globalThis.__args?.client ?? {}) +// @ts-ignore +export default new Client(globalThis.__args?.client ?? {}) From ee338d73be1d04c8d177fe8f1c4f2899b589f957 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 29 Jun 2024 12:17:43 +0200 Subject: [PATCH 0890/1178] chore(api): generate types + docs --- api/README.md | 72 +++++++++---------- api/index.d.ts | 189 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 159 insertions(+), 102 deletions(-) diff --git a/api/README.md b/api/README.md index e380fd4d5b..796049fb3a 100644 --- a/api/README.md +++ b/api/README.md @@ -2716,15 +2716,15 @@ Retrieves the computed styles for a given element. `socket:application` methods like `getCurrentWindow`, `createWindow`, `getWindow`, and `getWindows`. -## [ApplicationWindow](https://github.com/socketsupply/socket/blob/master/api/window.js#L34) +## [ApplicationWindow](https://github.com/socketsupply/socket/blob/master/api/window.js#L35) Represents a window in the application -### [`id()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L68) +### [`id()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L69) The unique ID of this window. -### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L76) +### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L77) Get the index of the window @@ -2732,15 +2732,15 @@ Get the index of the window | :--- | :--- | :--- | | Not specified | number | the index of the window | -### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L83) +### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L84) -### [`channel()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L91) +### [`channel()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L92) The broadcast channel for this window. -### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L99) +### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L100) Get the size of the window @@ -2748,7 +2748,7 @@ Get the size of the window | :--- | :--- | :--- | | Not specified | { width: number, height: number | } - the size of the window | -### [`getPosition()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L110) +### [`getPosition()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L111) Get the position of the window @@ -2756,7 +2756,7 @@ Get the position of the window | :--- | :--- | :--- | | Not specified | { x: number, y: number | } - the position of the window | -### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L121) +### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L122) Get the title of the window @@ -2764,7 +2764,7 @@ Get the title of the window | :--- | :--- | :--- | | Not specified | string | the title of the window | -### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L129) +### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L130) Get the status of the window @@ -2772,7 +2772,7 @@ Get the status of the window | :--- | :--- | :--- | | Not specified | string | the status of the window | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L137) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L138) Close the window @@ -2780,7 +2780,7 @@ Close the window | :--- | :--- | :--- | | Not specified | Promise<object> | the options of the window | -### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L152) +### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L153) Shows the window @@ -2788,7 +2788,7 @@ Shows the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L161) +### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L162) Hides the window @@ -2796,7 +2796,7 @@ Hides the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L170) +### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L171) Maximize the window @@ -2804,7 +2804,7 @@ Maximize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L179) +### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L180) Minimize the window @@ -2812,7 +2812,7 @@ Minimize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L188) +### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L189) Restore the window @@ -2820,7 +2820,7 @@ Restore the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L198) +### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L199) Sets the title of the window @@ -2832,7 +2832,7 @@ Sets the title of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L211) +### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L212) Sets the size of the window @@ -2846,7 +2846,7 @@ Sets the size of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L254) +### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L255) Sets the position of the window @@ -2860,7 +2860,7 @@ Sets the position of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L298) +### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L299) Navigate the window to a given path @@ -2872,7 +2872,7 @@ Navigate the window to a given path | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L307) +### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L308) Opens the Web Inspector for the window @@ -2880,7 +2880,7 @@ Opens the Web Inspector for the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L324) +### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L325) Sets the background color of the window @@ -2896,7 +2896,7 @@ Sets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L333) +### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L334) Gets the background color of the window @@ -2904,7 +2904,7 @@ Gets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L342) +### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L343) Opens a native context menu. @@ -2916,7 +2916,7 @@ Opens a native context menu. | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L351) +### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L352) Shows a native open file dialog. @@ -2928,7 +2928,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L369) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L370) Shows a native save file dialog. @@ -2940,7 +2940,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L387) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L388) Shows a native directory dialog. @@ -2952,7 +2952,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L412) +### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L413) This is a high-level API that you should use instead of `ipc.request` when you want to send a message to another window or to the backend. @@ -2966,7 +2966,7 @@ This is a high-level API that you should use instead of `ipc.request` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L453) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L454) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -2979,7 +2979,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L472) +### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L473) Opens an URL in the default application associated with the URL protocol, such as 'https:' for the default web browser. @@ -2992,7 +2992,7 @@ Opens an URL in the default application associated with the URL protocol, | :--- | :--- | :--- | | Not specified | Promise<{ url: string | >} | -### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L487) +### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L488) Opens a file in the default file explorer. @@ -3004,7 +3004,7 @@ Opens a file in the default file explorer. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L502) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L503) Adds a listener to the window. @@ -3013,7 +3013,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L520) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L521) Adds a listener to the window. An alias for `addListener`. @@ -3022,7 +3022,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L537) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L538) Adds a listener to the window. The listener is removed after the first call. @@ -3031,7 +3031,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L553) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L554) Removes a listener from the window. @@ -3040,7 +3040,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L566) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L567) Removes all listeners from the window. @@ -3048,7 +3048,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L582) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L583) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/index.d.ts b/api/index.d.ts index c2f19587c7..00e058d42b 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1282,7 +1282,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2046,7 +2046,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2406,9 +2406,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -3787,7 +3787,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -4022,7 +4022,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -4196,7 +4196,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -4577,7 +4577,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } @@ -5234,7 +5234,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6083,7 +6083,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6901,6 +6901,61 @@ declare module "socket:window/constants" { } +declare module "socket:window/client" { + /** + * @typedef {{ + * id?: string | null, + * type?: 'window' | 'worker', + * parent?: object | null, + * top?: object | null, + * frameType?: 'top-level' | 'nested' | 'none' + * }} ClientState + */ + export class Client { + /** + * `Client` class constructor + * @private + * @param {ClientState} state + */ + private constructor(); + /** + * The unique ID of the client. + * @type {string|null} + */ + get id(): string; + /** + * The frame type of the client. + * @type {'top-level'|'nested'|'none'} + */ + get frameType(): "none" | "top-level" | "nested"; + /** + * The type of the client. + * @type {'window'|'worker'} + */ + get type(): "window" | "worker"; + /** + * The parent client of the client. + * @type {Client|null} + */ + get parent(): Client; + /** + * The top client of the client. + * @type {Client|null} + */ + get top(): Client; + #private; + } + const _default: any; + export default _default; + export type ClientState = { + id?: string | null; + type?: "window" | "worker"; + parent?: object | null; + top?: object | null; + frameType?: "top-level" | "nested" | "none"; + }; +} + declare module "socket:window/hotkey" { /** * Normalizes an expression string. @@ -6971,7 +7026,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7184,6 +7239,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -7448,7 +7504,6 @@ declare module "socket:window" { off(event: string, cb: (arg0: any) => void): void; #private; } - export { hotkey }; export default ApplicationWindow; /** * @ignore @@ -7456,7 +7511,9 @@ declare module "socket:window" { export const constants: typeof statuses; import ipc from "socket:ipc"; import * as statuses from "socket:window/constants"; + import client from "socket:window/client"; import hotkey from "socket:window/hotkey"; + export { client, hotkey }; } declare module "socket:application" { @@ -8471,7 +8528,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9381,7 +9438,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9456,7 +9513,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -9952,7 +10009,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10016,7 +10073,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -10143,7 +10200,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10184,7 +10241,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -10367,7 +10424,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10402,13 +10459,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13121,7 +13178,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -13129,7 +13186,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13378,7 +13435,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -13389,7 +13446,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -14783,7 +14840,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -14794,13 +14851,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -14883,7 +14940,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -14927,7 +14984,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15287,7 +15344,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15325,9 +15382,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:module" { @@ -15667,7 +15724,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -15835,9 +15892,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -16505,7 +16562,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -16551,9 +16608,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; }; } export namespace Promise { @@ -16625,7 +16682,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16645,7 +16702,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -16667,8 +16724,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -16678,10 +16735,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -16782,7 +16839,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -16791,7 +16848,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -16872,8 +16929,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -16887,7 +16944,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17260,12 +17317,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17318,7 +17375,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17335,8 +17392,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 9bd0c81ebdc76beccda8e65089d847eca2700c59 Mon Sep 17 00:00:00 2001 From: chrisfarms <chris@farmiloe.com> Date: Sun, 30 Jun 2024 11:44:31 +0100 Subject: [PATCH 0891/1178] cli: only replace file ext, not path prevent paths like... "/my/github.com/path/thing.c" becoming... "/my/github.oom/path/thing.o" when generating build flags for extensions --- src/cli/cli.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 58a3d60514..e9711089a1 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -4377,10 +4377,10 @@ int main (const int argc, const char* argv[]) { } auto objectFile = source; - objectFile = replace(objectFile, "\\.mm", ".o"); - objectFile = replace(objectFile, "\\.m", ".o"); - objectFile = replace(objectFile, "\\.cc", ".o"); - objectFile = replace(objectFile, "\\.c", ".o"); + objectFile = replace(objectFile, "\\.mm$", ".o"); + objectFile = replace(objectFile, "\\.m$", ".o"); + objectFile = replace(objectFile, "\\.cc$", ".o"); + objectFile = replace(objectFile, "\\.c$", ".o"); auto filename = Path(objectFile).filename(); auto object = ( @@ -5758,10 +5758,10 @@ int main (const int argc, const char* argv[]) { } auto objectFile = source; - objectFile = replace(objectFile, "\\.mm", ".o"); - objectFile = replace(objectFile, "\\.m", ".o"); - objectFile = replace(objectFile, "\\.cc", ".o"); - objectFile = replace(objectFile, "\\.c", ".o"); + objectFile = replace(objectFile, "\\.mm$", ".o"); + objectFile = replace(objectFile, "\\.m$", ".o"); + objectFile = replace(objectFile, "\\.cc$", ".o"); + objectFile = replace(objectFile, "\\.c$", ".o"); auto object = Path(objectFile); From a21f22e6195d52fb926e19264c08cd3dddb02dc3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 20:52:03 +0200 Subject: [PATCH 0892/1178] refactor(app): clean up android bootstrap --- src/app/app.cc | 29 +++++++++++++++++------------ src/app/app.kt | 4 +++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 96438f3c2d..c5c9e55779 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -205,12 +205,6 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType self.app->windowManager.configure(windowManagerOptions); - auto defaultWindow = self.app->windowManager.createDefaultWindow(Window::Options { - .shouldExitApplicationOnClose = true - }); - - defaultWindow->setTitle(self.app->userConfig["meta_title"]); - static const auto port = getDevPort(); static const auto host = getDevHost(); @@ -220,7 +214,6 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType ) { auto serviceWorkerWindowOptions = Window::Options {}; auto serviceWorkerUserConfig = self.app->userConfig; - const auto screen = defaultWindow->getScreenSize(); serviceWorkerUserConfig["webview_watch_reload"] = "false"; serviceWorkerWindowOptions.shouldExitApplicationOnClose = false; @@ -240,6 +233,12 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType self.app->serviceWorkerContainer.init(&defaultWindow->bridge); } + auto defaultWindow = self.app->windowManager.createDefaultWindow(Window::Options { + .shouldExitApplicationOnClose = true + }); + + defaultWindow->setTitle(self.app->userConfig["meta_title"]); + if (isDebugEnabled() && port > 0 && host.size() > 0) { defaultWindow->navigate(host + ":" + std::to_string(port)); } else if (self.app->userConfig["webview_root"].size() != 0) { @@ -1184,8 +1183,6 @@ extern "C" { .shouldExitApplicationOnClose = true }); - defaultWindow->setTitle(app->userConfig["meta_title"]); - if ( app->userConfig["webview_service_worker_mode"] != "hybrid" && app->userConfig["permissions_allow_service_worker"] != "false" @@ -1193,7 +1190,6 @@ extern "C" { if (app->windowManager.getWindowStatus(SOCKET_RUNTIME_SERVICE_WORKER_CONTAINER_WINDOW_INDEX) == WindowManager::WINDOW_NONE) { auto serviceWorkerWindowOptions = Window::Options {}; auto serviceWorkerUserConfig = app->userConfig; - auto screen = defaultWindow->getScreenSize(); serviceWorkerUserConfig["webview_watch_reload"] = "false"; serviceWorkerWindowOptions.shouldExitApplicationOnClose = false; @@ -1208,12 +1204,21 @@ extern "C" { serviceWorkerWindow->navigate( "socket://" + app->userConfig["meta_bundle_identifier"] + "/socket/service-worker/index.html" ); + + app->core->setTimeout(256, [=](){ + serviceWorkerWindow->hide(); + }); } - } else if (app->userConfig["webview_service_worker_mode"] == "hybrid") { + } + + if ( + app->userConfig["permissions_allow_service_worker"] != "false" && + app->userConfig["webview_service_worker_mode"] == "hybrid" + ) { app->serviceWorkerContainer.init(&defaultWindow->bridge); } - defaultWindow->show(); + defaultWindow->setTitle(app->userConfig["meta_title"]); static const auto port = getDevPort(); static const auto host = getDevHost(); diff --git a/src/app/app.kt b/src/app/app.kt index 520f529d42..e162ae6843 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -64,6 +64,9 @@ open class AppActivity : WindowManagerActivity() { } override fun onCreate (savedInstanceState: Bundle?) { + this.supportActionBar?.hide() + this.getWindow()?.statusBarColor = android.graphics.Color.TRANSPARENT + super.onCreate(savedInstanceState) val rootDirectory = this.getRootDirectory() @@ -91,7 +94,6 @@ open class AppActivity : WindowManagerActivity() { ) } - console.log("onCreateAppActivity") app.onCreateAppActivity(this) if (savedInstanceState == null) { From a2b8d1fb240754cf24769a87031810e51927a3a4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 20:52:16 +0200 Subject: [PATCH 0893/1178] chore(core): clean up --- src/core/webview.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/webview.kt b/src/core/webview.kt index a54dc78cd5..52efc25fb8 100644 --- a/src/core/webview.kt +++ b/src/core/webview.kt @@ -1,10 +1,7 @@ // vim: set sw=2: package socket.runtime.core -import java.lang.ref.WeakReference - import android.content.Context -import android.util.AttributeSet import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse @@ -13,6 +10,12 @@ import android.webkit.WebResourceResponse */ open class WebView (context: android.content.Context) : android.webkit.WebView(context) +/** + * @see https://developer.android.com/reference/kotlin/android/webkit/WebChromeClient + */ open class WebChromeClient : android.webkit.WebChromeClient() {} -open class WebViewClient : android.webkit.WebViewClient() { -} + +/** + * @see https://developer.android.com/reference/kotlin/android/webkit/WebViewClient + */ +open class WebViewClient : android.webkit.WebViewClient() {} From 21da15002684c452ea8cf7d1728e1bcfe5413392 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 20:53:17 +0200 Subject: [PATCH 0894/1178] refactor(ipc): buffer scheme handler response output on android --- src/ipc/bridge.cc | 26 +++++++------- src/ipc/scheme_handlers.cc | 2 +- src/ipc/scheme_handlers.kt | 69 ++++++++++++++------------------------ 3 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 648bd286fd..5dda6454f7 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -13,16 +13,14 @@ namespace SSC::IPC { static Mutex mutex; // The `ESM_IMPORT_PROXY_TEMPLATE` is used to provide an ESM module as - // a proxy to a canonical URL for a module so `{{protocol}}:{{specifier}}` and - // `{{protocol}}://{{bundle_identifier}}/socket/{{pathname}}` resolve to the exact - // same module + // a proxy to a canonical URL for a module import. static constexpr auto ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT = R"S( /** - * This module exists to provide a proxy to a canonical URL for a module - * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` - * resolve to the exact same module instance. - * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} - */ + * This module exists to provide a proxy to a canonical URL for a module + * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` + * resolve to the exact same module instance. + * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} + */ import module from '{{url}}' export * from '{{url}}' export default module @@ -30,11 +28,11 @@ export default module static constexpr auto ESM_IMPORT_PROXY_TEMPLATE_WITHOUT_DEFAULT_EXPORT = R"S( /** - * This module exists to provide a proxy to a canonical URL for a module - * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` - * resolve to the exact same module instance. - * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} - */ + * This module exists to provide a proxy to a canonical URL for a module + * so `{{protocol}}:{{specifier}}` and `{{protocol}}://{bundle_identifier}/socket/{{pathname}}` + * resolve to the exact same module instance. + * @see {@link https://github.com/socketsupply/socket/blob/{{commit}}/api{{pathname}}} + */ export * from '{{url}}' )S"; @@ -582,7 +580,7 @@ export * from '{{url}}' // handle HEAD and GET requests for a file resource if (resourcePath.size() > 0) { if (resourcePath.starts_with(applicationResources)) { - contentLocation = resourcePath.substr(applicationResources.size() -1, resourcePath.size()); + contentLocation = resourcePath.substr(applicationResources.size(), resourcePath.size()); } auto resource = FileResource(resourcePath); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 9e3c83335f..42d85da16b 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -1239,7 +1239,7 @@ namespace SSC::IPC { if (!this->writeHead()) { debug( "IPC::SchemeHandlers::Response: Failed to write head for %s", - this->request->str().c_str() + this->request->str().c_str() ); return false; } diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index bb8214c98c..fae1ceb82d 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -76,6 +76,10 @@ open class SchemeHandlers (val bridge: Bridge) { fun getWebResourceResponse (): WebResourceResponse? { return this.response.response } + + fun waitForFinishedResponse () { + this.response.waitForFinish() + } } open class Response (val request: Request) { @@ -88,6 +92,9 @@ open class SchemeHandlers (val bridge: Bridge) { ) val headers = mutableMapOf<String, String>() + val buffers = mutableListOf<ByteArray>() + val semaphore = Semaphore(0) + var pendingWrites = 0 var finished = false @@ -119,25 +126,7 @@ open class SchemeHandlers (val bridge: Bridge) { } fun write (bytes: ByteArray) { - val stream = this.stream - val response = this - this.pendingWrites++ - try { - console.log("before write") - stream.write(bytes, 0, bytes.size) - } catch (err: Exception) { - if (!err.message.toString().contains("closed")) { - console.error("socket.runtime.ipc.SchemeHandlers.Response: ${err.toString()}") - } - } - - response.pendingWrites-- - console.log("after write pending=${response.pendingWrites}") - - if (response.pendingWrites == 0) { - response.finished = true - stream.close() - } + this.buffers += bytes } fun write (string: String) { @@ -145,25 +134,24 @@ open class SchemeHandlers (val bridge: Bridge) { } fun finish () { - val stream = this.stream - val response = this - thread { - if (!response.finished) { - console.log("waiting for writes to finish") - while (response.pendingWrites > 0) { - Thread.sleep(4) - } - - if (!response.finished) { - console.log("writes finished") - - for (entry in response.headers) { - console.log("${entry.key}: ${entry.value}") - } + if (!this.finished) { + this.finished = true + this.semaphore.release() + } + } - stream.flush() - stream.close() + fun waitForFinish () { + this.semaphore.acquireUninterruptibly() + this.semaphore.release() + if (this.finished) { + val stream = this.stream + val buffers = this.buffers + thread { + for (bytes in buffers) { + stream.write(bytes) } + stream.flush() + stream.close() } } } @@ -173,13 +161,8 @@ open class SchemeHandlers (val bridge: Bridge) { val request = Request(this.bridge, webResourceRequest) if (this.handleRequest(this.bridge.index, request)) { - val response = request.getWebResourceResponse() - response?.responseHeaders = mapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - return response + request.waitForFinishedResponse() + return request.getWebResourceResponse() } return null From 3f316215bef2c36400c50c760277f7d303d61a06 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 20:53:58 +0200 Subject: [PATCH 0895/1178] refactor(window): handle 'headless' on android, clean up --- src/window/android.cc | 6 ++++-- src/window/manager.cc | 13 ++++--------- src/window/manager.kt | 16 +++++++++++++--- src/window/window.kt | 23 +++++++---------------- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index 72606b5416..4fa4135096 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -19,6 +19,7 @@ namespace SSC { const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + this->index = this->options.index; this->bridge.userConfig = options.userConfig; this->bridge.configureNavigatorMounts(); @@ -44,9 +45,10 @@ namespace SSC { attachment.env, app->appActivity, "createWindow", - "(IZ)V", + "(IZZ)V", options.index, - options.shouldExitApplicationOnClose + options.shouldExitApplicationOnClose, + options.headless ); this->hotkey.init(); diff --git a/src/window/manager.cc b/src/window/manager.cc index 74d7d13ff2..43af547336 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -332,15 +332,10 @@ namespace SSC { } void WindowManager::ManagedWindow::hide () { - if ( - status > WindowStatus::WINDOW_HIDDEN && - status < WindowStatus::WINDOW_EXITING - ) { - auto index = std::to_string(this->index); - status = WindowStatus::WINDOW_HIDING; - Window::hide(); - status = WindowStatus::WINDOW_HIDDEN; - } + auto index = std::to_string(this->index); + status = WindowStatus::WINDOW_HIDING; + Window::hide(); + status = WindowStatus::WINDOW_HIDDEN; } void WindowManager::ManagedWindow::close (int code) { diff --git a/src/window/manager.kt b/src/window/manager.kt index 519fd84c93..f3a47ca2e4 100644 --- a/src/window/manager.kt +++ b/src/window/manager.kt @@ -39,7 +39,10 @@ open class WindowFragmentManager (protected val activity: WindowManagerActivity) // .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right) setReorderingAllowed(true) add(R.id.window, fragment) - addToBackStack("window#${options.index}") + if (!options.headless) { + //addToBackStack("window#${options.index}") + addToBackStack(null) + } } } } @@ -191,10 +194,15 @@ open class WindowManagerActivity : AppCompatActivity(R.layout.window_container_v /** * Creates a new window at a given `index`. */ - fun createWindow (index: Int = 0, shouldExitApplicationOnClose: Boolean = false) { + fun createWindow ( + index: Int = 0, + shouldExitApplicationOnClose: Boolean = false, + headless: Boolean = false + ) { this.windowFragmentManager.createWindowFragment(WindowOptions( index = index, - shouldExitApplicationOnClose = shouldExitApplicationOnClose + shouldExitApplicationOnClose = shouldExitApplicationOnClose, + headless = headless )) } @@ -229,12 +237,14 @@ open class WindowManagerActivity : AppCompatActivity(R.layout.window_container_v /** */ fun getWindowTitle (index: Int): String { + // TODO(@jwerle) return "" } /** */ fun setWindowTitle (index: Int, title: String): Boolean { + // TODO(@jwerle) return false } diff --git a/src/window/window.kt b/src/window/window.kt index 9eb5364d57..8130b4531d 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -46,7 +46,8 @@ data class ScreenSize (val width: Int, val height: Int); */ data class WindowOptions ( var index: Int = 0, - var shouldExitApplicationOnClose: Boolean = false + var shouldExitApplicationOnClose: Boolean = false, + var headless: Boolean = false ) /** @@ -245,6 +246,7 @@ open class WindowFragment : Fragment(R.layout.web_view) { arguments = bundleOf( "index" to options.index, "shouldExitApplicationOnClose" to options.shouldExitApplicationOnClose + "headless" to options.headless ) } } @@ -270,12 +272,10 @@ open class WindowFragment : Fragment(R.layout.web_view) { override fun onAttach (context: Context) { super.onAttach(context) - console.log("onAttach") } override fun onCreate (savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - console.log("onCreate") } override fun onCreateView ( @@ -283,28 +283,23 @@ open class WindowFragment : Fragment(R.layout.web_view) { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - console.log("onCreateView") return super.onCreateView(inflater, container, savedInstanceState) } override fun onStart () { super.onStart() - console.log("onStart") } override fun onStop () { super.onStop() - console.log("onStop") } override fun onResume () { super.onResume() - console.log("onResume") } override fun onPause () { super.onPause() - console.log("onPause") } /** @@ -319,6 +314,7 @@ open class WindowFragment : Fragment(R.layout.web_view) { if (arguments != null) { this.options.index = arguments.getInt("index", this.options.index) + this.options.headless = arguments.getBoolean("headless", this.options.headless) this.options.shouldExitApplicationOnClose = arguments.getBoolean( "shouldExitApplicationOnClose", this.options.shouldExitApplicationOnClose @@ -326,22 +322,17 @@ open class WindowFragment : Fragment(R.layout.web_view) { } this.webview = view.findViewById<WebView>(R.id.webview) - this.webview.apply { // features + settings.setGeolocationEnabled(app.hasRuntimePermission("geolocation")) + settings.javaScriptCanOpenWindowsAutomatically = true settings.javaScriptEnabled = true - settings.domStorageEnabled = app.hasRuntimePermission("data_access") settings.databaseEnabled = app.hasRuntimePermission("data_access") - settings.setGeolocationEnabled(app.hasRuntimePermission("geolocation")) - settings.javaScriptCanOpenWindowsAutomatically = true - - // allow list - settings.allowFileAccess = true settings.allowContentAccess = true + settings.allowFileAccess = true - // allow mixed content settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW } From 55b58b17b1788f951e38f24ca4b7b4362b91de4d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 20:54:18 +0200 Subject: [PATCH 0896/1178] chore(serviceworker): clean up --- src/serviceworker/container.cc | 7 ++++--- src/serviceworker/container.hh | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/serviceworker/container.cc b/src/serviceworker/container.cc index 8f9ef0a599..b599358b24 100644 --- a/src/serviceworker/container.cc +++ b/src/serviceworker/container.cc @@ -560,7 +560,7 @@ namespace SSC { return false; } - bool ServiceWorkerContainer::unregisterServiceWorker (uint64_t id) { + bool ServiceWorkerContainer::unregisterServiceWorker (ID id) { Lock lock(this->mutex); for (const auto& entry : this->registrations) { @@ -572,7 +572,7 @@ namespace SSC { return false; } - void ServiceWorkerContainer::skipWaiting (uint64_t id) { + void ServiceWorkerContainer::skipWaiting (ID id) { Lock lock(this->mutex); for (auto& entry : this->registrations) { @@ -593,7 +593,7 @@ namespace SSC { } } - void ServiceWorkerContainer::updateState (uint64_t id, const String& stateString) { + void ServiceWorkerContainer::updateState (ID id, const String& stateString) { Lock lock(this->mutex); for (auto& entry : this->registrations) { @@ -704,6 +704,7 @@ namespace SSC { return true; } + // the ID of the fetch request const auto id = rand64(); const auto client = JSON::Object::Entries { {"id", std::to_string(request.client.id)} diff --git a/src/serviceworker/container.hh b/src/serviceworker/container.hh index 7194f707d5..3b74079aa8 100644 --- a/src/serviceworker/container.hh +++ b/src/serviceworker/container.hh @@ -93,7 +93,7 @@ namespace SSC { }; struct FetchResponse { - uint64_t id = 0; + ID id = 0; int statusCode = 200; Headers headers; FetchBody body; @@ -102,8 +102,8 @@ namespace SSC { using Registrations = std::map<String, Registration>; using FetchCallback = std::function<void(const FetchResponse)>; - using FetchCallbacks = std::map<uint64_t, FetchCallback>; - using FetchRequests = std::map<uint64_t, FetchRequest>; + using FetchCallbacks = std::map<ID, FetchCallback>; + using FetchRequests = std::map<ID, FetchRequest>; SharedPointer<Core> core = nullptr; IPC::Bridge* bridge = nullptr; @@ -123,10 +123,10 @@ namespace SSC { void init (IPC::Bridge* bridge); void reset (); const Registration& registerServiceWorker (const RegistrationOptions& options); - bool unregisterServiceWorker (uint64_t id); + bool unregisterServiceWorker (ID id); bool unregisterServiceWorker (String scopeOrScriptURL); - void skipWaiting (uint64_t id); - void updateState (uint64_t id, const String& stateString); + void skipWaiting (ID id); + void updateState (ID id, const String& stateString); bool fetch (const FetchRequest& request, FetchCallback callback); bool claimClients (const String& scope); }; From f838a678dc7db184d2376dc202d5c11ef37f43e0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 20:55:07 +0200 Subject: [PATCH 0897/1178] feat(shared-worker): initial 'SharedWorker' implementation --- api/internal/shared-worker.js | 229 ---------------------------------- api/shared-worker.js | 16 +++ api/shared-worker/debug.js | 28 +++++ api/shared-worker/global.js | 27 ++++ api/shared-worker/index.html | 145 +++++++++++++++++++++ api/shared-worker/index.js | 175 ++++++++++++++++++++++++++ api/shared-worker/init.js | 162 ++++++++++++++++++++++++ api/shared-worker/state.js | 66 ++++++++++ api/shared-worker/worker.js | 209 +++++++++++++++++++++++++++++++ 9 files changed, 828 insertions(+), 229 deletions(-) delete mode 100644 api/internal/shared-worker.js create mode 100644 api/shared-worker.js create mode 100644 api/shared-worker/debug.js create mode 100644 api/shared-worker/global.js create mode 100644 api/shared-worker/index.html create mode 100644 api/shared-worker/index.js create mode 100644 api/shared-worker/init.js create mode 100644 api/shared-worker/state.js create mode 100644 api/shared-worker/worker.js diff --git a/api/internal/shared-worker.js b/api/internal/shared-worker.js deleted file mode 100644 index 4f36d72b44..0000000000 --- a/api/internal/shared-worker.js +++ /dev/null @@ -1,229 +0,0 @@ -/* global MessageChannel, MessagePort, EventTarget, Worker */ -import { murmur3, randomBytes } from '../crypto.js' -import process from '../process.js' -import globals from './globals.js' -import os from '../os.js' -import gc from '../gc.js' - -const workers = new Map() - -export class SharedHybridWorkerProxy extends EventTarget { - #eventListeners = [] - #started = false - #channel = null - #queue = [] - #port = null - #url = null - #id = null - - constructor (url, options) { - super() - - this.#id = randomBytes(8).toString('base64') - this.#url = new URL(url, globalThis.location.href) - this.#channel = globals.get('internal.sharedWorker.channel') - - let onmessage = null - - // temporary port until one becomes acquired - this.#port = Object.create(MessagePort.prototype, { - onmessage: { - configurable: true, - enumerable: true, - get: () => onmessage, - set: (value) => { - onmessage = value - this.#port.start() - } - }, - - onmessageerror: { - configurable: true, - enumerable: true - } - }) - - this.#port.start = () => { this.#started = true } - this.#port.close = () => {} - this.#port.postMessage = (...args) => { - this.#queue.push(args) - } - - this.#port.addEventListener = (...args) => { - this.#eventListeners.push(args) - } - - this.#port.removeEventListener = (...args) => { - this.#eventListeners = this.#eventListeners.filter((eventListener) => { - if ( - eventListener[0] === args[0] && - eventListener[1] === args[1] - ) { - return false - } - - return true - }) - } - - if (!this.#channel) { - throw new TypeError('Unable to acquire global SharedWorker BroadcastChannel') - } - - this.onChannelMessage = this.onChannelMessage.bind(this) - this.#channel.port2.addEventListener('message', this.onChannelMessage) - this.#channel.port2.postMessage({ - __runtime_shared_worker_proxy_create: { - id: this.#id, - url: this.#url.toString(), - options - } - }) - } - - get id () { - return this.#id - } - - get port () { - return this.#port - } - - onChannelMessage (event) { - const eventListeners = this.#eventListeners - const queue = this.#queue - - if (event.data?.__runtime_shared_worker_proxy_init) { - const { id, port } = event.data.__runtime_shared_worker_proxy_init - if (id === this.#id) { - const { start } = port - port.onmessage = this.#port.onmessage - this.#port = port - this.#port.start = () => { - start.call(port) - - for (const entry of queue) { - port.postMessage(...entry) - } - - for (const entry of eventListeners) { - port.addEventListener(...entry) - } - - eventListeners.splice(0, eventListeners.length) - queue.splice(0, queue.length) - } - - if (this.#started) { - this.#port.start() - } - - gc.ref(this) - } - } - } - - [gc.finalizer] () { - return { - args: [this.port, this.#channel, this.onChannelMessage], - handler (port, channel, onChannelMessage) { - try { - port.close() - } catch {} - - channel.removeEventListener('message', onChannelMessage) - } - } - } -} - -export class SharedHybridWorker extends EventTarget { - #id = null - #url = null - #name = null - #worker = null - #channel = null - #onmessage = null - - constructor (url, nameOrOptions) { - super() - if (typeof nameOrOptions === 'string') { - this.#name = nameOrOptions - } - - const options = nameOrOptions && typeof nameOrOptions === 'object' - ? { ...nameOrOptions } - : {} - - this.#url = new URL(url, globalThis.location.href) - // id is based on current origin and worker path name - this.#id = murmur3(globalThis.origin + this.#url.pathname) - - this.#worker = workers.get(this.#id) ?? new Worker(this.#url.toString(), { - [Symbol.for('socket.runtime.internal.worker.type')]: 'sharedWorker' - }) - - this.#channel = new MessageChannel() - - workers.set(this.#id, this.#worker) - - this.#worker.addEventListener('error', (event) => { - this.dispatchEvent(new Event(event.type, event)) - }) - - // notify worker of new message channel, transfering `port2` - // to be owned by the worker - this.#worker.postMessage({ - __runtime_shared_worker: { - ports: [this.#channel.port2], - origin: globalThis.origin, - options - } - }, { transfer: [this.#channel.port2] }) - } - - get port () { - return this.#channel.port1 - } -} - -const isInFrame = globalThis.window && globalThis.top !== globalThis.window - -export function getSharedWorkerImplementationForPlatform () { - if ( - os.platform() === 'android' || - (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) - ) { - if (isInFrame) { - return SharedHybridWorkerProxy - } - - return SharedHybridWorker - } - - return globalThis.SharedWorker ?? SharedHybridWorker -} - -export const SharedWorker = getSharedWorkerImplementationForPlatform() - -if (!isInFrame) { - const channel = new MessageChannel() - globals.register('internal.sharedWorker.channel', channel) - channel.port1.start() - channel.port2.start() - - channel.port1.addEventListener('message', (event) => { - if (event.data?.__runtime_shared_worker_proxy_create) { - const { id, url, options } = event.data?.__runtime_shared_worker_proxy_create - const worker = new SharedHybridWorker(url, options) - channel.port1.postMessage({ - __runtime_shared_worker_proxy_init: { - port: worker.port, - id - } - }, { transfer: [worker.port] }) - } - }) -} - -export default SharedWorker diff --git a/api/shared-worker.js b/api/shared-worker.js new file mode 100644 index 0000000000..d89b46d1ee --- /dev/null +++ b/api/shared-worker.js @@ -0,0 +1,16 @@ +import { SharedWorker } from './shared-worker/index.js' +import { Environment } from './shared-worker/env.js' + +/** + * A reference to the opened environment. This value is an instance of an + * `Environment` if the scope is a ServiceWorker scope. + * @type {Environment|null} + */ +export const env = Environment.instance + +export { + Environment, + SharedWorker +} + +export default SharedWorker diff --git a/api/shared-worker/debug.js b/api/shared-worker/debug.js new file mode 100644 index 0000000000..5acf272776 --- /dev/null +++ b/api/shared-worker/debug.js @@ -0,0 +1,28 @@ +import globals from '../internal/globals.js' +import util from '../util.js' + +export function debug (...args) { + const state = globals.get('SharedWorker.state') + + if (process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG) { + console.debug(...args) + } + + if (args[0] instanceof Error) { + globalThis.postMessage({ + __shared_worker_debug: [ + `[${state.sharedWorker.scriptURL}]: ${util.format(...args)}` + ] + }) + + if (typeof state?.reportError === 'function') { + state.reportError(args[0]) + } else if (typeof globalThis.reportError === 'function') { + globalThis.reportError(args[0]) + } + } else { + globalThis.postMessage({ __shared_worker_debug: [util.format(...args)] }) + } +} + +export default debug diff --git a/api/shared-worker/global.js b/api/shared-worker/global.js new file mode 100644 index 0000000000..14f6f53f38 --- /dev/null +++ b/api/shared-worker/global.js @@ -0,0 +1,27 @@ +// events +let onconnect = null + +export class SharedWorkerGlobalScope { + get isSharedWorkerScope () { + return true + } + + get onconnect () { + return onconnect + } + + set onconnect (listener) { + if (onconnect) { + globalThis.removeEventListener('connect', onconnect) + } + + onconnect = null + + if (typeof listener === 'function') { + globalThis.addEventListener('connect', listener) + onconnect = listener + } + } +} + +export default SharedWorkerGlobalScope.prototype diff --git a/api/shared-worker/index.html b/api/shared-worker/index.html new file mode 100644 index 0000000000..dba4d4fc0f --- /dev/null +++ b/api/shared-worker/index.html @@ -0,0 +1,145 @@ +<!doctype html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta + http-equiv="Content-Security-Policy" + content=" + connect-src socket: https: http: blob: ipc: wss: ws: ws://localhost:* {{protocol_handlers}}; + script-src socket: https: http: blob: http://localhost:* 'unsafe-eval' 'unsafe-inline' {{protocol_handlers}}; + worker-src socket: https: http: blob: 'unsafe-eval' 'unsafe-inline' {{protocol_handlers}}; + frame-src socket: https: http: blob: http://localhost:*; + img-src socket: https: http: blob: http://localhost:*; + child-src socket: https: http: blob:; + object-src 'none'; + " + > + <script charset="utf-8" type="text/javascript"> + Object.defineProperty(globalThis, '__RUNTIME_SHARED_WORKER_CONTEXT__', { + configurable: false, + enumerable: false, + writable: false, + value: true + }) + </script> + <script type="module"> + import application from 'socket:application' + import process from 'socket:process' + + Object.assign(globalThis, { + async openExternalLink (href) { + const currentWindow = await application.getCurrentWindow() + await currentWindow.openExternal(href) + } + }) + + document.title = `Shared Worker Debugger - v${process.version}` + </script> + <script type="module" src="./init.js"></script> + <style type="text/css" media="all"> + * { + box-sizing: border-box; + } + + body { + background: rgba(40, 40, 40, 1); + color: rgba(255, 255, 255, 1); + font-family: 'Inter-Light', sans-serif; + font-size: 14px; + margin: 0; + position: absolute; top: 0px; left: 0; right:0; bottom: 0px; + } + + a { + color: inherit; + text-decoration: none; + transition: all 0.2s ease; + + &.with-hover:hover { + border-bottom: 2px solid rgba(255, 255, 255, 1); + } + } + + p { + background: rgba(14, 85, 152, .25); + border-radius: 2px; + display: inline-block; + font: 12px/26px 'Inter-Light', sans-serif; + margin: 0; + text-align: center; + width: 100%; + + &.message { + display: block; + overflow: hidden; + padding: 0 8px; + position: relative; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } + } + + pre#log { + background: rgba(0, 0, 0, 1); + overflow-y: scroll; + padding: 16px; + margin: 0; + position: absolute; top: 0px; left: 0; right:0; bottom: 0px; + + & span { + opacity: 0.8; + transition: all 0.2s ease; + + &:hover { + opacity: 1; + transition: all 0.05s ease; + } + + & code { + color: rgb(215, 215, 215); + display: block; + font-size: 12px; + line-break: anywhere; + margin-bottom: 8px; + opacity: 0.8; + white-space: wrap; + transition: all 0.1s ease; + + &:hover { + color: rgb(225, 225, 225); + opacity: 1; + transition: all 0.025s ease; + } + + & span { + &.red { + color: red; + } + } + } + + & details { + &[open] span { + opacity: 1; + transition: all 0.05s ease; + & code { + color: rgb(225, 225, 225); + opacity: 1; + transition: all 0.025s ease; + } + } + + & > summary { + cursor: pointer; + list-style: none; + } + } + } + } + </style> + </head> + <body> + <pre id="log"></pre> + </body> +</html> diff --git a/api/shared-worker/index.js b/api/shared-worker/index.js new file mode 100644 index 0000000000..0098254a14 --- /dev/null +++ b/api/shared-worker/index.js @@ -0,0 +1,175 @@ +/* global ErrorEvent */ +import application from '../application.js' +import location from '../location.js' +import crypto from '../crypto.js' +import ipc from '../ipc.js' + +let contextWindow = null + +export const SHARED_WORKER_WINDOW_INDEX = 46 +export const SHARED_WORKER_WINDOW_TITLE = 'socket:shared-worker' +export const SHARED_WORKER_WINDOW_PATH = `${location.origin}/socket/shared-worker/index.html` + +export const channel = new BroadcastChannel('socket.runtime.sharedWorker') +export const workers = new Map() + +channel.addEventListener('message', (event) => { + if (event.data?.error?.id) { + const ref = workers.get(event.data.error.id) + if (ref) { + const worker = ref.deref() + if (!worker) { + workers.delete(event.data.error.id) + } else { + worker.dispatchEvent(new ErrorEvent('error', { + error: new Error(event.data.error.message) || '' + })) + } + } + } +}) + +export async function init (sharedWorker, options) { + await getContextWindow() + channel.postMessage({ + connect: { + scriptURL: options.scriptURL, + name: options.name, + port: sharedWorker.port, + id: sharedWorker.id + } + }) + + workers.set(sharedWorker.id, new WeakRef(sharedWorker)) +} + +export class SharedWorkerMessagePort extends ipc.IPCMessagePort { + [Symbol.for('socket.runtime.serialize')] () { + return { + ...(super[Symbol.for('socket.runtime.serialize')]()), + __type__: 'SharedWorkerMessagePort' + } + } +} + +export class SharedWorker extends EventTarget { + #id = null + #port = null + #ready = null + #onerror = null + + /** + * `SharedWorker` class constructor. + * @param {string} aURL + * @param {string|object=} [nameOrOptions] + */ + constructor (aURL, nameOrOptions = null) { + const url = new URL(aURL, location.origin) + const id = crypto.murmur3(url.origin + url.pathname) + + super(url.toString(), nameOrOptions) + + this.#id = id + this.#port = SharedWorkerMessagePort.create({ id }) + this.#ready = init(this, { + scriptURL: url.toString(), + name: typeof nameOrOptions === 'string' + ? nameOrOptions + : typeof nameOrOptions?.name === 'string' + ? nameOrOptions.name + : null + }) + } + + get onerror () { + return this.#onerror + } + + set onerror (onerror) { + if (typeof this.#onerror === 'function') { + this.removeEventListener('error', this.#onerror) + } + + this.#onerror = null + + if (typeof onerror === 'function') { + this.#onerror = onerror + this.addEventListener('error', onerror) + } + } + + get ready () { + return this.#ready + } + + get port () { + return this.#port + } + + get id () { + return this.#id + } +} + +/** + * Gets the SharedWorker context window. + * This function will create it if it does not already exist. + * @return {Promise<import('./window.js').ApplicationWindow} + */ +export async function getContextWindow () { + if (contextWindow) { + await contextWindow.ready + return contextWindow + } + + const existingContextWindow = await application.getWindow( + SHARED_WORKER_WINDOW_INDEX, + { max: false } + ) + + const pendingContextWindow = ( + existingContextWindow ?? + application.createWindow({ + canExit: false, + //headless: !process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG, + headless: false, + // @ts-ignore + //debug: Boolean(process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG), + debug: true, + index: SHARED_WORKER_WINDOW_INDEX, + title: SHARED_WORKER_WINDOW_TITLE, + path: SHARED_WORKER_WINDOW_PATH, + config: { + webview_watch_reload: false + } + }) + ) + + const promises = [] + promises.push(Promise.resolve(pendingContextWindow)) + + if (!existingContextWindow) { + promises.push(new Promise((resolve) => { + const timeout = setTimeout(resolve, 500) + channel.addEventListener('message', function onMessage (event) { + if (event.data?.ready === SHARED_WORKER_WINDOW_INDEX) { + clearTimeout(timeout) + resolve(null) + channel.removeEventListener('message', onMessage) + } + }) + })) + } + + const ready = Promise.all(promises) + contextWindow = pendingContextWindow + contextWindow.ready = ready + + await ready + contextWindow = await pendingContextWindow + contextWindow.ready = ready + + return contextWindow +} + +export default SharedWorker diff --git a/api/shared-worker/init.js b/api/shared-worker/init.js new file mode 100644 index 0000000000..a5326635cb --- /dev/null +++ b/api/shared-worker/init.js @@ -0,0 +1,162 @@ +/* global Worker */ +import { channel } from './index.js' +import globals from '../internal/globals.js' +import crypto from '../crypto.js' + +export const workers = new Map() +export { channel } + +globals.register('SharedWorkerContext.workers', workers) +globals.register('SharedWorkerContext.info', new Map()) + +channel.addEventListener('message', (event) => { + if (event.data?.connect) { + onConnect(event) + } +}) + +export class SharedWorkerInstance extends Worker { + #info = null + + constructor (filename, options) { + super(filename, { + name: `SharedWorker (${options?.info?.pathname ?? filename})`, + ...options, + [Symbol.for('socket.runtime.internal.worker.type')]: 'sharedWorker' + }) + + this.#info = options?.info ?? null + this.addEventListener('message', this.onMessage.bind(this)) + } + + get info () { + return this.#info + } + + async onMessage (event) { + if (Array.isArray(event.data.__shared_worker_debug)) { + const log = document.querySelector('#log') + if (log) { + for (const entry of event.data.__shared_worker_debug) { + const lines = entry.split('\n') + const span = document.createElement('span') + let target = span + + for (const line of lines) { + if (!line) continue + const item = document.createElement('code') + item.innerHTML = line + .replace(/\s/g, ' ') + .replace(/\\s/g, ' ') + .replace(/<anonymous>/g, '<anonymous>') + .replace(/([a-z|A-Z|_|0-9]+(Error|Exception)):/g, '<span class="red"><b>$1</b>:</span>') + + if (target === span && lines.length > 1) { + target = document.createElement('details') + const summary = document.createElement('summary') + summary.appendChild(item) + target.appendChild(summary) + span.appendChild(target) + } else { + target.appendChild(item) + } + } + + log.appendChild(span) + } + + log.scrollTop = log.scrollHeight + } + } + } +} + +export class SharedWorkerInfo { + id = null + port = null + scriptURL = null + + url = null + hash = null + + constructor (data) { + for (const key in data) { + const value = data[key] + if (key in this) { + this[key] = value + } + } + + const url = new URL(this.scriptURL) + this.url = url.toString() + this.hash = crypto.murmur3(url.toString()) + } + + get pathname () { + return new URL(this.url).pathname + } +} + +export async function onInstall (event) { + const info = new SharedWorkerInfo(event.data.install) + + if (!info.id || workers.has(info.hash)) { + return + } + + const worker = new SharedWorkerInstance('./worker.js', { + info + }) + + workers.set(info.hash, worker) + globals.get('SharedWorkerContext.info').set(info.hash, info) + worker.postMessage({ install: info }) +} + +export async function onUninstall (event) { + const info = new SharedWorkerInfo(event.data.uninstall) + + if (!workers.has(info.hash)) { + return + } + + const worker = workers.get(info.hash) + workers.delete(info.hash) + + worker.postMessage({ uninstall: info }) +} + +export async function onConnect (event) { + const info = new SharedWorkerInfo(event.data.connect) + + if (!info.id) { + return + } + + if (!workers.has(info.hash)) { + onInstall(new MessageEvent('message', { + data: { + install: event.data.connect + } + })) + + try { + await new Promise((resolve, reject) => { + channel.addEventListener('message', (event) => { + if (event.data?.error?.id === info.id) { + reject() + } else if (event.data?.installed?.id === info.id) { + resolve() + } + }) + }) + } catch (err) { + console.error(err) + } + } + + const worker = workers.get(info.hash) + worker.postMessage({ connect: info }) +} + +export default null diff --git a/api/shared-worker/state.js b/api/shared-worker/state.js new file mode 100644 index 0000000000..9228c74396 --- /dev/null +++ b/api/shared-worker/state.js @@ -0,0 +1,66 @@ +const descriptors = { + channel: { + configurable: false, + enumerable: false, + value: null + }, + + sharedWorker: { + configurable: false, + enumerable: true, + value: Object.create(null, { + scriptURL: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + state: { + configurable: false, + enumerable: true, + writable: true, + value: 'parsed' + }, + + id: { + configurable: false, + enumerable: true, + writable: true, + value: null + } + }) + }, + + id: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + connect: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + env: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + + reportError: { + configurable: false, + enumerable: false, + writable: true, + value: globalThis.reportError.bind(globalThis) + } +} + +export const state = Object.create(null, descriptors) + +export default state diff --git a/api/shared-worker/worker.js b/api/shared-worker/worker.js new file mode 100644 index 0000000000..28db06444f --- /dev/null +++ b/api/shared-worker/worker.js @@ -0,0 +1,209 @@ +/* eslint-disable import/first */ +globalThis.isSharedWorkerScope = true + +import { SharedWorkerMessagePort, channel } from './index.js' +import { SharedWorkerGlobalScope } from './global.js' +import { Module, createRequire } from '../module.js' +import { Environment } from '../service-worker/env.js' +import { Buffer } from '../buffer.js' +import { Cache } from '../commonjs/cache.js' +import globals from '../internal/globals.js' +import process from '../process.js' +import debug from './debug.js' +import hooks from '../hooks.js' +import state from './state.js' +import path from '../path.js' +import ipc from '../ipc.js' + +import '../console.js' + +export default null + +Object.defineProperties( + globalThis, + Object.getOwnPropertyDescriptors(SharedWorkerGlobalScope.prototype) +) + +export const SHARED_WORKER_READY_TOKEN = { __shared_worker_ready: true } + +// state +export const module = { exports: {} } + +// event listeners +hooks.onReady(onReady) +globalThis.addEventListener('message', onMessage) + +// shared worker globals +globals.register('SharedWorker.state', state) +globals.register('SharedWorker.module', module) + +export function onReady () { + globalThis.postMessage(SHARED_WORKER_READY_TOKEN) +} + +export async function onMessage (event) { + const { data } = event + + if (data?.install) { + event.stopImmediatePropagation() + + const { id, scriptURL } = data.install + const url = new URL(scriptURL) + + if (!url.pathname.startsWith('/socket/')) { + // preload commonjs cache for user space server workers + Cache.restore(['loader.status', 'loader.response']) + } + + state.id = id + state.sharedWorker.id = id + state.sharedWorker.scriptURL = scriptURL + + Module.main.addEventListener('error', (event) => { + if (event.error) { + debug(event.error) + } + }) + + Object.defineProperties(globalThis, { + require: { + configurable: false, + enumerable: false, + writable: false, + value: createRequire(scriptURL) + }, + + origin: { + configurable: false, + enumerable: true, + writable: false, + value: url.origin + }, + + __dirname: { + configurable: false, + enumerable: false, + writable: false, + value: path.dirname(url.pathname) + }, + + __filename: { + configurable: false, + enumerable: false, + writable: false, + value: url.pathname + }, + + module: { + configurable: false, + enumerable: false, + writable: false, + value: module + }, + + exports: { + configurable: false, + enumerable: false, + get: () => module.exports + }, + + process: { + configurable: false, + enumerable: false, + get: () => process + }, + + Buffer: { + configurable: false, + enumerable: false, + get: () => Buffer + }, + + global: { + configurable: false, + enumerable: false, + get: () => globalThis + } + }) + + try { + // define the actual location of the worker. not `blob:...` + globalThis.RUNTIME_WORKER_LOCATION = scriptURL + // update state + state.sharedWorker.state = 'installing' + // open envirnoment + state.env = await Environment.open({ type: 'sharedWorker', id }) + console.log({ scriptURL }) + // import module, which could be ESM, CommonJS, + // or a simple SharedWorker + const result = await import(scriptURL) + + if (typeof module.exports === 'function') { + module.exports = { + default: module.exports + } + } else { + Object.assign(module.exports, result) + } + // update state + state.sharedWorker.state = 'installed' + } catch (err) { + debug(err) + state.sharedWorker.state = 'error' + channel.postMessage({ + error: { id: state.id, message: err.message } + }) + return + } + + if (module.exports.default && typeof module.exports.default === 'object') { + if (typeof module.exports.default.connect === 'function') { + state.connect = module.exports.default.connect.bind(module.exports.default) + } + } else if (typeof module.exports.connect === 'function') { + state.connect = module.exports.connect.bind(module.exports) + } + + if (module.exports.default && typeof module.exports.default === 'object') { + if (typeof module.exports.default.reportError === 'function') { + state.reportError = module.exports.default.reportError.bind(module.exports.default) + } + } else if (typeof module.exports.reportError === 'function') { + state.reportError = module.reportError.bind(module.exports) + } + + if (typeof state.connect === 'function') { + globalThis.addEventListener('connect', async (event) => { + try { + const promise = state.connect(state.env, event.data, event.ports[0]) + await promise + } catch (err) { + debug(err) + channel.postMessage({ + error: { id: state.id, message: err.message } + }) + } + }) + } + channel.postMessage({ installed: { id: state.id } }) + return + } + + if (data?.connect) { + event.stopImmediatePropagation() + const connection = ipc.inflateIPCMessageTransfers(event.data.connect, new Map(Object.entries({ + 'SharedWorkerMessagePort': SharedWorkerMessagePort + }))) + + const connectEvent = new MessageEvent('connect') + Object.defineProperty(connectEvent, 'ports', { + configurable: false, + writable: false, + value: Object.seal(Object.freeze([connection.port])) + }) + console.log({ connectEvent }) + globalThis.dispatchEvent(connectEvent) + // eslint-disable-next-line + return + } +} From 044d6c8bb56ce39c836f12c95974bb8780e0bc3c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:13:58 +0200 Subject: [PATCH 0898/1178] refactor(api/shared-worker): include more client info --- api/shared-worker/index.js | 8 ++++---- api/shared-worker/init.js | 3 ++- api/shared-worker/worker.js | 23 ++++++++++++++++++++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/api/shared-worker/index.js b/api/shared-worker/index.js index 0098254a14..c758bd7281 100644 --- a/api/shared-worker/index.js +++ b/api/shared-worker/index.js @@ -2,6 +2,7 @@ import application from '../application.js' import location from '../location.js' import crypto from '../crypto.js' +import client from '../application/client.js' import ipc from '../ipc.js' let contextWindow = null @@ -34,6 +35,7 @@ export async function init (sharedWorker, options) { channel.postMessage({ connect: { scriptURL: options.scriptURL, + client: client.toJSON(), name: options.name, port: sharedWorker.port, id: sharedWorker.id @@ -131,11 +133,9 @@ export async function getContextWindow () { existingContextWindow ?? application.createWindow({ canExit: false, - //headless: !process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG, - headless: false, + headless: !process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG, // @ts-ignore - //debug: Boolean(process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG), - debug: true, + debug: Boolean(process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG), index: SHARED_WORKER_WINDOW_INDEX, title: SHARED_WORKER_WINDOW_TITLE, path: SHARED_WORKER_WINDOW_PATH, diff --git a/api/shared-worker/init.js b/api/shared-worker/init.js index a5326635cb..847b27cd03 100644 --- a/api/shared-worker/init.js +++ b/api/shared-worker/init.js @@ -74,6 +74,7 @@ export class SharedWorkerInstance extends Worker { export class SharedWorkerInfo { id = null port = null + client = null scriptURL = null url = null @@ -144,7 +145,7 @@ export async function onConnect (event) { await new Promise((resolve, reject) => { channel.addEventListener('message', (event) => { if (event.data?.error?.id === info.id) { - reject() + reject(new Error(event.data.error.message)) } else if (event.data?.installed?.id === info.id) { resolve() } diff --git a/api/shared-worker/worker.js b/api/shared-worker/worker.js index 28db06444f..f050c5b6f4 100644 --- a/api/shared-worker/worker.js +++ b/api/shared-worker/worker.js @@ -133,7 +133,6 @@ export async function onMessage (event) { state.sharedWorker.state = 'installing' // open envirnoment state.env = await Environment.open({ type: 'sharedWorker', id }) - console.log({ scriptURL }) // import module, which could be ESM, CommonJS, // or a simple SharedWorker const result = await import(scriptURL) @@ -185,14 +184,20 @@ export async function onMessage (event) { } }) } + channel.postMessage({ installed: { id: state.id } }) + debug( + '[%s]: SharedWorker (%s) installed', + new URL(scriptURL).pathname.replace(/^\/socket\//, 'socket:'), + state.id + ) return } if (data?.connect) { event.stopImmediatePropagation() const connection = ipc.inflateIPCMessageTransfers(event.data.connect, new Map(Object.entries({ - 'SharedWorkerMessagePort': SharedWorkerMessagePort + SharedWorkerMessagePort }))) const connectEvent = new MessageEvent('connect') @@ -201,8 +206,20 @@ export async function onMessage (event) { writable: false, value: Object.seal(Object.freeze([connection.port])) }) - console.log({ connectEvent }) + globalThis.dispatchEvent(connectEvent) + + debug( + '[%s]: SharedWorker (%s) connection from client (%s/%s) at %s', + new URL(state.sharedWorker.scriptURL).pathname.replace(/^\/socket\//, 'socket:'), + state.id, + connection.client.id, + [ + connection.client.frameType.replace('none', ''), + connection.client.type + ].filter(Boolean).join('-'), + connection.client.location + ) // eslint-disable-next-line return } From 3d17bbac5296ff326d597f43a9be771c3f293bcd Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:14:20 +0200 Subject: [PATCH 0899/1178] refactor(api/service-worker): use new 'SharedWorker', clean up --- api/{window => application}/client.js | 0 api/service-worker.js | 2 +- api/service-worker/container.js | 2 +- api/service-worker/debug.js | 2 +- api/service-worker/env.js | 16 +++++++++++++--- api/service-worker/events.js | 7 +++++++ api/service-worker/init.js | 21 ++++++++++++--------- api/service-worker/instance.js | 2 +- api/service-worker/notification.js | 2 +- api/service-worker/worker.js | 24 ++++++++++++++++-------- 10 files changed, 53 insertions(+), 25 deletions(-) rename api/{window => application}/client.js (100%) diff --git a/api/window/client.js b/api/application/client.js similarity index 100% rename from api/window/client.js rename to api/application/client.js diff --git a/api/service-worker.js b/api/service-worker.js index d124ed3b6a..ce00a3f976 100644 --- a/api/service-worker.js +++ b/api/service-worker.js @@ -3,7 +3,7 @@ import { Environment } from './service-worker/env.js' import { Context } from './service-worker/context.js' /** - * A reference toe opened environment. This value is an instance of an + * A reference to the opened environment. This value is an instance of an * `Environment` if the scope is a ServiceWorker scope. * @type {Environment|null} */ diff --git a/api/service-worker/container.js b/api/service-worker/container.js index b33668596b..3c0698eefb 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -1,7 +1,7 @@ /* global EventTarget */ import { ServiceWorkerRegistration } from './registration.js' import { createServiceWorker, SHARED_WORKER_URL } from './instance.js' -import { SharedWorker } from '../internal/shared-worker.js' +import { SharedWorker } from '../shared-worker/index.js' import { Deferred } from '../async.js' import application from '../application.js' import location from '../location.js' diff --git a/api/service-worker/debug.js b/api/service-worker/debug.js index 5c5dbbfcc8..db7a862394 100644 --- a/api/service-worker/debug.js +++ b/api/service-worker/debug.js @@ -4,7 +4,7 @@ import util from '../util.js' export function debug (...args) { const state = globals.get('ServiceWorker.state') - if (process.env.SOCKET_RUTIME_SERVICE_WORKER_DEBUG) { + if (process.env.SOCKET_RUNTIME_SERVICE_WORKER_DEBUG) { console.debug(...args) } diff --git a/api/service-worker/env.js b/api/service-worker/env.js index 2bfce19c6b..929e09dd9c 100644 --- a/api/service-worker/env.js +++ b/api/service-worker/env.js @@ -68,7 +68,8 @@ export class Environment extends EventTarget { #database = null #context = {} #proxy = null - #scope = null + #scope = '/' + #type = 'serviceWorker' /** * `Environment` class constructor @@ -80,7 +81,8 @@ export class Environment extends EventTarget { Environment.instance = this - this.#scope = options.scope + this.#type = options?.type ?? this.#type + this.#scope = options.scope ?? this.#scope this.#proxy = new Proxy(this.#context, { get: (target, property) => { return target[property] ?? undefined @@ -136,13 +138,21 @@ export class Environment extends EventTarget { return this.#proxy } + /** + * The environment type + * @type {string} + */ + get type () { + return this.#type + } + /** * The current environment name. This value is also used as the * internal database name. * @type {string} */ get name () { - return `socket.runtime.serviceWorker.env(${this.#scope})` + return `socket.runtime.${this.#type}.env(${this.#scope})` } /** diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 0cc5362cb5..3378b02d2d 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -396,6 +396,13 @@ export class ExtendableMessageEvent extends ExtendableEvent { return this.#source } + /** + * @type {string?} + */ + get origin () { + return this.#origin + } + /** * @type {string} */ diff --git a/api/service-worker/init.js b/api/service-worker/init.js index 134eb981e2..26287edcf4 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -1,6 +1,6 @@ /* global Worker */ import { SHARED_WORKER_URL } from './instance.js' -import { SharedWorker } from '../internal/shared-worker.js' +import { SharedWorker } from '../shared-worker/index.js' import { Notification } from '../notification.js' import { sleep } from '../timers.js' import globals from '../internal/globals.js' @@ -195,13 +195,17 @@ export async function onFetch (event) { } })(), new Promise((resolve) => { - globalThis.top.addEventListener('serviceWorker.activate', async function onActivate (event) { - const { hash } = new ServiceWorkerInfo(event.detail) - if (hash === info.hash) { - await sleep(64) - resolve() + globalThis.top.addEventListener( + 'serviceWorker.activate', + async function (event) { + // @ts-ignore + const { hash } = new ServiceWorkerInfo(event.detail) + if (hash === info.hash) { + await sleep(64) + resolve(null) + } } - }) + ) }) ]) @@ -213,8 +217,7 @@ export async function onFetch (event) { id: event.detail.fetch.id } - await ipc.write('serviceWorker.fetch.response', options) - return + return await ipc.write('serviceWorker.fetch.response', options) } const client = event.detail.fetch.client ?? {} diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 7fdeb715a2..e39e4f100c 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -1,4 +1,4 @@ -import { SharedWorker } from '../internal/shared-worker.js' +import { SharedWorker } from '../shared-worker/index.js' import location from '../location.js' import state from './state.js' diff --git a/api/service-worker/notification.js b/api/service-worker/notification.js index 6a324b15b5..ae094bfdf2 100644 --- a/api/service-worker/notification.js +++ b/api/service-worker/notification.js @@ -1,7 +1,7 @@ import { Notification, NotificationOptions } from '../notification.js' import { SHARED_WORKER_URL } from './instance.js' import { NotAllowedError } from '../errors.js' -import { SharedWorker } from '../internal/shared-worker.js' +import { SharedWorker } from '../shared-worker/index.js' import permissions from '../internal/permissions.js' let sharedWorker = null diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 7ef4248801..44c995da14 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -30,16 +30,18 @@ import { import '../console.js' -const SERVICE_WORKER_READY_TOKEN = { __service_worker_ready: true } - Object.defineProperties( globalThis, Object.getOwnPropertyDescriptors(ServiceWorkerGlobalScope.prototype) ) -const module = { exports: {} } -const events = new Set() -const stages = { register: null, install: null, activate: null } +export default null + +export const SERVICE_WORKER_READY_TOKEN = { __service_worker_ready: true } + +export const module = { exports: {} } +export const events = new Set() +export const stages = { register: null, install: null, activate: null } // service worker life cycle stages stages.register = new Deferred() stages.install = new Deferred(stages.register) @@ -49,17 +51,17 @@ stages.activate = new Deferred(stages.install) hooks.onReady(onReady) globalThis.addEventListener('message', onMessage) -// service worker globals +// service worker globals globals.register('ServiceWorker.state', state) globals.register('ServiceWorker.stages', stages) globals.register('ServiceWorker.events', events) globals.register('ServiceWorker.module', module) -function onReady () { +export function onReady () { globalThis.postMessage(SERVICE_WORKER_READY_TOKEN) } -async function onMessage (event) { +export async function onMessage (event) { if (event instanceof ExtendableMessageEvent) { return } @@ -136,6 +138,12 @@ async function onMessage (event) { get: () => process }, + Buffer: { + configurable: false, + enumerable: false, + get: () => Buffer + }, + global: { configurable: false, enumerable: false, From 9b91fc6c0088750e1899f8b0240dd68cbd3537f7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:14:50 +0200 Subject: [PATCH 0900/1178] fix(api/location.js): handle 'null' origin string --- api/location.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/location.js b/api/location.js index 7fdecaa625..29113030f0 100644 --- a/api/location.js +++ b/api/location.js @@ -30,7 +30,9 @@ export class Location { } get origin () { - return this.url.origin + return this.url.origin && this.url.origin !== 'null' + ? this.url.origin + : globalThis.origin || globalThis.location.origin } get href () { From 9beff1b3dc0a0baa04721f99294ceb59518ff972 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:15:24 +0200 Subject: [PATCH 0901/1178] refactor(api/internal): improve postMessage API usage --- api/internal/post-message.js | 35 ++++++++++++++++++++++++++--------- api/internal/primitives.js | 2 +- api/internal/serialize.js | 11 ++++++++--- api/internal/worker.js | 28 +++------------------------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/api/internal/post-message.js b/api/internal/post-message.js index 36ac2a4792..d4a5f5a176 100644 --- a/api/internal/post-message.js +++ b/api/internal/post-message.js @@ -1,22 +1,39 @@ import serialize from './serialize.js' +const { + BroadcastChannel, + MessagePort, + postMessage +} = globalThis + const platform = { - BroadcastChannelPostMessage: globalThis.BroadcastChannel.prototype.postMessage, - MessageChannelPostMessage: globalThis.MessageChannel.prototype.postMessage, - GlobalPostMessage: globalThis.postMessage + BroadcastChannelPostMessage: BroadcastChannel.prototype.postMessage, + MessagePortPostMessage: MessagePort.prototype.postMessage, + GlobalPostMessage: postMessage } -globalThis.BroadcastChannel.prototype.postMessage = function (message, ...args) { - message = handlePostMessage(message) - return platform.BroadcastChannelPostMessage.call(this, message, ...args) +BroadcastChannel.prototype.postMessage = function (message, ...args) { + return platform.BroadcastChannelPostMessage.call( + this, + handlePostMessage(message), + ...args + ) } -globalThis.MessageChannel.prototype.postMessage = function (message, ...args) { - return platform.MessageChannelPostMessage.call(this, handlePostMessage(message), ...args) +MessagePort.prototype.postMessage = function (message, ...args) { + return platform.MessagePortPostMessage.call( + this, + handlePostMessage(message), + ...args + ) } globalThis.postMessage = function (message, ...args) { - return platform.GlobalPostMessage.call(this, handlePostMessage(message), ...args) + return platform.GlobalPostMessage.call( + this, + handlePostMessage(message), + ...args + ) } function handlePostMessage (message) { diff --git a/api/internal/primitives.js b/api/internal/primitives.js index e9cabfd522..44d14257c5 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -5,8 +5,8 @@ import './error.js' import { fetch, Headers, Request, Response } from '../fetch.js' import { URL, URLPattern, URLSearchParams } from '../url.js' import { ServiceWorker } from '../service-worker/instance.js' +import { SharedWorker } from '../shared-worker/index.js' import serviceWorker from './service-worker.js' -import SharedWorker from './shared-worker.js' import Notification from '../notification.js' import geolocation from './geolocation.js' import permissions from './permissions.js' diff --git a/api/internal/serialize.js b/api/internal/serialize.js index a99b1fcb9e..ddec16e158 100644 --- a/api/internal/serialize.js +++ b/api/internal/serialize.js @@ -20,17 +20,22 @@ export default function serialize (value) { }) } -function map (object, callback) { +function map (object, callback, seen = new Set()) { + if (seen.has(object)) { + return object + } + + seen.add(object) if (Array.isArray(object)) { for (let i = 0; i < object.length; ++i) { - object[i] = map(object[i], callback) + object[i] = map(object[i], callback, seen) } } else if (object && typeof object === 'object') { object = callback(object) for (const key in object) { const descriptor = Object.getOwnPropertyDescriptor(object, key) if (descriptor && descriptor.writable) { - object[key] = map(object[key], callback) + object[key] = map(object[key], callback, seen) } } } diff --git a/api/internal/worker.js b/api/internal/worker.js index 65a8412a32..a6b9bfb24c 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -16,9 +16,6 @@ const ports = [] // level 1 Worker `EventTarget` 'message' listener let onmessage = null -// level 1 Worker `EventTarget` 'connect' listener -let onconnect = null - // 'close' state for a polyfilled `SharedWorker` let isClosed = false @@ -130,21 +127,6 @@ Object.defineProperties(WorkerGlobalScopePrototype, { } }, - onconnect: { - configurable: false, - get: () => onconnect, - set: (value) => { - if (typeof onconnect === 'function') { - workerGlobalScopeEventTarget.removeEventListener('connect', onconnect) - } - - if (value === null || typeof value === 'function') { - onconnect = value - workerGlobalScopeEventTarget.addEventListener('connect', onconnect) - } - } - }, - close: { configurable: false, enumerable: false, @@ -214,10 +196,6 @@ export async function onWorkerMessage (event) { } } - workerGlobalScopeEventTarget.dispatchEvent(new MessageEvent('connect', { - ...data?.__runtime_shared_worker - })) - event.stopImmediatePropagation() return false } @@ -226,7 +204,7 @@ export async function onWorkerMessage (event) { } export function addEventListener (eventName, callback, ...args) { - if (eventName === 'message' || eventName === 'connect') { + if (eventName === 'message') { return workerGlobalScopeEventTarget.addEventListener(eventName, callback, ...args) } else { return worker.addEventListener(eventName, callback, ...args) @@ -234,7 +212,7 @@ export function addEventListener (eventName, callback, ...args) { } export function removeEventListener (eventName, callback, ...args) { - if (eventName === 'message' || eventName === 'connect') { + if (eventName === 'message') { return workerGlobalScopeEventTarget.removeEventListener(eventName, callback, ...args) } else { return worker.removeEventListener(eventName, callback, ...args) @@ -242,7 +220,7 @@ export function removeEventListener (eventName, callback, ...args) { } export function dispatchEvent (event) { - if (event.type === 'message' || event.type === 'connect') { + if (event.type === 'message') { return workerGlobalScopeEventTarget.dispatchEvent(event) } From 89eea33e6c2ec260489b0d2a530b0e35e7f04c99 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:15:47 +0200 Subject: [PATCH 0902/1178] refactor(api/application): move 'client' to 'socket:application' --- api/application.js | 23 +++++++++++++---------- api/application/client.js | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/api/application.js b/api/application.js index 3d915dd402..286f513ed9 100644 --- a/api/application.js +++ b/api/application.js @@ -14,6 +14,7 @@ import ApplicationWindow, { formatURL } from './window.js' import { isValidPercentageValue } from './util.js' import ipc, { primordials } from './ipc.js' import menu, { setMenu } from './application/menu.js' +import client from './application/client.js' import os from './os.js' import * as exports from './application.js' @@ -31,7 +32,7 @@ function serializeConfig (config) { return entries.join('\n') } -export { menu } +export { client, menu } // get this from constant value in runtime export const MAX_WINDOWS = 32 @@ -317,17 +318,22 @@ export async function createWindow (opts) { * @returns {Promise<{ width: number, height: number }>} */ export async function getScreenSize () { - if (os.platform() === 'android') { + if (os.platform() === 'android' || os.platform() === 'ios') { return { width: globalThis.screen?.availWidth ?? 0, height: globalThis.screen?.availHeight ?? 0 } } - const { data, err } = await ipc.request('application.getScreenSize', { index: globalThis.__args.index }) - if (err) { - throw err + + const result = await ipc.request('application.getScreenSize', { + index: globalThis.__args.index + }) + + if (result.err) { + throw result.err } - return data + + return result.data } function throwOnInvalidIndex (index) { @@ -343,10 +349,7 @@ function throwOnInvalidIndex (index) { * @return {Promise<ApplicationWindowList>} */ export async function getWindows (indices, options = null) { - if ( - !globalThis.RUNTIME_APPLICATION_ALLOW_MULTI_WINDOWS && - (os.platform() === 'android') - ) { + if (globalThis.RUNTIME_APPLICATION_ALLOW_MULTI_WINDOWS === false) { return new ApplicationWindowList([ new ApplicationWindow({ index: 0, diff --git a/api/application/client.js b/api/application/client.js index 95c2764798..f2086e1607 100644 --- a/api/application/client.js +++ b/api/application/client.js @@ -1,3 +1,5 @@ +import location from '../location.js' + /** * @typedef {{ * id?: string | null, @@ -66,6 +68,27 @@ export class Client { ? new Client(this.#state.top) : null } + + /** + * A readonly `URL` of the current location of this client. + * @type {URL} + */ + get location () { + return new URL(globalThis.RUNTIME_WORKER_LOCATION ?? location.href) + } + + /** + * Converts this `Client` instance to JSON. + * @return {object} + */ + toJSON () { + return { + id: this.id, + frameType: this.frameType, + type: this.type, + location: this.location.toString() + } + } } // @ts-ignore From 206eb605da4a50233379757c89d4d7dc84adb360 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:16:08 +0200 Subject: [PATCH 0903/1178] feat(api/ipc.js): introduce special 'IPCMessagePort' and 'IPCMessageChannel' --- api/ipc.js | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) diff --git a/api/ipc.js b/api/ipc.js index acacbbb39e..156d2eb37f 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -54,6 +54,7 @@ import * as errors from './errors.js' import { Buffer } from './buffer.js' import { rand64 } from './crypto.js' import bookmarks from './fs/bookmarks.js' +import serialize from './internal/serialize.js' import { URL } from './url.js' let nextSeq = 1 @@ -1688,6 +1689,300 @@ if (typeof globalThis?.window !== 'undefined') { } } +export function inflateIPCMessageTransfers (object, types = new Map()) { + if (ArrayBuffer.isView(object)) { + return object + } else if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + object[i] = inflateIPCMessageTransfers(object[i], types) + } + } else if (object && typeof object === 'object') { + if ('__type__' in object && types.has(object.__type__)) { + const Type = types.get(object.__type__) + if (typeof Type === 'function') { + if (typeof Type.from === 'function') { + return Type.from(object) + } else { + return new Type(object) + } + } + } + + if (object.__type__ === 'IPCMessagePort' && object.id) { + return IPCMessagePort.create(object) + } else { + for (const key in object) { + const value = object[key] + object[key] = inflateIPCMessageTransfers(value, types) + } + } + } + + return object +} + +export function findIPCMessageTransfers (transfers, object) { + if (ArrayBuffer.isView(object)) { + add(object.buffer) + } else if (object instanceof ArrayBuffer) { + add(object) + } else if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + object[i] = findMessageTransfers(transfers, object[i]) + } + } else if (object && typeof object === 'object') { + if ( + object instanceof MessagePort || ( + typeof object.postMessage === 'function' && + Object.getPrototypeOf(object).constructor.name === 'MessagePort' + ) + ) { + const port = IPCMessagePort.create(object) + object.addEventListener('message', (event) => { + port.dispatchEvent(new MessageEvent('message', event)) + }) + port.onmessage = (event) => { + const transfers = new Set() + findIPCMessageTransfers(transfers, event.data) + object.postMessage(event.data, { + transfer: Array.from(transfers) + }) + } + add(port) + return port + } else { + for (const key in object) { + object[key] = findMessageTransfers(transfers, object[key]) + } + } + } + + return object + + function add (value) { + if ( + value && + !transfers.has(value) && + !(Symbol.for('socket.runtime.serialize') in value) + ) { + transfers.add(value) + } + } +} + +export const ports = new Map() + +export class IPCMessagePort extends MessagePort { + static from (options = null) { + return this.create(options) + } + + static create (options = null) { + const id = String(options?.id ?? rand64()) + const port = Object.create(this.prototype) + const channel = typeof options?.channel === 'string' + ? new BroadcastChannel(options.channel) + : options.channel ?? new BroadcastChannel(id) + + ports.set(port, Object.create(null, { + id: { writable: true, value: id }, + closed: { writable: true, value: false }, + started: { writable: true, value: false }, + channel: { writable: true, value: channel }, + onmessage: { writable: true, value: null }, + onmessageerror: { writable: true, value: null }, + eventTarget: { writable: true, value: new EventTarget() } + })) + + channel.addEventListener('message', (event) => { + if (ports.get(port)?.started) { + port.dispatchEvent(new MessageEvent('message', event)) + } + }) + + return port + } + + get id () { + return ports.get(this)?.id ?? null + } + + get started () { + return ports.get(this)?.started ?? false + } + + get closed () { + return ports.get(this)?.closed ?? false + } + + get onmessage () { + return ports.get(this)?.onmessage ?? null + } + + set onmessage (onmessage) { + const port = ports.get(this) + + if (!port) { + return + } + + if (typeof this.onmessage === 'function') { + this.removeEventListener('message', this.onmessage) + port.onmessage = null + } + + if (typeof onmessage === 'function') { + this.addEventListener('message', onmessage) + port.onmessage = onmessage + if (!port.started) { + this.start() + } + } + } + + get onmessageerror () { + return ports.get(this)?.onmessageerror ?? null + } + + set onmessageerror (onmessageerror) { + const port = ports.get(this) + + if (!port) { + return + } + + if (typeof this.onmessageerror === 'function') { + this.removeEventListener('messageerror', this.onmessageerror) + port.onmessageerror = null + } + + if (typeof onmessageerror === 'function') { + this.addEventListener('messageerror', onmessageerror) + port.onmessageerror = onmessageerror + } + } + + start () { + const port = ports.get(this) + if (port) { + port.started = true + } + } + + close () { + const port = ports.get(this) + if (port) { + port.closed = true + } + } + + postMessage (message, optionsOrTransferList) { + const port = ports.get(this) + const options = { transfer: [] } + + if (!port) { + return + } + + if (Array.isArray(optionsOrTransferList)) { + options.transfer.push(...optionsOrTransferList) + } else if (Array.isArray(optionsOrTransferList?.transfer)) { + options.transfer.push(...optionsOrTransferList.transfer) + } + + const transfers = new Set(options.transfer) + const handle = this[Symbol.for('socket.runtime.ipc.MessagePort.handlePostMessage')] + + const serializedMessage = serialize(findIPCMessageTransfers(transfers, message)) + options.transfer = Array.from(transfers) + + if (typeof handle === 'function') { + if (handle.call(this, serializedMessage, options) === false) { + return + } + } + + port.channel.postMessage(serializedMessage, options) + } + + addEventListener (...args) { + const eventTarget = ports.get(this)?.eventTarget + + if (eventTarget) { + return eventTarget.addEventListener(...args) + } + + return false + } + + removeEventListener (...args) { + const eventTarget = ports.get(this)?.eventTarget + + if (eventTarget) { + return eventTarget.removeEventListener(...args) + } + + return false + } + + dispatchEvent (event) { + const eventTarget = ports.get(this)?.eventTarget + + if (eventTarget) { + if (event.type === 'message') { + return eventTarget.dispatchEvent(new MessageEvent('message', { + ...event, + data: inflateIPCMessageTransfers(event.data) + })) + } else { + return eventTarget.dispatchEvent(event) + } + } + + return false + } + + [Symbol.for('socket.runtime.ipc.MessagePort.handlePostMessage')] (message, options = null) { + return true + } + + [Symbol.for('socket.runtime.serialize')] () { + return { + __type__: 'IPCMessagePort', + channel: ports.get(this)?.channel?.name ?? null, + id: this.id + } + } +} + +export class IPCMessageChannel extends MessageChannel { + #id = null + #channel = null + + port1 = null + port2 = null + + constructor (options = null) { + super() + this.#id = String(options?.id ?? rand64()) + this.#channel = options?.channel ?? new BroadcastChannel(this.#id) + + this.port1 = IPCMessagePort.create({ + channel: this.#channel, + ...options?.port1 + }) + + this.port2 = IPCMessagePort.create({ + channel: this.#channel, + ...options?.port2 + }) + } + + get id () { + return this.#id + } +} + // eslint-disable-next-line import * as exports from './ipc.js' export default exports From dfd41903cb86b79f97db3e9594e7a2fc87424297 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 6 Jul 2024 21:16:36 +0200 Subject: [PATCH 0904/1178] refactor(api): docs, types, use new 'SharedWorker' --- api/README.md | 73 ++++++++---- api/index.d.ts | 217 ++++++++++++++++++++++++++++------- api/vm.js | 54 +-------- api/window.js | 2 +- api/worker.js | 2 +- bin/build-runtime-library.sh | 1 + 6 files changed, 231 insertions(+), 118 deletions(-) diff --git a/api/README.md b/api/README.md index 796049fb3a..34749e752f 100644 --- a/api/README.md +++ b/api/README.md @@ -11,17 +11,17 @@ import { createWindow } from 'socket:application' ``` -## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/master/api/application.js#L37) +## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/master/api/application.js#L38) This is a `VariableDeclaration` named `MAX_WINDOWS` in `api/application.js`, it's exported but undocumented. -## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/master/api/application.js#L39) +## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/master/api/application.js#L40) This is a `ClassDeclaration` named `ApplicationWindowList` in `api/application.js`, it's exported but undocumented. -## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L164) +## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L165) Returns the current window index @@ -29,7 +29,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L199) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L200) Creates a new window and returns an instance of ApplicationWindow. @@ -67,15 +67,15 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L226) +### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L227) -### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L231) +### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L232) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L319) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L320) Returns the current screen size. @@ -83,7 +83,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L345) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L351) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -95,7 +95,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindowList> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L402) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L405) Returns the ApplicationWindow instance for the given index @@ -107,7 +107,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L412) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L415) Returns the ApplicationWindow instance for the current window. @@ -115,7 +115,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L421) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L424) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -127,7 +127,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L518) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L521) Set the native menu for the app. @@ -222,11 +222,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L525) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L528) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L534) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L537) Set the enabled state of the system menu. @@ -238,23 +238,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L542) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L545) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L548) +## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L551) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L554) +## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L557) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L559) +## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L562) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L565) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L568) @@ -267,7 +267,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L573) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L576) @@ -1683,17 +1683,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L268) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L269) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1020) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1021) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1181) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1182) Emit event to be dispatched on `window` object. @@ -1704,7 +1704,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1240) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1241) Sends an async IPC command request with parameters. @@ -1720,6 +1720,31 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1692) + +This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. + + +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1724) + +This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. + + +## [ports](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1773) + +This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. + + +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1775) + +This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. + + +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1958) + +This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. + + <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> diff --git a/api/index.d.ts b/api/index.d.ts index 00e058d42b..4e753edebb 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -580,6 +580,10 @@ declare module "socket:fs/bookmarks" { export default _default; } +declare module "socket:internal/serialize" { + export default function serialize(value: any): any; +} + declare module "socket:url/urlpattern/urlpattern" { export { me as URLPattern }; var me: { @@ -768,6 +772,8 @@ declare module "socket:ipc" { * @ignore */ export function createBinding(domain: string, ctx?: (Function | object) | undefined): ProxyConstructor; + export function inflateIPCMessageTransfers(object: any, types?: Map<any, any>): any; + export function findIPCMessageTransfers(transfers: any, object: any): any; /** * Represents an OK IPC status. * @ignore @@ -1051,6 +1057,29 @@ declare module "socket:ipc" { * @ignore */ export const primordials: any; + export const ports: Map<any, any>; + export class IPCMessagePort extends MessagePort { + static from(options?: any): any; + static create(options?: any): any; + get id(): any; + get started(): any; + get closed(): any; + set onmessage(onmessage: any); + get onmessage(): any; + set onmessageerror(onmessageerror: any); + get onmessageerror(): any; + postMessage(message: any, optionsOrTransferList: any): void; + addEventListener(...args: any[]): any; + removeEventListener(...args: any[]): any; + dispatchEvent(event: any): any; + } + export class IPCMessageChannel extends MessageChannel { + constructor(options?: any); + port1: any; + port2: any; + get id(): any; + #private; + } export default exports; import { Buffer } from "socket:buffer"; import { URL } from "socket:url/index"; @@ -6901,7 +6930,7 @@ declare module "socket:window/constants" { } -declare module "socket:window/client" { +declare module "socket:application/client" { /** * @typedef {{ * id?: string | null, @@ -6943,6 +6972,11 @@ declare module "socket:window/client" { * @type {Client|null} */ get top(): Client; + /** + * Converts this `Client` instance to JSON. + * @return {object} + */ + toJSON(): object; #private; } const _default: any; @@ -7511,7 +7545,7 @@ declare module "socket:window" { export const constants: typeof statuses; import ipc from "socket:ipc"; import * as statuses from "socket:window/constants"; - import client from "socket:window/client"; + import client from "socket:application/client"; import hotkey from "socket:window/hotkey"; export { client, hotkey }; } @@ -7714,7 +7748,6 @@ declare module "socket:application" { * @return {Promise<ipc.Result>} */ export function setSystemMenuItemEnabled(value: object): Promise<ipc.Result>; - export { menu }; export const MAX_WINDOWS: 32; export class ApplicationWindowList { static from(...args: any[]): exports.ApplicationWindowList; @@ -7765,9 +7798,11 @@ declare module "socket:application" { export default exports; import ApplicationWindow from "socket:window"; import ipc from "socket:ipc"; + import client from "socket:application/client"; import menu from "socket:application/menu"; import * as exports from "socket:application"; + export { client, menu }; } declare module "socket:test/fast-deep-equal" { @@ -7905,6 +7940,39 @@ declare module "socket:bootstrap" { import { EventEmitter } from "socket:events"; } +declare module "socket:shared-worker/index" { + export function init(sharedWorker: any, options: any): Promise<void>; + /** + * Gets the SharedWorker context window. + * This function will create it if it does not already exist. + * @return {Promise<import('./window.js').ApplicationWindow} + */ + export function getContextWindow(): Promise<any>; + export const SHARED_WORKER_WINDOW_INDEX: 46; + export const SHARED_WORKER_WINDOW_TITLE: "socket:shared-worker"; + export const SHARED_WORKER_WINDOW_PATH: string; + export const channel: BroadcastChannel; + export const workers: Map<any, any>; + export class SharedWorkerMessagePort extends ipc.IPCMessagePort { + } + export class SharedWorker extends EventTarget { + /** + * `SharedWorker` class constructor. + * @param {string} aURL + * @param {string|object=} [nameOrOptions] + */ + constructor(aURL: string, nameOrOptions?: (string | object) | undefined); + set onerror(onerror: any); + get onerror(): any; + get ready(): any; + get port(): any; + get id(): any; + #private; + } + export default SharedWorker; + import ipc from "socket:ipc"; +} + declare module "socket:internal/globals" { /** * Gets a runtime global value by name. @@ -7927,30 +7995,6 @@ declare module "socket:internal/globals" { const registry: any; } -declare module "socket:internal/shared-worker" { - export function getSharedWorkerImplementationForPlatform(): { - new (scriptURL: string | URL, options?: string | WorkerOptions): SharedWorker; - prototype: SharedWorker; - } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; - export class SharedHybridWorkerProxy extends EventTarget { - constructor(url: any, options: any); - onChannelMessage(event: any): void; - get id(): any; - get port(): any; - #private; - } - export class SharedHybridWorker extends EventTarget { - constructor(url: any, nameOrOptions: any); - get port(): any; - #private; - } - export const SharedWorker: { - new (scriptURL: string | URL, options?: string | WorkerOptions): SharedWorker; - prototype: SharedWorker; - } | typeof SharedHybridWorkerProxy | typeof SharedHybridWorker; - export default SharedWorker; -} - declare module "socket:console" { export function patchGlobalConsole(globalConsole: any, options?: {}): any; export const globalConsole: globalThis.Console; @@ -8396,6 +8440,7 @@ declare module "socket:vm" { filename?: string; context?: object; }; + import { SharedWorker } from "socket:shared-worker/index"; } declare module "socket:worker_threads/init" { @@ -10117,6 +10162,11 @@ declare module "socket:service-worker/env" { * @type {Proxy<object>} */ get context(): ProxyConstructor; + /** + * The environment type + * @type {string} + */ + get type(): string; /** * The current environment name. This value is also used as the * internal database name. @@ -10382,6 +10432,10 @@ declare module "socket:service-worker/events" { * @type {import('./clients.js').Client?} */ get source(): import("socket:service-worker/clients").Client; + /** + * @type {string?} + */ + get origin(): string; /** * @type {string} */ @@ -12888,7 +12942,7 @@ declare module "socket:network" { declare module "socket:service-worker" { /** - * A reference toe opened environment. This value is an instance of an + * A reference to the opened environment. This value is an instance of an * `Environment` if the scope is a ServiceWorker scope. * @type {Environment|null} */ @@ -13765,10 +13819,6 @@ declare module "socket:commonjs/builtins" { export default builtins; } -declare module "socket:internal/serialize" { - export default function serialize(value: any): any; -} - declare module "socket:commonjs/cache" { /** * @typedef {{ @@ -15878,6 +15928,18 @@ declare module "socket:notification" { import URL from "socket:url"; } +declare module "socket:shared-worker" { + /** + * A reference to the opened environment. This value is an instance of an + * `Environment` if the scope is a ServiceWorker scope. + * @type {Environment|null} + */ + export const env: Environment | null; + export default SharedWorker; + import { SharedWorker } from "socket:shared-worker/index"; + export { Environment, SharedWorker }; +} + declare module "socket:service-worker/instance" { export function createServiceWorker(currentState?: any, options?: any): any; export const SHARED_WORKER_URL: string; @@ -15902,7 +15964,7 @@ declare module "socket:service-worker/instance" { declare module "socket:worker" { export default Worker; - import { SharedWorker } from "socket:internal/shared-worker"; + import { SharedWorker } from "socket:shared-worker/index"; import { ServiceWorker } from "socket:service-worker/instance"; import { Worker } from "socket:worker_threads"; export { SharedWorker, ServiceWorker, Worker }; @@ -16599,11 +16661,11 @@ declare module "socket:internal/promise" { */ constructor(resolver: ResolverFunction); [resourceSymbol]: { - "__#15@#type": any; - "__#15@#destroyed": boolean; - "__#15@#asyncId": number; - "__#15@#triggerAsyncId": any; - "__#15@#requireManualDestroy": boolean; + "__#16@#type": any; + "__#16@#destroyed": boolean; + "__#16@#asyncId": number; + "__#16@#triggerAsyncId": any; + "__#16@#requireManualDestroy": boolean; readonly type: string; readonly destroyed: boolean; asyncId(): number; @@ -16903,7 +16965,7 @@ declare module "socket:service-worker/init" { export function onUnregister(event: any): Promise<void>; export function onSkipWaiting(event: any): Promise<void>; export function onActivate(event: any): Promise<void>; - export function onFetch(event: any): Promise<void>; + export function onFetch(event: any): Promise<any>; export function onNotificationShow(event: any, target: any): any; export function onNotificationClose(event: any, target: any): void; export function onGetNotifications(event: any, target: any): any; @@ -17267,7 +17329,82 @@ declare module "socket:service-worker/storage" { } declare module "socket:service-worker/worker" { - export {}; + export function onReady(): void; + export function onMessage(event: any): Promise<any>; + const _default: any; + export default _default; + export namespace SERVICE_WORKER_READY_TOKEN { + let __service_worker_ready: boolean; + } + export namespace module { + let exports: {}; + } + export const events: Set<any>; + export namespace stages { + let register: Deferred; + let install: Deferred; + let activate: Deferred; + } + import { Deferred } from "socket:async"; +} + +declare module "socket:shared-worker/debug" { + export function debug(...args: any[]): void; + export default debug; +} + +declare module "socket:shared-worker/global" { + export class SharedWorkerGlobalScope { + get isSharedWorkerScope(): boolean; + set onconnect(listener: any); + get onconnect(): any; + } + const _default: SharedWorkerGlobalScope; + export default _default; +} + +declare module "socket:shared-worker/init" { + export function onInstall(event: any): Promise<void>; + export function onUninstall(event: any): Promise<void>; + export function onConnect(event: any): Promise<void>; + export const workers: Map<any, any>; + export { channel }; + export class SharedWorkerInstance extends Worker { + constructor(filename: any, options: any); + get info(): any; + onMessage(event: any): Promise<void>; + #private; + } + export class SharedWorkerInfo { + constructor(data: any); + id: any; + port: any; + scriptURL: any; + url: any; + hash: any; + get pathname(): string; + } + const _default: any; + export default _default; + import { channel } from "socket:shared-worker/index"; +} + +declare module "socket:shared-worker/state" { + export const state: any; + export default state; +} + +declare module "socket:shared-worker/worker" { + export function onReady(): void; + export function onMessage(event: any): Promise<void>; + const _default: any; + export default _default; + export namespace SHARED_WORKER_READY_TOKEN { + let __shared_worker_ready: boolean; + } + export namespace module { + let exports: {}; + } } declare module "socket:test/harness" { diff --git a/api/vm.js b/api/vm.js index 4d8205436e..c2aba58038 100644 --- a/api/vm.js +++ b/api/vm.js @@ -27,7 +27,7 @@ /* eslint-disable no-new-func */ /* global ErrorEvent, EventTarget, MessagePort */ import { maybeMakeError } from './ipc.js' -import { SharedWorker } from './internal/shared-worker.js' +import { SharedWorker } from './shared-worker/index.js' import { isESMSource } from './util.js' import application from './application.js' import globals from './internal/globals.js' @@ -1203,61 +1203,11 @@ export async function getContextWindow () { return contextWindow } - const currentWindow = await application.getCurrentWindow() - - if (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) { - contextWindow = currentWindow - - if (!contextWindow.frame) { - const frameId = `__${os.platform()}-vm-frame__` - const existingFrame = globalThis.top.document.querySelector( - `iframe[id="${frameId}"]` - ) - - if (existingFrame) { - existingFrame.parentElement.removeChild(existingFrame) - } - - const frame = globalThis.top.document.createElement('iframe') - - frame.setAttribute('sandbox', 'allow-same-origin allow-scripts') - frame.src = VM_WINDOW_PATH - frame.id = frameId - - Object.assign(frame.style, { - display: 'none', - height: 0, - width: 0 - }) - - const target = ( - globalThis.top.document.head ?? - globalThis.top.document.body ?? - globalThis.top.document - ) - - target.prepend(frame) - contextWindow.frame = frame - contextWindow.ready = new Promise((resolve, reject) => { - frame.onload = resolve - frame.onerror = (event) => { - reject(new Error('Failed to load VM context window frame', { - // @ts-ignore - cause: event.error ?? event - })) - } - }) - } - - await contextWindow.ready - return contextWindow - } - const existingContextWindow = await application.getWindow(VM_WINDOW_INDEX, { max: false }) const pendingContextWindow = ( existingContextWindow ?? application.createWindow({ - canExit: true, + canExit: false, headless: !process.env.SOCKET_RUNTIME_VM_DEBUG, // @ts-ignore debug: Boolean(process.env.SOCKET_RUNTIME_VM_DEBUG), diff --git a/api/window.js b/api/window.js index b2a86c3ccf..d3c4183f0b 100644 --- a/api/window.js +++ b/api/window.js @@ -13,7 +13,7 @@ import { isValidPercentageValue } from './util.js' import * as statuses from './window/constants.js' import location from './location.js' import { URL } from './url.js' -import client from './window/client.js' +import client from './application/client.js' import hotkey from './window/hotkey.js' import menu from './application/menu.js' import ipc from './ipc.js' diff --git a/api/worker.js b/api/worker.js index 196ba9aa37..d4db7c958b 100644 --- a/api/worker.js +++ b/api/worker.js @@ -1,5 +1,5 @@ import { ServiceWorker } from './service-worker/instance.js' -import { SharedWorker } from './internal/shared-worker.js' +import { SharedWorker } from './shared-worker/index.js' import { Worker } from './worker_threads.js' export { SharedWorker, ServiceWorker, Worker } diff --git a/bin/build-runtime-library.sh b/bin/build-runtime-library.sh index 2a31559a4f..a06358efc1 100755 --- a/bin/build-runtime-library.sh +++ b/bin/build-runtime-library.sh @@ -101,6 +101,7 @@ declare sources=( $(find "$root"/src/ipc/*.cc) $(find "$root"/src/platform/*.cc) $(find "$root"/src/serviceworker/*.cc) + #$(find "$root"/src/sharedworker/*.cc) "$root/build/llama/common/common.cpp" "$root/build/llama/common/sampling.cpp" "$root/build/llama/common/json-schema-to-grammar.cpp" From 2ca557bb4908ccb07a948cd24c09faffe3302058 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:25:27 +0200 Subject: [PATCH 0905/1178] fix(window): fix bundle options on android --- src/window/window.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/window/window.kt b/src/window/window.kt index 8130b4531d..8e0e5c80f4 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -245,7 +245,7 @@ open class WindowFragment : Fragment(R.layout.web_view) { return WindowFragment().apply { arguments = bundleOf( "index" to options.index, - "shouldExitApplicationOnClose" to options.shouldExitApplicationOnClose + "shouldExitApplicationOnClose" to options.shouldExitApplicationOnClose, "headless" to options.headless ) } From dc59fe5dca663dffca1df9d93d19162c20600adb Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:25:56 +0200 Subject: [PATCH 0906/1178] refactor(ipc): inject common env vars, handle POST buffers on android --- src/ipc/preload.cc | 34 ++++++++++++++++++---------------- src/ipc/routes.cc | 20 +++++++++++++------- src/ipc/scheme_handlers.kt | 7 +++++-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 55dd7283ea..191029530c 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -59,20 +59,6 @@ namespace SSC::IPC { this->headers = options.headers; this->metadata = options.metadata; - /* - auto argv = options.argv; - #if SOCKET_RUNTIME_PLATFORM_WINDOWS - do { - // Escape backslashes in paths. - size_t lastPosition = 0; - while ((lastPosition = argv.find('\\', lastPosition)) != String::npos) { - argv.replace(lastPosition, 1, "\\\\"); - lastPosition += 2; - } - } while (0); - #endif - */ - if (options.features.useHTMLMarkup) { if (!this->metadata.contains("referrer")) { this->metadata["referrer"] = DEFAULT_REFERRER_POLICY; @@ -81,9 +67,25 @@ namespace SSC::IPC { if (!this->headers.has("referrer-policy")) { if (this->metadata["referrer"].size() > 0) { this->headers.set("referrer-policy", this->metadata["referrer"]); - } + } } } + + if (Env::has("SOCKET_RUNTIME_VM_DEBUG")) { + this->options.env["SOCKET_RUNTIME_VM_DEBUG"] = Env::get("SOCKET_RUNTIME_VM_DEBUG"); + } + + if (Env::has("SOCKET_RUNTIME_NPM_DEBUG")) { + this->options.env["SOCKET_RUNTIME_NPM_DEBUG"] = Env::get("SOCKET_RUNTIME_NPM_DEBUG"); + } + + if (Env::has("SOCKET_RUNTIME_SHARED_WORKER_DEBUG")) { + this->options.env["SOCKET_RUNTIME_SHARED_WORKER_DEBUG"] = Env::get("SOCKET_RUNTIME_SHARED_WORKER_DEBUG"); + } + + if (Env::has("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG")) { + this->options.env["SOCKET_RUNTIME_SERVICE_WORKER_DEBUG"] = Env::get("SOCKET_RUNTIME_SERVICE_WORKER_DEBUG"); + } } Preload& Preload::append (const String& source) { @@ -115,7 +117,7 @@ namespace SSC::IPC { for (const auto &entry : this->metadata) { if (entry.second.size() == 0) { continue; - } + } buffers.push_back(tmpl( R"HTML(<meta name="{{name}}" content="{{content}}">)HTML", diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 2dc3b2c909..8930c0e22e 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -3159,13 +3159,17 @@ static void mapIPCRoutes (Router *router) { options.headless = false; } - if (message.has("radius")) { - options.radius = std::stof(message.get("radius")); - } + try { + if (message.has("radius")) { + options.radius = std::stof(message.get("radius")); + } + } catch (...) {} - if (message.has("margin")) { - options.margin = std::stof(message.get("margin")); - } + try { + if (message.has("margin")) { + options.margin = std::stof(message.get("margin")); + } + } catch (...) {} options.width = message.get("width").size() ? window->getSizeInPixels(message.get("width"), screen.width) @@ -3222,7 +3226,9 @@ static void mapIPCRoutes (Router *router) { createdWindow->navigate(message.get("url")); } - createdWindow->show(); + if (!options.headless) { + createdWindow->show(); + } reply(Result::Data { message, createdWindow->json() }); }); diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index fae1ceb82d..002eef8657 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -23,7 +23,9 @@ open class SchemeHandlers (val bridge: Bridge) { if (seq != null && this.bridge.buffers.contains(seq)) { val buffer = this.bridge.buffers[seq] - this.bridge.buffers.remove(seq) + if (request.method == "POST" || request.method == "PUT" || request.method == "PATCH") { + this.bridge.buffers.remove(seq) + } buffer } else { null @@ -113,7 +115,7 @@ open class SchemeHandlers (val bridge: Bridge) { if (name.lowercase() == "content-type") { this.mimeType = value this.response.setMimeType(value) - } else { + } else if (name.lowercase() != "content-length") { this.headers.remove(name) if (this.response.responseHeaders != null) { @@ -150,6 +152,7 @@ open class SchemeHandlers (val bridge: Bridge) { for (bytes in buffers) { stream.write(bytes) } + stream.flush() stream.close() } From 0dbf9383fa0eb977ab750fe6dfd6fe82b92eaa13 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:26:09 +0200 Subject: [PATCH 0907/1178] refactor(serviceworker): clean up --- src/serviceworker/container.cc | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/serviceworker/container.cc b/src/serviceworker/container.cc index b599358b24..7efd6e9271 100644 --- a/src/serviceworker/container.cc +++ b/src/serviceworker/container.cc @@ -408,7 +408,6 @@ namespace SSC { }; // XXX(@jwerle): we handle this in the android runtime - #if !SOCKET_RUNTIME_PLATFORM_ANDROID const auto extname = Path(request.pathname).extension().string(); auto html = (message.buffer.bytes != nullptr && message.buffer.size > 0) ? String(response.body.bytes.get(), response.body.size) @@ -449,18 +448,15 @@ namespace SSC { memcpy(response.body.bytes.get(), html.c_str(), html.size()); } - #endif callback(response); reply(IPC::Result { message.seq, message }); - do { - Lock lock(this->mutex); - this->fetchCallbacks.erase(id); - if (this->fetchRequests.contains(id)) { - this->fetchRequests.erase(id); - } - } while (0); + Lock lock(this->mutex); + this->fetchCallbacks.erase(id); + if (this->fetchRequests.contains(id)) { + this->fetchRequests.erase(id); + } }); } From bd629dc13e7285445d38dce26bcf23b0959bb939 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:26:28 +0200 Subject: [PATCH 0908/1178] refactor(api/diagnostics/channels.js): call 'gc.ref' on channel --- api/diagnostics/channels.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/diagnostics/channels.js b/api/diagnostics/channels.js index 1db13e5949..24cfd049bc 100644 --- a/api/diagnostics/channels.js +++ b/api/diagnostics/channels.js @@ -1,5 +1,6 @@ import { toString, IllegalConstructor } from '../util.js' import process from '../process.js' +import gc from '../gc.js' /** * Used to preallocate a minimum sized array of subscribers for @@ -45,6 +46,7 @@ export class Channel { constructor (name) { this.name = name this.group = null + gc.ref(this) } /** From a0bf375aef26eacec336fd23325943c0bd55a4f7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:27:00 +0200 Subject: [PATCH 0909/1178] refactor(api/gc.js): simplify deps --- api/gc.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/api/gc.js b/api/gc.js index 4234de6f95..12829821ab 100644 --- a/api/gc.js +++ b/api/gc.js @@ -1,6 +1,4 @@ import { FinalizationRegistryCallbackError } from './errors.js' -import diagnostics from './diagnostics.js' -import { noop } from './util.js' import symbols from './internal/symbols.js' if (typeof FinalizationRegistry === 'undefined') { @@ -10,13 +8,6 @@ if (typeof FinalizationRegistry === 'undefined') { ) } -const dc = diagnostics.channels.group('gc', [ - 'finalizer.start', - 'finalizer.end', - 'unref', - 'ref' -]) - export const finalizers = new WeakMap() export const kFinalizer = Symbol.for('socket.runtime.gc.finalizer') export const finalizer = kFinalizer @@ -63,8 +54,6 @@ export default gc async function finalizationRegistryCallback (finalizer) { if (!finalizer) return // potentially called when finalizer is already gone - dc.channel('finalizer.start').publish({ finalizer }) - if (typeof finalizer.handle === 'function') { try { await finalizer.handle(...finalizer.args) @@ -94,7 +83,6 @@ async function finalizationRegistryCallback (finalizer) { pool.delete(weakRef) } - dc.channel('finalizer.end').publish({ finalizer }) finalizer = undefined } @@ -118,7 +106,7 @@ export class Finalizer { let { handle, args } = handler if (typeof handle !== 'function') { - handle = noop + handle = () => {} } if (!Array.isArray(args)) { @@ -163,8 +151,6 @@ export async function ref (object, ...args) { pool.add(weakRef) registry.register(object, finalizer, object) - - dc.channel('ref').publish({ object, finalizer: weakRef }) } return finalizers.has(object) @@ -195,7 +181,6 @@ export function unref (object) { finalizers.delete(object) registry.unregister(object) - dc.channel('unref').publish({ object, finalizer: weakRef }) return true } From 3bffca50f9fcebacd332e081b337ddcdd6dcb492 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:27:29 +0200 Subject: [PATCH 0910/1178] fix(api/internal): fix missing 'Symbol.serialize' and broken XHR on android --- api/internal/init.js | 16 +++++++++++++--- api/internal/primitives.js | 7 +++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 4d74adaecc..921dd0d698 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -567,7 +567,7 @@ if (globalThis.Worker === GlobalWorker) { if (typeof globalThis.XMLHttpRequest === 'function') { const { open, send, setRequestHeader } = globalThis.XMLHttpRequest.prototype const additionalHeaders = {} - const headerFilters = [] + const headerFilters = ['user-agent'] const isAsync = Symbol('isAsync') let queue = null @@ -590,12 +590,22 @@ if (typeof globalThis.XMLHttpRequest === 'function') { globalThis.XMLHttpRequest.prototype.setRequestHeader = function (name, value) { if (testHeaderFilters(name)) { - return setRequestHeader.call(this, name, value) + try { + setRequestHeader.call(this, name, value) + } catch {} } function testHeaderFilters (header) { + if (header.toLowerCase().startsWith('sec-')) { + return false + } + for (const filter of headerFilters) { - if (filter.test(header)) { + if (typeof filter === 'string') { + if (filter === name.toLowerCase()) { + return false + } + } else if (filter.test(header)) { return false } } diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 44d14257c5..646fb496e8 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -201,8 +201,11 @@ export function init () { } try { - Symbol.dispose = symbols.dispose - Symbol.serialize = symbols.serialize + globalThis.Symbol.dispose = symbols.dispose + } catch {} + + try { + globalThis.Symbol.serialize = symbols.serialize } catch {} if ( From a9857cebbe8a96230e779b2e7fdb5ff6f8e891cc Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:27:51 +0200 Subject: [PATCH 0911/1178] refactor(api/ipc.js): clean up ports bookkeeping --- api/ipc.js | 57 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 156d2eb37f..5df6306d02 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -56,6 +56,7 @@ import { rand64 } from './crypto.js' import bookmarks from './fs/bookmarks.js' import serialize from './internal/serialize.js' import { URL } from './url.js' +import gc from './gc.js' let nextSeq = 1 const cache = {} @@ -1784,7 +1785,8 @@ export class IPCMessagePort extends MessagePort { ? new BroadcastChannel(options.channel) : options.channel ?? new BroadcastChannel(id) - ports.set(port, Object.create(null, { + port[Symbol.for('socket.runtime.IPCMessagePort.id')] = id + ports.set(id, Object.create(null, { id: { writable: true, value: id }, closed: { writable: true, value: false }, started: { writable: true, value: false }, @@ -1795,32 +1797,41 @@ export class IPCMessagePort extends MessagePort { })) channel.addEventListener('message', (event) => { - if (ports.get(port)?.started) { + if (ports.get(id)?.started) { port.dispatchEvent(new MessageEvent('message', event)) } }) + gc.ref(port) return port } get id () { - return ports.get(this)?.id ?? null + return this[Symbol.for('socket.runtime.IPCMessagePort.id')] ?? null } get started () { - return ports.get(this)?.started ?? false + if (!ports.has(this.id)) { + return false + } + + return ports.get(this.id)?.started ?? false } get closed () { - return ports.get(this)?.closed ?? false + if (!ports.has(this.id)) { + return true + } + + return ports.get(this.id)?.closed ?? false } get onmessage () { - return ports.get(this)?.onmessage ?? null + return ports.get(this.id)?.onmessage ?? null } set onmessage (onmessage) { - const port = ports.get(this) + const port = ports.get(this.id) if (!port) { return @@ -1841,11 +1852,11 @@ export class IPCMessagePort extends MessagePort { } get onmessageerror () { - return ports.get(this)?.onmessageerror ?? null + return ports.get(this.id)?.onmessageerror ?? null } set onmessageerror (onmessageerror) { - const port = ports.get(this) + const port = ports.get(this.id) if (!port) { return @@ -1863,21 +1874,21 @@ export class IPCMessagePort extends MessagePort { } start () { - const port = ports.get(this) + const port = ports.get(this.id) if (port) { port.started = true } } close () { - const port = ports.get(this) + const port = ports.get(this.id) if (port) { port.closed = true } } postMessage (message, optionsOrTransferList) { - const port = ports.get(this) + const port = ports.get(this.id) const options = { transfer: [] } if (!port) { @@ -1906,7 +1917,7 @@ export class IPCMessagePort extends MessagePort { } addEventListener (...args) { - const eventTarget = ports.get(this)?.eventTarget + const eventTarget = ports.get(this.id)?.eventTarget if (eventTarget) { return eventTarget.addEventListener(...args) @@ -1916,7 +1927,7 @@ export class IPCMessagePort extends MessagePort { } removeEventListener (...args) { - const eventTarget = ports.get(this)?.eventTarget + const eventTarget = ports.get(this.id)?.eventTarget if (eventTarget) { return eventTarget.removeEventListener(...args) @@ -1926,7 +1937,7 @@ export class IPCMessagePort extends MessagePort { } dispatchEvent (event) { - const eventTarget = ports.get(this)?.eventTarget + const eventTarget = ports.get(this.id)?.eventTarget if (eventTarget) { if (event.type === 'message') { @@ -1942,17 +1953,29 @@ export class IPCMessagePort extends MessagePort { return false } - [Symbol.for('socket.runtime.ipc.MessagePort.handlePostMessage')] (message, options = null) { + [Symbol.for('socket.runtime.ipc.MessagePort.handlePostMessage')] ( + message, + options = null + ) { return true } [Symbol.for('socket.runtime.serialize')] () { return { __type__: 'IPCMessagePort', - channel: ports.get(this)?.channel?.name ?? null, + channel: ports.get(this.id)?.channel?.name ?? null, id: this.id } } + + [Symbol.for('socket.runtime.gc.finalizer')] () { + return { + args: [this.id], + handle (id) { + ports.delete(id) + } + } + } } export class IPCMessageChannel extends MessageChannel { From 7c4d0f2757ae7ca72dbe8479d7726862ec4c5310 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:28:16 +0200 Subject: [PATCH 0912/1178] refactor(api/service-worker/events.js): remove duped headers --- api/service-worker/events.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/service-worker/events.js b/api/service-worker/events.js index 3378b02d2d..b750e04e53 100644 --- a/api/service-worker/events.js +++ b/api/service-worker/events.js @@ -307,11 +307,6 @@ export class FetchEvent extends ExtendableEvent { .concat(Array.from(FetchEvent.defaultHeaders.entries())) .map((entry) => entry.join(':')) .concat('Runtime-Response-Source:serviceworker') - .concat('Access-Control-Allow-Credentials:true') - .concat('Access-Control-Allow-Origin:*') - .concat('Access-Control-Allow-Methods:*') - .concat('Access-Control-Allow-Headers:*') - .concat(`Content-Length:${arrayBuffer.byteLength}`) .join('\n') const params = { From ad3307853c10531410c597199c7a04d69c3b44a5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:28:39 +0200 Subject: [PATCH 0913/1178] refactor(api/shared-worker): handle race for context window, then hide --- api/shared-worker/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/api/shared-worker/index.js b/api/shared-worker/index.js index c758bd7281..cfeea2936b 100644 --- a/api/shared-worker/index.js +++ b/api/shared-worker/index.js @@ -142,11 +142,14 @@ export async function getContextWindow () { config: { webview_watch_reload: false } - }) + }).catch(() => application.getWindow(SHARED_WORKER_WINDOW_INDEX, { + max: false + })) ) - const promises = [] - promises.push(Promise.resolve(pendingContextWindow)) + const promises = [ + Promise.resolve(pendingContextWindow) + ] if (!existingContextWindow) { promises.push(new Promise((resolve) => { @@ -169,6 +172,8 @@ export async function getContextWindow () { contextWindow = await pendingContextWindow contextWindow.ready = ready + await contextWindow.hide() + return contextWindow } From 0f6b7ae09b58babc0324011143b6b0331b9bb572 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 16:29:10 +0200 Subject: [PATCH 0914/1178] chore(api): generate types + docs --- api/README.md | 18 +- api/index.d.ts | 3498 ++++++++++++++++++++++++------------------------ 2 files changed, 1761 insertions(+), 1755 deletions(-) diff --git a/api/README.md b/api/README.md index 34749e752f..5387f954d8 100644 --- a/api/README.md +++ b/api/README.md @@ -1683,17 +1683,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L269) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L270) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1021) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1022) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1182) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1183) Emit event to be dispatched on `window` object. @@ -1704,7 +1704,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1241) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1242) Sends an async IPC command request with parameters. @@ -1720,27 +1720,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1692) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1693) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1724) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1725) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1773) +## [ports](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1774) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1775) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1776) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1958) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1981) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index 4e753edebb..eb64e9f8e5 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -673,6 +673,92 @@ declare module "socket:url" { import URL from "socket:url/index"; } +declare module "socket:internal/symbols" { + export const dispose: any; + export const serialize: any; + namespace _default { + export { dispose }; + export { serialize }; + } + export default _default; +} + +declare module "socket:gc" { + /** + * Track `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when + * environment garbage collects object. + * @param {object} object + * @return {boolean} + */ + export function ref(object: object, ...args: any[]): boolean; + /** + * Stop tracking `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when + * environment garbage collects object. + * @param {object} object + * @return {boolean} + */ + export function unref(object: object): boolean; + /** + * An alias for `unref()` + * @param {object} object} + * @return {boolean} + */ + export function retain(object: object): boolean; + /** + * Call finalize on `object` for `gc.finalizer` implementation. + * @param {object} object] + * @return {Promise<boolean>} + */ + export function finalize(object: object, ...args: any[]): Promise<boolean>; + /** + * Calls all pending finalization handlers forcefully. This function + * may have unintended consequences as objects be considered finalized + * and still strongly held (retained) somewhere. + */ + export function release(): Promise<void>; + export const finalizers: WeakMap<object, any>; + export const kFinalizer: unique symbol; + export const finalizer: symbol; + /** + * @type {Set<WeakRef>} + */ + export const pool: Set<WeakRef<any>>; + /** + * Static registry for objects to clean up underlying resources when they + * are gc'd by the environment. There is no guarantee that the `finalizer()` + * is called at any time. + */ + export const registry: FinalizationRegistry<Finalizer>; + /** + * Default exports which also acts a retained value to persist bound + * `Finalizer#handle()` functions from being gc'd before the + * `FinalizationRegistry` callback is called because `heldValue` must be + * strongly held (retained) in order for the callback to be called. + */ + export const gc: any; + export default gc; + /** + * A container for strongly (retain) referenced finalizer function + * with arguments weakly referenced to an object that will be + * garbage collected. + */ + export class Finalizer { + /** + * Creates a `Finalizer` from input. + */ + static from(handler: any): Finalizer; + /** + * `Finalizer` class constructor. + * @private + * @param {array} args + * @param {function} handle + */ + private constructor(); + args: any[]; + handle: any; + } +} + declare module "socket:ipc" { export function maybeMakeError(error: any, caller: any): any; /** @@ -2175,1838 +2261,773 @@ declare module "socket:async/wrap" { export default wrap; } -declare module "socket:diagnostics/channels" { - /** - * Normalizes a channel name to lower case replacing white space, - * hyphens (-), underscores (_), with dots (.). - * @ignore - */ - export function normalizeName(group: any, name: any): string; +declare module "socket:internal/async/hooks" { + export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + export function getNextAsyncResourceId(): number; + export function executionAsyncResource(): any; + export function executionAsyncId(): any; + export function triggerAsyncId(): any; + export function getDefaultExecutionAsyncId(): any; + export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; + export function getTopLevelAsyncResourceName(): any; /** - * Used to preallocate a minimum sized array of subscribers for - * a channel. - * @ignore + * The default top level async resource ID + * @type {number} */ - export const MIN_CHANNEL_SUBSCRIBER_SIZE: 64; + export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; + export namespace state { + let defaultExecutionAsyncId: number; + } + export namespace hooks { + let init: any[]; + let before: any[]; + let after: any[]; + let destroy: any[]; + let promiseResolve: any[]; + } /** - * A general interface for diagnostic channels that can be subscribed to. + * A base class for the `AsyncResource` class or other higher level async + * resource classes. */ - export class Channel { - constructor(name: any); - name: any; - group: any; - /** - * Computed subscribers for all channels in this group. - * @type {Array<function>} - */ - get subscribers(): Function[]; - /** - * Accessor for determining if channel has subscribers. This - * is always `false` for `Channel instances and `true` for `ActiveChannel` - * instances. - */ - get hasSubscribers(): boolean; + export class CoreAsyncResource { /** - * Computed number of subscribers for this channel. + * `CoreAsyncResource` class constructor. + * @param {string} type + * @param {object|number=} [options] */ - get length(): number; + constructor(type: string, options?: (object | number) | undefined); /** - * Resets channel state. - * @param {(boolean)} [shouldOrphan = false] + * The `CoreAsyncResource` type. + * @type {string} */ - reset(shouldOrphan?: (boolean)): void; - channel(name: any): Channel; + get type(): string; /** - * Adds an `onMessage` subscription callback to the channel. - * @return {boolean} + * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This + * value is only set to `true` if `emitDestroy()` was called, likely from + * destroying the resource manually. + * @type {boolean} */ - subscribe(_: any, onMessage: any): boolean; + get destroyed(): boolean; /** - * Removes an `onMessage` subscription callback from the channel. - * @param {function} onMessage - * @return {boolean} + * The unique async resource ID. + * @return {number} */ - unsubscribe(_: any, onMessage: Function): boolean; + asyncId(): number; /** - * A no-op for `Channel` instances. This function always returns `false`. - * @param {string|object} name - * @param {object=} [message] - * @return Promise<boolean> + * The trigger async resource ID. + * @return {number} */ - publish(name: string | object, message?: object | undefined): Promise<boolean>; + triggerAsyncId(): number; /** - * Returns a string representation of the `ChannelRegistry`. - * @ignore + * Manually emits destroy hook for the resource. + * @return {CoreAsyncResource} */ - toString(): any; + emitDestroy(): CoreAsyncResource; /** - * Iterator interface - * @ignore + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @return {function} */ - get [Symbol.iterator](): any[]; + bind(fn: Function, thisArg?: object | undefined): Function; /** - * The `Channel` string tag. - * @ignore + * Runs function `fn` in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @param {...any} [args] + * @return {any} */ - [Symbol.toStringTag](): string; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; #private; } + export class TopLevelAsyncResource extends CoreAsyncResource { + } + export const asyncContextVariable: Variable<any>; + export const topLevelAsyncResource: TopLevelAsyncResource; + export default hooks; + import { Variable } from "socket:async/context"; +} + +declare module "socket:async/resource" { /** - * An `ActiveChannel` is a prototype implementation for a `Channel` - * that provides an interface what is considered an "active" channel. The - * `hasSubscribers` accessor always returns `true` for this class. + * @typedef {{ + * triggerAsyncId?: number, + * requireManualDestroy?: boolean + * }} AsyncResourceOptions */ - export class ActiveChannel extends Channel { - unsubscribe(onMessage: any): boolean; - /** - * @param {object|any} message - * @return Promise<boolean> - */ - publish(message: object | any): Promise<boolean>; - } /** - * A container for a grouping of channels that are named and owned - * by this group. A `ChannelGroup` can also be a regular channel. + * A container that should be extended that represents a resource with + * an asynchronous execution context. */ - export class ChannelGroup extends Channel { - /** - * @param {Array<Channel>} channels - * @param {string} name - */ - constructor(name: string, channels: Array<Channel>); - channels: Channel[]; + export class AsyncResource extends CoreAsyncResource { /** - * Subscribe to a channel or selection of channels in this group. - * @param {string} name - * @return {boolean} + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of an anonymous `AsyncResource`. + * @param {function} fn + * @param {object|string=} [type] + * @param {object=} [thisArg] + * @return {function} */ - subscribe(name: string, onMessage: any): boolean; + static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; /** - * Unsubscribe from a channel or selection of channels in this group. - * @param {string} name - * @return {boolean} + * `AsyncResource` class constructor. + * @param {string} type + * @param {AsyncResourceOptions|number=} [options] */ - unsubscribe(name: string, onMessage: any): boolean; - /** - * Gets or creates a channel for this group. - * @param {string} name - * @return {Channel} - */ - channel(name: string): Channel; + constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); + } + export default AsyncResource; + export type AsyncResourceOptions = { + triggerAsyncId?: number; + requireManualDestroy?: boolean; + }; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + import { CoreAsyncResource } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; +} + +declare module "socket:async/hooks" { + /** + * Factory for creating a `AsyncHook` instance. + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] + * @return {AsyncHook} + */ + export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; + /** + * A container for `AsyncHooks` callbacks. + * @ignore + */ + export class AsyncHookCallbacks { /** - * Select a test of channels from this group. - * The following syntax is supported: - * - One Channel: `group.channel` - * - All Channels: `*` - * - Many Channel: `group.*` - * - Collections: `['group.a', 'group.b', 'group.c'] or `group.a,group.b,group.c` - * @param {string|Array<string>} keys - * @param {(boolean)} [hasSubscribers = false] - Enforce subscribers in selection - * @return {Array<{name: string, channel: Channel}>} + * `AsyncHookCallbacks` class constructor. + * @ignore + * @param {AsyncHookCallbackOptions} [options] */ - select(keys: string | Array<string>, hasSubscribers?: (boolean)): Array<{ - name: string; - channel: Channel; - }>; + constructor(options?: AsyncHookCallbackOptions); + init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + before(asyncId: any): void; + after(asyncId: any): void; + destroy(asyncId: any): void; + promiseResolve(asyncId: any): void; } /** - * An object mapping of named channels to `WeakRef<Channel>` instances. + * A container for registering various callbacks for async resource hooks. */ - export const registry: { + export class AsyncHook { /** - * Subscribes callback `onMessage` to channel of `name`. - * @param {string} name - * @param {function} onMessage - * @return {boolean} + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] */ - subscribe(name: string, onMessage: Function): boolean; + constructor(callbacks?: any); /** - * Unsubscribes callback `onMessage` from channel of `name`. - * @param {string} name - * @param {function} onMessage - * @return {boolean} + * @type {boolean} */ - unsubscribe(name: string, onMessage: Function): boolean; + get enabled(): boolean; /** - * Predicate to determine if a named channel has subscribers. - * @param {string} name + * Enable the async hook. + * @return {AsyncHook} */ - hasSubscribers(name: string): boolean; + enable(): AsyncHook; /** - * Get or set a channel by `name`. - * @param {string} name - * @return {Channel} + * Disables the async hook + * @return {AsyncHook} */ - channel(name: string): Channel; + disable(): AsyncHook; + #private; + } + export default createHook; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; +} + +declare module "socket:async/storage" { + /** + * A container for storing values that remain present during + * asynchronous operations. + */ + export class AsyncLocalStorage { /** - * Creates a `ChannelGroup` for a set of channels - * @param {string} name - * @param {Array<string>} [channels] - * @return {ChannelGroup} + * Binds function `fn` to run in the execution context of an + * anonymous `AsyncResource`. + * @param {function} fn + * @return {function} */ - group(name: string, channels?: Array<string>): ChannelGroup; + static bind(fn: Function): Function; /** - * Get a channel by name. The name is normalized. - * @param {string} name - * @return {Channel?} + * Captures the current async context and returns a function that runs + * a function in that execution context. + * @return {function} */ - get(name: string): Channel | null; + static snapshot(): Function; /** - * Checks if a channel is known by name. The name is normalized. - * @param {string} name - * @return {boolean} + * @type {boolean} */ - has(name: string): boolean; + get enabled(): boolean; /** - * Set a channel by name. The name is normalized. - * @param {string} name - * @param {Channel} channel - * @return {Channel?} + * Disables the `AsyncLocalStorage` instance. When disabled, + * `getStore()` will always return `undefined`. */ - set(name: string, channel: Channel): Channel | null; + disable(): void; /** - * Removes a channel by `name` - * @return {boolean} + * Enables the `AsyncLocalStorage` instance. */ - remove(name: any): boolean; + enable(): void; /** - * Returns a string representation of the `ChannelRegistry`. - * @ignore + * Enables and sets the `AsyncLocalStorage` instance default store value. + * @param {any} store */ - toString(): any; + enterWith(store: any): void; /** - * Returns a JSON representation of the `ChannelRegistry`. - * @return {object} + * Runs function `fn` in the current asynchronous execution context with + * a given `store` value and arguments given to `fn`. + * @param {any} store + * @param {function} fn + * @param {...any} args + * @return {any} */ - toJSON(): object; + run(store: any, fn: Function, ...args: any[]): any; + exit(fn: any, ...args: any[]): any; /** - * The `ChannelRegistry` string tag. - * @ignore + * If the `AsyncLocalStorage` instance is enabled, it returns the current + * store value for this asynchronous execution context. + * @return {any|undefined} */ - [Symbol.toStringTag](): string; - }; - export default registry; -} - -declare module "socket:diagnostics/metric" { - export class Metric { - init(): void; - update(value: any): void; - destroy(): void; - toJSON(): {}; - toString(): string; - [Symbol.iterator](): any; - [Symbol.toStringTag](): string; - } - export default Metric; -} - -declare module "socket:diagnostics/window" { - export class RequestAnimationFrameMetric extends Metric { - constructor(options: any); - originalRequestAnimationFrame: typeof requestAnimationFrame; - requestAnimationFrame(callback: any): any; - sampleSize: any; - sampleTick: number; - channel: import("socket:diagnostics/channels").Channel; - value: { - rate: number; - samples: number; - }; - now: number; - samples: Uint8Array; - toJSON(): { - sampleSize: any; - sampleTick: number; - samples: number[]; - rate: number; - now: number; - }; - } - export class FetchMetric extends Metric { - constructor(options: any); - originalFetch: typeof fetch; - channel: import("socket:diagnostics/channels").Channel; - fetch(resource: any, options: any, extra: any): Promise<any>; - } - export class XMLHttpRequestMetric extends Metric { - constructor(options: any); - channel: import("socket:diagnostics/channels").Channel; - patched: { - open: { - (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; - }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; - }; - } - export class WorkerMetric extends Metric { - constructor(options: any); - GlobalWorker: { - new (scriptURL: string | URL, options?: WorkerOptions): Worker; - prototype: Worker; - } | { - new (): {}; - }; - channel: import("socket:diagnostics/channels").Channel; - Worker: { - new (url: any, options: any, ...args: any[]): {}; - }; - } - export const metrics: { - requestAnimationFrame: RequestAnimationFrameMetric; - XMLHttpRequest: XMLHttpRequestMetric; - Worker: WorkerMetric; - fetch: FetchMetric; - channel: import("socket:diagnostics/channels").ChannelGroup; - subscribe(...args: any[]): boolean; - unsubscribe(...args: any[]): boolean; - start(which: any): void; - stop(which: any): void; - }; - namespace _default { - export { metrics }; + getStore(): any | undefined; + #private; } - export default _default; - import { Metric } from "socket:diagnostics/metric"; + export default AsyncLocalStorage; } -declare module "socket:diagnostics/runtime" { - /** - * Queries runtime diagnostics. - * @return {Promise<QueryDiagnostic>} - */ - export function query(): Promise<QueryDiagnostic>; +declare module "socket:async/deferred" { /** - * A base container class for diagnostic information. + * Dispatched when a `Deferred` internal promise is resolved. */ - export class Diagnostic { + export class DeferredResolveEvent extends Event { /** - * A container for handles related to the diagnostics - */ - static Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; + * `DeferredResolveEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {any=} [result] + */ + constructor(type?: string | undefined, result?: any | undefined); /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} + * The `Deferred` promise result value. + * @type {any?} */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; + result: any | null; } /** - * A container for libuv diagnostics + * Dispatched when a `Deferred` internal promise is rejected. */ - export class UVDiagnostic extends Diagnostic { + export class DeferredRejectEvent { /** - * A container for libuv metrics. + * `DeferredRejectEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {Error=} [error] */ - static Metrics: { - new (): { - /** - * The number of event loop iterations. - * @type {number} - */ - loopCount: number; - /** - * Number of events that have been processed by the event handler. - * @type {number} - */ - events: number; - /** - * Number of events that were waiting to be processed when the - * event provider was called. - * @type {number} - */ - eventsWaiting: number; - }; - }; + constructor(type?: string | undefined, error?: Error | undefined); + } + /** + * A utility class for creating deferred promises. + */ + export class Deferred extends EventTarget { /** - * Known libuv metrics for this diagnostic. - * @type {UVDiagnostic.Metrics} + * `Deferred` class constructor. + * @param {Deferred|Promise?} [promise] */ - metrics: { - new (): { - /** - * The number of event loop iterations. - * @type {number} - */ - loopCount: number; - /** - * Number of events that have been processed by the event handler. - * @type {number} - */ - events: number; - /** - * Number of events that were waiting to be processed when the - * event provider was called. - * @type {number} - */ - eventsWaiting: number; - }; - }; + constructor(promise?: Deferred | (Promise<any> | null)); /** - * The current idle time of the libuv loop - * @type {number} + * Function to resolve the associated promise. + * @type {function} */ - idleTime: number; + resolve: Function; /** - * The number of active requests in the libuv loop - * @type {number} + * Function to reject the associated promise. + * @type {function} */ - activeRequests: number; + reject: Function; + /** + * Attaches a fulfillment callback and a rejection callback to the promise, + * and returns a new promise resolving to the return value of the called + * callback. + * @param {function(any)=} [resolve] + * @param {function(Error)=} [reject] + */ + then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise<any>; + /** + * Attaches a rejection callback to the promise, and returns a new promise + * resolving to the return value of the callback if it is called, or to its + * original fulfillment value if the promise is instead fulfilled. + * @param {function(Error)=} [callback] + */ + catch(callback?: ((arg0: Error) => any) | undefined): Promise<any>; + /** + * Attaches a callback for when the promise is settled (fulfilled or rejected). + * @param {function(any?)} [callback] + */ + finally(callback?: (arg0: any | null) => any): Promise<any>; + /** + * The promise associated with this Deferred instance. + * @type {Promise<any>} + */ + get promise(): Promise<any>; + /** + * A string representation of this Deferred instance. + * @type {string} + * @ignore + */ + get [Symbol.toStringTag](): string; + #private; } + export default Deferred; +} + +declare module "socket:async" { + export default exports; + import AsyncLocalStorage from "socket:async/storage"; + import AsyncResource from "socket:async/resource"; + import AsyncContext from "socket:async/context"; + import Deferred from "socket:async/deferred"; + import { executionAsyncResource } from "socket:async/hooks"; + import { executionAsyncId } from "socket:async/hooks"; + import { triggerAsyncId } from "socket:async/hooks"; + import { createHook } from "socket:async/hooks"; + import { AsyncHook } from "socket:async/hooks"; + import * as exports from "socket:async"; + + export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; +} + +declare module "socket:application/menu" { /** - * A container for Core Post diagnostics. + * Internal IPC for setting an application menu + * @ignore */ - export class PostsDiagnostic extends Diagnostic { - } + export function setMenu(options: any, type: any): Promise<ipc.Result>; /** - * A container for child process diagnostics. + * Internal IPC for setting an application context menu + * @ignore */ - export class ChildProcessDiagnostic extends Diagnostic { - } + export function setContextMenu(options: any): Promise<any>; /** - * A container for AI diagnostics. + * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. */ - export class AIDiagnostic extends Diagnostic { + export class Menu extends EventTarget { /** - * A container for AI LLM diagnostics. + * `Menu` class constructor. + * @ignore + * @param {string} type */ - static LLMDiagnostic: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + constructor(type: string); /** - * Known AI LLM diagnostics. - * @type {AIDiagnostic.LLMDiagnostic} + * The broadcast channel for this menu. + * @ignore + * @type {BroadcastChannel} */ - llm: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - } - /** - * A container for various filesystem diagnostics. - */ - export class FSDiagnostic extends Diagnostic { + get channel(): BroadcastChannel; /** - * A container for filesystem watcher diagnostics. + * The `Menu` instance type. + * @type {('context'|'system'|'tray')?} */ - static WatchersDiagnostic: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + get type(): "tray" | "system" | "context"; /** - * A container for filesystem descriptors diagnostics. + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} */ - static DescriptorsDiagnostic: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * Known FS watcher diagnostics. - * @type {FSDiagnostic.WatchersDiagnostic} + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - watchers: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + get onerror(): (arg0: ErrorEvent) => any; /** - * @type {FSDiagnostic.DescriptorsDiagnostic} + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} */ - descriptors: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + /** + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} + */ + get onmenuitem(): (arg0: menuitemEvent) => any; + /** + * Set the menu layout for this `Menu` instance. + * @param {string|object} layoutOrOptions + * @param {object=} [options] + */ + set(layoutOrOptions: string | object, options?: object | undefined): Promise<any>; + #private; } /** - * A container for various timers diagnostics. + * A container for various `Menu` instances. */ - export class TimersDiagnostic extends Diagnostic { + export class MenuContainer extends EventTarget { /** - * A container for core timeout timer diagnostics. + * `MenuContainer` class constructor. + * @param {EventTarget} [sourceEventTarget] + * @param {object=} [options] */ - static TimeoutDiagnostic: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + constructor(sourceEventTarget?: EventTarget, options?: object | undefined); /** - * A container for core interval timer diagnostics. + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} */ - static IntervalDiagnostic: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + set onerror(onerror: (arg0: ErrorEvent) => any); /** - * A container for core immediate timer diagnostics. + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} */ - static ImmediateDiagnostic: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + get onerror(): (arg0: ErrorEvent) => any; /** - * @type {TimersDiagnostic.TimeoutDiagnostic} + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} */ - timeout: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); /** - * @type {TimersDiagnostic.IntervalDiagnostic} + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} */ - interval: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + get onmenuitem(): (arg0: menuitemEvent) => any; /** - * @type {TimersDiagnostic.ImmediateDiagnostic} + * The `TrayMenu` instance for the application. + * @type {TrayMenu} */ - immediate: { - new (): { - /** - * Known handles for this diagnostics. - * @type {Diagnostic.Handles} - */ - handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; - /** - * A container for handles related to the diagnostics - */ - Handles: { - new (): { - /** - * The nunmber of handles in this diagnostics. - * @type {number} - */ - count: number; - /** - * A set of known handle IDs - * @type {string[]} - */ - ids: string[]; - }; - }; - }; + get tray(): TrayMenu; + /** + * The `SystemMenu` instance for the application. + * @type {SystemMenu} + */ + get system(): SystemMenu; + /** + * The `ContextMenu` instance for the application. + * @type {ContextMenu} + */ + get context(): ContextMenu; + #private; } /** - * A container for UDP diagnostics. + * A `Menu` instance that represents a context menu. */ - export class UDPDiagnostic extends Diagnostic { + export class ContextMenu extends Menu { + constructor(); } /** - * A container for various queried runtime diagnostics. + * A `Menu` instance that represents the system menu. */ - export class QueryDiagnostic { - posts: PostsDiagnostic; - childProcess: ChildProcessDiagnostic; - ai: AIDiagnostic; - fs: FSDiagnostic; - timers: TimersDiagnostic; - udp: UDPDiagnostic; - uv: UVDiagnostic; + export class SystemMenu extends Menu { + constructor(); } - namespace _default { - export { query }; + /** + * A `Menu` instance that represents the tray menu. + */ + export class TrayMenu extends Menu { + constructor(); } - export default _default; -} - -declare module "socket:diagnostics/index" { /** - * @param {string} name - * @return {import('./channels.js').Channel} + * The application tray menu. + * @type {TrayMenu} */ - export function channel(name: string): import("socket:diagnostics/channels").Channel; - export default exports; - import * as exports from "socket:diagnostics/index"; - import channels from "socket:diagnostics/channels"; - import window from "socket:diagnostics/window"; - import runtime from "socket:diagnostics/runtime"; - - export { channels, window, runtime }; -} - -declare module "socket:diagnostics" { - export * from "socket:diagnostics/index"; - export default exports; - import * as exports from "socket:diagnostics/index"; -} - -declare module "socket:internal/symbols" { - export const dispose: any; - export const serialize: any; - namespace _default { - export { dispose }; - export { serialize }; - } - export default _default; -} - -declare module "socket:gc" { - /** - * Track `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when - * environment garbage collects object. - * @param {object} object - * @return {boolean} - */ - export function ref(object: object, ...args: any[]): boolean; - /** - * Stop tracking `object` ref to call `Symbol.for('socket.runtime.gc.finalize')` method when - * environment garbage collects object. - * @param {object} object - * @return {boolean} - */ - export function unref(object: object): boolean; - /** - * An alias for `unref()` - * @param {object} object} - * @return {boolean} - */ - export function retain(object: object): boolean; - /** - * Call finalize on `object` for `gc.finalizer` implementation. - * @param {object} object] - * @return {Promise<boolean>} - */ - export function finalize(object: object, ...args: any[]): Promise<boolean>; - /** - * Calls all pending finalization handlers forcefully. This function - * may have unintended consequences as objects be considered finalized - * and still strongly held (retained) somewhere. - */ - export function release(): Promise<void>; - export const finalizers: WeakMap<object, any>; - export const kFinalizer: unique symbol; - export const finalizer: symbol; + export const tray: TrayMenu; /** - * @type {Set<WeakRef>} + * The application system menu. + * @type {SystemMenu} */ - export const pool: Set<WeakRef<any>>; + export const system: SystemMenu; /** - * Static registry for objects to clean up underlying resources when they - * are gc'd by the environment. There is no guarantee that the `finalizer()` - * is called at any time. + * The application context menu. + * @type {ContextMenu} */ - export const registry: FinalizationRegistry<Finalizer>; + export const context: ContextMenu; /** - * Default exports which also acts a retained value to persist bound - * `Finalizer#handle()` functions from being gc'd before the - * `FinalizationRegistry` callback is called because `heldValue` must be - * strongly held (retained) in order for the callback to be called. + * The application menus container. + * @type {MenuContainer} */ - export const gc: any; - export default gc; + export const container: MenuContainer; + export default container; + import ipc from "socket:ipc"; +} + +declare module "socket:internal/events" { /** - * A container for strongly (retain) referenced finalizer function - * with arguments weakly referenced to an object that will be - * garbage collected. + * An event dispatched when an application URL is opening the application. */ - export class Finalizer { + export class ApplicationURLEvent extends Event { /** - * Creates a `Finalizer` from input. + * `ApplicationURLEvent` class constructor. + * @param {string=} [type] + * @param {object=} [options] */ - static from(handler: any): Finalizer; + constructor(type?: string | undefined, options?: object | undefined); /** - * `Finalizer` class constructor. - * @private - * @param {array} args - * @param {function} handle + * `true` if the application URL is valid (parses correctly). + * @type {boolean} */ - private constructor(); - args: any[]; - handle: any; - } -} - -declare module "socket:internal/async/hooks" { - export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - export function getNextAsyncResourceId(): number; - export function executionAsyncResource(): any; - export function executionAsyncId(): any; - export function triggerAsyncId(): any; - export function getDefaultExecutionAsyncId(): any; - export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; - export function getTopLevelAsyncResourceName(): any; - /** - * The default top level async resource ID - * @type {number} - */ - export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; - export namespace state { - let defaultExecutionAsyncId: number; - } - export namespace hooks { - let init: any[]; - let before: any[]; - let after: any[]; - let destroy: any[]; - let promiseResolve: any[]; - } - /** - * A base class for the `AsyncResource` class or other higher level async - * resource classes. - */ - export class CoreAsyncResource { + get isValid(): boolean; /** - * `CoreAsyncResource` class constructor. - * @param {string} type - * @param {object|number=} [options] + * Data associated with the `ApplicationURLEvent`. + * @type {?any} */ - constructor(type: string, options?: (object | number) | undefined); + get data(): any; /** - * The `CoreAsyncResource` type. - * @type {string} + * The original source URI + * @type {?string} */ - get type(): string; + get source(): string; /** - * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This - * value is only set to `true` if `emitDestroy()` was called, likely from - * destroying the resource manually. - * @type {boolean} + * The `URL` for the `ApplicationURLEvent`. + * @type {?URL} */ - get destroyed(): boolean; + get url(): URL; /** - * The unique async resource ID. - * @return {number} + * String tag name for an `ApplicationURLEvent` instance. + * @type {string} */ - asyncId(): number; + get [Symbol.toStringTag](): string; + #private; + } + /** + * An event dispacted for a registered global hotkey expression. + */ + export class HotKeyEvent extends MessageEvent<any> { /** - * The trigger async resource ID. - * @return {number} + * `HotKeyEvent` class constructor. + * @ignore + * @param {string=} [type] + * @param {object=} [data] */ - triggerAsyncId(): number; + constructor(type?: string | undefined, data?: object | undefined); /** - * Manually emits destroy hook for the resource. - * @return {CoreAsyncResource} + * The global unique ID for this hotkey binding. + * @type {number?} */ - emitDestroy(): CoreAsyncResource; + get id(): number; /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @return {function} + * The computed hash for this hotkey binding. + * @type {number?} */ - bind(fn: Function, thisArg?: object | undefined): Function; + get hash(): number; /** - * Runs function `fn` in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @param {...any} [args] - * @return {any} + * The normalized hotkey expression as a sequence of tokens. + * @type {string[]} */ - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; - #private; - } - export class TopLevelAsyncResource extends CoreAsyncResource { + get sequence(): string[]; + /** + * The original expression of the hotkey binding. + * @type {string?} + */ + get expression(): string; } - export const asyncContextVariable: Variable<any>; - export const topLevelAsyncResource: TopLevelAsyncResource; - export default hooks; - import { Variable } from "socket:async/context"; -} - -declare module "socket:async/resource" { - /** - * @typedef {{ - * triggerAsyncId?: number, - * requireManualDestroy?: boolean - * }} AsyncResourceOptions - */ /** - * A container that should be extended that represents a resource with - * an asynchronous execution context. + * An event dispacted when a menu item is selected. */ - export class AsyncResource extends CoreAsyncResource { + export class MenuItemEvent extends MessageEvent<any> { /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of an anonymous `AsyncResource`. - * @param {function} fn - * @param {object|string=} [type] - * @param {object=} [thisArg] - * @return {function} + * `MenuItemEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [data] + * @param {import('../application/menu.js').Menu} menu */ - static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** - * `AsyncResource` class constructor. - * @param {string} type - * @param {AsyncResourceOptions|number=} [options] + * The `Menu` this event has been dispatched for. + * @type {import('../application/menu.js').Menu?} */ - constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); - } - export default AsyncResource; - export type AsyncResourceOptions = { - triggerAsyncId?: number; - requireManualDestroy?: boolean; - }; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - import { CoreAsyncResource } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/hooks" { - /** - * Factory for creating a `AsyncHook` instance. - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] - * @return {AsyncHook} - */ - export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; - /** - * A container for `AsyncHooks` callbacks. - * @ignore - */ - export class AsyncHookCallbacks { - /** - * `AsyncHookCallbacks` class constructor. - * @ignore - * @param {AsyncHookCallbackOptions} [options] - */ - constructor(options?: AsyncHookCallbackOptions); - init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - before(asyncId: any): void; - after(asyncId: any): void; - destroy(asyncId: any): void; - promiseResolve(asyncId: any): void; - } - /** - * A container for registering various callbacks for async resource hooks. - */ - export class AsyncHook { - /** - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] - */ - constructor(callbacks?: any); + get menu(): import("socket:application/menu").Menu; /** - * @type {boolean} + * The title of the menu item. + * @type {string?} */ - get enabled(): boolean; + get title(): string; /** - * Enable the async hook. - * @return {AsyncHook} + * An optional tag value for the menu item that may also be the + * parent menu item title. + * @type {string?} */ - enable(): AsyncHook; + get tag(): string; /** - * Disables the async hook - * @return {AsyncHook} + * The parent title of the menu item. + * @type {string?} */ - disable(): AsyncHook; + get parent(): string; #private; } - export default createHook; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/storage" { /** - * A container for storing values that remain present during - * asynchronous operations. + * An event dispacted when the application receives an OS signal */ - export class AsyncLocalStorage { - /** - * Binds function `fn` to run in the execution context of an - * anonymous `AsyncResource`. - * @param {function} fn - * @return {function} - */ - static bind(fn: Function): Function; - /** - * Captures the current async context and returns a function that runs - * a function in that execution context. - * @return {function} - */ - static snapshot(): Function; - /** - * @type {boolean} - */ - get enabled(): boolean; - /** - * Disables the `AsyncLocalStorage` instance. When disabled, - * `getStore()` will always return `undefined`. - */ - disable(): void; + export class SignalEvent extends MessageEvent<any> { /** - * Enables the `AsyncLocalStorage` instance. + * `SignalEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [options] */ - enable(): void; + constructor(type?: string | undefined, options?: object | undefined); /** - * Enables and sets the `AsyncLocalStorage` instance default store value. - * @param {any} store + * The code of the signal. + * @type {import('../signal.js').signal} */ - enterWith(store: any): void; + get code(): number; /** - * Runs function `fn` in the current asynchronous execution context with - * a given `store` value and arguments given to `fn`. - * @param {any} store - * @param {function} fn - * @param {...any} args - * @return {any} + * The name of the signal. + * @type {string} */ - run(store: any, fn: Function, ...args: any[]): any; - exit(fn: any, ...args: any[]): any; + get name(): string; /** - * If the `AsyncLocalStorage` instance is enabled, it returns the current - * store value for this asynchronous execution context. - * @return {any|undefined} + * An optional message describing the signal + * @type {string} */ - getStore(): any | undefined; + get message(): string; #private; } - export default AsyncLocalStorage; + namespace _default { + export { ApplicationURLEvent }; + export { MenuItemEvent }; + export { SignalEvent }; + export { HotKeyEvent }; + } + export default _default; } -declare module "socket:async/deferred" { +declare module "socket:path/well-known" { /** - * Dispatched when a `Deferred` internal promise is resolved. + * Well known path to the user's "Downloads" folder. + * @type {?string} */ - export class DeferredResolveEvent extends Event { - /** - * `DeferredResolveEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {any=} [result] - */ - constructor(type?: string | undefined, result?: any | undefined); - /** - * The `Deferred` promise result value. - * @type {any?} - */ - result: any | null; - } + export const DOWNLOADS: string | null; /** - * Dispatched when a `Deferred` internal promise is rejected. + * Well known path to the user's "Documents" folder. + * @type {?string} */ - export class DeferredRejectEvent { - /** - * `DeferredRejectEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {Error=} [error] - */ - constructor(type?: string | undefined, error?: Error | undefined); - } + export const DOCUMENTS: string | null; /** - * A utility class for creating deferred promises. + * Well known path to the user's "Pictures" folder. + * @type {?string} */ - export class Deferred extends EventTarget { - /** - * `Deferred` class constructor. - * @param {Deferred|Promise?} [promise] - */ - constructor(promise?: Deferred | (Promise<any> | null)); - /** - * Function to resolve the associated promise. - * @type {function} - */ - resolve: Function; - /** - * Function to reject the associated promise. - * @type {function} - */ - reject: Function; - /** - * Attaches a fulfillment callback and a rejection callback to the promise, - * and returns a new promise resolving to the return value of the called - * callback. - * @param {function(any)=} [resolve] - * @param {function(Error)=} [reject] - */ - then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise<any>; - /** - * Attaches a rejection callback to the promise, and returns a new promise - * resolving to the return value of the callback if it is called, or to its - * original fulfillment value if the promise is instead fulfilled. - * @param {function(Error)=} [callback] - */ - catch(callback?: ((arg0: Error) => any) | undefined): Promise<any>; - /** - * Attaches a callback for when the promise is settled (fulfilled or rejected). - * @param {function(any?)} [callback] - */ - finally(callback?: (arg0: any | null) => any): Promise<any>; - /** - * The promise associated with this Deferred instance. - * @type {Promise<any>} - */ - get promise(): Promise<any>; - /** - * A string representation of this Deferred instance. - * @type {string} - * @ignore - */ - get [Symbol.toStringTag](): string; - #private; - } - export default Deferred; -} - -declare module "socket:async" { - export default exports; - import AsyncLocalStorage from "socket:async/storage"; - import AsyncResource from "socket:async/resource"; - import AsyncContext from "socket:async/context"; - import Deferred from "socket:async/deferred"; - import { executionAsyncResource } from "socket:async/hooks"; - import { executionAsyncId } from "socket:async/hooks"; - import { triggerAsyncId } from "socket:async/hooks"; - import { createHook } from "socket:async/hooks"; - import { AsyncHook } from "socket:async/hooks"; - import * as exports from "socket:async"; - - export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; -} - -declare module "socket:application/menu" { + export const PICTURES: string | null; /** - * Internal IPC for setting an application menu - * @ignore + * Well known path to the user's "Desktop" folder. + * @type {?string} */ - export function setMenu(options: any, type: any): Promise<ipc.Result>; + export const DESKTOP: string | null; /** - * Internal IPC for setting an application context menu - * @ignore + * Well known path to the user's "Videos" folder. + * @type {?string} */ - export function setContextMenu(options: any): Promise<any>; + export const VIDEOS: string | null; /** - * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + * Well known path to the user's "Music" folder. + * @type {?string} */ - export class Menu extends EventTarget { - /** - * `Menu` class constructor. - * @ignore - * @param {string} type - */ - constructor(type: string); - /** - * The broadcast channel for this menu. - * @ignore - * @type {BroadcastChannel} - */ - get channel(): BroadcastChannel; - /** - * The `Menu` instance type. - * @type {('context'|'system'|'tray')?} - */ - get type(): "tray" | "system" | "context"; - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * Set the menu layout for this `Menu` instance. - * @param {string|object} layoutOrOptions - * @param {object=} [options] - */ - set(layoutOrOptions: string | object, options?: object | undefined): Promise<any>; - #private; - } + export const MUSIC: string | null; /** - * A container for various `Menu` instances. + * Well known path to the application's "resources" folder. + * @type {?string} */ - export class MenuContainer extends EventTarget { - /** - * `MenuContainer` class constructor. - * @param {EventTarget} [sourceEventTarget] - * @param {object=} [options] - */ - constructor(sourceEventTarget?: EventTarget, options?: object | undefined); - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * The `TrayMenu` instance for the application. - * @type {TrayMenu} - */ - get tray(): TrayMenu; - /** - * The `SystemMenu` instance for the application. - * @type {SystemMenu} - */ - get system(): SystemMenu; - /** - * The `ContextMenu` instance for the application. - * @type {ContextMenu} - */ - get context(): ContextMenu; - #private; - } + export const RESOURCES: string | null; /** - * A `Menu` instance that represents a context menu. + * Well known path to the application's "config" folder. + * @type {?string} */ - export class ContextMenu extends Menu { - constructor(); - } + export const CONFIG: string | null; /** - * A `Menu` instance that represents the system menu. + * Well known path to the application's "data" folder. + * @type {?string} */ - export class SystemMenu extends Menu { - constructor(); - } + export const DATA: string | null; /** - * A `Menu` instance that represents the tray menu. + * Well known path to the application's "log" folder. + * @type {?string} */ - export class TrayMenu extends Menu { - constructor(); + export const LOG: string | null; + /** + * Well known path to the application's "tmp" folder. + * @type {?string} + */ + export const TMP: string | null; + /** + * Well known path to the application's "home" folder. + * This may be the user's HOME directory or the application container sandbox. + * @type {?string} + */ + export const HOME: string | null; + namespace _default { + export { DOWNLOADS }; + export { DOCUMENTS }; + export { RESOURCES }; + export { PICTURES }; + export { DESKTOP }; + export { VIDEOS }; + export { CONFIG }; + export { MUSIC }; + export { HOME }; + export { DATA }; + export { LOG }; + export { TMP }; } + export default _default; +} + +declare module "socket:os" { /** - * The application tray menu. - * @type {TrayMenu} + * Returns the operating system CPU architecture for which Socket was compiled. + * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' */ - export const tray: TrayMenu; + export function arch(): string; /** - * The application system menu. - * @type {SystemMenu} + * Returns an array of objects containing information about each CPU/core. + * @returns {Array<object>} cpus - An array of objects containing information about each CPU/core. + * The properties of the objects are: + * - model `<string>` - CPU model name. + * - speed `<number>` - CPU clock speed (in MHz). + * - times `<object>` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. + * - user `<number>` - Time spent by this CPU or core in user mode. + * - nice `<number>` - Time spent by this CPU or core in user mode with low priority (nice). + * - sys `<number>` - Time spent by this CPU or core in system mode. + * - idle `<number>` - Time spent by this CPU or core in idle mode. + * - irq `<number>` - Time spent by this CPU or core in IRQ mode. + * @see {@link https://nodejs.org/api/os.html#os_os_cpus} */ - export const system: SystemMenu; + export function cpus(): Array<object>; /** - * The application context menu. - * @type {ContextMenu} + * Returns an object containing network interfaces that have been assigned a network address. + * @returns {object} - An object containing network interfaces that have been assigned a network address. + * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. + * The properties available on the assigned network address object include: + * - address `<string>` - The assigned IPv4 or IPv6 address. + * - netmask `<string>` - The IPv4 or IPv6 network mask. + * - family `<string>` - The address family ('IPv4' or 'IPv6'). + * - mac `<string>` - The MAC address of the network interface. + * - internal `<boolean>` - Indicates whether the network interface is a loopback interface. + * - scopeid `<number>` - The numeric scope ID (only specified when family is 'IPv6'). + * - cidr `<string>` - The CIDR notation of the interface. + * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} */ - export const context: ContextMenu; + export function networkInterfaces(): object; /** - * The application menus container. - * @type {MenuContainer} + * Returns the operating system platform. + * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_platform} + * The returned value is equivalent to `process.platform`. */ - export const container: MenuContainer; - export default container; - import ipc from "socket:ipc"; -} - -declare module "socket:internal/events" { + export function platform(): string; /** - * An event dispatched when an application URL is opening the application. + * Returns the operating system name. + * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_type} */ - export class ApplicationURLEvent extends Event { - /** - * `ApplicationURLEvent` class constructor. - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * `true` if the application URL is valid (parses correctly). - * @type {boolean} - */ - get isValid(): boolean; - /** - * Data associated with the `ApplicationURLEvent`. - * @type {?any} - */ - get data(): any; - /** - * The original source URI - * @type {?string} - */ - get source(): string; - /** - * The `URL` for the `ApplicationURLEvent`. - * @type {?URL} - */ - get url(): URL; - /** - * String tag name for an `ApplicationURLEvent` instance. - * @type {string} - */ - get [Symbol.toStringTag](): string; - #private; - } + export function type(): string; /** - * An event dispacted for a registered global hotkey expression. - */ - export class HotKeyEvent extends MessageEvent<any> { - /** - * `HotKeyEvent` class constructor. - * @ignore - * @param {string=} [type] - * @param {object=} [data] - */ - constructor(type?: string | undefined, data?: object | undefined); - /** - * The global unique ID for this hotkey binding. - * @type {number?} - */ - get id(): number; - /** - * The computed hash for this hotkey binding. - * @type {number?} - */ - get hash(): number; - /** - * The normalized hotkey expression as a sequence of tokens. - * @type {string[]} - */ - get sequence(): string[]; - /** - * The original expression of the hotkey binding. - * @type {string?} - */ - get expression(): string; - } - /** - * An event dispacted when a menu item is selected. - */ - export class MenuItemEvent extends MessageEvent<any> { - /** - * `MenuItemEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [data] - * @param {import('../application/menu.js').Menu} menu - */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); - /** - * The `Menu` this event has been dispatched for. - * @type {import('../application/menu.js').Menu?} - */ - get menu(): import("socket:application/menu").Menu; - /** - * The title of the menu item. - * @type {string?} - */ - get title(): string; - /** - * An optional tag value for the menu item that may also be the - * parent menu item title. - * @type {string?} - */ - get tag(): string; - /** - * The parent title of the menu item. - * @type {string?} - */ - get parent(): string; - #private; - } - /** - * An event dispacted when the application receives an OS signal - */ - export class SignalEvent extends MessageEvent<any> { - /** - * `SignalEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * The code of the signal. - * @type {import('../signal.js').signal} - */ - get code(): number; - /** - * The name of the signal. - * @type {string} - */ - get name(): string; - /** - * An optional message describing the signal - * @type {string} - */ - get message(): string; - #private; - } - namespace _default { - export { ApplicationURLEvent }; - export { MenuItemEvent }; - export { SignalEvent }; - export { HotKeyEvent }; - } - export default _default; -} - -declare module "socket:path/well-known" { - /** - * Well known path to the user's "Downloads" folder. - * @type {?string} - */ - export const DOWNLOADS: string | null; - /** - * Well known path to the user's "Documents" folder. - * @type {?string} - */ - export const DOCUMENTS: string | null; - /** - * Well known path to the user's "Pictures" folder. - * @type {?string} - */ - export const PICTURES: string | null; - /** - * Well known path to the user's "Desktop" folder. - * @type {?string} - */ - export const DESKTOP: string | null; - /** - * Well known path to the user's "Videos" folder. - * @type {?string} - */ - export const VIDEOS: string | null; - /** - * Well known path to the user's "Music" folder. - * @type {?string} - */ - export const MUSIC: string | null; - /** - * Well known path to the application's "resources" folder. - * @type {?string} - */ - export const RESOURCES: string | null; - /** - * Well known path to the application's "config" folder. - * @type {?string} - */ - export const CONFIG: string | null; - /** - * Well known path to the application's "data" folder. - * @type {?string} - */ - export const DATA: string | null; - /** - * Well known path to the application's "log" folder. - * @type {?string} - */ - export const LOG: string | null; - /** - * Well known path to the application's "tmp" folder. - * @type {?string} - */ - export const TMP: string | null; - /** - * Well known path to the application's "home" folder. - * This may be the user's HOME directory or the application container sandbox. - * @type {?string} - */ - export const HOME: string | null; - namespace _default { - export { DOWNLOADS }; - export { DOCUMENTS }; - export { RESOURCES }; - export { PICTURES }; - export { DESKTOP }; - export { VIDEOS }; - export { CONFIG }; - export { MUSIC }; - export { HOME }; - export { DATA }; - export { LOG }; - export { TMP }; - } - export default _default; -} - -declare module "socket:os" { - /** - * Returns the operating system CPU architecture for which Socket was compiled. - * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' - */ - export function arch(): string; - /** - * Returns an array of objects containing information about each CPU/core. - * @returns {Array<object>} cpus - An array of objects containing information about each CPU/core. - * The properties of the objects are: - * - model `<string>` - CPU model name. - * - speed `<number>` - CPU clock speed (in MHz). - * - times `<object>` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. - * - user `<number>` - Time spent by this CPU or core in user mode. - * - nice `<number>` - Time spent by this CPU or core in user mode with low priority (nice). - * - sys `<number>` - Time spent by this CPU or core in system mode. - * - idle `<number>` - Time spent by this CPU or core in idle mode. - * - irq `<number>` - Time spent by this CPU or core in IRQ mode. - * @see {@link https://nodejs.org/api/os.html#os_os_cpus} - */ - export function cpus(): Array<object>; - /** - * Returns an object containing network interfaces that have been assigned a network address. - * @returns {object} - An object containing network interfaces that have been assigned a network address. - * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. - * The properties available on the assigned network address object include: - * - address `<string>` - The assigned IPv4 or IPv6 address. - * - netmask `<string>` - The IPv4 or IPv6 network mask. - * - family `<string>` - The address family ('IPv4' or 'IPv6'). - * - mac `<string>` - The MAC address of the network interface. - * - internal `<boolean>` - Indicates whether the network interface is a loopback interface. - * - scopeid `<number>` - The numeric scope ID (only specified when family is 'IPv6'). - * - cidr `<string>` - The CIDR notation of the interface. - * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} - */ - export function networkInterfaces(): object; - /** - * Returns the operating system platform. - * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_platform} - * The returned value is equivalent to `process.platform`. - */ - export function platform(): string; - /** - * Returns the operating system name. - * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_type} - */ - export function type(): string; - /** - * @returns {boolean} - `true` if the operating system is Windows. + * @returns {boolean} - `true` if the operating system is Windows. */ export function isWindows(): boolean; /** @@ -5112,144 +4133,1123 @@ declare module "socket:fs/stream" { */ get highWaterMark(): number; /** - * Relative or absolute path of the underlying `FileHandle`. + * Relative or absolute path of the underlying `FileHandle`. + */ + get path(): any; + /** + * `true` if the stream is in a pending state. + */ + get pending(): boolean; + _open(callback: any): Promise<any>; + _read(callback: any): Promise<any>; + } + export namespace ReadStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + } + /** + * A `Writable` stream for a `FileHandle`. + */ + export class WriteStream extends Writable { + start: any; + handle: any; + signal: any; + timeout: any; + bytesWritten: number; + shouldEmitClose: boolean; + /** + * Sets file handle for the WriteStream. + * @param {FileHandle} handle + */ + setHandle(handle: FileHandle): void; + /** + * The max buffer size for the Writetream. + */ + get highWaterMark(): number; + /** + * Relative or absolute path of the underlying `FileHandle`. + */ + get path(): any; + /** + * `true` if the stream is in a pending state. + */ + get pending(): boolean; + _open(callback: any): Promise<any>; + _write(buffer: any, callback: any): any; + } + export namespace WriteStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + } + export const FileReadStream: typeof exports.ReadStream; + export const FileWriteStream: typeof exports.WriteStream; + export default exports; + export type FileHandle = import("socket:fs/handle").FileHandle; + import { Readable } from "socket:stream"; + import { Writable } from "socket:stream"; + import * as exports from "socket:fs/stream"; + +} + +declare module "socket:fs/constants" { + /** + * This flag can be used with uv_fs_copyfile() to return an error if the + * destination already exists. + */ + export const COPYFILE_EXCL: 1; + /** + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, a fallback copy mechanism is used. + */ + export const COPYFILE_FICLONE: 2; + /** + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, an error is returned. + */ + export const COPYFILE_FICLONE_FORCE: 4; + export const UV_DIRENT_UNKNOWN: any; + export const UV_DIRENT_FILE: any; + export const UV_DIRENT_DIR: any; + export const UV_DIRENT_LINK: any; + export const UV_DIRENT_FIFO: any; + export const UV_DIRENT_SOCKET: any; + export const UV_DIRENT_CHAR: any; + export const UV_DIRENT_BLOCK: any; + export const UV_FS_SYMLINK_DIR: any; + export const UV_FS_SYMLINK_JUNCTION: any; + export const UV_FS_O_FILEMAP: any; + export const O_RDONLY: any; + export const O_WRONLY: any; + export const O_RDWR: any; + export const O_APPEND: any; + export const O_ASYNC: any; + export const O_CLOEXEC: any; + export const O_CREAT: any; + export const O_DIRECT: any; + export const O_DIRECTORY: any; + export const O_DSYNC: any; + export const O_EXCL: any; + export const O_LARGEFILE: any; + export const O_NOATIME: any; + export const O_NOCTTY: any; + export const O_NOFOLLOW: any; + export const O_NONBLOCK: any; + export const O_NDELAY: any; + export const O_PATH: any; + export const O_SYNC: any; + export const O_TMPFILE: any; + export const O_TRUNC: any; + export const S_IFMT: any; + export const S_IFREG: any; + export const S_IFDIR: any; + export const S_IFCHR: any; + export const S_IFBLK: any; + export const S_IFIFO: any; + export const S_IFLNK: any; + export const S_IFSOCK: any; + export const S_IRWXU: any; + export const S_IRUSR: any; + export const S_IWUSR: any; + export const S_IXUSR: any; + export const S_IRWXG: any; + export const S_IRGRP: any; + export const S_IWGRP: any; + export const S_IXGRP: any; + export const S_IRWXO: any; + export const S_IROTH: any; + export const S_IWOTH: any; + export const S_IXOTH: any; + export const F_OK: any; + export const R_OK: any; + export const W_OK: any; + export const X_OK: any; + export default exports; + import * as exports from "socket:fs/constants"; + +} + +declare module "socket:fs/flags" { + export function normalizeFlags(flags: any): any; + export default exports; + import * as exports from "socket:fs/flags"; + +} + +declare module "socket:diagnostics/channels" { + /** + * Normalizes a channel name to lower case replacing white space, + * hyphens (-), underscores (_), with dots (.). + * @ignore + */ + export function normalizeName(group: any, name: any): string; + /** + * Used to preallocate a minimum sized array of subscribers for + * a channel. + * @ignore + */ + export const MIN_CHANNEL_SUBSCRIBER_SIZE: 64; + /** + * A general interface for diagnostic channels that can be subscribed to. + */ + export class Channel { + constructor(name: any); + name: any; + group: any; + /** + * Computed subscribers for all channels in this group. + * @type {Array<function>} + */ + get subscribers(): Function[]; + /** + * Accessor for determining if channel has subscribers. This + * is always `false` for `Channel instances and `true` for `ActiveChannel` + * instances. + */ + get hasSubscribers(): boolean; + /** + * Computed number of subscribers for this channel. + */ + get length(): number; + /** + * Resets channel state. + * @param {(boolean)} [shouldOrphan = false] + */ + reset(shouldOrphan?: (boolean)): void; + channel(name: any): Channel; + /** + * Adds an `onMessage` subscription callback to the channel. + * @return {boolean} + */ + subscribe(_: any, onMessage: any): boolean; + /** + * Removes an `onMessage` subscription callback from the channel. + * @param {function} onMessage + * @return {boolean} + */ + unsubscribe(_: any, onMessage: Function): boolean; + /** + * A no-op for `Channel` instances. This function always returns `false`. + * @param {string|object} name + * @param {object=} [message] + * @return Promise<boolean> + */ + publish(name: string | object, message?: object | undefined): Promise<boolean>; + /** + * Returns a string representation of the `ChannelRegistry`. + * @ignore + */ + toString(): any; + /** + * Iterator interface + * @ignore + */ + get [Symbol.iterator](): any[]; + /** + * The `Channel` string tag. + * @ignore + */ + [Symbol.toStringTag](): string; + #private; + } + /** + * An `ActiveChannel` is a prototype implementation for a `Channel` + * that provides an interface what is considered an "active" channel. The + * `hasSubscribers` accessor always returns `true` for this class. + */ + export class ActiveChannel extends Channel { + unsubscribe(onMessage: any): boolean; + /** + * @param {object|any} message + * @return Promise<boolean> + */ + publish(message: object | any): Promise<boolean>; + } + /** + * A container for a grouping of channels that are named and owned + * by this group. A `ChannelGroup` can also be a regular channel. + */ + export class ChannelGroup extends Channel { + /** + * @param {Array<Channel>} channels + * @param {string} name + */ + constructor(name: string, channels: Array<Channel>); + channels: Channel[]; + /** + * Subscribe to a channel or selection of channels in this group. + * @param {string} name + * @return {boolean} + */ + subscribe(name: string, onMessage: any): boolean; + /** + * Unsubscribe from a channel or selection of channels in this group. + * @param {string} name + * @return {boolean} + */ + unsubscribe(name: string, onMessage: any): boolean; + /** + * Gets or creates a channel for this group. + * @param {string} name + * @return {Channel} + */ + channel(name: string): Channel; + /** + * Select a test of channels from this group. + * The following syntax is supported: + * - One Channel: `group.channel` + * - All Channels: `*` + * - Many Channel: `group.*` + * - Collections: `['group.a', 'group.b', 'group.c'] or `group.a,group.b,group.c` + * @param {string|Array<string>} keys + * @param {(boolean)} [hasSubscribers = false] - Enforce subscribers in selection + * @return {Array<{name: string, channel: Channel}>} + */ + select(keys: string | Array<string>, hasSubscribers?: (boolean)): Array<{ + name: string; + channel: Channel; + }>; + } + /** + * An object mapping of named channels to `WeakRef<Channel>` instances. + */ + export const registry: { + /** + * Subscribes callback `onMessage` to channel of `name`. + * @param {string} name + * @param {function} onMessage + * @return {boolean} + */ + subscribe(name: string, onMessage: Function): boolean; + /** + * Unsubscribes callback `onMessage` from channel of `name`. + * @param {string} name + * @param {function} onMessage + * @return {boolean} + */ + unsubscribe(name: string, onMessage: Function): boolean; + /** + * Predicate to determine if a named channel has subscribers. + * @param {string} name + */ + hasSubscribers(name: string): boolean; + /** + * Get or set a channel by `name`. + * @param {string} name + * @return {Channel} + */ + channel(name: string): Channel; + /** + * Creates a `ChannelGroup` for a set of channels + * @param {string} name + * @param {Array<string>} [channels] + * @return {ChannelGroup} + */ + group(name: string, channels?: Array<string>): ChannelGroup; + /** + * Get a channel by name. The name is normalized. + * @param {string} name + * @return {Channel?} + */ + get(name: string): Channel | null; + /** + * Checks if a channel is known by name. The name is normalized. + * @param {string} name + * @return {boolean} + */ + has(name: string): boolean; + /** + * Set a channel by name. The name is normalized. + * @param {string} name + * @param {Channel} channel + * @return {Channel?} + */ + set(name: string, channel: Channel): Channel | null; + /** + * Removes a channel by `name` + * @return {boolean} + */ + remove(name: any): boolean; + /** + * Returns a string representation of the `ChannelRegistry`. + * @ignore + */ + toString(): any; + /** + * Returns a JSON representation of the `ChannelRegistry`. + * @return {object} + */ + toJSON(): object; + /** + * The `ChannelRegistry` string tag. + * @ignore + */ + [Symbol.toStringTag](): string; + }; + export default registry; +} + +declare module "socket:diagnostics/metric" { + export class Metric { + init(): void; + update(value: any): void; + destroy(): void; + toJSON(): {}; + toString(): string; + [Symbol.iterator](): any; + [Symbol.toStringTag](): string; + } + export default Metric; +} + +declare module "socket:diagnostics/window" { + export class RequestAnimationFrameMetric extends Metric { + constructor(options: any); + originalRequestAnimationFrame: typeof requestAnimationFrame; + requestAnimationFrame(callback: any): any; + sampleSize: any; + sampleTick: number; + channel: import("socket:diagnostics/channels").Channel; + value: { + rate: number; + samples: number; + }; + now: number; + samples: Uint8Array; + toJSON(): { + sampleSize: any; + sampleTick: number; + samples: number[]; + rate: number; + now: number; + }; + } + export class FetchMetric extends Metric { + constructor(options: any); + originalFetch: typeof fetch; + channel: import("socket:diagnostics/channels").Channel; + fetch(resource: any, options: any, extra: any): Promise<any>; + } + export class XMLHttpRequestMetric extends Metric { + constructor(options: any); + channel: import("socket:diagnostics/channels").Channel; + patched: { + open: { + (method: string, url: string | URL): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + }; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + }; + } + export class WorkerMetric extends Metric { + constructor(options: any); + GlobalWorker: { + new (scriptURL: string | URL, options?: WorkerOptions): Worker; + prototype: Worker; + } | { + new (): {}; + }; + channel: import("socket:diagnostics/channels").Channel; + Worker: { + new (url: any, options: any, ...args: any[]): {}; + }; + } + export const metrics: { + requestAnimationFrame: RequestAnimationFrameMetric; + XMLHttpRequest: XMLHttpRequestMetric; + Worker: WorkerMetric; + fetch: FetchMetric; + channel: import("socket:diagnostics/channels").ChannelGroup; + subscribe(...args: any[]): boolean; + unsubscribe(...args: any[]): boolean; + start(which: any): void; + stop(which: any): void; + }; + namespace _default { + export { metrics }; + } + export default _default; + import { Metric } from "socket:diagnostics/metric"; +} + +declare module "socket:diagnostics/runtime" { + /** + * Queries runtime diagnostics. + * @return {Promise<QueryDiagnostic>} + */ + export function query(): Promise<QueryDiagnostic>; + /** + * A base container class for diagnostic information. + */ + export class Diagnostic { + /** + * A container for handles related to the diagnostics + */ + static Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + } + /** + * A container for libuv diagnostics + */ + export class UVDiagnostic extends Diagnostic { + /** + * A container for libuv metrics. + */ + static Metrics: { + new (): { + /** + * The number of event loop iterations. + * @type {number} + */ + loopCount: number; + /** + * Number of events that have been processed by the event handler. + * @type {number} + */ + events: number; + /** + * Number of events that were waiting to be processed when the + * event provider was called. + * @type {number} + */ + eventsWaiting: number; + }; + }; + /** + * Known libuv metrics for this diagnostic. + * @type {UVDiagnostic.Metrics} + */ + metrics: { + new (): { + /** + * The number of event loop iterations. + * @type {number} + */ + loopCount: number; + /** + * Number of events that have been processed by the event handler. + * @type {number} + */ + events: number; + /** + * Number of events that were waiting to be processed when the + * event provider was called. + * @type {number} + */ + eventsWaiting: number; + }; + }; + /** + * The current idle time of the libuv loop + * @type {number} + */ + idleTime: number; + /** + * The number of active requests in the libuv loop + * @type {number} + */ + activeRequests: number; + } + /** + * A container for Core Post diagnostics. + */ + export class PostsDiagnostic extends Diagnostic { + } + /** + * A container for child process diagnostics. + */ + export class ChildProcessDiagnostic extends Diagnostic { + } + /** + * A container for AI diagnostics. + */ + export class AIDiagnostic extends Diagnostic { + /** + * A container for AI LLM diagnostics. + */ + static LLMDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * Known AI LLM diagnostics. + * @type {AIDiagnostic.LLMDiagnostic} + */ + llm: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + } + /** + * A container for various filesystem diagnostics. + */ + export class FSDiagnostic extends Diagnostic { + /** + * A container for filesystem watcher diagnostics. + */ + static WatchersDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for filesystem descriptors diagnostics. + */ + static DescriptorsDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * Known FS watcher diagnostics. + * @type {FSDiagnostic.WatchersDiagnostic} + */ + watchers: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {FSDiagnostic.DescriptorsDiagnostic} + */ + descriptors: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + } + /** + * A container for various timers diagnostics. + */ + export class TimersDiagnostic extends Diagnostic { + /** + * A container for core timeout timer diagnostics. + */ + static TimeoutDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for core interval timer diagnostics. + */ + static IntervalDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for core immediate timer diagnostics. + */ + static ImmediateDiagnostic: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {TimersDiagnostic.TimeoutDiagnostic} + */ + timeout: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * @type {TimersDiagnostic.IntervalDiagnostic} */ - get path(): any; + interval: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; /** - * `true` if the stream is in a pending state. + * @type {TimersDiagnostic.ImmediateDiagnostic} */ - get pending(): boolean; - _open(callback: any): Promise<any>; - _read(callback: any): Promise<any>; + immediate: { + new (): { + /** + * Known handles for this diagnostics. + * @type {Diagnostic.Handles} + */ + handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; + /** + * A container for handles related to the diagnostics + */ + Handles: { + new (): { + /** + * The nunmber of handles in this diagnostics. + * @type {number} + */ + count: number; + /** + * A set of known handle IDs + * @type {string[]} + */ + ids: string[]; + }; + }; + }; } - export namespace ReadStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + /** + * A container for UDP diagnostics. + */ + export class UDPDiagnostic extends Diagnostic { } /** - * A `Writable` stream for a `FileHandle`. + * A container for various queried runtime diagnostics. */ - export class WriteStream extends Writable { - start: any; - handle: any; - signal: any; - timeout: any; - bytesWritten: number; - shouldEmitClose: boolean; - /** - * Sets file handle for the WriteStream. - * @param {FileHandle} handle - */ - setHandle(handle: FileHandle): void; - /** - * The max buffer size for the Writetream. - */ - get highWaterMark(): number; - /** - * Relative or absolute path of the underlying `FileHandle`. - */ - get path(): any; - /** - * `true` if the stream is in a pending state. - */ - get pending(): boolean; - _open(callback: any): Promise<any>; - _write(buffer: any, callback: any): any; + export class QueryDiagnostic { + posts: PostsDiagnostic; + childProcess: ChildProcessDiagnostic; + ai: AIDiagnostic; + fs: FSDiagnostic; + timers: TimersDiagnostic; + udp: UDPDiagnostic; + uv: UVDiagnostic; } - export namespace WriteStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + namespace _default { + export { query }; } - export const FileReadStream: typeof exports.ReadStream; - export const FileWriteStream: typeof exports.WriteStream; - export default exports; - export type FileHandle = import("socket:fs/handle").FileHandle; - import { Readable } from "socket:stream"; - import { Writable } from "socket:stream"; - import * as exports from "socket:fs/stream"; - + export default _default; } -declare module "socket:fs/constants" { - /** - * This flag can be used with uv_fs_copyfile() to return an error if the - * destination already exists. - */ - export const COPYFILE_EXCL: 1; - /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, a fallback copy mechanism is used. - */ - export const COPYFILE_FICLONE: 2; +declare module "socket:diagnostics/index" { /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, an error is returned. + * @param {string} name + * @return {import('./channels.js').Channel} */ - export const COPYFILE_FICLONE_FORCE: 4; - export const UV_DIRENT_UNKNOWN: any; - export const UV_DIRENT_FILE: any; - export const UV_DIRENT_DIR: any; - export const UV_DIRENT_LINK: any; - export const UV_DIRENT_FIFO: any; - export const UV_DIRENT_SOCKET: any; - export const UV_DIRENT_CHAR: any; - export const UV_DIRENT_BLOCK: any; - export const UV_FS_SYMLINK_DIR: any; - export const UV_FS_SYMLINK_JUNCTION: any; - export const UV_FS_O_FILEMAP: any; - export const O_RDONLY: any; - export const O_WRONLY: any; - export const O_RDWR: any; - export const O_APPEND: any; - export const O_ASYNC: any; - export const O_CLOEXEC: any; - export const O_CREAT: any; - export const O_DIRECT: any; - export const O_DIRECTORY: any; - export const O_DSYNC: any; - export const O_EXCL: any; - export const O_LARGEFILE: any; - export const O_NOATIME: any; - export const O_NOCTTY: any; - export const O_NOFOLLOW: any; - export const O_NONBLOCK: any; - export const O_NDELAY: any; - export const O_PATH: any; - export const O_SYNC: any; - export const O_TMPFILE: any; - export const O_TRUNC: any; - export const S_IFMT: any; - export const S_IFREG: any; - export const S_IFDIR: any; - export const S_IFCHR: any; - export const S_IFBLK: any; - export const S_IFIFO: any; - export const S_IFLNK: any; - export const S_IFSOCK: any; - export const S_IRWXU: any; - export const S_IRUSR: any; - export const S_IWUSR: any; - export const S_IXUSR: any; - export const S_IRWXG: any; - export const S_IRGRP: any; - export const S_IWGRP: any; - export const S_IXGRP: any; - export const S_IRWXO: any; - export const S_IROTH: any; - export const S_IWOTH: any; - export const S_IXOTH: any; - export const F_OK: any; - export const R_OK: any; - export const W_OK: any; - export const X_OK: any; + export function channel(name: string): import("socket:diagnostics/channels").Channel; export default exports; - import * as exports from "socket:fs/constants"; + import * as exports from "socket:diagnostics/index"; + import channels from "socket:diagnostics/channels"; + import window from "socket:diagnostics/window"; + import runtime from "socket:diagnostics/runtime"; + export { channels, window, runtime }; } -declare module "socket:fs/flags" { - export function normalizeFlags(flags: any): any; +declare module "socket:diagnostics" { + export * from "socket:diagnostics/index"; export default exports; - import * as exports from "socket:fs/flags"; - + import * as exports from "socket:diagnostics/index"; } declare module "socket:fs/stats" { @@ -6972,6 +6972,11 @@ declare module "socket:application/client" { * @type {Client|null} */ get top(): Client; + /** + * A readonly `URL` of the current location of this client. + * @type {URL} + */ + get location(): URL; /** * Converts this `Client` instance to JSON. * @return {object} @@ -16661,11 +16666,11 @@ declare module "socket:internal/promise" { */ constructor(resolver: ResolverFunction); [resourceSymbol]: { - "__#16@#type": any; - "__#16@#destroyed": boolean; - "__#16@#asyncId": number; - "__#16@#triggerAsyncId": any; - "__#16@#requireManualDestroy": boolean; + "__#15@#type": any; + "__#15@#destroyed": boolean; + "__#15@#asyncId": number; + "__#15@#triggerAsyncId": any; + "__#15@#requireManualDestroy": boolean; readonly type: string; readonly destroyed: boolean; asyncId(): number; @@ -17379,6 +17384,7 @@ declare module "socket:shared-worker/init" { constructor(data: any); id: any; port: any; + client: any; scriptURL: any; url: any; hash: any; From bcf7be5dc64b5f931ee3b61e92349638d63b6591 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 8 Jul 2024 18:23:00 +0200 Subject: [PATCH 0915/1178] refactor(): remove dead android code --- api/ipc.js | 2 +- src/android/bridge.cc | 244 ------- src/android/bridge.kt | 1372 ------------------------------------ src/android/internal.hh | 131 ---- src/android/main.kt.bak | 308 -------- src/android/platform.hh | 11 - src/android/runtime.cc | 311 -------- src/android/runtime.kt | 349 --------- src/android/webview.kt | 900 ----------------------- src/android/window.cc | 194 ----- src/android/window.kt | 187 ----- src/ipc/scheme_handlers.hh | 4 - 12 files changed, 1 insertion(+), 4012 deletions(-) delete mode 100644 src/android/bridge.cc delete mode 100644 src/android/bridge.kt delete mode 100644 src/android/internal.hh delete mode 100644 src/android/main.kt.bak delete mode 100644 src/android/platform.hh delete mode 100644 src/android/runtime.cc delete mode 100644 src/android/runtime.kt delete mode 100644 src/android/webview.kt delete mode 100644 src/android/window.cc delete mode 100644 src/android/window.kt diff --git a/api/ipc.js b/api/ipc.js index 5df6306d02..688ad2790d 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -90,7 +90,7 @@ function initializeXHRIntercept () { if (url?.protocol === 'ipc:') { if ( - /put|post/i.test(method) && + /put|post|patch/i.test(method) && typeof body !== 'undefined' && typeof seq !== 'undefined' ) { diff --git a/src/android/bridge.cc b/src/android/bridge.cc deleted file mode 100644 index 0dd9304174..0000000000 --- a/src/android/bridge.cc +++ /dev/null @@ -1,244 +0,0 @@ -#include "internal.hh" - -using namespace SSC::android; - -static auto onInternalRouteResponseSignature = - "(" - "J" // requestId - "Ljava/lang/String;" // seq - "Ljava/lang/String;" // source - "Ljava/lang/String;" // value - "Ljava/lang/String;" // headers - "[B" // bytes - ")V"; - -namespace SSC::android { - Bridge::Bridge (JNIEnv* env, jobject self, Runtime* runtime) - : IPC::Bridge(runtime) - { - this->env = env; - this->self = env->NewGlobalRef(self); - this->pointer = reinterpret_cast<jlong>(this); - this->runtime = runtime; - this->router.dispatchFunction = [this](auto callback) { - // TODO(@jwerle): get `JavaVM*` from `JNIEnv*` above and then - // use use `JNIEnvironmentAttachment` to execute `callback` - if (callback != nullptr) { - callback(); - } - }; - - this->isAndroidEmulator = this->runtime->isEmulator; - } - - Bridge::~Bridge () { - this->env->DeleteGlobalRef(this->self); - IPC::Bridge::~Bridge(); - } -} - -extern "C" { - jlong external(Bridge, alloc)( - JNIEnv *env, - jobject self, - jlong runtimePointer - ) { - auto runtime = Runtime::from(runtimePointer); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return 0; - } - - auto bridge = new Bridge(env, self, runtime); - - if (bridge == nullptr) { - Throw(env, BridgeNotInitializedException); - return 0; - } - - return bridge->pointer; - } - - jboolean external(Bridge, dealloc)( - JNIEnv *env, - jobject self - ) { - auto bridge = Bridge::from(env, self); - - if (bridge == nullptr) { - Throw(env, BridgeNotInitializedException); - return false; - } - - delete bridge; - return true; - } - - jboolean external(Bridge, route)( - JNIEnv *env, - jobject self, - jstring uriString, - jbyteArray byteArray, - jlong requestId - ) { - auto bridge = Bridge::from(env, self); - - if (bridge == nullptr) { - Throw(env, BridgeNotInitializedException); - return false; - } - - JavaVM* jvm = nullptr; - auto jniVersion = env->GetVersion(); - env->GetJavaVM(&jvm); - auto attachment = JNIEnvironmentAttachment { jvm, jniVersion }; - - if (attachment.hasException()) { - return false; - } - - auto uri = StringWrap(env, uriString); - auto size = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0; - auto input = size > 0 ? new char[size]{0} : nullptr; - - if (size > 0 && input != nullptr) { - env->GetByteArrayRegion(byteArray, 0, size, (jbyte*) input); - } - - auto routed = bridge->route(uri.str(), input, size, [=](auto result) mutable { - if (result.seq == "-1") { - bridge->router.send(result.seq, result.str(), result.post); - return; - } - - auto attachment = JNIEnvironmentAttachment { jvm, jniVersion }; - auto self = bridge->self; - auto env = attachment.env; - - if (!attachment.hasException()) { - auto size = result.post.length; - auto body = *result.post.body; - auto bytes = body ? env->NewByteArray(size) : nullptr; - - if (bytes != nullptr) { - env->SetByteArrayRegion(bytes, 0, size, (jbyte *) body); - } - - auto seq = env->NewStringUTF(result.seq.c_str()); - auto source = env->NewStringUTF(result.source.c_str()); - auto value = env->NewStringUTF(result.str().c_str()); - auto headers = env->NewStringUTF(result.post.headers.c_str()); - - CallVoidClassMethodFromEnvironment( - env, - self, - "onInternalRouteResponse", - onInternalRouteResponseSignature, - requestId, - seq, - source, - value, - headers, - bytes - ); - - env->DeleteLocalRef(seq); - env->DeleteLocalRef(source); - env->DeleteLocalRef(value); - env->DeleteLocalRef(headers); - - if (bytes != nullptr) { - env->DeleteLocalRef(bytes); - } - } - }); - - delete [] input; - - if (!routed) { - auto attachment = JNIEnvironmentAttachment { jvm, jniVersion }; - auto env = attachment.env; - - if (!attachment.hasException()) { - auto msg = SSC::IPC::Message{uri.str()}; - auto err = SSC::JSON::Object::Entries { - {"source", uri.str()}, - {"err", SSC::JSON::Object::Entries { - {"message", "Not found"}, - {"type", "NotFoundError"}, - {"url", uri.str()} - }} - }; - - auto seq = env->NewStringUTF(msg.seq.c_str()); - auto source =env->NewStringUTF(msg.name.c_str()); - auto value = env->NewStringUTF(SSC::JSON::Object(err).str().c_str()); - auto headers = env->NewStringUTF(""); - - CallVoidClassMethodFromEnvironment( - env, - self, - "onInternalRouteResponse", - onInternalRouteResponseSignature, - requestId, - seq, - source, - value, - headers, - nullptr - ); - - env->DeleteLocalRef(seq); - env->DeleteLocalRef(source); - env->DeleteLocalRef(value); - env->DeleteLocalRef(headers); - } - } - - return routed; - } - - jboolean external(Bridge, emit)( - JNIEnv *env, - jobject self, - jstring eventNameString, - jstring eventDataString - ) { - auto bridge = Bridge::from(env, self); - - if (bridge == nullptr) { - Throw(env, BridgeNotInitializedException); - return false; - } - - JavaVM* jvm = nullptr; - auto jniVersion = env->GetVersion(); - env->GetJavaVM(&jvm); - auto attachment = JNIEnvironmentAttachment { jvm, jniVersion }; - - if (attachment.hasException()) { - return false; - } - - auto event = StringWrap(env, eventNameString); - auto data = StringWrap(env, eventDataString); - - return bridge->router.emit(event.str(), data.str()); - } - - jstring external(Bridge, getAllowedNodeCoreModulesList)( - JNIEnv *env, - jobject self - ) { - auto bridge = Bridge::from(env, self); - - if (bridge == nullptr) { - Throw(env, BridgeNotInitializedException); - return nullptr; - } - - static const auto list = SSC::join(bridge->getAllowedNodeCoreModules(), ","); - return env->NewStringUTF(list.c_str()); - } -} diff --git a/src/android/bridge.kt b/src/android/bridge.kt deleted file mode 100644 index 1171517adc..0000000000 --- a/src/android/bridge.kt +++ /dev/null @@ -1,1372 +0,0 @@ -// vim: set sw=2: -package __BUNDLE_IDENTIFIER__ - -interface IBridgeConfiguration { - val getRootDirectory: () -> String -} - -data class BridgeConfiguration ( - override val getRootDirectory: () -> String -) : IBridgeConfiguration - -data class Result ( - val id: Long, - val seq: String, - val source: String, - val value: String, - val bytes: ByteArray? = null, - val headers: Map<String, String> = emptyMap() -) - -typealias RouteCallback = (Result) -> Unit - -data class RouteRequest ( - val id: Long, - val callback: RouteCallback -) - -// container for a parseable IPC message (ipc://...) -class Message (message: String? = null) { - var uri: android.net.Uri? = - if (message != null) { - android.net.Uri.parse(message) - } else { - android.net.Uri.parse("ipc://") - } - - var name: String - get () = uri?.host ?: "" - set (name) { - uri = uri?.buildUpon()?.authority(name)?.build() - } - - var domain: String - get () { - val parts = name.split(".") - return parts.slice(0..(parts.size - 2)).joinToString(".") - } - set (_) {} - - var value: String - get () = get("value") - set (value) { - set("value", value) - } - - var seq: String - get () = get("seq") - set (seq) { - set("seq", seq) - } - - var bytes: ByteArray? = null - - fun get (key: String, defaultValue: String = ""): String { - val value = uri?.getQueryParameter(key) - - if (value != null && value.isNotEmpty()) { - return value - } - - return defaultValue - } - - fun has (key: String): Boolean { - return get(key).isNotEmpty() - } - - fun set (key: String, value: String): Boolean { - uri = uri?.buildUpon()?.appendQueryParameter(key, value)?.build() - return uri == null - } - - fun delete (key: String): Boolean { - if (uri?.getQueryParameter(key) == null) { - return false - } - - val params = uri?.queryParameterNames - val tmp = uri?.buildUpon()?.clearQuery() - - if (params != null) { - for (param: String in params) { - if (!param.equals(key)) { - val value = uri?.getQueryParameter(param) - tmp?.appendQueryParameter(param, value) - } - } - } - - uri = tmp?.build() - - return true - } - - override fun toString(): String { - return uri?.toString() ?: "" - } -} - -fun getPathFromContentDataColumn ( - activity: MainActivity, - uri: android.net.Uri, - id: String? = null -) : String? { - val context = activity.applicationContext - val column = android.provider.MediaStore.MediaColumns.DATA - var cursor: android.database.Cursor? = null - var result: String? = null - - try { - cursor = context.contentResolver.query(uri, arrayOf(column), null, null, null) - if (cursor != null) { - cursor.moveToFirst() - do { - if (id == null) { - result = cursor.getString(cursor.getColumnIndex(column)) - break - } else { - var index = cursor.getColumnIndex(android.provider.MediaStore.MediaColumns._ID) - var tmp: String? = null - - try { - tmp = cursor.getString(index) - } catch (e: Exception) {} - - if (tmp == id) { - index = cursor.getColumnIndex(column) - result = cursor.getString(index) - break - } - } - } while (cursor.moveToNext()) - } - } catch (err: Exception) { - return null - } finally { - if (cursor != null) { - cursor.close() - } - } - - return result -} - -fun isDocumentUri (activity: MainActivity, uri: android.net.Uri): Boolean { - return android.provider.DocumentsContract.isDocumentUri(activity.applicationContext, uri) -} - -fun isContentUri (uri: android.net.Uri): Boolean { - return uri.scheme == "content" -} - -fun isSocketUri (uri: android.net.Uri): Boolean { - return uri.scheme == "socket" -} - -fun isAssetBundleUri (uri: android.net.Uri): Boolean { - val path = uri.path - - if (path == null) { - return false - } - - return ( - (isContentUri(uri) || isSocketUri(uri)) && - uri.authority == "__BUNDLE_IDENTIFIER__" - ) -} - -fun isExternalStorageDocumentUri (uri: android.net.Uri): Boolean { - return uri.authority == "com.android.externalstorage.documents" -} - -fun isDownloadsDocumentUri (uri: android.net.Uri): Boolean { - return uri.authority == "com.android.providers.downloads.documents" -} - -fun isMediaDocumentUri (uri: android.net.Uri): Boolean { - return uri.authority == "com.android.providers.media.documents" -} - -fun getPathFromURI (activity: MainActivity, uri: android.net.Uri) : String? { - // just return the file path for `file://` based URIs - if (uri.scheme == "file") { - return uri.path - } - - if (isDocumentUri(activity, uri)) { - if (isExternalStorageDocumentUri(uri)) { - val externalStorage = android.os.Environment.getExternalStorageDirectory().absolutePath - val documentId = android.provider.DocumentsContract.getDocumentId(uri) - val parts = documentId.split(":") - val type = parts[0] - - if (type == "primary") { - return externalStorage + "/" + parts[1] - } - } - - if (isDownloadsDocumentUri(uri)) { - val documentId = android.provider.DocumentsContract.getDocumentId(uri) - val contentUri = android.content.ContentUris.withAppendedId( - android.net.Uri.parse("content://downloads/public_downloads"), - documentId.toLong() - ) - - return getPathFromContentDataColumn(activity, contentUri) - } - - if (isMediaDocumentUri(uri)) { - val documentId = android.provider.DocumentsContract.getDocumentId(uri) - val parts = documentId.split(":") - val type = parts[0] - val id = parts[1] - - val contentUri = ( - if (type == "image") { - android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI - } else if (type == "video") { - android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI - } else if (type == "audio") { - android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - } else { - android.provider.MediaStore.Files.getContentUri("external") - } - ) - - if (contentUri == null) { - return null - } - - return getPathFromContentDataColumn(activity, contentUri, id) - } - } else if (uri.scheme == "content") { - if (uri.authority == "com.google.android.apps.photos.content") { - return uri.lastPathSegment - } - - return getPathFromContentDataColumn(activity, uri) - } - - return null -} - -open class Bridge (runtime: Runtime, configuration: IBridgeConfiguration) { - open protected val TAG = "Bridge" - var pointer = alloc(runtime.pointer) - var runtime = java.lang.ref.WeakReference(runtime) - val requests = mutableMapOf<Long, RouteRequest>() - val configuration = configuration - val buffers = mutableMapOf<String, ByteArray>() - val semaphore = runtime.semaphore - - val openedAssetDirectories = mutableMapOf<String, String>() - val openedAssetDirectoriesEntryCache: MutableMap<String, Array<String>> = mutableMapOf() - val fileDescriptors = mutableMapOf<String, android.content.res.AssetFileDescriptor>() - val uris = mutableMapOf<String, android.net.Uri>() - - protected var nextRequestId = 0L - - fun finalize () { - if (this.pointer > 0) { - this.dealloc() - } - - this.pointer = 0 - } - - fun call (command: String, callback: RouteCallback? = null): Boolean { - return this.call(command, emptyMap(), callback) - } - - fun call (command: String, options: Map<String, String> = emptyMap(), callback: RouteCallback? = null): Boolean { - val message = Message("ipc://$command") - - for (entry in options.entries.iterator()) { - message.set(entry.key, entry.value) - } - - if (callback != null) { - return this.route(message.toString(), null, callback) - } - - return this.route(message.toString(), null, {}) - } - - fun route ( - value: String, - bytes: ByteArray? = null, - callback: RouteCallback - ): Boolean { - val activity = this.runtime.get()?.activity?.get() ?: return false - val runtime = activity.runtime - val message = Message(value) - val contentResolver = activity.applicationContext.contentResolver - val assetManager = activity.applicationContext.resources.assets - - message.bytes = bytes - - if (buffers.contains(message.seq)) { - message.bytes = buffers[message.seq] - buffers.remove(message.seq) - } - - if (message.domain == "fs") { - if (message.has("path")) { - var path = message.get("path") - val uri = android.net.Uri.parse(path) - if (!isAssetBundleUri(uri)) { - if (path.startsWith("/")) { - path = path.substring(1) - } else if (path.startsWith("./")) { - path = path.substring(2) - } - - try { - val stream = assetManager.open(path, 0) - message.set("path", "socket://__BUNDLE_IDENTIFIER__/$path") - stream.close() - } catch (e: java.io.FileNotFoundException) { - // noop - } catch (e: Exception) { - // noop - } - } - } - } - - when (message.name) { - "application.getScreenSize", - "application.getWindows" -> { - val windowManager = activity.applicationContext.getSystemService( - android.content.Context.WINDOW_SERVICE - ) as android.view.WindowManager - - val metrics = windowManager.getCurrentWindowMetrics() - val windowInsets = metrics.windowInsets - val insets = windowInsets.getInsetsIgnoringVisibility( - android.view.WindowInsets.Type.navigationBars() or - android.view.WindowInsets.Type.displayCutout() - ) - - val width = insets.right + insets.left - val height = insets.top + insets.bottom - - if (message.name == "application.getScreenSize") { - callback(Result(0, message.seq, message.name, """{ - "data": { - "width": $width, - "height": $height - } - }""")) - } else { - activity.runOnUiThread { - val status = 31 // WINDOW_SHOWN" - val title = activity.webview?.title ?: "" - callback(Result(0, message.seq, message.name, """{ - "data": [{ - "index": 0, - "title": "$title", - "width": $width, - "height": $height, - "status": $status - }] - }""")) - } - } - return true - } - - // handle WASM extensions here - "extension.stats" -> { - val name = message.get("name") - if (name.length > 0) { - val path = "socket/extensions/$name/$name.wasm" - try { - val stream = assetManager.open(path, 0) - val abi = 1 // TODO(@jwerle): read this over JNI - stream.close() - callback(Result(0, message.seq, message.name, """{ - "data": { - "abi": $abi, - "name": "$name", - "type": "wasm32", - "path": "socket://__BUNDLE_IDENTIFIER__/$path" - } - }""")) - return true - } catch (e: java.io.FileNotFoundException) { - // noop - } catch (e: Exception) { - // nooop - } - } - } - - // handle WASM extensions here - "extension.type" -> { - val name = message.get("name") - if (name.length > 0) { - try { - val path = "socket/extensions/$name/$name.wasm" - val stream = assetManager.open(path, 0) - stream.close() - callback(Result(0, message.seq, message.name, """{ - "data": { - "name": "$name", - "type": "wasm32" - } - }""")) - return true - } catch (e: java.io.FileNotFoundException) { - // noop - } catch (e: Exception) { - // nooop - } - } - } - - "window.showFileSystemPicker" -> { - val options = WebViewFilePickerOptions( - null, - arrayOf<String>(), - message.get("allowMultiple") == "true", - message.get("allowFiles") == "true", - message.get("allowDirs") == "true" - ) - - activity.showFileSystemPicker(options, fun (uris: Array<android.net.Uri>) { - var paths: Array<String> = arrayOf() - - for (uri in uris) { - val path = getPathFromURI(activity, uri) ?: uri - paths += "\"$path\"" - } - - callback(Result(0, message.seq, message.name, """{ - "data": { - "paths": ${paths.joinToString(prefix = "[", postfix = "]")} - } - }""")) - }) - return true - } - - "os.paths" -> { - val storage = android.os.Environment.getExternalStorageDirectory().absolutePath - - val resources = "socket://__BUNDLE_IDENTIFIER__" - var downloads = "$storage/Downloads" - var documents = "$storage/Documents" - var pictures = "$storage/Pictures" - var desktop = activity.getExternalFilesDir(null)?.absolutePath ?: "$storage/Desktop" - var videos = "$storage/DCIM/Camera/" - var music = "$storage/Music" - var home = desktop - - callback(Result(0, message.seq, message.name, """{ - "data": { - "resources": "$resources", - "downloads": "$downloads", - "documents": "$documents", - "pictures": "$pictures", - "desktop": "$desktop", - "videos": "$videos", - "music": "$music", - "home": "$home" - } - }""")) - return true - } - - "permissions.request" -> { - if (!message.has("name")) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "Expecting 'name' in parameters" } - }""")) - return true - } - - val name = message.get("name") - val permissions = mutableListOf<String>() - - when (name) { - "geolocation" -> { - if ( - activity.checkPermission("android.permission.ACCESS_COARSE_LOCATION") && - activity.checkPermission("android.permission.ACCESS_FINE_LOCATION") - ) { - callback(Result(0, message.seq, message.name, "{}")) - return true - } - - permissions.add("android.permission.ACCESS_COARSE_LOCATION") - permissions.add("android.permission.ACCESS_FINE_LOCATION") - } - - "push", "notifications" -> { - if (activity.checkPermission("android.permission.POST_NOTIFICATIONS")) { - callback(Result(0, message.seq, message.name, "{}")) - return true - } - - permissions.add("android.permission.POST_NOTIFICATIONS") - } - - else -> { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Unknown permission requested: '$name'" - } - }""")) - return true - } - } - - activity.requestPermissions(permissions.toTypedArray(), fun (granted: Boolean) { - if (granted) { - callback(Result(0, message.seq, message.name, "{}")) - } else { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "User denied permission request for '$name'" - } - }""")) - } - }) - - return true - } - - "permissions.query" -> { - if (!message.has("name")) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "Expecting 'name' in parameters" } - }""")) - return true - } - - val name = message.get("name") - - if (name == "geolocation") { - if (!runtime.isPermissionAllowed("geolocation")) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "User denied permissions to access the device's location" - } - }""")) - } else if ( - activity.checkPermission("android.permission.ACCESS_COARSE_LOCATION") && - activity.checkPermission("android.permission.ACCESS_FINE_LOCATION") - ) { - callback(Result(0, message.seq, message.name, """{ - "data": { - "state": "granted" - } - }""")) - } else { - callback(Result(0, message.seq, message.name, """{ - "data": { - "state": "prompt" - } - }""")) - } - } - - if (name == "notifications" || name == "push") { - if (!runtime.isPermissionAllowed("notifications")) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "User denied permissions to show notifications" - } - }""")) - } else if ( - activity.checkPermission("android.permission.POST_NOTIFICATIONS") && - androidx.core.app.NotificationManagerCompat.from(activity).areNotificationsEnabled() - ) { - callback(Result(0, message.seq, message.name, """{ - "data": { - "state": "granted" - } - }""")) - } else { - callback(Result(0, message.seq, message.name, """{ - "data": { - "state": "prompt" - } - }""")) - } - } - - if (name == "persistent-storage" || name == "storage-access") { - if (!runtime.isPermissionAllowed("data_access")) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "User denied permissions for ${name.replace('-', ' ')}" - } - }""")) - } - } - - return true - } - - "notification.show" -> { - if ( - !activity.checkPermission("android.permission.POST_NOTIFICATIONS") || - !androidx.core.app.NotificationManagerCompat.from(activity).areNotificationsEnabled() - ) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "User denied permissions for 'notifications'" } - }""")) - return true - } - - if (!message.has("id")) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "Expecting 'id' in parameters" } - }""")) - return true - } - - if (!message.has("title")) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "Expecting 'title' in parameters" } - }""")) - return true - } - - val id = message.get("id") - val channel = message.get("channel", "default").replace("default", "__BUNDLE_IDENTIFIER__"); - val vibrate = message.get("vibrate") - .split(",") - .filter({ it.length > 0 }) - .map({ it.toInt().toLong() }) - .toTypedArray() - - val identifier = id.toLongOrNull()?.toInt() ?: (0..16384).random().toInt() - - val contentIntent = android.content.Intent(activity, MainActivity::class.java).apply { - flags = ( - android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP or - android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP - ) - } - - val deleteIntent = android.content.Intent(activity, MainActivity::class.java).apply { - flags = ( - android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP or - android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP - ) - } - - contentIntent.setAction("notification.response.default") - contentIntent.putExtra("id", id) - - deleteIntent.setAction("notification.response.dismiss") - deleteIntent.putExtra("id", id) - - val pendingContentIntent: android.app.PendingIntent = android.app.PendingIntent.getActivity( - activity, - identifier, - contentIntent, - ( - android.app.PendingIntent.FLAG_UPDATE_CURRENT or - android.app.PendingIntent.FLAG_IMMUTABLE or - android.app.PendingIntent.FLAG_ONE_SHOT - ) - ) - - val pendingDeleteIntent: android.app.PendingIntent = android.app.PendingIntent.getActivity( - activity, - identifier, - deleteIntent, - ( - android.app.PendingIntent.FLAG_UPDATE_CURRENT or - android.app.PendingIntent.FLAG_IMMUTABLE - ) - ) - - val builder = androidx.core.app.NotificationCompat.Builder( - activity, - channel - ) - - builder - .setPriority(androidx.core.app.NotificationCompat.PRIORITY_DEFAULT) - .setContentTitle(message.get("title", "Notification")) - .setContentIntent(pendingContentIntent) - .setDeleteIntent(pendingDeleteIntent) - .setAutoCancel(true) - - if (message.has("body")) { - builder.setContentText(message.get("body")) - } - - if (message.has("icon")) { - val url = message.get("icon") - .replace("socket://__BUNDLE_IDENTIFIER__", "https://appassets.androidplatform.net/assets") - .replace("https://__BUNDLE_IDENTIFIER__", "https://appassets.androidplatform.net/assets") - - val icon = androidx.core.graphics.drawable.IconCompat.createWithContentUri(url) - builder.setSmallIcon(icon) - } else { - val icon = androidx.core.graphics.drawable.IconCompat.createWithResource( - activity, - R.mipmap.ic_launcher_round - ) - builder.setSmallIcon(icon) - } - - if (message.has("image")) { - val url = message.get("image") - .replace("socket://__BUNDLE_IDENTIFIER__", "https://appassets.androidplatform.net/assets") - .replace("https://__BUNDLE_IDENTIFIER__", "https://appassets.androidplatform.net/assets") - - val icon = android.graphics.drawable.Icon.createWithContentUri(url) - builder.setLargeIcon(icon) - } - - if (message.has("category")) { - var category = message.get("category") - .replace("msg", "message") - .replace("-", "_") - - builder.setCategory(category) - } - - if (message.get("silent") == "true") { - builder.setSilent(true) - } - - val notification = builder.build() - with (androidx.core.app.NotificationManagerCompat.from(activity)) { - notify( - message.get("tag"), - identifier, - notification - ) - } - - callback(Result(0, message.seq, message.name, """{ - "data": { - "id": "$id" - } - }""")) - - activity.runOnUiThread { - this.emit("notificationpresented", """{ - "id": "$id" - }""") - } - - return true - } - - "notification.close" -> { - if ( - !activity.checkPermission("android.permission.POST_NOTIFICATIONS") || - !androidx.core.app.NotificationManagerCompat.from(activity).areNotificationsEnabled() - ) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "User denied permissions for 'notifications'" } - }""")) - return true - } - - if (!message.has("id")) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "Expecting 'id' in parameters" } - }""")) - return true - } - - val id = message.get("id") - with (androidx.core.app.NotificationManagerCompat.from(activity)) { - cancel( - message.get("tag"), - id.toLongOrNull()?.toInt() ?: (0..16384).random().toInt() - ) - } - - callback(Result(0, message.seq, message.name, """{ - "data": { - "id": "$id" - } - }""")) - - activity.runOnUiThread { - this.emit("notificationresponse", """{ - "id": "$id", - "action": "dismiss" - }""") - } - - return true - } - - "notification.list" -> { - if ( - !activity.checkPermission("android.permission.POST_NOTIFICATIONS") || - !androidx.core.app.NotificationManagerCompat.from(activity).areNotificationsEnabled() - ) { - callback(Result(0, message.seq, message.name, """{ - "err": { "message": "User denied permissions for 'notifications'" } - }""")) - return true - } - - return true - } - - "buffer.map" -> { - if (bytes != null) { - buffers[message.seq] = bytes - } - callback(Result(0, message.seq, message.name, "{}")) - return true - } - - "log", "stdout" -> { - console.log(message.value) - return true - } - - "stderr" -> { - console.error(message.value) - return true - } - - "application.exit", "process.exit", "exit" -> { - val code = message.get("value", "0").toInt() - this.runtime.get()?.exit(code) - callback(Result(0, message.seq, message.name, "{}")) - return true - } - - "openExternal" -> { - this.runtime.get()?.openExternal(message.value) - callback(Result(0, message.seq, message.name, "{}")) - return true - } - - "fs.access" -> { - var mode = message.get("mode", "0").toInt() - val path = message.get("path") - val uri = android.net.Uri.parse(path) - if (isAssetBundleUri(uri) || isContentUri(uri) || isDocumentUri(activity, uri)) { - try { - val path = uri.path?.substring(1) - val stream = ( - if (path != null && isAssetBundleUri(uri)) assetManager.open(path, mode) - else contentResolver.openInputStream(uri) - ) - - if (stream == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Failed to open input stream for access check" - } - }""")) - return true; - } - - mode = 4 // R_OK - stream.close() - callback(Result(0, message.seq, message.name, """{ - "data": { - "mode": $mode - } - }""")) - } catch (e: java.io.FileNotFoundException) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "type": "NotFoundError", - "message": "${e.message}" - } - }""")) - } catch (e: Exception) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "${e.message}" - } - }""")) - } - - return true - } - } - - "fs.open" -> { - val path = message.get("path") - val uri = android.net.Uri.parse(path) - - if (isAssetBundleUri(uri) || isContentUri(uri) || isDocumentUri(activity, uri)) { - val id = message.get("id") - - if (id.length == 0) { - return false - } - - try { - val path = uri.path?.substring(1) - val fd = ( - if (path != null && isAssetBundleUri(uri)) assetManager.openFd(path) - else contentResolver.openTypedAssetFileDescriptor( - uri, - "*/*", - null - ) - ) - - if (fd == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Failed to open asset file descriptor" - } - }""")) - return true; - } - - this.fileDescriptors[id] = fd - this.uris[id] = uri - - callback(Result(0, message.seq, message.name, """{ - "data": { - "id": "$id", - "fd": ${fd.getParcelFileDescriptor().getFd()} - } - }""")) - } catch (e: java.io.FileNotFoundException) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "type": "NotFoundError", - "message": "${e.message}" - } - }""")) - } catch (e: Exception) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "${e.message}" - } - }""")) - } - - return true - } - } - - "fs.opendir" -> { - val path = message.get("path") - val uri = android.net.Uri.parse(path) - val id = message.get("id") - - if (isAssetBundleUri(uri)) { - var path = uri.path - if (path == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Missing pathspec in 'path'" - } - }""")) - } else { - if (path.length > 0) { - while (path != null && path.startsWith("/")) { - path = path.substring(1) - } - - if (path == null) { - path = "" - } - - if (path.length > 0 && !path.endsWith("/")) { - path += "/" - } - } - - val entries = assetManager.list(path) - if (entries == null || entries.size == 0) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "type": "NotFoundError", - "message": "Directory not found in asset manager" - } - }""")) - } else { - this.openedAssetDirectoriesEntryCache[id] = entries - this.openedAssetDirectories[id] = path - callback(Result(0, message.seq, message.name, """{ - "data": { - "id": "$id" - } - }""")) - } - } - - return true - } - } - - "fs.close" -> { - val id = message.get("id") - if (this.fileDescriptors.contains(id)) { - try { - val fd = this.fileDescriptors[id] - this.fileDescriptors.remove(id) - this.uris.remove(id) - - if (fd != null) { - fd.close() - } - - callback(Result(0, message.seq, message.name, """{ - "data": { - "id": "$id" - } - }""")) - } catch (e: Exception) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "${e.message}" - } - }""")) - } - - return true - } - } - - "fs.closedir" -> { - val id = message.get("id") - if (this.openedAssetDirectories.contains(id)) { - this.openedAssetDirectories.remove(id) - this.openedAssetDirectoriesEntryCache.remove(id) - callback(Result(0, message.seq, message.name, """{ - "data": { - "id": "$id" - } - }""")) - return true - } - } - - "fs.read" -> { - val id = message.get("id") - val size = message.get("size", "0").toInt() - val offset = message.get("offset", "0").toLong() - if (this.fileDescriptors.contains(id)) { - try { - val uri = this.uris[id] - - if (uri == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Could not determine URI for open asset file descriptor" - } - }""")) - return true - } - - val path = uri.path?.substring(1) - val stream = ( - if (path != null && isAssetBundleUri(uri)) assetManager.open(path, 2) - else contentResolver.openInputStream(uri) - ) - - if (stream == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Failed to open input stream for file read" - } - }""")) - return true; - } - - val bytes = ByteArray(size) - - if (offset > 0) { - stream.skip(offset) - } - - val bytesRead = stream.read(bytes, 0, size) - stream.close() - if (bytesRead > 0) { - callback(Result(0, message.seq, message.name, "{}", bytes.slice(0..(bytesRead - 1)).toByteArray())) - } else { - callback(Result(0, message.seq, message.name, "{}", ByteArray(0))) - } - } catch (e: Exception) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "${e.message}" - } - }""")) - } - - return true - } - } - - "fs.readdir" -> { - val id = message.get("id") - val max = message.get("entries", "8").toInt() - if (this.openedAssetDirectories.contains(id)) { - val path = this.openedAssetDirectories[id] - if (path != null) { - var entries = this.openedAssetDirectoriesEntryCache[id] - if (entries != null) { - var data: Array<String> = arrayOf() - var count = 0 - for (entry in entries) { - var type = 1 - - try { - val tmp = assetManager.list(path + entry) - if (entry.endsWith("/") || (tmp != null && tmp.size > 0)) { - type = 2 - } - } catch (e: Exception) {} - - data += """{ "name": "$entry", "type": $type }""" - - if (++count == max) { - break - } - } - - entries = entries.slice(count..(entries.size - 1)).toTypedArray() - this.openedAssetDirectoriesEntryCache[id] = entries - - callback(Result(0, message.seq, message.name, """{ - "data": ${data.joinToString(prefix = "[", postfix = "]")} - }""")) - return true; - } - } - callback(Result(0, message.seq, message.name, """{ "data": [] }""")) - return true; - } - } - - "fs.readFile" -> { - val path = message.get("path") - val uri = android.net.Uri.parse(path) - if (isAssetBundleUri(uri) || isContentUri(uri) || isDocumentUri(activity, uri)) { - try { - val path = uri.path?.substring(1) - val stream = ( - if (path != null && isAssetBundleUri(uri)) assetManager.open(path, 2) // ACCESS_STREAMING - else contentResolver.openInputStream(uri) - ) - - if (stream == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Failed to open input stream for file read" - } - }""")) - return true; - } - - val bytes = stream.readAllBytes() - stream.close() - callback(Result(0, message.seq, message.name, "{}", bytes)) - } catch (e: java.io.FileNotFoundException) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "type": "NotFoundError", - "message": "${e.message}" - } - }""")) - } catch (e: Exception) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "${e.message}" - } - }""")) - } - return true - } - } - - "fs.stat" -> { - val path = message.get("path") - val uri = android.net.Uri.parse(path) - if (isAssetBundleUri(uri) || isContentUri(uri) || isDocumentUri(activity, uri)) { - val path = uri.path?.substring(1) - val fd = ( - if (path != null && isAssetBundleUri(uri)) assetManager.openFd(path) - else contentResolver.openTypedAssetFileDescriptor( - uri, - "*/*", - null - ) - ) - - if (fd == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Failed to open asset file descriptor for stats" - } - }""")) - return true - } - - callback(Result(0, message.seq, message.name, """{ - "data": { - "st_mode": 4, - "st_size": ${fd.getParcelFileDescriptor().getStatSize()} - } - }""")) - - fd.close() - return true - } - } - - "fs.fstat" -> { - val path = message.get("path") - val id = message.get("id") - if (this.fileDescriptors.contains(id)) { - val fd = this.fileDescriptors[id] - - if (fd == null) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "message": "Failed to acquire open asset file descriptor for stats" - } - }""")) - return true - } - - callback(Result(0, message.seq, message.name, """{ - "data": { - "st_mode": 4, - "st_size": ${fd.getParcelFileDescriptor().getStatSize()} - } - }""")) - return true - } - } - } - - if (message.domain == "fs") { - if (message.has("id") || message.has("path")) { - val path = message.get("path") - val uri = android.net.Uri.parse(path) - val id = message.get("id") - if (isAssetBundleUri(uri) || isContentUri(uri) || isDocumentUri(activity, uri) || this.fileDescriptors.contains(id)) { - callback(Result(0, message.seq, message.name, """{ - "err": { - "type": "NotFoundError", - "message": "'${message.name}' is not supported for Android content URIs" - } - }""")) - return true - } - } - - val root = java.nio.file.Paths.get(configuration.getRootDirectory()) - if (message.has("path")) { - var path = message.get("path") - message.set("path", root.resolve(java.nio.file.Paths.get(path)).toString()) - } - - if (message.has("src")) { - var src = message.get("src") - message.set("src", root.resolve(java.nio.file.Paths.get(src)).toString()) - } - - if (message.has("dest")) { - var dest = message.get("dest") - message.set("dest", root.resolve(java.nio.file.Paths.get(dest)).toString()) - } - - if (message.has("dst")) { - var dest = message.get("dst") - message.set("dst", root.resolve(java.nio.file.Paths.get(dest)).toString()) - } - } - - val request = RouteRequest(this.nextRequestId++, callback) - this.requests[request.id] = request - - val routed = this.route(message.toString(), message.bytes, request.id) - - if (!routed && this.requests.contains(request.id)) { - this.requests.remove(request.id) - } - - return routed - } - - fun getAllowedNodeCoreModules (): List<String> { - return this.getAllowedNodeCoreModulesList().split(",") - } - - fun onInternalRouteResponse ( - id: Long, - seq: String, - source: String, - value: String? = null, - headersString: String? = null, - bytes: ByteArray? = null - ) { - val headers = try { - headersString - ?.split("\n") - ?.map { it.split(":", limit=2) } - ?.map { it.elementAt(0) to it.elementAt(1) } - ?.toMap() - } catch (err: Exception) { - null - } - - val result = Result(id, seq, source, value ?: "", bytes, headers ?: emptyMap<String, String>()) - - this.onResult(result) - } - - open fun onResult (result: Result) { - val semaphore = this.semaphore - val activity = this.runtime.get()?.activity?.get() - - this.requests[result.id]?.apply { - kotlin.concurrent.thread { - semaphore.acquireUninterruptibly() - - if (activity != null) { - activity.runOnUiThread { - semaphore.release() - } - } - - callback(result) - - if (activity == null) { - semaphore.release() - } - } - } - - if (this.requests.contains(result.id)) { - this.requests.remove(result.id) - } - } - - @Throws(java.lang.Exception::class) - external fun alloc (runtimePointer: Long): Long; - - @Throws(java.lang.Exception::class) - external fun dealloc (): Boolean; - - @Throws(java.lang.Exception::class) - external fun route (msg: String, bytes: ByteArray?, requestId: Long): Boolean; - - @Throws(java.lang.Exception::class) - external fun emit (event: String, data: String = ""): Boolean; - - @Throws(java.lang.Exception::class) - external fun getAllowedNodeCoreModulesList (): String; -} diff --git a/src/android/internal.hh b/src/android/internal.hh deleted file mode 100644 index 88e5e18751..0000000000 --- a/src/android/internal.hh +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef SOCKET_RUNTIME_ANDROID_INTERNAL_H -#define SOCKET_RUNTIME_ANDROID_INTERNAL_H - -#include "../core/core.hh" -#include "../ipc/ipc.hh" -#include "../window/options.hh" - -/** - * Defined by the Socket preprocessor - */ -#define PACKAGE_NAME __BUNDLE_IDENTIFIER__ - -/** - * Creates a named native package export for the configured bundle suitable for - * for definition only. - * @param name The name of the package function to export - */ -#define external(namespace, name) \ - JNIEXPORT JNICALL Java___BUNDLE_IDENTIFIER___##namespace##_##name - - -/** - * Generic `Exception` throw helper - */ -#define Throw(env, E) \ - ({ \ - env->ThrowNew(GetExceptionClassFromEnvironment(env), E); \ - (void) 0; \ - }) - -/** - * Translate a libuv error to a message suitable for `Throw(...)` - */ -#define UVException(code) uv_strerror(code) - -/** - * Errors thrown from the JNI/NDK bindings - */ -#define AssetManagerIsNotReachableException \ - "AssetManager is not reachable through binding" -#define ExceptionCheckException "ExceptionCheck" -#define JavaScriptPreloadSourceNotInitializedException \ - "JavaScript preload source is not initialized" -#define BridgeNotInitializedException "Bridge is not initialized" -#define CoreJavaVMNotInitializedException "Core JavaVM is not initialized" -#define CoreNotInitializedException "Core is not initialized" -#define CoreRefsNotInitializedException "Core refs are not initialized" -#define RuntimeNotInitializedException "Runtime is not initialized" -#define RootDirectoryIsNotReachableException \ - "Root directory in file system is not reachable through binding" -#define UVLoopNotInitializedException "UVLoop is not initialized" -#define WindowNotInitializedException "Window is not initialized" - -namespace SSC::android { - - class Runtime : public Core { - public: - static auto from (JNIEnv* env, jobject self) { - auto pointer = GetObjectClassFieldFromEnvironment(env, self, Long, "pointer", "J"); - return reinterpret_cast<Runtime*>(pointer); - } - - static auto from (jlong pointer) { - return reinterpret_cast<Runtime*>(pointer); - } - - JNIEnv *env = nullptr; - jobject self = nullptr; - jlong pointer = 0; - String rootDirectory = ""; - bool isEmulator = false; - - Runtime (JNIEnv* env, jobject self, String rootDirectory); - ~Runtime (); - bool isPermissionAllowed (const String&) const; - }; - - class Bridge : public IPC::Bridge { - public: - static auto from (JNIEnv* env, jobject self) { - auto pointer = GetObjectClassFieldFromEnvironment(env, self, Long, "pointer", "J"); - return reinterpret_cast<Bridge*>(pointer); - } - - static auto from (jlong pointer) { - return reinterpret_cast<Bridge*>(pointer); - } - - JNIEnv *env = nullptr; - jobject self = nullptr; - jlong pointer = 0; - Runtime* runtime = nullptr; - - Bridge (JNIEnv* env, jobject self, Runtime* runtime); - ~Bridge (); - }; - - class Window { - public: - static auto from (JNIEnv* env, jobject self) { - auto pointer = GetObjectClassFieldFromEnvironment(env, self, Long, "pointer", "J"); - return reinterpret_cast<Window*>(pointer); - } - - static auto from (jlong pointer) { - return reinterpret_cast<Window*>(pointer); - } - - JNIEnv* env = nullptr; - jobject self = nullptr; - jlong pointer = 0; - Bridge* bridge = nullptr; - Map config; - String preloadSource; - WindowOptions options; - Map envvars; - - Window ( - JNIEnv* env, - jobject self, - Bridge* bridge, - WindowOptions options - ); - - ~Window (); - - void evaluateJavaScript (String source, JVMEnvironment& jvm); - }; -} - -#endif diff --git a/src/android/main.kt.bak b/src/android/main.kt.bak deleted file mode 100644 index 92c3691a1c..0000000000 --- a/src/android/main.kt.bak +++ /dev/null @@ -1,308 +0,0 @@ -package __BUNDLE_IDENTIFIER__ - -object console { - val TAG = "Console" - fun log (string: String) { - android.util.Log.i(TAG, string) - } - - fun info (string: String) { - android.util.Log.i(TAG, string) - } - - fun debug (string: String) { - android.util.Log.d(TAG, string) - } - - fun error (string: String) { - android.util.Log.e(TAG, string) - } -} - -class PermissionRequest (callback: (Boolean) -> Unit) { - val id: Int = (0..16384).random().toInt() - val callback = callback -} - -/** - * An entry point for the main activity specified in - * `AndroidManifest.xml` and which can be overloaded in `socket.ini` for - * advanced usage. - - * Main `android.app.Activity` class for the `WebViewClient`. - * @see https://developer.android.com/reference/kotlin/android/app/Activity - */ -open class MainActivity : WebViewActivity() { - override open protected val TAG = "Mainctivity" - open lateinit var notificationChannel: android.app.NotificationChannel - override open lateinit var runtime: Runtime - override open lateinit var window: Window - - val permissionRequests = mutableListOf<PermissionRequest>() - val filePicker = WebViewFilePicker(this) - - companion object { - init { - System.loadLibrary("socket-runtime") - } - } - - fun checkPermission (permission: String): Boolean { - val status = androidx.core.content.ContextCompat.checkSelfPermission( - this.applicationContext, - permission - ) - - if (status == android.content.pm.PackageManager.PERMISSION_GRANTED) { - return true - } - - return false - } - - fun requestPermissions (permissions: Array<String>, callback: (Boolean) -> Unit) { - val request = PermissionRequest(callback) - this.permissionRequests.add(request) - androidx.core.app.ActivityCompat.requestPermissions( - this, - permissions, - request.id - ) - } - - fun showFileSystemPicker ( - options: WebViewFilePickerOptions, - callback: (Array<android.net.Uri>) -> Unit - ) : Boolean { - val filePicker = this.filePicker - this.runOnUiThread { - filePicker.launch(options, callback) - } - return true - } - - override fun onCreate (state: android.os.Bundle?) { - // called before `super.onCreate()` - this.supportActionBar?.hide() - this.getWindow()?.statusBarColor = android.graphics.Color.TRANSPARENT - - super.onCreate(state) - - this.notificationChannel = android.app.NotificationChannel( - "__BUNDLE_IDENTIFIER__", - "__BUNDLE_IDENTIFIER__ Notifications", - android.app.NotificationManager.IMPORTANCE_DEFAULT - ) - - this.runtime = Runtime(this, RuntimeConfiguration( - assetManager = this.applicationContext.resources.assets, - rootDirectory = this.getRootDirectory(), - - exit = { code -> - console.log("__EXIT_SIGNAL__=${code}") - this.finishAndRemoveTask() - }, - - openExternal = { value -> - val uri = android.net.Uri.parse(value) - val action = android.content.Intent.ACTION_VIEW - val intent = android.content.Intent(action, uri) - this.startActivity(intent) - } - )) - - this.runtime.setIsEmulator( - ( - android.os.Build.BRAND.startsWith("generic") && - android.os.Build.DEVICE.startsWith("generic") - ) || - android.os.Build.FINGERPRINT.startsWith("generic") || - android.os.Build.FINGERPRINT.startsWith("unknown") || - android.os.Build.HARDWARE.contains("goldfish") || - android.os.Build.HARDWARE.contains("ranchu") || - android.os.Build.MODEL.contains("google_sdk") || - android.os.Build.MODEL.contains("Emulator") || - android.os.Build.MODEL.contains("Android SDK built for x86") || - android.os.Build.MANUFACTURER.contains("Genymotion") || - android.os.Build.PRODUCT.contains("sdk_google") || - android.os.Build.PRODUCT.contains("google_sdk") || - android.os.Build.PRODUCT.contains("sdk") || - android.os.Build.PRODUCT.contains("sdk_x86") || - android.os.Build.PRODUCT.contains("sdk_gphone64_arm64") || - android.os.Build.PRODUCT.contains("vbox86p") || - android.os.Build.PRODUCT.contains("emulator") || - android.os.Build.PRODUCT.contains("simulator") - ) - - this.window = Window(this.runtime, this) - - this.window.load() - this.runtime.start() - - if (this.runtime.isPermissionAllowed("notifications")) { - val notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as android.app.NotificationManager - notificationManager.createNotificationChannel(this.notificationChannel) - } - } - - override fun onStart () { - super.onStart() - - val window = this.window - val action: String? = this.intent?.action - val data: android.net.Uri? = this.intent?.data - - if (action != null && data != null) { - this.onNewIntent(this.intent) - } - } - - override fun onResume () { - this.runtime.start() - return super.onResume() - } - - override fun onPause () { - this.runtime.stop() - return super.onPause() - } - - override fun onStop () { - this.runtime.stop() - return super.onStop() - } - - override fun onDestroy () { - this.runtime.destroy() - return super.onDestroy() - } - - override fun onNewIntent (intent: android.content.Intent) { - super.onNewIntent(intent) - val window = this.window - val action = intent.action - val data = intent.data - val id = intent.extras?.getCharSequence("id")?.toString() - - if (action == null) { - return - } - - when (action) { - "android.intent.action.MAIN", - "android.intent.action.VIEW" -> { - val scheme = data?.scheme ?: return - val applicationProtocol = this.runtime.getConfigValue("meta_application_protocol") - if ( - applicationProtocol.length > 0 && - scheme.startsWith(applicationProtocol) - ) { - window.bridge.emit("applicationurl", """{ - "url": "$data" - }""") - } - } - - "notification.response.default" -> { - window.bridge.emit("notificationresponse", """{ - "id": "$id", - "action": "default" - }""") - } - - "notification.response.dismiss" -> { - window.bridge.emit("notificationresponse", """{ - "id": "$id", - "action": "dismiss" - }""") - } - } - } - - override fun onActivityResult ( - requestCode: Int, - resultCode: Int, - intent: android.content.Intent? - ) { - super.onActivityResult(requestCode, resultCode, intent) - } - - override fun onPageStarted ( - view: android.webkit.WebView, - url: String, - bitmap: android.graphics.Bitmap? - ) { - super.onPageStarted(view, url, bitmap) - this.window.onPageStarted(view, url, bitmap) - } - - override fun onPageFinished ( - view: android.webkit.WebView, - url: String - ) { - super.onPageFinished(view, url) - this.window.onPageFinished(view, url) - } - - override fun onSchemeRequest ( - request: android.webkit.WebResourceRequest, - response: android.webkit.WebResourceResponse, - stream: java.io.PipedOutputStream - ): Boolean { - return this.window.onSchemeRequest(request, response, stream) - } - - override fun onRequestPermissionsResult ( - requestCode: Int, - permissions: Array<String>, - grantResults: IntArray - ) { - for (request in this.permissionRequests) { - if (request.id == requestCode) { - this.permissionRequests.remove(request) - request.callback(grantResults.all { r -> - r == android.content.pm.PackageManager.PERMISSION_GRANTED - }) - break - } - } - - var i = 0 - val seen = mutableSetOf<String>() - for (permission in permissions) { - val granted = ( - grantResults[i++] == android.content.pm.PackageManager.PERMISSION_GRANTED - ) - - var name = "" - when (permission) { - "android.permission.ACCESS_COARSE_LOCATION", - "android.permission.ACCESS_FINE_LOCATION" -> { - name = "geolocation" - } - - "android.permission.POST_NOTIFICATIONS" -> { - name = "notifications" - } - } - - if (seen.contains(name)) { - continue - } - - if (name.length == 0) { - continue - } - - seen.add(name) - - this.runOnUiThread { - val state = if (granted) "granted" else "denied" - window.bridge.emit("permissionchange", """{ - "name": "$name", - "state": "$state" - }""") - } - } - } -} diff --git a/src/android/platform.hh b/src/android/platform.hh deleted file mode 100644 index f91902f429..0000000000 --- a/src/android/platform.hh +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef SOCKET_RUNTIME_ANDROID_PLATFORM_H -#define SOCKET_RUNTIME_ANDROID_PLATFORM_H - -#include "../platform/platform.hh" - -namespace SSC::Android { - class WebResourceRequest; - class WebResourceResponse; -} - -#endif diff --git a/src/android/runtime.cc b/src/android/runtime.cc deleted file mode 100644 index 37eed07d98..0000000000 --- a/src/android/runtime.cc +++ /dev/null @@ -1,311 +0,0 @@ -#include "internal.hh" - -using namespace SSC::android; - -static auto onInternalServiceWorkerFetchResponseSignature = - "(" - "J" // requestId - "I" // statusCode - "Ljava/lang/String;" // headers - "[B" // bytes - ")V"; - -namespace SSC::android { - Runtime::Runtime (JNIEnv* env, jobject self, String rootDirectory) - : SSC::Core() - { - this->env = env; - this->self = env->NewGlobalRef(self); - this->pointer = reinterpret_cast<jlong>(this); - this->rootDirectory = rootDirectory; - } - - bool Runtime::isPermissionAllowed (const String& name) const { - static const auto config = SSC::getUserConfig(); - const auto permission = String("permissions_allow_") + replace(name, "-", "_"); - - // `true` by default - if (!config.contains(permission)) { - return true; - } - - return config.at(permission) != "false"; - } - - Runtime::~Runtime () { - this->env->DeleteGlobalRef(this->self); - } -} - -extern "C" { - jlong external(Runtime, alloc)( - JNIEnv *env, - jobject self, - jstring rootDirectory - ) { - auto runtime = new Runtime(env, self, StringWrap(env, rootDirectory).str()); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return 0; - } - - return runtime->pointer; - } - - jboolean external(Runtime, dealloc)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - delete runtime; - return true; - } - - jboolean external(Runtime, isDebugEnabled) ( - JNIEnv* env, - jobject self - ) { - return SSC::isDebugEnabled(); - } - - jboolean external(Runtime, startEventLoop)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->runEventLoop(); - return true; - } - - jboolean external(Runtime, stopEventLoop)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->stopEventLoop(); - return true; - } - - jboolean external(Runtime, startTimers)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->startTimers(); - return true; - } - - jboolean external(Runtime, stopTimers)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->stopTimers(); - return true; - } - - jboolean external(Runtime, pause)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->pauseAllPeers(); - runtime->stopTimers(); - runtime->stopEventLoop(); - - return true; - } - - jboolean external(Runtime, resume)( - JNIEnv *env, - jobject self - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->runEventLoop(); - runtime->resumeAllPeers(); - - return true; - } - - jboolean external(Runtime, isPermissionAllowed)( - JNIEnv *env, - jobject self, - jstring permission - ) { - auto runtime = Runtime::from(env, self); - auto name = StringWrap(env, permission).str(); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - return runtime->isPermissionAllowed(name); - } - - jboolean external(Runtime, setIsEmulator)( - JNIEnv *env, - jobject self, - jboolean value - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - runtime->isEmulator = value; - return true; - } - - jstring external(Runtime, getConfigValue)( - JNIEnv *env, - jobject self, - jstring keyString - ) { - static auto config = SSC::getUserConfig(); - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return nullptr; - } - - auto key = StringWrap(env, keyString).str(); - auto value = config[key]; - return env->NewStringUTF(value.c_str()); - } - - jboolean external(Runtime, serviceWorkerContainerFetch)( - JNIEnv *env, - jobject self, - jlong requestId, - jstring pathnameString, - jstring methodString, - jstring queryString, - jstring headersString, - jbyteArray byteArray, - ) { - auto runtime = Runtime::from(env, self); - - if (runtime == nullptr) { - Throw(env, RuntimeNotInitializedException); - return false; - } - - if (runtime->serviceWorker.registrations.size() == 0) { - return false; - } - - JavaVM* jvm = nullptr; - auto jniVersion = env->GetVersion(); - env->GetJavaVM(&jvm); - auto attachment = JNIEnvironmentAttachment { jvm, jniVersion }; - - if (attachment.hasException()) { - return false; - } - - auto request = SSC::ServiceWorkerContainer::FetchRequest { - StringWrap(env, pathnameString).str(), - StringWrap(env, methodString).str() - }; - - auto size = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0; - auto input = size > 0 ? new char[size]{0} : nullptr; - - if (size > 0 && input != nullptr) { - env->GetByteArrayRegion(byteArray, 0, size, (jbyte*) input); - } - - request.buffer.bytes = input; - request.buffer.size = size; - request.query = StringWrap(env, queryString).str(); - request.headers = SSC::split(StringWrap(env, headersString).str(), '\n'); - - return runtime->serviceWorker.fetch(request, [=](auto response) mutable { - auto attachment = JNIEnvironmentAttachment { jvm, jniVersion }; - auto env = attachment.env; - - if (attachment.hasException()) { - return; - } - - const auto statusCode = response.statusCode; - const auto headers = env->NewStringUTF(SSC::join(response.headers, '\n').c_str()); - const auto bytes = response.buffer.bytes && response.buffer.size > 0 - ? env->NewByteArray(response.buffer.size) - : nullptr; - - if (bytes != nullptr) { - env->SetByteArrayRegion( - bytes, - 0, - response.buffer.size, - (jbyte *) response.buffer.bytes - ); - } - - CallVoidClassMethodFromEnvironment( - env, - runtime->self, - "onInternalServiceWorkerFetchResponse", - onInternalServiceWorkerFetchResponseSignature, - requestId, - statusCode, - headers, - bytes - ); - - env->DeleteLocalRef(headers); - - if (bytes != nullptr) { - env->DeleteLocalRef(bytes); - } - }); - } -} diff --git a/src/android/runtime.kt b/src/android/runtime.kt deleted file mode 100644 index 63a8e52c52..0000000000 --- a/src/android/runtime.kt +++ /dev/null @@ -1,349 +0,0 @@ -// vim: set sw=2: -package __BUNDLE_IDENTIFIER__ -import java.lang.ref.WeakReference - -interface IRuntimeConfiguration { - val rootDirectory: String - val assetManager: android.content.res.AssetManager - val exit: (Int) -> Unit - val openExternal: (String) -> Unit -} - -data class RuntimeConfiguration ( - override val rootDirectory: String, - override val assetManager: android.content.res.AssetManager, - override val exit: (Int) -> Unit, - override val openExternal: (String) -> Unit -) : IRuntimeConfiguration - -data class RuntimeServiceWorkerFetchResponse ( - val id: Long, - val statusCode: Int, - val headers: Map<String, String> = emptyMap(), - val bytes: ByteArray? = null -) - -typealias RuntimeServiceWorkerFetchRequestCallback = (RuntimeServiceWorkerFetchResponse) -> Unit - -data class RuntimeServiceWorkerFetchRequest ( - val id: Long, - val callback: RuntimeServiceWorkerFetchRequestCallback -) - -open class RuntimeServiceWorkerContainer ( - runtime: Runtime -) { - val runtime = runtime - val requests = mutableMapOf<Long, RuntimeServiceWorkerFetchRequest>() - protected var nextFetchRequestId = 0L - - fun fetch ( - request: android.webkit.WebResourceRequest - ): android.webkit.WebResourceResponse? { - val url = request.url - val method = request.method - val headers = request.requestHeaders ?: mutableMapOf<String, String>() - - val path = url.path ?: return null - val query = url.query ?: "" - - return this.fetch(method, path, query, headers) - } - - fun fetch ( - method: String, - path: String, - query: String, - requestHeaders: MutableMap<String, String>, - bytes: ByteArray? = null - ): android.webkit.WebResourceResponse? { - val activity = runtime.activity.get() ?: return null - var pathname = path - var headers = "" - - if (pathname.startsWith("/assets/")) { - pathname = pathname.replace("/assets/", "/") - } - - for (entry in requestHeaders.iterator()) { - headers += "${entry.key}: ${entry.value}\n" - } - - val requestId = this.nextFetchRequestId++ - - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/html", - "utf-8", - java.io.PipedInputStream(stream) - ) - - val fetchRequest = RuntimeServiceWorkerFetchRequest(requestId, fun (res: RuntimeServiceWorkerFetchResponse) { - var contentType = "" - - response.apply { - if (res.statusCode == 0) { - setStatusCodeAndReasonPhrase(502, "Network Error") - return stream.close() - } - - when (res.statusCode) { - 200 -> { setStatusCodeAndReasonPhrase(res.statusCode, "OK") } - 201 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Created") } - 202 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Accepted") } - 204 -> { setStatusCodeAndReasonPhrase(res.statusCode, "No Content") } - 205 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Reset Content") } - 206 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Partial Content") } - 300 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Multiple Choices") } - 301 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Moved Permanently") } - 302 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Found") } - 304 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Not Modified") } - 307 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Temporary Redirect") } - 308 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Permanent Redirect") } - 400 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Bad Request") } - 401 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Unauthorized") } - 402 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Payment Required") } - 403 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Forbidden") } - 404 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Not Found") } - 405 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Method Not Allowed") } - 406 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Not Acceptable") } - 408 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Request Timeout") } - 409 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Conflict") } - 410 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Gone") } - 411 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Length Required") } - 412 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Precondition Failed") } - 413 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Payload Too Large") } - 414 -> { setStatusCodeAndReasonPhrase(res.statusCode, "URI Too Long") } - 415 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Unsupported Media Type") } - 416 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Range Not Satisfiable") } - 417 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Expectation Failed") } - 421 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Misdirected Request") } - 422 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Unprocessable Content") } - 423 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Locked") } - 425 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Too Early") } - 426 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Upgrade Required") } - 428 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Precondition Required") } - 429 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Too Many Requests") } - 431 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Request Header Fields Too Large") } - 500 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Internal Server Error") } - 501 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Not Implemented") } - 502 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Bad Gateway") } - 503 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Service Unavailable") } - 504 -> { setStatusCodeAndReasonPhrase(res.statusCode, "Gateway Timeout") } - else -> { - setStatusCodeAndReasonPhrase(res.statusCode, "Unknown Status") - } - } - - setResponseHeaders(res.headers) - - for (entry in res.headers.iterator()) { - if (entry.key.lowercase() == "content-type") { - contentType = entry.value - break - } - } - - if (contentType.length > 0) { - setMimeType(contentType) - } - } - - stream.apply { - var bytes = res.bytes - if (bytes != null) { - if (contentType == "text/html" || pathname.endsWith(".html")) { - val preload = ( - """ - <meta name="runtime-frame-source" content="serviceworker" /> - ${activity.window.getJavaScriptPreloadSource()} - """ - ) - - var html = String(bytes) - - if (html.contains("<head>")) { - html = html.replace("<head>", """ - <head> - $preload - """) - } else if (html.contains("<body>")) { - html = html.replace("<body>", """ - $preload - <body> - """) - } else if (html.contains("<html>")){ - html = html.replace("<html>", """ - <html> - $preload - """) - } else { - html = preload + html - } - - bytes = html.toByteArray() - } - - try { - write(bytes, 0, bytes.size) - } catch (err: Exception) { - if (err.message != "Pipe closed") { - console.error("RuntimeServiceWorkerContainer.fetch(): ${err.toString()}") - } - } - } - - kotlin.concurrent.thread { - close() - } - } - }) - - val fetched = runtime.serviceWorkerContainerFetch( - requestId, - pathname, - method, - query, - headers, - bytes - ) - - if (!fetched) { - stream.close() - return null - } - - this.requests[requestId] = fetchRequest - - return response - } -} - -open class Runtime ( - activity: MainActivity, - configuration: RuntimeConfiguration -) { - var pointer = alloc(activity.getRootDirectory()) - var activity = WeakReference(activity) - val configuration = configuration; - val serviceWorker = RuntimeServiceWorkerContainer(this) - val semaphore = java.util.concurrent.Semaphore( - java.lang.Runtime.getRuntime().availableProcessors() - ) - - var isRunning = false - - fun finalize () { - if (this.pointer > 0) { - this.dealloc() - } - - this.pointer = 0 - } - - fun exit (code: Int) { - this.configuration.exit(code) - } - - fun openExternal (value: String) { - this.configuration.openExternal(value) - } - - fun start () { - if (!this.isRunning) { - this.resume() - this.isRunning = true - } - } - - fun stop () { - if (this.isRunning) { - this.pause() - this.isRunning = false - } - } - - fun destroy () { - this.stop() - this.finalize() - } - - fun onInternalServiceWorkerFetchResponse ( - id: Long, - statusCode: Int, - headersString: String? = null, - bytes: ByteArray? = null - ) { - val activity = this.activity.get() - val headers = try { - headersString - ?.split("\n") - ?.map { it.split(":", limit=2) } - ?.map { it.elementAt(0) to it.elementAt(1) } - ?.toMap() - } catch (err: Exception) { - null - } - - val response = RuntimeServiceWorkerFetchResponse( - id, - statusCode, - headers ?: emptyMap<String, String>(), - bytes - ) - - this.serviceWorker.requests[id]?.apply { - kotlin.concurrent.thread { - callback(response) - } - } - - if (this.serviceWorker.requests.contains(id)) { - this.serviceWorker.requests.remove(id) - } - } - - @Throws(java.lang.Exception::class) - external fun alloc (rootDirectory: String): Long; - - @Throws(java.lang.Exception::class) - external fun dealloc (): Boolean; - - external fun isDebugEnabled (): Boolean; - - @Throws(java.lang.Exception::class) - external fun pause (): Boolean; - - @Throws(java.lang.Exception::class) - external fun resume (): Boolean; - - @Throws(java.lang.Exception::class) - external fun startEventLoop (): Boolean; - - @Throws(java.lang.Exception::class) - external fun stopEventLoop (): Boolean; - - @Throws(java.lang.Exception::class) - external fun startTimers (): Boolean; - - @Throws(java.lang.Exception::class) - external fun stopTimers (): Boolean; - - @Throws(java.lang.Exception::class) - external fun isPermissionAllowed (permission: String): Boolean; - - @Throws(java.lang.Exception::class) - external fun setIsEmulator (value: Boolean): Boolean; - - @Throws(java.lang.Exception::class) - external fun getConfigValue (key: String): String; - - @Throws(java.lang.Exception::class) - external fun serviceWorkerContainerFetch ( - requestId: Long, - pathname: String, - method: String, - query: String, - headers: String - ): Boolean -} diff --git a/src/android/webview.kt b/src/android/webview.kt deleted file mode 100644 index 36dd5964d8..0000000000 --- a/src/android/webview.kt +++ /dev/null @@ -1,900 +0,0 @@ -// vim: set sw=2: -package __BUNDLE_IDENTIFIER__ -import java.lang.ref.WeakReference - -fun decodeURIComponent (string: String): String { - val normalized = string.replace("+", "%2B") - return java.net.URLDecoder.decode(normalized, "UTF-8").replace("%2B", "+") -} - -fun isAndroidAssetsUri (uri: android.net.Uri): Boolean { - val scheme = uri.scheme - val host = uri.host - // handle no path segments, not currently required but future proofing - val path = uri.pathSegments?.get(0) - - if (host == "appassets.androidplatform.net") { - return true - } - - if (scheme == "file" && host == "" && path == "android_asset") { - return true - } - - return false -} - -/** - * @see https://developer.android.com/reference/kotlin/android/webkit/WebView - */ -open class WebView (context: android.content.Context) : android.webkit.WebView(context) - -/** - */ -open class WebViewFilePickerOptions ( - params: android.webkit.WebChromeClient.FileChooserParams? = null, - mimeTypes: Array<String> = arrayOf<String>(), - multiple: Boolean = false, - files: Boolean = true, - directories: Boolean = false -) { - val params = params - val multiple = multiple - val files = files - val directories = directories - var mimeTypes = mimeTypes - - init { - if (params != null && params.acceptTypes.size > 0) { - this.mimeTypes += params.acceptTypes - } - } -} - -open class WebViewFilePicker ( - activity: MainActivity -) { - protected val activity = WeakReference(activity) - var callback: ((Array<android.net.Uri>) -> Unit)? = null - - val launcherForSingleItem = activity.registerForActivityResult( - androidx.activity.result.contract.ActivityResultContracts.GetContent(), - { uri -> this.handleCallback(uri) } - ) - - val launcherForMulitpleItems = activity.registerForActivityResult( - androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents(), - { uris -> this.handleCallback(uris) } - ) - - val launcherForSingleDocument = activity.registerForActivityResult( - androidx.activity.result.contract.ActivityResultContracts.OpenDocument(), - { uri -> this.handleCallback(uri) } - ) - - val launcherForMulitpleDocuments = activity.registerForActivityResult( - androidx.activity.result.contract.ActivityResultContracts.OpenMultipleDocuments(), - { uris -> this.handleCallback(uris) } - ) - - val launcherForSingleVisualMedia = activity.registerForActivityResult( - androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia(), - { uri -> this.handleCallback(uri) } - ) - - val launcherForMultipleVisualMedia = activity.registerForActivityResult( - androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia(), - { uris -> this.handleCallback(uris) } - ) - - fun handleCallback (uris: Array<android.net.Uri>) { - val callback = this.callback - this.callback = null - if (callback != null) { - callback(uris) - } - } - - fun handleCallback (uris: List<android.net.Uri>) { - return this.handleCallback(uris.toTypedArray()) - } - - fun handleCallback (uri: android.net.Uri?) { - return this.handleCallback( - if (uri != null) { arrayOf(uri) } - else { arrayOf<android.net.Uri>() } - ) - } - - fun cancel () { - this.handleCallback(null) - } - - fun launch ( - options: WebViewFilePickerOptions, - callback: (Array<android.net.Uri>) -> Unit - ) { - this.cancel() - - var mimeType: String = "*/*" - - if (options.mimeTypes.size > 0) { - mimeType = options.mimeTypes[0] - } - - if (mimeType.length == 0) { - mimeType = "*/*" - } - - this.callback = callback - - if (options.multiple) { - this.launcherForMulitpleItems.launch(mimeType) - } else { - this.launcherForSingleItem.launch(mimeType) - } - } -} - -/** - * @see https://developer.android.com/reference/kotlin/android/webkit/WebViewClient - */ -open class WebChromeClient (activity: MainActivity) : android.webkit.WebChromeClient() { - protected val activity = WeakReference(activity) - - override fun onGeolocationPermissionsShowPrompt ( - origin: String, - callback: android.webkit.GeolocationPermissions.Callback - ) { - val runtime = this.activity.get()?.runtime ?: return callback(origin, false, false) - val allowed = runtime.isPermissionAllowed("geolocation") - - callback(origin, allowed, allowed) - } - - override fun onPermissionRequest (request: android.webkit.PermissionRequest) { - val runtime = this.activity.get()?.runtime ?: return request.deny() - val resources = request.resources - var grants = mutableListOf<String>() - for (resource in resources) { - when (resource) { - android.webkit.PermissionRequest.RESOURCE_AUDIO_CAPTURE -> { - if (runtime.isPermissionAllowed("microphone") || runtime.isPermissionAllowed("user_media")) { - grants.add(android.webkit.PermissionRequest.RESOURCE_AUDIO_CAPTURE) - } - } - - android.webkit.PermissionRequest.RESOURCE_VIDEO_CAPTURE -> { - if (runtime.isPermissionAllowed("camera") || runtime.isPermissionAllowed("user_media")) { - grants.add(android.webkit.PermissionRequest.RESOURCE_VIDEO_CAPTURE) - } - } - - // auto grant EME - android.webkit.PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID -> { - grants.add(android.webkit.PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID) - } - } - } - - if (grants.size > 0) { - request.grant(grants.toTypedArray()) - } else { - request.deny() - } - } - - override fun onProgressChanged ( - webview: android.webkit.WebView, - progress: Int - ) { - val activity = this.activity.get() ?: return; - activity.window.onProgressChanged(webview, progress) - } - - override fun onShowFileChooser ( - webview: android.webkit.WebView, - callback: android.webkit.ValueCallback<Array<android.net.Uri>>, - params: android.webkit.WebChromeClient.FileChooserParams - ): Boolean { - val activity = this.activity.get() ?: return false; - - super.onShowFileChooser(webview, callback, params) - - val options = WebViewFilePickerOptions(params) - activity.showFileSystemPicker(options, fun (uris: Array<android.net.Uri>) { - callback.onReceiveValue(uris) - }) - return true - } -} - -/** - * A container for a resolved URL path laoded in the WebView. - */ -final class WebViewURLPathResolution (path: String, redirect: Boolean = false) { - val path = path - val redirect = redirect -} - -/** - * @see https://developer.android.com/reference/kotlin/android/webkit/WebViewClient - */ -open class WebViewClient (activity: WebViewActivity) : android.webkit.WebViewClient() { - protected val activity = WeakReference(activity) - open protected val TAG = "WebViewClient" - open protected var rootDirectory = "" - - val pendingResponseMap = mutableMapOf<String, android.webkit.WebResourceResponse>() - - fun putRootDirectory(rootDirectory: String) { - this.rootDirectory = rootDirectory - } - - open protected var assetLoader: androidx.webkit.WebViewAssetLoader = androidx.webkit.WebViewAssetLoader.Builder() - .addPathHandler( - "/assets/", - androidx.webkit.WebViewAssetLoader.AssetsPathHandler(activity) - ) - .build() - - /** - * Handles URL loading overrides for various URI schemes. - */ - override fun shouldOverrideUrlLoading ( - view: android.webkit.WebView, - request: android.webkit.WebResourceRequest - ): Boolean { - val activity = this.activity.get() ?: return false - val assetManager = activity.getAssetManager() - val runtime = activity.runtime - val url = request.url - - if (this.pendingResponseMap.contains(url.toString())) { - return false - } - - if (url.scheme == "http" || url.scheme == "https") { - if (url.host == "__BUNDLE_IDENTIFIER__") { - var path = resolveURLPathForWebView(url.path ?: "/index.html")?.path - ?: url.path - ?: "" - - if (path.startsWith("/assets/")) { - path = path.replace("/assets/", "") - } - - if (path.startsWith("/")) { - path = path.substring(1, path.length) - } else if (path.startsWith("./")) { - path = path.substring(2, path.length) - } - - var isAssetRequest = false - if (path.length > 0) { - try { - val stream = assetManager.open(path, 2) - isAssetRequest = true - stream.close() - } catch (_: Exception) {} - } - - if (isAssetRequest) { - return false - } - - if (request.isForMainFrame() && path.endsWith(".html")) { - val response = runtime.serviceWorker.fetch(request) - if (response != null) { - this.pendingResponseMap[url.toString()] = response - - kotlin.concurrent.thread { - val bytes = response.data.readAllBytes() - response.data = java.io.ByteArrayInputStream(bytes) - activity.runOnUiThread { - view.loadUrl(url.toString(), request.requestHeaders) - } - } - return true - } - } - } - - return false - } - - if (isAndroidAssetsUri(url)) { - return false - } - - if ( - url.scheme == "ipc" || - url.scheme == "node" || - url.scheme == "file" || - url.scheme == "socket" - ) { - return true - } - - val intent = android.content.Intent(android.content.Intent.ACTION_VIEW, url) - - try { - activity.startActivity(intent) - } catch (err: Exception) { - // @TODO(jwelre): handle this error gracefully - console.error(err.toString()) - return false - } - - return true - } - - fun resolveURLPathForWebView (input: String? = null): WebViewURLPathResolution? { - var path = input ?: return null - val activity = this.activity.get() ?: return null - val assetManager = activity.getAssetManager() - - if (path == "/") { - try { - val htmlPath = "index.html" - val stream = assetManager.open(htmlPath) - stream.close() - return WebViewURLPathResolution("/" + htmlPath) - } catch (_: Exception) {} - } - - if (path.startsWith("/")) { - path = path.substring(1, path.length) - } else if (path.startsWith("./")) { - path = path.substring(2, path.length) - } - - try { - val htmlPath = path - val stream = assetManager.open(htmlPath) - stream.close() - return WebViewURLPathResolution("/" + htmlPath) - } catch (_: Exception) {} - - if (path.endsWith("/")) { - try { - val list = assetManager.list(path) - if (list != null && list.size > 0) { - try { - val htmlPath = path + "index.html" - val stream = assetManager.open(htmlPath) - stream.close() - return WebViewURLPathResolution("/" + htmlPath) - } catch (_: Exception) {} - } - } catch (_: Exception) {} - - return null - } else { - try { - val htmlPath = path + "/index.html" - val stream = assetManager.open(htmlPath) - stream.close() - return WebViewURLPathResolution("/" + path + "/", true) - } catch (_: Exception) {} - } - - try { - val htmlPath = path + ".html" - val stream = assetManager.open(htmlPath) - stream.close() - return WebViewURLPathResolution("/" + htmlPath) - } catch (_: Exception) {} - - return null - } - - override fun shouldInterceptRequest ( - view: android.webkit.WebView, - request: android.webkit.WebResourceRequest - ): android.webkit.WebResourceResponse? { - val activity = this.activity.get() ?: return null - val runtime = activity.runtime - var url = request.url - - // should be set in window loader - assert(rootDirectory.length > 0) - - if (this.pendingResponseMap.contains(url.toString())) { - val response = this.pendingResponseMap[url.toString()] - this.pendingResponseMap.remove(url.toString()) - return response - } - - if ( - (url.scheme == "socket" && url.host == "__BUNDLE_IDENTIFIER__") || - (url.scheme == "https" && url.host == "__BUNDLE_IDENTIFIER__") - ) { - var path = url.path - var redirect = false - val resolved = resolveURLPathForWebView(path) - - if (resolved != null) { - path = resolved.path - } - - if (resolved != null && resolved.redirect) { - redirect = true - } - - if (path == null) { - return null - } - - if (redirect && resolved != null) { - val redirectURL = "${url.scheme}://${url.host}${resolved.path}" - val redirectSource = """ - <meta http-equiv="refresh" content="0; url='${resolved.path}'" /> - """ - - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/html", - "utf-8", - java.io.PipedInputStream(stream) - ) - - response.responseHeaders = mapOf( - "Location" to redirectURL, - "Content-Location" to resolved.path - ) - - response.setStatusCodeAndReasonPhrase(200, "OK") - // prevent piped streams blocking each other, have to write on a separate thread if data > 1024 bytes - kotlin.concurrent.thread { - stream.write(redirectSource.toByteArray(), 0, redirectSource.length) - stream.close() - } - - return response - } - - if (path.startsWith("/")) { - path = path.substring(1, path.length) - } - - url = android.net.Uri.Builder() - .scheme("https") - .authority("appassets.androidplatform.net") - .path("/assets/${path}") - .build() - } - - // look for updated resources in ${pwd}/files - // live update systems can write to /files (/assets is read only) - if (url.host == "appassets.androidplatform.net" && url.pathSegments.get(0) == "assets") { - var first = true - val filePath = StringBuilder(rootDirectory) - for (item in url.pathSegments) { - if (!first) { - filePath.append("/${item}") - } - first = false - } - - val file = java.io.File(filePath.toString()) - if (file.exists() && !file.isDirectory()) { - val response = android.webkit.WebResourceResponse( - if (filePath.toString().endsWith(".js")) "text/javascript" else "text/html", - "utf-8", - java.io.FileInputStream(file) - ) - - val webviewHeaders = runtime.getConfigValue("webview_headers").split("\n") - val headers = mutableMapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - - for (line in webviewHeaders) { - val parts = line.split(":", limit = 2) - if (parts.size == 2) { - val key = parts[0].trim() - val value = parts[1].trim() - headers[key] = value - } - } - - response.responseHeaders = headers.toMap() - - return response - } else { - // default to normal asset loader behaviour - } - } - - if (url.scheme == "socket" || url.scheme == "node") { - if (url.scheme == "node") { - val allowedNodeCoreModules = activity.window.bridge.getAllowedNodeCoreModules() - val path = url - .toString() - .replace("node:", "") - .replace(".js", "") - - if (!allowedNodeCoreModules.contains(path)) { - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/javascript", - "utf-8", - java.io.PipedInputStream(stream) - ) - - response.responseHeaders = mapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - response.setStatusCodeAndReasonPhrase(404, "Not found") - stream.close() - return response - } - } - - var path = url - .toString() - .replace("socket:", "") - .replace("node:", "") - - val parts = path.split("?") - path = parts[0].trim() - - if (!path.endsWith(".js")) { - path += ".js" - } - - url = android.net.Uri.Builder() - .scheme("socket") - .authority("__BUNDLE_IDENTIFIER__") - .path("/socket/${path}") - .query(if (parts.size > 1) { parts[1] } else { "" }) - .build() - - val moduleTemplate = """ -import module from '$url' -export * from '$url' -export default module - """ - - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/javascript", - "utf-8", - java.io.PipedInputStream(stream) - ) - - val webviewHeaders = runtime.getConfigValue("webview_headers").split("\n") - val headers = mutableMapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - - for (line in webviewHeaders) { - val parts = line.split(":", limit = 2) - if (parts.size == 2) { - val key = parts[0].trim() - val value = parts[1].trim() - headers[key] = value - } - } - - response.responseHeaders = headers.toMap() - - // prevent piped streams blocking each other, - // have to write on a separate thread if data > 1024 bytes - kotlin.concurrent.thread { - stream.write(moduleTemplate.toByteArray(), 0, moduleTemplate.length) - stream.close() - } - - return response - } - - val assetManager = activity.getAssetManager() - var path = url.path - if (path != null && path.endsWith(".html") == true) { - val assetPath = path.replace("/assets/", "") - val assetStream = try { - assetManager.open(assetPath, 2) - } catch (_: Exception) { - null - } - - if (assetStream != null) { - var html = String(assetStream.readAllBytes()) - var preload = activity.window.getJavaScriptPreloadSource() - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/html", - "utf-8", - java.io.PipedInputStream(stream) - ) - - val webviewHeaders = runtime.getConfigValue("webview_headers").split("\n") - var webviewImportMapFilename = runtime.getConfigValue("webview_importmap") - - val headers = mutableMapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - - for (line in webviewHeaders) { - val parts = line.split(":", limit = 2) - if (parts.size == 2) { - val key = parts[0].trim() - val value = parts[1].trim() - headers[key] = value - } - } - - response.responseHeaders = headers.toMap() - - if (webviewImportMapFilename.length > 0) { - val webviewImportMapFile = try { - assetManager.open(webviewImportMapFilename, 2) - } catch (_: Exception) { - null - } - - if (webviewImportMapFile != null) { - var importmap = String(webviewImportMapFile.readAllBytes()) - if (importmap.length > 0) { - preload = ( - """ - <script type="importmap"> - $importmap - </script> - $preload - """ - ).trim() - } - } - } - - if (html.contains("<head>")) { - html = html.replace("<head>", """ - <head> - $preload - """) - } else if (html.contains("<body>")) { - html = html.replace("<body>", """ - $preload - <body> - """) - } else if (html.contains("<html>")){ - html = html.replace("<html>", """ - <html> - $preload - """) - } else { - html = preload + html - } - - kotlin.concurrent.thread { - stream.write(html.toByteArray(), 0, html.length) - stream.close() - } - - return response - } - } - - var assetLoaderResponse: android.webkit.WebResourceResponse? = null - if (path != null) { - try { - val assetManager = activity.getAssetManager() - val assetPath = path.replace("/assets/", "") - val stream = assetManager.open(assetPath, 2) - stream.close() - assetLoaderResponse = this.assetLoader.shouldInterceptRequest(url) - } catch (_: Exception) {} - } - - if (assetLoaderResponse != null) { - val webviewHeaders = runtime.getConfigValue("webview_headers").split("\n") - val headers = mutableMapOf( - "Origin" to "${url.scheme}://${url.host}", - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - - for (line in webviewHeaders) { - val parts = line.split(":", limit = 2) - if (parts.size == 2) { - val key = parts[0].trim() - val value = parts[1].trim() - headers[key] = value - } - } - - assetLoaderResponse.responseHeaders = headers.toMap() - - return assetLoaderResponse - } - - if (url.scheme == "ipc") { - when (url.host) { - "ping" -> { - val stream = java.io.ByteArrayInputStream("pong".toByteArray()) - val response = android.webkit.WebResourceResponse( - "text/plain", - "utf-8", - stream - ) - - response.responseHeaders = mapOf( - "Access-Control-Allow-Origin" to "*" - ) - - return response - } - - else -> { - if (request.method == "OPTIONS") { - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/plain", - "utf-8", - java.io.PipedInputStream(stream) - ) - - val webviewHeaders = runtime.getConfigValue("webview_headers").split("\n") - val headers = mutableMapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - - for (line in webviewHeaders) { - val parts = line.split(":", limit = 2) - if (parts.size == 2) { - val key = parts[0].trim() - val value = parts[1].trim() - headers[key] = value - } - } - - response.responseHeaders = headers.toMap() - - stream.close() - return response - } - - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "application/octet-stream", - "utf-8", - java.io.PipedInputStream(stream) - ) - - response.responseHeaders = mapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - - if (activity.onSchemeRequest(request, response, stream)) { - return response - } - } - } - } - - val fetchResponse = runtime.serviceWorker.fetch(request) - - if (fetchResponse != null) { - return fetchResponse - } - - val stream = java.io.PipedOutputStream() - val response = android.webkit.WebResourceResponse( - "text/plain", - "utf-8", - java.io.PipedInputStream(stream) - ) - - response.responseHeaders = mapOf( - "Access-Control-Allow-Origin" to "*", - "Access-Control-Allow-Headers" to "*", - "Access-Control-Allow-Methods" to "*" - ) - response.setStatusCodeAndReasonPhrase(404, "Not found") - stream.close() - - return response - } - - override fun onPageStarted ( - view: android.webkit.WebView, - url: String, - bitmap: android.graphics.Bitmap? - ) { - this.activity.get()?.onPageStarted(view, url, bitmap) - } - - override fun onPageFinished ( - view: android.webkit.WebView, - url: String - ) { - this.activity.get()?.onPageFinished(view, url) - } -} - -/** - * Main `android.app.Activity` class for the `WebViewClient`. - * @see https://developer.android.com/reference/kotlin/android/app/Activity - * @TODO(jwerle): look into `androidx.appcompat.app.AppCompatActivity` - */ -abstract class WebViewActivity : androidx.appcompat.app.AppCompatActivity() { - open protected val TAG = "WebViewActivity" - - open lateinit var client: WebViewClient - abstract var runtime: Runtime - abstract var window: Window - - open var webview: android.webkit.WebView? = null - - fun evaluateJavaScript ( - source: String, - callback: android.webkit.ValueCallback<String?>? = null - ) { - kotlin.concurrent.thread { - runOnUiThread { - webview?.evaluateJavascript(source, callback) - } - } - } - - fun getAssetManager (): android.content.res.AssetManager { - return this.applicationContext.resources.assets - } - - open fun getRootDirectory (): String { - return getExternalFilesDir(null)?.absolutePath - ?: "/sdcard/Android/data/__BUNDLE_IDENTIFIER__/files" - } - - /** - * Called when the `WebViewActivity` is first created - * @see https://developer.android.com/reference/kotlin/android/app/Activity#onCreate(android.os.Bundle) - */ - override fun onCreate (state: android.os.Bundle?) { - super.onCreate(state) - - setContentView(R.layout.web_view_activity) - - this.client = WebViewClient(this) - this.webview = findViewById(R.id.webview) as android.webkit.WebView? - } - - open fun onPageStarted ( - view: android.webkit.WebView, - url: String, - bitmap: android.graphics.Bitmap? - ) { - console.debug("WebViewActivity is loading: $url") - } - - open fun onPageFinished ( - view: android.webkit.WebView, - url: String - ) { - console.debug("WebViewActivity finished loading: $url") - } - - open fun onSchemeRequest ( - request: android.webkit.WebResourceRequest, - response: android.webkit.WebResourceResponse, - stream: java.io.PipedOutputStream - ): Boolean { - return false - } -} diff --git a/src/android/window.cc b/src/android/window.cc deleted file mode 100644 index d5831e17aa..0000000000 --- a/src/android/window.cc +++ /dev/null @@ -1,194 +0,0 @@ -#include "../core/core.hh" -#include "../ipc/ipc.hh" -#include "internal.hh" - -using namespace SSC::android; - -namespace SSC::android { - Window::Window ( - JNIEnv* env, - jobject self, - Bridge* bridge, - WindowOptions options - ) : options(options) { - this->env = env; - this->self = env->NewGlobalRef(self); - this->bridge = bridge; - this->config = SSC::getUserConfig(); - this->pointer = reinterpret_cast<jlong>(this); - this->bridge->runtime->serviceWorker.init(reinterpret_cast<IPC::Bridge*>(this->bridge)); - - StringStream stream; - - for (auto const &var : parseStringList(this->config["build_env"])) { - auto key = trim(var); - - if (!Env::has(key)) { - continue; - } - - auto value = Env::get(key.c_str()); - - if (value.size() > 0) { - stream << key << "=" << encodeURIComponent(value) << "&"; - envvars[key] = value; - } - } - - StringWrap rootDirectory(env, (jstring) CallObjectClassMethodFromEnvironment( - env, - self, - "getRootDirectory", - "()Ljava/lang/String;" - )); - - Vector<String> argv; - for (const auto& arg : split(this->config["ssc_argv"], ',')) { - argv.push_back("'" + trim(arg) + "'"); - } - - options.headless = this->config["build_headless"] == "true"; - options.debug = isDebugEnabled() ? true : false; - options.env = stream.str(); - options.cwd = rootDirectory.str(); - options.userConfig = this->config; - options.argv = join(argv, ","); - options.isTest = this->config["ssc_argv"].find("--test") != -1; - options.clientId = this->bridge->id; - - preloadSource = createPreload(options, PreloadOptions { - .module = true, - .wrap = true - }); - } - - Window::~Window () { - this->env->DeleteGlobalRef(this->self); - } - - void Window::evaluateJavaScript (String source, JVMEnvironment& jvm) { - auto attachment = JNIEnvironmentAttachment { jvm.get(), jvm.version() }; - auto env = attachment.env; - if (!attachment.hasException()) { - auto sourceString = env->NewStringUTF(source.c_str()); - CallVoidClassMethodFromEnvironment( - env, - self, - "evaluateJavaScript", - "(Ljava/lang/String;)V", - sourceString - ); - - env->DeleteLocalRef(sourceString); - } - } -} - -extern "C" { - jlong external(Window, alloc)( - JNIEnv *env, - jobject self, - jlong bridgePointer - ) { - auto bridge = Bridge::from(bridgePointer); - - if (bridge == nullptr) { - Throw(env, BridgeNotInitializedException); - return 0; - } - - auto options = SSC::WindowOptions {}; - auto window = new Window(env, self, bridge, options); - auto jvm = JVMEnvironment(env); - - if (window == nullptr) { - Throw(env, WindowNotInitializedException); - return 0; - } - - bridge->router.evaluateJavaScriptFunction = [window, jvm](auto source) mutable { - window->evaluateJavaScript(source, jvm); - }; - - return window->pointer; - } - - jboolean external(Window, dealloc)( - JNIEnv *env, - jobject self - ) { - auto window = Window::from(env, self); - - if (window == nullptr) { - Throw(env, WindowNotInitializedException); - return false; - } - - delete window; - return true; - } - - jstring external(Window, getPathToFileToLoad)( - JNIEnv *env, - jobject self - ) { - auto window = Window::from(env, self); - - if (window == nullptr) { - Throw(env, WindowNotInitializedException); - return nullptr; - } - - auto filename = window->config["webview_root"]; - - if (filename.size() > 0) { - if (filename.ends_with("/")) { - return env->NewStringUTF((filename + "index.html").c_str()); - } else { - return env->NewStringUTF(filename.c_str()); - } - } - - return env->NewStringUTF("/index.html"); - } - - jstring external(Window, getJavaScriptPreloadSource)( - JNIEnv *env, - jobject self - ) { - auto window = Window::from(env, self); - - if (window == nullptr) { - Throw(env, WindowNotInitializedException); - return nullptr; - } - - auto source = window->preloadSource.c_str(); - - return env->NewStringUTF(source); - } - - jstring external(Window, getResolveToRenderProcessJavaScript)( - JNIEnv *env, - jobject self, - jstring seq, - jstring state, - jstring value - ) { - auto window = Window::from(env, self); - - if (window == nullptr) { - Throw(env, WindowNotInitializedException); - return nullptr; - } - - auto resolved = SSC::encodeURIComponent(StringWrap(env, value).str()); - auto source = SSC::getResolveToRenderProcessJavaScript( - StringWrap(env, seq).str(), - StringWrap(env, state).str(), - resolved - ); - - return env->NewStringUTF(source.c_str()); - } -} diff --git a/src/android/window.kt b/src/android/window.kt deleted file mode 100644 index a134b37a25..0000000000 --- a/src/android/window.kt +++ /dev/null @@ -1,187 +0,0 @@ -// vim: set sw=2: -package __BUNDLE_IDENTIFIER__ -import java.lang.ref.WeakReference - -open class Window (runtime: Runtime, activity: MainActivity) { - open protected val TAG = "Window" - - val bridge = Bridge(runtime, BridgeConfiguration( - getRootDirectory = { -> - this.getRootDirectory() - } - )) - - val userMessageHandler = UserMessageHandler(this) - val activity = WeakReference(activity) - val runtime = WeakReference(runtime) - val pointer = alloc(bridge.pointer) - var isLoading = false - - fun evaluateJavaScript (source: String) { - this.activity.get()?.evaluateJavaScript(source) - } - - fun getRootDirectory (): String { - return this.activity.get()?.getRootDirectory() ?: "" - } - - fun load () { - val runtime = this.runtime.get() ?: return - val isDebugEnabled = this.runtime.get()?.isDebugEnabled() ?: false - val filename = this.getPathToFileToLoad() - val activity = this.activity.get() ?: return - - val rootDirectory = this.getRootDirectory() - - this.bridge.route("ipc://internal.setcwd?value=${rootDirectory}", null, fun (_: Result) { - activity.applicationContext - .getSharedPreferences("WebSettings", android.app.Activity.MODE_PRIVATE) - .edit() - .apply { - putString("scheme", "socket") - putString("hostname", "__BUNDLE_IDENTIFIER__") - apply() - } - - activity.runOnUiThread { - // enable/disable debug module in webview - android.webkit.WebView.setWebContentsDebuggingEnabled(isDebugEnabled) - - activity.webview?.apply { - // features - settings.javaScriptEnabled = true - - settings.domStorageEnabled = runtime.isPermissionAllowed("data_access") - settings.databaseEnabled = runtime.isPermissionAllowed("data_access") - - settings.setGeolocationEnabled(runtime.isPermissionAllowed("geolocation")) - settings.javaScriptCanOpenWindowsAutomatically = true - - // allow list - settings.allowFileAccess = true - settings.allowContentAccess = true - - // allow mixed content - settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW - - activity.client.putRootDirectory(rootDirectory) - - // clients - webViewClient = activity.client - webChromeClient = WebChromeClient(activity) - - addJavascriptInterface(userMessageHandler, "external") - - var baseUrl = "https://__BUNDLE_IDENTIFIER__$filename" - loadUrl(baseUrl) - } - } - }) - } - - open fun onSchemeRequest ( - request: android.webkit.WebResourceRequest, - response: android.webkit.WebResourceResponse, - stream: java.io.PipedOutputStream - ): Boolean { - return bridge.route(request.url.toString(), null, fun (result: Result) { - var bytes = result.value.toByteArray() - var contentType = "application/json" - - if (result.bytes != null) { - bytes = result.bytes - contentType = "application/octet-stream" - } - - response.apply { - setStatusCodeAndReasonPhrase(200, "OK") - setResponseHeaders(responseHeaders + result.headers) - setMimeType(contentType) - } - - kotlin.concurrent.thread { - try { - stream.write(bytes, 0, bytes.size) - } catch (err: Exception) { - if (err.message != "Pipe closed") { - console.error("onSchemeRequest(): ${err.toString()}") - } - } - - stream.close() - } - }) - } - - open fun onPageStarted ( - view: android.webkit.WebView, - url: String, - bitmap: android.graphics.Bitmap? - ) { - this.isLoading = true - } - - open fun onPageFinished ( - view: android.webkit.WebView, - url: String - ) { - this.isLoading = false - } - - open fun onProgressChanged ( - view: android.webkit.WebView, - progress: Int - ) { - } - - @Throws(java.lang.Exception::class) - external fun alloc (bridgePointer: Long): Long; - - @Throws(java.lang.Exception::class) - external fun dealloc (): Boolean; - - @Throws(java.lang.Exception::class) - external fun getPathToFileToLoad (): String; - - @Throws(java.lang.Exception::class) - external fun getJavaScriptPreloadSource (): String; - - @Throws(java.lang.Exception::class) - external fun getResolveToRenderProcessJavaScript ( - seq: String, - state: String, - value: String - ): String; -} - -/** - * External JavaScript interface attached to the webview at - * `window.external` - */ -open class UserMessageHandler (window: Window) { - val TAG = "UserMessageHandler" - - val namespace = "external" - val activity = window.bridge.runtime.get()?.activity - val runtime = window.bridge.runtime - val window = WeakReference(window) - - @android.webkit.JavascriptInterface - open fun postMessage (value: String): Boolean { - return this.postMessage(value, null) - } - - /** - * Low level external message handler - */ - @android.webkit.JavascriptInterface - open fun postMessage (value: String, bytes: ByteArray? = null): Boolean { - val bridge = this.window.get()?.bridge ?: return false - - return bridge.route(value, bytes, fun (result: Result) { - val window = this.window.get() ?: return - val javascript = window.getResolveToRenderProcessJavaScript(result.seq, "1", result.value) - this.window.get()?.evaluateJavaScript(javascript) - }) - } -} diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index dee8f81b94..9dcfe18317 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -5,10 +5,6 @@ #include "../core/trace.hh" #include "../core/webview.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../android/platform.hh" -#endif - #include "client.hh" namespace SSC::IPC { From 96d8d915318901a50e1954db3123a521812dd0de Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 9 Jul 2024 18:29:58 +0200 Subject: [PATCH 0916/1178] fix(api/internal/init.js): handle 'Blob' without types for 'Worker' constructor --- api/internal/init.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index 921dd0d698..d151808473 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -1,4 +1,4 @@ -/* global requestAnimationFrame, Blob, DataTransfer, DragEvent, FileList, MessageEvent, reportError */ +/* global XMLHttpRequest, requestAnimationFrame, Blob, DataTransfer, DragEvent, FileList, MessageEvent, reportError */ /* eslint-disable import/first */ // mark when runtime did init console.assert( @@ -320,8 +320,25 @@ class RuntimeWorker extends GlobalWorker { constructor (filename, options, ...args) { options = { ...options } + if (typeof filename === 'string' && !URL.canParse(filename, location.href)) { + const blob = new Blob([filename], { type: 'text/javascript' }) + filename = URL.createObjectURL(blob).toString() + } else if (String(filename).startsWith('blob')) { + const request = new XMLHttpRequest() + request.open('GET', String(filename), false) + request.send() + + const blob = new Blob([request.responseText || request.response], { + type: 'text/javascript' + }) + + filename = URL + .createObjectURL(blob) + .toString() + } + const workerType = options[Symbol.for('socket.runtime.internal.worker.type')] ?? 'worker' - const url = encodeURIComponent(new URL(filename, globalThis.location.href).toString()) + const url = encodeURIComponent(new URL(filename, location.href).toString()) const id = String(rand64()) const topClient = globalThis.__args.client.top || globalThis.__args.client From b678d40f25ff0e073e002568ab6177f50c745544 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 9 Jul 2024 18:30:34 +0200 Subject: [PATCH 0917/1178] fix(api/shared-worker): handle 'Blob' without types for 'SharedWorker' constructor --- api/shared-worker/index.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/shared-worker/index.js b/api/shared-worker/index.js index cfeea2936b..6c869fea06 100644 --- a/api/shared-worker/index.js +++ b/api/shared-worker/index.js @@ -1,4 +1,4 @@ -/* global ErrorEvent */ +/* global XMLHttpRequest, ErrorEvent */ import application from '../application.js' import location from '../location.js' import crypto from '../crypto.js' @@ -62,10 +62,21 @@ export class SharedWorker extends EventTarget { /** * `SharedWorker` class constructor. - * @param {string} aURL + * @param {string|URL|Blob} aURL * @param {string|object=} [nameOrOptions] */ constructor (aURL, nameOrOptions = null) { + if (typeof aURL === 'string' && !URL.canParse(aURL, location.href)) { + const blob = new Blob([aURL], { type: 'text/javascript' }) + aURL = URL.createObjectURL(blob).toString() + } else if (String(aURL).startsWith('blob:')) { + const request = new XMLHttpRequest() + request.open('GET', String(aURL), false) + request.send() + const blob = new Blob([request.responseText || request.response], { type: 'application/javascript' }) + aURL = URL.createObjectURL(blob) + } + const url = new URL(aURL, location.origin) const id = crypto.murmur3(url.origin + url.pathname) From 5908c629920ca1e3ffe078cc23bd05b3456e93af Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 9 Jul 2024 18:31:32 +0200 Subject: [PATCH 0918/1178] fix(api/service-worker/container.js): handle 'blob:' URL origins --- api/service-worker/container.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 3c0698eefb..07cdf2fb84 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -332,7 +332,11 @@ export class ServiceWorkerContainer extends EventTarget { } if (globalThis.isWorkerScope) { - currentScope = new URL('.', globalThis.RUNTIME_WORKER_LOCATION).pathname + if (globalThis.RUNTIME_WORKER_LOCATION.startsWith('blob:')) { + currentScope = new URL('.', new URL(globalThis.RUNTIME_WORKER_LOCATION).pathname).pathname + } else { + currentScope = new URL('.', globalThis.RUNTIME_WORKER_LOCATION).pathname + } } else if (globalThis.location.protocol === 'blob:') { currentScope = new URL('.', globalThis.location.pathname).pathname } else { From bc52ef6d6952ed4425b3e92a78a606b64600a94d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 9 Jul 2024 18:31:59 +0200 Subject: [PATCH 0919/1178] chore(api): generate types --- api/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index eb64e9f8e5..9c999dcdad 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -7963,10 +7963,10 @@ declare module "socket:shared-worker/index" { export class SharedWorker extends EventTarget { /** * `SharedWorker` class constructor. - * @param {string} aURL + * @param {string|URL|Blob} aURL * @param {string|object=} [nameOrOptions] */ - constructor(aURL: string, nameOrOptions?: (string | object) | undefined); + constructor(aURL: string | URL | Blob, nameOrOptions?: (string | object) | undefined); set onerror(onerror: any); get onerror(): any; get ready(): any; From 293a2a28b4e10b0a55a81a3718f879d6808a8bd1 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 25 Jun 2024 17:37:53 +0200 Subject: [PATCH 0920/1178] feature(core): high thoughput streams --- api/latica/api.js | 21 ++- api/latica/cache.js | 4 +- api/latica/index.js | 112 ++++++++------- api/latica/proxy.js | 18 ++- api/latica/worker.js | 1 - src/core/codec.cc | 149 ++++++++++++++++++++ src/core/core.hh | 9 ++ src/core/modules/conduit.cc | 265 ++++++++++++++++++++++++++++++++++++ src/core/modules/conduit.hh | 46 +++++++ src/ipc/routes.cc | 8 ++ 10 files changed, 573 insertions(+), 60 deletions(-) create mode 100644 src/core/modules/conduit.cc create mode 100644 src/core/modules/conduit.hh diff --git a/api/latica/api.js b/api/latica/api.js index 349e279d3f..4db70b3280 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -59,7 +59,6 @@ async function api (options = {}, events, dgram) { _peer.onSend = (...args) => bus._emit('#send', ...args) _peer.onFirewall = (...args) => bus._emit('#firewall', ...args) _peer.onMulticast = (...args) => bus._emit('#multicast', ...args) - _peer.onJoin = (...args) => bus._emit('#join', ...args) _peer.onSync = (...args) => bus._emit('#sync', ...args) _peer.onSyncStart = (...args) => bus._emit('#sync-start', ...args) _peer.onSyncEnd = (...args) => bus._emit('#sync-end', ...args) @@ -234,6 +233,17 @@ async function api (options = {}, events, dgram) { sub.sharedKey = options.sharedKey sub.derivedKeys = derivedKeys + sub.stream = async (eventName, value, opts = {}) => { + opts.clusterId = opts.clusterId || clusterId + opts.subclusterId = opts.subclusterId || sub.subclusterId + + const args = await pack(eventName, value, opts) + + for (const p of sub.peers.values()) { + await _peer.stream(p.peerId, sub.sharedKey, args) + } + } + sub.emit = async (eventName, value, opts = {}) => { opts.clusterId = opts.clusterId || clusterId opts.subclusterId = opts.subclusterId || sub.subclusterId @@ -244,13 +254,13 @@ async function api (options = {}, events, dgram) { let packets = [] for (const p of sub.peers.values()) { - const r = await p._peer.write(sub.sharedKey, args) + const r = await _peer.stream(p.peerId, sub.sharedKey, args) if (packets.length === 0) packets = r } for (const packet of packets) { const p = Packet.from(packet) - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') _peer.cache.insert(pid, p) _peer.unpublished[pid] = Date.now() @@ -300,6 +310,7 @@ async function api (options = {}, events, dgram) { const scid = Buffer.from(packet.subclusterId).toString('base64') const sub = bus.subclusters.get(scid) if (!sub) return + if (!peer || !peer.peerId) return let ee = sub.peers.get(peer.peerId) @@ -314,13 +325,11 @@ async function api (options = {}, events, dgram) { ee.port = peer.port ee.emit = async (eventName, value, opts = {}) => { - if (!ee._peer.write) return - opts.clusterId = opts.clusterId || clusterId opts.subclusterId = opts.subclusterId || sub.subclusterId const args = await pack(eventName, value, opts) - return peer.write(sub.sharedKey, args) + return _peer.stream(peer.peerId, sub.sharedKey, args) } ee.on = async (eventName, cb) => { diff --git a/api/latica/cache.js b/api/latica/cache.js index ced5e70b0c..1cad610b56 100644 --- a/api/latica/cache.js +++ b/api/latica/cache.js @@ -142,7 +142,7 @@ export class Cache { // some random and cluster-related. .pop() - this.data.delete(oldest.packetId.toString('hex')) + this.data.delete(Buffer.from(oldest.packetId).toString('hex')) if (this.onEjected) this.onEjected(oldest) } @@ -189,7 +189,7 @@ export class Cache { async compose (packet, source = this.data) { let previous = packet - if (packet?.index > 0) previous = source.get(packet.previousId.toString('hex')) + if (packet?.index > 0) previous = source.get(Buffer.from(packet.previousId).toString('hex')) if (!previous) return null const { meta, size, indexes, ts } = previous.message diff --git a/api/latica/index.js b/api/latica/index.js index 89d42ff092..47834c9189 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -157,8 +157,8 @@ export class RemotePeer { if (o.indexed) o.natType = NAT.UNRESTRICTED if (o.natType && !NAT.isValid(o.natType)) throw new Error(`invalid .natType (${o.natType})`) - const cid = o.clusterId?.toString('base64') - const scid = o.subclusterId?.toString('base64') + const cid = Buffer.from(o.clusterId || '').toString('base64') + const scid = Buffer.from(o.subclusterId || '').toString('base64') if (cid && scid) { this.clusters[cid] = { [scid]: { rateLimit: MAX_BANDWIDTH } } @@ -173,8 +173,8 @@ export class RemotePeer { const keys = await Encryption.createKeyPair(sharedKey) - args.subclusterId = Buffer.from(keys.publicKey) - args.clusterId = Buffer.from(this.localPeer.clusterId, 'base64') + args.subclusterId = keys.publicKey + args.clusterId = this.localPeer.clusterId args.usr3 = Buffer.from(this.peerId, 'hex') args.usr4 = Buffer.from(this.localPeer.peerId, 'hex') args.message = this.localPeer.encryption.seal(args.message, keys) @@ -190,7 +190,7 @@ export class RemotePeer { const to = this.peerId.slice(0, 6) this.localPeer._onDebug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) - this.localPeer.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) + this.localPeer.gate.set(packet.packetId.toString('hex'), 1) await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) } @@ -514,6 +514,14 @@ export class Peer { this._scheduleSend() } + /** + * @private + */ + async stream (peerId, sharedKey, args) { + const p = this.peers.find(p => p.peerId === peerId) + if (p) p.write(sharedKey, args) + } + /** * @private */ @@ -537,7 +545,7 @@ export class Peer { if (!packet) return this.metrics.o[packet.type]++ - delete this.unpublished[Buffer.from(packet.packetId).toString('hex')] + delete this.unpublished[packet.packetId.toString('hex')] if (this.onSend && packet.type) this.onSend(packet, port, address) this._onDebug(`>> SEND (from=${this.address}:${this.port}, to=${address}:${port}, type=${packet.type})`) }) @@ -597,7 +605,7 @@ export class Peer { } async cacheInsert (packet) { - this.cache.insert(Buffer.from(packet.packetId).toString('hex'), Packet.from(packet)) + this.cache.insert(packet.packetId.toString('hex'), Packet.from(packet)) } async addIndexedPeer (info) { @@ -688,7 +696,7 @@ export class Peer { */ async mcast (packet, ignorelist = []) { const peers = this.getPeers(packet, this.peers, ignorelist) - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') packet.hops += 1 @@ -935,7 +943,7 @@ export class Peer { if (this.onState) this.onState(this.getState()) this.mcast(packet) - this.gate.set(Buffer.from(packet.packetId).toString('hex'), 1) + this.gate.set(packet.packetId.toString('hex'), 1) } /** @@ -990,8 +998,8 @@ export class Peer { sig })) - if (packet) packets[0].previousId = Buffer.from(packet.packetId) - if (nextId) packets[packets.length - 1].nextId = Buffer.from(nextId) + if (packet) packets[0].previousId = packet.packetId + if (nextId) packets[packets.length - 1].nextId = nextId // set the .packetId (any maybe the .previousId and .nextId) for (let i = 0; i < packets.length; i++) { @@ -1002,7 +1010,7 @@ export class Peer { } else { // all fragments will have the same previous packetId // the index is used to stitch them back together in order. - packets[i].previousId = Buffer.from(packets[0].packetId) + packets[i].previousId = packets[0].packetId } if (packets[i + 1]) { @@ -1039,8 +1047,8 @@ export class Peer { const keys = await Encryption.createKeyPair(sharedKey) - args.subclusterId = Buffer.from(keys.publicKey) - args.clusterId = Buffer.from(args.clusterId || this.config.clusterId) + args.subclusterId = keys.publicKey + args.clusterId = args.clusterId || this.config.clusterId const message = this.encryption.seal(args.message, keys) const packets = await this._message2packets(PacketPublish, message, args) @@ -1053,7 +1061,7 @@ export class Peer { this.onPacket(packet, this.port, this.address, true) } - this.unpublished[Buffer.from(packet.packetId).toString('hex')] = Date.now() + this.unpublished[packet.packetId.toString('hex')] = Date.now() if (globalThis.navigator && !globalThis.navigator.onLine) continue this.mcast(packet) @@ -1064,6 +1072,7 @@ export class Peer { if (this.onPacket && head) { const p = await this.cache.compose(head) this.onPacket(p, this.port, this.address, true) + this._onDebug(`-> PUBLISH (multicasted=true, packetId=${p.packetId.toString('hex').slice(0, 8)})`) return [p] } @@ -1121,7 +1130,7 @@ export class Peer { const data = await Packet.encode(packet) const p = Packet.decode(data) // finalize a packet - const pid = Buffer.from(p.packetId).toString('hex') + const pid = p.packetId.toString('hex') if (this.gate.has(pid)) return this.returnRoutes.set(p.usr3.toString('hex'), {}) @@ -1191,7 +1200,7 @@ export class Peer { if (!peer.localPeer) peer.localPeer = this if (!this.connections) this.connections = new Map() - this._onDebug('<- CONNECTION ( ' + + this._onDebug('<- CONNECTION (' + `peerId=${peer.peerId.slice(0, 6)}, ` + `address=${address}:${port}, ` + `type=${packet.type}, ` + @@ -1218,7 +1227,7 @@ export class Peer { this.metrics.i[packet.type]++ this.lastSync = Date.now() - const pid = Buffer.from(packet.packetId || '').toString('hex') + const pid = packet.packetId.toString('hex') if (!isBufferLike(packet.message)) return if (this.gate.has(pid)) return @@ -1254,7 +1263,7 @@ export class Peer { const packet = Packet.from(p) if (!this.cachePredicate(packet)) continue - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') this._onDebug(`-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) this.send(await Packet.encode(packet), port, address) @@ -1296,13 +1305,13 @@ export class Peer { async _onQuery (packet, port, address) { this.metrics.i[packet.type]++ - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 1) - const queryTimestamp = parseInt(Buffer.from(packet.usr1).toString(), 10) - const queryId = Buffer.from(packet.usr3).toString('hex') - const queryType = parseInt(Buffer.from(packet.usr4).toString(), 10) + const queryTimestamp = parseInt(packet.usr1.toString(), 10) + const queryId = packet.usr3.toString('hex') + const queryType = parseInt(packet.usr4.toString(), 10) // if the timestamp in usr1 is older than now - 2s, bail if (queryTimestamp < (Date.now() - 2048)) return @@ -1561,13 +1570,13 @@ export class Peer { this.metrics.i[packet.type]++ if (this.closing) return - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') // the packet needs to be gated, but should allow for attempt // recursion so that the fallback can still be selected. if (this.gate.has(pid) && opts.attempts === 0) return this.gate.set(pid, 1) - const ts = packet.usr1.length && Number(packet.usr1.toString()) + const ts = packet.usr1.length && Number(Buffer.from(packet.usr1).toString()) if (packet.hops >= this.maxHops) return if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return @@ -1598,8 +1607,8 @@ export class Peer { if (this.gate.has('CONN' + peer.peerId) && opts.attempts === 0) return this.gate.set('CONN' + peer.peerId, 1) - const cid = Buffer.from(clusterId).toString('base64') - const scid = Buffer.from(subclusterId).toString('base64') + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') this._onDebug('<- INTRO (' + `isRendezvous=${packet.message.isRendezvous}, ` + @@ -1788,7 +1797,7 @@ export class Peer { async _onJoin (packet, port, address, data) { this.metrics.i[packet.type]++ - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') if (packet.message.requesterPeerId === this.peerId) return if (this.gate.has(pid)) return if (packet.clusterId.length !== 32) return @@ -1809,8 +1818,8 @@ export class Peer { // a rendezvous isn't relevant if it's too old, just drop the packet if (rendezvousDeadline && rendezvousDeadline < Date.now()) return - const cid = Buffer.from(clusterId).toString('base64') - const scid = Buffer.from(subclusterId).toString('base64') + const cid = clusterId.toString('base64') + const scid = subclusterId.toString('base64') this._onDebug('<- JOIN (' + `peerId=${peerId.slice(0, 6)}, ` + @@ -1821,6 +1830,10 @@ export class Peer { `address=${address}:${port})` ) + if (this.onJoin && this.clusters[cid]) { + this.onJoin(packet, peer, port, address) + } + // // This packet represents a peer who wants to join the network and is a // member of our cluster. The packet was replicated though the network @@ -1829,7 +1842,7 @@ export class Peer { // if (rendezvousDeadline && !this.indexed && this.clusters[cid]) { if (!packet.message.rendezvousRequesterPeerId) { - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') this.gate.set(pid, 2) // TODO it would tighten up the transition time between dropped peers @@ -1928,11 +1941,11 @@ export class Peer { this.send(intro2, peer.port, peer.address) this.send(intro1, packet.message.port, packet.message.address) - this.gate.set(Buffer.from(Packet.decode(intro1).packetId).toString('hex'), 2) - this.gate.set(Buffer.from(Packet.decode(intro2).packetId).toString('hex'), 2) + this.gate.set(Packet.decode(intro1).packetId.toString('hex'), 2) + this.gate.set(Packet.decode(intro2).packetId.toString('hex'), 2) } - this.gate.set(Buffer.from(packet.packetId).toString('hex'), 2) + this.gate.set(packet.packetId.toString('hex'), 2) if (packet.hops >= this.maxHops) return if (this.indexed && !packet.clusterId) return @@ -1965,20 +1978,20 @@ export class Peer { // const cluster = this.clusters[packet.clusterId] // if (cluster && cluster[packet.subclusterId]) { - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 6) if (this.cache.has(pid)) { this.metrics.i.DROPPED++ - const cid = Buffer.from(packet.clusterId).toString('base64') - const scid = Buffer.from(packet.subclusterId).toString('base64') - this._onDebug(`<- PUBLISH DROP (packetId=${pid.slice(0, 8)}, clusterId=${cid}, subclueterId=${scid}, from=${address}:${port}, hops=${packet.hops})`) + const cid = packet.clusterId.toString('base64') + const scid = packet.subclusterId.toString('base64') + this._onDebug(`<- DROP (packetId=${pid.slice(0, 8)}, clusterId=${cid}, subclueterId=${scid}, from=${address}:${port}, hops=${packet.hops})`) return } - this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${Buffer.from(packet.usr4).toString() === 'SYNC'})`) + this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) this.cacheInsert(packet) const ignorelist = [{ address, port }] @@ -2005,12 +2018,12 @@ export class Peer { async _onStream (packet, port, address, data) { this.metrics.i[packet.type]++ - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 1) - const streamTo = Buffer.from(packet.usr3).toString('hex') - const streamFrom = Buffer.from(packet.usr4).toString('hex') + const streamTo = packet.usr3.toString('hex') + const streamFrom = packet.usr4.toString('hex') // only help packets with a higher hop count if they are in our cluster // if (packet.hops > 2 && !this.clusters[packet.cluster]) return @@ -2020,25 +2033,25 @@ export class Peer { // stream message is for this peer if (streamTo === this.peerId) { - const scid = Buffer.from(packet.subclusterId).toString('base64') + const scid = packet.subclusterId.toString('base64') if (this.encryption.has(scid)) { let p = packet.copy() // clone the packet so it's not modified if (packet.index > -1) { // if it needs to be composed... p.timestamp = Date.now() - this.streamBuffer.set(Buffer.from(p.packetId).toString('hex'), p) // cache the partial + this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial p = await this.cache.compose(p, this.streamBuffer) // try to compose if (!p) return // could not compose if (p) { // if successful, delete the artifacts - const previousId = Buffer.from(p.index === 0 ? p.packetId : p.previousId) + const previousId = p.index === 0 ? p.packetId : p.previousId const pid = previousId.toString('hex') this.streamBuffer.forEach((v, k) => { if (k === pid) this.streamBuffer.delete(k) - if (Buffer.from(v.previousId).toString('hex') === pid) this.streamBuffer.delete(k) + if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) }) } } @@ -2063,7 +2076,10 @@ export class Peer { this._onDebug(`>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) this.send(await Packet.encode(packet), peerTo.port, peerTo.address) - if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) this.mcast(packet) + + if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) { + this.mcast(packet, [{ port, address }, { port: peerFrom.port, address: peerFrom.address }]) + } } /** @@ -2079,7 +2095,7 @@ export class Peer { if (!packet || packet.version !== VERSION) return if (packet?.type !== 2) return - const pid = Buffer.from(packet.packetId).toString('hex') + const pid = packet.packetId.toString('hex') if (this.gate.has(pid)) return this.gate.set(pid, 1) diff --git a/api/latica/proxy.js b/api/latica/proxy.js index a0a8b8d2b3..f98e27ca51 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -209,6 +209,10 @@ class PeerWorkerProxy { return await this.callWorkerThread('requestReflection', args) } + async stream (...args) { + return await this.callWorkerThread('stream', args) + } + async join (...args) { return await this.callWorkerThread('join', args) } @@ -259,9 +263,17 @@ class PeerWorkerProxy { callMainThread (prop, args) { for (const i in args) { const arg = args[i] - if (!arg?.peerId) continue - args[i] = { ...arg } - delete args[i].localPeer // don't copy this over + if (arg?.constructor.name === 'RemotePeer' || arg?.constructor.name === 'Peer') { + args[i] = { + peerId: arg.peerId, + address: arg.address, + port: arg.port, + natType: arg.natType, + clusters: arg.clusters + } + + delete args[i].localPeer // don't copy this over + } } try { diff --git a/api/latica/worker.js b/api/latica/worker.js index fdd6f4f7ee..0e805d10c9 100644 --- a/api/latica/worker.js +++ b/api/latica/worker.js @@ -25,7 +25,6 @@ globalThis.addEventListener('message', ({ data: source }) => { peer.onConnecting = (...args) => this.callMainThread('onConnecting', args) peer.onConnection = (...args) => this.callMainThread('onConnection', args) peer.onDisconnection = (...args) => this.callMainThread('onDisconnection', args) - peer.onJoin = (...args) => this.callMainThread('onJoin', args) peer.onPacket = (...args) => this.callMainThread('onPacket', args) peer.onStream = (...args) => this.callMainThread('onStream', args) peer.onData = (...args) => this.callMainThread('onData', args) diff --git a/src/core/codec.cc b/src/core/codec.cc index d2ce6d8c8f..43bbf111cc 100644 --- a/src/core/codec.cc +++ b/src/core/codec.cc @@ -61,6 +61,19 @@ static const char SAFE[256] = { /* F */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; +static char encoding_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +static int mod_table[] = {0, 2, 1}; + namespace SSC { const Array<uint8_t, 8> toBytes (const uint64_t input) { Array<uint8_t, 8> bytes; @@ -76,6 +89,142 @@ namespace SSC { return bytes; } + inline const unsigned int rol (const unsigned int value, const unsigned int steps) { + return ((value << steps) | (value >> (32 - steps))); + } + + inline void clearWBuffert (unsigned int* buffert) { + int pos = 0; + for (pos = 16; --pos >= 0;) { + buffert[pos] = 0; + } + } + + void innerHash (unsigned int* result, unsigned int* w) { + unsigned int a = result[0]; + unsigned int b = result[1]; + unsigned int c = result[2]; + unsigned int d = result[3]; + unsigned int e = result[4]; + int round = 0; + + #define sha1macro(func,val) \ + { \ + const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \ + e = d; \ + d = c; \ + c = rol(b, 30); \ + b = a; \ + a = t; \ + } + while (round < 16) { + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 20) { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 40) { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0x6ed9eba1) + ++round; + } + while (round < 60) { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc) + ++round; + } + while (round < 80) { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0xca62c1d6) + ++round; + } + #undef sha1macro + + result[0] += a; + result[1] += b; + result[2] += c; + result[3] += d; + result[4] += e; + } + + void shacalc (const char* src, char* dest) { + unsigned char hash[20]; + int bytelength = strlen(src); + unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; + const unsigned char* sarray = (const unsigned char*) src; + unsigned int w[80]; + const int endOfFullBlocks = bytelength - 64; + int endCurrentBlock; + int currentBlock = 0; + + while (currentBlock <= endOfFullBlocks) { + endCurrentBlock = currentBlock + 64; + int roundPos = 0; + + for (roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) { + w[roundPos++] = (unsigned int) sarray[currentBlock + 3] + | (((unsigned int) sarray[currentBlock + 2]) << 8) + | (((unsigned int) sarray[currentBlock + 1]) << 16) + | (((unsigned int) sarray[currentBlock]) << 24); + } + innerHash(result, w); + } + + endCurrentBlock = bytelength - currentBlock; + clearWBuffert(w); + int lastBlockBytes = 0; + + for (; lastBlockBytes < endCurrentBlock; ++lastBlockBytes) { + w[lastBlockBytes >> 2] |= (unsigned int) sarray[lastBlockBytes + currentBlock] << ((3 - (lastBlockBytes & 3)) << 3); + } + + w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3); + + if (endCurrentBlock >= 56) { + innerHash(result, w); + clearWBuffert(w); + } + + w[15] = bytelength << 3; + innerHash(result, w); + int hashByte = 0; + + for (hashByte = 20; --hashByte >= 0;) { + hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff; + } + memcpy(dest, hash, 20); + } + + unsigned char* base64Encode(const unsigned char *data, size_t input_length, size_t *output_length) { + *output_length = (size_t) (4.0 * ceil((double) input_length / 3.0)); + unsigned char *encoded_data = (unsigned char *) malloc(*output_length + 1); + if (!encoded_data) return 0; + + int i = 0; + int j = 0; + + for (i = 0, j = 0; i < input_length;) { + uint32_t octet_a = i < input_length ? data[i++] : 0; + uint32_t octet_b = i < input_length ? data[i++] : 0; + uint32_t octet_c = i < input_length ? data[i++] : 0; + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; + } + + for (i = 0; i < mod_table[input_length % 3]; i++) { + encoded_data[*output_length - 1 - i] = '='; + } + + encoded_data[*output_length] = '\0'; // Null-terminate the string + return encoded_data; + } + String encodeURIComponent (const String& input) { auto pointer = (unsigned char*) input.c_str(); const auto length = (int) input.length(); diff --git a/src/core/core.hh b/src/core/core.hh index ba0df50892..1f7093238f 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -26,6 +26,7 @@ #include "modules/ai.hh" #include "modules/child_process.hh" +#include "modules/conduit.hh" #include "modules/diagnostics.hh" #include "modules/dns.hh" #include "modules/fs.hh" @@ -54,6 +55,7 @@ namespace SSC { using DNS = CoreDNS; using Diagnostics = CoreDiagnostics; using FS = CoreFS; + using Conduit = CoreConduit; using Geolocation = CoreGeolocation; using NetworkStatus = CoreNetworkStatus; using Notifications = CoreNotifications; @@ -74,6 +76,7 @@ namespace SSC { bool useGeolocation = true; bool useNetworkStatus = true; bool useNotifications = true; + bool useConduit = true; bool useOS = true; bool usePlatform = true; bool useTimers = true; @@ -103,6 +106,7 @@ namespace SSC { Diagnostics diagnostics; DNS dns; FS fs; + Conduit conduit; Geolocation geolocation; NetworkStatus networkStatus; Notifications notifications; @@ -153,6 +157,7 @@ namespace SSC { #endif ai(this), diagnostics(this), + conduit(this), dns(this), fs(this), geolocation(this), @@ -167,6 +172,10 @@ namespace SSC { if (options.features.useNetworkStatus) { this->networkStatus.start(); } + + if (options.features.useConduit) { + this->conduit.open(); + } } Core () diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc new file mode 100644 index 0000000000..3329f0788a --- /dev/null +++ b/src/core/modules/conduit.cc @@ -0,0 +1,265 @@ +#include "conduit.hh" +#include "core.hh" + +namespace SSC { + // + // ~~1. where to create this instance~~ + // ~~2. how to listen to the stream (listeners)~~ + // 3. how to write data to the stream (emit) + // + const char *WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + CoreConduit::~CoreCoreConduit() { + this->close(); + } + + void CoreConduit::handshake(CoreConduit::Client *client, const char *request) { + Headers headers(request); + auto protocolHeader = headers["Sec-WebSocket-Protocol"]; + auto keyHeader = headers["Sec-WebSocket-Key"]; + + if (protocolHeader.empty() || keyHeader.empty()) { + // Handle missing headers appropriately, e.g., close connection or send error + return; + } + + uint64_t id = std::stoll(protocolHeader); + client->id = id; + this->clients[id] = client; + + debug("Received key: %s\n", keyHeader.c_str()); + + std::string acceptKey = keyHeader + WS_GUID; + char calculatedHash[SHA_DIGEST_LENGTH]; + shacalc(acceptKey.c_str(), calculatedHash); + + size_t base64_len; + unsigned char *base64_accept_key = base64Encode((unsigned char*)calculatedHash, SHA_DIGEST_LENGTH, &base64_len); + + debug("Generated Accept Key: %s\n", base64_accept_key); // Debugging statement + + std::ostringstream ss; + + ss << "HTTP/1.1 101 Switching Protocols\r\n" + << "Upgrade: websocket\r\n" + << "Connection: Upgrade\r\n" + << "Sec-WebSocket-Accept: " << base64_accept_key << "\r\n\r\n"; + + std::string response = ss.str(); + debug(response.c_str()); + + uv_buf_t wrbuf = uv_buf_init(strdup(response.c_str()), response.size()); + uv_write_t *write_req = new uv_write_t; + write_req->bufs = &wrbuf; + + uv_write(write_req, (uv_stream_t*)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { + if (status) debug(stderr, "write error %s\n", uv_strerror(status)); + + if (req->bufs != nullptr) { + free(req->bufs->base); + } + + free(req); + }); + + free(base64_accept_key); + + client->is_handshake_done = 1; + } + + void CoreConduit::processFrame(CoreConduit::Client *client, const char *frame, ssize_t len) { + if (len < 2) return; // Frame too short to be valid + + unsigned char *data = (unsigned char *)frame; + int fin = data[0] & 0x80; + int opcode = data[0] & 0x0F; + int mask = data[1] & 0x80; + uint64_t payload_len = data[1] & 0x7F; + size_t pos = 2; + + if (payload_len == 126) { + if (len < 4) return; // too short to be valid + payload_len = (data[2] << 8) | data[3]; + pos = 4; + } else if (payload_len == 127) { + if (len < 10) return; // too short to be valid + payload_len = 0; + for (int i = 0; i < 8; i++) { + payload_len = (payload_len << 8) | data[2 + i]; + } + pos = 10; + } + + if (!mask) return; + if (len < pos + 4 + payload_len) return; // too short to be valid + + unsigned char masking_key[4]; + memcpy(masking_key, data + pos, 4); + pos += 4; + + if (payload_len > client->frame_buffer_size) { + client->frame_buffer = (unsigned char *)realloc(client->frame_buffer, payload_len); + client->frame_buffer_size = payload_len; + } + + unsigned char *payload_data = client->frame_buffer; + + for (uint64_t i = 0; i < payload_len; i++) { + payload_data[i] = data[pos + i] ^ masking_key[i % 4]; + } + + pos += payload_len; + + debug("Received message: %.*s\n", (int)payload_len, payload_data); + + auto cb = this->listeners[client->id]; + if (cb) cb((int)payload_len, payload_data); + } + + bool CoreConduit::Client::emit(std::shared_ptr<char[]> message, size_t length) { + this->conduit->core->dispatchEventLoop([this, message, length]() mutable { + size_t frame_size = 2 + length; + std::vector<unsigned char> frame(frame_size); + frame[0] = 0x81; // FIN and opcode 1 (text) + frame[1] = length; + memcpy(frame.data() + 2, message, length); + + uv_buf_t wrbuf = uv_buf_init(reinterpret_cast<char*>(frame.data()), frame_size); + uv_write_t *write_req = new uv_write_t; + write_req->bufs = &wrbuf; + write_req->data = new std::vector<unsigned char>(std::move(frame)); + + uv_write(write_req, (uv_stream_t *)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { + if (status) debug("Write error %s\n", uv_strerror(status)); + delete static_cast<std::vector<unsigned char>*>(req->data); + delete req; + }); + }); + + return true; + } + + void CoreConduit::addListener(uint64_t key, Callback callback) { + listeners[key] = callback; + } + + void CoreConduit::removeListener(uint64_t key) { + listeners.erase(key); + } + + void CoreConduit::open() { + std::promise<int> p; + std::future<int> future = p.get_future(); + + this->core->dispatchEventLoop([this, p]() mutable { + auto loop = this->core->getEventLoop(); + + uv_tcp_init(loop, &this->conduitSocket); + uv_ip4_addr("0.0.0.0", 0, &addr); + uv_tcp_bind(&this->conduitSocket, (const struct sockaddr *)&this->addr, 0); + + this->conduitSocket.data = this; + + int r = uv_listen((uv_stream_t*)&this->conduitSocket, 128, [](uv_stream_t *stream, int status) { + if (status < 0) { + debug("New connection error %s\n", uv_strerror(status)); + return; + } + + auto client = new CoreConduit::Client(); + client->self = static_cast<CoreConduit*>(stream->data); + client->is_handshake_done = 0; + client->frame_buffer = nullptr; + client->frame_buffer_size = 0; + client->handle.data = client; + + uv_tcp_init(uv_default_loop(), &client->handle); + + if (uv_accept(stream, (uv_stream_t*)&client->handle) == 0) { + uv_read_start( + (uv_stream_t *)&client->handle, + [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { + buf->base = (char *) new char[size]{0}; + buf->len = size; + }, + [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { + auto client = static_cast<CoreConduit::Client*>(stream->data); + if (nread > 0) { + if (!client->is_handshake_done) { + client->self->handshake(client, buf->base); + } else { + client->self->processFrame(client, buf->base, nread); + } + } else if (nread < 0) { + if (nread != UV_EOF) { + debug("Read error %s\n", uv_err_name(nread)); + } + uv_close((uv_handle_t *)stream, nullptr); + } + + if (buf->base) { + delete buf->base; + } + } + ); + return; + } + + uv_close((uv_handle_t *)&client->handle, nullptr); + delete client; + }); + + if (r) { + debug("Listen error %s\n", uv_strerror(r)); + p.set_value(0); + return; + } + + struct sockaddr_in sockname; + int namelen = sizeof(sockname); + uv_tcp_getsockname(&this->conduitSocket, (struct sockaddr *)&sockname, &namelen); + + p.set_value(ntohs(sockname.sin_port)); + }); + + this->port = future.get(); + } + + bool CoreConduit::close() { + std::promise<bool> p; + std::future<bool> future = p.get_future(); + + this->core->dispatchEventLoop([this, p]() mutable { + if (!uv_is_closing((uv_handle_t*)&this->conduitSocket)) { + uv_close((uv_handle_t*)&this->conduitSocket, [](uv_handle_t* handle) { + auto conduit = static_cast<CoreConduit*>(handle->data); + p.set_value(true); + }); + } else { + p.set_value(true); + } + + for (auto& clientPair : this->clients) { + auto client = clientPair.second; + + if (client && !uv_is_closing((uv_handle_t*)&client->handle)) { + uv_close((uv_handle_t*)&client->handle, [](uv_handle_t* handle) { + auto client = static_cast<CoreConduit::Client*>(handle->data); + + if (client->frame_buffer) { + free(client->frame_buffer); + client->frame_buffer = nullptr; + client->frame_buffer_size = 0; + } + delete client; + }); + } + } + + this->clients.clear(); + this->listeners.clear(); + }); + + return future.get(); + } +} diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh new file mode 100644 index 0000000000..1c35d86609 --- /dev/null +++ b/src/core/modules/conduit.hh @@ -0,0 +1,46 @@ +#ifndef SOCKET_RUNTIME_CORE_CONDUIT_H +#define SOCKET_RUNTIME_CORE_CONDUIT_H + +#include "../module.hh" + +namespace SSC { + class Core; + class CoreConduit : public CoreModule { + using Callback = std::function<void(int, unsigned char*)>; + + uv_tcp_t conduitSocket; + struct sockaddr_in addr; + + typedef struct { + uv_tcp_t handle; + uv_write_t write_req; + uv_shutdown_t shutdown_req; + uv_buf_t buffer; + int is_handshake_done; + unsigned char *frame_buffer; + size_t frame_buffer_size; + Conduit* self; + + bool emit(std::shared_ptr<char[]> message, size_t length); + } Client; + + std::map<unit64_t, SharedPointer<Client>> clients; + std::map<uint64_t, Callback> listeners; + + void handshake(client_t *client, const char *request); + void processFrame(client_t *client, const char *frame, ssize_t len); + + public: + CoreConduit (Core* core) : CoreModule(core) {}; + ~CoreConduit (); + + int port = 0; + + void open(); + bool close(); + void addListener(uint64_t key, Callback callback); + void removeListener(uint64_t key); + } +} + +#endif diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 8930c0e22e..3a720d8b0d 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2888,6 +2888,14 @@ static void mapIPCRoutes (Router *router) { message.seq, id, [message, reply](auto seq, auto json, auto post) { + if (seq == "-1") { + for (auto &client : router->bridge->core->conduit.clients) { + if (client.first === router->bridge.client.id) { + client.second->emit(post.body, post.length); + return; + } + } + } reply(Result { seq, message, json, post }); } ); From 8d2a2877e14339e3ed147c9f60f446f122c02281 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Wed, 26 Jun 2024 19:36:03 +0200 Subject: [PATCH 0921/1178] refactor(core): condiuit should use existing routes --- src/core/modules/conduit.cc | 55 ++++++++++++++++++++----------------- src/core/modules/conduit.hh | 14 +++------- src/ipc/routes.cc | 12 ++++---- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index 3329f0788a..d8f4ef0737 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -2,11 +2,6 @@ #include "core.hh" namespace SSC { - // - // ~~1. where to create this instance~~ - // ~~2. how to listen to the stream (listeners)~~ - // 3. how to write data to the stream (emit) - // const char *WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; CoreConduit::~CoreCoreConduit() { @@ -14,17 +9,39 @@ namespace SSC { } void CoreConduit::handshake(CoreConduit::Client *client, const char *request) { + std::string req(request); + + size_t reqeol = req.find("\r\n"); + if (reqeol == std::string::npos) return; // nope + + std::istringstream iss(req.substr(0, reqeol)); + std::string method; + std::string url; + std::string version; + + iss >> method >> url >> version; + + const auto parsed = URL::Components::parse(url); + auto parts = split(parsed.pathname, '/'); + if (!parsed.empty() && parsed.size() != 3) return; // nope + + if (parsed.searchParams.get("id") == parsed.searchParams.end()) { + return; // nope + } + Headers headers(request); - auto protocolHeader = headers["Sec-WebSocket-Protocol"]; auto keyHeader = headers["Sec-WebSocket-Key"]; - if (protocolHeader.empty() || keyHeader.empty()) { + if (keyHeader.empty()) { // Handle missing headers appropriately, e.g., close connection or send error return; } - uint64_t id = std::stoll(protocolHeader); - client->id = id; + const port = std::to_string(client->conduit->port); + auto wsa = "ws://localhost:" + port + "/"; + client->route = replace(url, wsa, "ipc://"); + + auto id = std::stoll(parsed.searchParams.get("id")); this->clients[id] = client; debug("Received key: %s\n", keyHeader.c_str()); @@ -38,14 +55,14 @@ namespace SSC { debug("Generated Accept Key: %s\n", base64_accept_key); // Debugging statement - std::ostringstream ss; + std::ostringstream oss; - ss << "HTTP/1.1 101 Switching Protocols\r\n" + oss << "HTTP/1.1 101 Switching Protocols\r\n" << "Upgrade: websocket\r\n" << "Connection: Upgrade\r\n" << "Sec-WebSocket-Accept: " << base64_accept_key << "\r\n\r\n"; - std::string response = ss.str(); + std::string response = oss.str(); debug(response.c_str()); uv_buf_t wrbuf = uv_buf_init(strdup(response.c_str()), response.size()); @@ -110,10 +127,7 @@ namespace SSC { pos += payload_len; - debug("Received message: %.*s\n", (int)payload_len, payload_data); - - auto cb = this->listeners[client->id]; - if (cb) cb((int)payload_len, payload_data); + this->core->router.invoke(this->route, payload_data, (int)payload_len); } bool CoreConduit::Client::emit(std::shared_ptr<char[]> message, size_t length) { @@ -139,14 +153,6 @@ namespace SSC { return true; } - void CoreConduit::addListener(uint64_t key, Callback callback) { - listeners[key] = callback; - } - - void CoreConduit::removeListener(uint64_t key) { - listeners.erase(key); - } - void CoreConduit::open() { std::promise<int> p; std::future<int> future = p.get_future(); @@ -257,7 +263,6 @@ namespace SSC { } this->clients.clear(); - this->listeners.clear(); }); return future.get(); diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 1c35d86609..b74414c3f7 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -6,11 +6,6 @@ namespace SSC { class Core; class CoreConduit : public CoreModule { - using Callback = std::function<void(int, unsigned char*)>; - - uv_tcp_t conduitSocket; - struct sockaddr_in addr; - typedef struct { uv_tcp_t handle; uv_write_t write_req; @@ -20,12 +15,12 @@ namespace SSC { unsigned char *frame_buffer; size_t frame_buffer_size; Conduit* self; - + String route = ""; bool emit(std::shared_ptr<char[]> message, size_t length); } Client; - std::map<unit64_t, SharedPointer<Client>> clients; - std::map<uint64_t, Callback> listeners; + uv_tcp_t conduitSocket; + struct sockaddr_in addr; void handshake(client_t *client, const char *request); void processFrame(client_t *client, const char *frame, ssize_t len); @@ -34,12 +29,11 @@ namespace SSC { CoreConduit (Core* core) : CoreModule(core) {}; ~CoreConduit (); + std::map<unit64_t, SharedPointer<Client>> clients; int port = 0; void open(); bool close(); - void addListener(uint64_t key, Callback callback); - void removeListener(uint64_t key); } } diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 3a720d8b0d..0e5edfcd66 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2889,13 +2889,15 @@ static void mapIPCRoutes (Router *router) { id, [message, reply](auto seq, auto json, auto post) { if (seq == "-1") { - for (auto &client : router->bridge->core->conduit.clients) { - if (client.first === router->bridge.client.id) { - client.second->emit(post.body, post.length); - return; - } + const clients = router->bridge->core->conduit.clients; + + if (clients.find(id) != clients.end()) { + const client = router->bridge->core->conduit.clients[id]; + client.emit(post.body, post.length); + return; } } + reply(Result { seq, message, json, post }); } ); From b4cbaa5806891c69e6e8693bb4944e2c5b49889d Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 28 Jun 2024 15:21:04 +0200 Subject: [PATCH 0922/1178] refactor(window): update preload call --- api/dgram.js | 35 ++++++++++++++++++++++++++++++----- src/ipc/preload.cc | 1 + src/ipc/preload.hh | 1 + src/window/android.cc | 1 + src/window/apple.mm | 1 + src/window/linux.cc | 1 + src/window/win.cc | 1 + 7 files changed, 36 insertions(+), 5 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index a3d4f76684..b9563e9a09 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -14,6 +14,7 @@ import { isArrayBufferView, isFunction, noop } from './util.js' import { murmur3, rand64 } from './crypto.js' import { InternalError } from './errors.js' import { AsyncResource } from './async/resource.js' +import { Conduit } from './internal/conduit.js' import { EventEmitter } from './events.js' import diagnostics from './diagnostics.js' import { Buffer } from './buffer.js' @@ -510,11 +511,21 @@ async function send (socket, options, callback) { address: options.address }) - result = await ipc.write('udp.send', { - id: socket.id, - port: options.port, - address: options.address - }, options.buffer) + if (socket.conduit) { + const headers = { + port: options.port, + address: options.address + } + + socket.conduit.send(headers, options.buffer) + result = { data: true } + } else { + result = await ipc.write('udp.send', { + id: socket.id, + port: options.port, + address: options.address + }, options.buffer) + } callback(result.err, result.data) } catch (err) { @@ -762,6 +773,20 @@ export class Socket extends EventEmitter { }) } + if (this.highThroughput) { + this.conduit = new Conduit({ method: 'udp.send', id: this.id }) + + this.conduit.receive(({ headers, payload }) => { + const rinfo = { + port: headers.port, + address: headers.address, + family: getAddressFamily(address) + } + + this.emit('message', payload, rinfo) + }) + } + startReading(this, (err) => { this.#resource.runInAsyncScope(() => { if (err) { diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 191029530c..fd843b8184 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -102,6 +102,7 @@ namespace SSC::IPC { {"client", JSON::Object {}}, {"config", JSON::Object {}}, {"debug", this->options.debug}, + {"conduit", this->options.conduit}, {"headless", this->options.headless}, {"env", JSON::Object {}}, {"index", this->options.index} diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh index 74a85362ee..356fdd4296 100644 --- a/src/ipc/preload.hh +++ b/src/ipc/preload.hh @@ -70,6 +70,7 @@ namespace SSC::IPC { }; bool headless = false; + int conduit = 0; bool debug = false; Features features; diff --git a/src/window/android.cc b/src/window/android.cc index 4fa4135096..308aa99199 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -37,6 +37,7 @@ namespace SSC { .index = this->bridge.client.index }, .index = options.index, + .conduit = this->core->conduit.port, .userScript = options.userScript }); diff --git a/src/window/apple.mm b/src/window/apple.mm index e6d6a28e68..9e4bb77661 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -208,6 +208,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { .id = this->bridge.client.id, .index = this->bridge.client.index }, + .conduit = this->core->conduit.port, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/linux.cc b/src/window/linux.cc index 845b16ca93..6b664c9e6a 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -240,6 +240,7 @@ namespace SSC { .id = this->bridge.client.id, .index = this->bridge.client.index }, + .conduit = this->core->conduit.port, .index = options.index, .userScript = options.userScript }); diff --git a/src/window/win.cc b/src/window/win.cc index dca6dc230a..c0aa823fa6 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -700,6 +700,7 @@ namespace SSC { .id = this->bridge.client.id, .index = this->bridge.client.index }, + .conduit = this->core->conduit.port, .index = options.index, .userScript = options.userScript }); From d6d43a0a0738af458911e58b150a628934bfb99e Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 28 Jun 2024 15:21:23 +0200 Subject: [PATCH 0923/1178] refactor(api): add conduit api --- api/internal/conduit.js | 168 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 api/internal/conduit.js diff --git a/api/internal/conduit.js b/api/internal/conduit.js new file mode 100644 index 0000000000..1815326c03 --- /dev/null +++ b/api/internal/conduit.js @@ -0,0 +1,168 @@ +/** + * @class Conduit + * + * @classdesc A class for managing WebSocket connections with custom headers and payload encoding. + */ +export class Conduit { + /** + * Creates an instance of Conduit. + * + * @param {Object} params - The parameters for the Conduit. + * @param {string} params.id - The ID for the connection. + * @param {string} params.method - The method to use for the connection. + */ + constructor ({ id, method }) { + const port = globalThis.__args.conduit + const uri = `ws://localhost:${port}/${method}?id=${id}` + this.socket = new globalThis.WebSocket(uri) + } + + /** + * Encodes a single header into a Uint8Array. + * + * @private + * @param {string} key - The header key. + * @param {string|number} value - The header value. + * @returns {Uint8Array} The encoded header. + * @throws Will throw an error if the header value type is invalid. + */ + encodeHeader (key, value) { + const keyLength = key.length + const keyBuffer = new TextEncoder().encode(key) + + let valueBuffer + let valueType + + if (typeof value === 'number') { + valueType = 0 + valueBuffer = new TextEncoder().encode(value.toString()) + } else if (typeof value === 'string') { + valueType = 1 + valueBuffer = new TextEncoder().encode(value) + } else { + throw new Error('Invalid header value type') + } + + const valueLength = valueBuffer.length + + const buffer = new ArrayBuffer(1 + keyLength + 1 + 2 + valueLength) + const view = new DataView(buffer) + + view.setUint8(0, keyLength) + new Uint8Array(buffer, 1, keyLength).set(keyBuffer) + + view.setUint8(1 + keyLength, valueType) + view.setUint16(2 + keyLength, valueLength, false) + new Uint8Array(buffer, 4 + keyLength, valueLength).set(valueBuffer) + + return new Uint8Array(buffer) + } + + /** + * Encodes headers and payload into a single Uint8Array message. + * + * @private + * @param {Object} headers - The headers to encode. + * @param {Uint8Array} payload - The payload to encode. + * @returns {Uint8Array} The encoded message. + */ + encodeMessage (headers, payload) { + const headerBuffers = Object.entries(headers) + .map(([key, value]) => this.encodeHeader(key, value)) + + const totalHeaderLength = headerBuffers.reduce((sum, buf) => sum + buf.length, 0) + const bodyLength = payload.length + const buffer = new ArrayBuffer(1 + totalHeaderLength + 2 + bodyLength) + const view = new DataView(buffer) + + view.setUint8(0, headerBuffers.length) + + let offset = 1 + + headerBuffers.forEach(headerBuffer => { + new Uint8Array(buffer, offset, headerBuffer.length).set(headerBuffer) + offset += headerBuffer.length + }) + + view.setUint16(offset, bodyLength) + offset += 2 + + new Uint8Array(buffer, offset, bodyLength).set(payload) + + return new Uint8Array(buffer) + } + + /** + * Decodes a Uint8Array message into headers and payload. + * @param {Uint8Array} data - The data to decode. + * @returns {Object} The decoded message containing headers and payload. + * @throws Will throw an error if the data is invalid. + */ + decodeMessage (data) { + const view = new DataView(data.buffer) + const numHeaders = view.getUint8(0) + + let offset = 1 + const headers = {} + + for (let i = 0; i < numHeaders; i++) { + const keyLength = view.getUint8(offset) + offset += 1 + + const key = new TextDecoder().decode(new Uint8Array(data.buffer, offset, keyLength)) + offset += keyLength + + const valueType = view.getUint8(offset) + offset += 1 + + const valueLength = view.getUint16(offset, false) + offset += 2 + + const valueBuffer = new Uint8Array(data.buffer, offset, valueLength) + offset += valueLength + + let value + if (valueType === 0) { + value = parseInt(new TextDecoder().decode(valueBuffer), 10) + } else if (valueType === 1) { + value = new TextDecoder().decode(valueBuffer) + } else { + throw new Error('Invalid header value type') + } + + headers[key] = value + } + + const bodyLength = view.getUint16(offset, false) + offset += 2 + + const payload = new Uint8Array(data.buffer, offset, bodyLength) + + return { headers, payload } + } + + /** + * Registers a callback to handle incoming messages. + * The callback will receive an object containing decoded headers and payload. + * + * @param {Function} cb - The callback function to handle incoming messages. + * The callback receives a single parameter: + * @param {Object} cb.message - The decoded message object. + * @param {Object} cb.message.headers - The decoded headers as key-value pairs. + * @param {Uint8Array} cb.message.payload - The actual data of the payload. + */ + + receive (cb) { + this.socket.onmessage = event => this.decodeMessage(data) + } + + /** + * Sends a message with the specified headers and payload over the WebSocket connection. + * + * @param {Object} headers - The headers to send. + * @param {Uint8Array} payload - The payload to send. + */ + send (headers, payload) { + this.socket.send(this.encodeMessage(headers, payload)) + } +} From 678b025972e75e7ab415893c4a87e7de41a27388 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 28 Jun 2024 18:19:58 +0200 Subject: [PATCH 0924/1178] refactor(core): conduit protocol --- api/internal/conduit.js | 40 ++------- src/core/modules/conduit.cc | 164 ++++++++++++++++++++++++++++-------- src/core/modules/conduit.hh | 64 ++++++++++---- src/ipc/routes.cc | 12 ++- 4 files changed, 195 insertions(+), 85 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 1815326c03..50950c86f1 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -22,38 +22,24 @@ export class Conduit { * * @private * @param {string} key - The header key. - * @param {string|number} value - The header value. + * @param {string} value - The header value. * @returns {Uint8Array} The encoded header. - * @throws Will throw an error if the header value type is invalid. */ encodeHeader (key, value) { const keyLength = key.length const keyBuffer = new TextEncoder().encode(key) - let valueBuffer - let valueType - - if (typeof value === 'number') { - valueType = 0 - valueBuffer = new TextEncoder().encode(value.toString()) - } else if (typeof value === 'string') { - valueType = 1 - valueBuffer = new TextEncoder().encode(value) - } else { - throw new Error('Invalid header value type') - } - + const valueBuffer = new TextEncoder().encode(value) const valueLength = valueBuffer.length - const buffer = new ArrayBuffer(1 + keyLength + 1 + 2 + valueLength) + const buffer = new ArrayBuffer(1 + keyLength + 2 + valueLength) const view = new DataView(buffer) view.setUint8(0, keyLength) new Uint8Array(buffer, 1, keyLength).set(keyBuffer) - view.setUint8(1 + keyLength, valueType) - view.setUint16(2 + keyLength, valueLength, false) - new Uint8Array(buffer, 4 + keyLength, valueLength).set(valueBuffer) + view.setUint16(1 + keyLength, valueLength, false) + new Uint8Array(buffer, 3 + keyLength, valueLength).set(valueBuffer) return new Uint8Array(buffer) } @@ -84,7 +70,7 @@ export class Conduit { offset += headerBuffer.length }) - view.setUint16(offset, bodyLength) + view.setUint16(offset, bodyLength, false) offset += 2 new Uint8Array(buffer, offset, bodyLength).set(payload) @@ -112,23 +98,13 @@ export class Conduit { const key = new TextDecoder().decode(new Uint8Array(data.buffer, offset, keyLength)) offset += keyLength - const valueType = view.getUint8(offset) - offset += 1 - const valueLength = view.getUint16(offset, false) offset += 2 const valueBuffer = new Uint8Array(data.buffer, offset, valueLength) offset += valueLength - let value - if (valueType === 0) { - value = parseInt(new TextDecoder().decode(valueBuffer), 10) - } else if (valueType === 1) { - value = new TextDecoder().decode(valueBuffer) - } else { - throw new Error('Invalid header value type') - } + const value = new TextDecoder().decode(valueBuffer) headers[key] = value } @@ -153,7 +129,7 @@ export class Conduit { */ receive (cb) { - this.socket.onmessage = event => this.decodeMessage(data) + this.socket.onmessage = event => this.decodeMessage(Uint8Array.from(data)) } /** diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index d8f4ef0737..b3c8dd4217 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -8,45 +8,125 @@ namespace SSC { this->close(); } - void CoreConduit::handshake(CoreConduit::Client *client, const char *request) { - std::string req(request); + Vector<uint8_t> CoreConduit::encodeMessage (const CoreConduit::Options& options, const Vector<uint8_t>& payload) { + Vector<uint8_t> encodedMessage; + + encodedMessage.push_back(static_cast<uint8_t>(options.size())); + + for (const auto& option : options) { + const String& key = option.first; + const String& value = option.second; + + // length + encodedMessage.push_back(static_cast<uint8_t>(key.length())); + // key + encodedMessage.insert(encodedMessage.end(), key.begin(), key.end()); + + // value length + uint16_t valueLength = static_cast<uint16_t>(value.length()); + encodedMessage.push_back(static_cast<uint8_t>((valueLength >> 8) & 0xFF)); + encodedMessage.push_back(static_cast<uint8_t>(valueLength & 0xFF)); + // value + encodedMessage.insert(encodedMessage.end(), value.begin(), value.end()); + } + + // len + uint16_t bodyLength = static_cast<uint16_t>(payload.size()); + encodedMessage.push_back(static_cast<uint8_t>((bodyLength >> 8) & 0xFF)); + encodedMessage.push_back(static_cast<uint8_t>(bodyLength & 0xFF)); + + // body + encodedMessage.insert(encodedMessage.end(), payload.begin(), payload.end()); + + return encodedMessage; + } + + CoreConduit::EncodedMessage decodeMessage (const Vector<uint8_t>& data) { + EncodedMessage message; + size_t offset = 0; + + uint8_t numOpts = data[offset++]; + + for (uint8_t i = 0; i < numOpts; ++i) { + // len + uint8_t keyLength = data[offset++]; + // key + String key(data.begin() + offset, data.begin() + offset + keyLength); + offset += keyLength; + + // len + uint16_t valueLength = (data[offset] << 8) | data[offset + 1]; + offset += 2; + + // val + String value(data.begin() + offset, data.begin() + offset + valueLength); + offset += valueLength; + + message.options[key] = value; + } + + // len + uint16_t bodyLength = (data[offset] << 8) | data[offset + 1]; + offset += 2; + + // body + message.payload = Vector<uint8_t>(data.begin() + offset, data.begin() + offset + bodyLength); + + return message; + } + + CoreConduit::EncodedMessage decodeMessage (const unsigned char* payload_data, int payload_len) { + Vector<uint8_t> data(payload_data, payload_data + payload_len); + return decodeMessage(data); + } + + bool has (uint64_t id) const { + std::lock_guard<std::mutex> lock(clientsMutex); + return this->clients.find(id) != this->clients.end(); + } + + std::shared_ptr<CoreConduit::Client> get (uint64_t id) const { + std::lock_guard<std::mutex> lock(clientsMutex); + auto it = clients.find(id); + + if (it != clients.end()) { + return it->second; + } + return nullptr; + } + + void CoreConduit::handshake (std::shared_ptr<CoreConduit::Client> client, const char *request) { + String req(request); size_t reqeol = req.find("\r\n"); - if (reqeol == std::string::npos) return; // nope + if (reqeol == String::npos) return; // nope std::istringstream iss(req.substr(0, reqeol)); - std::string method; - std::string url; - std::string version; + String method; + String url; + String version; iss >> method >> url >> version; const auto parsed = URL::Components::parse(url); - auto parts = split(parsed.pathname, '/'); - if (!parsed.empty() && parsed.size() != 3) return; // nope - if (parsed.searchParams.get("id") == parsed.searchParams.end()) { - return; // nope + return; } Headers headers(request); auto keyHeader = headers["Sec-WebSocket-Key"]; if (keyHeader.empty()) { - // Handle missing headers appropriately, e.g., close connection or send error + debug("Sec-WebSocket-Key is required but missing."); return; } - const port = std::to_string(client->conduit->port); - auto wsa = "ws://localhost:" + port + "/"; - client->route = replace(url, wsa, "ipc://"); - auto id = std::stoll(parsed.searchParams.get("id")); this->clients[id] = client; debug("Received key: %s\n", keyHeader.c_str()); - std::string acceptKey = keyHeader + WS_GUID; + String acceptKey = keyHeader + WS_GUID; char calculatedHash[SHA_DIGEST_LENGTH]; shacalc(acceptKey.c_str(), calculatedHash); @@ -62,7 +142,7 @@ namespace SSC { << "Connection: Upgrade\r\n" << "Sec-WebSocket-Accept: " << base64_accept_key << "\r\n\r\n"; - std::string response = oss.str(); + String response = oss.str(); debug(response.c_str()); uv_buf_t wrbuf = uv_buf_init(strdup(response.c_str()), response.size()); @@ -84,7 +164,7 @@ namespace SSC { client->is_handshake_done = 1; } - void CoreConduit::processFrame(CoreConduit::Client *client, const char *frame, ssize_t len) { + void CoreConduit::processFrame (std::shared_ptr<CoreConduit::Client> client, const char *frame, ssize_t len) { if (len < 2) return; // Frame too short to be valid unsigned char *data = (unsigned char *)frame; @@ -127,25 +207,41 @@ namespace SSC { pos += payload_len; - this->core->router.invoke(this->route, payload_data, (int)payload_len); + auto decoded = this->decodeMessage(payload_data, (int)payload_len); + + std::stringstream ss("ipc://"); + ss << decoded.pluck("route"); + ss << "&id=" << std::to_string(client->id); + + for (auto& key : decoded.options) { + ss << "&" << option.first << "=" << option.second; + } + + this->core->router.invoke(new URL(ss), decodedMessage.payload, decodedMessage.payload.size()); } - bool CoreConduit::Client::emit(std::shared_ptr<char[]> message, size_t length) { - this->conduit->core->dispatchEventLoop([this, message, length]() mutable { - size_t frame_size = 2 + length; - std::vector<unsigned char> frame(frame_size); + bool CoreConduit::Client::emit (const CoreConduit::Options& options, const unsigned char* payload_data, size_t length) { + Vector<uint8_t> payloadVec(payload_data, payload_data + length); + Vector<uint8_t> encodedMessage = encodeMessage(options, payloadVec); + + this->conduit->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { + size_t encodedLength = encodedMessage.size(); + Vector<unsigned char> frame(2 + encodedLength); + frame[0] = 0x81; // FIN and opcode 1 (text) - frame[1] = length; - memcpy(frame.data() + 2, message, length); + frame[1] = static_cast<unsigned char>(encodedLength); + std::memcpy(frame.data() + 2, encodedMessage.data(), encodedLength); - uv_buf_t wrbuf = uv_buf_init(reinterpret_cast<char*>(frame.data()), frame_size); - uv_write_t *write_req = new uv_write_t; - write_req->bufs = &wrbuf; - write_req->data = new std::vector<unsigned char>(std::move(frame)); + uv_buf_t wrbuf = uv_buf_init(reinterpret_cast<char*>(frame.data()), frame.size()); + auto writeReq = new uv_write_t; + writeReq->bufs = &wrbuf; + writeReq->data = new Vector<unsigned char>(std::move(frame)); - uv_write(write_req, (uv_stream_t *)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { - if (status) debug("Write error %s\n", uv_strerror(status)); - delete static_cast<std::vector<unsigned char>*>(req->data); + uv_write(writeReq, (uv_stream_t *)&client, &wrbuf, 1, [](uv_write_t *req, int status) { + if (status) { + std::cerr << "Write error: " << uv_strerror(status) << std::endl; + } + delete static_cast<Vector<unsigned char>*>(req->data); delete req; }); }); @@ -153,7 +249,7 @@ namespace SSC { return true; } - void CoreConduit::open() { + void CoreConduit::open () { std::promise<int> p; std::future<int> future = p.get_future(); @@ -231,7 +327,7 @@ namespace SSC { this->port = future.get(); } - bool CoreConduit::close() { + bool CoreConduit::close () { std::promise<bool> p; std::future<bool> future = p.get_future(); diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index b74414c3f7..885ab1ab76 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -6,29 +6,63 @@ namespace SSC { class Core; class CoreConduit : public CoreModule { - typedef struct { - uv_tcp_t handle; - uv_write_t write_req; - uv_shutdown_t shutdown_req; - uv_buf_t buffer; - int is_handshake_done; - unsigned char *frame_buffer; - size_t frame_buffer_size; - Conduit* self; - String route = ""; - bool emit(std::shared_ptr<char[]> message, size_t length); - } Client; - uv_tcp_t conduitSocket; struct sockaddr_in addr; + mutable std::mutex clientsMutex; - void handshake(client_t *client, const char *request); - void processFrame(client_t *client, const char *frame, ssize_t len); + void handshake (client_t *client, const char *request); + void processFrame (client_t *client, const char *frame, ssize_t len); public: + using Options = std::unordered_map<String, String>; + + struct EncodedMessage { + Options options; + Vector<uint8_t> payload; + + String get (const String& key) const { + auto it = options.find(key); + if (it != options.end()) { + return it->second; + } + return ""; + } + + String pluck (const String& key) { + auto it = options.find(key); + if (it != options.end()) { + String value = it->second; + options.erase(it); + return value; + } + return ""; + } + }; + + struct Client{ + uv_tcp_t handle; + uv_write_t write_req; + uv_shutdown_t shutdown_req; + uv_buf_t buffer; + int is_handshake_done; + unsigned char *frame_buffer; + size_t frame_buffer_size; + Conduit* self; + String route = ""; + bool emit(const Options& options, const unsigned char* payload_data, size_t length); + }; + CoreConduit (Core* core) : CoreModule(core) {}; ~CoreConduit (); + EncodedMessage decodeMessage (const unsigned char* payload_data, int payload_len); + EncodedMessage decodeMessage(const Vector<uint8_t>& data); + Vector<uint8_t> encodeMessage(const CoreConduit::Options& options, const Vector<uint8_t>& payload); + bool has (uint64_t id); + + CoreConduit::Client get(uint64_t id) const; + std::shared_ptr<CoreConduit::Client> get (uint64_t id) const; + std::map<unit64_t, SharedPointer<Client>> clients; int port = 0; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 0e5edfcd66..dfaf4609aa 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2889,11 +2889,15 @@ static void mapIPCRoutes (Router *router) { id, [message, reply](auto seq, auto json, auto post) { if (seq == "-1") { - const clients = router->bridge->core->conduit.clients; + if (router->bridge->core->conduit.has(id)) { + const client = router->bridge->core->conduit.get(id); - if (clients.find(id) != clients.end()) { - const client = router->bridge->core->conduit.clients[id]; - client.emit(post.body, post.length); + CoreConduit::Options options = { + { "port": json["data"]["port"] }, + { "address": json["data"]["address"] } + }; + + client.emit(options, post.body, post.length); return; } } From a9ffcfbce79ef7940122e893da62ff5c734f50d2 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 28 Jun 2024 18:21:51 +0200 Subject: [PATCH 0925/1178] refactor(core): improve uri as the frame passes though the conduit --- src/core/modules/conduit.cc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index b3c8dd4217..b2b4c08eeb 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -209,15 +209,14 @@ namespace SSC { auto decoded = this->decodeMessage(payload_data, (int)payload_len); - std::stringstream ss("ipc://"); - ss << decoded.pluck("route"); - ss << "&id=" << std::to_string(client->id); - - for (auto& key : decoded.options) { - ss << "&" << option.first << "=" << option.second; - } - - this->core->router.invoke(new URL(ss), decodedMessage.payload, decodedMessage.payload.size()); + const auto uri = URL::Builder() + .setProtocol("ipc") + .setHostname(decoded.pluck("route")) + .setSearchParam("id", client->id) + .setSearchParams(decoded.options) + .build(); + + this->core->router.invoke(uri, decodedMessage.payload, decodedMessage.payload.size()); } bool CoreConduit::Client::emit (const CoreConduit::Options& options, const unsigned char* payload_data, size_t length) { From 11e5810a1966b795f4e0dade0db89950ab24d3b8 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 28 Jun 2024 18:31:07 +0200 Subject: [PATCH 0926/1178] refactor(api): update conduit javascript to reflect new api --- api/dgram.js | 15 +++++++-------- api/internal/conduit.js | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index b9563e9a09..b386eb282b 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -512,12 +512,11 @@ async function send (socket, options, callback) { }) if (socket.conduit) { - const headers = { + socket.conduit.send({ + route: 'udp.send', port: options.port, address: options.address - } - - socket.conduit.send(headers, options.buffer) + }, options.buffer) result = { data: true } } else { result = await ipc.write('udp.send', { @@ -774,12 +773,12 @@ export class Socket extends EventEmitter { } if (this.highThroughput) { - this.conduit = new Conduit({ method: 'udp.send', id: this.id }) + this.conduit = new Conduit({ id: this.id }) - this.conduit.receive(({ headers, payload }) => { + this.conduit.receive(({ options, payload }) => { const rinfo = { - port: headers.port, - address: headers.address, + port: options.port, + address: options.address, family: getAddressFamily(address) } diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 50950c86f1..85eac7c1e3 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -11,9 +11,9 @@ export class Conduit { * @param {string} params.id - The ID for the connection. * @param {string} params.method - The method to use for the connection. */ - constructor ({ id, method }) { + constructor ({ id }) { const port = globalThis.__args.conduit - const uri = `ws://localhost:${port}/${method}?id=${id}` + const uri = `ws://localhost:${port}?id=${id}` this.socket = new globalThis.WebSocket(uri) } From 4febd402ac30d95527356ef1679da0d2a307f187 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 29 Jun 2024 10:17:45 +0200 Subject: [PATCH 0927/1178] factor(core): continues integration of conduit into core --- src/core/codec.cc | 24 ------------------ src/core/codec.hh | 50 +++++++++++++++++++++++++++++++++++++ src/core/modules/conduit.cc | 39 ++++++++++++++++------------- src/core/modules/conduit.hh | 27 +++++++++++++------- 4 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/core/codec.cc b/src/core/codec.cc index 43bbf111cc..12c24f7f37 100644 --- a/src/core/codec.cc +++ b/src/core/codec.cc @@ -61,19 +61,6 @@ static const char SAFE[256] = { /* F */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; -static char encoding_table[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/' -}; - -static int mod_table[] = {0, 2, 1}; - namespace SSC { const Array<uint8_t, 8> toBytes (const uint64_t input) { Array<uint8_t, 8> bytes; @@ -89,17 +76,6 @@ namespace SSC { return bytes; } - inline const unsigned int rol (const unsigned int value, const unsigned int steps) { - return ((value << steps) | (value >> (32 - steps))); - } - - inline void clearWBuffert (unsigned int* buffert) { - int pos = 0; - for (pos = 16; --pos >= 0;) { - buffert[pos] = 0; - } - } - void innerHash (unsigned int* result, unsigned int* w) { unsigned int a = result[0]; unsigned int b = result[1]; diff --git a/src/core/codec.hh b/src/core/codec.hh index 7b078f0ce5..5c154ee647 100644 --- a/src/core/codec.hh +++ b/src/core/codec.hh @@ -50,6 +50,56 @@ namespace SSC { * @return An array of `uint8_t` values */ const Array<uint8_t, 8> toBytes (const uint64_t input); + + /** + * Rotates an unsigned integer value left by a specified number of steps. + * @param value The unsigned integer value to rotate + * @param steps The number of steps to rotate left + * @return The rotated unsigned integer value + */ + inline const unsigned int rol (const unsigned int value, const unsigned int steps) { + return ((value << steps) | (value >> (32 - steps))); + } + + /** + * Clears a buffer of unsigned integers by setting each element to zero. + * @param buffert Pointer to the buffer to clear + */ + inline void clearWBuffert (unsigned int* buffert) { + int pos = 0; + for (pos = 16; --pos >= 0;) { + buffert[pos] = 0; + } + } + + /** + * Computes the inner hash for the SHA-1 algorithm. + * @param result Pointer to an array of unsigned integers to store the result + * @param w Pointer to an array of unsigned integers to use in computation + */ + void innerHash (unsigned int* result, unsigned int* w); + + /** + * Calculates the SHA-1 hash of a given input string. + * @param src Pointer to the input string + * @param dest Pointer to the destination buffer to store the hash + */ + void shacalc (const char* src, char* dest); + + /** + * Encodes a given input data to a Base64 encoded string. + * @param data Pointer to the input data + * @param input_length Length of the input data + * @param output_length Pointer to store the length of the encoded output + * @return Pointer to the Base64 encoded string + */ + unsigned char* base64Encode(const unsigned char *data, size_t input_length, size_t *output_length); + + // Encoding table for base64 encoding + const char encoding_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // Modulus table for base64 encoding + const int mod_table[] = {0, 2, 1}; } #endif diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index b2b4c08eeb..d80d7f1743 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -1,10 +1,13 @@ #include "conduit.hh" -#include "core.hh" +#include "../core.hh" +#include "../codec.hh" + +#define SHA_DIGEST_LENGTH 20 namespace SSC { const char *WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - CoreConduit::~CoreCoreConduit() { + CoreConduit::~CoreConduit() { this->close(); } @@ -42,7 +45,7 @@ namespace SSC { } CoreConduit::EncodedMessage decodeMessage (const Vector<uint8_t>& data) { - EncodedMessage message; + CoreConduit::EncodedMessage message; size_t offset = 0; uint8_t numOpts = data[offset++]; @@ -80,12 +83,12 @@ namespace SSC { return decodeMessage(data); } - bool has (uint64_t id) const { + bool CoreConduit::has (uint64_t id) const { std::lock_guard<std::mutex> lock(clientsMutex); return this->clients.find(id) != this->clients.end(); } - std::shared_ptr<CoreConduit::Client> get (uint64_t id) const { + std::shared_ptr<CoreConduit::Client> CoreConduit::get (uint64_t id) const { std::lock_guard<std::mutex> lock(clientsMutex); auto it = clients.find(id); @@ -108,8 +111,8 @@ namespace SSC { iss >> method >> url >> version; - const auto parsed = URL::Components::parse(url); - if (parsed.searchParams.get("id") == parsed.searchParams.end()) { + URL parsed(url); + if (parsed.searchParams.count("id") == 0) { return; } @@ -117,14 +120,14 @@ namespace SSC { auto keyHeader = headers["Sec-WebSocket-Key"]; if (keyHeader.empty()) { - debug("Sec-WebSocket-Key is required but missing."); + // debug("Sec-WebSocket-Key is required but missing."); return; } - auto id = std::stoll(parsed.searchParams.get("id")); + auto id = std::stoll(parsed.searchParams["id"]); this->clients[id] = client; - debug("Received key: %s\n", keyHeader.c_str()); + // debug("Received key: %s\n", keyHeader.c_str()); String acceptKey = keyHeader + WS_GUID; char calculatedHash[SHA_DIGEST_LENGTH]; @@ -133,7 +136,7 @@ namespace SSC { size_t base64_len; unsigned char *base64_accept_key = base64Encode((unsigned char*)calculatedHash, SHA_DIGEST_LENGTH, &base64_len); - debug("Generated Accept Key: %s\n", base64_accept_key); // Debugging statement + // debug("Generated Accept Key: %s\n", base64_accept_key); // Debugging statement std::ostringstream oss; @@ -143,14 +146,14 @@ namespace SSC { << "Sec-WebSocket-Accept: " << base64_accept_key << "\r\n\r\n"; String response = oss.str(); - debug(response.c_str()); + // debug(response.c_str()); uv_buf_t wrbuf = uv_buf_init(strdup(response.c_str()), response.size()); uv_write_t *write_req = new uv_write_t; write_req->bufs = &wrbuf; uv_write(write_req, (uv_stream_t*)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { - if (status) debug(stderr, "write error %s\n", uv_strerror(status)); + // if (status) debug(stderr, "write error %s\n", uv_strerror(status)); if (req->bufs != nullptr) { free(req->bufs->base); @@ -213,10 +216,10 @@ namespace SSC { .setProtocol("ipc") .setHostname(decoded.pluck("route")) .setSearchParam("id", client->id) - .setSearchParams(decoded.options) + .setSearchParams(decoded.getOptionsAsMap()) .build(); - this->core->router.invoke(uri, decodedMessage.payload, decodedMessage.payload.size()); + this->core->bridge->router.invoke(uri, decodedMessage.payload, decodedMessage.payload.size()); } bool CoreConduit::Client::emit (const CoreConduit::Options& options, const unsigned char* payload_data, size_t length) { @@ -263,7 +266,7 @@ namespace SSC { int r = uv_listen((uv_stream_t*)&this->conduitSocket, 128, [](uv_stream_t *stream, int status) { if (status < 0) { - debug("New connection error %s\n", uv_strerror(status)); + // debug("New connection error %s\n", uv_strerror(status)); return; } @@ -293,7 +296,7 @@ namespace SSC { } } else if (nread < 0) { if (nread != UV_EOF) { - debug("Read error %s\n", uv_err_name(nread)); + // debug("Read error %s\n", uv_err_name(nread)); } uv_close((uv_handle_t *)stream, nullptr); } @@ -311,7 +314,7 @@ namespace SSC { }); if (r) { - debug("Listen error %s\n", uv_strerror(r)); + // debug("Listen error %s\n", uv_strerror(r)); p.set_value(0); return; } diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 885ab1ab76..f0cbaa1479 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -10,9 +10,6 @@ namespace SSC { struct sockaddr_in addr; mutable std::mutex clientsMutex; - void handshake (client_t *client, const char *request); - void processFrame (client_t *client, const char *frame, ssize_t len); - public: using Options = std::unordered_map<String, String>; @@ -37,9 +34,19 @@ namespace SSC { } return ""; } + + std::map<String, String> getOptionsAsMap () { + std::map<String, String> omap; + + for (const auto& pair : this->options) { + omap.insert(pair); + } + return omap; + } }; struct Client{ + uint64_t id; uv_tcp_t handle; uv_write_t write_req; uv_shutdown_t shutdown_req; @@ -47,7 +54,7 @@ namespace SSC { int is_handshake_done; unsigned char *frame_buffer; size_t frame_buffer_size; - Conduit* self; + CoreConduit* self; String route = ""; bool emit(const Options& options, const unsigned char* payload_data, size_t length); }; @@ -58,17 +65,19 @@ namespace SSC { EncodedMessage decodeMessage (const unsigned char* payload_data, int payload_len); EncodedMessage decodeMessage(const Vector<uint8_t>& data); Vector<uint8_t> encodeMessage(const CoreConduit::Options& options, const Vector<uint8_t>& payload); - bool has (uint64_t id); - - CoreConduit::Client get(uint64_t id) const; + bool has (uint64_t id) const; std::shared_ptr<CoreConduit::Client> get (uint64_t id) const; - std::map<unit64_t, SharedPointer<Client>> clients; + std::map<uint64_t, SharedPointer<Client>> clients; int port = 0; void open(); bool close(); - } + + private: + void handshake (std::shared_ptr<Client> client, const char *request); + void processFrame (std::shared_ptr<Client> client, const char *frame, ssize_t len); + }; } #endif From 412f41f3104e1812fb42fd4789b9f1912a1af444 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 29 Jun 2024 14:02:25 +0200 Subject: [PATCH 0928/1178] factor(core): integrate core changes to conduit --- api/internal/conduit.js | 3 ++- src/core/modules/conduit.cc | 15 ++++++++++++++- src/core/modules/conduit.hh | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 85eac7c1e3..81cac0a3ad 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -13,7 +13,8 @@ export class Conduit { */ constructor ({ id }) { const port = globalThis.__args.conduit - const uri = `ws://localhost:${port}?id=${id}` + const clientId = globalThis.__args.client.top.id + const uri = `ws://localhost:${port}?id=${id}&clientId=${clientId}` this.socket = new globalThis.WebSocket(uri) } diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index d80d7f1743..9ede617bc5 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -116,6 +116,10 @@ namespace SSC { return; } + if (parsed.searchParams.count("clientId") == 0) { + return; + } + Headers headers(request); auto keyHeader = headers["Sec-WebSocket-Key"]; @@ -125,6 +129,11 @@ namespace SSC { } auto id = std::stoll(parsed.searchParams["id"]); + auto clientId = std::stoll(parsed.searchParams["clientId"]); + + client.id = id; + client.clientId = clientId; + this->clients[id] = client; // debug("Received key: %s\n", keyHeader.c_str()); @@ -219,7 +228,11 @@ namespace SSC { .setSearchParams(decoded.getOptionsAsMap()) .build(); - this->core->bridge->router.invoke(uri, decodedMessage.payload, decodedMessage.payload.size()); + auto app = App::sharedApplication(); + auto window = app->windowManager.getWindowForClient({ .id = client->clientID }); + + auto router = window->bridge.router; + router.invoke(uri, decodedMessage.payload, decodedMessage.payload.size()); } bool CoreConduit::Client::emit (const CoreConduit::Options& options, const unsigned char* payload_data, size_t length) { diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index f0cbaa1479..60a1b2ce94 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -47,6 +47,7 @@ namespace SSC { struct Client{ uint64_t id; + uint64_t clientId; uv_tcp_t handle; uv_write_t write_req; uv_shutdown_t shutdown_req; From 4d3c393f54599db0c6a9bb6e8580d9c7567e794e Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 29 Jun 2024 17:32:51 +0200 Subject: [PATCH 0929/1178] factor(core): fix syntax issues, types --- src/core/modules/conduit.cc | 52 ++++++++++++++++++------------------- src/core/modules/conduit.hh | 4 +-- src/ipc/routes.cc | 8 +++--- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index 9ede617bc5..f0bf4b92df 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -1,5 +1,6 @@ #include "conduit.hh" #include "../core.hh" +#include "../../app/app.hh" #include "../codec.hh" #define SHA_DIGEST_LENGTH 20 @@ -7,6 +8,12 @@ namespace SSC { const char *WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + std::shared_ptr<char[]> vectorToShared(const std::vector<uint8_t>& vec) { + std::shared_ptr<char[]> sharedArray(new char[vec.size()]); + std::memcpy(sharedArray.get(), vec.data(), vec.size()); + return sharedArray; + } + CoreConduit::~CoreConduit() { this->close(); } @@ -131,8 +138,8 @@ namespace SSC { auto id = std::stoll(parsed.searchParams["id"]); auto clientId = std::stoll(parsed.searchParams["clientId"]); - client.id = id; - client.clientId = clientId; + client->id = id; + client->clientId = clientId; this->clients[id] = client; @@ -229,17 +236,16 @@ namespace SSC { .build(); auto app = App::sharedApplication(); - auto window = app->windowManager.getWindowForClient({ .id = client->clientID }); - - auto router = window->bridge.router; - router.invoke(uri, decodedMessage.payload, decodedMessage.payload.size()); + auto window = app->windowManager.getWindowForClient({ .id = client->clientId }); + + window->bridge.router.invoke(uri.str(), vectorToShared(decoded.payload), decoded.payload.size()); } bool CoreConduit::Client::emit (const CoreConduit::Options& options, const unsigned char* payload_data, size_t length) { Vector<uint8_t> payloadVec(payload_data, payload_data + length); - Vector<uint8_t> encodedMessage = encodeMessage(options, payloadVec); + Vector<uint8_t> encodedMessage = this->self->encodeMessage(options, payloadVec); - this->conduit->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { + this->self->core->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { size_t encodedLength = encodedMessage.size(); Vector<unsigned char> frame(2 + encodedLength); @@ -252,7 +258,7 @@ namespace SSC { writeReq->bufs = &wrbuf; writeReq->data = new Vector<unsigned char>(std::move(frame)); - uv_write(writeReq, (uv_stream_t *)&client, &wrbuf, 1, [](uv_write_t *req, int status) { + uv_write(writeReq, (uv_stream_t *)&this->handle, &wrbuf, 1, [](uv_write_t *req, int status) { if (status) { std::cerr << "Write error: " << uv_strerror(status) << std::endl; } @@ -268,7 +274,7 @@ namespace SSC { std::promise<int> p; std::future<int> future = p.get_future(); - this->core->dispatchEventLoop([this, p]() mutable { + this->core->dispatchEventLoop([&, this]() mutable { auto loop = this->core->getEventLoop(); uv_tcp_init(loop, &this->conduitSocket); @@ -283,25 +289,28 @@ namespace SSC { return; } - auto client = new CoreConduit::Client(); + auto client = std::make_shared<CoreConduit::Client>(); client->self = static_cast<CoreConduit*>(stream->data); client->is_handshake_done = 0; client->frame_buffer = nullptr; client->frame_buffer_size = 0; - client->handle.data = client; + client->handle.data = new std::shared_ptr<CoreConduit::Client>(client); uv_tcp_init(uv_default_loop(), &client->handle); if (uv_accept(stream, (uv_stream_t*)&client->handle) == 0) { uv_read_start( (uv_stream_t *)&client->handle, - [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { - buf->base = (char *) new char[size]{0}; + [](uv_handle_t *handle, size_t size, uv_buf_t *buf) { + buf->base = (char*) new char[size]{0}; buf->len = size; }, [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - auto client = static_cast<CoreConduit::Client*>(stream->data); if (nread > 0) { + auto handle = uv_handle_get_data((uv_handle_t*)stream); + auto shared_client_ptr = static_cast<std::shared_ptr<CoreConduit::Client>*>(handle); + auto client = *shared_client_ptr; + if (!client->is_handshake_done) { client->self->handshake(client, buf->base); } else { @@ -323,7 +332,6 @@ namespace SSC { } uv_close((uv_handle_t *)&client->handle, nullptr); - delete client; }); if (r) { @@ -342,18 +350,12 @@ namespace SSC { this->port = future.get(); } - bool CoreConduit::close () { - std::promise<bool> p; - std::future<bool> future = p.get_future(); - - this->core->dispatchEventLoop([this, p]() mutable { + void CoreConduit::close () { + this->core->dispatchEventLoop([this]() mutable { if (!uv_is_closing((uv_handle_t*)&this->conduitSocket)) { uv_close((uv_handle_t*)&this->conduitSocket, [](uv_handle_t* handle) { auto conduit = static_cast<CoreConduit*>(handle->data); - p.set_value(true); }); - } else { - p.set_value(true); } for (auto& clientPair : this->clients) { @@ -375,7 +377,5 @@ namespace SSC { this->clients.clear(); }); - - return future.get(); } } diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 60a1b2ce94..e0b8c755c4 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -45,7 +45,7 @@ namespace SSC { } }; - struct Client{ + struct Client { uint64_t id; uint64_t clientId; uv_tcp_t handle; @@ -73,7 +73,7 @@ namespace SSC { int port = 0; void open(); - bool close(); + void close(); private: void handshake (std::shared_ptr<Client> client, const char *request); diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index dfaf4609aa..8382173177 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2887,14 +2887,14 @@ static void mapIPCRoutes (Router *router) { router->bridge->core->udp.readStart( message.seq, id, - [message, reply](auto seq, auto json, auto post) { + [&, message, reply](auto seq, auto json, auto post) { if (seq == "-1") { if (router->bridge->core->conduit.has(id)) { - const client = router->bridge->core->conduit.get(id); + auto client = router->bridge->core->conduit.get(id); CoreConduit::Options options = { - { "port": json["data"]["port"] }, - { "address": json["data"]["address"] } + { "port", json["data"]["port"] }, + { "address", json["data"]["address"] } }; client.emit(options, post.body, post.length); From 02749c6e8890b780ab60d9964d0d2766f10d156c Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 29 Jun 2024 22:49:40 +0200 Subject: [PATCH 0930/1178] factor(core): integrate conduit into core --- src/core/json.cc | 28 ++++++++++++++++++++++++++++ src/core/json.hh | 5 +++++ src/core/modules/conduit.cc | 22 +++++++++------------- src/core/modules/conduit.hh | 9 ++++----- src/ipc/routes.cc | 24 ++++++++++++++---------- 5 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/core/json.cc b/src/core/json.cc index eba6280bf6..b4d495ecad 100644 --- a/src/core/json.cc +++ b/src/core/json.cc @@ -220,6 +220,34 @@ namespace SSC::JSON { } #endif + Any Any::operator[](const SSC::String& key) const { + if (this->type == Type::Object) { + return this->as<Object>()[key]; + } + throw Error("TypeError", "cannot use operator[] on non-object type", __PRETTY_FUNCTION__); + } + + Any& Any::operator[](const SSC::String& key) { + if (this->type == Type::Object) { + return this->as<Object>()[key]; + } + throw Error("TypeError", "cannot use operator[] on non-object type", __PRETTY_FUNCTION__); + } + + Any Any::operator[](const unsigned int index) const { + if (this->type == Type::Array) { + return this->as<Array>()[index]; + } + throw Error("TypeError", "cannot use operator[] on non-array type", __PRETTY_FUNCTION__); + } + + Any& Any::operator[](const unsigned int index) { + if (this->type == Type::Array) { + return this->as<Array>()[index]; + } + throw Error("TypeError", "cannot use operator[] on non-array type", __PRETTY_FUNCTION__); + } + SSC::String Any::str () const { const auto ptr = this->pointer.get() == nullptr ? reinterpret_cast<const void*>(this) diff --git a/src/core/json.hh b/src/core/json.hh index e3ded25443..ea61725d9a 100644 --- a/src/core/json.hh +++ b/src/core/json.hh @@ -217,6 +217,11 @@ namespace SSC::JSON { throw Error("BadCastError", "cannot cast to null value", __PRETTY_FUNCTION__); } + + Any operator[](const SSC::String& key) const; + Any& operator[](const SSC::String& key); + Any operator[](const unsigned int index) const; + Any& operator[](const unsigned int index); }; class Raw : public Value<SSC::String, Type::Raw> { diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index f0bf4b92df..4a27b3b718 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -51,7 +51,7 @@ namespace SSC { return encodedMessage; } - CoreConduit::EncodedMessage decodeMessage (const Vector<uint8_t>& data) { + CoreConduit::EncodedMessage CoreConduit::decodeMessage (std::vector<uint8_t>& data) { CoreConduit::EncodedMessage message; size_t offset = 0; @@ -85,18 +85,13 @@ namespace SSC { return message; } - CoreConduit::EncodedMessage decodeMessage (const unsigned char* payload_data, int payload_len) { - Vector<uint8_t> data(payload_data, payload_data + payload_len); - return decodeMessage(data); - } - bool CoreConduit::has (uint64_t id) const { - std::lock_guard<std::mutex> lock(clientsMutex); + // std::lock_guard<std::mutex> lock(clientsMutex); return this->clients.find(id) != this->clients.end(); } std::shared_ptr<CoreConduit::Client> CoreConduit::get (uint64_t id) const { - std::lock_guard<std::mutex> lock(clientsMutex); + // std::lock_guard<std::mutex> lock(clientsMutex); auto it = clients.find(id); if (it != clients.end()) { @@ -218,15 +213,16 @@ namespace SSC { client->frame_buffer_size = payload_len; } - unsigned char *payload_data = client->frame_buffer; + unsigned char *payload = client->frame_buffer; for (uint64_t i = 0; i < payload_len; i++) { - payload_data[i] = data[pos + i] ^ masking_key[i % 4]; + payload[i] = data[pos + i] ^ masking_key[i % 4]; } pos += payload_len; - auto decoded = this->decodeMessage(payload_data, (int)payload_len); + Vector<uint8_t> vec(payload, payload + payload_len); + auto decoded = this->decodeMessage(vec); const auto uri = URL::Builder() .setProtocol("ipc") @@ -241,8 +237,8 @@ namespace SSC { window->bridge.router.invoke(uri.str(), vectorToShared(decoded.payload), decoded.payload.size()); } - bool CoreConduit::Client::emit (const CoreConduit::Options& options, const unsigned char* payload_data, size_t length) { - Vector<uint8_t> payloadVec(payload_data, payload_data + length); + bool SSC::CoreConduit::Client::emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload, size_t length) { + Vector<uint8_t> payloadVec(payload.get(), payload.get() + length); Vector<uint8_t> encodedMessage = this->self->encodeMessage(options, payloadVec); this->self->core->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index e0b8c755c4..4a9ea36f5d 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -15,7 +15,7 @@ namespace SSC { struct EncodedMessage { Options options; - Vector<uint8_t> payload; + std::vector<uint8_t> payload; String get (const String& key) const { auto it = options.find(key); @@ -57,15 +57,14 @@ namespace SSC { size_t frame_buffer_size; CoreConduit* self; String route = ""; - bool emit(const Options& options, const unsigned char* payload_data, size_t length); + bool emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload_data, size_t length); }; CoreConduit (Core* core) : CoreModule(core) {}; ~CoreConduit (); - EncodedMessage decodeMessage (const unsigned char* payload_data, int payload_len); - EncodedMessage decodeMessage(const Vector<uint8_t>& data); - Vector<uint8_t> encodeMessage(const CoreConduit::Options& options, const Vector<uint8_t>& payload); + EncodedMessage decodeMessage (std::vector<uint8_t>& data); + std::vector<uint8_t> encodeMessage (const CoreConduit::Options& options, const std::vector<uint8_t>& payload); bool has (uint64_t id) const; std::shared_ptr<CoreConduit::Client> get (uint64_t id) const; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 8382173177..91beb8a7b8 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2884,22 +2884,26 @@ static void mapIPCRoutes (Router *router) { uint64_t id; REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); + std::shared_ptr<CoreConduit::Client> client; + + if (router->bridge->core->conduit.has(id)) { + client = router->bridge->core->conduit.get(id); + } + router->bridge->core->udp.readStart( message.seq, id, [&, message, reply](auto seq, auto json, auto post) { - if (seq == "-1") { - if (router->bridge->core->conduit.has(id)) { - auto client = router->bridge->core->conduit.get(id); + if (seq == "-1" && client != nullptr) { + auto data = json["data"]; - CoreConduit::Options options = { - { "port", json["data"]["port"] }, - { "address", json["data"]["address"] } - }; + CoreConduit::Options options = { + { "port", data["port"].str() }, + { "address", data["address"].str() } + }; - client.emit(options, post.body, post.length); - return; - } + client->emit(options, post.body, post.length); + return; } reply(Result { seq, message, json, post }); From e3b44a57f65f31c9abdfacdfc7fb7babb0dd2957 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 1 Jul 2024 09:22:21 +0200 Subject: [PATCH 0931/1178] refactor(core): integrate conduit with dgram --- api/dgram.js | 49 +++++++++--- api/internal/conduit.js | 65 +++++++++------- src/core/modules/conduit.cc | 151 +++++++++++++++++++++++++----------- src/core/modules/conduit.hh | 49 ++++++++---- src/ipc/preload.cc | 11 ++- src/ipc/routes.cc | 11 +-- 6 files changed, 222 insertions(+), 114 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index b386eb282b..21462cbb05 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -155,9 +155,17 @@ async function startReading (socket, callback) { } try { - result = await ipc.send('udp.readStart', { - id: socket.id - }) + if (socket.conduit.isActive) { + const opts = { + route: 'udp.readStart' + } + socket.conduit.send(opts, Buffer.from('')) + result = { data: true } + } else { + result = await ipc.send('udp.readStart', { + id: socket.id + }) + } callback(result.err, result.data) } catch (err) { @@ -511,12 +519,13 @@ async function send (socket, options, callback) { address: options.address }) - if (socket.conduit) { - socket.conduit.send({ + if (socket.conduit.isActive) { + const opts = { route: 'udp.send', port: options.port, address: options.address - }, options.buffer) + } + socket.conduit.send(opts, options.buffer) result = { data: true } } else { result = await ipc.write('udp.send', { @@ -772,18 +781,34 @@ export class Socket extends EventEmitter { }) } - if (this.highThroughput) { + if (!this.legacy) { this.conduit = new Conduit({ id: this.id }) - this.conduit.receive(({ options, payload }) => { + this.conduit.receive((err, decoded) => { const rinfo = { - port: options.port, - address: options.address, - family: getAddressFamily(address) + port: decoded.options.port, + address: decoded.options.address, + family: getAddressFamily(decoded.options.address) } - this.emit('message', payload, rinfo) + this.emit('message', decoded.payload, rinfo) }) + + this.conduit.socket.onopen = () => { + this.conduit.isActive = true + + startReading(this, (err) => { + this.#resource.runInAsyncScope(() => { + if (err) { + cb(err) + } else { + this.dataListener = createDataListener(this, this.#resource) + cb(null) + this.emit('listening') + } + }) + }) + } } startReading(this, (err) => { diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 81cac0a3ad..351ff9b82b 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -1,9 +1,12 @@ /** * @class Conduit * - * @classdesc A class for managing WebSocket connections with custom headers and payload encoding. + * @classdesc A class for managing WebSocket connections with custom options and payload encoding. */ export class Conduit { + isActive = false + id = null + /** * Creates an instance of Conduit. * @@ -14,8 +17,9 @@ export class Conduit { constructor ({ id }) { const port = globalThis.__args.conduit const clientId = globalThis.__args.client.top.id - const uri = `ws://localhost:${port}?id=${id}&clientId=${clientId}` + const uri = `ws://localhost:${port}/${id}/${clientId}` this.socket = new globalThis.WebSocket(uri) + this.socket.binaryType = 'arraybuffer' } /** @@ -26,7 +30,7 @@ export class Conduit { * @param {string} value - The header value. * @returns {Uint8Array} The encoded header. */ - encodeHeader (key, value) { + encodeOption (key, value) { const keyLength = key.length const keyBuffer = new TextEncoder().encode(key) @@ -46,20 +50,20 @@ export class Conduit { } /** - * Encodes headers and payload into a single Uint8Array message. + * Encodes options and payload into a single Uint8Array message. * * @private - * @param {Object} headers - The headers to encode. + * @param {Object} options - The options to encode. * @param {Uint8Array} payload - The payload to encode. * @returns {Uint8Array} The encoded message. */ - encodeMessage (headers, payload) { - const headerBuffers = Object.entries(headers) - .map(([key, value]) => this.encodeHeader(key, value)) + encodeMessage (options, payload) { + const headerBuffers = Object.entries(options) + .map(([key, value]) => this.encodeOption(key, value)) - const totalHeaderLength = headerBuffers.reduce((sum, buf) => sum + buf.length, 0) + const totalOptionLength = headerBuffers.reduce((sum, buf) => sum + buf.length, 0) const bodyLength = payload.length - const buffer = new ArrayBuffer(1 + totalHeaderLength + 2 + bodyLength) + const buffer = new ArrayBuffer(1 + totalOptionLength + 2 + bodyLength) const view = new DataView(buffer) view.setUint8(0, headerBuffers.length) @@ -80,19 +84,19 @@ export class Conduit { } /** - * Decodes a Uint8Array message into headers and payload. + * Decodes a Uint8Array message into options and payload. * @param {Uint8Array} data - The data to decode. - * @returns {Object} The decoded message containing headers and payload. + * @returns {Object} The decoded message containing options and payload. * @throws Will throw an error if the data is invalid. */ decodeMessage (data) { const view = new DataView(data.buffer) - const numHeaders = view.getUint8(0) + const numOpts = view.getUint8(0) let offset = 1 - const headers = {} + const options = {} - for (let i = 0; i < numHeaders; i++) { + for (let i = 0; i < numOpts; i++) { const keyLength = view.getUint8(offset) offset += 1 @@ -106,40 +110,45 @@ export class Conduit { offset += valueLength const value = new TextDecoder().decode(valueBuffer) - - headers[key] = value + options[key] = value } const bodyLength = view.getUint16(offset, false) offset += 2 const payload = new Uint8Array(data.buffer, offset, bodyLength) - - return { headers, payload } + return { options, payload } } /** * Registers a callback to handle incoming messages. - * The callback will receive an object containing decoded headers and payload. + * The callback will receive an error object and an object containing decoded options and payload. * * @param {Function} cb - The callback function to handle incoming messages. - * The callback receives a single parameter: + * @param {Error} cb.error - The error object, if an error occurs. Null if no error. * @param {Object} cb.message - The decoded message object. - * @param {Object} cb.message.headers - The decoded headers as key-value pairs. + * @param {Object} cb.message.options - The decoded options as key-value pairs. * @param {Uint8Array} cb.message.payload - The actual data of the payload. */ - receive (cb) { - this.socket.onmessage = event => this.decodeMessage(Uint8Array.from(data)) + this.socket.onerror = err => { + cb(err) + } + + this.socket.onmessage = event => { + const arrayBuffer = event.data + const data = new Uint8Array(arrayBuffer) + cb(null, this.decodeMessage(data)) + } } /** - * Sends a message with the specified headers and payload over the WebSocket connection. + * Sends a message with the specified options and payload over the WebSocket connection. * - * @param {Object} headers - The headers to send. + * @param {Object} options - The options to send. * @param {Uint8Array} payload - The payload to send. */ - send (headers, payload) { - this.socket.send(this.encodeMessage(headers, payload)) + send (options, payload) { + this.socket.send(this.encodeMessage(options, payload)) } } diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index 4a27b3b718..a8867a9acf 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -21,14 +21,19 @@ namespace SSC { Vector<uint8_t> CoreConduit::encodeMessage (const CoreConduit::Options& options, const Vector<uint8_t>& payload) { Vector<uint8_t> encodedMessage; - encodedMessage.push_back(static_cast<uint8_t>(options.size())); + std::vector<std::pair<String, String>> sortedOptions(options.begin(), options.end()); + std::sort(sortedOptions.begin(), sortedOptions.end()); - for (const auto& option : options) { + // the total number of options + encodedMessage.push_back(static_cast<uint8_t>(sortedOptions.size())); + + for (const auto& option : sortedOptions) { const String& key = option.first; const String& value = option.second; - // length + // ket length encodedMessage.push_back(static_cast<uint8_t>(key.length())); + // key encodedMessage.insert(encodedMessage.end(), key.begin(), key.end()); @@ -36,18 +41,18 @@ namespace SSC { uint16_t valueLength = static_cast<uint16_t>(value.length()); encodedMessage.push_back(static_cast<uint8_t>((valueLength >> 8) & 0xFF)); encodedMessage.push_back(static_cast<uint8_t>(valueLength & 0xFF)); + // value encodedMessage.insert(encodedMessage.end(), value.begin(), value.end()); } - // len + // payload length uint16_t bodyLength = static_cast<uint16_t>(payload.size()); encodedMessage.push_back(static_cast<uint8_t>((bodyLength >> 8) & 0xFF)); encodedMessage.push_back(static_cast<uint8_t>(bodyLength & 0xFF)); - // body + // actual payload encodedMessage.insert(encodedMessage.end(), payload.begin(), payload.end()); - return encodedMessage; } @@ -86,12 +91,10 @@ namespace SSC { } bool CoreConduit::has (uint64_t id) const { - // std::lock_guard<std::mutex> lock(clientsMutex); return this->clients.find(id) != this->clients.end(); } - std::shared_ptr<CoreConduit::Client> CoreConduit::get (uint64_t id) const { - // std::lock_guard<std::mutex> lock(clientsMutex); + CoreConduit::Client* CoreConduit::get (uint64_t id) const { auto it = clients.find(id); if (it != clients.end()) { @@ -100,7 +103,7 @@ namespace SSC { return nullptr; } - void CoreConduit::handshake (std::shared_ptr<CoreConduit::Client> client, const char *request) { + void CoreConduit::handshake (CoreConduit::Client *client, const char *request) { String req(request); size_t reqeol = req.find("\r\n"); @@ -114,14 +117,6 @@ namespace SSC { iss >> method >> url >> version; URL parsed(url); - if (parsed.searchParams.count("id") == 0) { - return; - } - - if (parsed.searchParams.count("clientId") == 0) { - return; - } - Headers headers(request); auto keyHeader = headers["Sec-WebSocket-Key"]; @@ -130,15 +125,30 @@ namespace SSC { return; } - auto id = std::stoll(parsed.searchParams["id"]); - auto clientId = std::stoll(parsed.searchParams["clientId"]); + auto parts = split(parsed.pathname, "/"); + uint64_t socketId = 0; + uint64_t clientId = 0; + + try { + socketId = std::stoull(trim(parts[1])); + } catch (...) { + // debug("Unable to parse socket id"); + } + + try { + clientId = std::stoull(trim(parts[2])); + } catch (...) { + // debug("Unable to parse client id"); + } - client->id = id; + client->id = socketId; client->clientId = clientId; - this->clients[id] = client; + this->clients.emplace(socketId, client); - // debug("Received key: %s\n", keyHeader.c_str()); + std::cout << "added client " << this->clients.size() << std::endl; + + // debug("Received key: %s", keyHeader.c_str()); String acceptKey = keyHeader + WS_GUID; char calculatedHash[SHA_DIGEST_LENGTH]; @@ -178,7 +188,7 @@ namespace SSC { client->is_handshake_done = 1; } - void CoreConduit::processFrame (std::shared_ptr<CoreConduit::Client> client, const char *frame, ssize_t len) { + void CoreConduit::processFrame (CoreConduit::Client *client, const char *frame, ssize_t len) { if (len < 2) return; // Frame too short to be valid unsigned char *data = (unsigned char *)frame; @@ -224,60 +234,101 @@ namespace SSC { Vector<uint8_t> vec(payload, payload + payload_len); auto decoded = this->decodeMessage(vec); - const auto uri = URL::Builder() + /* const auto uri = URL::Builder() .setProtocol("ipc") .setHostname(decoded.pluck("route")) .setSearchParam("id", client->id) .setSearchParams(decoded.getOptionsAsMap()) - .build(); + .build(); */ auto app = App::sharedApplication(); auto window = app->windowManager.getWindowForClient({ .id = client->clientId }); - - window->bridge.router.invoke(uri.str(), vectorToShared(decoded.payload), decoded.payload.size()); + + std::stringstream ss; + + ss << "ipc://"; + ss << decoded.pluck("route"); + ss << "/?id=" << std::to_string(client->id); + + for (auto& option : decoded.getOptionsAsMap()) { + auto key = option.first; + auto value = option.second == "value" ? encodeURIComponent(option.second) : option.second; + ss << "&" << key << "=" << value; + } + + const auto invoked = window->bridge.router.invoke(ss.str(), vectorToShared(decoded.payload), decoded.payload.size()); + if (!invoked) { + // debug("there was a problem invoking the router %s", ss.str().c_str()); + } } bool SSC::CoreConduit::Client::emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload, size_t length) { + if (!this->self) { + std::cout << "Error: 'self' is a null pointer." << std::endl; + return false; + } + Vector<uint8_t> payloadVec(payload.get(), payload.get() + length); - Vector<uint8_t> encodedMessage = this->self->encodeMessage(options, payloadVec); + Vector<uint8_t> encodedMessage; + + try { + encodedMessage = this->self->encodeMessage(options, payloadVec); + } catch (const std::exception& e) { + std::cerr << "Error in encodeMessage: " << e.what() << std::endl; + return false; + } this->self->core->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { size_t encodedLength = encodedMessage.size(); - Vector<unsigned char> frame(2 + encodedLength); + std::vector<unsigned char> frame; + + if (encodedLength <= 125) { + frame.resize(2 + encodedLength); + frame[1] = static_cast<unsigned char>(encodedLength); + } else if (encodedLength <= 65535) { + frame.resize(4 + encodedLength); + frame[1] = 126; + frame[2] = (encodedLength >> 8) & 0xFF; + frame[3] = encodedLength & 0xFF; + } else { + frame.resize(10 + encodedLength); + frame[1] = 127; + for (int i = 0; i < 8; i++) { + frame[9 - i] = (encodedLength >> (i * 8)) & 0xFF; + } + } - frame[0] = 0x81; // FIN and opcode 1 (text) - frame[1] = static_cast<unsigned char>(encodedLength); - std::memcpy(frame.data() + 2, encodedMessage.data(), encodedLength); + frame[0] = 0x82; // FIN and opcode 2 (binary) + std::memcpy(frame.data() + frame.size() - encodedLength, encodedMessage.data(), encodedLength); uv_buf_t wrbuf = uv_buf_init(reinterpret_cast<char*>(frame.data()), frame.size()); auto writeReq = new uv_write_t; writeReq->bufs = &wrbuf; - writeReq->data = new Vector<unsigned char>(std::move(frame)); + writeReq->data = new std::vector<unsigned char>(std::move(frame)); - uv_write(writeReq, (uv_stream_t *)&this->handle, &wrbuf, 1, [](uv_write_t *req, int status) { + uv_write(writeReq, (uv_stream_t*)&this->handle, &wrbuf, 1, [](uv_write_t* req, int status) { if (status) { - std::cerr << "Write error: " << uv_strerror(status) << std::endl; + // debug("Write error: %s", uv_strerror(status)); } - delete static_cast<Vector<unsigned char>*>(req->data); delete req; }); }); - return true; + return true; } void CoreConduit::open () { std::promise<int> p; std::future<int> future = p.get_future(); - this->core->dispatchEventLoop([&, this]() mutable { + this->core->dispatchEventLoop([=, &p, this]() mutable { auto loop = this->core->getEventLoop(); uv_tcp_init(loop, &this->conduitSocket); uv_ip4_addr("0.0.0.0", 0, &addr); uv_tcp_bind(&this->conduitSocket, (const struct sockaddr *)&this->addr, 0); - this->conduitSocket.data = this; + this->conduitSocket.data = (void*)this; int r = uv_listen((uv_stream_t*)&this->conduitSocket, 128, [](uv_stream_t *stream, int status) { if (status < 0) { @@ -285,16 +336,20 @@ namespace SSC { return; } - auto client = std::make_shared<CoreConduit::Client>(); - client->self = static_cast<CoreConduit*>(stream->data); + auto self = static_cast<CoreConduit*>(stream->data); + auto client = new CoreConduit::Client(self); + client->is_handshake_done = 0; client->frame_buffer = nullptr; client->frame_buffer_size = 0; - client->handle.data = new std::shared_ptr<CoreConduit::Client>(client); + client->handle.data = client; + + uv_loop_t *loop = uv_handle_get_loop((uv_handle_t*)stream); + uv_tcp_init(loop, &client->handle); - uv_tcp_init(uv_default_loop(), &client->handle); + auto accepted = uv_accept(stream, (uv_stream_t*)&client->handle); - if (uv_accept(stream, (uv_stream_t*)&client->handle) == 0) { + if (accepted == 0) { uv_read_start( (uv_stream_t *)&client->handle, [](uv_handle_t *handle, size_t size, uv_buf_t *buf) { @@ -304,8 +359,7 @@ namespace SSC { [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { if (nread > 0) { auto handle = uv_handle_get_data((uv_handle_t*)stream); - auto shared_client_ptr = static_cast<std::shared_ptr<CoreConduit::Client>*>(handle); - auto client = *shared_client_ptr; + auto client = (CoreConduit::Client*)(handle); if (!client->is_handshake_done) { client->self->handshake(client, buf->base); @@ -325,6 +379,9 @@ namespace SSC { } ); return; + } else { + // debug("uv_accept error: %s\n", uv_strerror(accepted)); + // delete static_cast<std::shared_ptr<CoreConduit::Client>*>(client->handle.data); } uv_close((uv_handle_t *)&client->handle, nullptr); diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 4a9ea36f5d..ce11447861 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -2,9 +2,13 @@ #define SOCKET_RUNTIME_CORE_CONDUIT_H #include "../module.hh" +#include <iostream> namespace SSC { class Core; + class CoreConduit; + + class CoreConduit : public CoreModule { uv_tcp_t conduitSocket; struct sockaddr_in addr; @@ -45,19 +49,30 @@ namespace SSC { } }; - struct Client { - uint64_t id; - uint64_t clientId; - uv_tcp_t handle; - uv_write_t write_req; - uv_shutdown_t shutdown_req; - uv_buf_t buffer; - int is_handshake_done; - unsigned char *frame_buffer; - size_t frame_buffer_size; - CoreConduit* self; - String route = ""; - bool emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload_data, size_t length); + class Client { + public: + uint64_t id; + uint64_t clientId; + uv_tcp_t handle; + uv_write_t write_req; + uv_shutdown_t shutdown_req; + uv_buf_t buffer; + int is_handshake_done; + unsigned char *frame_buffer; + size_t frame_buffer_size; + + CoreConduit* self; + bool emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload_data, size_t length); + + Client(CoreConduit* self) : self(self), id(0), clientId(0), is_handshake_done(0) { + } + + ~Client() { + if (frame_buffer) { + delete[] frame_buffer; + } + uv_close((uv_handle_t*)&handle, nullptr); + } }; CoreConduit (Core* core) : CoreModule(core) {}; @@ -66,17 +81,17 @@ namespace SSC { EncodedMessage decodeMessage (std::vector<uint8_t>& data); std::vector<uint8_t> encodeMessage (const CoreConduit::Options& options, const std::vector<uint8_t>& payload); bool has (uint64_t id) const; - std::shared_ptr<CoreConduit::Client> get (uint64_t id) const; + CoreConduit::Client* get (uint64_t id) const; - std::map<uint64_t, SharedPointer<Client>> clients; + std::map<uint64_t, Client*> clients; int port = 0; void open(); void close(); private: - void handshake (std::shared_ptr<Client> client, const char *request); - void processFrame (std::shared_ptr<Client> client, const char *frame, ssize_t len); + void handshake (Client *client, const char *request); + void processFrame (Client *client, const char *frame, ssize_t len); }; } diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index fd843b8184..f68726c7b2 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -100,9 +100,9 @@ namespace SSC::IPC { JSON::Object::Entries { {"argv", JSON::Array {}}, {"client", JSON::Object {}}, + {"conduit", this->options.conduit}, {"config", JSON::Object {}}, {"debug", this->options.debug}, - {"conduit", this->options.conduit}, {"headless", this->options.headless}, {"env", JSON::Object {}}, {"index", this->options.index} @@ -118,7 +118,7 @@ namespace SSC::IPC { for (const auto &entry : this->metadata) { if (entry.second.size() == 0) { continue; - } + } buffers.push_back(tmpl( R"HTML(<meta name="{{name}}" content="{{content}}">)HTML", @@ -177,6 +177,12 @@ namespace SSC::IPC { writable: false, value: {} }, + conduit: { + configurable: false, + enumerable: true, + writable: false, + value: {{conduit}} + }, config: { configurable: false, enumerable: true, @@ -211,6 +217,7 @@ namespace SSC::IPC { )JAVASCRIPT", Map { {"argv", args["argv"].str()}, + {"conduit", args["conduit"].str()}, {"debug", args["debug"].str()}, {"headless", args["headless"].str()}, {"index", args["index"].str()}, diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 91beb8a7b8..0b74442a3f 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2884,17 +2884,11 @@ static void mapIPCRoutes (Router *router) { uint64_t id; REQUIRE_AND_GET_MESSAGE_VALUE(id, "id", std::stoull); - std::shared_ptr<CoreConduit::Client> client; - - if (router->bridge->core->conduit.has(id)) { - client = router->bridge->core->conduit.get(id); - } - router->bridge->core->udp.readStart( message.seq, id, - [&, message, reply](auto seq, auto json, auto post) { - if (seq == "-1" && client != nullptr) { + [&, id, router, message, reply](auto seq, auto json, auto post) { + if (seq == "-1" && router->bridge->core->conduit.has(id)) { auto data = json["data"]; CoreConduit::Options options = { @@ -2902,6 +2896,7 @@ static void mapIPCRoutes (Router *router) { { "address", data["address"].str() } }; + auto client = router->bridge->core->conduit.get(id); client->emit(options, post.body, post.length); return; } From 229d2d3ff9e0bb14fc2595daf951b46bdf2952fb Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 1 Jul 2024 12:08:14 +0200 Subject: [PATCH 0932/1178] factor(api): add condition for non-legacy sockets --- api/dgram.js | 2 ++ src/ipc/routes.cc | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/dgram.js b/api/dgram.js index 21462cbb05..d690d9ae1f 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -809,6 +809,8 @@ export class Socket extends EventEmitter { }) }) } + + return } startReading(this, (err) => { diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 0b74442a3f..3854bdfe6f 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2893,7 +2893,7 @@ static void mapIPCRoutes (Router *router) { CoreConduit::Options options = { { "port", data["port"].str() }, - { "address", data["address"].str() } + { "address", data["address"].template as<JSON::String>().data } }; auto client = router->bridge->core->conduit.get(id); From d252eaf7e669f34f6ad69804dbd31e0d921d3bea Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 1 Jul 2024 13:05:33 +0200 Subject: [PATCH 0933/1178] refactor(api): ensure dgram uses runInAsyncScope as well as publishes to dc --- api/dgram.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index d690d9ae1f..07f8a5fe2d 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -786,12 +786,18 @@ export class Socket extends EventEmitter { this.conduit.receive((err, decoded) => { const rinfo = { - port: decoded.options.port, + port: Number(decoded.options.port), address: decoded.options.address, family: getAddressFamily(decoded.options.address) } - this.emit('message', decoded.payload, rinfo) + const message = Buffer.from(decoded.payload) + + this.#resource.runInAsyncScope(() => { + this.emit('message', message, rinfo) + }) + + dc.channel('message').publish({ socket, buffer: message, info }) }) this.conduit.socket.onopen = () => { @@ -802,7 +808,6 @@ export class Socket extends EventEmitter { if (err) { cb(err) } else { - this.dataListener = createDataListener(this, this.#resource) cb(null) this.emit('listening') } From 084c4e4591c1f700348784385ceef5503012fbac Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 9 Jul 2024 14:02:01 +0200 Subject: [PATCH 0934/1178] refactor(core): correct conduit decode, allow ios --- api/dgram.js | 8 +- api/latica/api.js | 14 ++-- api/latica/cache.js | 10 ++- api/latica/index.js | 148 +++++++++++++++++++++--------------- api/latica/packets.js | 2 +- api/latica/proxy.js | 13 ++-- src/core/modules/conduit.cc | 14 ++++ src/window/apple.mm | 5 ++ 8 files changed, 132 insertions(+), 82 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index 07f8a5fe2d..b066766801 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -155,7 +155,7 @@ async function startReading (socket, callback) { } try { - if (socket.conduit.isActive) { + if (socket?.conduit?.isActive) { const opts = { route: 'udp.readStart' } @@ -519,7 +519,7 @@ async function send (socket, options, callback) { address: options.address }) - if (socket.conduit.isActive) { + if (socket?.conduit?.isActive) { const opts = { route: 'udp.send', port: options.port, @@ -785,6 +785,8 @@ export class Socket extends EventEmitter { this.conduit = new Conduit({ id: this.id }) this.conduit.receive((err, decoded) => { + if (!decoded || !decoded.options) return + const rinfo = { port: Number(decoded.options.port), address: decoded.options.address, @@ -797,7 +799,7 @@ export class Socket extends EventEmitter { this.emit('message', message, rinfo) }) - dc.channel('message').publish({ socket, buffer: message, info }) + dc.channel('message').publish({ socket: this, buffer: message, info }) }) this.conduit.socket.onopen = () => { diff --git a/api/latica/api.js b/api/latica/api.js index 4db70b3280..ad4bbd5730 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -36,21 +36,21 @@ async function api (options = {}, events, dgram) { if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters - const Ctor = globalThis.isSocketRuntime ? PeerWorkerProxy : Peer + const Ctor = globalThis.window ? PeerWorkerProxy : Peer const _peer = new Ctor(options, dgram) _peer.onJoin = (packet, ...args) => { - if (!Buffer.from(packet.clusterId).equals(clusterId)) return + if (Buffer.from(packet.clusterId).compare(clusterId) !== 0) return bus._emit('#join', packet, ...args) } _peer.onPacket = (packet, ...args) => { - if (!Buffer.from(packet.clusterId).equals(clusterId)) return + if (Buffer.from(packet.clusterId).compare(clusterId) !== 0) return bus._emit('#packet', packet, ...args) } _peer.onStream = (packet, ...args) => { - if (!Buffer.from(packet.clusterId).equals(clusterId)) return + if (Buffer.from(packet.clusterId).compare(clusterId) !== 0) return bus._emit('#stream', packet, ...args) } @@ -239,9 +239,13 @@ async function api (options = {}, events, dgram) { const args = await pack(eventName, value, opts) + let packets + for (const p of sub.peers.values()) { - await _peer.stream(p.peerId, sub.sharedKey, args) + const result = await _peer.stream(p.peerId, sub.sharedKey, args) + if (!packets) packets = result } + return packets } sub.emit = async (eventName, value, opts = {}) => { diff --git a/api/latica/cache.js b/api/latica/cache.js index 1cad610b56..cc477d9eda 100644 --- a/api/latica/cache.js +++ b/api/latica/cache.js @@ -189,17 +189,19 @@ export class Cache { async compose (packet, source = this.data) { let previous = packet - if (packet?.index > 0) previous = source.get(Buffer.from(packet.previousId).toString('hex')) + if (packet?.index > 0) previous = source.get(packet.previousId?.toString('hex')) if (!previous) return null const { meta, size, indexes, ts } = previous.message // follow the chain to get the buffers in order - const bufs = [...source.values()] - .filter(p => Buffer.from(p.previousId || '').toString('hex') === Buffer.from(previous.packetId).toString('hex')) - .sort((a, b) => a.index - b.index) + let bufs = [...source.values()].filter(p => { + if (!p.previousId) return + return Buffer.from(p.previousId).compare(Buffer.from(previous.packetId)) === 0 + }) if (!indexes || bufs.length < indexes) return null + bufs = bufs.sort((a, b) => a.index - b.index) // sort after confirming they are all there // concat and then hash, the original should match const messages = bufs.map(p => p.message) diff --git a/api/latica/index.js b/api/latica/index.js index 47834c9189..fa521dae7b 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -92,7 +92,7 @@ const isReplicatable = type => ( export function rateLimit (rates, type, port, address, subclusterIdQuota) { const R = isReplicatable(type) const key = (R ? 'R' : 'C') + ':' + address + ':' + port - const quota = subclusterIdQuota || (R ? 512 : 4096) + const quota = subclusterIdQuota || (R ? 1024 : 1024 * 1024) const time = Math.floor(Date.now() / 60000) const rate = rates.get(key) || { time, quota, used: 0 } @@ -179,22 +179,27 @@ export class RemotePeer { args.usr4 = Buffer.from(this.localPeer.peerId, 'hex') args.message = this.localPeer.encryption.seal(args.message, keys) + const cache = new Map() const packets = await this.localPeer._message2packets(PacketStream, args.message, args) if (this.proxy) { - this.localPeer._onDebug(this.localPeer.peerId, `>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) + this.localPeer._onDebug(`>> WRITE STREAM HAS PROXY ${this.proxy.address}:${this.proxy.port}`) } for (const packet of packets) { const from = this.localPeer.peerId.slice(0, 6) const to = this.peerId.slice(0, 6) - this.localPeer._onDebug(this.localPeer.peerId, `>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) + this.localPeer._onDebug(`>> WRITE STREAM (from=${from}, to=${to}, via=${rinfo.address}:${rinfo.port})`) - this.localPeer.gate.set(packet.packetId.toString('hex'), 1) + const pid = packet.packetId.toString('hex') + cache.set(pid, packet) + this.localPeer.gate.set(pid, 1) await this.localPeer.send(await Packet.encode(packet), rinfo.port, rinfo.address, this.socket) } - return packets + const head = packets.find(p => p.index === 0) // has a head, should compose + const p = await this.localPeer.cache.compose(head, cache) + return [p] } } @@ -420,10 +425,9 @@ export class Peer { async _mainLoop (ts) { if (this.closing) return this._clearInterval(this.mainLoopTimer) - // Node 21.x will need this... - // const offline = typeof globalThis.navigator.onLine !== 'undefined' && !globalThis.navigator.onLine - const offline = globalThis.navigator && !globalThis.navigator.onLine - if (offline) { + // if `globalThis.navigator` doesn't exist (such as in Node) assume online. + const online = !globalThis.navigator || globalThis.navigator.onLine + if (!online) { if (this.onConnecting) this.onConnecting({ code: -2, status: 'Offline' }) return true } @@ -433,6 +437,16 @@ export class Peer { this.uptime += this.config.keepalive + // heartbeat + for (const [, peer] of Object.entries(this.peers)) { + this.ping(peer, false, { + message: { + requesterPeerId: this.peerId, + natType: this.natType + } + }) + } + // wait for nat type to be discovered if (!NAT.isValid(this.natType)) return true @@ -477,19 +491,6 @@ export class Peer { } } - // heartbeat - const { hash } = await this.cache.summarize('', this.cachePredicate) - for (const [, peer] of Object.entries(this.peers)) { - this.ping(peer, false, { - message: { - requesterPeerId: this.peerId, - natType: this.natType, - cacheSummaryHash: hash || null, - cacheSize: this.cache.size - } - }) - } - // if this peer has previously tried to join any clusters, multicast a // join messages for each into the network so we are always searching. for (const cluster of Object.values(this.clusters)) { @@ -519,7 +520,7 @@ export class Peer { */ async stream (peerId, sharedKey, args) { const p = this.peers.find(p => p.peerId === peerId) - if (p) p.write(sharedKey, args) + if (p) return p.write(sharedKey, args) } /** @@ -738,12 +739,12 @@ export class Peer { // tell all well-known peers that we would like to hear from them, if // we hear from any we can ask for the reflection information we need. - for (const peer of this.peers.filter(p => p.indexed).sort(() => Math.random() - 0.5).slice(0, 3)) { - await this.ping(peer, false, { message: { requesterPeerId: this.peerId } }) + for (const peer of this.peers.filter(p => p.indexed).sort(() => Math.random() - 0.5).slice(0, 32)) { + await this.ping(peer, false, { message: { isConnection: true, requesterPeerId: this.peerId } }) } if (++this.reflectionRetry > 16) this.reflectionRetry = 1 - return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 512) + return this._setTimeout(() => this.requestReflection(), this.reflectionRetry * 256) } this.reflectionRetry = 1 @@ -939,7 +940,7 @@ export class Peer { } }) - this._onDebug(`-> JOIN (clusterId=${cid}, subclusterId=${scid}, clock=${packet.clock}/${this.clock})`) + this._onDebug(`-> JOIN (clusterId=${cid.slice(0, 6)}, subclusterId=${scid.slice(0, 6)}, clock=${packet.clock}/${this.clock})`) if (this.onState) this.onState(this.getState()) this.mcast(packet) @@ -959,11 +960,14 @@ export class Peer { const len = message?.byteLength ?? message?.length ?? 0 let clock = packet?.clock || 0 - const siblings = [...this.cache.data.values()] + const siblings = packet && [...this.cache.data.values()] .filter(Boolean) - .filter(p => p?.previousId?.toString('hex') === packet?.packetId?.toString('hex')) + .filter(p => { + if (!p.previousId || !packet.packetId) return + return Buffer.from(p.previousId).compare(Buffer.from(packet.packetId)) === 0 + }) - if (siblings.length) { + if (siblings?.length) { // if there are siblings of the previous packet // pick the highest clock value, the parent packet or the sibling const sort = (a, b) => a.clock - b.clock @@ -998,19 +1002,19 @@ export class Peer { sig })) - if (packet) packets[0].previousId = packet.packetId - if (nextId) packets[packets.length - 1].nextId = nextId + if (packet) packets[0].previousId = Buffer.from(packet.packetId) + if (nextId) packets[packets.length - 1].nextId = Buffer.from(nextId) // set the .packetId (any maybe the .previousId and .nextId) for (let i = 0; i < packets.length; i++) { - if (packets.length > 1) packets[i].index = i + packets[i].index = i if (i === 0) { packets[0].packetId = await sha256(packets[0].message, { bytes: true }) } else { // all fragments will have the same previous packetId // the index is used to stitch them back together in order. - packets[i].previousId = packets[0].packetId + packets[i].previousId = Buffer.from(packets[0].packetId) } if (packets[i + 1]) { @@ -1021,7 +1025,8 @@ export class Peer { ]), { bytes: true } ) - packets[i].nextId = packets[i + 1].packetId + + packets[i].nextId = Buffer.from(packets[i + 1].packetId) } } @@ -1071,9 +1076,11 @@ export class Peer { // peer a consistent view of the data as it has been published. if (this.onPacket && head) { const p = await this.cache.compose(head) - this.onPacket(p, this.port, this.address, true) - this._onDebug(`-> PUBLISH (multicasted=true, packetId=${p.packetId.toString('hex').slice(0, 8)})`) - return [p] + if (p) { + this.onPacket(p, this.port, this.address, true) + this._onDebug(`-> PUBLISH (multicasted=true, packetId=${p.packetId.toString('hex').slice(0, 8)})`) + return [p] + } } return packets @@ -1159,6 +1166,7 @@ export class Peer { * @ignore */ async _onConnection (packet, peerId, port, address, proxy, socket) { + this._onDebug('<- CONNECTION') if (this.closing) return const natType = packet.message.natType @@ -1176,9 +1184,12 @@ export class Peer { if (oldPeerIndex > -1) this.peers.splice(oldPeerIndex, 1) } + this._onDebug(`<- CONNECTION ADDING PEER ${peer.peerId}@${address}:${port}`) + this.peers.push(peer) } + this._onDebug(`<- CONNECTION AF BRA ${peer.peerId}@${address}:${port}`) peer.connected = true peer.lastUpdate = Date.now() peer.port = port @@ -1204,8 +1215,8 @@ export class Peer { `peerId=${peer.peerId.slice(0, 6)}, ` + `address=${address}:${port}, ` + `type=${packet.type}, ` + - `cluster=${cid.slice(0, 8)}, ` + - `sub-cluster=${scid.slice(0, 8)})` + `clusterId=${cid.slice(0, 6)}, ` + + `subclusterId=${scid.slice(0, 6)})` ) if (this.onJoin && this.clusters[cid]) { @@ -1614,7 +1625,8 @@ export class Peer { `isRendezvous=${packet.message.isRendezvous}, ` + `from=${address}:${port}, ` + `to=${packet.message.address}:${packet.message.port}, ` + - `clustering=${cid.slice(0, 4)}/${scid.slice(0, 4)}` + + `clusterId=${cid.slice(0, 6)}, ` + + `subclusterId=${scid.slice(0, 6)}` + ')') if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) @@ -1825,8 +1837,8 @@ export class Peer { `peerId=${peerId.slice(0, 6)}, ` + `clock=${packet.clock}, ` + `hops=${packet.hops}, ` + - `clusterId=${cid}, ` + - `subclusterId=${scid}, ` + + `clusterId=${cid.slice(0, 6)}, ` + + `subclusterId=${scid.slice(0, 6)}, ` + `address=${address}:${port})` ) @@ -1987,11 +1999,11 @@ export class Peer { this.metrics.i.DROPPED++ const cid = packet.clusterId.toString('base64') const scid = packet.subclusterId.toString('base64') - this._onDebug(`<- DROP (packetId=${pid.slice(0, 8)}, clusterId=${cid}, subclueterId=${scid}, from=${address}:${port}, hops=${packet.hops})`) + this._onDebug(`<- DROP (packetId=${pid.slice(0, 6)}, clusterId=${cid.slice(0, 6)}, subclueterId=${scid.slice(0, 6)}, from=${address}:${port}, hops=${packet.hops})`) return } - this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 8)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) + this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 6)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) this.cacheInsert(packet) const ignorelist = [{ address, port }] @@ -2019,8 +2031,6 @@ export class Peer { this.metrics.i[packet.type]++ const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) const streamTo = packet.usr3.toString('hex') const streamFrom = packet.usr4.toString('hex') @@ -2028,35 +2038,44 @@ export class Peer { // only help packets with a higher hop count if they are in our cluster // if (packet.hops > 2 && !this.clusters[packet.cluster]) return - const peerFrom = this.peers.find(p => p.peerId === streamFrom) - if (!peerFrom) return + this._onDebug(`<- STREAM (from=${address}:${port}, pid=${pid}, hops=${packet.hops}, to=${streamTo}, from=${streamFrom})`) // stream message is for this peer if (streamTo === this.peerId) { + if (this.gate.has(pid)) return + this.gate.set(pid, 1) + + this._onDebug(`<- STREAM ACCEPTED (received=true, from=${address}:${port})`) const scid = packet.subclusterId.toString('base64') if (this.encryption.has(scid)) { let p = packet.copy() // clone the packet so it's not modified if (packet.index > -1) { // if it needs to be composed... + if (packet.index === 0) this.streamBuffer.clear() p.timestamp = Date.now() - this.streamBuffer.set(p.packetId.toString('hex'), p) // cache the partial + this.streamBuffer.set(pid, p) // cache the partial p = await this.cache.compose(p, this.streamBuffer) // try to compose if (!p) return // could not compose - if (p) { // if successful, delete the artifacts - const previousId = p.index === 0 ? p.packetId : p.previousId - const pid = previousId.toString('hex') + this._onDebug(`<- STREAM COMPOSED (pid=${pid.slice(0, 6)}, bufsize=${this.streamBuffer.size})`) - this.streamBuffer.forEach((v, k) => { - if (k === pid) this.streamBuffer.delete(k) - if (v.previousId.toString('hex') === pid) this.streamBuffer.delete(k) - }) - } + const previousId = p.index === 0 ? p.packetId : p.previousId + const parentId = previousId.toString('hex') + + this.streamBuffer.forEach((v, k) => { + if (k === parentId) this.streamBuffer.delete(k) + if (v.previousId.compare(previousId) === 0) this.streamBuffer.delete(k) + }) } - if (this.onStream) this.onStream(p, peerFrom, port, address) + this._onDebug(`<- STREAM COMPLETE (pid=${pid.slice(0, 6)}, bufsize=${this.streamBuffer.size})`) + + if (this.onStream) { + const peerFrom = this.peers.find(p => p.peerId === streamFrom) + if (peerFrom) this.onStream(p, peerFrom, port, address) + } } return @@ -2075,11 +2094,16 @@ export class Peer { } this._onDebug(`>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) + // I am the proxy! this.send(await Packet.encode(packet), peerTo.port, peerTo.address) - if (packet.hops <= 2 && this.natType === NAT.UNRESTRICTED) { - this.mcast(packet, [{ port, address }, { port: peerFrom.port, address: peerFrom.address }]) - } + // + // What % of packets hit the server. + // + + // if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED) { + // this.mcast(packet, [{ port, address }, { port: peerFrom.port, address: peerFrom.address }]) + // } } /** diff --git a/api/latica/packets.js b/api/latica/packets.js index 609eb2418b..13ffbb3f76 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -367,7 +367,7 @@ export class Packet { bufs.push(buf) } - return Buffer.concat(bufs, FRAME_BYTES) + return Buffer.concat(bufs) } static decode (buf) { diff --git a/api/latica/proxy.js b/api/latica/proxy.js index f98e27ca51..bab8ee7e5a 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -10,8 +10,6 @@ * Protocol * */ -import path from '../path.js' -const { pathname } = new URL(import.meta.url) function deepClone (object, map = new Map()) { if (map.has(object)) return map.get(object) @@ -83,7 +81,7 @@ class PeerWorkerProxy { constructor (options, port, fn) { if (!options.isWorkerThread) { this.#channel = new MessageChannel() - this.#worker = new window.Worker(path.join(path.dirname(pathname), 'worker.js')) + this.#worker = new window.Worker(new URL('./worker.js', import.meta.url)) this.#worker.addEventListener('error', err => { throw err @@ -114,7 +112,7 @@ class PeerWorkerProxy { this[prop](data) } } catch (err) { - throw new Error(err) + throw new Error(`Unable to call ${prop} (${err.message})`) } return } @@ -130,7 +128,6 @@ class PeerWorkerProxy { if (err) { p.reject(err) } else { - if (prop === 'open') console.log('<<<', data) p.resolve(data) } @@ -263,13 +260,15 @@ class PeerWorkerProxy { callMainThread (prop, args) { for (const i in args) { const arg = args[i] + if (arg?.constructor.name === 'RemotePeer' || arg?.constructor.name === 'Peer') { - args[i] = { + args[i] = { // what details we want to expose outside of the protocol peerId: arg.peerId, address: arg.address, port: arg.port, natType: arg.natType, - clusters: arg.clusters + clusters: arg.clusters, + connected: arg.connected } delete args[i].localPeer // don't copy this over diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index a8867a9acf..5e4c8e3964 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -58,21 +58,31 @@ namespace SSC { CoreConduit::EncodedMessage CoreConduit::decodeMessage (std::vector<uint8_t>& data) { CoreConduit::EncodedMessage message; + + if (data.size() < 1) return message; + size_t offset = 0; uint8_t numOpts = data[offset++]; for (uint8_t i = 0; i < numOpts; ++i) { + if (offset >= data.size()) continue; + // len uint8_t keyLength = data[offset++]; + if (offset + keyLength > data.size()) continue; // key String key(data.begin() + offset, data.begin() + offset + keyLength); offset += keyLength; + if (offset + 2 > data.size()) continue; + // len uint16_t valueLength = (data[offset] << 8) | data[offset + 1]; offset += 2; + if (offset + valueLength > data.size()) continue; + // val String value(data.begin() + offset, data.begin() + offset + valueLength); offset += valueLength; @@ -80,10 +90,14 @@ namespace SSC { message.options[key] = value; } + if (offset + 2 > data.size()) return message; + // len uint16_t bodyLength = (data[offset] << 8) | data[offset + 1]; offset += 2; + if (offset + bodyLength > data.size()) return message; + // body message.payload = Vector<uint8_t>(data.begin() + offset, data.begin() + offset + bodyLength); diff --git a/src/window/apple.mm b/src/window/apple.mm index 9e4bb77661..4a729292fb 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -214,6 +214,11 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { }); configuration.defaultWebpagePreferences.allowsContentJavaScript = YES; + + #if SOCKET_RUNTIME_PLATFORM_IOS + configuration.allowsInlineMediaPlayback = YES; + #endif + configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; // https://webkit.org/blog/10882/app-bound-domains/ // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/3585117-limitsnavigationstoappbounddomai From 51771be9c9573ac0bf5f934f3f9e11b2d25ca4fc Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 9 Jul 2024 14:22:32 +0200 Subject: [PATCH 0935/1178] fix(api): fixes regression in cache --- api/latica/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index fa521dae7b..feb221cd3c 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -606,7 +606,8 @@ export class Peer { } async cacheInsert (packet) { - this.cache.insert(packet.packetId.toString('hex'), Packet.from(packet)) + const p = Packet.from(packet) + this.cache.insert(p.packetId.toString('hex'), p) } async addIndexedPeer (info) { @@ -1055,11 +1056,13 @@ export class Peer { args.subclusterId = keys.publicKey args.clusterId = args.clusterId || this.config.clusterId + const cache = new Map() const message = this.encryption.seal(args.message, keys) const packets = await this._message2packets(PacketPublish, message, args) const head = packets.find(p => p.index === 0) // has a head, should compose - for (const packet of packets) { + for (let packet of packets) { + packet = Packet.from(packet) this.cacheInsert(packet) if (this.onPacket && packet.index === -1) { @@ -1075,7 +1078,7 @@ export class Peer { // if there is a head, we can recompose the packets, this gives this // peer a consistent view of the data as it has been published. if (this.onPacket && head) { - const p = await this.cache.compose(head) + const p = await this.cache.compose(head, cache) if (p) { this.onPacket(p, this.port, this.address, true) this._onDebug(`-> PUBLISH (multicasted=true, packetId=${p.packetId.toString('hex').slice(0, 8)})`) From 1ccbe839d8bbedcddd7d57ede0172da7979740ab Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 9 Jul 2024 16:09:36 +0200 Subject: [PATCH 0936/1178] chore(api): update protocol --- api/latica/index.js | 43 ++++++++++++++++++++++++++----------------- api/latica/packets.js | 2 -- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index feb221cd3c..3db57f40ae 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -278,7 +278,7 @@ export class Peer { this.encryption = new Encryption() if (!config.peerId) throw new Error('constructor expected .peerId') - if (typeof config.peerId !== 'string' || !PEERID_REGEX.test(config.peerId)) throw new Error(`invalid .peerId (${config.peerId})`) + if (!Peer.isValidPeerId(config.peerId)) throw new Error(`invalid .peerId (${config.peerId})`) // // The purpose of this.config is to seperate transitioned state from initial state. @@ -1008,7 +1008,7 @@ export class Peer { // set the .packetId (any maybe the .previousId and .nextId) for (let i = 0; i < packets.length; i++) { - packets[i].index = i + if (packets.length > 1) packets[i].index = i if (i === 0) { packets[0].packetId = await sha256(packets[0].message, { bytes: true }) @@ -1405,18 +1405,13 @@ export class Peer { this.metrics.i[packet.type]++ this.lastUpdate = Date.now() - const { reflectionId, isReflection, isConnection, isHeartbeat } = packet.message + const { reflectionId, isReflection, isConnection, requesterPeerId } = packet.message - if (packet.message.requesterPeerId === this.peerId) return + if (!Peer.isValidPeerId(requesterPeerId)) return // required field + if (requesterPeerId === this.peerId) return // from self? const { probeExternalPort, isProbe, pingId } = packet.message - if (isHeartbeat) { - // const peer = this.getPeer(packet.message.requesterPeerId) - // if (peer && natType) peer.natType = natType - return - } - // if (peer && reflectionId) peer.reflectionId = reflectionId if (!port) port = packet.message.port if (!address) address = packet.message.address @@ -1425,14 +1420,13 @@ export class Peer { cacheSize: this.cache.size, uptime: this.uptime, responderPeerId: this.peerId, - requesterPeerId: packet.message.requesterPeerId, + requesterPeerId, port, isProbe, address } if (reflectionId) message.reflectionId = reflectionId - if (isHeartbeat) message.isHeartbeat = Date.now() if (pingId) message.pingId = pingId if (isReflection) { @@ -1444,8 +1438,7 @@ export class Peer { } if (isConnection) { - const peerId = packet.message.requesterPeerId - this._onConnection(packet, peerId, port, address) + this._onConnection(packet, requesterPeerId, port, address) message.isConnection = true delete message.address @@ -1481,12 +1474,15 @@ export class Peer { const { reflectionId, pingId, isReflection, responderPeerId } = packet.message + if (!Peer.isValidPeerId(responderPeerId)) return // required field + if (responderPeerId === this.peerId) return // from self? + this._onDebug(`<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) - const peer = this.getPeer(packet.message.responderPeerId) + const peer = this.getPeer(responderPeerId) if (packet.message.isConnection) { if (pingId) peer.pingId = pingId - this._onConnection(packet, packet.message.responderPeerId, port, address) + this._onConnection(packet, responderPeerId, port, address) return } @@ -1594,6 +1590,8 @@ export class Peer { if (packet.hops >= this.maxHops) return if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return + if (!Peer.isValidPeerId(packet.message.requesterPeerId)) return + if (!Peer.isValidPeerId(packet.message.responderPeerId)) return if (packet.message.requesterPeerId === this.peerId) return // intro to myself? if (packet.message.responderPeerId === this.peerId) return // intro from myself? @@ -1813,7 +1811,8 @@ export class Peer { this.metrics.i[packet.type]++ const pid = packet.packetId.toString('hex') - if (packet.message.requesterPeerId === this.peerId) return + if (!Peer.isValidPeerId(packet.message.requesterPeerId)) return // required field + if (packet.message.requesterPeerId === this.peerId) return // from self? if (this.gate.has(pid)) return if (packet.clusterId.length !== 32) return @@ -2218,6 +2217,16 @@ export class Peer { case PacketQuery.type: return this._onQuery(...args) } } + + /** + * Test a peerID is valid + * + * @param {string} pid + * @returns boolean + */ + static isValidPeerId (pid) { + return typeof pid === 'string' && PEERID_REGEX.test(pid) + } } export default Peer diff --git a/api/latica/packets.js b/api/latica/packets.js index 13ffbb3f76..900adeead6 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -388,7 +388,6 @@ export class PacketPing extends Packet { pingId: { type: 'string' }, natType: { type: 'number' }, uptime: { type: 'number' }, - isHeartbeat: { type: 'number' }, cacheSize: { type: 'number' }, isConnection: { type: 'boolean' }, isReflection: { type: 'boolean' }, @@ -414,7 +413,6 @@ export class PacketPong extends Packet { cacheSize: { type: 'number' }, natType: { type: 'number' }, isReflection: { type: 'boolean' }, - isHeartbeat: { type: 'number' }, isConnection: { type: 'boolean' }, reflectionId: { type: 'string' }, pingId: { type: 'string' }, From 2422e5c6c071b97c8b85fe1e0cea1a36748a8d22 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 9 Jul 2024 16:42:02 +0200 Subject: [PATCH 0937/1178] chore(api): update protocol --- api/latica/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index 3db57f40ae..79302b416f 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -1059,10 +1059,10 @@ export class Peer { const cache = new Map() const message = this.encryption.seal(args.message, keys) const packets = await this._message2packets(PacketPublish, message, args) - const head = packets.find(p => p.index === 0) // has a head, should compose for (let packet of packets) { packet = Packet.from(packet) + cache.set(packet.packetId.toString('hex'), packet) this.cacheInsert(packet) if (this.onPacket && packet.index === -1) { @@ -1075,9 +1075,10 @@ export class Peer { this.mcast(packet) } + const head = [...cache.values()][0] // if there is a head, we can recompose the packets, this gives this // peer a consistent view of the data as it has been published. - if (this.onPacket && head) { + if (this.onPacket && head && head.index === 0) { const p = await this.cache.compose(head, cache) if (p) { this.onPacket(p, this.port, this.address, true) From e862bc2f9a3cad543e0b109ebd23dc4186e4ce3d Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 9 Jul 2024 21:19:41 +0200 Subject: [PATCH 0938/1178] chore(api): update protocol --- api/latica/api.js | 15 +++++++++------ api/latica/index.js | 28 +++++++++++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index ad4bbd5730..09ea0e5d1b 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -40,17 +40,20 @@ async function api (options = {}, events, dgram) { const _peer = new Ctor(options, dgram) _peer.onJoin = (packet, ...args) => { - if (Buffer.from(packet.clusterId).compare(clusterId) !== 0) return + packet = Packet.from(packet) + if (packet.clusterId.compare(clusterId) !== 0) return bus._emit('#join', packet, ...args) } _peer.onPacket = (packet, ...args) => { - if (Buffer.from(packet.clusterId).compare(clusterId) !== 0) return + packet = Packet.from(packet) + if (packet.clusterId.compare(clusterId) !== 0) return bus._emit('#packet', packet, ...args) } _peer.onStream = (packet, ...args) => { - if (Buffer.from(packet.clusterId).compare(clusterId) !== 0) return + packet = Packet.from(packet) + if (packet.clusterId.compare(clusterId) !== 0) return bus._emit('#stream', packet, ...args) } @@ -301,7 +304,7 @@ async function api (options = {}, events, dgram) { sub.join = () => _peer.join(sub.sharedKey, options) bus._on('#ready', () => { - const scid = Buffer.from(sub.subclusterId).toString('base64') + const scid = sub.subclusterId.toString('base64') const subcluster = bus.subclusters.get(scid) if (subcluster) _peer.join(subcluster.sharedKey, options) }) @@ -311,7 +314,7 @@ async function api (options = {}, events, dgram) { } bus._on('#join', async (packet, peer) => { - const scid = Buffer.from(packet.subclusterId).toString('base64') + const scid = packet.subclusterId.toString('base64') const sub = bus.subclusters.get(scid) if (!sub) return if (!peer || !peer.peerId) return @@ -355,7 +358,7 @@ async function api (options = {}, events, dgram) { }) const handlePacket = async (packet, peer, port, address) => { - const scid = Buffer.from(packet.subclusterId).toString('base64') + const scid = packet.subclusterId.toString('base64') const sub = bus.subclusters.get(scid) if (!sub) return diff --git a/api/latica/index.js b/api/latica/index.js index 79302b416f..afbdcb22c5 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -1994,28 +1994,42 @@ export class Peer { // if (cluster && cluster[packet.subclusterId]) { const pid = packet.packetId.toString('hex') + const cid = packet.clusterId.toString('base64') + const scid = packet.subclusterId.toString('base64') + + if (this.gate.has(pid)) { + this.metrics.i.DROPPED++ + return + } - if (this.gate.has(pid)) return this.gate.set(pid, 6) if (this.cache.has(pid)) { this.metrics.i.DROPPED++ - const cid = packet.clusterId.toString('base64') - const scid = packet.subclusterId.toString('base64') this._onDebug(`<- DROP (packetId=${pid.slice(0, 6)}, clusterId=${cid.slice(0, 6)}, subclueterId=${scid.slice(0, 6)}, from=${address}:${port}, hops=${packet.hops})`) return } - this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 6)}, from=${address}:${port}, is-sync=${packet.usr4.toString() === 'SYNC'})`) this.cacheInsert(packet) const ignorelist = [{ address, port }] - const scid = packet.subclusterId.toString('base64') if (!this.indexed && this.encryption.has(scid)) { let p = packet.copy() - if (p.index > -1) p = await this.cache.compose(p) - if (p?.index === -1 && this.onPacket) this.onPacket(p, port, address) + + if (p.index > -1) { + this._onDebug(`<- PUBLISH REQUIRES COMPOSE (packetId=${pid.slice(0, 6)}, index=${p.index}, from=${address}:${port})`) + + p = await this.cache.compose(p) + if (p?.isComposed) this._onDebug(`<- PUBLISH COMPOSED (packetId=${pid.slice(0, 6)}, from=${address}:${port})`) + } + + if (p?.index === -1 && this.onPacket) { + this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 6)}, from=${address}:${port})`) + this.onPacket(p, port, address) + } + } else { + this._onDebug(`<- PUBLISH (packetId=${pid.slice(0, 6)}, index=${packet.index}, from=${address}:${port})`) } if (packet.hops >= this.maxHops) return From b359c93ff239c2f3b1393cf319b24417e1c3ecd8 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Wed, 10 Jul 2024 19:04:28 +0200 Subject: [PATCH 0939/1178] chore(api): update protocol --- api/latica/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index afbdcb22c5..599214b1eb 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -1174,6 +1174,7 @@ export class Peer { if (this.closing) return const natType = packet.message.natType + if (!NAT.isValid(natType)) return const { clusterId, subclusterId } = packet @@ -1406,7 +1407,7 @@ export class Peer { this.metrics.i[packet.type]++ this.lastUpdate = Date.now() - const { reflectionId, isReflection, isConnection, requesterPeerId } = packet.message + const { reflectionId, isReflection, isConnection, requesterPeerId, natType } = packet.message if (!Peer.isValidPeerId(requesterPeerId)) return // required field if (requesterPeerId === this.peerId) return // from self? @@ -1438,7 +1439,7 @@ export class Peer { message.natType = this.natType } - if (isConnection) { + if (isConnection && natType) { this._onConnection(packet, requesterPeerId, port, address) message.isConnection = true @@ -1498,7 +1499,15 @@ export class Peer { this.reflectionFirstResponder = { port, address, responderPeerId, reflectionId, packet } if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) this._onDebug('<- NAT REFLECT - STAGE2: FIRST RESPONSE', port, address, this.reflectionId) + this.reflectionFirstReponderTimeout = this._setTimeout(() => { + this.reflectionStage = 0 + this.lastUpdate = 0 + this.reflectionId = null + this.requestReflection() + this._onDebug('<- NAT REFLECTi FAILED TO ACQUIRE SECOND RESPONSE', this.reflectionId) + }, PROBE_WAIT) } else { + this._clearTimeout(this.reflectionFirstReponderTimeout) if (this.onConnecting) this.onConnecting({ code: 2.5, status: `Received reflection from ${address}:${port}` }) this._onDebug('<- NAT REFLECT - STAGE2: SECOND RESPONSE', port, address, this.reflectionId) if (packet.message.address !== this.address) return From 4130fbdcdeb83759e0fa46155eb2f62eb8642fb4 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Wed, 10 Jul 2024 19:17:18 +0200 Subject: [PATCH 0940/1178] chore(api): update protocol, allow specifying worker --- api/latica/api.js | 7 +++++-- api/latica/index.js | 13 +++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index 09ea0e5d1b..6326636910 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -36,7 +36,10 @@ async function api (options = {}, events, dgram) { if (clusterId) clusterId = Buffer.from(clusterId) // some peers don't have clusters - const Ctor = globalThis.window ? PeerWorkerProxy : Peer + let useWorker = globalThis.isSocketRuntime + if (options.worker === false) useWorker = false + + const Ctor = useWorker ? PeerWorkerProxy : Peer const _peer = new Ctor(options, dgram) _peer.onJoin = (packet, ...args) => { @@ -362,7 +365,7 @@ async function api (options = {}, events, dgram) { const sub = bus.subclusters.get(scid) if (!sub) return - const eventName = Buffer.from(packet.usr1).toString('hex') + const eventName = packet.usr1.toString('hex') const { verified, opened } = await unpack(packet) if (verified) packet.verified = true diff --git a/api/latica/index.js b/api/latica/index.js index 599214b1eb..de9348752c 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -1175,12 +1175,15 @@ export class Peer { const natType = packet.message.natType if (!NAT.isValid(natType)) return + if (!Peer.isValidPeerId(peerId)) return + if (peerId === this.peerId) return const { clusterId, subclusterId } = packet let peer = this.getPeer(peerId) + const firstContact = !peer - if (!peer) { + if (firstContact) { peer = new RemotePeer({ peerId }) if (this.peers.length >= 256) { @@ -1214,7 +1217,6 @@ export class Peer { } if (!peer.localPeer) peer.localPeer = this - if (!this.connections) this.connections = new Map() this._onDebug('<- CONNECTION (' + `peerId=${peer.peerId.slice(0, 6)}, ` + @@ -1228,9 +1230,8 @@ export class Peer { this.onJoin(packet, peer, port, address) } - if (!this.connections.has(peer)) { - this.onConnection && this.onConnection(packet, peer, port, address) - this.connections.set(peer, packet.message.cacheSummaryHash) + if (firstContact && this.onConnection) { + this.onConnection(packet, peer, port, address) } } @@ -1503,8 +1504,8 @@ export class Peer { this.reflectionStage = 0 this.lastUpdate = 0 this.reflectionId = null + this._onDebug('<- NAT REFLECT FAILED TO ACQUIRE SECOND RESPONSE', this.reflectionId) this.requestReflection() - this._onDebug('<- NAT REFLECTi FAILED TO ACQUIRE SECOND RESPONSE', this.reflectionId) }, PROBE_WAIT) } else { this._clearTimeout(this.reflectionFirstReponderTimeout) From e376b9833f8a6dd8557bfc3f8d9e70f2f81c2dc8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:16:31 +0200 Subject: [PATCH 0941/1178] fix(window): fix struct init members --- src/window/apple.mm | 2 +- src/window/linux.cc | 2 +- src/window/win.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 4a729292fb..6e1616f34c 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -208,8 +208,8 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { .id = this->bridge.client.id, .index = this->bridge.client.index }, - .conduit = this->core->conduit.port, .index = options.index, + .conduit = this->core->conduit.port, .userScript = options.userScript }); diff --git a/src/window/linux.cc b/src/window/linux.cc index 6b664c9e6a..5e9fee3d8e 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -240,8 +240,8 @@ namespace SSC { .id = this->bridge.client.id, .index = this->bridge.client.index }, - .conduit = this->core->conduit.port, .index = options.index, + .conduit = this->core->conduit.port, .userScript = options.userScript }); diff --git a/src/window/win.cc b/src/window/win.cc index c0aa823fa6..7d156f805e 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -700,8 +700,8 @@ namespace SSC { .id = this->bridge.client.id, .index = this->bridge.client.index }, - .conduit = this->core->conduit.port, .index = options.index, + .conduit = this->core->conduit.port, .userScript = options.userScript }); From 54b179c808d88fc07f9ad31faaf3fcb1c91dc1fe Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:17:21 +0200 Subject: [PATCH 0942/1178] fix(ipc): detect more bad 'ipc:' URIs --- src/ipc/message.cc | 3 ++- src/ipc/preload.hh | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ipc/message.cc b/src/ipc/message.cc index ee6de2043d..0b791a320b 100644 --- a/src/ipc/message.cc +++ b/src/ipc/message.cc @@ -28,6 +28,7 @@ namespace SSC::IPC { // bail if malformed if (str.compare("ipc://") == 0) return; if (str.compare("ipc://?") == 0) return; + if (str.compare("ipc:///?") == 0) return; String query; String path; @@ -37,7 +38,7 @@ namespace SSC::IPC { if (raw.size() > 1) query = raw[1]; auto parts = split(path, '/'); - if (parts.size() >= 1) name = parts[1]; + if (parts.size() > 1) name = parts[1]; if (raw.size() != 2) return; auto pairs = split(raw[1], '&'); diff --git a/src/ipc/preload.hh b/src/ipc/preload.hh index 356fdd4296..9d9af8f96b 100644 --- a/src/ipc/preload.hh +++ b/src/ipc/preload.hh @@ -70,13 +70,13 @@ namespace SSC::IPC { }; bool headless = false; - int conduit = 0; bool debug = false; Features features; - UniqueClient client; + int index = 0; + int conduit = 0; Headers headers = {}; // depends on 'features.useHTMLMarkup' Map metadata; // depends on 'features.useHTMLMarkup' From 83cd891aa805e8efddb08c660699827d07ab78ac Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:17:46 +0200 Subject: [PATCH 0943/1178] refactor(core): introduce lifecycle methods, fix 'CoreConduit::open' --- src/core/core.cc | 47 ++++++++++++++++++++++++++++++++++++- src/core/core.hh | 13 ++++------ src/core/modules/conduit.cc | 35 +++++++++++++-------------- src/core/modules/conduit.hh | 6 ++--- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 9ac2c17a62..3640abcbf0 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -15,14 +15,59 @@ namespace SSC { return; } + this->pause(); this->shuttingDown = true; - #if SOCKET_RUNTIME_PLATFORM_DESKTOP + + #if !SOCKET_RUNTIME_PLATFORM_IOS this->childProcess.shutdown(); #endif + this->stopEventLoop(); this->shuttingDown = false; } + void Core::resume () { + if (!this->isPaused) { + return; + } + + if (this->options.features.useUDP) { + this->udp.resumeAllSockets(); + } + + if (options.features.useNetworkStatus) { + this->networkStatus.start(); + } + + if (options.features.useConduit) { + this->conduit.open(); + } + + this->runEventLoop(); + this->isPaused = false; + } + + void Core::pause () { + if (this->isPaused) { + return; + } + + if (this->options.features.useUDP) { + this->udp.pauseAllSockets(); + } + + if (options.features.useNetworkStatus) { + this->networkStatus.stop(); + } + + if (options.features.useConduit) { + this->conduit.close(); + } + + this->stopEventLoop(); + this->isPaused = true; + } + bool Core::hasPost (uint64_t id) { return posts.find(id) != posts.end(); } diff --git a/src/core/core.hh b/src/core/core.hh index 1f7093238f..0ab01cf5a5 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -130,6 +130,7 @@ namespace SSC { Atomic<bool> didTimersStart = false; Atomic<bool> isLoopRunning = false; Atomic<bool> shuttingDown = false; + Atomic<bool> isPaused = true; uv_loop_t eventLoop; uv_async_t eventLoopAsync; @@ -168,14 +169,8 @@ namespace SSC { timers(this), udp(this) { - initEventLoop(); - if (options.features.useNetworkStatus) { - this->networkStatus.start(); - } - - if (options.features.useConduit) { - this->conduit.open(); - } + this->initEventLoop(); + this->resume(); } Core () @@ -188,6 +183,8 @@ namespace SSC { // called when the application is shutting down void shutdown (); + void resume (); + void pause (); void retainSharedPointerBuffer (SharedPointer<char[]> pointer, unsigned int ttl); void releaseSharedPointerBuffer (SharedPointer<char[]> pointer); diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index 5e4c8e3964..c8cca6f625 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -328,22 +328,28 @@ namespace SSC { }); }); - return true; + return true; } void CoreConduit::open () { - std::promise<int> p; - std::future<int> future = p.get_future(); + if (this->port != 0) { + return; + } + + auto loop = this->core->getEventLoop(); - this->core->dispatchEventLoop([=, &p, this]() mutable { - auto loop = this->core->getEventLoop(); + uv_tcp_init(loop, &this->conduitSocket); + uv_ip4_addr("0.0.0.0", 0, &addr); + uv_tcp_bind(&this->conduitSocket, (const struct sockaddr *)&this->addr, 0); - uv_tcp_init(loop, &this->conduitSocket); - uv_ip4_addr("0.0.0.0", 0, &addr); - uv_tcp_bind(&this->conduitSocket, (const struct sockaddr *)&this->addr, 0); + this->conduitSocket.data = (void*)this; + struct sockaddr_in sockname; + int namelen = sizeof(sockname); + uv_tcp_getsockname(&this->conduitSocket, (struct sockaddr *)&sockname, &namelen); - this->conduitSocket.data = (void*)this; + this->port = ntohs(sockname.sin_port); + this->core->dispatchEventLoop([=, this]() mutable { int r = uv_listen((uv_stream_t*)&this->conduitSocket, 128, [](uv_stream_t *stream, int status) { if (status < 0) { // debug("New connection error %s\n", uv_strerror(status)); @@ -402,19 +408,10 @@ namespace SSC { }); if (r) { + this->port = -1; // debug("Listen error %s\n", uv_strerror(r)); - p.set_value(0); - return; } - - struct sockaddr_in sockname; - int namelen = sizeof(sockname); - uv_tcp_getsockname(&this->conduitSocket, (struct sockaddr *)&sockname, &namelen); - - p.set_value(ntohs(sockname.sin_port)); }); - - this->port = future.get(); } void CoreConduit::close () { diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index ce11447861..21833c055b 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -84,10 +84,10 @@ namespace SSC { CoreConduit::Client* get (uint64_t id) const; std::map<uint64_t, Client*> clients; - int port = 0; + Atomic<int> port = 0; - void open(); - void close(); + void open (); + void close (); private: void handshake (Client *client, const char *request); From c51e7b90f5f8c8f36b75400e7c632ddc56d20fa2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:18:02 +0200 Subject: [PATCH 0944/1178] refactor(app): use 'resume/pause' Core lifecycle methods --- src/app/app.cc | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index c5c9e55779..142b98b920 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -20,6 +20,18 @@ static dispatch_queue_t queue = dispatch_queue_create( self.statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: NSVariableStatusItemLength]; } +- (void) applicationWillBecomeActive: (NSNotification*) notification { + dispatch_async(queue, ^{ + self.app->core->resume(); + }); +} + +- (void) applicationWillResignActive: (NSNotification*) notification { + dispatch_async(queue, ^{ + self.app->core->pause(); + }); +} + - (void) menuWillOpen: (NSMenu*) menu { auto app = self.app; auto window = app->windowManager.getWindow(0); @@ -287,15 +299,13 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType - (void) applicationDidBecomeActive: (UIApplication*) application { dispatch_async(queue, ^{ - self.app->core->udp.resumeAllSockets(); - self.app->core->runEventLoop(); + self.app->core->resume(); }); } - (void) applicationWillResignActive: (UIApplication*) application { dispatch_async(queue, ^{ - self.app->core->stopEventLoop(); - self.app->core->udp.pauseAllSockets(); + self.app->core->pause(); }); } @@ -1269,8 +1279,7 @@ extern "C" { app->jni = nullptr; app->self = nullptr; - app->core->udp.pauseAllSockets(); - app->core->stopEventLoop(); + app->core->pause(); } void ANDROID_EXTERNAL(app, App, onStart)(JNIEnv *env, jobject self) { @@ -1279,8 +1288,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->udp.resumeAllSockets(); - app->core->runEventLoop(); + app->core->resume(); } void ANDROID_EXTERNAL(app, App, onStop)(JNIEnv *env, jobject self) { @@ -1289,8 +1297,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->udp.pauseAllSockets(); - app->core->stopEventLoop(); + app->core->pause(); } void ANDROID_EXTERNAL(app, App, onResume)(JNIEnv *env, jobject self) { @@ -1299,8 +1306,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->udp.resumeAllSockets(); - app->core->runEventLoop(); + app->core->resume(); } void ANDROID_EXTERNAL(app, App, onPause)(JNIEnv *env, jobject self) { @@ -1309,8 +1315,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->udp.pauseAllSockets(); - app->core->stopEventLoop(); + app->core->pause(); } void ANDROID_EXTERNAL(app, App, onPermissionChange)( From da1eb3a448d6d36d046e55343f9ad9547534bc83 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:18:25 +0200 Subject: [PATCH 0945/1178] refactor(cli): provide 'socket_*' C extern impls --- src/cli/cli.cc | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index e9711089a1..9862f9c0fd 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -74,6 +74,7 @@ Thread* sourcesWatcherSupportThread = nullptr; Mutex signalHandlerMutex; Path targetPath; +String settingsSource = ""; Map settings; Map rc; @@ -154,24 +155,30 @@ bool equal (const String& s1, const String& s2) { return s1.compare(s2) == 0; }; -const Map SSC::getUserConfig () { - return settings; -} +extern "C" { + const unsigned char* socket_runtime_init_get_user_config_bytes () { + return reinterpret_cast<const unsigned char*>(settingsSource.c_str()); + } -const String SSC::getDevHost () { - return settings["host"]; -} + unsigned int socket_runtime_init_get_user_config_bytes_size () { + return settingsSource.size(); + } -int SSC::getDevPort () { - if (settings.contains("port")) { - return std::stoi(settings["port"].c_str()); + bool socket_runtime_init_is_debug_enabled () { + return DEBUG == 1; } - return 0; -} + const char* socket_runtime_init_get_dev_host () { + return settings["host"].c_str(); + } -bool SSC::isDebugEnabled () { - return DEBUG == 1; + int socket_runtime_init_get_dev_port () { + if (settings.contains("port")) { + return std::stoi(settings["port"].c_str()); + } + + return 0; + } } void printHelp (const String& command) { @@ -2083,12 +2090,14 @@ int main (const int argc, const char* argv[]) { ); } - settings = INI::parse(tmpl(ini, Map { + settingsSource = tmpl(ini, Map { {"platform.arch", platform.arch}, {"platform.arch.short", replace(platform.arch, "x86_64", "x64")}, {"platform.os", platform.os}, {"platform.os.short", replace(platform.os, "win32", "win")} - })); + }); + + settings = INI::parse(settingsSource); if (settings["meta_type"] == "extension" || settings["build_type"] == "extension") { auto extension = settings["build_name"]; From ea7c12c9cbd76a367976457b14b93a30a64d5736 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:18:48 +0200 Subject: [PATCH 0946/1178] refactor(api/internal/conduit.js): introduce reconnect logic and lifecycle state --- api/internal/conduit.js | 245 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 225 insertions(+), 20 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 351ff9b82b..6be6528331 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -1,12 +1,46 @@ +/* global CloseEvent, ErrorEvent, MessageEvent, WebSocket */ +import client from '../application/client.js' + +export const DEFALUT_MAX_RECONNECT_RETRIES = 32 +export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 + /** * @class Conduit + * @ignore * * @classdesc A class for managing WebSocket connections with custom options and payload encoding. */ -export class Conduit { +export class Conduit extends EventTarget { + static get port () { + return globalThis.__args.conduit + } + + isConnecting = false isActive = false + socket = null + port = 0 id = null + /** + * @type {function(MessageEvent)} + */ + #onmessage = null + + /** + * @type {function(CloseEvent)} + */ + #onclose = null + + /** + * @type {function(ErrorEvent)} + */ + #onerror = null + + /** + * @type {function(Event)} + */ + #onopen = null + /** * Creates an instance of Conduit. * @@ -15,11 +49,179 @@ export class Conduit { * @param {string} params.method - The method to use for the connection. */ constructor ({ id }) { - const port = globalThis.__args.conduit - const clientId = globalThis.__args.client.top.id - const uri = `ws://localhost:${port}/${id}/${clientId}` - this.socket = new globalThis.WebSocket(uri) + super() + + this.id = id + // @ts-ignore + this.port = this.constructor.port + this.connect((err) => { + if (err) { + this.reconnect() + } + }) + + this.addEventListener('close', () => { + this.reconnect() + }) + } + + /** + * The URL string for the WebSocket server. + * @type {string} + */ + get url () { + return `ws://localhost:${this.port}/${this.id}/${client.top.id}` + } + + /** + * @type {function(MessageEvent)} + */ + get onmessage () { return this.#onmessage } + set onmessage (onmessage) { + if (typeof this.#onmessage === 'function') { + this.removeEventListener('message', this.#onmessage) + } + + this.#onmessage = null + + if (typeof onmessage === 'function') { + this.#onmessage = onmessage + this.addEventListener('message', onmessage) + } + } + + /** + * @type {function(ErrorEvent)} + */ + get onerror () { return this.#onerror } + set onerror (onerror) { + if (typeof this.#onerror === 'function') { + this.removeEventListener('error', this.#onerror) + } + + this.#onerror = null + + if (typeof onerror === 'function') { + this.#onerror = onerror + this.addEventListener('error', onerror) + } + } + + /** + * @type {function(CloseEvent)} + */ + get onclose () { return this.#onclose } + set onclose (onclose) { + if (typeof this.#onclose === 'function') { + this.removeEventListener('close', this.#onclose) + } + + this.#onclose = null + + if (typeof onclose === 'function') { + this.#onclose = onclose + this.addEventListener('close', onclose) + } + } + + /** + * @type {function(Event)} + */ + get onopen () { return this.#onopen } + set onopen (onopen) { + if (typeof this.#onopen === 'function') { + this.removeEventListener('open', this.#onopen) + } + + this.#onopen = null + + if (typeof onopen === 'function') { + this.#onopen = onopen + this.addEventListener('open', onopen) + } + } + + /** + * Connects the underlying conduit `WebSocket`. + * @param {function(Error?)=} [callback] + * @return {Conduit} + */ + connect (callback = null) { + if (this.isConnecting) { + return this + } + + if (this.socket) { + this.socket.close() + } + + console.log('connect', this.url) + + // reset + this.isActive = false + this.isConnecting = true + + this.socket = new WebSocket(this.url) this.socket.binaryType = 'arraybuffer' + this.socket.onerror = (e) => { + this.isActive = false + this.isConnecting = false + this.dispatchEvent(new ErrorEvent('error', e)) + if (typeof callback === 'function') { + callback(e.error || new Error('Failed to connect Conduit')) + callback = null + } + } + + this.socket.onclose = (e) => { + this.isActive = false + this.isConnecting = false + this.dispatchEvent(new CloseEvent('close', e)) + } + + this.socket.onopen = (e) => { + this.isActive = true + this.isConnecting = false + this.dispatchEvent(new Event('open')) + if (typeof callback === 'function') { + callback(null) + callback = null + } + } + + this.socket.onmessage = (e) => { + this.isActive = true + this.isConnecting = false + this.dispatchEvent(new MessageEvent('message', e)) + } + + return this + } + + /** + * Reconnects a `Conduit` socket. + * @param {{retries?: number, timeout?: number}} [options] + * @return {Conduit} + */ + reconnect (options = null) { + if (this.isConnecting) { + return this + } + + const retries = options?.retries ?? DEFALUT_MAX_RECONNECT_RETRIES + const timeout = options?.timeout ?? DEFAULT_MAX_RECONNECT_TIMEOUT + + return this.connect((err) => { + if (err) { + this.isActive = false + if (retries > 0) { + setTimeout(() => this.reconnect({ + retries: retries - 1, + timeout + }), timeout) + } + } + }) } /** @@ -53,7 +255,7 @@ export class Conduit { * Encodes options and payload into a single Uint8Array message. * * @private - * @param {Object} options - The options to encode. + * @param {object} options - The options to encode. * @param {Uint8Array} payload - The payload to encode. * @returns {Uint8Array} The encoded message. */ @@ -124,31 +326,34 @@ export class Conduit { * Registers a callback to handle incoming messages. * The callback will receive an error object and an object containing decoded options and payload. * - * @param {Function} cb - The callback function to handle incoming messages. - * @param {Error} cb.error - The error object, if an error occurs. Null if no error. - * @param {Object} cb.message - The decoded message object. - * @param {Object} cb.message.options - The decoded options as key-value pairs. - * @param {Uint8Array} cb.message.payload - The actual data of the payload. + * @param {function(Error?, { options: object, payload: Uint8Array })} cb - The callback function to handle incoming messages. */ receive (cb) { - this.socket.onerror = err => { - cb(err) - } + this.addEventListener('error', (event) => { + // @ts-ignore + cb(event.error) + }) - this.socket.onmessage = event => { - const arrayBuffer = event.data - const data = new Uint8Array(arrayBuffer) + this.addEventListener('message', (event) => { + // @ts-ignore + const data = new Uint8Array(event.data) cb(null, this.decodeMessage(data)) - } + }) } /** * Sends a message with the specified options and payload over the WebSocket connection. * - * @param {Object} options - The options to send. + * @param {object} options - The options to send. * @param {Uint8Array} payload - The payload to send. + * @return {boolean} */ send (options, payload) { - this.socket.send(this.encodeMessage(options, payload)) + if (this.isActive) { + this.socket.send(this.encodeMessage(options, payload)) + return true + } + + return false } } From ec74cc8a7b77c394352126d52f67bcf48f81bb40 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:19:08 +0200 Subject: [PATCH 0947/1178] refactor(api/latica): clean up lint --- api/latica/cache.js | 2 +- api/latica/index.js | 2 +- api/latica/proxy.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/latica/cache.js b/api/latica/cache.js index cc477d9eda..d4e1a8749a 100644 --- a/api/latica/cache.js +++ b/api/latica/cache.js @@ -196,7 +196,7 @@ export class Cache { // follow the chain to get the buffers in order let bufs = [...source.values()].filter(p => { - if (!p.previousId) return + if (!p.previousId) return false return Buffer.from(p.previousId).compare(Buffer.from(previous.packetId)) === 0 }) diff --git a/api/latica/index.js b/api/latica/index.js index de9348752c..471a5a599d 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -964,7 +964,7 @@ export class Peer { const siblings = packet && [...this.cache.data.values()] .filter(Boolean) .filter(p => { - if (!p.previousId || !packet.packetId) return + if (!p.previousId || !packet.packetId) return false return Buffer.from(p.previousId).compare(Buffer.from(packet.packetId)) === 0 }) diff --git a/api/latica/proxy.js b/api/latica/proxy.js index bab8ee7e5a..92e740f1ab 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -262,7 +262,7 @@ class PeerWorkerProxy { const arg = args[i] if (arg?.constructor.name === 'RemotePeer' || arg?.constructor.name === 'Peer') { - args[i] = { // what details we want to expose outside of the protocol + args[i] = { // what details we want to expose outside of the protocol peerId: arg.peerId, address: arg.address, port: arg.port, From 488021d4412eb038d4dd9c241b41bc74eeb5724d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:19:30 +0200 Subject: [PATCH 0948/1178] refactor(api/dgram.js): clean up conduit usage --- api/dgram.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index b066766801..1cd527c8ca 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -784,7 +784,7 @@ export class Socket extends EventEmitter { if (!this.legacy) { this.conduit = new Conduit({ id: this.id }) - this.conduit.receive((err, decoded) => { + this.conduit.receive((_, decoded) => { if (!decoded || !decoded.options) return const rinfo = { @@ -802,9 +802,7 @@ export class Socket extends EventEmitter { dc.channel('message').publish({ socket: this, buffer: message, info }) }) - this.conduit.socket.onopen = () => { - this.conduit.isActive = true - + this.conduit.onopen = () => { startReading(this, (err) => { this.#resource.runInAsyncScope(() => { if (err) { From 6f91240edc408ddd4a9da4dc7758095610e78bd3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:19:52 +0200 Subject: [PATCH 0949/1178] chore(api): generate types + docs --- api/README.md | 40 +++++++-------- api/index.d.ts | 130 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 21 deletions(-) diff --git a/api/README.md b/api/README.md index 5387f954d8..7d47123647 100644 --- a/api/README.md +++ b/api/README.md @@ -549,7 +549,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L632) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L651) Creates a `Socket` instance. @@ -568,12 +568,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L638) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L657) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L719) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L738) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -590,7 +590,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L797) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L853) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -610,7 +610,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L834) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L890) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -618,7 +618,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L893) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L949) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -669,7 +669,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L980) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1036) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -681,7 +681,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1052) +### [`address()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1108) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -697,7 +697,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1087) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1143) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -712,7 +712,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1118) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1174) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -723,7 +723,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1135) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1191) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -734,12 +734,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1148) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1204) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1156) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1212) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -748,31 +748,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1224) +### [`code()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1280) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1230) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1286) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1247) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1303) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1254) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1310) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1262) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1318) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1269) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1325) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1279) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1335) Thrown when a bad port is given. diff --git a/api/index.d.ts b/api/index.d.ts index 9c999dcdad..08547829da 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -8947,6 +8947,120 @@ declare module "socket:constants" { export default _default; } +declare module "socket:internal/conduit" { + export const DEFALUT_MAX_RECONNECT_RETRIES: 32; + export const DEFAULT_MAX_RECONNECT_TIMEOUT: 256; + /** + * @class Conduit + * @ignore + * + * @classdesc A class for managing WebSocket connections with custom options and payload encoding. + */ + export class Conduit extends EventTarget { + static get port(): any; + /** + * Creates an instance of Conduit. + * + * @param {Object} params - The parameters for the Conduit. + * @param {string} params.id - The ID for the connection. + * @param {string} params.method - The method to use for the connection. + */ + constructor({ id }: { + id: string; + method: string; + }); + isConnecting: boolean; + isActive: boolean; + socket: any; + port: number; + id: any; + /** + * The URL string for the WebSocket server. + * @type {string} + */ + get url(): string; + set onmessage(onmessage: (arg0: MessageEvent) => any); + /** + * @type {function(MessageEvent)} + */ + get onmessage(): (arg0: MessageEvent) => any; + set onerror(onerror: (arg0: ErrorEvent) => any); + /** + * @type {function(ErrorEvent)} + */ + get onerror(): (arg0: ErrorEvent) => any; + set onclose(onclose: (arg0: CloseEvent) => any); + /** + * @type {function(CloseEvent)} + */ + get onclose(): (arg0: CloseEvent) => any; + set onopen(onopen: (arg0: Event) => any); + /** + * @type {function(Event)} + */ + get onopen(): (arg0: Event) => any; + /** + * Connects the underlying conduit `WebSocket`. + * @param {function(Error?)=} [callback] + * @return {Conduit} + */ + connect(callback?: ((arg0: Error | null) => any) | undefined): Conduit; + /** + * Reconnects a `Conduit` socket. + * @param {{retries?: number, timeout?: number}} [options] + * @return {Conduit} + */ + reconnect(options?: { + retries?: number; + timeout?: number; + }): Conduit; + /** + * Encodes a single header into a Uint8Array. + * + * @private + * @param {string} key - The header key. + * @param {string} value - The header value. + * @returns {Uint8Array} The encoded header. + */ + private encodeOption; + /** + * Encodes options and payload into a single Uint8Array message. + * + * @private + * @param {object} options - The options to encode. + * @param {Uint8Array} payload - The payload to encode. + * @returns {Uint8Array} The encoded message. + */ + private encodeMessage; + /** + * Decodes a Uint8Array message into options and payload. + * @param {Uint8Array} data - The data to decode. + * @returns {Object} The decoded message containing options and payload. + * @throws Will throw an error if the data is invalid. + */ + decodeMessage(data: Uint8Array): any; + /** + * Registers a callback to handle incoming messages. + * The callback will receive an error object and an object containing decoded options and payload. + * + * @param {function(Error?, { options: object, payload: Uint8Array })} cb - The callback function to handle incoming messages. + */ + receive(cb: (arg0: Error | null, arg1: { + options: object; + payload: Uint8Array; + }) => any): void; + /** + * Sends a message with the specified options and payload over the WebSocket connection. + * + * @param {object} options - The options to send. + * @param {Uint8Array} payload - The payload to send. + * @return {boolean} + */ + send(options: object, payload: Uint8Array): boolean; + #private; + } +} + declare module "socket:ip" { /** * Normalizes input as an IPv4 address string @@ -9063,6 +9177,7 @@ declare module "socket:dgram" { dataListener: ({ detail }: { detail: any; }) => any; + conduit: Conduit; /** * Associates the dgram.Socket to a remote address and port. Every message sent * by this handle is automatically sent to that destination. Also, the socket @@ -9271,6 +9386,7 @@ declare module "socket:dgram" { export default exports; export type SocketOptions = any; import { EventEmitter } from "socket:events"; + import { Conduit } from "socket:internal/conduit"; import { InternalError } from "socket:errors"; import * as exports from "socket:dgram"; @@ -12486,13 +12602,20 @@ declare module "socket:latica/index" { lastUpdate: number; lastRequest: number; localPeer: any; - write(sharedKey: any, args: any): Promise<any>; + write(sharedKey: any, args: any): Promise<any[]>; } /** * `Peer` class factory. * @param {{ createSocket: function('udp4', null, object?): object }} options */ export class Peer { + /** + * Test a peerID is valid + * + * @param {string} pid + * @returns boolean + */ + static isValidPeerId(pid: string): boolean; /** * `Peer` class constructor. * @param {object=} opts - Options @@ -12622,6 +12745,10 @@ declare module "socket:latica/index" { * @ignore */ send(data: Buffer, port: number, address: string, socket?: any): undefined; + /** + * @private + */ + private stream; /** * @private */ @@ -12879,6 +13006,7 @@ declare module "socket:latica/proxy" { cacheInsert(...args: any[]): Promise<any>; mcast(...args: any[]): Promise<any>; requestReflection(...args: any[]): Promise<any>; + stream(...args: any[]): Promise<any>; join(...args: any[]): Promise<any>; publish(...args: any[]): Promise<any>; sync(...args: any[]): Promise<any>; From d8d188e646c608dc858361ab55f145ad3e0ea28d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 19:25:19 +0200 Subject: [PATCH 0950/1178] fix(app): fix typo --- src/app/app.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 142b98b920..48dfac9636 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -241,13 +241,15 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType serviceWorkerWindow->navigate( "socket://" + self.app->userConfig["meta_bundle_identifier"] + "/socket/service-worker/index.html" ); - } else if (self.app->userConfig["webview_service_worker_mode"] == "hybrid") { - self.app->serviceWorkerContainer.init(&defaultWindow->bridge); } auto defaultWindow = self.app->windowManager.createDefaultWindow(Window::Options { .shouldExitApplicationOnClose = true - }); + }); + + if (self.app->userConfig["webview_service_worker_mode"] == "hybrid") { + self.app->serviceWorkerContainer.init(&defaultWindow->bridge); + } defaultWindow->setTitle(self.app->userConfig["meta_title"]); From 8d92571b3fe09cfa4520caff698d52974d38a7f1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 22:18:10 +0200 Subject: [PATCH 0951/1178] fix(app): set 'applicationInstance' to 'nullptr' --- src/app/app.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/app.cc b/src/app/app.cc index 48dfac9636..af6156ef08 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -863,6 +863,7 @@ namespace SSC { } void App::kill () { + applicationInstance = nullptr; this->killed = true; this->core->shutdown(); // Distinguish window closing with app exiting From 4a5ec13051391e2b62dde74ab1a0765f574888c3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 22:18:29 +0200 Subject: [PATCH 0952/1178] refactor(desktop): do not destroy window manager in shutdown --- src/desktop/main.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index ec803547b4..98d74ecc13 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -884,8 +884,6 @@ MAIN { process = nullptr; } - app.windowManager.destroy(); - #if SOCKET_RUNTIME_PLATFORM_APPLE if (app.wasLaunchedFromCli) { debug("__EXIT_SIGNAL__=%d", 0); From 3893d8fbb358b1bb6c53e5c359683d2a30830c25 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 10 Jul 2024 22:18:45 +0200 Subject: [PATCH 0953/1178] refactor(window): check if 'app' is a 'nullptr' --- src/window/linux.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 5e9fee3d8e..b7a3cceed5 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -843,8 +843,12 @@ namespace SSC { G_OBJECT(this->window), "destroy", G_CALLBACK((+[](GtkWidget* object, gpointer arg) { - auto w = reinterpret_cast<Window*>(arg); auto app = App::sharedApplication(); + if (app == nullptr) { + return FALSE; + } + + auto w = reinterpret_cast<Window*>(arg); int index = w != nullptr ? w->index : -1; for (auto& window : app->windowManager.windows) { From ce957479d26f7a8b9e768c17328f377ef1c700ed Mon Sep 17 00:00:00 2001 From: chrisfarms <chris@farmiloe.com> Date: Wed, 3 Jul 2024 16:31:50 +0100 Subject: [PATCH 0954/1178] fix(api/fetch): attempt to pass untyped buffer Buffer.concat expects either a Buffer or a Uint8Array not an untyped ArrayBuffer --- api/fetch/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fetch/index.js b/api/fetch/index.js index 42d77473a7..1ddb3282a4 100644 --- a/api/fetch/index.js +++ b/api/fetch/index.js @@ -72,7 +72,7 @@ async function initBody (body) { for await (const chunk of body) { controller.enqueue(chunk) - chunks.push(chunk) + chunks.push(new Uint8Array(chunk)) } const buffer = Buffer.concat(chunks) From 445020675e390acbb857953843620ba4541988b6 Mon Sep 17 00:00:00 2001 From: chrisfarms <chris@farmiloe.com> Date: Wed, 3 Jul 2024 16:33:17 +0100 Subject: [PATCH 0955/1178] fix(api/internal): url.protocol can be undefined during xhr open "url" is sometimes a string (if url is invalid) protect against this in the protocol check --- api/internal/init.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/internal/init.js b/api/internal/init.js index d151808473..79712e8df2 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -648,8 +648,8 @@ if (typeof globalThis.XMLHttpRequest === 'function') { if ( globalThis.__args?.config?.webview_fetch_allow_runtime_headers === true || - /(socket|ipc|node|npm):/.test(url.protocol) || - protocols.handlers.has(url.protocol.slice(0, -1)) || + (url.protocol && /(socket|ipc|node|npm):/.test(url.protocol)) || + (url.protocol && protocols.handlers.has(url.protocol.slice(0, -1))) || url.hostname === globalThis.__args.config.meta_bundle_identifier ) { for (const key in additionalHeaders) { From 045c6331b6856f14fb1d320d8b954191475a6f98 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 11 Jul 2024 17:54:54 +0200 Subject: [PATCH 0956/1178] refactor(core): conduit cleanup, more diagnostics --- src/core/core.cc | 5 +- src/core/modules/conduit.cc | 164 +++++++++++++++++------------ src/core/modules/conduit.hh | 107 ++++++++++++------- src/core/modules/diagnostics.cc | 18 +++- src/core/modules/diagnostics.hh | 6 ++ src/core/modules/network_status.cc | 39 ++++--- src/core/modules/network_status.hh | 6 +- src/core/socket.cc | 2 +- 8 files changed, 220 insertions(+), 127 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 3640abcbf0..fbddbd8337 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -40,7 +40,7 @@ namespace SSC { } if (options.features.useConduit) { - this->conduit.open(); + this->conduit.start(); } this->runEventLoop(); @@ -61,7 +61,8 @@ namespace SSC { } if (options.features.useConduit) { - this->conduit.close(); + // FIXME(@jwerle) + // this->conduit.stop(); } this->stopEventLoop(); diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index c8cca6f625..abcdca8ee6 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -6,22 +6,27 @@ #define SHA_DIGEST_LENGTH 20 namespace SSC { - const char *WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - std::shared_ptr<char[]> vectorToShared(const std::vector<uint8_t>& vec) { - std::shared_ptr<char[]> sharedArray(new char[vec.size()]); - std::memcpy(sharedArray.get(), vec.data(), vec.size()); - return sharedArray; + static constexpr char WS_GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + static SharedPointer<char[]> vectorToSharedPointer (const Vector<uint8_t>& vector) { + const auto size = vector.size(); + const auto data = vector.data(); + const auto pointer = std::make_shared<char[]>(size); + std::memcpy(pointer.get(), data, size); + return std::move(pointer); } - CoreConduit::~CoreConduit() { - this->close(); + CoreConduit::~CoreConduit () { + this->stop(); } - Vector<uint8_t> CoreConduit::encodeMessage (const CoreConduit::Options& options, const Vector<uint8_t>& payload) { + Vector<uint8_t> CoreConduit::encodeMessage ( + const CoreConduit::Options& options, + const Vector<uint8_t>& payload + ) { Vector<uint8_t> encodedMessage; - std::vector<std::pair<String, String>> sortedOptions(options.begin(), options.end()); + Vector<std::pair<String, String>> sortedOptions(options.begin(), options.end()); std::sort(sortedOptions.begin(), sortedOptions.end()); // the total number of options @@ -56,8 +61,10 @@ namespace SSC { return encodedMessage; } - CoreConduit::EncodedMessage CoreConduit::decodeMessage (std::vector<uint8_t>& data) { - CoreConduit::EncodedMessage message; + CoreConduit::EncodedMessage CoreConduit::decodeMessage ( + const Vector<uint8_t>& data + ) { + EncodedMessage message; if (data.size() < 1) return message; @@ -104,16 +111,19 @@ namespace SSC { return message; } - bool CoreConduit::has (uint64_t id) const { + bool CoreConduit::has (uint64_t id) { + Lock lock(this->mutex); return this->clients.find(id) != this->clients.end(); } - CoreConduit::Client* CoreConduit::get (uint64_t id) const { - auto it = clients.find(id); + CoreConduit::Client* CoreConduit::get (uint64_t id) { + Lock lock(this->mutex); + const auto it = clients.find(id); if (it != clients.end()) { return it->second; } + return nullptr; } @@ -184,10 +194,10 @@ namespace SSC { // debug(response.c_str()); uv_buf_t wrbuf = uv_buf_init(strdup(response.c_str()), response.size()); - uv_write_t *write_req = new uv_write_t; - write_req->bufs = &wrbuf; + uv_write_t *writeRequest = new uv_write_t; + writeRequest->bufs = &wrbuf; - uv_write(write_req, (uv_stream_t*)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { + uv_write(writeRequest, (uv_stream_t*)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { // if (status) debug(stderr, "write error %s\n", uv_strerror(status)); if (req->bufs != nullptr) { @@ -199,10 +209,14 @@ namespace SSC { free(base64_accept_key); - client->is_handshake_done = 1; + client->isHandshakeDone = 1; } - void CoreConduit::processFrame (CoreConduit::Client *client, const char *frame, ssize_t len) { + void CoreConduit::processFrame ( + Client* client, + const char* frame, + ssize_t len + ) { if (len < 2) return; // Frame too short to be valid unsigned char *data = (unsigned char *)frame; @@ -232,12 +246,12 @@ namespace SSC { memcpy(masking_key, data + pos, 4); pos += 4; - if (payload_len > client->frame_buffer_size) { - client->frame_buffer = (unsigned char *)realloc(client->frame_buffer, payload_len); - client->frame_buffer_size = payload_len; + if (payload_len > client->frameBufferSize) { + client->frameBuffer = (unsigned char *)realloc(client->frameBuffer, payload_len); + client->frameBufferSize = payload_len; } - unsigned char *payload = client->frame_buffer; + unsigned char *payload = client->frameBuffer; for (uint64_t i = 0; i < payload_len; i++) { payload[i] = data[pos + i] ^ masking_key[i % 4]; @@ -248,6 +262,11 @@ namespace SSC { Vector<uint8_t> vec(payload, payload + payload_len); auto decoded = this->decodeMessage(vec); + if (!decoded.has("route")) { + // TODO(@jwerle,@heapwolf): handle this + return; + } + /* const auto uri = URL::Builder() .setProtocol("ipc") .setHostname(decoded.pluck("route")) @@ -255,9 +274,6 @@ namespace SSC { .setSearchParams(decoded.getOptionsAsMap()) .build(); */ - auto app = App::sharedApplication(); - auto window = app->windowManager.getWindowForClient({ .id = client->clientId }); - std::stringstream ss; ss << "ipc://"; @@ -270,31 +286,42 @@ namespace SSC { ss << "&" << key << "=" << value; } - const auto invoked = window->bridge.router.invoke(ss.str(), vectorToShared(decoded.payload), decoded.payload.size()); - if (!invoked) { - // debug("there was a problem invoking the router %s", ss.str().c_str()); + const auto app = App::sharedApplication(); + const auto window = app->windowManager.getWindowForClient({ .id = client->clientId }); + if (window != nullptr) { + const auto invoked = window->bridge.router.invoke(ss.str(), vectorToSharedPointer(decoded.payload), decoded.payload.size()); + if (!invoked) { + // TODO(@jwerle,@heapwolf): handle this + // debug("there was a problem invoking the router %s", ss.str().c_str()); + } + } else { + // TODO(@jwerle,@heapwolf): handle this } } - bool SSC::CoreConduit::Client::emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload, size_t length) { - if (!this->self) { - std::cout << "Error: 'self' is a null pointer." << std::endl; + bool SSC::CoreConduit::Client::emit ( + const CoreConduit::Options& options, + SharedPointer<char[]> bytes, + size_t length + ) { + if (!this->conduit) { + // std::cout << "Error: 'conduit' is a null pointer." << std::endl; return false; } - Vector<uint8_t> payloadVec(payload.get(), payload.get() + length); + Vector<uint8_t> payload(bytes.get(), bytes.get() + length); Vector<uint8_t> encodedMessage; try { - encodedMessage = this->self->encodeMessage(options, payloadVec); + encodedMessage = this->conduit->encodeMessage(options, payload); } catch (const std::exception& e) { - std::cerr << "Error in encodeMessage: " << e.what() << std::endl; + // std::cerr << "Error in encodeMessage: " << e.what() << std::endl; return false; } - this->self->core->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { + this->conduit->core->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { size_t encodedLength = encodedMessage.size(); - std::vector<unsigned char> frame; + Vector<unsigned char> frame; if (encodedLength <= 125) { frame.resize(2 + encodedLength); @@ -318,7 +345,7 @@ namespace SSC { uv_buf_t wrbuf = uv_buf_init(reinterpret_cast<char*>(frame.data()), frame.size()); auto writeReq = new uv_write_t; writeReq->bufs = &wrbuf; - writeReq->data = new std::vector<unsigned char>(std::move(frame)); + writeReq->data = new Vector<unsigned char>(std::move(frame)); uv_write(writeReq, (uv_stream_t*)&this->handle, &wrbuf, 1, [](uv_write_t* req, int status) { if (status) { @@ -331,37 +358,38 @@ namespace SSC { return true; } - void CoreConduit::open () { - if (this->port != 0) { + void CoreConduit::start () { + if (this->isStarted) { return; } auto loop = this->core->getEventLoop(); - uv_tcp_init(loop, &this->conduitSocket); - uv_ip4_addr("0.0.0.0", 0, &addr); - uv_tcp_bind(&this->conduitSocket, (const struct sockaddr *)&this->addr, 0); + uv_tcp_init(loop, &this->socket); + uv_ip4_addr("0.0.0.0", this->port.load(), &addr); + uv_tcp_bind(&this->socket, (const struct sockaddr *)&this->addr, 0); - this->conduitSocket.data = (void*)this; + this->socket.data = (void*)this; struct sockaddr_in sockname; int namelen = sizeof(sockname); - uv_tcp_getsockname(&this->conduitSocket, (struct sockaddr *)&sockname, &namelen); + uv_tcp_getsockname(&this->socket, (struct sockaddr *)&sockname, &namelen); this->port = ntohs(sockname.sin_port); + this->isStarted = true; this->core->dispatchEventLoop([=, this]() mutable { - int r = uv_listen((uv_stream_t*)&this->conduitSocket, 128, [](uv_stream_t *stream, int status) { + int r = uv_listen((uv_stream_t*)&this->socket, 128, [](uv_stream_t *stream, int status) { if (status < 0) { // debug("New connection error %s\n", uv_strerror(status)); return; } - auto self = static_cast<CoreConduit*>(stream->data); - auto client = new CoreConduit::Client(self); + auto conduit = static_cast<CoreConduit*>(stream->data); + auto client = new CoreConduit::Client(conduit); - client->is_handshake_done = 0; - client->frame_buffer = nullptr; - client->frame_buffer_size = 0; + client->isHandshakeDone = false; + client->frameBuffer = nullptr; + client->frameBufferSize = 0; client->handle.data = client; uv_loop_t *loop = uv_handle_get_loop((uv_handle_t*)stream); @@ -373,7 +401,7 @@ namespace SSC { uv_read_start( (uv_stream_t *)&client->handle, [](uv_handle_t *handle, size_t size, uv_buf_t *buf) { - buf->base = (char*) new char[size]{0}; + buf->base = new char[size]{0}; buf->len = size; }, [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { @@ -381,10 +409,10 @@ namespace SSC { auto handle = uv_handle_get_data((uv_handle_t*)stream); auto client = (CoreConduit::Client*)(handle); - if (!client->is_handshake_done) { - client->self->handshake(client, buf->base); + if (!client->isHandshakeDone) { + client->conduit->handshake(client, buf->base); } else { - client->self->processFrame(client, buf->base, nread); + client->conduit->processFrame(client, buf->base, nread); } } else if (nread < 0) { if (nread != UV_EOF) { @@ -401,23 +429,28 @@ namespace SSC { return; } else { // debug("uv_accept error: %s\n", uv_strerror(accepted)); - // delete static_cast<std::shared_ptr<CoreConduit::Client>*>(client->handle.data); + // delete static_cast<SharedPointer<CoreConduit::Client>*>(client->handle.data); } uv_close((uv_handle_t *)&client->handle, nullptr); }); if (r) { - this->port = -1; + this->isStarted = false; // debug("Listen error %s\n", uv_strerror(r)); } }); } - void CoreConduit::close () { + void CoreConduit::stop () { + if (!this->isStarted) { + return; + } + + this->isStarted = false; this->core->dispatchEventLoop([this]() mutable { - if (!uv_is_closing((uv_handle_t*)&this->conduitSocket)) { - uv_close((uv_handle_t*)&this->conduitSocket, [](uv_handle_t* handle) { + if (!uv_is_closing((uv_handle_t*)&this->socket)) { + uv_close((uv_handle_t*)&this->socket, [](uv_handle_t* handle) { auto conduit = static_cast<CoreConduit*>(handle->data); }); } @@ -429,11 +462,12 @@ namespace SSC { uv_close((uv_handle_t*)&client->handle, [](uv_handle_t* handle) { auto client = static_cast<CoreConduit::Client*>(handle->data); - if (client->frame_buffer) { - free(client->frame_buffer); - client->frame_buffer = nullptr; - client->frame_buffer_size = 0; + if (client->frameBuffer) { + free(client->frameBuffer); + client->frameBuffer = nullptr; + client->frameBufferSize = 0; } + client->handle.loop = nullptr; delete client; }); } diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 21833c055b..113a41928c 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -6,30 +6,32 @@ namespace SSC { class Core; - class CoreConduit; - class CoreConduit : public CoreModule { - uv_tcp_t conduitSocket; - struct sockaddr_in addr; - mutable std::mutex clientsMutex; - public: using Options = std::unordered_map<String, String>; struct EncodedMessage { Options options; - std::vector<uint8_t> payload; + Vector<uint8_t> payload; - String get (const String& key) const { - auto it = options.find(key); + inline String get (const String& key) const { + const auto it = options.find(key); if (it != options.end()) { return it->second; } return ""; } - String pluck (const String& key) { + inline bool has (const String& key) const { + const auto it = options.find(key); + if (it != options.end()) { + return true; + } + return false; + } + + inline String pluck (const String& key) { auto it = options.find(key); if (it != options.end()) { String value = it->second; @@ -39,60 +41,89 @@ namespace SSC { return ""; } - std::map<String, String> getOptionsAsMap () { - std::map<String, String> omap; + inline Map getOptionsAsMap () { + Map map; for (const auto& pair : this->options) { - omap.insert(pair); + map.insert(pair); } - return omap; + return map; } }; class Client { public: + // client state uint64_t id; uint64_t clientId; + Atomic<bool> isHandshakeDone; + + // uv statae uv_tcp_t handle; - uv_write_t write_req; - uv_shutdown_t shutdown_req; uv_buf_t buffer; - int is_handshake_done; - unsigned char *frame_buffer; - size_t frame_buffer_size; - CoreConduit* self; - bool emit(const CoreConduit::Options& options, std::shared_ptr<char[]> payload_data, size_t length); + // websocket frame buffer state + unsigned char *frameBuffer; + size_t frameBufferSize; - Client(CoreConduit* self) : self(self), id(0), clientId(0), is_handshake_done(0) { - } + CoreConduit* conduit; + + Client (CoreConduit* conduit) + : conduit(conduit), + id(0), + clientId(0), + isHandshakeDone(0) + {} + + ~Client () { + auto handle = reinterpret_cast<uv_handle_t*>(&this->handle); - ~Client() { - if (frame_buffer) { - delete[] frame_buffer; + if (frameBuffer) { + delete [] frameBuffer; + } + + if (handle->loop != nullptr && !uv_is_closing(handle)) { + uv_close(handle, nullptr); } - uv_close((uv_handle_t*)&handle, nullptr); } + + bool emit ( + const CoreConduit::Options& options, + SharedPointer<char[]> payload, + size_t length + ); }; + // state + std::map<uint64_t, Client*> clients; + Mutex mutex; + Atomic<bool> isStarted = false; + Atomic<int> port = 0; + CoreConduit (Core* core) : CoreModule(core) {}; ~CoreConduit (); - EncodedMessage decodeMessage (std::vector<uint8_t>& data); - std::vector<uint8_t> encodeMessage (const CoreConduit::Options& options, const std::vector<uint8_t>& payload); - bool has (uint64_t id) const; - CoreConduit::Client* get (uint64_t id) const; + // codec + EncodedMessage decodeMessage (const Vector<uint8_t>& data); + Vector<uint8_t> encodeMessage ( + const Options& options, + const Vector<uint8_t>& payload + ); - std::map<uint64_t, Client*> clients; - Atomic<int> port = 0; + // client access + bool has (uint64_t id); + CoreConduit::Client* get (uint64_t id); - void open (); - void close (); + // lifecycle + void start (); + void stop (); private: - void handshake (Client *client, const char *request); - void processFrame (Client *client, const char *frame, ssize_t len); + uv_tcp_t socket; + struct sockaddr_in addr; + + void handshake (Client* client, const char *request); + void processFrame (Client* client, const char *frame, ssize_t size); }; } - #endif diff --git a/src/core/modules/diagnostics.cc b/src/core/modules/diagnostics.cc index 6a7b57bfa1..bb211e4006 100644 --- a/src/core/modules/diagnostics.cc +++ b/src/core/modules/diagnostics.cc @@ -89,6 +89,15 @@ namespace SSC { } } while (0); + // conduit + do { + Lock lock(this->core->conduit.mutex); + query.conduit.handles.count = this->core->conduit.clients.size(); + for (const auto& entry : this->core->conduit.clients) { + query.conduit.handles.ids.push_back(entry.first); + } + } while (0); + // uv do { Lock lock(this->core->loopMutex); @@ -204,6 +213,12 @@ namespace SSC { }; } + JSON::Object CoreDiagnostics::ConduitDiagnostic::json () const { + return JSON::Object::Entries { + {"handles", this->handles.json()} + }; + } + JSON::Object CoreDiagnostics::QueryDiagnostic::json () const { return JSON::Object::Entries { {"posts", this->posts.json()}, @@ -212,7 +227,8 @@ namespace SSC { {"fs", this->fs.json()}, {"timers", this->timers.json()}, {"udp", this->udp.json()}, - {"uv", this->uv.json()} + {"uv", this->uv.json()}, + {"conduit", this->conduit.json()} }; } } diff --git a/src/core/modules/diagnostics.hh b/src/core/modules/diagnostics.hh index fad12f231d..adc1a2ed8d 100644 --- a/src/core/modules/diagnostics.hh +++ b/src/core/modules/diagnostics.hh @@ -92,6 +92,11 @@ namespace SSC { JSON::Object json () const override; }; + struct ConduitDiagnostic : public Diagnostic { + Handles handles; + JSON::Object json () const override; + }; + struct QueryDiagnostic : public Diagnostic { PostsDiagnostic posts; ChildProcessDiagnostic childProcess; @@ -100,6 +105,7 @@ namespace SSC { TimersDiagnostic timers; UDPDiagnostic udp; UVDiagnostic uv; + ConduitDiagnostic conduit; JSON::Object json () const override; }; diff --git a/src/core/modules/network_status.cc b/src/core/modules/network_status.cc index 7b2f9ab1d0..7cd15379c7 100644 --- a/src/core/modules/network_status.cc +++ b/src/core/modules/network_status.cc @@ -16,6 +16,23 @@ namespace SSC { attrs ); + #elif SOCKET_RUNTIME_PLATFORM_LINUX + this->monitor = g_network_monitor_get_default(); + #endif + } + + CoreNetworkStatus::~CoreNetworkStatus () { + #if SOCKET_RUNTIME_PLATFORM_APPLE + this->stop(); + dispatch_release(this->queue); + this->monitor = nullptr; + this->queue = nullptr; + #endif + } + + bool CoreNetworkStatus::start () { + this->stop(); + #if SOCKET_RUNTIME_PLATFORM_APPLE this->monitor = nw_path_monitor_create(); nw_path_monitor_set_queue(this->monitor, this->queue); @@ -59,22 +76,6 @@ namespace SSC { this->observers.dispatch(json); }); - #elif SOCKET_RUNTIME_PLATFORM_LINUX - this->monitor = g_network_monitor_get_default(); - #endif - } - - CoreNetworkStatus::~CoreNetworkStatus () { - #if SOCKET_RUNTIME_PLATFORM_APPLE - dispatch_release(this->queue); - nw_release(this->monitor); - this->monitor = nullptr; - this->queue = nullptr; - #endif - } - - bool CoreNetworkStatus::start () { - #if SOCKET_RUNTIME_PLATFORM_APPLE nw_path_monitor_start(this->monitor); return true; #elif SOCKET_RUNTIME_PLATFORM_LINUX @@ -101,7 +102,11 @@ namespace SSC { bool CoreNetworkStatus::stop () { #if SOCKET_RUNTIME_PLATFORM_APPLE - nw_path_monitor_cancel(this->monitor); + if (this->monitor) { + nw_path_monitor_cancel(this->monitor); + nw_release(this->monitor); + } + this->monitor = nullptr; return true; #elif SOCKET_RUNTIME_PLATFORM_LINUX if (this->signal) { diff --git a/src/core/modules/network_status.hh b/src/core/modules/network_status.hh index 68b02262ce..5d59c7e9f9 100644 --- a/src/core/modules/network_status.hh +++ b/src/core/modules/network_status.hh @@ -11,10 +11,10 @@ namespace SSC { using Observers = CoreModule::Observers<Observer>; #if SOCKET_RUNTIME_PLATFORM_APPLE - dispatch_queue_t queue; - nw_path_monitor_t monitor; + dispatch_queue_t queue = nullptr; + nw_path_monitor_t monitor = nullptr; #elif SOCKET_RUNTIME_PLATFORM_LINUX - GNetworkMonitor* monitor; + GNetworkMonitor* monitor = nullptr; guint signal = 0; #endif diff --git a/src/core/socket.cc b/src/core/socket.cc index 0970b1ed66..93cab232a0 100644 --- a/src/core/socket.cc +++ b/src/core/socket.cc @@ -498,7 +498,7 @@ namespace SSC { return this->close(nullptr); } - void Socket::close (std::function<void()> onclose) { + void Socket::close (Function<void()> onclose) { if (this->isClosed()) { this->core->udp.removeSocket(this->id); if (onclose != nullptr) { From 5198e47ceeb80122095b59d9279b26a98d1c05fe Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 11 Jul 2024 17:55:09 +0200 Subject: [PATCH 0957/1178] refactor(api/internal/conduit.js): improve reconnect logic --- api/internal/conduit.js | 85 +++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 6be6528331..bcdf591464 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -4,6 +4,11 @@ import client from '../application/client.js' export const DEFALUT_MAX_RECONNECT_RETRIES = 32 export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 +/** + * @typedef {{ options: object, payload: Uint8Array }} ReceiveMessage + * @typedef {function(Error?, ReceiveCallback | undefined)} ReceiveCallback + */ + /** * @class Conduit * @ignore @@ -15,23 +20,56 @@ export class Conduit extends EventTarget { return globalThis.__args.conduit } + /** + * @type {boolean} + */ + shouldReconnect = true + + /** + * @type {boolean} + */ isConnecting = false + + /** + * @type {boolean} + */ isActive = false + + /** + * @type {WebSocket?} + */ socket = null + + /** + * @type {number} + */ port = 0 + + /** + * @type {number?} + */ id = null /** + * @private + * @type {number} + */ + #loop = 0 + + /** + * @private * @type {function(MessageEvent)} */ #onmessage = null /** + * @private * @type {function(CloseEvent)} */ #onclose = null /** + * @private * @type {function(ErrorEvent)} */ #onerror = null @@ -54,15 +92,19 @@ export class Conduit extends EventTarget { this.id = id // @ts-ignore this.port = this.constructor.port - this.connect((err) => { - if (err) { - this.reconnect() - } - }) + this.connect() - this.addEventListener('close', () => { - this.reconnect() - }) + const reconnectState = { + // TODO(@jwerle): consume from 'options' + retries: DEFALUT_MAX_RECONNECT_RETRIES + } + this.#loop = setInterval(() => { + if (!this.isActive && !this.isConnecting && this.shouldReconnect) { + this.reconnect({ + retries: --reconnectState.retries + }) + } + }, 256) } /** @@ -155,8 +197,6 @@ export class Conduit extends EventTarget { this.socket.close() } - console.log('connect', this.url) - // reset this.isActive = false this.isConnecting = true @@ -324,9 +364,9 @@ export class Conduit extends EventTarget { /** * Registers a callback to handle incoming messages. - * The callback will receive an error object and an object containing decoded options and payload. - * - * @param {function(Error?, { options: object, payload: Uint8Array })} cb - The callback function to handle incoming messages. + * The callback will receive an error object and an object containing + * decoded options and payload. + * @param {ReceiveCallback} cb - The callback function to handle incoming messages. */ receive (cb) { this.addEventListener('error', (event) => { @@ -342,8 +382,8 @@ export class Conduit extends EventTarget { } /** - * Sends a message with the specified options and payload over the WebSocket connection. - * + * Sends a message with the specified options and payload over the + * WebSocket connection. * @param {object} options - The options to send. * @param {Uint8Array} payload - The payload to send. * @return {boolean} @@ -356,4 +396,19 @@ export class Conduit extends EventTarget { return false } + + /** + * Closes the WebSocket connection, preventing reconnects. + */ + close () { + this.shouldReconnect = false + if (this.#loop) { + clearInterval(this.#loop) + this.#loop = 0 + } + if (this.socket) { + this.socket.close() + this.socket = null + } + } } From 4aa415d0898ff6db3dba9cefa0e98a5ff02283e4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:37:04 +0200 Subject: [PATCH 0958/1178] refactor(src/app/app.cc): do not pause core on macos desktop --- src/app/app.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app.cc b/src/app/app.cc index af6156ef08..e26f3461e3 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -28,7 +28,7 @@ static dispatch_queue_t queue = dispatch_queue_create( - (void) applicationWillResignActive: (NSNotification*) notification { dispatch_async(queue, ^{ - self.app->core->pause(); + // self.app->core->pause(); }); } From 7ededcc608e1830e9fde3aa6a2ac486e368a15ff Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:37:39 +0200 Subject: [PATCH 0959/1178] refactor(core/modules/conduit): call callback in 'start() when actually started' --- src/core/core.cc | 3 +-- src/core/modules/conduit.cc | 9 ++++++++- src/core/modules/conduit.hh | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index fbddbd8337..36fca03fef 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -61,8 +61,7 @@ namespace SSC { } if (options.features.useConduit) { - // FIXME(@jwerle) - // this->conduit.stop(); + this->conduit.stop(); } this->stopEventLoop(); diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index abcdca8ee6..ac144b216e 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -358,8 +358,11 @@ namespace SSC { return true; } - void CoreConduit::start () { + void CoreConduit::start (const Function<void()>& callback) { if (this->isStarted) { + if (callback != nullptr) { + callback(); + } return; } @@ -439,6 +442,10 @@ namespace SSC { this->isStarted = false; // debug("Listen error %s\n", uv_strerror(r)); } + + if (callback != nullptr) { + callback(); + } }); } diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 113a41928c..238ee3fa99 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -115,7 +115,7 @@ namespace SSC { CoreConduit::Client* get (uint64_t id); // lifecycle - void start (); + void start (const Function<void()>& callback = nullptr); void stop (); private: From 76def96ef796e097900c52bae464775f96d7461b Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:39:17 +0200 Subject: [PATCH 0960/1178] refactor(ipc/routes): introduce 'internal.conduit.*' routes --- src/ipc/routes.cc | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 3854bdfe6f..6e92dec161 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1655,6 +1655,47 @@ static void mapIPCRoutes (Router *router) { reply(Result { message.seq, message, JSON::Object{} }); }); + /** + * A private API for starting the Runtime `Core::Conduit`, if it isn't running. + */ + router->map("internal.conduit.start", [=](auto message, auto router, auto reply) { + router->bridge->core->conduit.start([=]() { + if (router->bridge->core->conduit.isStarted) { + reply(Result::Data { + message, + JSON::Object::Entries { + {"started", true}, + {"port", router->bridge->core->conduit.port.load()} + } + }); + } else { + const auto err = JSON::Object::Entries {{ "message", "Failed to start Conduit"}}; + reply(Result::Err { message, err }); + } + }); + }); + + /** + * A private API for stopping the Runtime `Core::Conduit`, if it is running. + */ + router->map("internal.conduit.stop", [=](auto message, auto router, auto reply) { + router->bridge->core->conduit.stop(); + reply(Result { message.seq, message, JSON::Object{} }); + }); + + /** + * A private API for getting the status of the Runtime `Core::Conduit. + */ + router->map("internal.conduit.status", [=](auto message, auto router, auto reply) { + reply(Result::Data { + message, + JSON::Object::Entries { + {"started", router->bridge->core->conduit.isStarted.load()}, + {"port", router->bridge->core->conduit.port.load()} + } + }); + }); + /** * Log `value to stdout` with platform dependent logger. * @param value From 0aed4eb6e33ab9721d26ef6836f800ae6c863986 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:39:34 +0200 Subject: [PATCH 0961/1178] fix(window/linux): fix permissions logic --- src/window/linux.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index b7a3cceed5..ffeac0fdbd 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -446,17 +446,17 @@ namespace SSC { } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) { if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { name = "microphone"; - result = userConfig["permissions_allow_microphone"] == "false"; + result = userConfig["permissions_allow_microphone"] != "false"; description = "{{meta_title}} would like access to your microphone."; } if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { name = "camera"; - result = userConfig["permissions_allow_camera"] == "false"; + result = userConfig["permissions_allow_camera"] != "false"; description = "{{meta_title}} would like access to your camera."; } - result = userConfig["permissions_allow_user_media"] == "false"; + result = userConfig["permissions_allow_user_media"] != "false"; } else if (WEBKIT_IS_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST(request)) { name = "storage-access"; result = userConfig["permissions_allow_data_access"] != "false"; @@ -464,6 +464,10 @@ namespace SSC { } else if (WEBKIT_IS_DEVICE_INFO_PERMISSION_REQUEST(request)) { result = userConfig["permissions_allow_device_info"] != "false"; description = "{{meta_title}} would like access to your device information."; + if (result) { + webkit_permission_request_allow(request); + return result; + } } else if (WEBKIT_IS_MEDIA_KEY_SYSTEM_PERMISSION_REQUEST(request)) { result = userConfig["permissions_allow_media_key_system"] != "false"; description = "{{meta_title}} would like access to your media key system."; @@ -493,11 +497,12 @@ namespace SSC { } if (name.size() > 0) { - JSON::Object::Entries json = JSON::Object::Entries { + JSON::Object json = JSON::Object::Entries { {"name", name}, {"state", result ? "granted" : "denied"} }; // TODO(@heapwolf): properly return this data + // TODO(@jwerle): maybe this could be dispatched to webview } return result; From 84b6c4eba2dff49314008d605ae8aeb9b80cbf6e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:40:40 +0200 Subject: [PATCH 0962/1178] fix(api): process, timers, conduit - ensure conduit server - fix missing variadic arguments in callbacks for process.nextTick - fix missing variadic arguments in callbacks for setTimeout/setInterval --- api/internal/conduit.js | 61 +++++++++++++++++++++++++++++++---------- api/process.js | 20 ++++++++------ api/timers/timer.js | 2 +- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index bcdf591464..a480c419e8 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -1,12 +1,16 @@ /* global CloseEvent, ErrorEvent, MessageEvent, WebSocket */ import client from '../application/client.js' +import ipc from '../ipc.js' export const DEFALUT_MAX_RECONNECT_RETRIES = 32 export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 +let defaultConduitPort = globalThis.__args.conduit + /** * @typedef {{ options: object, payload: Uint8Array }} ReceiveMessage * @typedef {function(Error?, ReceiveCallback | undefined)} ReceiveCallback + * @typedef {{ id?: string|BigInt|number, reconnect?: {} }} ConduitOptions */ /** @@ -16,8 +20,15 @@ export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 * @classdesc A class for managing WebSocket connections with custom options and payload encoding. */ export class Conduit extends EventTarget { - static get port () { - return globalThis.__args.conduit + /** + * The global `Conduit` port + * @type {number} + */ + static get port () { return defaultConduitPort } + static set port (port) { + if (port && typeof port === 'number') { + defaultConduitPort = port + } } /** @@ -82,7 +93,7 @@ export class Conduit extends EventTarget { /** * Creates an instance of Conduit. * - * @param {Object} params - The parameters for the Conduit. + * @param {object} params - The parameters for the Conduit. * @param {string} params.id - The ID for the connection. * @param {string} params.method - The method to use for the connection. */ @@ -95,9 +106,10 @@ export class Conduit extends EventTarget { this.connect() const reconnectState = { - // TODO(@jwerle): consume from 'options' + // TODO(@jwerle): eventually consume from 'options' when it exists retries: DEFALUT_MAX_RECONNECT_RETRIES } + this.#loop = setInterval(() => { if (!this.isActive && !this.isConnecting && this.shouldReconnect) { this.reconnect({ @@ -186,9 +198,9 @@ export class Conduit extends EventTarget { /** * Connects the underlying conduit `WebSocket`. * @param {function(Error?)=} [callback] - * @return {Conduit} + * @return {Promise<Conduit>} */ - connect (callback = null) { + async connect (callback = null) { if (this.isConnecting) { return this } @@ -201,16 +213,33 @@ export class Conduit extends EventTarget { this.isActive = false this.isConnecting = true + // @ts-ignore + const resolvers = Promise.withResolvers() + const result = await ipc.send('internal.conduit.start') + + if (result.err) { + if (typeof callback === 'function') { + callback(result.err) + return this + } else { + throw result.err + } + } + + this.port = result.data.port this.socket = new WebSocket(this.url) this.socket.binaryType = 'arraybuffer' this.socket.onerror = (e) => { this.isActive = false this.isConnecting = false this.dispatchEvent(new ErrorEvent('error', e)) + if (typeof callback === 'function') { callback(e.error || new Error('Failed to connect Conduit')) callback = null } + + resolvers.reject(e.error ?? new Error()) } this.socket.onclose = (e) => { @@ -223,10 +252,13 @@ export class Conduit extends EventTarget { this.isActive = true this.isConnecting = false this.dispatchEvent(new Event('open')) + if (typeof callback === 'function') { callback(null) callback = null } + + resolvers.resolve() } this.socket.onmessage = (e) => { @@ -235,15 +267,16 @@ export class Conduit extends EventTarget { this.dispatchEvent(new MessageEvent('message', e)) } + await resolvers.promise return this } /** * Reconnects a `Conduit` socket. * @param {{retries?: number, timeout?: number}} [options] - * @return {Conduit} + * @return {Promise<Conduit>} */ - reconnect (options = null) { + async reconnect (options = null) { if (this.isConnecting) { return this } @@ -251,7 +284,7 @@ export class Conduit extends EventTarget { const retries = options?.retries ?? DEFALUT_MAX_RECONNECT_RETRIES const timeout = options?.timeout ?? DEFAULT_MAX_RECONNECT_TIMEOUT - return this.connect((err) => { + return await this.connect((err) => { if (err) { this.isActive = false if (retries > 0) { @@ -328,7 +361,7 @@ export class Conduit extends EventTarget { /** * Decodes a Uint8Array message into options and payload. * @param {Uint8Array} data - The data to decode. - * @returns {Object} The decoded message containing options and payload. + * @return {ReceiveMessage} The decoded message containing options and payload. * @throws Will throw an error if the data is invalid. */ decodeMessage (data) { @@ -366,18 +399,18 @@ export class Conduit extends EventTarget { * Registers a callback to handle incoming messages. * The callback will receive an error object and an object containing * decoded options and payload. - * @param {ReceiveCallback} cb - The callback function to handle incoming messages. + * @param {ReceiveCallback} callback - The callback function to handle incoming messages. */ - receive (cb) { + receive (callback) { this.addEventListener('error', (event) => { // @ts-ignore - cb(event.error) + callback(event.error || new Error()) }) this.addEventListener('message', (event) => { // @ts-ignore const data = new Uint8Array(event.data) - cb(null, this.decodeMessage(data)) + callback(null, this.decodeMessage(data)) }) } diff --git a/api/process.js b/api/process.js index 90afbdafe4..49c7bafaa8 100644 --- a/api/process.js +++ b/api/process.js @@ -140,8 +140,8 @@ class Process extends EventEmitter { return exit(code) } - nextTick (callback) { - return nextTick(callback) + nextTick (callback, ...args) { + return nextTick(callback, ...args) } hrtime (time = [0, 0]) { @@ -194,23 +194,25 @@ export default process * Adds callback to the 'nextTick' queue. * @param {Function} callback */ -export function nextTick (callback) { +export function nextTick (callback, ...args) { if (isNode && typeof process.nextTick === 'function' && process.nextTick !== nextTick) { - process.nextTick(callback) - } else if (typeof globalThis.setImmediate === 'function') { - globalThis.setImmediate(callback) + process.nextTick(callback, ...args) } else if (typeof globalThis.queueMicrotask === 'function') { globalThis.queueMicrotask(() => { try { - callback() + // eslint-disable-next-line + callback(...args) } catch (err) { setTimeout(() => { throw err }) } }) + } else if (typeof globalThis.setImmediate === 'function') { + globalThis.setImmediate(callback, ...args) } else if (typeof globalThis.setTimeout === 'function') { - globalThis.setTimeout(callback) + globalThis.setTimeout(callback, ...args) } else if (typeof globalThis.requestAnimationFrame === 'function') { - globalThis.requestAnimationFrame(callback) + // eslint-disable-next-line + globalThis.requestAnimationFrame(() => callback(...args)) } else { throw new TypeError('\'process.nextTick\' is not supported in environment.') } diff --git a/api/timers/timer.js b/api/timers/timer.js index 12550cd688..f31c2b3fc2 100644 --- a/api/timers/timer.js +++ b/api/timers/timer.js @@ -25,7 +25,7 @@ export class Timer extends AsyncResource { } this.#create = (callback, ...args) => { - return create(() => this.runInAsyncScope(callback), ...args) + return create((...args) => this.runInAsyncScope(callback, globalThis, ...args), ...args) } this.#destroy = (...args) => { From f630c8dc9e0530047a50c0064b559da864b199d9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:41:44 +0200 Subject: [PATCH 0963/1178] chore(): set version to '0.6.0-next' --- VERSION.txt | 2 +- clib.json | 2 +- .../@socketsupply/socket-darwin-arm64/package.json | 2 +- .../@socketsupply/socket-darwin-x64/package.json | 2 +- .../@socketsupply/socket-linux-arm64/package.json | 2 +- .../@socketsupply/socket-linux-x64/package.json | 2 +- .../@socketsupply/socket-win32-x64/package.json | 2 +- npm/packages/@socketsupply/socket/package.json | 12 ++++++------ 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 7d8568351b..a3a7988754 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.5.4 +0.6.0-next diff --git a/clib.json b/clib.json index 35c68d2106..55e440a4ea 100644 --- a/clib.json +++ b/clib.json @@ -1,7 +1,7 @@ { "name": "socket", "repo": "socketsupply/socket", - "version": "0.5.4", + "version": "0.6.0-next", "description": "Build and package lean, fast, native desktop and mobile applications using the web technologies you already know.", "install": "install.sh", "src": [ diff --git a/npm/packages/@socketsupply/socket-darwin-arm64/package.json b/npm/packages/@socketsupply/socket-darwin-arm64/package.json index b6f0a6527c..2fe3101af2 100644 --- a/npm/packages/@socketsupply/socket-darwin-arm64/package.json +++ b/npm/packages/@socketsupply/socket-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@socketsupply/socket-darwin-arm64", - "version": "0.5.4", + "version": "0.6.0-next", "description": "A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.", "type": "module", "main": "src/index.js", diff --git a/npm/packages/@socketsupply/socket-darwin-x64/package.json b/npm/packages/@socketsupply/socket-darwin-x64/package.json index b6667aa521..1b65140cee 100644 --- a/npm/packages/@socketsupply/socket-darwin-x64/package.json +++ b/npm/packages/@socketsupply/socket-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@socketsupply/socket-darwin-x64", - "version": "0.5.4", + "version": "0.6.0-next", "description": "A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.", "type": "module", "main": "src/index.js", diff --git a/npm/packages/@socketsupply/socket-linux-arm64/package.json b/npm/packages/@socketsupply/socket-linux-arm64/package.json index 56ceae122c..4757c57bbc 100644 --- a/npm/packages/@socketsupply/socket-linux-arm64/package.json +++ b/npm/packages/@socketsupply/socket-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@socketsupply/socket-linux-arm64", - "version": "0.5.4", + "version": "0.6.0-next", "description": "A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.", "type": "module", "main": "src/index.js", diff --git a/npm/packages/@socketsupply/socket-linux-x64/package.json b/npm/packages/@socketsupply/socket-linux-x64/package.json index 7dba08a92a..795ce92cc6 100644 --- a/npm/packages/@socketsupply/socket-linux-x64/package.json +++ b/npm/packages/@socketsupply/socket-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@socketsupply/socket-linux-x64", - "version": "0.5.4", + "version": "0.6.0-next", "description": "A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.", "type": "module", "main": "src/index.js", diff --git a/npm/packages/@socketsupply/socket-win32-x64/package.json b/npm/packages/@socketsupply/socket-win32-x64/package.json index eaed3aa19d..774ce119f2 100644 --- a/npm/packages/@socketsupply/socket-win32-x64/package.json +++ b/npm/packages/@socketsupply/socket-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@socketsupply/socket-win32-x64", - "version": "0.5.4", + "version": "0.6.0-next", "description": "A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.", "type": "module", "main": "src/index.js", diff --git a/npm/packages/@socketsupply/socket/package.json b/npm/packages/@socketsupply/socket/package.json index c1333d0ebc..e9f46f3ff6 100644 --- a/npm/packages/@socketsupply/socket/package.json +++ b/npm/packages/@socketsupply/socket/package.json @@ -1,6 +1,6 @@ { "name": "@socketsupply/socket", - "version": "0.5.4", + "version": "0.6.0-next", "description": "A Cross-Platform, Native Runtime for Desktop and Mobile Apps — Create apps using HTML, CSS, and JavaScript. Written from the ground up to be small and maintainable.", "type": "module", "main": "./index.js", @@ -39,10 +39,10 @@ }, "homepage": "https://github.com/socketsupply/socket#readme", "optionalDependencies": { - "@socketsupply/socket-darwin-arm64": "0.5.4", - "@socketsupply/socket-darwin-x64": "0.5.4", - "@socketsupply/socket-linux-arm64": "0.5.4", - "@socketsupply/socket-linux-x64": "0.5.4", - "@socketsupply/socket-win32-x64": "0.5.4" + "@socketsupply/socket-darwin-arm64": "0.6.0-next", + "@socketsupply/socket-darwin-x64": "0.6.0-next", + "@socketsupply/socket-linux-arm64": "0.6.0-next", + "@socketsupply/socket-linux-x64": "0.6.0-next", + "@socketsupply/socket-win32-x64": "0.6.0-next" } } From 5ea2c6b369f9719a4b9d3ba44af1553fe39016c7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 15:42:02 +0200 Subject: [PATCH 0964/1178] chore(api): generate types + docs --- api/README.md | 540 ++++++++++++++++++++++++------------------------- api/index.d.ts | 81 ++++++-- 2 files changed, 330 insertions(+), 291 deletions(-) diff --git a/api/README.md b/api/README.md index 7d47123647..eee752514f 100644 --- a/api/README.md +++ b/api/README.md @@ -1,7 +1,7 @@ <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Application](https://github.com/socketsupply/socket/blob/master/api/application.js#L13) +# [Application](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L13) Provides Application level methods @@ -11,17 +11,17 @@ import { createWindow } from 'socket:application' ``` -## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/master/api/application.js#L38) +## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L38) This is a `VariableDeclaration` named `MAX_WINDOWS` in `api/application.js`, it's exported but undocumented. -## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/master/api/application.js#L40) +## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L40) This is a `ClassDeclaration` named `ApplicationWindowList` in `api/application.js`, it's exported but undocumented. -## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L165) +## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L165) Returns the current window index @@ -29,7 +29,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L200) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L200) Creates a new window and returns an instance of ApplicationWindow. @@ -67,15 +67,15 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L227) +### [`radius()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L227) -### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L232) +### [`margin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L232) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L320) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L320) Returns the current screen size. @@ -83,7 +83,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L351) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L351) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -95,7 +95,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindowList> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L405) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L405) Returns the ApplicationWindow instance for the given index @@ -107,7 +107,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L415) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L415) Returns the ApplicationWindow instance for the current window. @@ -115,7 +115,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L424) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L424) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -127,7 +127,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L521) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L521) Set the native menu for the app. @@ -222,11 +222,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L528) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L528) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L537) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L537) Set the enabled state of the system menu. @@ -238,23 +238,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L545) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L545) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L551) +## [debug](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L551) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L557) +## [config](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L557) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L562) +## [backend](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L562) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L568) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L568) @@ -267,7 +267,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L576) +### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L576) @@ -279,7 +279,7 @@ The application's backend instance. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Bluetooth](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L12) +# [Bluetooth](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L12) A high-level, cross-platform API for Bluetooth Pub-Sub @@ -289,11 +289,11 @@ The application's backend instance. import { Bluetooth } from 'socket:bluetooth' ``` -## [`Bluetooth` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L32) +## [`Bluetooth` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L32) Create an instance of a Bluetooth service. -### [`constructor(serviceId)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L40) +### [`constructor(serviceId)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L40) constructor is an example property that is set to `true` Creates a new service with key-value pairs @@ -302,7 +302,7 @@ constructor is an example property that is set to `true` | :--- | :--- | :---: | :---: | :--- | | serviceId | string | | false | Given a default value to determine the type | -### [`start()`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L90) +### [`start()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L90) Start the Bluetooth service. @@ -310,7 +310,7 @@ Start the Bluetooth service. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`subscribe(id)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L119) +### [`subscribe(id)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L119) Start scanning for published values that correspond to a well-known UUID. Once subscribed to a UUID, events that correspond to that UUID will be @@ -333,7 +333,7 @@ Start scanning for published values that correspond to a well-known UUID. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`publish(id, value)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L142) +### [`publish(id, value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L142) Start advertising a new value for a well-known UUID @@ -357,7 +357,7 @@ External docs: https://nodejs.org/api/buffer.html <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Crypto](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L15) +# [Crypto](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L15) Some high-level methods around the `crypto.subtle` API for getting @@ -368,16 +368,16 @@ External docs: https://nodejs.org/api/buffer.html import { randomBytes } from 'socket:crypto' ``` -## [webcrypto](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L30) +## [webcrypto](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L30) External docs: https://developer.mozilla.org/en-US/docs/Web/API/Crypto WebCrypto API -## [ready](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L59) +## [ready](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L59) A promise that resolves when all internals to be loaded/ready. -## [`getRandomValues(buffer)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L74) +## [`getRandomValues(buffer)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L74) External docs: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues Generate cryptographically strong random values into the `buffer` @@ -390,7 +390,7 @@ Generate cryptographically strong random values into the `buffer` | :--- | :--- | :--- | | Not specified | TypedArray | | -## [`rand64()`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L97) +## [`rand64()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L97) Generate a random 64-bit number. @@ -398,19 +398,19 @@ Generate a random 64-bit number. | :--- | :--- | :--- | | A random 64-bit number. | BigInt | | -## [RANDOM_BYTES_QUOTA](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L105) +## [RANDOM_BYTES_QUOTA](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L105) Maximum total size of random bytes per page -## [MAX_RANDOM_BYTES](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L110) +## [MAX_RANDOM_BYTES](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L110) Maximum total size for random bytes. -## [MAX_RANDOM_BYTES_PAGES](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L115) +## [MAX_RANDOM_BYTES_PAGES](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L115) Maximum total amount of allocated per page of bytes (max/quota) -## [`randomBytes(size)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L123) +## [`randomBytes(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L123) Generate `size` random bytes. @@ -422,7 +422,7 @@ Generate `size` random bytes. | :--- | :--- | :--- | | Not specified | Buffer | A promise that resolves with an instance of socket.Buffer with random bytes. | -## [`createDigest(algorithm, message)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L150) +## [`createDigest(algorithm, message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L150) @@ -435,7 +435,7 @@ Generate `size` random bytes. | :--- | :--- | :--- | | Not specified | Promise<Buffer> | A promise that resolves with an instance of socket.Buffer with the hash. | -## [`murmur3(value, seed)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L161) +## [`murmur3(value, seed)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L161) A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c that works on strings and `ArrayBuffer` views (typed arrays) @@ -453,7 +453,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [DNS](https://github.com/socketsupply/socket/blob/master/api/dns/index.js#L17) +# [DNS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/index.js#L17) This module enables name resolution. For example, use it to look up IP @@ -468,7 +468,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c import { lookup } from 'socket:dns' ``` -## [`lookup(hostname, options, cb)`](https://github.com/socketsupply/socket/blob/master/api/dns/index.js#L60) +## [`lookup(hostname, options, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/index.js#L60) External docs: https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or @@ -504,7 +504,7 @@ Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [DNS.promises](https://github.com/socketsupply/socket/blob/master/api/dns/promises.js#L17) +# [DNS.promises](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/promises.js#L17) This module enables name resolution. For example, use it to look up IP @@ -519,7 +519,7 @@ Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or import { lookup } from 'socket:dns/promises' ``` -## [`lookup(hostname, opts)`](https://github.com/socketsupply/socket/blob/master/api/dns/promises.js#L37) +## [`lookup(hostname, opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/promises.js#L37) External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options @@ -538,7 +538,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Dgram](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L13) +# [Dgram](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L13) This module provides an implementation of UDP datagram sockets. It does @@ -549,7 +549,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L651) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L651) Creates a `Socket` instance. @@ -568,12 +568,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L657) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L657) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L738) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L738) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -590,7 +590,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L853) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L853) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -610,7 +610,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L890) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L890) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -618,7 +618,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L949) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L949) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -669,7 +669,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1036) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1036) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -681,7 +681,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1108) +### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1108) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -697,7 +697,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1143) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1143) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -712,7 +712,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1174) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1174) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -723,7 +723,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1191) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1191) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -734,12 +734,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1204) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1204) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1212) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1212) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -748,31 +748,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1280) +### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1280) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1286) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1286) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1303) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1303) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1310) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1310) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1318) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1318) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1325) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1325) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1335) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1335) Thrown when a bad port is given. @@ -787,7 +787,7 @@ External docs: https://nodejs.org/api/events.html <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [FS](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L26) +# [FS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L26) This module enables interacting with the file system in a way modeled on @@ -811,7 +811,7 @@ External docs: https://nodejs.org/api/events.html import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L109) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L109) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -823,7 +823,7 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L134) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L134) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` @@ -834,7 +834,7 @@ Synchronously check access a file for a given mode calling `callback` | path | string \| Buffer \| URL | | false | | | mode | string? | F_OK(0) | true | | -## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L151) +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L151) Checks if a path exists @@ -843,7 +843,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L168) +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L168) Checks if a path exists @@ -852,7 +852,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L188) +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L188) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -866,7 +866,7 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L214) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L214) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Synchronously changes the permissions of a file. @@ -877,7 +877,7 @@ Synchronously changes the permissions of a file. | path | string \| Buffer \| URL | | false | | | mode | number | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L238) +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L238) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -888,7 +888,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L267) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L267) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -898,7 +898,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | uid | number | | false | | | gid | number | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L294) +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L294) External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -908,7 +908,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L314) +## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L314) Synchronously close a file descriptor. @@ -916,7 +916,7 @@ Synchronously close a file descriptor. | :--- | :--- | :---: | :---: | :--- | | fd | number | | false | fd | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L331) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L331) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -928,7 +928,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L363) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L363) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -939,7 +939,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L392) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L392) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -953,7 +953,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L437) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L437) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -967,7 +967,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L485) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L485) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -981,7 +981,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L512) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L512) Request that all data for the open file descriptor is flushed to the storage device. @@ -991,7 +991,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L534) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L534) Truncates the file up to `offset` bytes. @@ -1001,7 +1001,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L562) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L562) Chages ownership of link at `path` with `uid` and `gid. @@ -1012,7 +1012,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L592) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L592) Creates a link to `dest` from `src`. @@ -1022,7 +1022,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L680) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L680) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1035,7 +1035,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L733) +## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L733) Synchronously open a file. @@ -1046,7 +1046,7 @@ Synchronously open a file. | mode | string? | 0o666 | true | | | options | object? \| function? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L780) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L780) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1059,7 +1059,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L807) +## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L807) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously open a directory. @@ -1075,7 +1075,7 @@ Synchronously open a directory. | :--- | :--- | :--- | | Not specified | Dir | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L834) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L834) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1089,7 +1089,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L869) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L869) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1103,7 +1103,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L903) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L903) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1116,7 +1116,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L955) +## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L955) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously read all entries in a directory. @@ -1128,7 +1128,7 @@ Synchronously read all entries in a directory. | options.encoding ? utf8 | string? | | true | | | options.withFileTypes ? false | boolean? | | true | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L985) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L985) @@ -1141,7 +1141,7 @@ Synchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1027) +## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1027) @@ -1152,7 +1152,7 @@ Synchronously read all entries in a directory. | options | object? \| function(Error?, Buffer?) | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1090) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1090) Reads link at `path` @@ -1161,7 +1161,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1110) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1110) Computes real path for `path` @@ -1170,7 +1170,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1128) +## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1128) Computes real path for `path` @@ -1178,7 +1178,7 @@ Computes real path for `path` | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1146) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1146) Renames file or directory at `src` to `dest`. @@ -1188,7 +1188,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1172) +## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1172) Renames file or directory at `src` to `dest`, synchronously. @@ -1197,7 +1197,7 @@ Renames file or directory at `src` to `dest`, synchronously. | src | string | | false | | | dest | string | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1196) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1196) Removes directory at `path`. @@ -1206,7 +1206,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1216) +## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1216) Removes directory at `path`, synchronously. @@ -1214,7 +1214,7 @@ Removes directory at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1237) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1237) Synchronously get the stats of a file @@ -1225,7 +1225,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1257) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1257) Get the stats of a file @@ -1238,7 +1238,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1295) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1295) Get the stats of a symbolic link @@ -1251,7 +1251,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1329) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1329) Creates a symlink of `src` at `dest`. @@ -1260,7 +1260,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1372) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1372) Unlinks (removes) file at `path`. @@ -1269,7 +1269,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1392) +## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1392) Unlinks (removes) file at `path`, synchronously. @@ -1277,7 +1277,7 @@ Unlinks (removes) file at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1417) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1417) @@ -1292,7 +1292,7 @@ Unlinks (removes) file at `path`, synchronously. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1462) +## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1462) External docs: https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options Writes data to a file synchronously. @@ -1307,7 +1307,7 @@ Writes data to a file synchronously. | options.flag ? w | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1497) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1497) Watch for changes at `path` calling `callback` @@ -1326,7 +1326,7 @@ Watch for changes at `path` calling `callback` <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [FS.promises](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L25) +# [FS.promises](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L25) * This module enables interacting with the file system in a way modeled on @@ -1350,7 +1350,7 @@ Watch for changes at `path` calling `callback` import fs from 'socket:fs/promises' ``` -## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L107) +## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L107) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode Asynchronously check access a file. @@ -1361,7 +1361,7 @@ Asynchronously check access a file. | mode | string? | | true | | | options | object? | | true | | -## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L118) +## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L118) External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode @@ -1375,7 +1375,7 @@ External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L143) +## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L143) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -1389,7 +1389,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L172) +## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L172) Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -1403,7 +1403,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | :--- | :--- | :--- | | Not specified | Promise | | -## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L202) +## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L202) Chages ownership of link at `path` with `uid` and `gid. @@ -1417,7 +1417,7 @@ Chages ownership of link at `path` with `uid` and `gid. | :--- | :--- | :--- | | Not specified | Promise | | -## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L230) +## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L230) Creates a link to `dest` from `dest`. @@ -1430,7 +1430,7 @@ Creates a link to `dest` from `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L258) +## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L258) Asynchronously creates a directory. @@ -1446,7 +1446,7 @@ Asynchronously creates a directory. | :--- | :--- | :--- | | Not specified | Promise<any> | Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. | -## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L288) +## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L288) External docs: https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode Asynchronously open a file. @@ -1462,7 +1462,7 @@ Asynchronously open a file. | :--- | :--- | :--- | | Not specified | Promise<FileHandle> | | -## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L301) +## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L301) External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options @@ -1478,7 +1478,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options | :--- | :--- | :--- | | Not specified | Promise<Dir> | | -## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L314) +## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L314) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options @@ -1490,7 +1490,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | options.encoding | string? | utf8 | true | | | options.withFileTypes | boolean? | false | true | | -## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L352) +## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L352) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options @@ -1507,7 +1507,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | :--- | :--- | :--- | | Not specified | Promise<Buffer \| string> | | -## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L370) +## [`readlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L370) Reads link at `path` @@ -1519,7 +1519,7 @@ Reads link at `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L391) +## [`realpath(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L391) Computes real path for `path` @@ -1531,7 +1531,7 @@ Computes real path for `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L413) +## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L413) Renames file or directory at `src` to `dest`. @@ -1544,7 +1544,7 @@ Renames file or directory at `src` to `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L437) +## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L437) Removes directory at `path`. @@ -1556,7 +1556,7 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L459) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L459) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options Get the stats of a file @@ -1571,7 +1571,7 @@ Get the stats of a file | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L474) +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L474) External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options Get the stats of a symbolic link. @@ -1586,7 +1586,7 @@ Get the stats of a symbolic link. | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L487) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L487) Creates a symlink of `src` at `dest`. @@ -1599,7 +1599,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L523) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L523) Unlinks (removes) file at `path`. @@ -1611,7 +1611,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L548) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L548) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1630,7 +1630,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L569) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L569) Watch for changes at `path` calling `callback` @@ -1649,7 +1649,7 @@ Watch for changes at `path` calling `callback` <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [IPC](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L37) +# [IPC](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L37) This is a low-level API that you don't need unless you are implementing @@ -1683,17 +1683,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L270) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L270) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1022) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1022) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1183) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1183) Emit event to be dispatched on `window` object. @@ -1704,7 +1704,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1242) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1242) Sends an async IPC command request with parameters. @@ -1720,27 +1720,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1693) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1693) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1725) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1725) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1774) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1774) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1776) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1776) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1981) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1981) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. @@ -1749,7 +1749,7 @@ This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Network](https://github.com/socketsupply/socket/blob/master/api/network.js#L9) +# [Network](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/network.js#L9) External docs: https://socketsupply.co/guides/#p2p-guide @@ -1760,7 +1760,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [OS](https://github.com/socketsupply/socket/blob/master/api/os.js#L13) +# [OS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L13) This module provides normalized system information from all the major @@ -1771,7 +1771,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide import { arch, platform } from 'socket:os' ``` -## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L60) +## [`arch()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L60) Returns the operating system CPU architecture for which Socket was compiled. @@ -1779,7 +1779,7 @@ Returns the operating system CPU architecture for which Socket was compiled. | :--- | :--- | :--- | | Not specified | string | 'arm64', 'ia32', 'x64', or 'unknown' | -## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L78) +## [`cpus()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L78) External docs: https://nodejs.org/api/os.html#os_os_cpus Returns an array of objects containing information about each CPU/core. @@ -1797,7 +1797,7 @@ Returns an array of objects containing information about each CPU/core. | :--- | :--- | :--- | | cpus | Array<object> | An array of objects containing information about each CPU/core. | -## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L102) +## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L102) External docs: https://nodejs.org/api/os.html#os_os_networkinterfaces Returns an object containing network interfaces that have been assigned a network address. @@ -1815,7 +1815,7 @@ Returns an object containing network interfaces that have been assigned a networ | :--- | :--- | :--- | | Not specified | object | An object containing network interfaces that have been assigned a network address. | -## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L190) +## [`platform()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L190) External docs: https://nodejs.org/api/os.html#os_os_platform Returns the operating system platform. @@ -1825,7 +1825,7 @@ Returns the operating system platform. | :--- | :--- | :--- | | Not specified | string | 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' | -## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L199) +## [`type()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L199) External docs: https://nodejs.org/api/os.html#os_os_type Returns the operating system name. @@ -1834,7 +1834,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' | -## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L238) +## [`isWindows()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L238) @@ -1842,7 +1842,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | boolean | `true` if the operating system is Windows. | -## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L250) +## [`tmpdir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L250) @@ -1850,15 +1850,15 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system's default directory for temporary files. | -## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L298) +## [EOL](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L298) The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. -## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L310) +## [`rusage()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L310) Get resource usage. -## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L320) +## [`uptime()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L320) Returns the system uptime in seconds. @@ -1866,7 +1866,7 @@ Returns the system uptime in seconds. | :--- | :--- | :--- | | Not specified | number | The system uptime in seconds. | -## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L331) +## [`uname()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L331) Returns the operating system name. @@ -1874,7 +1874,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system name. | -## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L389) +## [`homedir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L389) Returns the home directory of the current user. @@ -1886,7 +1886,7 @@ Returns the home directory of the current user. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L9) +# [Path](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L9) Example usage: @@ -1894,7 +1894,7 @@ Returns the home directory of the current user. import { Path } from 'socket:path' ``` -## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L44) +## [`resolve()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L44) External docs: https://nodejs.org/api/path.html#path_path_resolve_paths The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -1907,7 +1907,7 @@ The path.resolve() method resolves a sequence of paths or path segments into an | :--- | :--- | :--- | | Not specified | string | | -## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L82) +## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L82) Computes current working directory for a path @@ -1920,7 +1920,7 @@ Computes current working directory for a path | :--- | :--- | :--- | | Not specified | string | | -## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L106) +## [`origin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L106) Computed location origin. Defaults to `socket:///` if not available. @@ -1928,7 +1928,7 @@ Computed location origin. Defaults to `socket:///` if not available. | :--- | :--- | :--- | | Not specified | string | | -## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L117) +## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L117) Computes the relative path from `from` to `to`. @@ -1942,7 +1942,7 @@ Computes the relative path from `from` to `to`. | :--- | :--- | :--- | | Not specified | string | | -## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L164) +## [`join(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L164) Joins path components. This function may not return an absolute path. @@ -1955,7 +1955,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L221) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L221) Computes directory name of path. @@ -1968,7 +1968,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L263) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L263) Computes base name of path. @@ -1981,7 +1981,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L277) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L277) Computes extension name of path. @@ -1994,7 +1994,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L288) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L288) Computes normalized path @@ -2007,7 +2007,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L338) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L338) Formats `Path` object into a string. @@ -2020,7 +2020,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L354) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L354) Parses input `path` into a `Path` instance. @@ -2032,11 +2032,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L382) +## [Path](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L382) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L388) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L388) Creates a `Path` instance from `input` and optional `cwd`. @@ -2045,7 +2045,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L411) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L411) `Path` class constructor. @@ -2054,47 +2054,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L484) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L484) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L491) +### [`value()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L491) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L525) +### [`source()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L525) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L533) +### [`parent()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L533) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L552) +### [`root()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L552) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L573) +### [`dir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L573) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L608) +### [`base()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L608) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L620) +### [`name()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L620) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L628) +### [`ext()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L628) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L648) +### [`drive()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L648) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L655) +### [`toURL()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L655) @@ -2102,7 +2102,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L663) +### [`toString()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L663) Converts this `Path` instance to a string. @@ -2114,7 +2114,7 @@ Converts this `Path` instance to a string. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Process](https://github.com/socketsupply/socket/blob/master/api/process.js#L9) +# [Process](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L9) Example usage: @@ -2122,22 +2122,22 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L18) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L18) This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. -## [`ProcessEnvironment` (extends `EventTarget`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L29) +## [`ProcessEnvironment` (extends `EventTarget`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L29) This is a `ClassDeclaration` named ``ProcessEnvironment` (extends `EventTarget`)` in `api/process.js`, it's exported but undocumented. -## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L34) +## [env](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L34) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L197) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L197) Adds callback to the 'nextTick' queue. @@ -2145,7 +2145,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L228) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L230) Computed high resolution time as a `BigInt`. @@ -2157,7 +2157,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L254) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L256) @@ -2165,7 +2165,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L266) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L268) Returns an object describing the memory usage of the Node.js process measured in bytes. @@ -2177,7 +2177,7 @@ Returns an object describing the memory usage of the Node.js process measured in <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Test](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L17) +# [Test](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L17) Provides a test runner for Socket Runtime. @@ -2191,7 +2191,7 @@ Returns an object describing the memory usage of the Node.js process measured in }) ``` -## [`getDefaultTestRunnerTimeout()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L54) +## [`getDefaultTestRunnerTimeout()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L54) @@ -2199,11 +2199,11 @@ Returns an object describing the memory usage of the Node.js process measured in | :--- | :--- | :--- | | Not specified | number | The default timeout for tests in milliseconds. | -## [Test](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L69) +## [Test](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L69) -### [`constructor(name, fn, runner)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L127) +### [`constructor(name, fn, runner)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L127) @@ -2213,7 +2213,7 @@ Returns an object describing the memory usage of the Node.js process measured in | fn | TestFn | | false | | | runner | TestRunner | | false | | -### [`comment(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L138) +### [`comment(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L138) @@ -2221,7 +2221,7 @@ Returns an object describing the memory usage of the Node.js process measured in | :--- | :--- | :---: | :---: | :--- | | msg | string | | false | | -### [`plan(n)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L148) +### [`plan(n)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L148) Plan the number of assertions. @@ -2230,7 +2230,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | n | number | | false | | -### [`deepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L159) +### [`deepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L159) @@ -2240,7 +2240,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`notDeepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L174) +### [`notDeepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L174) @@ -2250,7 +2250,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`equal(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L189) +### [`equal(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L189) @@ -2260,7 +2260,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`notEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L204) +### [`notEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L204) @@ -2270,7 +2270,7 @@ Plan the number of assertions. | expected | unknown | | false | | | msg | string | | true | | -### [`fail(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L217) +### [`fail(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L217) @@ -2278,7 +2278,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | msg | string | | true | | -### [`ok(actual, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L230) +### [`ok(actual, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L230) @@ -2287,7 +2287,7 @@ Plan the number of assertions. | actual | unknown | | false | | | msg | string | | true | | -### [`pass(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L242) +### [`pass(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L242) @@ -2295,7 +2295,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | msg | string | | true | | -### [`ifError(err, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L251) +### [`ifError(err, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L251) @@ -2304,7 +2304,7 @@ Plan the number of assertions. | err | Error \| null \| undefined | | false | | | msg | string | | true | | -### [`throws(fn, expected, message)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L264) +### [`throws(fn, expected, message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L264) @@ -2314,7 +2314,7 @@ Plan the number of assertions. | expected | RegExp \| any | | true | | | message | string | | true | | -### [`sleep(ms, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L313) +### [`sleep(ms, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L313) Sleep for ms with an optional msg @@ -2332,7 +2332,7 @@ Sleep for ms with an optional msg | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`requestAnimationFrame(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L331) +### [`requestAnimationFrame(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L331) Request animation frame with an optional msg. Falls back to a 0ms setTimeout when tests are run headlessly. @@ -2350,7 +2350,7 @@ Request animation frame with an optional msg. Falls back to a 0ms setTimeout whe | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`click(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L354) +### [`click(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L354) Dispatch the `click`` method on an element specified by selector. @@ -2368,7 +2368,7 @@ Dispatch the `click`` method on an element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`eventClick(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L380) +### [`eventClick(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L380) Dispatch the click window.MouseEvent on an element specified by selector. @@ -2386,7 +2386,7 @@ Dispatch the click window.MouseEvent on an element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`dispatchEvent(event, target, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L408) +### [`dispatchEvent(event, target, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L408) Dispatch an event on the target. @@ -2405,7 +2405,7 @@ Dispatch an event on the target. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`focus(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L428) +### [`focus(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L428) Call the focus method on element specified by selector. @@ -2423,7 +2423,7 @@ Call the focus method on element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`blur(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L452) +### [`blur(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L452) Call the blur method on element specified by selector. @@ -2441,7 +2441,7 @@ Call the blur method on element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`type(selector, str, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L477) +### [`type(selector, str, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L477) Consecutively set the str value of the element specified by selector to simulate typing. @@ -2460,7 +2460,7 @@ Consecutively set the str value of the element specified by selector to simulate | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`appendChild(parentSelector, el, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L509) +### [`appendChild(parentSelector, el, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L509) appendChild an element el to a parent selector element. @@ -2480,7 +2480,7 @@ appendChild an element el to a parent selector element. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`removeElement(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L529) +### [`removeElement(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L529) Remove an element from the DOM. @@ -2498,7 +2498,7 @@ Remove an element from the DOM. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`elementVisible(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L548) +### [`elementVisible(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L548) Test if an element is visible @@ -2516,7 +2516,7 @@ Test if an element is visible | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`elementInvisible(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L569) +### [`elementInvisible(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L569) Test if an element is invisible @@ -2534,7 +2534,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`waitFor(querySelectorOrFn, opts, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L593) +### [`waitFor(querySelectorOrFn, opts, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L593) Test if an element is invisible @@ -2555,7 +2555,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<HTMLElement \| Element \| void> | | -### [`waitForText(selector, opts, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L655) +### [`waitForText(selector, opts, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L655) Test if an element is invisible @@ -2585,7 +2585,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<HTMLElement \| Element \| void> | | -### [`querySelector(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L692) +### [`querySelector(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L692) Run a querySelector as an assert and also get the results @@ -2603,7 +2603,7 @@ Run a querySelector as an assert and also get the results | :--- | :--- | :--- | | Not specified | HTMLElement \| Element | | -### [`querySelectorAll(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L711) +### [`querySelectorAll(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L711) Run a querySelectorAll as an assert and also get the results @@ -2621,7 +2621,7 @@ Run a querySelectorAll as an assert and also get the results | :--- | :--- | :--- | | Not specified | Array<HTMLElement \| Element> | | -### [`getComputedStyle(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L740) +### [`getComputedStyle(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L740) Retrieves the computed styles for a given element. @@ -2646,17 +2646,17 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | CSSStyleDeclaration | The computed styles of the element. | -### [`run()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L837) +### [`run()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L837) pass: number, fail: number }>} -## [TestRunner](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L918) +## [TestRunner](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L918) -### [`constructor(report)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L969) +### [`constructor(report)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L969) @@ -2664,7 +2664,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :---: | :---: | :--- | | report | (lines: string) => void | | true | | -### [`nextId()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L978) +### [`nextId()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L978) @@ -2672,11 +2672,11 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | string | | -### [`length()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L985) +### [`length()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L985) -### [`add(name, fn, only)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L995) +### [`add(name, fn, only)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L995) @@ -2686,7 +2686,7 @@ Retrieves the computed styles for a given element. | fn | TestFn | | false | | | only | boolean | | false | | -### [`run()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1017) +### [`run()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1017) @@ -2694,7 +2694,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`onFinish())`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1064) +### [`onFinish())`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1064) @@ -2702,7 +2702,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :---: | :---: | :--- | | ) | (result: { total: number, success: number, fail: number | > void} callback | false | | -## [`only(name, fn)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1092) +## [`only(name, fn)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1092) @@ -2711,7 +2711,7 @@ Retrieves the computed styles for a given element. | name | string | | false | | | fn | TestFn | | true | | -## [`skip(_name, _fn)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1102) +## [`skip(_name, _fn)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1102) @@ -2720,7 +2720,7 @@ Retrieves the computed styles for a given element. | _name | string | | false | | | _fn | TestFn | | true | | -## [`setStrict(strict)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1108) +## [`setStrict(strict)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1108) @@ -2732,7 +2732,7 @@ Retrieves the computed styles for a given element. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Window](https://github.com/socketsupply/socket/blob/master/api/window.js#L12) +# [Window](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L12) Provides ApplicationWindow class and methods @@ -2741,15 +2741,15 @@ Retrieves the computed styles for a given element. `socket:application` methods like `getCurrentWindow`, `createWindow`, `getWindow`, and `getWindows`. -## [ApplicationWindow](https://github.com/socketsupply/socket/blob/master/api/window.js#L35) +## [ApplicationWindow](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L35) Represents a window in the application -### [`id()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L69) +### [`id()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L69) The unique ID of this window. -### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L77) +### [`index()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L77) Get the index of the window @@ -2757,15 +2757,15 @@ Get the index of the window | :--- | :--- | :--- | | Not specified | number | the index of the window | -### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L84) +### [`hotkey()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L84) -### [`channel()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L92) +### [`channel()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L92) The broadcast channel for this window. -### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L100) +### [`getSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L100) Get the size of the window @@ -2773,7 +2773,7 @@ Get the size of the window | :--- | :--- | :--- | | Not specified | { width: number, height: number | } - the size of the window | -### [`getPosition()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L111) +### [`getPosition()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L111) Get the position of the window @@ -2781,7 +2781,7 @@ Get the position of the window | :--- | :--- | :--- | | Not specified | { x: number, y: number | } - the position of the window | -### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L122) +### [`getTitle()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L122) Get the title of the window @@ -2789,7 +2789,7 @@ Get the title of the window | :--- | :--- | :--- | | Not specified | string | the title of the window | -### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L130) +### [`getStatus()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L130) Get the status of the window @@ -2797,7 +2797,7 @@ Get the status of the window | :--- | :--- | :--- | | Not specified | string | the status of the window | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L138) +### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L138) Close the window @@ -2805,7 +2805,7 @@ Close the window | :--- | :--- | :--- | | Not specified | Promise<object> | the options of the window | -### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L153) +### [`show()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L153) Shows the window @@ -2813,7 +2813,7 @@ Shows the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L162) +### [`hide()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L162) Hides the window @@ -2821,7 +2821,7 @@ Hides the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L171) +### [`maximize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L171) Maximize the window @@ -2829,7 +2829,7 @@ Maximize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L180) +### [`minimize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L180) Minimize the window @@ -2837,7 +2837,7 @@ Minimize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L189) +### [`restore()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L189) Restore the window @@ -2845,7 +2845,7 @@ Restore the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L199) +### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L199) Sets the title of the window @@ -2857,7 +2857,7 @@ Sets the title of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L212) +### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L212) Sets the size of the window @@ -2871,7 +2871,7 @@ Sets the size of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L255) +### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L255) Sets the position of the window @@ -2885,7 +2885,7 @@ Sets the position of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L299) +### [`navigate(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L299) Navigate the window to a given path @@ -2897,7 +2897,7 @@ Navigate the window to a given path | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L308) +### [`showInspector()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L308) Opens the Web Inspector for the window @@ -2905,7 +2905,7 @@ Opens the Web Inspector for the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L325) +### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L325) Sets the background color of the window @@ -2921,7 +2921,7 @@ Sets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L334) +### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L334) Gets the background color of the window @@ -2929,7 +2929,7 @@ Gets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L343) +### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L343) Opens a native context menu. @@ -2941,7 +2941,7 @@ Opens a native context menu. | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L352) +### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L352) Shows a native open file dialog. @@ -2953,7 +2953,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L370) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L370) Shows a native save file dialog. @@ -2965,7 +2965,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L388) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L388) Shows a native directory dialog. @@ -2977,7 +2977,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L413) +### [`send(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L413) This is a high-level API that you should use instead of `ipc.request` when you want to send a message to another window or to the backend. @@ -2991,7 +2991,7 @@ This is a high-level API that you should use instead of `ipc.request` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L454) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L454) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -3004,7 +3004,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L473) +### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L473) Opens an URL in the default application associated with the URL protocol, such as 'https:' for the default web browser. @@ -3017,7 +3017,7 @@ Opens an URL in the default application associated with the URL protocol, | :--- | :--- | :--- | | Not specified | Promise<{ url: string | >} | -### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L488) +### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L488) Opens a file in the default file explorer. @@ -3029,7 +3029,7 @@ Opens a file in the default file explorer. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L503) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L503) Adds a listener to the window. @@ -3038,7 +3038,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L521) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L521) Adds a listener to the window. An alias for `addListener`. @@ -3047,7 +3047,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L538) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L538) Adds a listener to the window. The listener is removed after the first call. @@ -3056,7 +3056,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L554) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L554) Removes a listener from the window. @@ -3065,7 +3065,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L567) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L567) Removes all listeners from the window. @@ -3073,7 +3073,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L583) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L583) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/index.d.ts b/api/index.d.ts index 08547829da..6078fe6163 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -3597,7 +3597,7 @@ declare module "socket:process" { * Adds callback to the 'nextTick' queue. * @param {Function} callback */ - export function nextTick(callback: Function): void; + export function nextTick(callback: Function, ...args: any[]): void; /** * Computed high resolution time as a `BigInt`. * @param {Array<number>?} [time] @@ -8950,6 +8950,11 @@ declare module "socket:constants" { declare module "socket:internal/conduit" { export const DEFALUT_MAX_RECONNECT_RETRIES: 32; export const DEFAULT_MAX_RECONNECT_TIMEOUT: 256; + /** + * @typedef {{ options: object, payload: Uint8Array }} ReceiveMessage + * @typedef {function(Error?, ReceiveCallback | undefined)} ReceiveCallback + * @typedef {{ id?: string|BigInt|number, reconnect?: {} }} ConduitOptions + */ /** * @class Conduit * @ignore @@ -8957,11 +8962,16 @@ declare module "socket:internal/conduit" { * @classdesc A class for managing WebSocket connections with custom options and payload encoding. */ export class Conduit extends EventTarget { - static get port(): any; + static set port(port: number); + /** + * The global `Conduit` port + * @type {number} + */ + static get port(): number; /** * Creates an instance of Conduit. * - * @param {Object} params - The parameters for the Conduit. + * @param {object} params - The parameters for the Conduit. * @param {string} params.id - The ID for the connection. * @param {string} params.method - The method to use for the connection. */ @@ -8969,11 +8979,30 @@ declare module "socket:internal/conduit" { id: string; method: string; }); + /** + * @type {boolean} + */ + shouldReconnect: boolean; + /** + * @type {boolean} + */ isConnecting: boolean; + /** + * @type {boolean} + */ isActive: boolean; - socket: any; + /** + * @type {WebSocket?} + */ + socket: WebSocket | null; + /** + * @type {number} + */ port: number; - id: any; + /** + * @type {number?} + */ + id: number | null; /** * The URL string for the WebSocket server. * @type {string} @@ -9002,18 +9031,18 @@ declare module "socket:internal/conduit" { /** * Connects the underlying conduit `WebSocket`. * @param {function(Error?)=} [callback] - * @return {Conduit} + * @return {Promise<Conduit>} */ - connect(callback?: ((arg0: Error | null) => any) | undefined): Conduit; + connect(callback?: ((arg0: Error | null) => any) | undefined): Promise<Conduit>; /** * Reconnects a `Conduit` socket. * @param {{retries?: number, timeout?: number}} [options] - * @return {Conduit} + * @return {Promise<Conduit>} */ reconnect(options?: { retries?: number; timeout?: number; - }): Conduit; + }): Promise<Conduit>; /** * Encodes a single header into a Uint8Array. * @@ -9035,30 +9064,40 @@ declare module "socket:internal/conduit" { /** * Decodes a Uint8Array message into options and payload. * @param {Uint8Array} data - The data to decode. - * @returns {Object} The decoded message containing options and payload. + * @return {ReceiveMessage} The decoded message containing options and payload. * @throws Will throw an error if the data is invalid. */ - decodeMessage(data: Uint8Array): any; + decodeMessage(data: Uint8Array): ReceiveMessage; /** * Registers a callback to handle incoming messages. - * The callback will receive an error object and an object containing decoded options and payload. - * - * @param {function(Error?, { options: object, payload: Uint8Array })} cb - The callback function to handle incoming messages. + * The callback will receive an error object and an object containing + * decoded options and payload. + * @param {ReceiveCallback} callback - The callback function to handle incoming messages. */ - receive(cb: (arg0: Error | null, arg1: { - options: object; - payload: Uint8Array; - }) => any): void; + receive(callback: ReceiveCallback): void; /** - * Sends a message with the specified options and payload over the WebSocket connection. - * + * Sends a message with the specified options and payload over the + * WebSocket connection. * @param {object} options - The options to send. * @param {Uint8Array} payload - The payload to send. * @return {boolean} */ send(options: object, payload: Uint8Array): boolean; + /** + * Closes the WebSocket connection, preventing reconnects. + */ + close(): void; #private; } + export type ReceiveMessage = { + options: object; + payload: Uint8Array; + }; + export type ReceiveCallback = (arg0: Error | null, arg1: ReceiveCallback | undefined) => any; + export type ConduitOptions = { + id?: string | BigInt | number; + reconnect?: {}; + }; } declare module "socket:ip" { @@ -12879,7 +12918,6 @@ declare module "socket:latica/index" { * @ignore */ _onConnection(packet: any, peerId: any, port: any, address: any, proxy: any, socket: any): undefined; - connections: Map<any, any>; /** * Received a Sync Packet * @return {undefined} @@ -12920,6 +12958,7 @@ declare module "socket:latica/index" { * @ignore */ _onPong(packet: any, port: any, address: any): undefined; + reflectionFirstReponderTimeout: number; /** * Received an Intro Packet * @return {undefined} From df2ae302fd60a839d172702370c2177a9077d91b Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 12 Jul 2024 23:01:54 +0200 Subject: [PATCH 0965/1178] refactor(core): improve logs --- .eslintrc | 6 ++-- src/cli/cli.cc | 89 +++++++++++++++++++++++++---------------------- src/ipc/router.hh | 2 ++ src/ipc/routes.cc | 56 +++++++++++++++-------------- 4 files changed, 83 insertions(+), 70 deletions(-) diff --git a/.eslintrc b/.eslintrc index 5aa84678b6..9623dd6e93 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,9 @@ { "parserOptions": { - "ecmaVersion": 2020 + "ecmaVersion": "latest", + "sourceType": "module" }, "env": { "es6": true - }, - "sourceType": "module" + } } diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 9862f9c0fd..3d3ce39369 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -319,9 +319,10 @@ static std::atomic<int> appStatus = -1; static std::mutex appMutex; static uv_loop_t *loop = nullptr; static uv_udp_t logsocket; +static NSDate* lastLogTime = [NSDate now]; +static int lastLogSequence = 0; #if defined(__APPLE__) - unsigned short createLogSocket() { std::promise<int> p; std::future<int> future = p.get_future(); @@ -334,59 +335,64 @@ unsigned short createLogSocket() { struct sockaddr_in addr; int port; - NSDate* lastLogTime = [NSDate now]; - logsocket.data = (void*) lastLogTime; - uv_ip4_addr("0.0.0.0", 0, &addr); uv_udp_bind(&logsocket, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR); uv_udp_recv_start( - &logsocket, - [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { - *buf = uv_buf_init(new char[suggested_size], suggested_size); - }, - [](uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) { - if (nread > 0) { - NSDate* lastLogTime = (NSDate*)req->data; - - String data = trim(String(buf->base, nread)); - if (data[0] != '+') return; - - @autoreleasepool { - NSError *err = nil; - auto logs = [OSLogStore storeWithScope: OSLogStoreSystem error: &err]; // get snapshot - - if (err) { - debug("ERROR: Failed to open logstore"); - return; - } + &logsocket, + [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + *buf = uv_buf_init(new char[suggested_size], suggested_size); + }, + [](uv_udp_t* req, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { + if (nread > 0) { + std::string data(buf->base, nread); + data = trim(data); // Assuming trim is defined elsewhere + if (data[0] != '+') return; + + @autoreleasepool { + NSError* err = nil; + auto logs = [OSLogStore storeWithScope: OSLogStoreSystem error: &err]; // get snapshot + + if (err) { + std::cerr << "ERROR: Failed to open logstore" << std::endl; + return; + } - auto position = [logs positionWithDate: lastLogTime]; - auto predicate = [NSPredicate predicateWithFormat: @"(category == 'socket.runtime')"]; - auto enumerator = [logs entriesEnumeratorWithOptions: 0 position: position predicate: predicate error: &err]; + NSDate* adjustedLogTime = [lastLogTime dateByAddingTimeInterval: -1]; // adjust by subtracting 1 second + auto position = [logs positionWithDate: adjustedLogTime]; + auto predicate = [NSPredicate predicateWithFormat: @"(category == 'socket.runtime')"]; + auto enumerator = [logs entriesEnumeratorWithOptions: 0 position: position predicate: predicate error: &err]; - if (err) { - debug("ERROR: Failed to open logstore"); - return; - } + if (err) { + std::cerr << "ERROR: Failed to open logstore" << std::endl; + return; + } - int count = 0; - id logEntry; + id logEntry; - while ((logEntry = [enumerator nextObject]) != nil) { - OSLogEntryLog *entry = (OSLogEntryLog *)logEntry; + while ((logEntry = [enumerator nextObject]) != nil) { + OSLogEntryLog* entry = (OSLogEntryLog*)logEntry; + std::string message = entry.composedMessage.UTF8String; + int seq = 0; + Vector<String> parts; - count++; + try { + parts = split(message, "\xFF\xFF"); + seq = std::stoi(parts[0]); + } catch (...) { + continue; + } - NSString *message = [entry composedMessage]; - std::cout << message.UTF8String << std::endl; - req->data = (void*) [NSDate now]; + if (seq <= lastLogSequence && lastLogSequence > 0) continue; + lastLogSequence = seq; + + std::cout << parts[1] << std::endl; + } } } - } - if (buf->base) delete[] buf->base; - } + if (buf->base) delete[] buf->base; + } ); int len = sizeof(addr); @@ -401,6 +407,7 @@ unsigned short createLogSocket() { port = future.get(); t.detach(); + return port; } #endif diff --git a/src/ipc/router.hh b/src/ipc/router.hh index c3ba108d83..f603020ee5 100644 --- a/src/ipc/router.hh +++ b/src/ipc/router.hh @@ -34,6 +34,8 @@ namespace SSC::IPC { Mutex mutex; Table table; + std::atomic<int> logSeq{0}; + Bridge *bridge = nullptr; Router () = default; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 6e92dec161..2b72c1bda8 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2358,19 +2358,21 @@ static void mapIPCRoutes (Router *router) { */ router->map("stdout", [=](auto message, auto router, auto reply) { if (message.value.size() > 0) { - #if SOCKET_RUNTIME_PLATFORM_APPLE - os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", message.value.c_str()); - - if (Env::get("SSC_LOG_SOCKET").size() > 0) { - Core::UDP::SendOptions options; - options.size = 2; - options.bytes = SharedPointer<char[]>(new char[3]{ '+', 'N', '\0' }); - options.address = "0.0.0.0"; - options.port = std::stoi(Env::get("SSC_LOG_SOCKET")); - options.ephemeral = true; - router->bridge->core->udp.send("-1", 0, options, [](auto seq, auto json, auto post) {}); - } - #endif + #if SOCKET_RUNTIME_PLATFORM_APPLE + int seq = ++router->logSeq; + auto msg = String(std::to_string(seq) + "\xFF\xFF" + message.value.c_str()); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", msg.c_str()); + + if (Env::get("SSC_LOG_SOCKET").size() > 0) { + Core::UDP::SendOptions options; + options.size = 2; + options.bytes = SharedPointer<char[]>(new char[3]{ '+', 'N', '\0' }); + options.address = "0.0.0.0"; + options.port = std::stoi(Env::get("SSC_LOG_SOCKET")); + options.ephemeral = true; + router->bridge->core->udp.send("-1", 0, options, [](auto seq, auto json, auto post) {}); + } + #endif IO::write(message.value, false); } else if (message.buffer.bytes != nullptr && message.buffer.size > 0) { IO::write(String(message.buffer.bytes.get(), message.buffer.size), false); @@ -2389,19 +2391,21 @@ static void mapIPCRoutes (Router *router) { debug("%s", message.value.c_str()); } } else if (message.value.size() > 0) { - #if SOCKET_RUNTIME_PLATFORM_APPLE - os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", message.value.c_str()); - - if (Env::get("SSC_LOG_SOCKET").size() > 0) { - Core::UDP::SendOptions options; - options.size = 2; - options.bytes = SharedPointer<char[]>(new char[3]{ '+', 'N', '\0' }); - options.address = "0.0.0.0"; - options.port = std::stoi(Env::get("SSC_LOG_SOCKET")); - options.ephemeral = true; - router->bridge->core->udp.send("-1", 0, options, [](auto seq, auto json, auto post) {}); - } - #endif + #if SOCKET_RUNTIME_PLATFORM_APPLE + int seq = ++router->logSeq; + auto msg = String(std::to_string(seq) + "\xFF\xFF" + message.value.c_str()); + os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", msg.c_str()); + + if (Env::get("SSC_LOG_SOCKET").size() > 0) { + Core::UDP::SendOptions options; + options.size = 2; + options.bytes = SharedPointer<char[]>(new char[3]{ '+', 'N', '\0' }); + options.address = "0.0.0.0"; + options.port = std::stoi(Env::get("SSC_LOG_SOCKET")); + options.ephemeral = true; + router->bridge->core->udp.send("-1", 0, options, [](auto seq, auto json, auto post) {}); + } + #endif IO::write(message.value, true); } else if (message.buffer.bytes != nullptr && message.buffer.size > 0) { IO::write(String(message.buffer.bytes.get(), message.buffer.size), true); From 939896480759de544b49f2d3dbbebf59930f6804 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Sat, 13 Jul 2024 20:53:10 +0200 Subject: [PATCH 0966/1178] refactor(core): improve logging on macos --- src/cli/cli.cc | 11 +++++++---- src/core/core.hh | 28 +++++++++++++++------------- src/ipc/router.hh | 2 -- src/ipc/routes.cc | 8 ++++---- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 3d3ce39369..de1ea27bf0 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -372,13 +372,16 @@ unsigned short createLogSocket() { while ((logEntry = [enumerator nextObject]) != nil) { OSLogEntryLog* entry = (OSLogEntryLog*)logEntry; - std::string message = entry.composedMessage.UTF8String; + String message = entry.composedMessage.UTF8String; + String body; int seq = 0; - Vector<String> parts; + Vector<String> parts = split(message, "::::"); + if (parts.size() < 2) continue; try { - parts = split(message, "\xFF\xFF"); seq = std::stoi(parts[0]); + body = parts[1]; + if (body.size() == 0) continue; } catch (...) { continue; } @@ -386,7 +389,7 @@ unsigned short createLogSocket() { if (seq <= lastLogSequence && lastLogSequence > 0) continue; lastLogSequence = seq; - std::cout << parts[1] << std::endl; + std::cout << body << std::endl; } } } diff --git a/src/core/core.hh b/src/core/core.hh index 0ab01cf5a5..609ea6fd5d 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -67,9 +67,9 @@ namespace SSC { struct Options : SSC::Options { struct Features { - #if !SOCKET_RUNTIME_PLATFORM_IOS - bool useChildProcess = true; - #endif + #if !SOCKET_RUNTIME_PLATFORM_IOS + bool useChildProcess = true; + #endif bool useDNS = true; bool useFS = true; @@ -86,13 +86,13 @@ namespace SSC { Features features; - #if SOCKET_RUNTIME_PLATFORM_LINUX - // this is turned on in the WebKitWebProcess extension to avoid - // deadlocking the GTK loop AND WebKit WebView thread as they - // are shared and we typically "interpolate" loop execution - // with the GTK thread on the main runtime process - bool dedicatedLoopThread = false; - #endif + #if SOCKET_RUNTIME_PLATFORM_LINUX + // this is turned on in the WebKitWebProcess extension to avoid + // deadlocking the GTK loop AND WebKit WebView thread as they + // are shared and we typically "interpolate" loop execution + // with the GTK thread on the main runtime process + bool dedicatedLoopThread = false; + #endif }; struct SharedPointerBuffer { @@ -100,9 +100,9 @@ namespace SSC { unsigned int ttl = 0; }; - #if !SOCKET_RUNTIME_PLATFORM_IOS - ChildProcess childProcess; - #endif + #if !SOCKET_RUNTIME_PLATFORM_IOS + ChildProcess childProcess; + #endif Diagnostics diagnostics; DNS dns; FS fs; @@ -186,6 +186,8 @@ namespace SSC { void resume (); void pause (); + int logSeq{0}; + void retainSharedPointerBuffer (SharedPointer<char[]> pointer, unsigned int ttl); void releaseSharedPointerBuffer (SharedPointer<char[]> pointer); diff --git a/src/ipc/router.hh b/src/ipc/router.hh index f603020ee5..c3ba108d83 100644 --- a/src/ipc/router.hh +++ b/src/ipc/router.hh @@ -34,8 +34,6 @@ namespace SSC::IPC { Mutex mutex; Table table; - std::atomic<int> logSeq{0}; - Bridge *bridge = nullptr; Router () = default; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 2b72c1bda8..3b14e2ecb9 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2359,8 +2359,8 @@ static void mapIPCRoutes (Router *router) { router->map("stdout", [=](auto message, auto router, auto reply) { if (message.value.size() > 0) { #if SOCKET_RUNTIME_PLATFORM_APPLE - int seq = ++router->logSeq; - auto msg = String(std::to_string(seq) + "\xFF\xFF" + message.value.c_str()); + int seq = ++router->bridge->core->logSeq; + auto msg = String(std::to_string(seq) + "::::" + message.value.c_str()); os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_INFO, "%{public}s", msg.c_str()); if (Env::get("SSC_LOG_SOCKET").size() > 0) { @@ -2392,8 +2392,8 @@ static void mapIPCRoutes (Router *router) { } } else if (message.value.size() > 0) { #if SOCKET_RUNTIME_PLATFORM_APPLE - int seq = ++router->logSeq; - auto msg = String(std::to_string(seq) + "\xFF\xFF" + message.value.c_str()); + int seq = ++router->bridge->core->logSeq; + auto msg = String(std::to_string(seq) + "::::" + message.value.c_str()); os_log_with_type(SOCKET_RUNTIME_OS_LOG_BUNDLE, OS_LOG_TYPE_ERROR, "%{public}s", msg.c_str()); if (Env::get("SSC_LOG_SOCKET").size() > 0) { From 9839eeb3184bb3f6a520045beff0a37b29889744 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 17:03:33 +0200 Subject: [PATCH 0967/1178] refactor(window): consolidate 'applicationurl' handler --- src/window/android.cc | 30 ++++++++++++++++++++++++++++++ src/window/apple.mm | 8 ++++++++ src/window/linux.cc | 16 ++++++++++++++++ src/window/manager.cc | 14 ++++++++++++-- src/window/manager.kt | 4 ++-- src/window/win.cc | 8 ++++++++ src/window/window.hh | 27 ++++++++++++++++++++++++++- src/window/window.kt | 7 +++++++ 8 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index 308aa99199..4bd9632368 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -263,6 +263,14 @@ namespace SSC { void Window::showInspector () { } + + void Window::handleApplicationURL (const String& url) { + JSON::Object json = JSON::Object::Entries {{ + "url", url + }}; + + this->bridge.emit("applicationurl", json.str()); + } } extern "C" { @@ -342,4 +350,26 @@ extern "C" { window->pendingNavigationLocation = ""; return attachment.env->NewStringUTF(pendingNavigationLocation.c_str()); } + + void ANDROID_EXTERNAL(window, Window, handleApplicationURL) ( + JNIEnv* env, + jobject self, + jint index, + jstring urlString + ) { + const auto app = App::sharedApplication(); + + if (!app) { + return ANDROID_THROW(env, "Missing 'App' in environment"); + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + return ANDROID_THROW(env, "Invalid window index (%d) requested", index); + } + + const auto url = Android::StringWrap(env, urlString).str(); + window->handleApplicationURL(url); + } } diff --git a/src/window/apple.mm b/src/window/apple.mm index 6e1616f34c..c5f905259f 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -1343,4 +1343,12 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } #endif } + + void Window::handleApplicationURL (const String& url) { + JSON::Object json = JSON::Object::Entries {{ + "url", url + }}; + + this->bridge.emit("applicationurl", json.str()); + } } diff --git a/src/window/linux.cc b/src/window/linux.cc index ffeac0fdbd..9b900379ea 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1667,4 +1667,20 @@ namespace SSC { event ); } + + void Window::handleApplicationURL (const String& url) { + JSON::Object json = JSON::Object::Entries {{ + "url", url + }}; + + if (this->index == 0 && this->window && this->webview) { + gtk_widget_show_all(GTK_WIDGET(this->window)); + gtk_widget_grab_focus(GTK_WIDGET(this->webview)); + gtk_widget_grab_focus(GTK_WIDGET(this->window)); + gtk_window_activate_focus(GTK_WINDOW(this->window)); + gtk_window_present(GTK_WINDOW(this->window)); + } + + this->bridge.emit("applicationurl", json.str()); + } } diff --git a/src/window/manager.cc b/src/window/manager.cc index 43af547336..868b6ddb1c 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -366,9 +366,18 @@ namespace SSC { } JSON::Object WindowManager::ManagedWindow::json () const { - const auto index = this->index; - const auto size = this->getSize(); const auto id = this->bridge.id; + const auto size = this->getSize(); + const auto index = this->index; + const auto readyState = String( + this->readyState == Window::ReadyState::Loading + ? "loading" + : this->readyState == Window::ReadyState::Interactive + ? "interactive" + : this->readyState == Window::ReadyState::Complete + ? "complete" + : "none" + ); return JSON::Object::Entries { {"id", std::to_string(id)}, @@ -377,6 +386,7 @@ namespace SSC { {"width", size.width}, {"height", size.height}, {"status", this->status}, + {"readyState", readyState}, {"position", JSON::Object::Entries { {"x", this->position.x}, {"y", this->position.y} diff --git a/src/window/manager.kt b/src/window/manager.kt index f3a47ca2e4..8e4cc297aa 100644 --- a/src/window/manager.kt +++ b/src/window/manager.kt @@ -22,8 +22,8 @@ import __BUNDLE_IDENTIFIER__.R * A `WindowFragmentManager` manages `WindowFragment` instances. */ open class WindowFragmentManager (protected val activity: WindowManagerActivity) { - protected val fragments = mutableListOf<WindowFragment>() - protected val manager = activity.supportFragmentManager + open val fragments = mutableListOf<WindowFragment>() + open val manager = activity.supportFragmentManager /** * Creates a new `WindowFragment` from `WindowOptions` if `options.index` diff --git a/src/window/win.cc b/src/window/win.cc index 7d156f805e..cbaf04bc5c 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1680,4 +1680,12 @@ namespace SSC { return string; } + + void Window::handleApplicationURL (const String& url) { + JSON::Object json = JSON::Object::Entries {{ + "url", url + }}; + + this->bridge.emit("applicationurl", json.str()); + } } diff --git a/src/window/window.hh b/src/window/window.hh index 87687d2de7..8c5d78ba19 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -89,16 +89,35 @@ namespace SSC { */ class Window { public: + /** + * A container for representing the window position in a + * Cartesian coordinate system (screen coordinates) + */ struct Position { float x = 0.0f; float y = 0.0f; }; + /** + * A container for representing the size of a window. + */ struct Size { int width = 0; int height = 0; }; + /** + * An enumeration of the "ready state" of a window. + * These values closel relate to the `globalThis.document.readyState` + * possible values (loading, interactive, complete) + */ + enum class ReadyState { + None, + Loading, + Interactive, + Complete + }; + /** * `Window::Options` is an extended `IPC::Preload::Options` container for * configuring a new `Window`. @@ -253,6 +272,11 @@ namespace SSC { ExitCallback onExit = nullptr; }; + /** + * The current "ready state" of the window + */ + ReadyState readyState = ReadyState::None; + /** * The options used to create this window. */ @@ -433,6 +457,8 @@ namespace SSC { void setTrayMenu (const String& dsl); void showInspector (); + void handleApplicationURL (const String& url); + void resolvePromise ( const String& seq, const String& state, @@ -548,7 +574,6 @@ namespace SSC { SharedPointer<ManagedWindow> createDefaultWindow (const Window::Options& options); void destroyWindow (int index); - JSON::Array json (const Vector<int>& indices); }; } diff --git a/src/window/window.kt b/src/window/window.kt index 8e0e5c80f4..f1f03ab30b 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -215,11 +215,18 @@ open class Window (val fragment: WindowFragment) { webview.setLayoutParams(layout) } + fun handleApplicationURL (url: String) { + return this.handleApplicationURL(this.index, url) + } + @Throws(Exception::class) external fun onMessage (index: Int, value: String, bytes: ByteArray? = null): Unit @Throws(Exception::class) external fun getPendingNavigationLocation (index: Int): String + + @Throws(Exception::class) + external fun handleApplicationURL (index: Int, url: String): Unit } /** From 49b1dd0a4f1d2ff3c380e2abfdadca0c813e0648 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 17:04:09 +0200 Subject: [PATCH 0968/1178] refactor(ipc): track 'readyState' for 'Window' from 'document' --- src/ipc/bridge.cc | 31 +++++++++++++++++++++++++++++++ src/ipc/bridge.kt | 7 +++++++ src/ipc/navigator.cc | 24 ++++++++++++++---------- src/ipc/preload.cc | 8 ++++++++ src/ipc/routes.cc | 37 ++++++++++++++++++++++++++++++++----- 5 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 5dda6454f7..fa1c6892d0 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -1042,3 +1042,34 @@ export * from '{{url}}' this->navigator.configureMounts(); } } + +#if SOCKET_RUNTIME_PLATFORM_ANDROID +extern "C" { + jboolean ANDROID_EXTERNAL(ipc, Bridge, emit) ( + JNIEnv* env, + jobject self, + jint index, + jstring eventString, + jstring dataString + ) { + using namespace SSC; + auto app = App::sharedApplication(); + + if (!app) { + ANDROID_THROW(env, "Missing 'App' in environment"); + return false; + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + ANDROID_THROW(env, "Invalid window requested"); + return false; + } + + const auto event = Android::StringWrap(env, eventString).str(); + const auto data = Android::StringWrap(env, dataString).str(); + return window->bridge.emit(event, data); + } +} +#endif diff --git a/src/ipc/bridge.kt b/src/ipc/bridge.kt index 01090ded66..0adaa0abf3 100644 --- a/src/ipc/bridge.kt +++ b/src/ipc/bridge.kt @@ -101,4 +101,11 @@ open class Bridge ( ): WebResourceResponse? { return this.schemeHandlers.handleRequest(request) } + + fun emit (event: String, data: String): Boolean { + return this.emit(this.index, event, data) + } + + @Throws(Exception::class) + external fun emit (index: Int, event: String, data: String): Boolean } diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index 11df655a35..a85bff5bd5 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -290,7 +290,9 @@ namespace SSC::IPC { const String& requestedURL ) { auto userConfig = this->bridge->userConfig; + const auto app = App::sharedApplication(); const auto links = parseStringList(userConfig["meta_application_links"], ' '); + const auto window = app->windowManager.getWindowForBridge(this->bridge); const auto applinks = parseStringList(userConfig["meta_application_links"], ' '); const auto currentURLComponents = URL::Components::parse(currentURL); @@ -307,12 +309,13 @@ namespace SSC::IPC { } if (hasAppLink) { - JSON::Object json = JSON::Object::Entries {{ - "url", requestedURL - }}; + if (window) { + window->handleApplicationURL(requestedURL); + return false; + } - this->bridge->emit("applicationurl", json.str()); - return false; + // should be unreachable, but... + return true; } if ( @@ -321,12 +324,13 @@ namespace SSC::IPC { !requestedURL.starts_with("socket://" + userConfig["meta_bundle_identifier"]) ) { - SSC::JSON::Object json = SSC::JSON::Object::Entries {{ - "url", requestedURL - }}; + if (window) { + window->handleApplicationURL(requestedURL); + return false; + } - this->bridge->emit("applicationurl", json.str()); - return false; + // should be unreachable, but... + return true; } if (!this->isNavigationRequestAllowed(currentURL, requestedURL)) { diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index f68726c7b2..0ce0d2b96a 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -384,6 +384,14 @@ namespace SSC::IPC { } }) + globalThis.document.addEventListener('readystatechange', async (e) => { + const ipc = await import('socket:ipc') + ipc.send('platform.event', { + value: 'readystatechange', + state: globalThis.document.readyState + }) + }) + globalThis.addEventListener('applicationurl', (event) => { if (globalThis.document.readyState !== 'complete') { globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG.push(event) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 3b14e2ecb9..ad3c1bee91 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2037,14 +2037,35 @@ static void mapIPCRoutes (Router *router) { */ router->map("platform.event", [=](auto message, auto router, auto reply) { const auto err = validateMessageParameters(message, {"value"}); - const auto frameType = message.get("runtime-frame-type"); - const auto frameSource = message.get("runtime-frame-source"); - auto userConfig = router->bridge->userConfig; + const auto app = App::sharedApplication(); + const auto window = app->windowManager.getWindowForBridge(router->bridge); if (err.type != JSON::Type::Null) { return reply(Result { message.seq, message, err }); } + if (message.value == "readystatechange") { + const auto err = validateMessageParameters(message, {"state"}); + + if (err.type != JSON::Type::Null) { + return reply(Result { message.seq, message, err }); + } + + const auto state = message.get("state"); + + if (state == "loading") { + window->readyState = Window::ReadyState::Loading; + } else if (state == "interactive") { + window->readyState = Window::ReadyState::Interactive; + } else if (state == "complete") { + window->readyState = Window::ReadyState::Complete; + } + } + + const auto frameType = message.get("runtime-frame-type"); + const auto frameSource = message.get("runtime-frame-source"); + auto userConfig = router->bridge->userConfig; + if (frameType == "top-level" && frameSource != "serviceworker") { if (message.value == "load") { const auto href = message.get("location.href"); @@ -2062,7 +2083,7 @@ static void mapIPCRoutes (Router *router) { } if (router->bridge == router->bridge->navigator.serviceWorker.bridge) { - if (router->bridge->userConfig["webview_service_worker_mode"] == "hybrid" || platform.ios || platform.android) { + if (router->bridge->userConfig["webview_service_worker_mode"] == "hybrid") { if (router->bridge->navigator.location.href.size() > 0 && message.value == "beforeruntimeinit") { router->bridge->navigator.serviceWorker.reset(); router->bridge->navigator.serviceWorker.isReady = false; @@ -2135,6 +2156,7 @@ static void mapIPCRoutes (Router *router) { */ router->map("platform.openExternal", [=](auto message, auto router, auto reply) mutable { const auto applicationProtocol = router->bridge->userConfig["meta_application_protocol"]; + const auto app = App::sharedApplication(); auto err = validateMessageParameters(message, {"value"}); if (err.type != JSON::Type::Null) { @@ -2146,7 +2168,12 @@ static void mapIPCRoutes (Router *router) { { "url", message.value } }; - router->bridge->emit("applicationurl", json.str()); + const auto window = app->windowManager.getWindowForBridge(router->bridge); + + if (window) { + window->handleApplicationURL(message.value); + } + reply(Result { message.seq, message, From 3b394acb3457be26d9123d12f860491dc1f4c27a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 12 Jul 2024 17:04:38 +0200 Subject: [PATCH 0969/1178] refactor(app): handle 'applicationurl' on android correctly --- src/app/app.cc | 28 ++++++++-------------------- src/app/app.hh | 1 + src/app/app.kt | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index e26f3461e3..f4409423b8 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -53,13 +53,9 @@ static dispatch_queue_t queue = dispatch_queue_create( auto app = self.app; if (app != nullptr) { for (NSURL* url in urls) { - JSON::Object json = JSON::Object::Entries {{ - "url", [url.absoluteString UTF8String] - }}; - for (auto& window : self.app->windowManager.windows) { if (window != nullptr) { - window->bridge.emit("applicationurl", json); + window->handleApplicationURL(url.absoluteString.UTF8String); } } } @@ -122,13 +118,12 @@ continueUserActivity: (NSUserActivity*) userActivity } const auto url = String(webpageURL.absoluteString.UTF8String); - const auto json = JSON::Object::Entries {{ "url", url }}; bool emitted = false; for (auto& window : self.app->windowManager.windows) { if (window != nullptr) { - window->bridge.emit("applicationurl", json); + window->handleApplicationURL(url); emitted = true; } } @@ -369,9 +364,7 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType bool emitted = false; for (const auto& window : self.app->windowManager.windows) { if (window != nullptr) { - window->bridge.emit("applicationurl", JSON::Object::Entries { - { "url", webpageURL.absoluteString.UTF8String} - }); + window->handleApplicationURL(webpageURL.absoluteString.UTF8String); emitted = true; } } @@ -471,10 +464,8 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType { for (const auto window : self.app->windowManager.windows) { if (window) { - // TODO can this be escaped or is the url encoded property already? - return window->bridge.emit("applicationurl", JSON::Object::Entries { - {"url", url.absoluteString.UTF8String} - }); + window->handleApplicationURL(url.absoluteString.UTF8String); + return YES; } } @@ -674,19 +665,16 @@ namespace SSC { case WM_HOTKEY: { if (window != nullptr) { window->hotkey.onHotKeyBindingCallback((HotKeyBinding::ID) wParam); - } + } break; } case WM_HANDLE_DEEP_LINK: { - auto url = SSC::String(reinterpret_cast<const char*>(lParam), wParam); - const JSON::Object json = JSON::Object::Entries { - {"url", url} - }; + const auto url = String(reinterpret_cast<const char*>(lParam), wParam); for (auto window : app->windowManager.windows) { if (window != nullptr) { - window->bridge.emit("applicationurl", json); + window->handleApplicationURL(url); } } break; diff --git a/src/app/app.hh b/src/app/app.hh index 79529fc574..53e6cee479 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -91,6 +91,7 @@ namespace SSC { AtomicBool killed = false; bool wasLaunchedFromCli = false; + Vector<String> pendingApplicationURLs; WindowManager windowManager; ServiceWorkerContainer serviceWorkerContainer; SharedPointer<Core> core = nullptr; diff --git a/src/app/app.kt b/src/app/app.kt index e162ae6843..d13508d26e 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -112,6 +112,12 @@ open class AppActivity : WindowManagerActivity() { override fun onStart () { super.onStart() + val action: String? = this.intent?.action + val data: android.net.Uri? = this.intent?.data + + if (action != null && data != null) { + this.onNewIntent(this.intent) + } } override fun onResume () { @@ -133,6 +139,46 @@ open class AppActivity : WindowManagerActivity() { override fun onNewIntent (intent: Intent) { super.onNewIntent(intent) + val action = intent.action + val data = intent.data + val id = intent.extras?.getCharSequence("id")?.toString() + + when (action) { + "android.intent.action.MAIN", + "android.intent.action.VIEW" -> { + val scheme = data?.scheme ?: return + val applicationProtocol = App.getInstance().getUserConfigValue("meta_application_protocol") + if ( + applicationProtocol.length > 0 && + scheme.startsWith(applicationProtocol) + ) { + for (fragment in this.windowFragmentManager.fragments) { + val window = fragment.window ?: continue + window.handleApplicationURL(data.toString()) + } + } + } + + "notification.response.default" -> { + for (fragment in this.windowFragmentManager.fragments) { + val bridge = fragment.window?.bridge ?: continue + bridge.emit("notificationresponse", """{ + "id": "$id", + "action": "default" + }""") + } + } + + "notification.response.dismiss" -> { + for (fragment in this.windowFragmentManager.fragments) { + val bridge = fragment.window?.bridge ?: continue + bridge.emit("notificationresponse", """{ + "id": "$id", + "action": "dismiss" + }""") + } + } + } } override fun onActivityResult ( From f4f08f6e1cd02a264a32de18723309340f72e9f4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 13:47:27 +0200 Subject: [PATCH 0970/1178] refactor(app): remove pending application URL state --- src/app/app.cc | 22 ---------------------- src/app/app.hh | 1 - 2 files changed, 23 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index f4409423b8..fdf3ad36ce 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -118,7 +118,6 @@ continueUserActivity: (NSUserActivity*) userActivity } const auto url = String(webpageURL.absoluteString.UTF8String); - bool emitted = false; for (auto& window : self.app->windowManager.windows) { @@ -952,27 +951,6 @@ namespace SSC { return userConfig.at(key) != "false"; } - /* -#if SOCKET_RUNTIME_PLATFORM_ANDROID - bool App::isAndroidPermissionAllowed (const String& permission) { - const auto attachment = Android::JNIEnvironmentAttachment( - this->jvm.get(), - this->jvm.jniVersion - ); - - return CallClassMethodFromAndroidEnvironment( - this->jni, - Boolean, - this->self, - "checkPermission", - "(" - "Ljava/lang/String;" // permission name - ")Z" // Boolean return type - ); - } -#endif -*/ - void App::exit (int code) { if (this->onExit != nullptr) { this->onExit(code); diff --git a/src/app/app.hh b/src/app/app.hh index 53e6cee479..79529fc574 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -91,7 +91,6 @@ namespace SSC { AtomicBool killed = false; bool wasLaunchedFromCli = false; - Vector<String> pendingApplicationURLs; WindowManager windowManager; ServiceWorkerContainer serviceWorkerContainer; SharedPointer<Core> core = nullptr; From f89699c9e50457e133039edf879c8fb6d5bdd196 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 13:47:48 +0200 Subject: [PATCH 0971/1178] refactor(core/modules/platform.cc): do not use loop dispatch --- src/core/modules/platform.cc | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index e298210fb9..8f56dbae3f 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -13,14 +13,9 @@ namespace SSC { const String& frameSource, const CoreModule::Callback& callback ) { - this->core->dispatchEventLoop([=, this]() { - // init page - if (event == "domcontentloaded") { - Lock lock(this->core->fs.mutex); - - this->wasFirstDOMContentLoadedEventDispatched = true; - } - }); + if (event == "domcontentloaded") { + this->wasFirstDOMContentLoadedEventDispatched = true; + } const auto json = JSON::Object::Entries { {"source", "platform.event"}, From 70e426b0d92e39899d47ae209131efe7ddbb8c4d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 13:48:08 +0200 Subject: [PATCH 0972/1178] refactor(desktop/main): simplify app url handler on linux --- src/desktop/main.cc | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 98d74ecc13..9a24b8c966 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -115,25 +115,12 @@ void signalHandler (int signum) { } #if SOCKET_RUNTIME_PLATFORM_LINUX -static void handleApplicationURLEvent (const String url) { +static void handleApplicationURLEvent (const String& url) { auto app = App::sharedApplication(); - - JSON::Object json = JSON::Object::Entries {{ - "url", url - }}; - - if (app != nullptr) { + if (app != nullptr && url.size() > 0) { for (auto window : app->windowManager.windows) { if (window != nullptr) { - if (window->index == 0 && window->window && window->webview) { - gtk_widget_show_all(GTK_WIDGET(window->window)); - gtk_widget_grab_focus(GTK_WIDGET(window->webview)); - gtk_widget_grab_focus(GTK_WIDGET(window->window)); - gtk_window_activate_focus(GTK_WINDOW(window->window)); - gtk_window_present(GTK_WINDOW(window->window)); - } - - window->bridge.emit("applicationurl", json.str()); + window->handleApplicationURL(url); } } } @@ -146,7 +133,9 @@ static void onGTKApplicationActivation ( const gchar* hint, gpointer userData ) { - handleApplicationURLEvent(String(hint)); + if (hint != nullptr) { + handleApplicationURLEvent(String(hint)); + } } static DBusHandlerResult onDBusMessage ( From b6b91ed735b5b73bf7d06298036c19bf2102d1d2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 13:48:34 +0200 Subject: [PATCH 0973/1178] refactor(ipc): handle 'readystatechange' on 'Window' --- src/ipc/routes.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index ad3c1bee91..a35bee5d65 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -2060,6 +2060,8 @@ static void mapIPCRoutes (Router *router) { } else if (state == "complete") { window->readyState = Window::ReadyState::Complete; } + + window->onReadyStateChange(window->readyState); } const auto frameType = message.get("runtime-frame-type"); From 42e541150c6885f8f47dfa7f369f7de3a5f2d375 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 13:49:30 +0200 Subject: [PATCH 0974/1178] refactor(window): introduce 'onReadyStateChange' callback --- src/window/manager.cc | 22 ++++++++++++++++++++++ src/window/window.hh | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/src/window/manager.cc b/src/window/manager.cc index 868b6ddb1c..15a5051cc5 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -393,4 +393,26 @@ namespace SSC { }} }; } + + void WindowManager::ManagedWindow::onReadyStateChange (const ReadyState& readyState) { + if (readyState == ReadyState::Complete) { + const auto pendingApplicationURLs = this->pendingApplicationURLs; + this->pendingApplicationURLs.clear(); + for (const auto& url : pendingApplicationURLs) { + Window::handleApplicationURL(url); + } + } + } + + void WindowManager::ManagedWindow::handleApplicationURL (const String& url) { + Lock lock(this->mutex); + this->pendingApplicationURLs.push_back(url); + if (this->readyState == ReadyState::Complete) { + const auto pendingApplicationURLs = this->pendingApplicationURLs; + this->pendingApplicationURLs.clear(); + for (const auto& url : pendingApplicationURLs) { + Window::handleApplicationURL(url); + } + } + } } diff --git a/src/window/window.hh b/src/window/window.hh index 8c5d78ba19..d60e341107 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -458,6 +458,7 @@ namespace SSC { void showInspector (); void handleApplicationURL (const String& url); + void onReadyStateChange (const ReadyState readyState) {} void resolvePromise ( const String& seq, @@ -528,6 +529,8 @@ namespace SSC { public: WindowStatus status; WindowManager &manager; + Vector<String> pendingApplicationURLs; + Mutex mutex; int index = 0; ManagedWindow ( @@ -545,6 +548,8 @@ namespace SSC { void kill (); void gc (); JSON::Object json () const; + void handleApplicationURL (const String& url); + void onReadyStateChange (const ReadyState& readyState); }; Vector<SharedPointer<ManagedWindow>> windows; From 1e6a5bd6471c7ccaca8c1ec98e1919e8a4780559 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 14:43:30 +0200 Subject: [PATCH 0975/1178] fix(cli): guard apple --- src/cli/cli.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index de1ea27bf0..28691bdda4 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -318,11 +318,12 @@ static SharedPointer<Process> appProcess = nullptr; static std::atomic<int> appStatus = -1; static std::mutex appMutex; static uv_loop_t *loop = nullptr; + static uv_udp_t logsocket; -static NSDate* lastLogTime = [NSDate now]; static int lastLogSequence = 0; -#if defined(__APPLE__) +#if SOCKET_RUNTIME_PLATFORM_APPLE +static NSDate* lastLogTime = [NSDate now]; unsigned short createLogSocket() { std::promise<int> p; std::future<int> future = p.get_future(); @@ -354,7 +355,7 @@ unsigned short createLogSocket() { auto logs = [OSLogStore storeWithScope: OSLogStoreSystem error: &err]; // get snapshot if (err) { - std::cerr << "ERROR: Failed to open logstore" << std::endl; + std::cerr << "ERROR: Failed to open OSLogStore" << std::endl; return; } @@ -364,7 +365,7 @@ unsigned short createLogSocket() { auto enumerator = [logs entriesEnumeratorWithOptions: 0 position: position predicate: predicate error: &err]; if (err) { - std::cerr << "ERROR: Failed to open logstore" << std::endl; + std::cerr << "ERROR: Failed to open OSLogStore" << std::endl; return; } From 0d5e89910a74c8236a428d91bdfb21a7987757fc Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 15 Jul 2024 15:20:57 +0200 Subject: [PATCH 0976/1178] reafactor(cli): include bundle id in log predicate --- src/cli/cli.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 28691bdda4..232f264fac 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -361,7 +361,9 @@ unsigned short createLogSocket() { NSDate* adjustedLogTime = [lastLogTime dateByAddingTimeInterval: -1]; // adjust by subtracting 1 second auto position = [logs positionWithDate: adjustedLogTime]; - auto predicate = [NSPredicate predicateWithFormat: @"(category == 'socket.runtime')"]; + auto bid = settings["meta_bundle_identifier"]; + auto query = String("(category == 'socket.runtime') AND (subsystem == '" + bid + "')"); + auto predicate = [NSPredicate predicateWithFormat: [NSString stringWithUTF8String: query.c_str()]]; auto enumerator = [logs entriesEnumeratorWithOptions: 0 position: position predicate: predicate error: &err]; if (err) { From 2f27db9c26ffb6f5cd197212b1f19231cd5f6976 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 23:29:03 +0200 Subject: [PATCH 0977/1178] docs(api): use more appropriate naming --- api/README.md | 722 +++---- api/ai.js | 2 +- api/application.js | 2 +- api/async.js | 2 +- api/async/context.js | 2 +- api/async/hooks.js | 2 +- api/async/resource.js | 2 +- api/async/storage.js | 2 +- api/bluetooth.js | 2 +- api/bootstrap.js | 2 +- api/buffer.js | 2 +- api/crypto.js | 2 +- api/dgram.js | 2 +- api/dns/index.js | 2 +- api/dns/promises.js | 2 +- api/fs/index.js | 2 +- api/fs/promises.js | 2 +- api/fs/stream.js | 2 +- api/index.d.ts | 3818 ++++++++++++++++++----------------- api/internal/events.js | 2 +- api/internal/init.js | 2 +- api/internal/permissions.js | 2 +- api/ip.js | 2 +- api/ipc.js | 2 +- api/language.js | 2 +- api/module.js | 2 +- api/network.js | 2 +- api/notification.js | 2 +- api/os.js | 2 +- api/path/path.js | 2 +- api/{ => process}/signal.js | 0 api/stream.js | 2 +- api/test/dom-helpers.js | 2 +- api/test/index.js | 2 +- api/vm.js | 2 +- api/window.js | 2 +- 36 files changed, 2305 insertions(+), 2301 deletions(-) rename api/{ => process}/signal.js (100%) diff --git a/api/README.md b/api/README.md index eee752514f..25a8707f3c 100644 --- a/api/README.md +++ b/api/README.md @@ -1,7 +1,21 @@ + +# [Buffer](https://github.com/socketsupply/socket/blob/master/api/buffer.js) + +Buffer module is a [third party](https://github.com/feross/buffer) vendor module provided by Feross Aboukhadijeh and other contributors (MIT License). + +External docs: https://nodejs.org/api/buffer.html + + +# [Events](https://github.com/socketsupply/socket/blob/master/api/events.js) + +Events module is a [third party](https://github.com/browserify/events/blob/main/events.js) module provided by Browserify and Node.js contributors (MIT License). + +External docs: https://nodejs.org/api/events.html + <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Application](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L13) +# [application](https://github.com/socketsupply/socket/blob/master/api/application.js#L13) Provides Application level methods @@ -11,17 +25,17 @@ import { createWindow } from 'socket:application' ``` -## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L38) +## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/master/api/application.js#L38) This is a `VariableDeclaration` named `MAX_WINDOWS` in `api/application.js`, it's exported but undocumented. -## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L40) +## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/master/api/application.js#L40) This is a `ClassDeclaration` named `ApplicationWindowList` in `api/application.js`, it's exported but undocumented. -## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L165) +## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L165) Returns the current window index @@ -29,7 +43,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L200) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L200) Creates a new window and returns an instance of ApplicationWindow. @@ -67,15 +81,15 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -### [`radius()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L227) +### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L227) -### [`margin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L232) +### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L232) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L320) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L320) Returns the current screen size. @@ -83,7 +97,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L351) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L351) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -95,7 +109,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindowList> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L405) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L405) Returns the ApplicationWindow instance for the given index @@ -107,7 +121,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L415) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L415) Returns the ApplicationWindow instance for the current window. @@ -115,7 +129,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L424) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L424) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -127,7 +141,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L521) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L521) Set the native menu for the app. @@ -222,11 +236,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L528) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L528) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L537) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L537) Set the enabled state of the system menu. @@ -238,23 +252,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L545) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L545) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L551) +## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L551) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L557) +## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L557) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L562) +## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L562) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L568) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L568) @@ -267,7 +281,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L576) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L576) @@ -279,7 +293,7 @@ The application's backend instance. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Bluetooth](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L12) +# [bluetooth](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L12) A high-level, cross-platform API for Bluetooth Pub-Sub @@ -289,11 +303,11 @@ The application's backend instance. import { Bluetooth } from 'socket:bluetooth' ``` -## [`Bluetooth` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L32) +## [`Bluetooth` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L32) Create an instance of a Bluetooth service. -### [`constructor(serviceId)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L40) +### [`constructor(serviceId)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L40) constructor is an example property that is set to `true` Creates a new service with key-value pairs @@ -302,7 +316,7 @@ constructor is an example property that is set to `true` | :--- | :--- | :---: | :---: | :--- | | serviceId | string | | false | Given a default value to determine the type | -### [`start()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L90) +### [`start()`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L90) Start the Bluetooth service. @@ -310,7 +324,7 @@ Start the Bluetooth service. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`subscribe(id)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L119) +### [`subscribe(id)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L119) Start scanning for published values that correspond to a well-known UUID. Once subscribed to a UUID, events that correspond to that UUID will be @@ -333,7 +347,7 @@ Start scanning for published values that correspond to a well-known UUID. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`publish(id, value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L142) +### [`publish(id, value)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L142) Start advertising a new value for a well-known UUID @@ -347,17 +361,10 @@ Start advertising a new value for a well-known UUID | Not specified | Promise<void> | | - -# [Buffer](https://github.com/socketsupply/socket/blob/master/api/buffer.js) - -Buffer module is a [third party](https://github.com/feross/buffer) vendor module provided by Feross Aboukhadijeh and other contributors (MIT License). - -External docs: https://nodejs.org/api/buffer.html - <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Crypto](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L15) +# [crypto](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L15) Some high-level methods around the `crypto.subtle` API for getting @@ -368,16 +375,16 @@ External docs: https://nodejs.org/api/buffer.html import { randomBytes } from 'socket:crypto' ``` -## [webcrypto](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L30) +## [webcrypto](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L30) External docs: https://developer.mozilla.org/en-US/docs/Web/API/Crypto WebCrypto API -## [ready](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L59) +## [ready](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L59) A promise that resolves when all internals to be loaded/ready. -## [`getRandomValues(buffer)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L74) +## [`getRandomValues(buffer)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L74) External docs: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues Generate cryptographically strong random values into the `buffer` @@ -390,7 +397,7 @@ Generate cryptographically strong random values into the `buffer` | :--- | :--- | :--- | | Not specified | TypedArray | | -## [`rand64()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L97) +## [`rand64()`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L97) Generate a random 64-bit number. @@ -398,19 +405,19 @@ Generate a random 64-bit number. | :--- | :--- | :--- | | A random 64-bit number. | BigInt | | -## [RANDOM_BYTES_QUOTA](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L105) +## [RANDOM_BYTES_QUOTA](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L105) Maximum total size of random bytes per page -## [MAX_RANDOM_BYTES](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L110) +## [MAX_RANDOM_BYTES](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L110) Maximum total size for random bytes. -## [MAX_RANDOM_BYTES_PAGES](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L115) +## [MAX_RANDOM_BYTES_PAGES](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L115) Maximum total amount of allocated per page of bytes (max/quota) -## [`randomBytes(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L123) +## [`randomBytes(size)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L123) Generate `size` random bytes. @@ -422,7 +429,7 @@ Generate `size` random bytes. | :--- | :--- | :--- | | Not specified | Buffer | A promise that resolves with an instance of socket.Buffer with random bytes. | -## [`createDigest(algorithm, message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L150) +## [`createDigest(algorithm, message)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L150) @@ -435,7 +442,7 @@ Generate `size` random bytes. | :--- | :--- | :--- | | Not specified | Promise<Buffer> | A promise that resolves with an instance of socket.Buffer with the hash. | -## [`murmur3(value, seed)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L161) +## [`murmur3(value, seed)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L161) A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c that works on strings and `ArrayBuffer` views (typed arrays) @@ -453,92 +460,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [DNS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/index.js#L17) - - - This module enables name resolution. For example, use it to look up IP - addresses of host names. Although named for the Domain Name System (DNS), - it does not always use the DNS protocol for lookups. dns.lookup() uses the - operating system facilities to perform name resolution. It may not need to - perform any network communication. To perform name resolution the way other - applications on the same system do, use dns.lookup(). - - Example usage: - ```js - import { lookup } from 'socket:dns' - ``` - -## [`lookup(hostname, options, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/index.js#L60) - -External docs: https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback -Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or - AAAA (IPv6) record. All option properties are optional. If options is an - integer, then it must be 4 or 6 – if options is 0 or not provided, then IPv4 - and IPv6 addresses are both returned if found. - - From the node.js website... - - > With the all option set to true, the arguments for callback change to (err, - addresses), with addresses being an array of objects with the properties - address and family. - - > On error, err is an Error object, where err.code is the error code. Keep in - mind that err.code will be set to 'ENOTFOUND' not only when the host name does - not exist but also when the lookup fails in other ways such as no available - file descriptors. dns.lookup() does not necessarily have anything to do with - the DNS protocol. The implementation uses an operating system facility that - can associate names with addresses and vice versa. This implementation can - have subtle but important consequences on the behavior of any Node.js program. - Please take some time to consult the Implementation considerations section - before using dns.lookup(). - - -| Argument | Type | Default | Optional | Description | -| :--- | :--- | :---: | :---: | :--- | -| hostname | string | | false | The host name to resolve. | -| options | object \| intenumberger | | true | An options object or record family. | -| options.family | number \| string | 0 | true | The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. | -| cb | function | | false | The function to call after the method is complete. | - - -<!-- This file is generated by bin/docs-generator/api-module.js --> -<!-- Do not edit this file directly. --> - -# [DNS.promises](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/promises.js#L17) - - - This module enables name resolution. For example, use it to look up IP - addresses of host names. Although named for the Domain Name System (DNS), - it does not always use the DNS protocol for lookups. dns.lookup() uses the - operating system facilities to perform name resolution. It may not need to - perform any network communication. To perform name resolution the way other - applications on the same system do, use dns.lookup(). - - Example usage: - ```js - import { lookup } from 'socket:dns/promises' - ``` - -## [`lookup(hostname, opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/promises.js#L37) - -External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options - - -| Argument | Type | Default | Optional | Description | -| :--- | :--- | :---: | :---: | :--- | -| hostname | string | | false | The host name to resolve. | -| opts | Object | | true | An options object. | -| opts.family | number \| string | 0 | true | The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. | - -| Return Value | Type | Description | -| :--- | :--- | :--- | -| Not specified | Promise | | - - -<!-- This file is generated by bin/docs-generator/api-module.js --> -<!-- Do not edit this file directly. --> - -# [Dgram](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L13) +# [dgram](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L13) This module provides an implementation of UDP datagram sockets. It does @@ -549,7 +471,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L651) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L651) Creates a `Socket` instance. @@ -568,12 +490,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L657) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L657) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L738) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L738) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -590,7 +512,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L853) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L853) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -610,7 +532,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L890) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L890) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -618,7 +540,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L949) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L949) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -669,7 +591,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1036) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1036) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -681,7 +603,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1108) +### [`address()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1108) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -697,7 +619,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1143) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1143) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -712,7 +634,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1174) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1174) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -723,7 +645,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1191) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1191) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -734,12 +656,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1204) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1204) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1212) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1212) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -748,46 +670,124 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1280) +### [`code()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1280) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1286) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1286) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1303) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1303) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1310) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1310) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1318) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1318) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1325) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1325) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1335) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1335) Thrown when a bad port is given. +<!-- This file is generated by bin/docs-generator/api-module.js --> +<!-- Do not edit this file directly. --> -# [Events](https://github.com/socketsupply/socket/blob/master/api/events.js) +# [dns](https://github.com/socketsupply/socket/blob/master/api/dns/index.js#L17) -Events module is a [third party](https://github.com/browserify/events/blob/main/events.js) module provided by Browserify and Node.js contributors (MIT License). -External docs: https://nodejs.org/api/events.html + This module enables name resolution. For example, use it to look up IP + addresses of host names. Although named for the Domain Name System (DNS), + it does not always use the DNS protocol for lookups. dns.lookup() uses the + operating system facilities to perform name resolution. It may not need to + perform any network communication. To perform name resolution the way other + applications on the same system do, use dns.lookup(). + + Example usage: + ```js + import { lookup } from 'socket:dns' + ``` + +## [`lookup(hostname, options, cb)`](https://github.com/socketsupply/socket/blob/master/api/dns/index.js#L60) + +External docs: https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback +Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or + AAAA (IPv6) record. All option properties are optional. If options is an + integer, then it must be 4 or 6 – if options is 0 or not provided, then IPv4 + and IPv6 addresses are both returned if found. + + From the node.js website... + + > With the all option set to true, the arguments for callback change to (err, + addresses), with addresses being an array of objects with the properties + address and family. + + > On error, err is an Error object, where err.code is the error code. Keep in + mind that err.code will be set to 'ENOTFOUND' not only when the host name does + not exist but also when the lookup fails in other ways such as no available + file descriptors. dns.lookup() does not necessarily have anything to do with + the DNS protocol. The implementation uses an operating system facility that + can associate names with addresses and vice versa. This implementation can + have subtle but important consequences on the behavior of any Node.js program. + Please take some time to consult the Implementation considerations section + before using dns.lookup(). + + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| hostname | string | | false | The host name to resolve. | +| options | object \| intenumberger | | true | An options object or record family. | +| options.family | number \| string | 0 | true | The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. | +| cb | function | | false | The function to call after the method is complete. | + + +<!-- This file is generated by bin/docs-generator/api-module.js --> +<!-- Do not edit this file directly. --> + +# [dns.promises](https://github.com/socketsupply/socket/blob/master/api/dns/promises.js#L17) + + + This module enables name resolution. For example, use it to look up IP + addresses of host names. Although named for the Domain Name System (DNS), + it does not always use the DNS protocol for lookups. dns.lookup() uses the + operating system facilities to perform name resolution. It may not need to + perform any network communication. To perform name resolution the way other + applications on the same system do, use dns.lookup(). + + Example usage: + ```js + import { lookup } from 'socket:dns/promises' + ``` + +## [`lookup(hostname, opts)`](https://github.com/socketsupply/socket/blob/master/api/dns/promises.js#L37) + +External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options + + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| hostname | string | | false | The host name to resolve. | +| opts | Object | | true | An options object. | +| opts.family | number \| string | 0 | true | The record family. Must be 4, 6, or 0. For backward compatibility reasons,'IPv4' and 'IPv6' are interpreted as 4 and 6 respectively. The value 0 indicates that IPv4 and IPv6 addresses are both returned. Default: 0. | + +| Return Value | Type | Description | +| :--- | :--- | :--- | +| Not specified | Promise | | + <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [FS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L26) +# [fs](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L26) This module enables interacting with the file system in a way modeled on @@ -811,7 +811,7 @@ External docs: https://nodejs.org/api/events.html import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L109) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L109) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -823,7 +823,7 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L134) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L134) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` @@ -834,7 +834,7 @@ Synchronously check access a file for a given mode calling `callback` | path | string \| Buffer \| URL | | false | | | mode | string? | F_OK(0) | true | | -## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L151) +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L151) Checks if a path exists @@ -843,7 +843,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L168) +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L168) Checks if a path exists @@ -852,7 +852,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L188) +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L188) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -866,7 +866,7 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L214) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L214) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Synchronously changes the permissions of a file. @@ -877,7 +877,7 @@ Synchronously changes the permissions of a file. | path | string \| Buffer \| URL | | false | | | mode | number | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L238) +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L238) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -888,7 +888,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L267) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L267) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -898,7 +898,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | uid | number | | false | | | gid | number | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L294) +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L294) External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -908,7 +908,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L314) +## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L314) Synchronously close a file descriptor. @@ -916,7 +916,7 @@ Synchronously close a file descriptor. | :--- | :--- | :---: | :---: | :--- | | fd | number | | false | fd | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L331) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L331) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -928,7 +928,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L363) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L363) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -939,7 +939,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L392) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L392) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -953,7 +953,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L437) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L437) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -967,7 +967,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L485) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L485) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -981,7 +981,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L512) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L512) Request that all data for the open file descriptor is flushed to the storage device. @@ -991,7 +991,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L534) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L534) Truncates the file up to `offset` bytes. @@ -1001,7 +1001,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L562) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L562) Chages ownership of link at `path` with `uid` and `gid. @@ -1012,7 +1012,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L592) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L592) Creates a link to `dest` from `src`. @@ -1022,7 +1022,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L680) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L680) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1035,7 +1035,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L733) +## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L733) Synchronously open a file. @@ -1046,7 +1046,7 @@ Synchronously open a file. | mode | string? | 0o666 | true | | | options | object? \| function? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L780) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L780) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1059,7 +1059,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L807) +## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L807) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously open a directory. @@ -1075,7 +1075,7 @@ Synchronously open a directory. | :--- | :--- | :--- | | Not specified | Dir | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L834) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L834) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1089,7 +1089,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L869) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L869) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1103,7 +1103,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L903) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L903) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1116,7 +1116,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L955) +## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L955) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously read all entries in a directory. @@ -1128,7 +1128,7 @@ Synchronously read all entries in a directory. | options.encoding ? utf8 | string? | | true | | | options.withFileTypes ? false | boolean? | | true | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L985) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L985) @@ -1141,7 +1141,7 @@ Synchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1027) +## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1027) @@ -1152,7 +1152,7 @@ Synchronously read all entries in a directory. | options | object? \| function(Error?, Buffer?) | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1090) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1090) Reads link at `path` @@ -1161,7 +1161,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1110) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1110) Computes real path for `path` @@ -1170,7 +1170,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1128) +## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1128) Computes real path for `path` @@ -1178,7 +1178,7 @@ Computes real path for `path` | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1146) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1146) Renames file or directory at `src` to `dest`. @@ -1188,7 +1188,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1172) +## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1172) Renames file or directory at `src` to `dest`, synchronously. @@ -1197,7 +1197,7 @@ Renames file or directory at `src` to `dest`, synchronously. | src | string | | false | | | dest | string | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1196) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1196) Removes directory at `path`. @@ -1206,7 +1206,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1216) +## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1216) Removes directory at `path`, synchronously. @@ -1214,7 +1214,7 @@ Removes directory at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1237) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1237) Synchronously get the stats of a file @@ -1225,7 +1225,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1257) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1257) Get the stats of a file @@ -1238,7 +1238,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1295) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1295) Get the stats of a symbolic link @@ -1251,7 +1251,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1329) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1329) Creates a symlink of `src` at `dest`. @@ -1260,7 +1260,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1372) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1372) Unlinks (removes) file at `path`. @@ -1269,7 +1269,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1392) +## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1392) Unlinks (removes) file at `path`, synchronously. @@ -1277,7 +1277,7 @@ Unlinks (removes) file at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1417) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1417) @@ -1292,7 +1292,7 @@ Unlinks (removes) file at `path`, synchronously. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1462) +## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1462) External docs: https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options Writes data to a file synchronously. @@ -1307,7 +1307,7 @@ Writes data to a file synchronously. | options.flag ? w | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1497) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1497) Watch for changes at `path` calling `callback` @@ -1326,7 +1326,7 @@ Watch for changes at `path` calling `callback` <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [FS.promises](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L25) +# [fs.promises](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L25) * This module enables interacting with the file system in a way modeled on @@ -1350,7 +1350,7 @@ Watch for changes at `path` calling `callback` import fs from 'socket:fs/promises' ``` -## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L107) +## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L107) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode Asynchronously check access a file. @@ -1361,7 +1361,7 @@ Asynchronously check access a file. | mode | string? | | true | | | options | object? | | true | | -## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L118) +## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L118) External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode @@ -1375,7 +1375,7 @@ External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L143) +## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L143) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -1389,7 +1389,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L172) +## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L172) Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -1403,7 +1403,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | :--- | :--- | :--- | | Not specified | Promise | | -## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L202) +## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L202) Chages ownership of link at `path` with `uid` and `gid. @@ -1417,7 +1417,7 @@ Chages ownership of link at `path` with `uid` and `gid. | :--- | :--- | :--- | | Not specified | Promise | | -## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L230) +## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L230) Creates a link to `dest` from `dest`. @@ -1430,7 +1430,7 @@ Creates a link to `dest` from `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L258) +## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L258) Asynchronously creates a directory. @@ -1446,7 +1446,7 @@ Asynchronously creates a directory. | :--- | :--- | :--- | | Not specified | Promise<any> | Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. | -## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L288) +## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L288) External docs: https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode Asynchronously open a file. @@ -1462,7 +1462,7 @@ Asynchronously open a file. | :--- | :--- | :--- | | Not specified | Promise<FileHandle> | | -## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L301) +## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L301) External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options @@ -1478,7 +1478,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options | :--- | :--- | :--- | | Not specified | Promise<Dir> | | -## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L314) +## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L314) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options @@ -1490,7 +1490,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | options.encoding | string? | utf8 | true | | | options.withFileTypes | boolean? | false | true | | -## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L352) +## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L352) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options @@ -1507,7 +1507,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | :--- | :--- | :--- | | Not specified | Promise<Buffer \| string> | | -## [`readlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L370) +## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L370) Reads link at `path` @@ -1519,7 +1519,7 @@ Reads link at `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`realpath(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L391) +## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L391) Computes real path for `path` @@ -1531,7 +1531,7 @@ Computes real path for `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L413) +## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L413) Renames file or directory at `src` to `dest`. @@ -1544,7 +1544,7 @@ Renames file or directory at `src` to `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L437) +## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L437) Removes directory at `path`. @@ -1556,7 +1556,7 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L459) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L459) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options Get the stats of a file @@ -1571,7 +1571,7 @@ Get the stats of a file | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L474) +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L474) External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options Get the stats of a symbolic link. @@ -1586,7 +1586,7 @@ Get the stats of a symbolic link. | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L487) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L487) Creates a symlink of `src` at `dest`. @@ -1599,7 +1599,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L523) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L523) Unlinks (removes) file at `path`. @@ -1611,7 +1611,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L548) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L548) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1630,7 +1630,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L569) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L569) Watch for changes at `path` calling `callback` @@ -1649,7 +1649,7 @@ Watch for changes at `path` calling `callback` <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [IPC](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L37) +# [ipc](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L37) This is a low-level API that you don't need unless you are implementing @@ -1683,17 +1683,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L270) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L270) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1022) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1022) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1183) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1183) Emit event to be dispatched on `window` object. @@ -1704,7 +1704,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1242) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1242) Sends an async IPC command request with parameters. @@ -1720,27 +1720,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1693) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1693) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1725) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1725) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1774) +## [ports](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1774) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1776) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1776) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1981) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1981) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. @@ -1749,7 +1749,7 @@ This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Network](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/network.js#L9) +# [network](https://github.com/socketsupply/socket/blob/master/api/network.js#L9) External docs: https://socketsupply.co/guides/#p2p-guide @@ -1760,7 +1760,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [OS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L13) +# [os](https://github.com/socketsupply/socket/blob/master/api/os.js#L13) This module provides normalized system information from all the major @@ -1771,7 +1771,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide import { arch, platform } from 'socket:os' ``` -## [`arch()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L60) +## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L60) Returns the operating system CPU architecture for which Socket was compiled. @@ -1779,7 +1779,7 @@ Returns the operating system CPU architecture for which Socket was compiled. | :--- | :--- | :--- | | Not specified | string | 'arm64', 'ia32', 'x64', or 'unknown' | -## [`cpus()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L78) +## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L78) External docs: https://nodejs.org/api/os.html#os_os_cpus Returns an array of objects containing information about each CPU/core. @@ -1797,7 +1797,7 @@ Returns an array of objects containing information about each CPU/core. | :--- | :--- | :--- | | cpus | Array<object> | An array of objects containing information about each CPU/core. | -## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L102) +## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L102) External docs: https://nodejs.org/api/os.html#os_os_networkinterfaces Returns an object containing network interfaces that have been assigned a network address. @@ -1815,7 +1815,7 @@ Returns an object containing network interfaces that have been assigned a networ | :--- | :--- | :--- | | Not specified | object | An object containing network interfaces that have been assigned a network address. | -## [`platform()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L190) +## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L190) External docs: https://nodejs.org/api/os.html#os_os_platform Returns the operating system platform. @@ -1825,7 +1825,7 @@ Returns the operating system platform. | :--- | :--- | :--- | | Not specified | string | 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' | -## [`type()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L199) +## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L199) External docs: https://nodejs.org/api/os.html#os_os_type Returns the operating system name. @@ -1834,7 +1834,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' | -## [`isWindows()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L238) +## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L238) @@ -1842,7 +1842,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | boolean | `true` if the operating system is Windows. | -## [`tmpdir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L250) +## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L250) @@ -1850,15 +1850,15 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system's default directory for temporary files. | -## [EOL](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L298) +## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L298) The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. -## [`rusage()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L310) +## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L310) Get resource usage. -## [`uptime()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L320) +## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L320) Returns the system uptime in seconds. @@ -1866,7 +1866,7 @@ Returns the system uptime in seconds. | :--- | :--- | :--- | | Not specified | number | The system uptime in seconds. | -## [`uname()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L331) +## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L331) Returns the operating system name. @@ -1874,7 +1874,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system name. | -## [`homedir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L389) +## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L389) Returns the home directory of the current user. @@ -1886,7 +1886,7 @@ Returns the home directory of the current user. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Path](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L9) +# [path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L9) Example usage: @@ -1894,7 +1894,7 @@ Returns the home directory of the current user. import { Path } from 'socket:path' ``` -## [`resolve()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L44) +## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L44) External docs: https://nodejs.org/api/path.html#path_path_resolve_paths The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -1907,7 +1907,7 @@ The path.resolve() method resolves a sequence of paths or path segments into an | :--- | :--- | :--- | | Not specified | string | | -## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L82) +## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L82) Computes current working directory for a path @@ -1920,7 +1920,7 @@ Computes current working directory for a path | :--- | :--- | :--- | | Not specified | string | | -## [`origin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L106) +## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L106) Computed location origin. Defaults to `socket:///` if not available. @@ -1928,7 +1928,7 @@ Computed location origin. Defaults to `socket:///` if not available. | :--- | :--- | :--- | | Not specified | string | | -## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L117) +## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L117) Computes the relative path from `from` to `to`. @@ -1942,7 +1942,7 @@ Computes the relative path from `from` to `to`. | :--- | :--- | :--- | | Not specified | string | | -## [`join(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L164) +## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L164) Joins path components. This function may not return an absolute path. @@ -1955,7 +1955,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L221) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L221) Computes directory name of path. @@ -1968,7 +1968,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L263) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L263) Computes base name of path. @@ -1981,7 +1981,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L277) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L277) Computes extension name of path. @@ -1994,7 +1994,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L288) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L288) Computes normalized path @@ -2007,7 +2007,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L338) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L338) Formats `Path` object into a string. @@ -2020,7 +2020,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L354) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L354) Parses input `path` into a `Path` instance. @@ -2032,11 +2032,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L382) +## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L382) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L388) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L388) Creates a `Path` instance from `input` and optional `cwd`. @@ -2045,7 +2045,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L411) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L411) `Path` class constructor. @@ -2054,47 +2054,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L484) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L484) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L491) +### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L491) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L525) +### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L525) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L533) +### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L533) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L552) +### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L552) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L573) +### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L573) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L608) +### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L608) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L620) +### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L620) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L628) +### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L628) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L648) +### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L648) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L655) +### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L655) @@ -2102,7 +2102,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L663) +### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L663) Converts this `Path` instance to a string. @@ -2114,7 +2114,7 @@ Converts this `Path` instance to a string. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Process](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L9) +# [process](https://github.com/socketsupply/socket/blob/master/api/process.js#L9) Example usage: @@ -2122,22 +2122,22 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L18) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L18) This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. -## [`ProcessEnvironment` (extends `EventTarget`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L29) +## [`ProcessEnvironment` (extends `EventTarget`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L29) This is a `ClassDeclaration` named ``ProcessEnvironment` (extends `EventTarget`)` in `api/process.js`, it's exported but undocumented. -## [env](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L34) +## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L35) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L197) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L202) Adds callback to the 'nextTick' queue. @@ -2145,7 +2145,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L230) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L235) Computed high resolution time as a `BigInt`. @@ -2157,7 +2157,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L256) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L261) @@ -2165,7 +2165,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L268) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L273) Returns an object describing the memory usage of the Node.js process measured in bytes. @@ -2177,7 +2177,7 @@ Returns an object describing the memory usage of the Node.js process measured in <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Test](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L17) +# [test](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L17) Provides a test runner for Socket Runtime. @@ -2191,7 +2191,7 @@ Returns an object describing the memory usage of the Node.js process measured in }) ``` -## [`getDefaultTestRunnerTimeout()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L54) +## [`getDefaultTestRunnerTimeout()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L54) @@ -2199,11 +2199,11 @@ Returns an object describing the memory usage of the Node.js process measured in | :--- | :--- | :--- | | Not specified | number | The default timeout for tests in milliseconds. | -## [Test](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L69) +## [Test](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L69) -### [`constructor(name, fn, runner)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L127) +### [`constructor(name, fn, runner)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L127) @@ -2213,7 +2213,7 @@ Returns an object describing the memory usage of the Node.js process measured in | fn | TestFn | | false | | | runner | TestRunner | | false | | -### [`comment(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L138) +### [`comment(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L138) @@ -2221,7 +2221,7 @@ Returns an object describing the memory usage of the Node.js process measured in | :--- | :--- | :---: | :---: | :--- | | msg | string | | false | | -### [`plan(n)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L148) +### [`plan(n)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L148) Plan the number of assertions. @@ -2230,7 +2230,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | n | number | | false | | -### [`deepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L159) +### [`deepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L159) @@ -2240,7 +2240,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`notDeepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L174) +### [`notDeepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L174) @@ -2250,7 +2250,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`equal(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L189) +### [`equal(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L189) @@ -2260,7 +2260,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`notEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L204) +### [`notEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L204) @@ -2270,7 +2270,7 @@ Plan the number of assertions. | expected | unknown | | false | | | msg | string | | true | | -### [`fail(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L217) +### [`fail(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L217) @@ -2278,7 +2278,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | msg | string | | true | | -### [`ok(actual, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L230) +### [`ok(actual, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L230) @@ -2287,7 +2287,7 @@ Plan the number of assertions. | actual | unknown | | false | | | msg | string | | true | | -### [`pass(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L242) +### [`pass(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L242) @@ -2295,7 +2295,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | msg | string | | true | | -### [`ifError(err, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L251) +### [`ifError(err, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L251) @@ -2304,7 +2304,7 @@ Plan the number of assertions. | err | Error \| null \| undefined | | false | | | msg | string | | true | | -### [`throws(fn, expected, message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L264) +### [`throws(fn, expected, message)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L264) @@ -2314,7 +2314,7 @@ Plan the number of assertions. | expected | RegExp \| any | | true | | | message | string | | true | | -### [`sleep(ms, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L313) +### [`sleep(ms, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L313) Sleep for ms with an optional msg @@ -2332,7 +2332,7 @@ Sleep for ms with an optional msg | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`requestAnimationFrame(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L331) +### [`requestAnimationFrame(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L331) Request animation frame with an optional msg. Falls back to a 0ms setTimeout when tests are run headlessly. @@ -2350,7 +2350,7 @@ Request animation frame with an optional msg. Falls back to a 0ms setTimeout whe | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`click(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L354) +### [`click(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L354) Dispatch the `click`` method on an element specified by selector. @@ -2368,7 +2368,7 @@ Dispatch the `click`` method on an element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`eventClick(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L380) +### [`eventClick(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L380) Dispatch the click window.MouseEvent on an element specified by selector. @@ -2386,7 +2386,7 @@ Dispatch the click window.MouseEvent on an element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`dispatchEvent(event, target, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L408) +### [`dispatchEvent(event, target, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L408) Dispatch an event on the target. @@ -2405,7 +2405,7 @@ Dispatch an event on the target. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`focus(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L428) +### [`focus(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L428) Call the focus method on element specified by selector. @@ -2423,7 +2423,7 @@ Call the focus method on element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`blur(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L452) +### [`blur(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L452) Call the blur method on element specified by selector. @@ -2441,7 +2441,7 @@ Call the blur method on element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`type(selector, str, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L477) +### [`type(selector, str, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L477) Consecutively set the str value of the element specified by selector to simulate typing. @@ -2460,7 +2460,7 @@ Consecutively set the str value of the element specified by selector to simulate | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`appendChild(parentSelector, el, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L509) +### [`appendChild(parentSelector, el, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L509) appendChild an element el to a parent selector element. @@ -2480,7 +2480,7 @@ appendChild an element el to a parent selector element. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`removeElement(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L529) +### [`removeElement(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L529) Remove an element from the DOM. @@ -2498,7 +2498,7 @@ Remove an element from the DOM. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`elementVisible(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L548) +### [`elementVisible(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L548) Test if an element is visible @@ -2516,7 +2516,7 @@ Test if an element is visible | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`elementInvisible(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L569) +### [`elementInvisible(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L569) Test if an element is invisible @@ -2534,7 +2534,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`waitFor(querySelectorOrFn, opts, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L593) +### [`waitFor(querySelectorOrFn, opts, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L593) Test if an element is invisible @@ -2555,7 +2555,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<HTMLElement \| Element \| void> | | -### [`waitForText(selector, opts, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L655) +### [`waitForText(selector, opts, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L655) Test if an element is invisible @@ -2585,7 +2585,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<HTMLElement \| Element \| void> | | -### [`querySelector(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L692) +### [`querySelector(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L692) Run a querySelector as an assert and also get the results @@ -2603,7 +2603,7 @@ Run a querySelector as an assert and also get the results | :--- | :--- | :--- | | Not specified | HTMLElement \| Element | | -### [`querySelectorAll(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L711) +### [`querySelectorAll(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L711) Run a querySelectorAll as an assert and also get the results @@ -2621,7 +2621,7 @@ Run a querySelectorAll as an assert and also get the results | :--- | :--- | :--- | | Not specified | Array<HTMLElement \| Element> | | -### [`getComputedStyle(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L740) +### [`getComputedStyle(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L740) Retrieves the computed styles for a given element. @@ -2646,17 +2646,17 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | CSSStyleDeclaration | The computed styles of the element. | -### [`run()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L837) +### [`run()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L837) pass: number, fail: number }>} -## [TestRunner](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L918) +## [TestRunner](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L918) -### [`constructor(report)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L969) +### [`constructor(report)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L969) @@ -2664,7 +2664,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :---: | :---: | :--- | | report | (lines: string) => void | | true | | -### [`nextId()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L978) +### [`nextId()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L978) @@ -2672,11 +2672,11 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | string | | -### [`length()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L985) +### [`length()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L985) -### [`add(name, fn, only)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L995) +### [`add(name, fn, only)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L995) @@ -2686,7 +2686,7 @@ Retrieves the computed styles for a given element. | fn | TestFn | | false | | | only | boolean | | false | | -### [`run()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1017) +### [`run()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1017) @@ -2694,7 +2694,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`onFinish())`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1064) +### [`onFinish())`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1064) @@ -2702,7 +2702,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :---: | :---: | :--- | | ) | (result: { total: number, success: number, fail: number | > void} callback | false | | -## [`only(name, fn)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1092) +## [`only(name, fn)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1092) @@ -2711,7 +2711,7 @@ Retrieves the computed styles for a given element. | name | string | | false | | | fn | TestFn | | true | | -## [`skip(_name, _fn)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1102) +## [`skip(_name, _fn)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1102) @@ -2720,7 +2720,7 @@ Retrieves the computed styles for a given element. | _name | string | | false | | | _fn | TestFn | | true | | -## [`setStrict(strict)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1108) +## [`setStrict(strict)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1108) @@ -2732,7 +2732,7 @@ Retrieves the computed styles for a given element. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [Window](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L12) +# [window](https://github.com/socketsupply/socket/blob/master/api/window.js#L12) Provides ApplicationWindow class and methods @@ -2741,15 +2741,15 @@ Retrieves the computed styles for a given element. `socket:application` methods like `getCurrentWindow`, `createWindow`, `getWindow`, and `getWindows`. -## [ApplicationWindow](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L35) +## [ApplicationWindow](https://github.com/socketsupply/socket/blob/master/api/window.js#L35) Represents a window in the application -### [`id()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L69) +### [`id()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L69) The unique ID of this window. -### [`index()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L77) +### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L77) Get the index of the window @@ -2757,15 +2757,15 @@ Get the index of the window | :--- | :--- | :--- | | Not specified | number | the index of the window | -### [`hotkey()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L84) +### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L84) -### [`channel()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L92) +### [`channel()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L92) The broadcast channel for this window. -### [`getSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L100) +### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L100) Get the size of the window @@ -2773,7 +2773,7 @@ Get the size of the window | :--- | :--- | :--- | | Not specified | { width: number, height: number | } - the size of the window | -### [`getPosition()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L111) +### [`getPosition()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L111) Get the position of the window @@ -2781,7 +2781,7 @@ Get the position of the window | :--- | :--- | :--- | | Not specified | { x: number, y: number | } - the position of the window | -### [`getTitle()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L122) +### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L122) Get the title of the window @@ -2789,7 +2789,7 @@ Get the title of the window | :--- | :--- | :--- | | Not specified | string | the title of the window | -### [`getStatus()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L130) +### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L130) Get the status of the window @@ -2797,7 +2797,7 @@ Get the status of the window | :--- | :--- | :--- | | Not specified | string | the status of the window | -### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L138) +### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L138) Close the window @@ -2805,7 +2805,7 @@ Close the window | :--- | :--- | :--- | | Not specified | Promise<object> | the options of the window | -### [`show()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L153) +### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L153) Shows the window @@ -2813,7 +2813,7 @@ Shows the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`hide()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L162) +### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L162) Hides the window @@ -2821,7 +2821,7 @@ Hides the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`maximize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L171) +### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L171) Maximize the window @@ -2829,7 +2829,7 @@ Maximize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`minimize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L180) +### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L180) Minimize the window @@ -2837,7 +2837,7 @@ Minimize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`restore()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L189) +### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L189) Restore the window @@ -2845,7 +2845,7 @@ Restore the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L199) +### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L199) Sets the title of the window @@ -2857,7 +2857,7 @@ Sets the title of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L212) +### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L212) Sets the size of the window @@ -2871,7 +2871,7 @@ Sets the size of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L255) +### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L255) Sets the position of the window @@ -2885,7 +2885,7 @@ Sets the position of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`navigate(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L299) +### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L299) Navigate the window to a given path @@ -2897,7 +2897,7 @@ Navigate the window to a given path | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`showInspector()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L308) +### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L308) Opens the Web Inspector for the window @@ -2905,7 +2905,7 @@ Opens the Web Inspector for the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L325) +### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L325) Sets the background color of the window @@ -2921,7 +2921,7 @@ Sets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L334) +### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L334) Gets the background color of the window @@ -2929,7 +2929,7 @@ Gets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L343) +### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L343) Opens a native context menu. @@ -2941,7 +2941,7 @@ Opens a native context menu. | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L352) +### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L352) Shows a native open file dialog. @@ -2953,7 +2953,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L370) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L370) Shows a native save file dialog. @@ -2965,7 +2965,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L388) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L388) Shows a native directory dialog. @@ -2977,7 +2977,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L413) +### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L413) This is a high-level API that you should use instead of `ipc.request` when you want to send a message to another window or to the backend. @@ -2991,7 +2991,7 @@ This is a high-level API that you should use instead of `ipc.request` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L454) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L454) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -3004,7 +3004,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L473) +### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L473) Opens an URL in the default application associated with the URL protocol, such as 'https:' for the default web browser. @@ -3017,7 +3017,7 @@ Opens an URL in the default application associated with the URL protocol, | :--- | :--- | :--- | | Not specified | Promise<{ url: string | >} | -### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L488) +### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L488) Opens a file in the default file explorer. @@ -3029,7 +3029,7 @@ Opens a file in the default file explorer. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L503) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L503) Adds a listener to the window. @@ -3038,7 +3038,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L521) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L521) Adds a listener to the window. An alias for `addListener`. @@ -3047,7 +3047,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L538) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L538) Adds a listener to the window. The listener is removed after the first call. @@ -3056,7 +3056,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L554) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L554) Removes a listener from the window. @@ -3065,7 +3065,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L567) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L567) Removes all listeners from the window. @@ -3073,7 +3073,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L583) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L583) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/ai.js b/api/ai.js index f2661c4ad9..e56a3ae4eb 100644 --- a/api/ai.js +++ b/api/ai.js @@ -1,6 +1,6 @@ // @ts-check /** - * @module AI + * @module ai * * Provides high level classes for common AI tasks. * diff --git a/api/application.js b/api/application.js index 286f513ed9..e0d5370156 100644 --- a/api/application.js +++ b/api/application.js @@ -1,6 +1,6 @@ // @ts-check /** - * @module Application + * @module application * * Provides Application level methods * diff --git a/api/async.js b/api/async.js index adf0aa6ad4..0c4e8ba6d4 100644 --- a/api/async.js +++ b/api/async.js @@ -1,5 +1,5 @@ /** - * @module Async + * @module async * * Various primitives for async hooks, storage, resources, and contexts. */ diff --git a/api/async/context.js b/api/async/context.js index 0b0ebd3bc5..7add8bbcad 100644 --- a/api/async/context.js +++ b/api/async/context.js @@ -1,5 +1,5 @@ /** - * @module Async.AsyncContext + * @module async.context * * Async Context for JavaScript based on the TC39 proposal. * diff --git a/api/async/hooks.js b/api/async/hooks.js index 3519592844..77cbda3a84 100644 --- a/api/async/hooks.js +++ b/api/async/hooks.js @@ -1,5 +1,5 @@ /** - * @module AsyncHooks + * @module async.hooks * * Primitives for hooks into async lifecycles such as `queueMicrotask`, * `setTimeout`, `setInterval`, and `Promise` built-ins as well as user defined diff --git a/api/async/resource.js b/api/async/resource.js index 8f4bd84fc4..7bc30bbe93 100644 --- a/api/async/resource.js +++ b/api/async/resource.js @@ -1,5 +1,5 @@ /** - * @module AsyncContext + * @module async.resource * * Primitives for creating user defined async resources that implement * async hooks. diff --git a/api/async/storage.js b/api/async/storage.js index 17d966ab6d..4541287fed 100644 --- a/api/async/storage.js +++ b/api/async/storage.js @@ -1,5 +1,5 @@ /** - * @module AsyncStorage + * @module async.storage * * Primitives for creating user defined async storage contexts. */ diff --git a/api/bluetooth.js b/api/bluetooth.js index a762728844..4406282a84 100644 --- a/api/bluetooth.js +++ b/api/bluetooth.js @@ -1,5 +1,5 @@ /** - * @module Bluetooth + * @module bluetooth * * A high-level, cross-platform API for Bluetooth Pub-Sub * diff --git a/api/bootstrap.js b/api/bootstrap.js index e96f9a8632..79568b5491 100644 --- a/api/bootstrap.js +++ b/api/bootstrap.js @@ -1,5 +1,5 @@ /** - * @module Bootstrap + * @module bootstrap * * This module is responsible for downloading the bootstrap file and writing it to disk. * It also provides a function to check the hash of the file on disk. This is used to diff --git a/api/buffer.js b/api/buffer.js index 012db82088..4a1ed1a736 100644 --- a/api/buffer.js +++ b/api/buffer.js @@ -1,5 +1,5 @@ /** - * @module Buffer + * @module buffer * * The buffer module from node.js, for the browser. * diff --git a/api/crypto.js b/api/crypto.js index 9c68889f1a..bf3c452853 100644 --- a/api/crypto.js +++ b/api/crypto.js @@ -1,7 +1,7 @@ /* global console */ /* eslint-disable no-fallthrough */ /** - * @module Crypto + * @module crypto * * Some high-level methods around the `crypto.subtle` API for getting * random bytes and hashing. diff --git a/api/dgram.js b/api/dgram.js index 1cd527c8ca..bc943b4eb5 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -1,5 +1,5 @@ /** - * @module Dgram + * @module dgram * * This module provides an implementation of UDP datagram sockets. It does * not (yet) provide any of the multicast methods or properties. diff --git a/api/dns/index.js b/api/dns/index.js index d7130a9a5c..03a307a47b 100644 --- a/api/dns/index.js +++ b/api/dns/index.js @@ -1,5 +1,5 @@ /** - * @module DNS + * @module dns * * This module enables name resolution. For example, use it to look up IP * addresses of host names. Although named for the Domain Name System (DNS), diff --git a/api/dns/promises.js b/api/dns/promises.js index 35db86bfbe..770c599049 100644 --- a/api/dns/promises.js +++ b/api/dns/promises.js @@ -1,5 +1,5 @@ /** - * @module DNS.promises + * @module dns.promises * * This module enables name resolution. For example, use it to look up IP * addresses of host names. Although named for the Domain Name System (DNS), diff --git a/api/fs/index.js b/api/fs/index.js index 797e42d866..a28816addc 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -1,5 +1,5 @@ /** - * @module FS + * @module fs * * This module enables interacting with the file system in a way modeled on * standard POSIX functions. diff --git a/api/fs/promises.js b/api/fs/promises.js index 9d08cd3003..5c13428587 100644 --- a/api/fs/promises.js +++ b/api/fs/promises.js @@ -1,5 +1,5 @@ /** - * @module FS.promises + * @module fs.promises * * * This module enables interacting with the file system in a way modeled on * standard POSIX functions. diff --git a/api/fs/stream.js b/api/fs/stream.js index c77940a687..c79b0d6761 100644 --- a/api/fs/stream.js +++ b/api/fs/stream.js @@ -1,5 +1,5 @@ /** - * @module FS.Stream + * @module fs.stream */ import { Readable, Writable } from '../stream.js' import { AbortError } from '../errors.js' diff --git a/api/index.d.ts b/api/index.d.ts index 6078fe6163..4d1d6d11b0 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -1,7 +1,7 @@ declare module "socket:async/context" { /** - * @module Async.AsyncContext + * @module async.context * * Async Context for JavaScript based on the TC39 proposal. * @@ -2230,993 +2230,6 @@ declare module "socket:util" { } -declare module "socket:async/wrap" { - /** - * Returns `true` if a given function `fn` has the "async" wrapped tag, - * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this - * function will return `false`. - * @ignore - * @param {function} fn - * @param {boolean} - */ - export function isTagged(fn: Function): boolean; - /** - * Tags a function `fn` as being "async wrapped" so subsequent calls to - * `wrap(fn)` do not wrap an already wrapped function. - * @ignore - * @param {function} fn - * @return {function} - */ - export function tag(fn: Function): Function; - /** - * Wraps a function `fn` that captures a snapshot of the current async - * context. This function is idempotent and will not wrap a function more - * than once. - * @ignore - * @param {function} fn - * @return {function} - */ - export function wrap(fn: Function): Function; - export const symbol: unique symbol; - export default wrap; -} - -declare module "socket:internal/async/hooks" { - export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - export function getNextAsyncResourceId(): number; - export function executionAsyncResource(): any; - export function executionAsyncId(): any; - export function triggerAsyncId(): any; - export function getDefaultExecutionAsyncId(): any; - export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; - export function getTopLevelAsyncResourceName(): any; - /** - * The default top level async resource ID - * @type {number} - */ - export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; - export namespace state { - let defaultExecutionAsyncId: number; - } - export namespace hooks { - let init: any[]; - let before: any[]; - let after: any[]; - let destroy: any[]; - let promiseResolve: any[]; - } - /** - * A base class for the `AsyncResource` class or other higher level async - * resource classes. - */ - export class CoreAsyncResource { - /** - * `CoreAsyncResource` class constructor. - * @param {string} type - * @param {object|number=} [options] - */ - constructor(type: string, options?: (object | number) | undefined); - /** - * The `CoreAsyncResource` type. - * @type {string} - */ - get type(): string; - /** - * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This - * value is only set to `true` if `emitDestroy()` was called, likely from - * destroying the resource manually. - * @type {boolean} - */ - get destroyed(): boolean; - /** - * The unique async resource ID. - * @return {number} - */ - asyncId(): number; - /** - * The trigger async resource ID. - * @return {number} - */ - triggerAsyncId(): number; - /** - * Manually emits destroy hook for the resource. - * @return {CoreAsyncResource} - */ - emitDestroy(): CoreAsyncResource; - /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @return {function} - */ - bind(fn: Function, thisArg?: object | undefined): Function; - /** - * Runs function `fn` in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @param {...any} [args] - * @return {any} - */ - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; - #private; - } - export class TopLevelAsyncResource extends CoreAsyncResource { - } - export const asyncContextVariable: Variable<any>; - export const topLevelAsyncResource: TopLevelAsyncResource; - export default hooks; - import { Variable } from "socket:async/context"; -} - -declare module "socket:async/resource" { - /** - * @typedef {{ - * triggerAsyncId?: number, - * requireManualDestroy?: boolean - * }} AsyncResourceOptions - */ - /** - * A container that should be extended that represents a resource with - * an asynchronous execution context. - */ - export class AsyncResource extends CoreAsyncResource { - /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of an anonymous `AsyncResource`. - * @param {function} fn - * @param {object|string=} [type] - * @param {object=} [thisArg] - * @return {function} - */ - static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; - /** - * `AsyncResource` class constructor. - * @param {string} type - * @param {AsyncResourceOptions|number=} [options] - */ - constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); - } - export default AsyncResource; - export type AsyncResourceOptions = { - triggerAsyncId?: number; - requireManualDestroy?: boolean; - }; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - import { CoreAsyncResource } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/hooks" { - /** - * Factory for creating a `AsyncHook` instance. - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] - * @return {AsyncHook} - */ - export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; - /** - * A container for `AsyncHooks` callbacks. - * @ignore - */ - export class AsyncHookCallbacks { - /** - * `AsyncHookCallbacks` class constructor. - * @ignore - * @param {AsyncHookCallbackOptions} [options] - */ - constructor(options?: AsyncHookCallbackOptions); - init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - before(asyncId: any): void; - after(asyncId: any): void; - destroy(asyncId: any): void; - promiseResolve(asyncId: any): void; - } - /** - * A container for registering various callbacks for async resource hooks. - */ - export class AsyncHook { - /** - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] - */ - constructor(callbacks?: any); - /** - * @type {boolean} - */ - get enabled(): boolean; - /** - * Enable the async hook. - * @return {AsyncHook} - */ - enable(): AsyncHook; - /** - * Disables the async hook - * @return {AsyncHook} - */ - disable(): AsyncHook; - #private; - } - export default createHook; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/storage" { - /** - * A container for storing values that remain present during - * asynchronous operations. - */ - export class AsyncLocalStorage { - /** - * Binds function `fn` to run in the execution context of an - * anonymous `AsyncResource`. - * @param {function} fn - * @return {function} - */ - static bind(fn: Function): Function; - /** - * Captures the current async context and returns a function that runs - * a function in that execution context. - * @return {function} - */ - static snapshot(): Function; - /** - * @type {boolean} - */ - get enabled(): boolean; - /** - * Disables the `AsyncLocalStorage` instance. When disabled, - * `getStore()` will always return `undefined`. - */ - disable(): void; - /** - * Enables the `AsyncLocalStorage` instance. - */ - enable(): void; - /** - * Enables and sets the `AsyncLocalStorage` instance default store value. - * @param {any} store - */ - enterWith(store: any): void; - /** - * Runs function `fn` in the current asynchronous execution context with - * a given `store` value and arguments given to `fn`. - * @param {any} store - * @param {function} fn - * @param {...any} args - * @return {any} - */ - run(store: any, fn: Function, ...args: any[]): any; - exit(fn: any, ...args: any[]): any; - /** - * If the `AsyncLocalStorage` instance is enabled, it returns the current - * store value for this asynchronous execution context. - * @return {any|undefined} - */ - getStore(): any | undefined; - #private; - } - export default AsyncLocalStorage; -} - -declare module "socket:async/deferred" { - /** - * Dispatched when a `Deferred` internal promise is resolved. - */ - export class DeferredResolveEvent extends Event { - /** - * `DeferredResolveEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {any=} [result] - */ - constructor(type?: string | undefined, result?: any | undefined); - /** - * The `Deferred` promise result value. - * @type {any?} - */ - result: any | null; - } - /** - * Dispatched when a `Deferred` internal promise is rejected. - */ - export class DeferredRejectEvent { - /** - * `DeferredRejectEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {Error=} [error] - */ - constructor(type?: string | undefined, error?: Error | undefined); - } - /** - * A utility class for creating deferred promises. - */ - export class Deferred extends EventTarget { - /** - * `Deferred` class constructor. - * @param {Deferred|Promise?} [promise] - */ - constructor(promise?: Deferred | (Promise<any> | null)); - /** - * Function to resolve the associated promise. - * @type {function} - */ - resolve: Function; - /** - * Function to reject the associated promise. - * @type {function} - */ - reject: Function; - /** - * Attaches a fulfillment callback and a rejection callback to the promise, - * and returns a new promise resolving to the return value of the called - * callback. - * @param {function(any)=} [resolve] - * @param {function(Error)=} [reject] - */ - then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise<any>; - /** - * Attaches a rejection callback to the promise, and returns a new promise - * resolving to the return value of the callback if it is called, or to its - * original fulfillment value if the promise is instead fulfilled. - * @param {function(Error)=} [callback] - */ - catch(callback?: ((arg0: Error) => any) | undefined): Promise<any>; - /** - * Attaches a callback for when the promise is settled (fulfilled or rejected). - * @param {function(any?)} [callback] - */ - finally(callback?: (arg0: any | null) => any): Promise<any>; - /** - * The promise associated with this Deferred instance. - * @type {Promise<any>} - */ - get promise(): Promise<any>; - /** - * A string representation of this Deferred instance. - * @type {string} - * @ignore - */ - get [Symbol.toStringTag](): string; - #private; - } - export default Deferred; -} - -declare module "socket:async" { - export default exports; - import AsyncLocalStorage from "socket:async/storage"; - import AsyncResource from "socket:async/resource"; - import AsyncContext from "socket:async/context"; - import Deferred from "socket:async/deferred"; - import { executionAsyncResource } from "socket:async/hooks"; - import { executionAsyncId } from "socket:async/hooks"; - import { triggerAsyncId } from "socket:async/hooks"; - import { createHook } from "socket:async/hooks"; - import { AsyncHook } from "socket:async/hooks"; - import * as exports from "socket:async"; - - export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; -} - -declare module "socket:application/menu" { - /** - * Internal IPC for setting an application menu - * @ignore - */ - export function setMenu(options: any, type: any): Promise<ipc.Result>; - /** - * Internal IPC for setting an application context menu - * @ignore - */ - export function setContextMenu(options: any): Promise<any>; - /** - * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. - */ - export class Menu extends EventTarget { - /** - * `Menu` class constructor. - * @ignore - * @param {string} type - */ - constructor(type: string); - /** - * The broadcast channel for this menu. - * @ignore - * @type {BroadcastChannel} - */ - get channel(): BroadcastChannel; - /** - * The `Menu` instance type. - * @type {('context'|'system'|'tray')?} - */ - get type(): "tray" | "system" | "context"; - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * Set the menu layout for this `Menu` instance. - * @param {string|object} layoutOrOptions - * @param {object=} [options] - */ - set(layoutOrOptions: string | object, options?: object | undefined): Promise<any>; - #private; - } - /** - * A container for various `Menu` instances. - */ - export class MenuContainer extends EventTarget { - /** - * `MenuContainer` class constructor. - * @param {EventTarget} [sourceEventTarget] - * @param {object=} [options] - */ - constructor(sourceEventTarget?: EventTarget, options?: object | undefined); - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * The `TrayMenu` instance for the application. - * @type {TrayMenu} - */ - get tray(): TrayMenu; - /** - * The `SystemMenu` instance for the application. - * @type {SystemMenu} - */ - get system(): SystemMenu; - /** - * The `ContextMenu` instance for the application. - * @type {ContextMenu} - */ - get context(): ContextMenu; - #private; - } - /** - * A `Menu` instance that represents a context menu. - */ - export class ContextMenu extends Menu { - constructor(); - } - /** - * A `Menu` instance that represents the system menu. - */ - export class SystemMenu extends Menu { - constructor(); - } - /** - * A `Menu` instance that represents the tray menu. - */ - export class TrayMenu extends Menu { - constructor(); - } - /** - * The application tray menu. - * @type {TrayMenu} - */ - export const tray: TrayMenu; - /** - * The application system menu. - * @type {SystemMenu} - */ - export const system: SystemMenu; - /** - * The application context menu. - * @type {ContextMenu} - */ - export const context: ContextMenu; - /** - * The application menus container. - * @type {MenuContainer} - */ - export const container: MenuContainer; - export default container; - import ipc from "socket:ipc"; -} - -declare module "socket:internal/events" { - /** - * An event dispatched when an application URL is opening the application. - */ - export class ApplicationURLEvent extends Event { - /** - * `ApplicationURLEvent` class constructor. - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * `true` if the application URL is valid (parses correctly). - * @type {boolean} - */ - get isValid(): boolean; - /** - * Data associated with the `ApplicationURLEvent`. - * @type {?any} - */ - get data(): any; - /** - * The original source URI - * @type {?string} - */ - get source(): string; - /** - * The `URL` for the `ApplicationURLEvent`. - * @type {?URL} - */ - get url(): URL; - /** - * String tag name for an `ApplicationURLEvent` instance. - * @type {string} - */ - get [Symbol.toStringTag](): string; - #private; - } - /** - * An event dispacted for a registered global hotkey expression. - */ - export class HotKeyEvent extends MessageEvent<any> { - /** - * `HotKeyEvent` class constructor. - * @ignore - * @param {string=} [type] - * @param {object=} [data] - */ - constructor(type?: string | undefined, data?: object | undefined); - /** - * The global unique ID for this hotkey binding. - * @type {number?} - */ - get id(): number; - /** - * The computed hash for this hotkey binding. - * @type {number?} - */ - get hash(): number; - /** - * The normalized hotkey expression as a sequence of tokens. - * @type {string[]} - */ - get sequence(): string[]; - /** - * The original expression of the hotkey binding. - * @type {string?} - */ - get expression(): string; - } - /** - * An event dispacted when a menu item is selected. - */ - export class MenuItemEvent extends MessageEvent<any> { - /** - * `MenuItemEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [data] - * @param {import('../application/menu.js').Menu} menu - */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); - /** - * The `Menu` this event has been dispatched for. - * @type {import('../application/menu.js').Menu?} - */ - get menu(): import("socket:application/menu").Menu; - /** - * The title of the menu item. - * @type {string?} - */ - get title(): string; - /** - * An optional tag value for the menu item that may also be the - * parent menu item title. - * @type {string?} - */ - get tag(): string; - /** - * The parent title of the menu item. - * @type {string?} - */ - get parent(): string; - #private; - } - /** - * An event dispacted when the application receives an OS signal - */ - export class SignalEvent extends MessageEvent<any> { - /** - * `SignalEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * The code of the signal. - * @type {import('../signal.js').signal} - */ - get code(): number; - /** - * The name of the signal. - * @type {string} - */ - get name(): string; - /** - * An optional message describing the signal - * @type {string} - */ - get message(): string; - #private; - } - namespace _default { - export { ApplicationURLEvent }; - export { MenuItemEvent }; - export { SignalEvent }; - export { HotKeyEvent }; - } - export default _default; -} - -declare module "socket:path/well-known" { - /** - * Well known path to the user's "Downloads" folder. - * @type {?string} - */ - export const DOWNLOADS: string | null; - /** - * Well known path to the user's "Documents" folder. - * @type {?string} - */ - export const DOCUMENTS: string | null; - /** - * Well known path to the user's "Pictures" folder. - * @type {?string} - */ - export const PICTURES: string | null; - /** - * Well known path to the user's "Desktop" folder. - * @type {?string} - */ - export const DESKTOP: string | null; - /** - * Well known path to the user's "Videos" folder. - * @type {?string} - */ - export const VIDEOS: string | null; - /** - * Well known path to the user's "Music" folder. - * @type {?string} - */ - export const MUSIC: string | null; - /** - * Well known path to the application's "resources" folder. - * @type {?string} - */ - export const RESOURCES: string | null; - /** - * Well known path to the application's "config" folder. - * @type {?string} - */ - export const CONFIG: string | null; - /** - * Well known path to the application's "data" folder. - * @type {?string} - */ - export const DATA: string | null; - /** - * Well known path to the application's "log" folder. - * @type {?string} - */ - export const LOG: string | null; - /** - * Well known path to the application's "tmp" folder. - * @type {?string} - */ - export const TMP: string | null; - /** - * Well known path to the application's "home" folder. - * This may be the user's HOME directory or the application container sandbox. - * @type {?string} - */ - export const HOME: string | null; - namespace _default { - export { DOWNLOADS }; - export { DOCUMENTS }; - export { RESOURCES }; - export { PICTURES }; - export { DESKTOP }; - export { VIDEOS }; - export { CONFIG }; - export { MUSIC }; - export { HOME }; - export { DATA }; - export { LOG }; - export { TMP }; - } - export default _default; -} - -declare module "socket:os" { - /** - * Returns the operating system CPU architecture for which Socket was compiled. - * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' - */ - export function arch(): string; - /** - * Returns an array of objects containing information about each CPU/core. - * @returns {Array<object>} cpus - An array of objects containing information about each CPU/core. - * The properties of the objects are: - * - model `<string>` - CPU model name. - * - speed `<number>` - CPU clock speed (in MHz). - * - times `<object>` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. - * - user `<number>` - Time spent by this CPU or core in user mode. - * - nice `<number>` - Time spent by this CPU or core in user mode with low priority (nice). - * - sys `<number>` - Time spent by this CPU or core in system mode. - * - idle `<number>` - Time spent by this CPU or core in idle mode. - * - irq `<number>` - Time spent by this CPU or core in IRQ mode. - * @see {@link https://nodejs.org/api/os.html#os_os_cpus} - */ - export function cpus(): Array<object>; - /** - * Returns an object containing network interfaces that have been assigned a network address. - * @returns {object} - An object containing network interfaces that have been assigned a network address. - * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. - * The properties available on the assigned network address object include: - * - address `<string>` - The assigned IPv4 or IPv6 address. - * - netmask `<string>` - The IPv4 or IPv6 network mask. - * - family `<string>` - The address family ('IPv4' or 'IPv6'). - * - mac `<string>` - The MAC address of the network interface. - * - internal `<boolean>` - Indicates whether the network interface is a loopback interface. - * - scopeid `<number>` - The numeric scope ID (only specified when family is 'IPv6'). - * - cidr `<string>` - The CIDR notation of the interface. - * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} - */ - export function networkInterfaces(): object; - /** - * Returns the operating system platform. - * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_platform} - * The returned value is equivalent to `process.platform`. - */ - export function platform(): string; - /** - * Returns the operating system name. - * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_type} - */ - export function type(): string; - /** - * @returns {boolean} - `true` if the operating system is Windows. - */ - export function isWindows(): boolean; - /** - * @returns {string} - The operating system's default directory for temporary files. - */ - export function tmpdir(): string; - /** - * Get resource usage. - */ - export function rusage(): any; - /** - * Returns the system uptime in seconds. - * @returns {number} - The system uptime in seconds. - */ - export function uptime(): number; - /** - * Returns the operating system name. - * @returns {string} - The operating system name. - */ - export function uname(): string; - /** - * It's implemented in process.hrtime.bigint() - * @ignore - */ - export function hrtime(): any; - /** - * Node.js doesn't have this method. - * @ignore - */ - export function availableMemory(): any; - /** - * The host operating system. This value can be one of: - * - android - * - android-emulator - * - iphoneos - * - iphone-simulator - * - linux - * - macosx - * - unix - * - unknown - * - win32 - * @ignore - * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} - */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; - /** - * Returns the home directory of the current user. - * @return {string} - */ - export function homedir(): string; - export { constants }; - /** - * @type {string} - * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. - */ - export const EOL: string; - export default exports; - import constants from "socket:os/constants"; - import * as exports from "socket:os"; - -} - -declare module "socket:signal" { - /** - * Converts an `signal` code to its corresponding string message. - * @param {import('./os/constants.js').signal} {code} - * @return {string} - */ - export function toString(code: any): string; - /** - * Gets the code for a given 'signal' name. - * @param {string|number} name - * @return {signal} - */ - export function getCode(name: string | number): signal; - /** - * Gets the name for a given 'signal' code - * @return {string} - * @param {string|number} code - */ - export function getName(code: string | number): string; - /** - * Gets the message for a 'signal' code. - * @param {number|string} code - * @return {string} - */ - export function getMessage(code: number | string): string; - /** - * Add a signal event listener. - * @param {string|number} signal - * @param {function(SignalEvent)} callback - * @param {{ once?: boolean }=} [options] - */ - export function addEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { - once?: boolean; - } | undefined): void; - /** - * Remove a signal event listener. - * @param {string|number} signal - * @param {function(SignalEvent)} callback - * @param {{ once?: boolean }=} [options] - */ - export function removeEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { - once?: boolean; - } | undefined): void; - export { constants }; - export const channel: BroadcastChannel; - export const SIGHUP: any; - export const SIGINT: any; - export const SIGQUIT: any; - export const SIGILL: any; - export const SIGTRAP: any; - export const SIGABRT: any; - export const SIGIOT: any; - export const SIGBUS: any; - export const SIGFPE: any; - export const SIGKILL: any; - export const SIGUSR1: any; - export const SIGSEGV: any; - export const SIGUSR2: any; - export const SIGPIPE: any; - export const SIGALRM: any; - export const SIGTERM: any; - export const SIGCHLD: any; - export const SIGCONT: any; - export const SIGSTOP: any; - export const SIGTSTP: any; - export const SIGTTIN: any; - export const SIGTTOU: any; - export const SIGURG: any; - export const SIGXCPU: any; - export const SIGXFSZ: any; - export const SIGVTALRM: any; - export const SIGPROF: any; - export const SIGWINCH: any; - export const SIGIO: any; - export const SIGINFO: any; - export const SIGSYS: any; - export const strings: { - [x: number]: string; - }; - namespace _default { - export { addEventListener }; - export { removeEventListener }; - export { constants }; - export { channel }; - export { strings }; - export { toString }; - export { getName }; - export { getCode }; - export { getMessage }; - export { SIGHUP }; - export { SIGINT }; - export { SIGQUIT }; - export { SIGILL }; - export { SIGTRAP }; - export { SIGABRT }; - export { SIGIOT }; - export { SIGBUS }; - export { SIGFPE }; - export { SIGKILL }; - export { SIGUSR1 }; - export { SIGSEGV }; - export { SIGUSR2 }; - export { SIGPIPE }; - export { SIGALRM }; - export { SIGTERM }; - export { SIGCHLD }; - export { SIGCONT }; - export { SIGSTOP }; - export { SIGTSTP }; - export { SIGTTIN }; - export { SIGTTOU }; - export { SIGURG }; - export { SIGXCPU }; - export { SIGXFSZ }; - export { SIGVTALRM }; - export { SIGPROF }; - export { SIGWINCH }; - export { SIGIO }; - export { SIGINFO }; - export { SIGSYS }; - } - export default _default; - export type signal = import("socket:os/constants").signal; - import { SignalEvent } from "socket:internal/events"; - import { signal as constants } from "socket:os/constants"; -} - declare module "socket:internal/streams/web" { export class ByteLengthQueuingStrategy { constructor(e: any); @@ -3555,722 +2568,1287 @@ declare module "socket:stream" { write(data: any): boolean; end(data: any): this; } - export class Transform extends Duplex { - _transformState: TransformState; - _transform(data: any, cb: any): void; - _flush(cb: any): void; + export class Transform extends Duplex { + _transformState: TransformState; + _transform(data: any, cb: any): void; + _flush(cb: any): void; + } + export class PassThrough extends Transform { + } + const _default: typeof Stream & { + web: typeof web; + Readable: typeof Readable; + Writable: typeof Writable; + Duplex: typeof Duplex; + Transform: typeof Transform; + PassThrough: typeof PassThrough; + pipeline: typeof pipeline & { + [x: symbol]: typeof pipelinePromise; + }; + }; + export default _default; + import web from "socket:stream/web"; + import { EventEmitter } from "socket:events"; +} + +declare module "socket:tty" { + export function WriteStream(fd: any): Writable; + export function ReadStream(fd: any): Readable; + export function isatty(fd: any): boolean; + namespace _default { + export { WriteStream }; + export { ReadStream }; + export { isatty }; + } + export default _default; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; +} + +declare module "socket:path/well-known" { + /** + * Well known path to the user's "Downloads" folder. + * @type {?string} + */ + export const DOWNLOADS: string | null; + /** + * Well known path to the user's "Documents" folder. + * @type {?string} + */ + export const DOCUMENTS: string | null; + /** + * Well known path to the user's "Pictures" folder. + * @type {?string} + */ + export const PICTURES: string | null; + /** + * Well known path to the user's "Desktop" folder. + * @type {?string} + */ + export const DESKTOP: string | null; + /** + * Well known path to the user's "Videos" folder. + * @type {?string} + */ + export const VIDEOS: string | null; + /** + * Well known path to the user's "Music" folder. + * @type {?string} + */ + export const MUSIC: string | null; + /** + * Well known path to the application's "resources" folder. + * @type {?string} + */ + export const RESOURCES: string | null; + /** + * Well known path to the application's "config" folder. + * @type {?string} + */ + export const CONFIG: string | null; + /** + * Well known path to the application's "data" folder. + * @type {?string} + */ + export const DATA: string | null; + /** + * Well known path to the application's "log" folder. + * @type {?string} + */ + export const LOG: string | null; + /** + * Well known path to the application's "tmp" folder. + * @type {?string} + */ + export const TMP: string | null; + /** + * Well known path to the application's "home" folder. + * This may be the user's HOME directory or the application container sandbox. + * @type {?string} + */ + export const HOME: string | null; + namespace _default { + export { DOWNLOADS }; + export { DOCUMENTS }; + export { RESOURCES }; + export { PICTURES }; + export { DESKTOP }; + export { VIDEOS }; + export { CONFIG }; + export { MUSIC }; + export { HOME }; + export { DATA }; + export { LOG }; + export { TMP }; + } + export default _default; +} + +declare module "socket:os" { + /** + * Returns the operating system CPU architecture for which Socket was compiled. + * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' + */ + export function arch(): string; + /** + * Returns an array of objects containing information about each CPU/core. + * @returns {Array<object>} cpus - An array of objects containing information about each CPU/core. + * The properties of the objects are: + * - model `<string>` - CPU model name. + * - speed `<number>` - CPU clock speed (in MHz). + * - times `<object>` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. + * - user `<number>` - Time spent by this CPU or core in user mode. + * - nice `<number>` - Time spent by this CPU or core in user mode with low priority (nice). + * - sys `<number>` - Time spent by this CPU or core in system mode. + * - idle `<number>` - Time spent by this CPU or core in idle mode. + * - irq `<number>` - Time spent by this CPU or core in IRQ mode. + * @see {@link https://nodejs.org/api/os.html#os_os_cpus} + */ + export function cpus(): Array<object>; + /** + * Returns an object containing network interfaces that have been assigned a network address. + * @returns {object} - An object containing network interfaces that have been assigned a network address. + * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. + * The properties available on the assigned network address object include: + * - address `<string>` - The assigned IPv4 or IPv6 address. + * - netmask `<string>` - The IPv4 or IPv6 network mask. + * - family `<string>` - The address family ('IPv4' or 'IPv6'). + * - mac `<string>` - The MAC address of the network interface. + * - internal `<boolean>` - Indicates whether the network interface is a loopback interface. + * - scopeid `<number>` - The numeric scope ID (only specified when family is 'IPv6'). + * - cidr `<string>` - The CIDR notation of the interface. + * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} + */ + export function networkInterfaces(): object; + /** + * Returns the operating system platform. + * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_platform} + * The returned value is equivalent to `process.platform`. + */ + export function platform(): string; + /** + * Returns the operating system name. + * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_type} + */ + export function type(): string; + /** + * @returns {boolean} - `true` if the operating system is Windows. + */ + export function isWindows(): boolean; + /** + * @returns {string} - The operating system's default directory for temporary files. + */ + export function tmpdir(): string; + /** + * Get resource usage. + */ + export function rusage(): any; + /** + * Returns the system uptime in seconds. + * @returns {number} - The system uptime in seconds. + */ + export function uptime(): number; + /** + * Returns the operating system name. + * @returns {string} - The operating system name. + */ + export function uname(): string; + /** + * It's implemented in process.hrtime.bigint() + * @ignore + */ + export function hrtime(): any; + /** + * Node.js doesn't have this method. + * @ignore + */ + export function availableMemory(): any; + /** + * The host operating system. This value can be one of: + * - android + * - android-emulator + * - iphoneos + * - iphone-simulator + * - linux + * - macosx + * - unix + * - unknown + * - win32 + * @ignore + * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} + */ + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + /** + * Returns the home directory of the current user. + * @return {string} + */ + export function homedir(): string; + export { constants }; + /** + * @type {string} + * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. + */ + export const EOL: string; + export default exports; + import constants from "socket:os/constants"; + import * as exports from "socket:os"; + +} + +declare module "socket:process" { + /** + * Adds callback to the 'nextTick' queue. + * @param {Function} callback + */ + export function nextTick(callback: Function, ...args: any[]): void; + /** + * Computed high resolution time as a `BigInt`. + * @param {Array<number>?} [time] + * @return {bigint} + */ + export function hrtime(time?: Array<number> | null): bigint; + export namespace hrtime { + function bigint(): any; + } + /** + * @param {number=} [code=0] - The exit code. Default: 0. + */ + export function exit(code?: number | undefined): Promise<void>; + /** + * Returns an object describing the memory usage of the Node.js process measured in bytes. + * @returns {Object} + */ + export function memoryUsage(): any; + export namespace memoryUsage { + function rss(): any; + } + export class ProcessEnvironmentEvent extends Event { + constructor(type: any, key: any, value: any); + key: any; + value: any; } - export class PassThrough extends Transform { + export class ProcessEnvironment extends EventTarget { + get [Symbol.toStringTag](): string; } - const _default: typeof Stream & { - web: typeof web; - Readable: typeof Readable; - Writable: typeof Writable; - Duplex: typeof Duplex; - Transform: typeof Transform; - PassThrough: typeof PassThrough; - pipeline: typeof pipeline & { - [x: symbol]: typeof pipelinePromise; - }; - }; + export const env: ProcessEnvironment; + export default process; + const process: any; +} + +declare module "socket:location" { + export class Location { + get url(): URL; + get protocol(): string; + get host(): string; + get hostname(): string; + get port(): string; + get pathname(): string; + get search(): string; + get origin(): string; + get href(): string; + get hash(): string; + toString(): string; + } + const _default: Location; export default _default; - import web from "socket:stream/web"; - import { EventEmitter } from "socket:events"; } -declare module "socket:tty" { - export function WriteStream(fd: any): Writable; - export function ReadStream(fd: any): Readable; - export function isatty(fd: any): boolean; - namespace _default { - export { WriteStream }; - export { ReadStream }; - export { isatty }; +declare module "socket:path/path" { + /** + * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. + * @param {strig} ...paths + * @returns {string} + * @see {@link https://nodejs.org/api/path.html#path_path_resolve_paths} + */ + export function resolve(options: any, ...components: any[]): string; + /** + * Computes current working directory for a path + * @param {object=} [opts] + * @param {boolean=} [opts.posix] Set to `true` to force POSIX style path + * @return {string} + */ + export function cwd(opts?: object | undefined): string; + /** + * Computed location origin. Defaults to `socket:///` if not available. + * @return {string} + */ + export function origin(): string; + /** + * Computes the relative path from `from` to `to`. + * @param {object} options + * @param {PathComponent} from + * @param {PathComponent} to + * @return {string} + */ + export function relative(options: object, from: PathComponent, to: PathComponent): string; + /** + * Joins path components. This function may not return an absolute path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} + */ + export function join(options: object, ...components: PathComponent[]): string; + /** + * Computes directory name of path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} + */ + export function dirname(options: object, path: any): string; + /** + * Computes base name of path. + * @param {object} options + * @param {...PathComponent} components + * @return {string} + */ + export function basename(options: object, path: any): string; + /** + * Computes extension name of path. + * @param {object} options + * @param {PathComponent} path + * @return {string} + */ + export function extname(options: object, path: PathComponent): string; + /** + * Computes normalized path + * @param {object} options + * @param {PathComponent} path + * @return {string} + */ + export function normalize(options: object, path: PathComponent): string; + /** + * Formats `Path` object into a string. + * @param {object} options + * @param {object|Path} path + * @return {string} + */ + export function format(options: object, path: object | Path): string; + /** + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {object} + */ + export function parse(path: PathComponent): object; + /** + * @typedef {(string|Path|URL|{ pathname: string }|{ url: string)} PathComponent + */ + /** + * A container for a parsed Path. + */ + export class Path { + /** + * Creates a `Path` instance from `input` and optional `cwd`. + * @param {PathComponent} input + * @param {string} [cwd] + */ + static from(input: PathComponent, cwd?: string): any; + /** + * `Path` class constructor. + * @protected + * @param {string} pathname + * @param {string} [cwd = Path.cwd()] + */ + protected constructor(); + pattern: { + "__#11@#i": any; + "__#11@#n": {}; + "__#11@#t": {}; + "__#11@#e": {}; + "__#11@#s": {}; + "__#11@#l": boolean; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + readonly hasRegExpGroups: boolean; + }; + url: any; + get pathname(): any; + get protocol(): any; + get href(): any; + /** + * `true` if the path is relative, otherwise `false. + * @type {boolean} + */ + get isRelative(): boolean; + /** + * The working value of this path. + */ + get value(): any; + /** + * The original source, unresolved. + * @type {string} + */ + get source(): string; + /** + * Computed parent path. + * @type {string} + */ + get parent(): string; + /** + * Computed root in path. + * @type {string} + */ + get root(): string; + /** + * Computed directory name in path. + * @type {string} + */ + get dir(): string; + /** + * Computed base name in path. + * @type {string} + */ + get base(): string; + /** + * Computed base name in path without path extension. + * @type {string} + */ + get name(): string; + /** + * Computed extension name in path. + * @type {string} + */ + get ext(): string; + /** + * The computed drive, if given in the path. + * @type {string?} + */ + get drive(): string; + /** + * @return {URL} + */ + toURL(): URL; + /** + * Converts this `Path` instance to a string. + * @return {string} + */ + toString(): string; + /** + * @ignore + */ + inspect(): { + root: string; + dir: string; + base: string; + ext: string; + name: string; + }; + /** + * @ignore + */ + [Symbol.toStringTag](): string; + #private; } + export default Path; + export type PathComponent = (string | Path | URL | { + pathname: string; + } | { + url: string; + }); + import { URL } from "socket:url/index"; +} + +declare module "socket:path/mounts" { + const _default: {}; export default _default; - import { Writable } from "socket:stream"; - import { Readable } from "socket:stream"; } -declare module "socket:process" { +declare module "socket:path/win32" { /** - * Adds callback to the 'nextTick' queue. - * @param {Function} callback + * Computes current working directory for a path + * @param {string} */ - export function nextTick(callback: Function, ...args: any[]): void; + export function cwd(): any; /** - * Computed high resolution time as a `BigInt`. - * @param {Array<number>?} [time] - * @return {bigint} + * Resolves path components to an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export function hrtime(time?: Array<number> | null): bigint; - export namespace hrtime { - function bigint(): any; + export function resolve(...components: PathComponent[]): string; + /** + * Joins path components. This function may not return an absolute path. + * @param {...PathComponent} components + * @return {string} + */ + export function join(...components: PathComponent[]): string; + /** + * Computes directory name of path. + * @param {PathComponent} path + * @return {string} + */ + export function dirname(path: PathComponent): string; + /** + * Computes base name of path. + * @param {PathComponent} path + * @param {string=} [suffix] + * @return {string} + */ + export function basename(path: PathComponent, suffix?: string | undefined): string; + /** + * Computes extension name of path. + * @param {PathComponent} path + * @return {string} + */ + export function extname(path: PathComponent): string; + /** + * Predicate helper to determine if path is absolute. + * @param {PathComponent} path + * @return {boolean} + */ + export function isAbsolute(path: PathComponent): boolean; + /** + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} + */ + export function parse(path: PathComponent): Path; + /** + * Formats `Path` object into a string. + * @param {object|Path} path + * @return {string} + */ + export function format(path: object | Path): string; + /** + * Normalizes `path` resolving `..` and `.\` preserving trailing + * slashes. + * @param {string} path + */ + export function normalize(path: string): any; + /** + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} + */ + export function relative(from: string, to: string): string; + export default exports; + export namespace win32 { + let sep: "\\"; + let delimiter: ";"; + } + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; + import * as mounts from "socket:path/mounts"; + import * as posix from "socket:path/posix"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/win32"; + + export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path/posix" { + /** + * Computes current working directory for a path + * @param {string} + * @return {string} + */ + export function cwd(): string; + /** + * Resolves path components to an absolute path. + * @param {...PathComponent} components + * @return {string} + */ + export function resolve(...components: PathComponent[]): string; + /** + * Joins path components. This function may not return an absolute path. + * @param {...PathComponent} components + * @return {string} + */ + export function join(...components: PathComponent[]): string; + /** + * Computes directory name of path. + * @param {PathComponent} path + * @return {string} + */ + export function dirname(path: PathComponent): string; + /** + * Computes base name of path. + * @param {PathComponent} path + * @param {string=} [suffix] + * @return {string} + */ + export function basename(path: PathComponent, suffix?: string | undefined): string; + /** + * Computes extension name of path. + * @param {PathComponent} path + * @return {string} + */ + export function extname(path: PathComponent): string; + /** + * Predicate helper to determine if path is absolute. + * @param {PathComponent} path + * @return {boolean} + */ + export function isAbsolute(path: PathComponent): boolean; + /** + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} + */ + export function parse(path: PathComponent): Path; + /** + * Formats `Path` object into a string. + * @param {object|Path} path + * @return {string} + */ + export function format(path: object | Path): string; + /** + * Normalizes `path` resolving `..` and `./` preserving trailing + * slashes. + * @param {string} path + */ + export function normalize(path: string): any; + /** + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} + */ + export function relative(from: string, to: string): string; + export default exports; + export namespace posix { + let sep: "/"; + let delimiter: ":"; } + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; + import * as mounts from "socket:path/mounts"; + import * as win32 from "socket:path/win32"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/posix"; + + export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path/index" { + export default exports; + import * as mounts from "socket:path/mounts"; + import * as posix from "socket:path/posix"; + import * as win32 from "socket:path/win32"; + import { Path } from "socket:path/path"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/index"; + + export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path" { + export const sep: "\\" | "/"; + export const delimiter: ":" | ";"; + export const resolve: typeof posix.win32.resolve; + export const join: typeof posix.win32.join; + export const dirname: typeof posix.win32.dirname; + export const basename: typeof posix.win32.basename; + export const extname: typeof posix.win32.extname; + export const cwd: typeof posix.win32.cwd; + export const isAbsolute: typeof posix.win32.isAbsolute; + export const parse: typeof posix.win32.parse; + export const format: typeof posix.win32.format; + export const normalize: typeof posix.win32.normalize; + export const relative: typeof posix.win32.relative; + const _default: typeof posix | typeof posix.win32; + export default _default; + import { posix } from "socket:path/index"; + import { Path } from "socket:path/index"; + import { win32 } from "socket:path/index"; + import { mounts } from "socket:path/index"; + import { DOWNLOADS } from "socket:path/index"; + import { DOCUMENTS } from "socket:path/index"; + import { RESOURCES } from "socket:path/index"; + import { PICTURES } from "socket:path/index"; + import { DESKTOP } from "socket:path/index"; + import { VIDEOS } from "socket:path/index"; + import { CONFIG } from "socket:path/index"; + import { MUSIC } from "socket:path/index"; + import { HOME } from "socket:path/index"; + import { DATA } from "socket:path/index"; + import { LOG } from "socket:path/index"; + import { TMP } from "socket:path/index"; + export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:fs/stream" { + export const DEFAULT_STREAM_HIGH_WATER_MARK: number; /** - * @param {number=} [code=0] - The exit code. Default: 0. + * @typedef {import('./handle.js').FileHandle} FileHandle */ - export function exit(code?: number | undefined): Promise<void>; /** - * Returns an object describing the memory usage of the Node.js process measured in bytes. - * @returns {Object} + * A `Readable` stream for a `FileHandle`. */ - export function memoryUsage(): any; - export namespace memoryUsage { - function rss(): any; + export class ReadStream extends Readable { + end: any; + start: any; + handle: any; + buffer: ArrayBuffer; + signal: any; + timeout: any; + bytesRead: number; + shouldEmitClose: boolean; + /** + * Sets file handle for the ReadStream. + * @param {FileHandle} handle + */ + setHandle(handle: FileHandle): void; + /** + * The max buffer size for the ReadStream. + */ + get highWaterMark(): number; + /** + * Relative or absolute path of the underlying `FileHandle`. + */ + get path(): any; + /** + * `true` if the stream is in a pending state. + */ + get pending(): boolean; + _open(callback: any): Promise<any>; + _read(callback: any): Promise<any>; } - export class ProcessEnvironmentEvent extends Event { - constructor(type: any, key: any, value: any); - key: any; - value: any; + export namespace ReadStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; } - export class ProcessEnvironment extends EventTarget { - get [Symbol.toStringTag](): string; + /** + * A `Writable` stream for a `FileHandle`. + */ + export class WriteStream extends Writable { + start: any; + handle: any; + signal: any; + timeout: any; + bytesWritten: number; + shouldEmitClose: boolean; + /** + * Sets file handle for the WriteStream. + * @param {FileHandle} handle + */ + setHandle(handle: FileHandle): void; + /** + * The max buffer size for the Writetream. + */ + get highWaterMark(): number; + /** + * Relative or absolute path of the underlying `FileHandle`. + */ + get path(): any; + /** + * `true` if the stream is in a pending state. + */ + get pending(): boolean; + _open(callback: any): Promise<any>; + _write(buffer: any, callback: any): any; } - export const env: ProcessEnvironment; - export default process; - const process: any; -} - -declare module "socket:location" { - export class Location { - get url(): URL; - get protocol(): string; - get host(): string; - get hostname(): string; - get port(): string; - get pathname(): string; - get search(): string; - get origin(): string; - get href(): string; - get hash(): string; - toString(): string; + export namespace WriteStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; } - const _default: Location; - export default _default; + export const FileReadStream: typeof exports.ReadStream; + export const FileWriteStream: typeof exports.WriteStream; + export default exports; + export type FileHandle = import("socket:fs/handle").FileHandle; + import { Readable } from "socket:stream"; + import { Writable } from "socket:stream"; + import * as exports from "socket:fs/stream"; + } -declare module "socket:path/path" { - /** - * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. - * @param {strig} ...paths - * @returns {string} - * @see {@link https://nodejs.org/api/path.html#path_path_resolve_paths} - */ - export function resolve(options: any, ...components: any[]): string; - /** - * Computes current working directory for a path - * @param {object=} [opts] - * @param {boolean=} [opts.posix] Set to `true` to force POSIX style path - * @return {string} - */ - export function cwd(opts?: object | undefined): string; - /** - * Computed location origin. Defaults to `socket:///` if not available. - * @return {string} - */ - export function origin(): string; - /** - * Computes the relative path from `from` to `to`. - * @param {object} options - * @param {PathComponent} from - * @param {PathComponent} to - * @return {string} - */ - export function relative(options: object, from: PathComponent, to: PathComponent): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {object} options - * @param {...PathComponent} components - * @return {string} - */ - export function join(options: object, ...components: PathComponent[]): string; +declare module "socket:fs/constants" { /** - * Computes directory name of path. - * @param {object} options - * @param {...PathComponent} components - * @return {string} + * This flag can be used with uv_fs_copyfile() to return an error if the + * destination already exists. */ - export function dirname(options: object, path: any): string; + export const COPYFILE_EXCL: 1; /** - * Computes base name of path. - * @param {object} options - * @param {...PathComponent} components - * @return {string} + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, a fallback copy mechanism is used. */ - export function basename(options: object, path: any): string; + export const COPYFILE_FICLONE: 2; /** - * Computes extension name of path. - * @param {object} options - * @param {PathComponent} path - * @return {string} + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, an error is returned. */ - export function extname(options: object, path: PathComponent): string; + export const COPYFILE_FICLONE_FORCE: 4; + export const UV_DIRENT_UNKNOWN: any; + export const UV_DIRENT_FILE: any; + export const UV_DIRENT_DIR: any; + export const UV_DIRENT_LINK: any; + export const UV_DIRENT_FIFO: any; + export const UV_DIRENT_SOCKET: any; + export const UV_DIRENT_CHAR: any; + export const UV_DIRENT_BLOCK: any; + export const UV_FS_SYMLINK_DIR: any; + export const UV_FS_SYMLINK_JUNCTION: any; + export const UV_FS_O_FILEMAP: any; + export const O_RDONLY: any; + export const O_WRONLY: any; + export const O_RDWR: any; + export const O_APPEND: any; + export const O_ASYNC: any; + export const O_CLOEXEC: any; + export const O_CREAT: any; + export const O_DIRECT: any; + export const O_DIRECTORY: any; + export const O_DSYNC: any; + export const O_EXCL: any; + export const O_LARGEFILE: any; + export const O_NOATIME: any; + export const O_NOCTTY: any; + export const O_NOFOLLOW: any; + export const O_NONBLOCK: any; + export const O_NDELAY: any; + export const O_PATH: any; + export const O_SYNC: any; + export const O_TMPFILE: any; + export const O_TRUNC: any; + export const S_IFMT: any; + export const S_IFREG: any; + export const S_IFDIR: any; + export const S_IFCHR: any; + export const S_IFBLK: any; + export const S_IFIFO: any; + export const S_IFLNK: any; + export const S_IFSOCK: any; + export const S_IRWXU: any; + export const S_IRUSR: any; + export const S_IWUSR: any; + export const S_IXUSR: any; + export const S_IRWXG: any; + export const S_IRGRP: any; + export const S_IWGRP: any; + export const S_IXGRP: any; + export const S_IRWXO: any; + export const S_IROTH: any; + export const S_IWOTH: any; + export const S_IXOTH: any; + export const F_OK: any; + export const R_OK: any; + export const W_OK: any; + export const X_OK: any; + export default exports; + import * as exports from "socket:fs/constants"; + +} + +declare module "socket:fs/flags" { + export function normalizeFlags(flags: any): any; + export default exports; + import * as exports from "socket:fs/flags"; + +} + +declare module "socket:async/wrap" { /** - * Computes normalized path - * @param {object} options - * @param {PathComponent} path - * @return {string} + * Returns `true` if a given function `fn` has the "async" wrapped tag, + * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this + * function will return `false`. + * @ignore + * @param {function} fn + * @param {boolean} */ - export function normalize(options: object, path: PathComponent): string; + export function isTagged(fn: Function): boolean; /** - * Formats `Path` object into a string. - * @param {object} options - * @param {object|Path} path - * @return {string} + * Tags a function `fn` as being "async wrapped" so subsequent calls to + * `wrap(fn)` do not wrap an already wrapped function. + * @ignore + * @param {function} fn + * @return {function} */ - export function format(options: object, path: object | Path): string; + export function tag(fn: Function): Function; /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {object} + * Wraps a function `fn` that captures a snapshot of the current async + * context. This function is idempotent and will not wrap a function more + * than once. + * @ignore + * @param {function} fn + * @return {function} */ - export function parse(path: PathComponent): object; + export function wrap(fn: Function): Function; + export const symbol: unique symbol; + export default wrap; +} + +declare module "socket:internal/async/hooks" { + export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + export function getNextAsyncResourceId(): number; + export function executionAsyncResource(): any; + export function executionAsyncId(): any; + export function triggerAsyncId(): any; + export function getDefaultExecutionAsyncId(): any; + export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; + export function getTopLevelAsyncResourceName(): any; /** - * @typedef {(string|Path|URL|{ pathname: string }|{ url: string)} PathComponent + * The default top level async resource ID + * @type {number} */ + export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; + export namespace state { + let defaultExecutionAsyncId: number; + } + export namespace hooks { + let init: any[]; + let before: any[]; + let after: any[]; + let destroy: any[]; + let promiseResolve: any[]; + } /** - * A container for a parsed Path. + * A base class for the `AsyncResource` class or other higher level async + * resource classes. */ - export class Path { - /** - * Creates a `Path` instance from `input` and optional `cwd`. - * @param {PathComponent} input - * @param {string} [cwd] - */ - static from(input: PathComponent, cwd?: string): any; - /** - * `Path` class constructor. - * @protected - * @param {string} pathname - * @param {string} [cwd = Path.cwd()] - */ - protected constructor(); - pattern: { - "__#11@#i": any; - "__#11@#n": {}; - "__#11@#t": {}; - "__#11@#e": {}; - "__#11@#s": {}; - "__#11@#l": boolean; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - readonly hasRegExpGroups: boolean; - }; - url: any; - get pathname(): any; - get protocol(): any; - get href(): any; - /** - * `true` if the path is relative, otherwise `false. - * @type {boolean} - */ - get isRelative(): boolean; - /** - * The working value of this path. - */ - get value(): any; - /** - * The original source, unresolved. - * @type {string} - */ - get source(): string; - /** - * Computed parent path. - * @type {string} - */ - get parent(): string; - /** - * Computed root in path. - * @type {string} - */ - get root(): string; - /** - * Computed directory name in path. - * @type {string} - */ - get dir(): string; - /** - * Computed base name in path. - * @type {string} - */ - get base(): string; + export class CoreAsyncResource { /** - * Computed base name in path without path extension. - * @type {string} + * `CoreAsyncResource` class constructor. + * @param {string} type + * @param {object|number=} [options] */ - get name(): string; + constructor(type: string, options?: (object | number) | undefined); /** - * Computed extension name in path. + * The `CoreAsyncResource` type. * @type {string} */ - get ext(): string; + get type(): string; /** - * The computed drive, if given in the path. - * @type {string?} + * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This + * value is only set to `true` if `emitDestroy()` was called, likely from + * destroying the resource manually. + * @type {boolean} */ - get drive(): string; + get destroyed(): boolean; /** - * @return {URL} + * The unique async resource ID. + * @return {number} */ - toURL(): URL; + asyncId(): number; /** - * Converts this `Path` instance to a string. - * @return {string} + * The trigger async resource ID. + * @return {number} */ - toString(): string; + triggerAsyncId(): number; /** - * @ignore + * Manually emits destroy hook for the resource. + * @return {CoreAsyncResource} */ - inspect(): { - root: string; - dir: string; - base: string; - ext: string; - name: string; - }; + emitDestroy(): CoreAsyncResource; /** - * @ignore + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @return {function} */ - [Symbol.toStringTag](): string; - #private; - } - export default Path; - export type PathComponent = (string | Path | URL | { - pathname: string; - } | { - url: string; - }); - import { URL } from "socket:url/index"; -} - -declare module "socket:path/mounts" { - const _default: {}; - export default _default; -} - -declare module "socket:path/win32" { - /** - * Computes current working directory for a path - * @param {string} - */ - export function cwd(): any; - /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function resolve(...components: PathComponent[]): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function join(...components: PathComponent[]): string; - /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} - */ - export function dirname(path: PathComponent): string; - /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string=} [suffix] - * @return {string} - */ - export function basename(path: PathComponent, suffix?: string | undefined): string; - /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} - */ - export function extname(path: PathComponent): string; - /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} - */ - export function isAbsolute(path: PathComponent): boolean; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} - */ - export function parse(path: PathComponent): Path; - /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} - */ - export function format(path: object | Path): string; - /** - * Normalizes `path` resolving `..` and `.\` preserving trailing - * slashes. - * @param {string} path - */ - export function normalize(path: string): any; - /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} - */ - export function relative(from: string, to: string): string; - export default exports; - export namespace win32 { - let sep: "\\"; - let delimiter: ";"; + bind(fn: Function, thisArg?: object | undefined): Function; + /** + * Runs function `fn` in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @param {...any} [args] + * @return {any} + */ + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + #private; } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as mounts from "socket:path/mounts"; - import * as posix from "socket:path/posix"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/win32"; - - export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export class TopLevelAsyncResource extends CoreAsyncResource { + } + export const asyncContextVariable: Variable<any>; + export const topLevelAsyncResource: TopLevelAsyncResource; + export default hooks; + import { Variable } from "socket:async/context"; } -declare module "socket:path/posix" { - /** - * Computes current working directory for a path - * @param {string} - * @return {string} - */ - export function cwd(): string; - /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function resolve(...components: PathComponent[]): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function join(...components: PathComponent[]): string; - /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} - */ - export function dirname(path: PathComponent): string; - /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string=} [suffix] - * @return {string} - */ - export function basename(path: PathComponent, suffix?: string | undefined): string; - /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} - */ - export function extname(path: PathComponent): string; +declare module "socket:async/resource" { /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} + * @typedef {{ + * triggerAsyncId?: number, + * requireManualDestroy?: boolean + * }} AsyncResourceOptions */ - export function isAbsolute(path: PathComponent): boolean; /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} + * A container that should be extended that represents a resource with + * an asynchronous execution context. */ - export function parse(path: PathComponent): Path; + export class AsyncResource extends CoreAsyncResource { + /** + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of an anonymous `AsyncResource`. + * @param {function} fn + * @param {object|string=} [type] + * @param {object=} [thisArg] + * @return {function} + */ + static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; + /** + * `AsyncResource` class constructor. + * @param {string} type + * @param {AsyncResourceOptions|number=} [options] + */ + constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); + } + export default AsyncResource; + export type AsyncResourceOptions = { + triggerAsyncId?: number; + requireManualDestroy?: boolean; + }; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + import { CoreAsyncResource } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; +} + +declare module "socket:async/hooks" { /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} + * Factory for creating a `AsyncHook` instance. + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] + * @return {AsyncHook} */ - export function format(path: object | Path): string; + export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; /** - * Normalizes `path` resolving `..` and `./` preserving trailing - * slashes. - * @param {string} path + * A container for `AsyncHooks` callbacks. + * @ignore */ - export function normalize(path: string): any; + export class AsyncHookCallbacks { + /** + * `AsyncHookCallbacks` class constructor. + * @ignore + * @param {AsyncHookCallbackOptions} [options] + */ + constructor(options?: AsyncHookCallbackOptions); + init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + before(asyncId: any): void; + after(asyncId: any): void; + destroy(asyncId: any): void; + promiseResolve(asyncId: any): void; + } /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} + * A container for registering various callbacks for async resource hooks. */ - export function relative(from: string, to: string): string; - export default exports; - export namespace posix { - let sep: "/"; - let delimiter: ":"; + export class AsyncHook { + /** + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] + */ + constructor(callbacks?: any); + /** + * @type {boolean} + */ + get enabled(): boolean; + /** + * Enable the async hook. + * @return {AsyncHook} + */ + enable(): AsyncHook; + /** + * Disables the async hook + * @return {AsyncHook} + */ + disable(): AsyncHook; + #private; } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as mounts from "socket:path/mounts"; - import * as win32 from "socket:path/win32"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/posix"; - - export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path/index" { - export default exports; - import * as mounts from "socket:path/mounts"; - import * as posix from "socket:path/posix"; - import * as win32 from "socket:path/win32"; - import { Path } from "socket:path/path"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/index"; - - export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path" { - export const sep: "\\" | "/"; - export const delimiter: ":" | ";"; - export const resolve: typeof posix.win32.resolve; - export const join: typeof posix.win32.join; - export const dirname: typeof posix.win32.dirname; - export const basename: typeof posix.win32.basename; - export const extname: typeof posix.win32.extname; - export const cwd: typeof posix.win32.cwd; - export const isAbsolute: typeof posix.win32.isAbsolute; - export const parse: typeof posix.win32.parse; - export const format: typeof posix.win32.format; - export const normalize: typeof posix.win32.normalize; - export const relative: typeof posix.win32.relative; - const _default: typeof posix | typeof posix.win32; - export default _default; - import { posix } from "socket:path/index"; - import { Path } from "socket:path/index"; - import { win32 } from "socket:path/index"; - import { mounts } from "socket:path/index"; - import { DOWNLOADS } from "socket:path/index"; - import { DOCUMENTS } from "socket:path/index"; - import { RESOURCES } from "socket:path/index"; - import { PICTURES } from "socket:path/index"; - import { DESKTOP } from "socket:path/index"; - import { VIDEOS } from "socket:path/index"; - import { CONFIG } from "socket:path/index"; - import { MUSIC } from "socket:path/index"; - import { HOME } from "socket:path/index"; - import { DATA } from "socket:path/index"; - import { LOG } from "socket:path/index"; - import { TMP } from "socket:path/index"; - export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export default createHook; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; } -declare module "socket:fs/stream" { - export const DEFAULT_STREAM_HIGH_WATER_MARK: number; - /** - * @typedef {import('./handle.js').FileHandle} FileHandle - */ +declare module "socket:async/storage" { /** - * A `Readable` stream for a `FileHandle`. + * A container for storing values that remain present during + * asynchronous operations. */ - export class ReadStream extends Readable { - end: any; - start: any; - handle: any; - buffer: ArrayBuffer; - signal: any; - timeout: any; - bytesRead: number; - shouldEmitClose: boolean; + export class AsyncLocalStorage { /** - * Sets file handle for the ReadStream. - * @param {FileHandle} handle + * Binds function `fn` to run in the execution context of an + * anonymous `AsyncResource`. + * @param {function} fn + * @return {function} */ - setHandle(handle: FileHandle): void; + static bind(fn: Function): Function; /** - * The max buffer size for the ReadStream. + * Captures the current async context and returns a function that runs + * a function in that execution context. + * @return {function} */ - get highWaterMark(): number; + static snapshot(): Function; /** - * Relative or absolute path of the underlying `FileHandle`. + * @type {boolean} */ - get path(): any; + get enabled(): boolean; /** - * `true` if the stream is in a pending state. + * Disables the `AsyncLocalStorage` instance. When disabled, + * `getStore()` will always return `undefined`. */ - get pending(): boolean; - _open(callback: any): Promise<any>; - _read(callback: any): Promise<any>; - } - export namespace ReadStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; - } - /** - * A `Writable` stream for a `FileHandle`. - */ - export class WriteStream extends Writable { - start: any; - handle: any; - signal: any; - timeout: any; - bytesWritten: number; - shouldEmitClose: boolean; + disable(): void; /** - * Sets file handle for the WriteStream. - * @param {FileHandle} handle + * Enables the `AsyncLocalStorage` instance. */ - setHandle(handle: FileHandle): void; + enable(): void; /** - * The max buffer size for the Writetream. + * Enables and sets the `AsyncLocalStorage` instance default store value. + * @param {any} store */ - get highWaterMark(): number; + enterWith(store: any): void; /** - * Relative or absolute path of the underlying `FileHandle`. + * Runs function `fn` in the current asynchronous execution context with + * a given `store` value and arguments given to `fn`. + * @param {any} store + * @param {function} fn + * @param {...any} args + * @return {any} */ - get path(): any; + run(store: any, fn: Function, ...args: any[]): any; + exit(fn: any, ...args: any[]): any; /** - * `true` if the stream is in a pending state. + * If the `AsyncLocalStorage` instance is enabled, it returns the current + * store value for this asynchronous execution context. + * @return {any|undefined} */ - get pending(): boolean; - _open(callback: any): Promise<any>; - _write(buffer: any, callback: any): any; - } - export namespace WriteStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + getStore(): any | undefined; + #private; } - export const FileReadStream: typeof exports.ReadStream; - export const FileWriteStream: typeof exports.WriteStream; - export default exports; - export type FileHandle = import("socket:fs/handle").FileHandle; - import { Readable } from "socket:stream"; - import { Writable } from "socket:stream"; - import * as exports from "socket:fs/stream"; - + export default AsyncLocalStorage; } -declare module "socket:fs/constants" { +declare module "socket:async/deferred" { /** - * This flag can be used with uv_fs_copyfile() to return an error if the - * destination already exists. + * Dispatched when a `Deferred` internal promise is resolved. */ - export const COPYFILE_EXCL: 1; + export class DeferredResolveEvent extends Event { + /** + * `DeferredResolveEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {any=} [result] + */ + constructor(type?: string | undefined, result?: any | undefined); + /** + * The `Deferred` promise result value. + * @type {any?} + */ + result: any | null; + } /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, a fallback copy mechanism is used. + * Dispatched when a `Deferred` internal promise is rejected. */ - export const COPYFILE_FICLONE: 2; + export class DeferredRejectEvent { + /** + * `DeferredRejectEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {Error=} [error] + */ + constructor(type?: string | undefined, error?: Error | undefined); + } /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, an error is returned. + * A utility class for creating deferred promises. */ - export const COPYFILE_FICLONE_FORCE: 4; - export const UV_DIRENT_UNKNOWN: any; - export const UV_DIRENT_FILE: any; - export const UV_DIRENT_DIR: any; - export const UV_DIRENT_LINK: any; - export const UV_DIRENT_FIFO: any; - export const UV_DIRENT_SOCKET: any; - export const UV_DIRENT_CHAR: any; - export const UV_DIRENT_BLOCK: any; - export const UV_FS_SYMLINK_DIR: any; - export const UV_FS_SYMLINK_JUNCTION: any; - export const UV_FS_O_FILEMAP: any; - export const O_RDONLY: any; - export const O_WRONLY: any; - export const O_RDWR: any; - export const O_APPEND: any; - export const O_ASYNC: any; - export const O_CLOEXEC: any; - export const O_CREAT: any; - export const O_DIRECT: any; - export const O_DIRECTORY: any; - export const O_DSYNC: any; - export const O_EXCL: any; - export const O_LARGEFILE: any; - export const O_NOATIME: any; - export const O_NOCTTY: any; - export const O_NOFOLLOW: any; - export const O_NONBLOCK: any; - export const O_NDELAY: any; - export const O_PATH: any; - export const O_SYNC: any; - export const O_TMPFILE: any; - export const O_TRUNC: any; - export const S_IFMT: any; - export const S_IFREG: any; - export const S_IFDIR: any; - export const S_IFCHR: any; - export const S_IFBLK: any; - export const S_IFIFO: any; - export const S_IFLNK: any; - export const S_IFSOCK: any; - export const S_IRWXU: any; - export const S_IRUSR: any; - export const S_IWUSR: any; - export const S_IXUSR: any; - export const S_IRWXG: any; - export const S_IRGRP: any; - export const S_IWGRP: any; - export const S_IXGRP: any; - export const S_IRWXO: any; - export const S_IROTH: any; - export const S_IWOTH: any; - export const S_IXOTH: any; - export const F_OK: any; - export const R_OK: any; - export const W_OK: any; - export const X_OK: any; - export default exports; - import * as exports from "socket:fs/constants"; - + export class Deferred extends EventTarget { + /** + * `Deferred` class constructor. + * @param {Deferred|Promise?} [promise] + */ + constructor(promise?: Deferred | (Promise<any> | null)); + /** + * Function to resolve the associated promise. + * @type {function} + */ + resolve: Function; + /** + * Function to reject the associated promise. + * @type {function} + */ + reject: Function; + /** + * Attaches a fulfillment callback and a rejection callback to the promise, + * and returns a new promise resolving to the return value of the called + * callback. + * @param {function(any)=} [resolve] + * @param {function(Error)=} [reject] + */ + then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise<any>; + /** + * Attaches a rejection callback to the promise, and returns a new promise + * resolving to the return value of the callback if it is called, or to its + * original fulfillment value if the promise is instead fulfilled. + * @param {function(Error)=} [callback] + */ + catch(callback?: ((arg0: Error) => any) | undefined): Promise<any>; + /** + * Attaches a callback for when the promise is settled (fulfilled or rejected). + * @param {function(any?)} [callback] + */ + finally(callback?: (arg0: any | null) => any): Promise<any>; + /** + * The promise associated with this Deferred instance. + * @type {Promise<any>} + */ + get promise(): Promise<any>; + /** + * A string representation of this Deferred instance. + * @type {string} + * @ignore + */ + get [Symbol.toStringTag](): string; + #private; + } + export default Deferred; } -declare module "socket:fs/flags" { - export function normalizeFlags(flags: any): any; +declare module "socket:async" { export default exports; - import * as exports from "socket:fs/flags"; + import AsyncLocalStorage from "socket:async/storage"; + import AsyncResource from "socket:async/resource"; + import AsyncContext from "socket:async/context"; + import Deferred from "socket:async/deferred"; + import { executionAsyncResource } from "socket:async/hooks"; + import { executionAsyncId } from "socket:async/hooks"; + import { triggerAsyncId } from "socket:async/hooks"; + import { createHook } from "socket:async/hooks"; + import { AsyncHook } from "socket:async/hooks"; + import * as exports from "socket:async"; + export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; } declare module "socket:diagnostics/channels" { @@ -6623,376 +6201,796 @@ declare module "socket:fs/index" { */ export function rename(src: string, dest: string, callback: Function): void; /** - * Renames file or directory at `src` to `dest`, synchronously. - * @param {string} src - * @param {string} dest + * Renames file or directory at `src` to `dest`, synchronously. + * @param {string} src + * @param {string} dest + */ + export function renameSync(src: string, dest: string): void; + /** + * Removes directory at `path`. + * @param {string} path + * @param {function} callback + */ + export function rmdir(path: string, callback: Function): void; + /** + * Removes directory at `path`, synchronously. + * @param {string} path + */ + export function rmdirSync(path: string): void; + /** + * Synchronously get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + */ + export function statSync(path: string | Buffer | URL | number, options?: object | null): promises.Stats; + /** + * Get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback + */ + export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + /** + * Get the stats of a symbolic link + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback + */ + export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + /** + * Creates a symlink of `src` at `dest`. + * @param {string} src + * @param {string} dest + */ + export function symlink(src: string, dest: string, type: any, callback: any): void; + /** + * Unlinks (removes) file at `path`. + * @param {string} path + * @param {function} callback + */ + export function unlink(path: string, callback: Function): void; + /** + * Unlinks (removes) file at `path`, synchronously. + * @param {string} path + */ + export function unlinkSync(path: string): void; + /** + * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?)} callback + */ + export function writeFile(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null, callback: (arg0: Error | null) => any): void; + /** + * Writes data to a file synchronously. + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} + */ + export function writeFileSync(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null): void; + /** + * Watch for changes at `path` calling `callback` + * @param {string} + * @param {function|object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {?function} [callback] + * @return {Watcher} + */ + export function watch(path: any, options?: (Function | object) | undefined, callback?: Function | null): Watcher; + export default exports; + export type Buffer = import("socket:buffer").Buffer; + export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; + import { ReadStream } from "socket:fs/stream"; + import { WriteStream } from "socket:fs/stream"; + import { Dir } from "socket:fs/dir"; + import * as promises from "socket:fs/promises"; + import { Stats } from "socket:fs/stats"; + import { Watcher } from "socket:fs/watcher"; + import bookmarks from "socket:fs/bookmarks"; + import * as constants from "socket:fs/constants"; + import { DirectoryHandle } from "socket:fs/handle"; + import { Dirent } from "socket:fs/dir"; + import fds from "socket:fs/fds"; + import { FileHandle } from "socket:fs/handle"; + import * as exports from "socket:fs/index"; + + export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; +} + +declare module "socket:fs" { + export * from "socket:fs/index"; + export default exports; + import * as exports from "socket:fs/index"; +} + +declare module "socket:external/libsodium/index" { + const _default: any; + export default _default; +} + +declare module "socket:crypto/sodium" { + export {}; +} + +declare module "socket:crypto" { + /** + * Generate cryptographically strong random values into the `buffer` + * @param {TypedArray} buffer + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues} + * @return {TypedArray} + */ + export function getRandomValues(buffer: TypedArray, ...args: any[]): TypedArray; + /** + * Generate a random 64-bit number. + * @returns {BigInt} - A random 64-bit number. + */ + export function rand64(): BigInt; + /** + * Generate `size` random bytes. + * @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1. + * @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes. + */ + export function randomBytes(size: number): Buffer; + /** + * @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512` + * @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview. + * @returns {Promise<Buffer>} - A promise that resolves with an instance of socket.Buffer with the hash. + */ + export function createDigest(algorithm: string, buf: any): Promise<Buffer>; + /** + * A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c + * that works on strings and `ArrayBuffer` views (typed arrays) + * @param {string|Uint8Array|ArrayBuffer} value + * @param {number=} [seed = 0] + * @return {number} + */ + export function murmur3(value: string | Uint8Array | ArrayBuffer, seed?: number | undefined): number; + /** + * @typedef {Uint8Array|Int8Array} TypedArray + */ + /** + * WebCrypto API + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto} + */ + export let webcrypto: any; + /** + * A promise that resolves when all internals to be loaded/ready. + * @type {Promise} + */ + export const ready: Promise<any>; + /** + * Maximum total size of random bytes per page + */ + export const RANDOM_BYTES_QUOTA: number; + /** + * Maximum total size for random bytes. + */ + export const MAX_RANDOM_BYTES: 281474976710655; + /** + * Maximum total amount of allocated per page of bytes (max/quota) + */ + export const MAX_RANDOM_BYTES_PAGES: number; + export default exports; + export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; + export namespace sodium { + let ready: Promise<any>; + } + import * as exports from "socket:crypto"; + +} + +declare module "socket:ai" { + /** + * A class to interact with large language models (using llama.cpp) + */ + export class LLM extends EventEmitter { + /** + * Constructs an LLM instance. Each parameter is designed to configure and control + * the behavior of the underlying large language model provided by llama.cpp. + * @param {Object} options - Configuration options for the LLM instance. + * @param {string} options.path - The file path to the model in .gguf format. This model file contains + * the weights and configuration necessary for initializing the language model. + * @param {string} options.prompt - The initial input text to the model, setting the context or query + * for generating responses. The model uses this as a starting point for text generation. + * @param {string} [options.id] - An optional unique identifier for this specific instance of the model, + * useful for tracking or referencing the model in multi-model setups. + * @param {number} [options.n_ctx=1024] - Specifies the maximum number of tokens that the model can consider + * for a single query. This is crucial for managing memory and computational + * efficiency. Exceeding the model's configuration may lead to errors or truncated outputs. + * @param {number} [options.n_threads=8] - The number of threads allocated for the model's computation, + * affecting performance and speed of response generation. + * @param {number} [options.temp=1.1] - Sampling temperature controls the randomness of predictions. + * Higher values increase diversity, potentially at the cost of coherence. + * @param {number} [options.max_tokens=512] - The upper limit on the number of tokens that the model can generate + * in response to a single prompt. This prevents runaway generations. + * @param {number} [options.n_gpu_layers=32] - The number of GPU layers dedicated to the model processing. + * More layers can increase accuracy and complexity of the outputs. + * @param {number} [options.n_keep=0] - Determines how many of the top generated responses are retained after + * the initial generation phase. Useful for models that generate multiple outputs. + * @param {number} [options.n_batch=0] - The size of processing batches. Larger batch sizes can reduce + * the time per token generation by parallelizing computations. + * @param {number} [options.n_predict=0] - Specifies how many forward predictions the model should make + * from the current state. This can pre-generate responses or calculate probabilities. + * @param {number} [options.grp_attn_n=0] - Group attention parameter 'N' modifies how attention mechanisms + * within the model are grouped and interact, affecting the model’s focus and accuracy. + * @param {number} [options.grp_attn_w=0] - Group attention parameter 'W' adjusts the width of each attention group, + * influencing the breadth of context considered by each attention group. + * @param {number} [options.seed=0] - A seed for the random number generator used in the model. Setting this ensures + * consistent results in model outputs, important for reproducibility in experiments. + * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, + * reducing the risk of less likely, potentially nonsensical outputs. + * @param {number} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool + * to only those whose cumulative probability exceeds this value, enhancing output relevance. + * @param {number} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring + * that generated tokens have at least this likelihood of being relevant or coherent. + * @param {number} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how + * the model weights novel or unseen prompts during generation. + * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. + */ + constructor(options?: { + path: string; + prompt: string; + id?: string; + n_ctx?: number; + n_threads?: number; + temp?: number; + max_tokens?: number; + n_gpu_layers?: number; + n_keep?: number; + n_batch?: number; + n_predict?: number; + grp_attn_n?: number; + grp_attn_w?: number; + seed?: number; + top_k?: number; + tok_p?: number; + min_p?: number; + tfs_z?: number; + }); + path: string; + prompt: string; + id: string | BigInt; + /** + * Tell the LLM to stop after the next token. + * @returns {Promise<void>} A promise that resolves when the LLM stops. + */ + stop(): Promise<void>; + /** + * Send a message to the chat. + * @param {string} message - The message to send to the chat. + * @returns {Promise<any>} A promise that resolves with the response from the chat. + */ + chat(message: string): Promise<any>; + } + export default exports; + import { EventEmitter } from "socket:events"; + import * as exports from "socket:ai"; + +} + +declare module "socket:window/constants" { + export const WINDOW_ERROR: -1; + export const WINDOW_NONE: 0; + export const WINDOW_CREATING: 10; + export const WINDOW_CREATED: 11; + export const WINDOW_HIDING: 20; + export const WINDOW_HIDDEN: 21; + export const WINDOW_SHOWING: 30; + export const WINDOW_SHOWN: 31; + export const WINDOW_CLOSING: 40; + export const WINDOW_CLOSED: 41; + export const WINDOW_EXITING: 50; + export const WINDOW_EXITED: 51; + export const WINDOW_KILLING: 60; + export const WINDOW_KILLED: 61; + export default exports; + import * as exports from "socket:window/constants"; + +} + +declare module "socket:application/client" { + /** + * @typedef {{ + * id?: string | null, + * type?: 'window' | 'worker', + * parent?: object | null, + * top?: object | null, + * frameType?: 'top-level' | 'nested' | 'none' + * }} ClientState */ - export function renameSync(src: string, dest: string): void; + export class Client { + /** + * `Client` class constructor + * @private + * @param {ClientState} state + */ + private constructor(); + /** + * The unique ID of the client. + * @type {string|null} + */ + get id(): string; + /** + * The frame type of the client. + * @type {'top-level'|'nested'|'none'} + */ + get frameType(): "none" | "top-level" | "nested"; + /** + * The type of the client. + * @type {'window'|'worker'} + */ + get type(): "window" | "worker"; + /** + * The parent client of the client. + * @type {Client|null} + */ + get parent(): Client; + /** + * The top client of the client. + * @type {Client|null} + */ + get top(): Client; + /** + * A readonly `URL` of the current location of this client. + * @type {URL} + */ + get location(): URL; + /** + * Converts this `Client` instance to JSON. + * @return {object} + */ + toJSON(): object; + #private; + } + const _default: any; + export default _default; + export type ClientState = { + id?: string | null; + type?: "window" | "worker"; + parent?: object | null; + top?: object | null; + frameType?: "top-level" | "nested" | "none"; + }; +} + +declare module "socket:application/menu" { /** - * Removes directory at `path`. - * @param {string} path - * @param {function} callback + * Internal IPC for setting an application menu + * @ignore */ - export function rmdir(path: string, callback: Function): void; + export function setMenu(options: any, type: any): Promise<ipc.Result>; /** - * Removes directory at `path`, synchronously. - * @param {string} path + * Internal IPC for setting an application context menu + * @ignore */ - export function rmdirSync(path: string): void; + export function setContextMenu(options: any): Promise<any>; /** - * Synchronously get the stats of a file - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] + * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. */ - export function statSync(path: string | Buffer | URL | number, options?: object | null): promises.Stats; + export class Menu extends EventTarget { + /** + * `Menu` class constructor. + * @ignore + * @param {string} type + */ + constructor(type: string); + /** + * The broadcast channel for this menu. + * @ignore + * @type {BroadcastChannel} + */ + get channel(): BroadcastChannel; + /** + * The `Menu` instance type. + * @type {('context'|'system'|'tray')?} + */ + get type(): "tray" | "system" | "context"; + /** + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} + */ + set onerror(onerror: (arg0: ErrorEvent) => any); + /** + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} + */ + get onerror(): (arg0: ErrorEvent) => any; + /** + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} + */ + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + /** + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} + */ + get onmenuitem(): (arg0: menuitemEvent) => any; + /** + * Set the menu layout for this `Menu` instance. + * @param {string|object} layoutOrOptions + * @param {object=} [options] + */ + set(layoutOrOptions: string | object, options?: object | undefined): Promise<any>; + #private; + } /** - * Get the stats of a file - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Stats?)} callback + * A container for various `Menu` instances. */ - export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + export class MenuContainer extends EventTarget { + /** + * `MenuContainer` class constructor. + * @param {EventTarget} [sourceEventTarget] + * @param {object=} [options] + */ + constructor(sourceEventTarget?: EventTarget, options?: object | undefined); + /** + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} + */ + set onerror(onerror: (arg0: ErrorEvent) => any); + /** + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} + */ + get onerror(): (arg0: ErrorEvent) => any; + /** + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} + */ + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + /** + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} + */ + get onmenuitem(): (arg0: menuitemEvent) => any; + /** + * The `TrayMenu` instance for the application. + * @type {TrayMenu} + */ + get tray(): TrayMenu; + /** + * The `SystemMenu` instance for the application. + * @type {SystemMenu} + */ + get system(): SystemMenu; + /** + * The `ContextMenu` instance for the application. + * @type {ContextMenu} + */ + get context(): ContextMenu; + #private; + } /** - * Get the stats of a symbolic link - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Stats?)} callback + * A `Menu` instance that represents a context menu. */ - export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + export class ContextMenu extends Menu { + constructor(); + } /** - * Creates a symlink of `src` at `dest`. - * @param {string} src - * @param {string} dest + * A `Menu` instance that represents the system menu. */ - export function symlink(src: string, dest: string, type: any, callback: any): void; + export class SystemMenu extends Menu { + constructor(); + } /** - * Unlinks (removes) file at `path`. - * @param {string} path - * @param {function} callback + * A `Menu` instance that represents the tray menu. */ - export function unlink(path: string, callback: Function): void; + export class TrayMenu extends Menu { + constructor(); + } /** - * Unlinks (removes) file at `path`, synchronously. - * @param {string} path + * The application tray menu. + * @type {TrayMenu} */ - export function unlinkSync(path: string): void; + export const tray: TrayMenu; /** - * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {string | Buffer | TypedArray | DataView | object } data - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.mode ? 0o666] - * @param {string?} [options.flag ? 'w'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?)} callback + * The application system menu. + * @type {SystemMenu} */ - export function writeFile(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null, callback: (arg0: Error | null) => any): void; + export const system: SystemMenu; /** - * Writes data to a file synchronously. - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {string | Buffer | TypedArray | DataView | object } data - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.mode ? 0o666] - * @param {string?} [options.flag ? 'w'] - * @param {AbortSignal?} [options.signal] - * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} + * The application context menu. + * @type {ContextMenu} */ - export function writeFileSync(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null): void; + export const context: ContextMenu; /** - * Watch for changes at `path` calling `callback` - * @param {string} - * @param {function|object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {?function} [callback] - * @return {Watcher} + * The application menus container. + * @type {MenuContainer} */ - export function watch(path: any, options?: (Function | object) | undefined, callback?: Function | null): Watcher; - export default exports; - export type Buffer = import("socket:buffer").Buffer; - export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - import { ReadStream } from "socket:fs/stream"; - import { WriteStream } from "socket:fs/stream"; - import { Dir } from "socket:fs/dir"; - import * as promises from "socket:fs/promises"; - import { Stats } from "socket:fs/stats"; - import { Watcher } from "socket:fs/watcher"; - import bookmarks from "socket:fs/bookmarks"; - import * as constants from "socket:fs/constants"; - import { DirectoryHandle } from "socket:fs/handle"; - import { Dirent } from "socket:fs/dir"; - import fds from "socket:fs/fds"; - import { FileHandle } from "socket:fs/handle"; - import * as exports from "socket:fs/index"; - - export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; -} - -declare module "socket:fs" { - export * from "socket:fs/index"; - export default exports; - import * as exports from "socket:fs/index"; -} - -declare module "socket:external/libsodium/index" { - const _default: any; - export default _default; -} - -declare module "socket:crypto/sodium" { - export {}; + export const container: MenuContainer; + export default container; + import ipc from "socket:ipc"; } -declare module "socket:crypto" { - /** - * Generate cryptographically strong random values into the `buffer` - * @param {TypedArray} buffer - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues} - * @return {TypedArray} - */ - export function getRandomValues(buffer: TypedArray, ...args: any[]): TypedArray; - /** - * Generate a random 64-bit number. - * @returns {BigInt} - A random 64-bit number. - */ - export function rand64(): BigInt; - /** - * Generate `size` random bytes. - * @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1. - * @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes. - */ - export function randomBytes(size: number): Buffer; - /** - * @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512` - * @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview. - * @returns {Promise<Buffer>} - A promise that resolves with an instance of socket.Buffer with the hash. - */ - export function createDigest(algorithm: string, buf: any): Promise<Buffer>; - /** - * A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c - * that works on strings and `ArrayBuffer` views (typed arrays) - * @param {string|Uint8Array|ArrayBuffer} value - * @param {number=} [seed = 0] - * @return {number} - */ - export function murmur3(value: string | Uint8Array | ArrayBuffer, seed?: number | undefined): number; +declare module "socket:process/signal" { /** - * @typedef {Uint8Array|Int8Array} TypedArray + * Converts an `signal` code to its corresponding string message. + * @param {import('./os/constants.js').signal} {code} + * @return {string} */ + export function toString(code: any): string; /** - * WebCrypto API - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto} + * Gets the code for a given 'signal' name. + * @param {string|number} name + * @return {signal} */ - export let webcrypto: any; + export function getCode(name: string | number): signal; /** - * A promise that resolves when all internals to be loaded/ready. - * @type {Promise} + * Gets the name for a given 'signal' code + * @return {string} + * @param {string|number} code */ - export const ready: Promise<any>; + export function getName(code: string | number): string; /** - * Maximum total size of random bytes per page + * Gets the message for a 'signal' code. + * @param {number|string} code + * @return {string} */ - export const RANDOM_BYTES_QUOTA: number; + export function getMessage(code: number | string): string; /** - * Maximum total size for random bytes. + * Add a signal event listener. + * @param {string|number} signal + * @param {function(SignalEvent)} callback + * @param {{ once?: boolean }=} [options] */ - export const MAX_RANDOM_BYTES: 281474976710655; + export function addEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { + once?: boolean; + } | undefined): void; /** - * Maximum total amount of allocated per page of bytes (max/quota) + * Remove a signal event listener. + * @param {string|number} signal + * @param {function(SignalEvent)} callback + * @param {{ once?: boolean }=} [options] */ - export const MAX_RANDOM_BYTES_PAGES: number; - export default exports; - export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - export namespace sodium { - let ready: Promise<any>; + export function removeEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { + once?: boolean; + } | undefined): void; + export { constants }; + export const channel: BroadcastChannel; + export const SIGHUP: any; + export const SIGINT: any; + export const SIGQUIT: any; + export const SIGILL: any; + export const SIGTRAP: any; + export const SIGABRT: any; + export const SIGIOT: any; + export const SIGBUS: any; + export const SIGFPE: any; + export const SIGKILL: any; + export const SIGUSR1: any; + export const SIGSEGV: any; + export const SIGUSR2: any; + export const SIGPIPE: any; + export const SIGALRM: any; + export const SIGTERM: any; + export const SIGCHLD: any; + export const SIGCONT: any; + export const SIGSTOP: any; + export const SIGTSTP: any; + export const SIGTTIN: any; + export const SIGTTOU: any; + export const SIGURG: any; + export const SIGXCPU: any; + export const SIGXFSZ: any; + export const SIGVTALRM: any; + export const SIGPROF: any; + export const SIGWINCH: any; + export const SIGIO: any; + export const SIGINFO: any; + export const SIGSYS: any; + export const strings: { + [x: number]: string; + }; + namespace _default { + export { addEventListener }; + export { removeEventListener }; + export { constants }; + export { channel }; + export { strings }; + export { toString }; + export { getName }; + export { getCode }; + export { getMessage }; + export { SIGHUP }; + export { SIGINT }; + export { SIGQUIT }; + export { SIGILL }; + export { SIGTRAP }; + export { SIGABRT }; + export { SIGIOT }; + export { SIGBUS }; + export { SIGFPE }; + export { SIGKILL }; + export { SIGUSR1 }; + export { SIGSEGV }; + export { SIGUSR2 }; + export { SIGPIPE }; + export { SIGALRM }; + export { SIGTERM }; + export { SIGCHLD }; + export { SIGCONT }; + export { SIGSTOP }; + export { SIGTSTP }; + export { SIGTTIN }; + export { SIGTTOU }; + export { SIGURG }; + export { SIGXCPU }; + export { SIGXFSZ }; + export { SIGVTALRM }; + export { SIGPROF }; + export { SIGWINCH }; + export { SIGIO }; + export { SIGINFO }; + export { SIGSYS }; } - import * as exports from "socket:crypto"; - + export default _default; + export type signal = any; } -declare module "socket:ai" { +declare module "socket:internal/events" { /** - * A class to interact with large language models (using llama.cpp) + * An event dispatched when an application URL is opening the application. */ - export class LLM extends EventEmitter { + export class ApplicationURLEvent extends Event { /** - * Constructs an LLM instance. Each parameter is designed to configure and control - * the behavior of the underlying large language model provided by llama.cpp. - * @param {Object} options - Configuration options for the LLM instance. - * @param {string} options.path - The file path to the model in .gguf format. This model file contains - * the weights and configuration necessary for initializing the language model. - * @param {string} options.prompt - The initial input text to the model, setting the context or query - * for generating responses. The model uses this as a starting point for text generation. - * @param {string} [options.id] - An optional unique identifier for this specific instance of the model, - * useful for tracking or referencing the model in multi-model setups. - * @param {number} [options.n_ctx=1024] - Specifies the maximum number of tokens that the model can consider - * for a single query. This is crucial for managing memory and computational - * efficiency. Exceeding the model's configuration may lead to errors or truncated outputs. - * @param {number} [options.n_threads=8] - The number of threads allocated for the model's computation, - * affecting performance and speed of response generation. - * @param {number} [options.temp=1.1] - Sampling temperature controls the randomness of predictions. - * Higher values increase diversity, potentially at the cost of coherence. - * @param {number} [options.max_tokens=512] - The upper limit on the number of tokens that the model can generate - * in response to a single prompt. This prevents runaway generations. - * @param {number} [options.n_gpu_layers=32] - The number of GPU layers dedicated to the model processing. - * More layers can increase accuracy and complexity of the outputs. - * @param {number} [options.n_keep=0] - Determines how many of the top generated responses are retained after - * the initial generation phase. Useful for models that generate multiple outputs. - * @param {number} [options.n_batch=0] - The size of processing batches. Larger batch sizes can reduce - * the time per token generation by parallelizing computations. - * @param {number} [options.n_predict=0] - Specifies how many forward predictions the model should make - * from the current state. This can pre-generate responses or calculate probabilities. - * @param {number} [options.grp_attn_n=0] - Group attention parameter 'N' modifies how attention mechanisms - * within the model are grouped and interact, affecting the model’s focus and accuracy. - * @param {number} [options.grp_attn_w=0] - Group attention parameter 'W' adjusts the width of each attention group, - * influencing the breadth of context considered by each attention group. - * @param {number} [options.seed=0] - A seed for the random number generator used in the model. Setting this ensures - * consistent results in model outputs, important for reproducibility in experiments. - * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, - * reducing the risk of less likely, potentially nonsensical outputs. - * @param {number} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool - * to only those whose cumulative probability exceeds this value, enhancing output relevance. - * @param {number} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring - * that generated tokens have at least this likelihood of being relevant or coherent. - * @param {number} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how - * the model weights novel or unseen prompts during generation. - * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. + * `ApplicationURLEvent` class constructor. + * @param {string=} [type] + * @param {object=} [options] */ - constructor(options?: { - path: string; - prompt: string; - id?: string; - n_ctx?: number; - n_threads?: number; - temp?: number; - max_tokens?: number; - n_gpu_layers?: number; - n_keep?: number; - n_batch?: number; - n_predict?: number; - grp_attn_n?: number; - grp_attn_w?: number; - seed?: number; - top_k?: number; - tok_p?: number; - min_p?: number; - tfs_z?: number; - }); - path: string; - prompt: string; - id: string | BigInt; + constructor(type?: string | undefined, options?: object | undefined); /** - * Tell the LLM to stop after the next token. - * @returns {Promise<void>} A promise that resolves when the LLM stops. + * `true` if the application URL is valid (parses correctly). + * @type {boolean} */ - stop(): Promise<void>; + get isValid(): boolean; /** - * Send a message to the chat. - * @param {string} message - The message to send to the chat. - * @returns {Promise<any>} A promise that resolves with the response from the chat. + * Data associated with the `ApplicationURLEvent`. + * @type {?any} */ - chat(message: string): Promise<any>; + get data(): any; + /** + * The original source URI + * @type {?string} + */ + get source(): string; + /** + * The `URL` for the `ApplicationURLEvent`. + * @type {?URL} + */ + get url(): URL; + /** + * String tag name for an `ApplicationURLEvent` instance. + * @type {string} + */ + get [Symbol.toStringTag](): string; + #private; } - export default exports; - import { EventEmitter } from "socket:events"; - import * as exports from "socket:ai"; - -} - -declare module "socket:window/constants" { - export const WINDOW_ERROR: -1; - export const WINDOW_NONE: 0; - export const WINDOW_CREATING: 10; - export const WINDOW_CREATED: 11; - export const WINDOW_HIDING: 20; - export const WINDOW_HIDDEN: 21; - export const WINDOW_SHOWING: 30; - export const WINDOW_SHOWN: 31; - export const WINDOW_CLOSING: 40; - export const WINDOW_CLOSED: 41; - export const WINDOW_EXITING: 50; - export const WINDOW_EXITED: 51; - export const WINDOW_KILLING: 60; - export const WINDOW_KILLED: 61; - export default exports; - import * as exports from "socket:window/constants"; - -} - -declare module "socket:application/client" { /** - * @typedef {{ - * id?: string | null, - * type?: 'window' | 'worker', - * parent?: object | null, - * top?: object | null, - * frameType?: 'top-level' | 'nested' | 'none' - * }} ClientState + * An event dispacted for a registered global hotkey expression. */ - export class Client { + export class HotKeyEvent extends MessageEvent<any> { /** - * `Client` class constructor - * @private - * @param {ClientState} state + * `HotKeyEvent` class constructor. + * @ignore + * @param {string=} [type] + * @param {object=} [data] */ - private constructor(); + constructor(type?: string | undefined, data?: object | undefined); /** - * The unique ID of the client. - * @type {string|null} + * The global unique ID for this hotkey binding. + * @type {number?} */ - get id(): string; + get id(): number; /** - * The frame type of the client. - * @type {'top-level'|'nested'|'none'} + * The computed hash for this hotkey binding. + * @type {number?} */ - get frameType(): "none" | "top-level" | "nested"; + get hash(): number; /** - * The type of the client. - * @type {'window'|'worker'} + * The normalized hotkey expression as a sequence of tokens. + * @type {string[]} */ - get type(): "window" | "worker"; + get sequence(): string[]; /** - * The parent client of the client. - * @type {Client|null} + * The original expression of the hotkey binding. + * @type {string?} */ - get parent(): Client; + get expression(): string; + } + /** + * An event dispacted when a menu item is selected. + */ + export class MenuItemEvent extends MessageEvent<any> { /** - * The top client of the client. - * @type {Client|null} + * `MenuItemEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [data] + * @param {import('../application/menu.js').Menu} menu */ - get top(): Client; + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** - * A readonly `URL` of the current location of this client. - * @type {URL} + * The `Menu` this event has been dispatched for. + * @type {import('../application/menu.js').Menu?} */ - get location(): URL; + get menu(): import("socket:application/menu").Menu; /** - * Converts this `Client` instance to JSON. - * @return {object} + * The title of the menu item. + * @type {string?} */ - toJSON(): object; + get title(): string; + /** + * An optional tag value for the menu item that may also be the + * parent menu item title. + * @type {string?} + */ + get tag(): string; + /** + * The parent title of the menu item. + * @type {string?} + */ + get parent(): string; #private; } - const _default: any; + /** + * An event dispacted when the application receives an OS signal + */ + export class SignalEvent extends MessageEvent<any> { + /** + * `SignalEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [options] + */ + constructor(type?: string | undefined, options?: object | undefined); + /** + * The code of the signal. + * @type {import('../process/signal.js').signal} + */ + get code(): any; + /** + * The name of the signal. + * @type {string} + */ + get name(): string; + /** + * An optional message describing the signal + * @type {string} + */ + get message(): string; + #private; + } + namespace _default { + export { ApplicationURLEvent }; + export { MenuItemEvent }; + export { SignalEvent }; + export { HotKeyEvent }; + } export default _default; - export type ClientState = { - id?: string | null; - type?: "window" | "worker"; - parent?: object | null; - top?: object | null; - frameType?: "top-level" | "nested" | "none"; - }; } declare module "socket:window/hotkey" { @@ -16112,6 +16110,12 @@ declare module "socket:shared-worker" { export { Environment, SharedWorker }; } +declare module "socket:signal" { + export * from "socket:process/signal"; + export default signal; + import signal from "socket:process/signal"; +} + declare module "socket:service-worker/instance" { export function createServiceWorker(currentState?: any, options?: any): any; export const SHARED_WORKER_URL: string; @@ -16833,11 +16837,11 @@ declare module "socket:internal/promise" { */ constructor(resolver: ResolverFunction); [resourceSymbol]: { - "__#15@#type": any; - "__#15@#destroyed": boolean; - "__#15@#asyncId": number; - "__#15@#triggerAsyncId": any; - "__#15@#requireManualDestroy": boolean; + "__#16@#type": any; + "__#16@#destroyed": boolean; + "__#16@#asyncId": number; + "__#16@#triggerAsyncId": any; + "__#16@#requireManualDestroy": boolean; readonly type: string; readonly destroyed: boolean; asyncId(): number; diff --git a/api/internal/events.js b/api/internal/events.js index 3804e19722..33e6a1af3d 100644 --- a/api/internal/events.js +++ b/api/internal/events.js @@ -211,7 +211,7 @@ export class SignalEvent extends MessageEvent { /** * The code of the signal. - * @type {import('../signal.js').signal} + * @type {import('../process/signal.js').signal} */ get code () { return this.#code ?? 0 diff --git a/api/internal/init.js b/api/internal/init.js index 79712e8df2..5a2aed8c81 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -922,7 +922,7 @@ hooks.onReady(async () => { await ipc.request('fs.constants', {}, { cache: true }) await ipc.request('os.constants', {}, { cache: true }) await import('../diagnostics.js') - await import('../signal.js') + await import('../process/signal.js') await import('../fs/fds.js') await import('../constants.js') const errors = await import('../errors.js') diff --git a/api/internal/permissions.js b/api/internal/permissions.js index 1261c80c0e..2ad366cbc2 100644 --- a/api/internal/permissions.js +++ b/api/internal/permissions.js @@ -1,6 +1,6 @@ /* global EventTarget, CustomEvent, Event */ /** - * @module Permissions + * @module permissions * This module provides an API for querying and requesting permissions. */ import { IllegalConstructorError } from '../errors.js' diff --git a/api/ip.js b/api/ip.js index 596a94c6c3..04ea587193 100644 --- a/api/ip.js +++ b/api/ip.js @@ -1,5 +1,5 @@ /** - * @module IP + * @module ip * * Various functions for working with IP addresses. * diff --git a/api/ipc.js b/api/ipc.js index 688ad2790d..b748e81f0b 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1,5 +1,5 @@ /** - * @module IPC + * @module ipc * * This is a low-level API that you don't need unless you are implementing * a library on top of Socket runtime. A Socket app has one or more processes. diff --git a/api/language.js b/api/language.js index e2c5934f1d..40635b647b 100644 --- a/api/language.js +++ b/api/language.js @@ -1,5 +1,5 @@ /** - * @module Language + * @module language * * A module for querying ISO 639-1 language names and codes and working with * RFC 5646 language tags. diff --git a/api/module.js b/api/module.js index dda319793a..d5f1c44abf 100644 --- a/api/module.js +++ b/api/module.js @@ -1,5 +1,5 @@ /** - * @module Module + * @module module */ import builtins, { defineBuiltin, isBuiltin } from './commonjs/builtins.js' import { createRequire, Module } from './commonjs/module.js' diff --git a/api/network.js b/api/network.js index 6f78ee46cd..7b0cf0385d 100644 --- a/api/network.js +++ b/api/network.js @@ -1,5 +1,5 @@ /** - * @module Network + * @module network * * Provides a higher level API over the latica protocol. * diff --git a/api/notification.js b/api/notification.js index d3cb149d0d..803a122f4d 100644 --- a/api/notification.js +++ b/api/notification.js @@ -1,6 +1,6 @@ /* global CustomEvent, Event, ErrorEvent, EventTarget */ /** - * @module Notification + * @module notification * The Notification modules provides an API to configure and display * desktop and mobile notifications to the user. It also includes mechanisms * for request permissions to use notifications on the user's device. diff --git a/api/os.js b/api/os.js index e4efec5a2c..c1afc508d4 100644 --- a/api/os.js +++ b/api/os.js @@ -1,5 +1,5 @@ /** - * @module OS + * @module os * * This module provides normalized system information from all the major * operating systems. diff --git a/api/path/path.js b/api/path/path.js index 3a2850446e..6fb98fd874 100644 --- a/api/path/path.js +++ b/api/path/path.js @@ -1,5 +1,5 @@ /** - * @module Path + * @module path * * Example usage: * ```js diff --git a/api/signal.js b/api/process/signal.js similarity index 100% rename from api/signal.js rename to api/process/signal.js diff --git a/api/stream.js b/api/stream.js index 40bc38f5fe..f98b7499bf 100644 --- a/api/stream.js +++ b/api/stream.js @@ -23,7 +23,7 @@ **/ /** - * @module Stream + * @module stream */ import { EventEmitter } from './events.js' import web from './stream/web.js' diff --git a/api/test/dom-helpers.js b/api/test/dom-helpers.js index 1c1670fd65..837b4bbca6 100644 --- a/api/test/dom-helpers.js +++ b/api/test/dom-helpers.js @@ -1,6 +1,6 @@ // @ts-check /** - * @module Test.DOM-helpers + * @module test.dom-helpers * * Provides a test runner for Socket Runtime. * diff --git a/api/test/index.js b/api/test/index.js index d9f2d5a3f1..07f2d8d617 100644 --- a/api/test/index.js +++ b/api/test/index.js @@ -1,6 +1,6 @@ // @ts-check /** - * @module Test + * @module test * * Provides a test runner for Socket Runtime. * diff --git a/api/vm.js b/api/vm.js index c2aba58038..0178c42ef3 100644 --- a/api/vm.js +++ b/api/vm.js @@ -1,5 +1,5 @@ /** - * @module VM + * @module vm * * This module enables compiling and running JavaScript source code in an * isolated execution context optionally with a user supplied context object. diff --git a/api/window.js b/api/window.js index d3c4183f0b..b8cf3b6aec 100644 --- a/api/window.js +++ b/api/window.js @@ -1,6 +1,6 @@ // @ts-check /** - * @module Window + * @module window * * Provides ApplicationWindow class and methods * From 6754986460b18d25e314ecc623961d70559fce1c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 23:29:21 +0200 Subject: [PATCH 0978/1178] fix(bin/generate-docs.js): handle offline --- bin/generate-docs.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/generate-docs.js b/bin/generate-docs.js index 132befba36..8d94d4d92d 100755 --- a/bin/generate-docs.js +++ b/bin/generate-docs.js @@ -8,7 +8,15 @@ import { generateCli } from './docs-generator/cli.js' const VERSION = `v${(await fs.readFile('./VERSION.txt', 'utf8')).trim()}` const isCurrentTag = execSync('git describe --tags --always').toString().trim() === VERSION -const tagNotPresentOnRemote = execSync(`git ls-remote --tags origin ${VERSION}`).toString().length === 0 + +let tagNotPresentOnRemote = false +if (!isCurrentTag) { + try { + tagNotPresentOnRemote = execSync(`git ls-remote --tags origin ${VERSION}`).toString().length === 0 + } catch (err) { + console.warn(err.message) + } +} const gitTagOrBranch = (isCurrentTag || tagNotPresentOnRemote) ? VERSION : 'master' From 1cdd6ab619831558d7220bcf62ed9cdc7d1ece33 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 23:29:49 +0200 Subject: [PATCH 0979/1178] fix(api/location.js): normalize origin protocol for 'socket:location' --- api/location.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/location.js b/api/location.js index 29113030f0..cd0e9faa3a 100644 --- a/api/location.js +++ b/api/location.js @@ -30,9 +30,11 @@ export class Location { } get origin () { - return this.url.origin && this.url.origin !== 'null' + const origin = this.url.origin && this.url.origin !== 'null' ? this.url.origin : globalThis.origin || globalThis.location.origin + + return origin.replace('https://', 'socket://') } get href () { From 636fb2d5d056d461d767da4a9baa1fc301c6b049 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 15 Jul 2024 23:30:15 +0200 Subject: [PATCH 0980/1178] refactor(api/process): move 'signal.js' to 'process/signal.js' --- api/child_process.js | 2 +- api/child_process/worker.js | 2 +- api/commonjs/builtins.js | 1 + api/commonjs/loader.js | 9 ++++++--- api/commonjs/package.js | 2 +- api/process.js | 9 +++++++-- api/process/signal.js | 2 +- api/signal.js | 15 +++++++++++++++ 8 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 api/signal.js diff --git a/api/child_process.js b/api/child_process.js index 50f20c8572..9470b50e20 100644 --- a/api/child_process.js +++ b/api/child_process.js @@ -6,7 +6,7 @@ import { Worker } from './worker_threads.js' import { Buffer } from './buffer.js' import { rand64 } from './crypto.js' import process from './process.js' -import signal from './signal.js' +import signal from './process/signal.js' import ipc from './ipc.js' import gc from './gc.js' import os from './os.js' diff --git a/api/child_process/worker.js b/api/child_process/worker.js index 932172ebe4..9fcea0d413 100644 --- a/api/child_process/worker.js +++ b/api/child_process/worker.js @@ -1,6 +1,6 @@ import { parentPort } from '../worker_threads.js' import process from '../process.js' -import signal from '../signal.js' +import signal from '../process/signal.js' import ipc from '../ipc.js' const state = {} diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index 066d1de156..322a0dd542 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -100,6 +100,7 @@ defineBuiltin('async_hooks', { createHook, AsyncHook }) +defineBuiltin('ai', ai) defineBuiltin('assert', assert, false) defineBuiltin('buffer', buffer, false) defineBuiltin('console', console, false) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 25772a7c75..340d35c773 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -1,6 +1,6 @@ /* global XMLHttpRequest */ /** - * @module CommonJS.Loader + * @module commonjs.loader */ import { CacheCollection, Cache } from './cache.js' import { defineBuiltin } from './builtins.js' @@ -102,8 +102,11 @@ export class RequestStatus { set request (request) { this.#request = request - if (!this.#status && request.loader?.cache?.status) { - this.#status = request.loader.cache.status.get(request.id)?.value + if ( + !this.#status && + request?.loader?.cache?.status?.has?.(request?.id) + ) { + this.#status = request.loader.cache.status.get(request.id)?.value ?? null } } diff --git a/api/commonjs/package.js b/api/commonjs/package.js index ba5ee6f467..3be9bdb762 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -1,5 +1,5 @@ /** - * @module CommonJS.Package + * @module commonjs.package */ import { ModuleNotFoundError } from '../errors.js' import { defineBuiltin } from './builtins.js' diff --git a/api/process.js b/api/process.js index 49c7bafaa8..f83a5daf05 100644 --- a/api/process.js +++ b/api/process.js @@ -1,5 +1,5 @@ /** - * @module Process + * @module process * * Example usage: * ```js @@ -8,7 +8,7 @@ */ import { primordials, send } from './ipc.js' import { EventEmitter } from './events.js' -import signal from './signal.js' +import signal from './proces/signal.js' import tty from './tty.js' import os from './os.js' @@ -31,6 +31,7 @@ export class ProcessEnvironment extends EventTarget { return 'ProcessEnvironment' } } + export const env = Object.defineProperties(new ProcessEnvironment(), { proxy: { configurable: false, @@ -132,6 +133,10 @@ class Process extends EventEmitter { } } + uptime () { + return os.uptime() + } + cwd () { return cwd } diff --git a/api/process/signal.js b/api/process/signal.js index 691af94a51..602c1e7833 100644 --- a/api/process/signal.js +++ b/api/process/signal.js @@ -1,5 +1,5 @@ /** - * @module Signal + * @module signal */ import { signal as constants } from './os/constants.js' import { SignalEvent } from './internal/events.js' diff --git a/api/signal.js b/api/signal.js new file mode 100644 index 0000000000..b9861c719b --- /dev/null +++ b/api/signal.js @@ -0,0 +1,15 @@ +/** + * @module signal + * @deprecated Use `socket:process/signal` instead. + */ + +import signal from './process/signal.js' +export * from './process/signal.js' +export default signal + +// XXX(@jwerle): we should probably use a standard `deprecated()` function +// like nodejs' `util.deprecate()` instead of `console.warn()` +console.warn( + 'The module "socket:signal" is deprecated. ' + + 'Please use "socket:process/signal" instead"' +) From acaaac5642007104e746faa0540beb9e489475c3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 16 Jul 2024 16:00:03 +0200 Subject: [PATCH 0981/1178] fix(api): fix 'process/signal.js' imports --- api/process.js | 2 +- api/process/signal.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/process.js b/api/process.js index f83a5daf05..57d866731f 100644 --- a/api/process.js +++ b/api/process.js @@ -8,7 +8,7 @@ */ import { primordials, send } from './ipc.js' import { EventEmitter } from './events.js' -import signal from './proces/signal.js' +import signal from './process/signal.js' import tty from './tty.js' import os from './os.js' diff --git a/api/process/signal.js b/api/process/signal.js index 602c1e7833..90ae0b40e6 100644 --- a/api/process/signal.js +++ b/api/process/signal.js @@ -1,9 +1,9 @@ /** * @module signal */ -import { signal as constants } from './os/constants.js' -import { SignalEvent } from './internal/events.js' -import os from './os.js' +import { signal as constants } from '../os/constants.js' +import { SignalEvent } from '../internal/events.js' +import os from '../os.js' /** * @typedef {import('./os/constants.js').signal} signal From 7093b2ce72f2dac9d126243c60e2f384ebc867a1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 16 Jul 2024 16:26:52 +0200 Subject: [PATCH 0982/1178] refactor(app): conslidate pause/resume logic into 'App' --- src/app/app.cc | 68 ++++++++++++++++++++++++-------------------------- src/app/app.hh | 14 ++++++----- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index fdf3ad36ce..0dbc5e34b8 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -22,13 +22,13 @@ static dispatch_queue_t queue = dispatch_queue_create( - (void) applicationWillBecomeActive: (NSNotification*) notification { dispatch_async(queue, ^{ - self.app->core->resume(); + self.app->resume(); }); } - (void) applicationWillResignActive: (NSNotification*) notification { dispatch_async(queue, ^{ - // self.app->core->pause(); + // self.app->pause(); }); } @@ -295,13 +295,13 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType - (void) applicationDidBecomeActive: (UIApplication*) application { dispatch_async(queue, ^{ - self.app->core->resume(); + self.app->resume(); }); } - (void) applicationWillResignActive: (UIApplication*) application { dispatch_async(queue, ^{ - self.app->core->pause(); + self.app->pause(); }); } @@ -849,12 +849,31 @@ namespace SSC { return shouldExit ? 1 : 0; } - void App::kill () { - applicationInstance = nullptr; - this->killed = true; + void App::resume () { + if (this->core != nullptr) { + this->core->resume(); + } + } + + void App::pause () { + if (this->core != nullptr) { + this->core->pause(); + } + } + + void App::stop () { + if (this->stopped) { + return; + } + + this->pause(); + + SSC::applicationInstance = nullptr; + + this->stopped = true; + this->shouldExit = true; + this->core->shutdown(); - // Distinguish window closing with app exiting - shouldExit = true; #if SOCKET_RUNTIME_PLATFORM_LINUX && !SOCKET_RUNTIME_DESKTOP_EXTENSION gtk_main_quit(); #elif SOCKET_RUNTIME_PLATFORM_MACOS @@ -868,21 +887,6 @@ namespace SSC { #endif } - void App::restart () { - #if SOCKET_RUNTIME_PLATFORM_LINUX - // @TODO - #elif SOCKET_RUNTIME_PLATFORM_MACOS - // @TODO - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - char filename[MAX_PATH] = ""; - PROCESS_INFORMATION pi; - STARTUPINFO si = { sizeof(STARTUPINFO) }; - GetModuleFileName(NULL, filename, MAX_PATH); - CreateProcess(NULL, filename, NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi); - std::exit(0); - #endif - } - void App::dispatch (Function<void()> callback) { #if SOCKET_RUNTIME_PLATFORM_LINUX g_main_context_invoke( @@ -951,12 +955,6 @@ namespace SSC { return userConfig.at(key) != "false"; } - void App::exit (int code) { - if (this->onExit != nullptr) { - this->onExit(code); - } - } - #if SOCKET_RUNTIME_PLATFORM_WINDOWS LRESULT App::forwardWindowProcMessage ( HWND hWnd, @@ -1248,7 +1246,7 @@ extern "C" { app->jni = nullptr; app->self = nullptr; - app->core->pause(); + app->pause(); } void ANDROID_EXTERNAL(app, App, onStart)(JNIEnv *env, jobject self) { @@ -1257,7 +1255,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->resume(); + app->resume(); } void ANDROID_EXTERNAL(app, App, onStop)(JNIEnv *env, jobject self) { @@ -1266,7 +1264,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->pause(); + app->pause(); } void ANDROID_EXTERNAL(app, App, onResume)(JNIEnv *env, jobject self) { @@ -1275,7 +1273,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->resume(); + app->resume(); } void ANDROID_EXTERNAL(app, App, onPause)(JNIEnv *env, jobject self) { @@ -1284,7 +1282,7 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - app->core->pause(); + app->pause(); } void ANDROID_EXTERNAL(app, App, onPermissionChange)( diff --git a/src/app/app.hh b/src/app/app.hh index 79529fc574..7de352694b 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -66,8 +66,10 @@ namespace SSC { static constexpr int DEFAULT_INSTANCE_ID = 0; #if SOCKET_RUNTIME_PLATFORM_APPLE - // created and set in `App::App()` on macOS or - // created by `UIApplicationMain` and set in `application:didFinishLaunchingWithOptions:` on iOS + // created and set in `App::App()` on macOS or created by + // `UIApplicationMain` and set in the + // `application:didFinishLaunchingWithOptions:` delegate methdo on iOS + // TODO(@jwerle): remove this field SSCApplicationDelegate* applicationDelegate = nullptr; #if SOCKET_RUNTIME_PLATFORM_MACOS NSAutoreleasePool* pool = [NSAutoreleasePool new]; @@ -88,7 +90,7 @@ namespace SSC { ExitCallback onExit = nullptr; AtomicBool shouldExit = false; - AtomicBool killed = false; + AtomicBool stopped = false; bool wasLaunchedFromCli = false; WindowManager windowManager; @@ -134,9 +136,9 @@ namespace SSC { int run (int argc = 0, char** argv = nullptr); void init (); - void kill (); - void exit (int code); - void restart (); + void stop (); + void resume (); + void pause (); void dispatch (Function<void()>); String getcwd (); bool hasRuntimePermission (const String& permission) const; From bae7c5578b78991437926e962a31f3bb48ff4d2d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 16 Jul 2024 16:29:56 +0200 Subject: [PATCH 0983/1178] refactor(desktop/main): verify pid when launching second instance --- src/desktop/main.cc | 49 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 9a24b8c966..50f23c04c0 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -12,7 +12,6 @@ #endif #include <iostream> -#include <ostream> #include <chrono> #include <regex> #include <span> @@ -59,6 +58,22 @@ static void installSignalHandler (int signum, void (*handler)(int)) { using namespace SSC; +static inline String readFile (const Path& path) { + static String buffer; + auto stream = InputFileStream(path.string()); + auto begin = InputStreamBufferIterator<char>(stream); + auto end = InputStreamBufferIterator<char>(); + buffer.assign(begin, end); + stream.close(); + return buffer; +} + +static inline void writeFile (const Path& path, const String& source) { + auto stream = OutputFileStream(path.string()); + stream << source; + stream.close(); +} + static Function<void(int)> shutdownHandler; // propagate signals to the default window which will use the @@ -274,22 +289,42 @@ MAIN { #if SOCKET_RUNTIME_PLATFORM_LINUX static const auto TMPDIR = Env::get("TMPDIR", "/tmp"); - static const auto appInstanceLock = fs::path(TMPDIR) / (bundleIdentifier + ".lock"); + static const auto appInstanceLock = Path(TMPDIR) / (bundleIdentifier + ".lock"); + static const auto appInstancePID = Path(TMPDIR) / (bundleIdentifier + ".pid"); + static const auto appProtocol = userConfig["meta_application_protocol"]; + + const auto existingPIDValue = readFile(appInstancePID); + if (existingPIDValue.size() > 0) { + try { + const pid_t pid = std::stoi(existingPIDValue); + if (kill(pid, 0) != 0) { + throw std::runtime_error(""); + } + } catch (...) { + unlink(appInstanceLock.c_str()); + unlink(appInstancePID.c_str()); + } + } + + // lock + pid files auto appInstanceLockFd = open(appInstanceLock.c_str(), O_CREAT | O_EXCL, 0600); - auto appProtocol = userConfig["meta_application_protocol"]; + + // dbus auto dbusError = DBusError {}; dbus_error_init(&dbusError); static auto connection = dbus_bus_get(DBUS_BUS_SESSION, &dbusError); auto dbusBundleIdentifier = replace(bundleIdentifier, "-", "_"); // instance is running if fd was acquired - if (appInstanceLockFd != -1) { - auto filter = ( + if (appInstanceLockFd > 0) { + const auto pid = getpid(); + const auto filter = ( String("type='method_call',") + String("interface='") + dbusBundleIdentifier + String("',") + String("member='handleApplicationURLEvent'") ); dbus_bus_add_match(connection, filter.c_str(), &dbusError); + writeFile(appInstancePID, std::to_string(pid)); if (dbus_error_is_set(&dbusError)) { fprintf(stderr, "error: dbus: Failed to add match rule: %s\n", dbusError.message); @@ -553,7 +588,7 @@ MAIN { if (cmd[0] == '.') { auto index = cmd.find_first_of('.'); auto executable = cmd.substr(0, index); - auto absPath = fs::path(cwd) / fs::path(executable); + auto absPath = Path(cwd) / Path(executable); cmd = absPath.string() + cmd.substr(index); } @@ -880,7 +915,7 @@ MAIN { } #endif - app.kill(); + app.stop(); exit(code); }; From cef088fdf5b205e6dea4f5056bccb6425f058876 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 16 Jul 2024 16:30:13 +0200 Subject: [PATCH 0984/1178] refactor(ipc,platform): clean up + more types --- src/ipc/bridge.cc | 4 +++- src/platform/types.hh | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index fa1c6892d0..b23539671c 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -209,7 +209,9 @@ export * from '{{url}}' }; core->networkStatus.addObserver(this->networkStatusObserver, [this](auto json) { - if (json.has("name")) this->emit(json["name"].str(), json.str()); + if (json.has("name")) { + this->emit(json["name"].str(), json.str()); + } }); core->networkStatus.start(); diff --git a/src/platform/types.hh b/src/platform/types.hh index 27ab83d748..def72808bd 100644 --- a/src/platform/types.hh +++ b/src/platform/types.hh @@ -4,10 +4,12 @@ #include <array> #include <atomic> #include <filesystem> +#include <fstream> #include <functional> #include <future> #include <map> #include <mutex> +#include <ostream> #include <queue> #include <set> #include <sstream> @@ -29,6 +31,8 @@ namespace SSC { using StringStream = std::stringstream; using WString = std::wstring; using WStringStream = std::wstringstream; + using InputFileStream = std::ifstream; + using OutputFileStream = std::ofstream; using Mutex = std::recursive_mutex; using Lock = std::lock_guard<Mutex>; using Map = std::map<String, String>; @@ -46,6 +50,7 @@ namespace SSC { template <typename T> using SharedPointer = std::shared_ptr<T>; template <typename T> using UniquePointer = std::unique_ptr<T>; template <typename T> using Promise = std::promise<T>; + template <typename T> using InputStreamBufferIterator = std::istreambuf_iterator<T>; using ExitCallback = Function<void(int code)>; using MessageCallback = Function<void(const String)>; From fe3817d5933e58fb01ee1abcad2b4309746b469d Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Tue, 16 Jul 2024 17:04:22 +0200 Subject: [PATCH 0985/1178] chore(api): update protocol --- api/latica/api.js | 2 +- api/latica/cache.js | 21 +++-- api/latica/index.js | 65 +++++++++++----- api/latica/packets.js | 175 ++++++++++++++++++++++++++---------------- api/latica/proxy.js | 2 +- 5 files changed, 168 insertions(+), 97 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index 6326636910..578a64c8a3 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -274,7 +274,7 @@ async function api (options = {}, events, dgram) { _peer.cache.insert(pid, p) _peer.unpublished[pid] = Date.now() - if (globalThis.navigator && !globalThis.navigator.onLine) continue + if (!Peer.onLine()) continue _peer.mcast(packet) } diff --git a/api/latica/cache.js b/api/latica/cache.js index d4e1a8749a..f8773a7121 100644 --- a/api/latica/cache.js +++ b/api/latica/cache.js @@ -3,12 +3,6 @@ import { Buffer } from '../buffer.js' import { createDigest } from '../crypto.js' import { Packet, PacketPublish, PACKET_BYTES, sha256 } from './packets.js' -const EMPTY_CACHE = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' - -export const trim = (/** @type {Buffer} */ buf) => { - return buf.toString().split('~')[0].split('\x00')[0] -} - /** * Tries to convert input to a `Buffer`, if possible, otherwise `null`. * @ignore @@ -89,6 +83,7 @@ export class Cache { maxSize = DEFAULT_MAX_SIZE static HASH_SIZE_BYTES = 20 + static HASH_EMPTY = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' /** * `Cache` class constructor. @@ -196,7 +191,7 @@ export class Cache { // follow the chain to get the buffers in order let bufs = [...source.values()].filter(p => { - if (!p.previousId) return false + if (!p.previousId) return return Buffer.from(p.previousId).compare(Buffer.from(previous.packetId)) === 0 }) @@ -296,7 +291,7 @@ export class Cache { if (!buckets.every(b => b === null)) { hash = await this.sha1(buckets.join(''), true) } else { - hash = EMPTY_CACHE + hash = Cache.HASH_EMPTY } return { prefix, hash, buckets } @@ -358,6 +353,16 @@ export class Cache { return { prefix, hash, buckets } } + + /** + * Test a summary hash format is valid + * + * @param {string} hash + * @returns boolean + */ + static isValidSummaryHashFormat (hash) { + return typeof hash === 'string' && /^[A-Fa-f0-9]{40}$/.test(hash) + } } export default Cache diff --git a/api/latica/index.js b/api/latica/index.js index 471a5a599d..9b83c710bf 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -119,7 +119,7 @@ export class RemotePeer { peerId = null address = null port = 0 - natType = null + natType = NAT.UNKNOWN clusters = {} pingId = null distance = 0 @@ -425,9 +425,7 @@ export class Peer { async _mainLoop (ts) { if (this.closing) return this._clearInterval(this.mainLoopTimer) - // if `globalThis.navigator` doesn't exist (such as in Node) assume online. - const online = !globalThis.navigator || globalThis.navigator.onLine - if (!online) { + if (!Peer.onLine()) { if (this.onConnecting) this.onConnecting({ code: -2, status: 'Offline' }) return true } @@ -964,7 +962,7 @@ export class Peer { const siblings = packet && [...this.cache.data.values()] .filter(Boolean) .filter(p => { - if (!p.previousId || !packet.packetId) return false + if (!p.previousId || !packet.packetId) return return Buffer.from(p.previousId).compare(Buffer.from(packet.packetId)) === 0 }) @@ -1070,7 +1068,7 @@ export class Peer { } this.unpublished[packet.packetId.toString('hex')] = Date.now() - if (globalThis.navigator && !globalThis.navigator.onLine) continue + if (!Peer.onLine()) continue this.mcast(packet) } @@ -1410,7 +1408,6 @@ export class Peer { this.lastUpdate = Date.now() const { reflectionId, isReflection, isConnection, requesterPeerId, natType } = packet.message - if (!Peer.isValidPeerId(requesterPeerId)) return // required field if (requesterPeerId === this.peerId) return // from self? const { probeExternalPort, isProbe, pingId } = packet.message @@ -1477,7 +1474,6 @@ export class Peer { const { reflectionId, pingId, isReflection, responderPeerId } = packet.message - if (!Peer.isValidPeerId(responderPeerId)) return // required field if (responderPeerId === this.peerId) return // from self? this._onDebug(`<- PONG (from=${address}:${port}, hash=${packet.message.cacheSummaryHash}, isConnection=${!!packet.message.isConnection})`) @@ -1601,8 +1597,6 @@ export class Peer { if (packet.hops >= this.maxHops) return if (!isNaN(ts) && ((ts + this.config.keepalive) < Date.now())) return - if (!Peer.isValidPeerId(packet.message.requesterPeerId)) return - if (!Peer.isValidPeerId(packet.message.responderPeerId)) return if (packet.message.requesterPeerId === this.peerId) return // intro to myself? if (packet.message.responderPeerId === this.peerId) return // intro from myself? @@ -1643,7 +1637,7 @@ export class Peer { if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) - const pingId = Math.random().toString(16).slice(2) + const pingId = randomBytes(6).toString('hex').padStart(12, '0') const { hash } = await this.cache.summarize('', this.cachePredicate) const props = { @@ -1652,7 +1646,7 @@ export class Peer { message: { natType: this.natType, isConnection: true, - cacheSummaryHash: hash || null, + cacheSummaryHash: hash, pingId: packet.message.pingId, requesterPeerId: this.peerId } @@ -1711,7 +1705,7 @@ export class Peer { subclusterId, message: { requesterPeerId: this.peerId, - cacheSummaryHash: hash || null, + cacheSummaryHash: hash, natType: this.natType, uptime: this.uptime, isConnection: true, @@ -1822,7 +1816,6 @@ export class Peer { this.metrics.i[packet.type]++ const pid = packet.packetId.toString('hex') - if (!Peer.isValidPeerId(packet.message.requesterPeerId)) return // required field if (packet.message.requesterPeerId === this.peerId) return // from self? if (this.gate.has(pid)) return if (packet.clusterId.length !== 32) return @@ -2121,13 +2114,9 @@ export class Peer { } this._onDebug(`>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) - // I am the proxy! this.send(await Packet.encode(packet), peerTo.port, peerTo.address) - // - // What % of packets hit the server. - // - + // Might be interesting to allow retransmission in some cases // if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED) { // this.mcast(packet, [{ port, address }, { port: peerFrom.port, address: peerFrom.address }]) // } @@ -2144,7 +2133,7 @@ export class Peer { const packet = Packet.decode(data) if (!packet || packet.version !== VERSION) return - if (packet?.type !== 2) return + if (packet?.type !== PacketPong.type) return const pid = packet.packetId.toString('hex') if (this.gate.has(pid)) return @@ -2252,6 +2241,42 @@ export class Peer { static isValidPeerId (pid) { return typeof pid === 'string' && PEERID_REGEX.test(pid) } + + /** + * Test a reflectionID is valid + * + * @param {string} rid + * @returns boolean + */ + static isValidReflectionId (rid) { + return typeof rid === 'string' && /^[A-Fa-f0-9]{12}$/.test(rid) + } + + /** + * Test a pingID is valid + * + * @param {string} pid + * @returns boolean + */ + static isValidPingId (pid) { + return typeof pid === 'string' && /^[A-Fa-f0-9]{12,13}$/.test(pid) + + // the above line is provided for backwards compatibility due to a breaking change introduced in: + // https://github.com/socketsupply/latica/commit/f02db9e37ad3ed476cebc7f6269738f4e0c9acaf + // once all peers have received that commit we can enforce an exact length of 12 hex chars: + // return typeof pid === 'string' && /^[A-Fa-f0-9]{12}$/.test(pid) + } + + /** + * Returns the online status of the browser, else true. + * + * note: globalThis.navigator was added to node in v22. + * + * @returns boolean + */ + static onLine () { + return globalThis.navigator?.onLine !== false + } } export default Peer diff --git a/api/latica/packets.js b/api/latica/packets.js index 900adeead6..87458adb48 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -1,6 +1,8 @@ import { randomBytes } from '../crypto.js' import { isBufferLike } from '../util.js' import { Buffer } from '../buffer.js' +import { isIPv4 } from '../ip.js' +import Peer, { Cache, NAT } from './index.js' /** * Hash function factory. @@ -140,25 +142,46 @@ export const PACKET_BYTES = FRAME_BYTES + MESSAGE_BYTES export const MAX_HOPS = 16 /** - * @param {object} message - * @param {{ [key: string]: { required: boolean, type: string }}} constraints + * @typedef constraint + * @type {Object} + * @property {string} type + * @property {boolean} [required] + * @property {function} [assert] optional validator fn returning boolean */ -export const validatePacket = (o, constraints) => { - if (!o) throw new Error('Expected object') + +/** + * @param {object} o + * @param {{ [key: string]: constraint }} constraints + */ +export const validateMessage = (o, constraints) => { + if (({}).toString.call(o) !== '[object Object]') throw new Error('expected object') + if (({}).toString.call(constraints) !== '[object Object]') throw new Error('expected constraints') + const allowedKeys = Object.keys(constraints) const actualKeys = Object.keys(o) - const unknown = actualKeys.filter(k => allowedKeys.indexOf(k) === -1) - if (unknown.length) throw new Error(`Unexpected keys [${unknown}]`) + const unknownKeys = actualKeys.filter(k => allowedKeys.indexOf(k) === -1) + if (unknownKeys.length) throw new Error(`unexpected keys [${unknownKeys}]`) for (const [key, con] of Object.entries(constraints)) { - if (con.required && !o[key]) { + const unset = !Object.prototype.hasOwnProperty.call(o, key) + + if (con.required && unset) { // console.warn(new Error(`${key} is required (${JSON.stringify(o, null, 2)})`), JSON.stringify(o)) + throw new Error(`key '${key}' is required`) } + const empty = typeof o[key] === 'undefined' + if (empty) return // nothing to validate + const type = ({}).toString.call(o[key]).slice(8, -1).toLowerCase() + if (type !== con.type) { + // console.warn(`expected .${key} to be of type ${con.type}, got ${type} in packet.. ` + JSON.stringify(o)) + throw new Error(`expected '${key}' to be of type '${con.type}', got '${type}'`) + } - if (o[key] && type !== con.type) { + if (typeof con.assert === 'function' && !con.assert(o[key])) { // console.warn(`expected .${key} to be of type ${con.type}, got ${type} in packet.. ` + JSON.stringify(o)) + throw new Error(`expected '${key}' to pass constraint assertion`) } } } @@ -193,7 +216,7 @@ export const decode = buf => { buf = buf.slice(MAGIC_BYTES) - const o = new Packet() + const o = {} let offset = 0 for (const [k, spec] of Object.entries(PACKET_SPEC)) { @@ -228,7 +251,7 @@ export const decode = buf => { } } - return o + return Packet.from(o) } export const getTypeFromBytes = (buf) => buf.byteLength > 4 ? buf.readUInt8(4) : 0 @@ -250,8 +273,19 @@ export class Packet { * @return {Packet} */ static from (packet) { - if (packet instanceof Packet) return packet - return new Packet(packet) + if (packet instanceof Packet && packet.constructor !== Packet) return packet + + switch (packet.type) { + case PacketPing.type: return new PacketPing(packet) + case PacketPong.type: return new PacketPong(packet) + case PacketIntro.type: return new PacketIntro(packet) + case PacketJoin.type: return new PacketJoin(packet) + case PacketPublish.type: return new PacketPublish(packet) + case PacketStream.type: return new PacketStream(packet) + case PacketSync.type: return new PacketSync(packet) + case PacketQuery.type: return new PacketQuery(packet) + default: throw new Error('invalid packet type', packet.type) + } } /** @@ -259,7 +293,8 @@ export class Packet { * @return {Packet} */ copy () { - return Object.assign(new Packet({}), this) + const PacketConstructor = this.constructor + return Object.assign(new PacketConstructor({}), this) } /** @@ -371,22 +406,27 @@ export class Packet { } static decode (buf) { - return decode(buf) + try { + return decode(buf) + } catch (e) { + console.warn('failed to decode packet', e) + return null + } } } export class PacketPing extends Packet { static type = 1 - constructor ({ message, clusterId, subclusterId }) { - super({ type: PacketPing.type, message, clusterId, subclusterId }) - - validatePacket(message, { - requesterPeerId: { required: true, type: 'string' }, - cacheSummaryHash: { type: 'string' }, - probeExternalPort: { type: 'number' }, - reflectionId: { type: 'string' }, - pingId: { type: 'string' }, - natType: { type: 'number' }, + constructor (args) { + super({ ...args, type: PacketPing.type }) + + validateMessage(args.message, { + requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, + cacheSummaryHash: { type: 'string', assert: Cache.isValidSummaryHashFormat }, + probeExternalPort: { type: 'number', assert: isValidPort }, + reflectionId: { type: 'string', assert: Peer.isValidReflectionId }, + pingId: { type: 'string', assert: Peer.isValidPingId }, + natType: { type: 'number', assert: NAT.isValid }, uptime: { type: 'number' }, cacheSize: { type: 'number' }, isConnection: { type: 'boolean' }, @@ -400,22 +440,22 @@ export class PacketPing extends Packet { export class PacketPong extends Packet { static type = 2 - constructor ({ message, clusterId, subclusterId }) { - super({ type: PacketPong.type, message, clusterId, subclusterId }) - - validatePacket(message, { - requesterPeerId: { required: true, type: 'string' }, - responderPeerId: { required: true, type: 'string' }, - cacheSummaryHash: { type: 'string' }, - port: { type: 'number' }, - address: { type: 'string' }, + constructor (args) { + super({ ...args, type: PacketPong.type }) + + validateMessage(args.message, { + requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, + responderPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, + cacheSummaryHash: { type: 'string', assert: Cache.isValidSummaryHashFormat }, + port: { type: 'number', assert: isValidPort }, + address: { type: 'string', assert: isIPv4 }, uptime: { type: 'number' }, cacheSize: { type: 'number' }, - natType: { type: 'number' }, + natType: { type: 'number', assert: NAT.isValid }, isReflection: { type: 'boolean' }, isConnection: { type: 'boolean' }, - reflectionId: { type: 'string' }, - pingId: { type: 'string' }, + reflectionId: { type: 'string', assert: Peer.isValidReflectionId }, + pingId: { type: 'string', assert: Peer.isValidPingId }, isDebug: { type: 'boolean' }, isProbe: { type: 'boolean' }, rejected: { type: 'boolean' } @@ -425,16 +465,16 @@ export class PacketPong extends Packet { export class PacketIntro extends Packet { static type = 3 - constructor ({ clock, hops, clusterId, subclusterId, usr1, message }) { - super({ type: PacketIntro.type, clock, hops, clusterId, subclusterId, usr1, message }) + constructor (args) { + super({ ...args, type: PacketIntro.type }) - validatePacket(message, { - requesterPeerId: { required: true, type: 'string' }, - responderPeerId: { required: true, type: 'string' }, + validateMessage(args.message, { + requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, + responderPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, isRendezvous: { type: 'boolean' }, - natType: { required: true, type: 'number' }, - address: { required: true, type: 'string' }, - port: { required: true, type: 'number' }, + natType: { required: true, type: 'number', assert: NAT.isValid }, + address: { required: true, type: 'string', assert: isIPv4 }, + port: { required: true, type: 'number', assert: isValidPort }, timestamp: { type: 'number' } }) } @@ -442,52 +482,53 @@ export class PacketIntro extends Packet { export class PacketJoin extends Packet { static type = 4 - constructor ({ clock, hops, clusterId, subclusterId, message }) { - super({ type: PacketJoin.type, clock, hops, clusterId, subclusterId, message }) - - validatePacket(message, { - rendezvousAddress: { type: 'string' }, - rendezvousPort: { type: 'number' }, - rendezvousType: { type: 'number' }, - rendezvousPeerId: { type: 'string' }, + constructor (args) { + super({ ...args, type: PacketJoin.type }) + + validateMessage(args.message, { + rendezvousAddress: { type: 'string', assert: isIPv4 }, + rendezvousPort: { type: 'number', assert: isValidPort }, + rendezvousType: { type: 'number', assert: NAT.isValid }, + rendezvousPeerId: { type: 'string', assert: Peer.isValidPeerId }, rendezvousDeadline: { type: 'number' }, - rendezvousRequesterPeerId: { type: 'string' }, - requesterPeerId: { required: true, type: 'string' }, - natType: { required: true, type: 'number' }, - initial: { type: 'boolean' }, - address: { required: true, type: 'string' }, - port: { required: true, type: 'number' }, + rendezvousRequesterPeerId: { type: 'string', assert: Peer.isValidPeerId }, + requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, + natType: { required: true, type: 'number', assert: NAT.isValid }, + address: { required: true, type: 'string', assert: isIPv4 }, + port: { required: true, type: 'number', assert: isValidPort }, isConnection: { type: 'boolean' } }) } } export class PacketPublish extends Packet { - static type = 5 // no need to validatePacket, message is whatever you want - constructor ({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }) { - super({ type: PacketPublish.type, message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }) + static type = 5 // no need to validateMessage, message is whatever you want + constructor (args) { + super({ ...args, type: PacketPublish.type }) } } export class PacketStream extends Packet { static type = 6 - constructor ({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }) { - super({ type: PacketStream.type, message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }) + constructor (args) { + super({ ...args, type: PacketStream.type }) } } export class PacketSync extends Packet { static type = 7 - constructor ({ packetId, message = Buffer.from([0b0]) }) { - super({ type: PacketSync.type, packetId, message }) + constructor (args) { + super({ message: Buffer.from([0b0]), ...args, type: PacketSync.type }) } } export class PacketQuery extends Packet { static type = 8 - constructor ({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message = {} }) { - super({ type: PacketQuery.type, packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }) + constructor (args) { + super({ message: {}, ...args, type: PacketQuery.type }) } } export default Packet + +const isValidPort = (n) => typeof n === 'number' && (n & 0xFFFF) === n && n !== 0 diff --git a/api/latica/proxy.js b/api/latica/proxy.js index 92e740f1ab..bab8ee7e5a 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -262,7 +262,7 @@ class PeerWorkerProxy { const arg = args[i] if (arg?.constructor.name === 'RemotePeer' || arg?.constructor.name === 'Peer') { - args[i] = { // what details we want to expose outside of the protocol + args[i] = { // what details we want to expose outside of the protocol peerId: arg.peerId, address: arg.address, port: arg.port, From 0b9cb74610faf9b94ec1aa4d0ea9042ab29529d8 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Wed, 17 Jul 2024 17:10:29 +0200 Subject: [PATCH 0986/1178] chore(api): update protocol to improve logging --- api/latica/api.js | 1 - api/latica/cache.js | 2 +- api/latica/index.js | 40 +++++++++++++++++++++++++--------------- api/latica/packets.js | 11 +++++------ 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index 578a64c8a3..26728f2d5f 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -68,7 +68,6 @@ async function api (options = {}, events, dgram) { _peer.onSync = (...args) => bus._emit('#sync', ...args) _peer.onSyncStart = (...args) => bus._emit('#sync-start', ...args) _peer.onSyncEnd = (...args) => bus._emit('#sync-end', ...args) - _peer.onConnection = (...args) => bus._emit('#connection', ...args) _peer.onDisconnection = (...args) => bus._emit('#disconnection', ...args) _peer.onQuery = (...args) => bus._emit('#query', ...args) _peer.onNat = (...args) => bus._emit('#network-change', ...args) diff --git a/api/latica/cache.js b/api/latica/cache.js index f8773a7121..fd44690a3a 100644 --- a/api/latica/cache.js +++ b/api/latica/cache.js @@ -191,7 +191,7 @@ export class Cache { // follow the chain to get the buffers in order let bufs = [...source.values()].filter(p => { - if (!p.previousId) return + if (!p.previousId) return false return Buffer.from(p.previousId).compare(Buffer.from(previous.packetId)) === 0 }) diff --git a/api/latica/index.js b/api/latica/index.js index 9b83c710bf..cdad3fb70e 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -119,7 +119,7 @@ export class RemotePeer { peerId = null address = null port = 0 - natType = NAT.UNKNOWN + natType = null clusters = {} pingId = null distance = 0 @@ -484,7 +484,7 @@ export class Peer { const expired = (peer.lastUpdate + this.config.keepalive) < Date.now() if (expired) { // || !NAT.isValid(peer.natType)) { const p = this.peers.splice(i, 1) - if (this.onDisconnect) this.onDisconnect(p) + if (this.onDisconnection) this.onDisconnection(p) continue } } @@ -1168,7 +1168,6 @@ export class Peer { * @ignore */ async _onConnection (packet, peerId, port, address, proxy, socket) { - this._onDebug('<- CONNECTION') if (this.closing) return const natType = packet.message.natType @@ -1176,7 +1175,8 @@ export class Peer { if (!Peer.isValidPeerId(peerId)) return if (peerId === this.peerId) return - const { clusterId, subclusterId } = packet + const cid = packet.clusterId.toString('base64') + const scid = packet.subclusterId.toString('base64') let peer = this.getPeer(peerId) const firstContact = !peer @@ -1190,23 +1190,19 @@ export class Peer { if (oldPeerIndex > -1) this.peers.splice(oldPeerIndex, 1) } - this._onDebug(`<- CONNECTION ADDING PEER ${peer.peerId}@${address}:${port}`) - + this._onDebug(`<- CONNECTION ADDING PEER (id=${peer.peerId}, address=${address}:${port})`) this.peers.push(peer) } - this._onDebug(`<- CONNECTION AF BRA ${peer.peerId}@${address}:${port}`) peer.connected = true peer.lastUpdate = Date.now() peer.port = port peer.natType = natType peer.address = address + if (proxy) peer.proxy = proxy if (socket) peer.socket = socket - const cid = Buffer.from(clusterId).toString('base64') - const scid = Buffer.from(subclusterId).toString('base64') - if (cid) peer.clusters[cid] ??= {} if (cid && scid) { @@ -1438,6 +1434,7 @@ export class Peer { } if (isConnection && natType) { + this._onDebug(`<- CONNECTION (source=ping)`) this._onConnection(packet, requesterPeerId, port, address) message.isConnection = true @@ -1481,6 +1478,7 @@ export class Peer { if (packet.message.isConnection) { if (pingId) peer.pingId = pingId + this._onDebug(`<- CONNECTION (source=pong)`) this._onConnection(packet, responderPeerId, port, address) return } @@ -1547,7 +1545,7 @@ export class Peer { this._onDebug(`-> PING (to=${peer.address}:${peer.port}, peer-id=${peer.peerId.slice(0, 8)}, is-connection=true)`) - await this.ping(peer, false, { + this.ping(peer, false, { message: { requesterPeerId: this.peerId, natType: this.natType, @@ -1557,6 +1555,8 @@ export class Peer { }) } + this._mainLoop(Date.now()) + if (this.onNat) this.onNat(this.natType) this._onDebug(`++ NAT (type=${NAT.toString(this.natType)})`) @@ -1656,6 +1656,7 @@ export class Peer { const proxyCandidate = this.peers.find(p => p.peerId === packet.message.responderPeerId) if (opts.attempts >= 2) { + this._onDebug(`<- CONNECTION (source=intro)`) this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) return false } @@ -1667,10 +1668,10 @@ export class Peer { }, 1024 * 2) if (packet.message.isRendezvous) { - this._onDebug(`<- INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) + this._onDebug(`<- JOIN INTRO FROM RENDEZVOUS (to=${packet.message.address}:${packet.message.port}, dest=${packet.message.requesterPeerId.slice(0, 6)}, via=${address}:${port}, strategy=${NAT.toStringStrategy(strategy)})`) } - this._onDebug(`++ NAT INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) + this._onDebug(`++ JOIN INTRO (strategy=${NAT.toStringStrategy(strategy)}, from=${this.address}:${this.port} [${NAT.toString(this.natType)}], to=${packet.message.address}:${packet.message.port} [${NAT.toString(packet.message.natType)}])`) if (strategy === NAT.STRATEGY_TRAVERSAL_CONNECT) { this._onDebug(`## NAT CONNECT (from=${this.address}:${this.port}, to=${peerAddress}:${peerPort}, pingId=${pingId})`) @@ -1740,6 +1741,7 @@ export class Peer { this._onMessage(msg, rinfo) }) + this._onDebug(`<- CONNECTION (source=intro)`) this._onConnection(packet, peer.peerId, rinfo.port, rinfo.address, undefined, pooledSocket) const p = { @@ -1776,6 +1778,7 @@ export class Peer { if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { // TODO could allow multiple proxies + this._onDebug(`<- CONNECTION (source=proxy)`) this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) this._onDebug('++ INTRO CHOSE PROXY STRATEGY') } @@ -1866,7 +1869,7 @@ export class Peer { // TODO it would tighten up the transition time between dropped peers // if we check strategy from (packet.message.natType, this.natType) and // make introductions that create more mutually known peers. - this._onDebug(`<- JOIN RENDEZVOUS START (to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) + this._onDebug(`<- JOIN RENDEZVOUS RECV (dest=${packet.message.requesterPeerId?.slice(0, 6)}, to=${peerAddress}:${peerPort}, via=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) const data = await Packet.encode(new PacketJoin({ clock: packet.clock, @@ -1887,6 +1890,8 @@ export class Peer { packet.message.rendezvousPort, packet.message.rendezvousAddress ) + + this._onDebug(`-> JOIN RENDEZVOUS SEND ( to=${packet.message.rendezvousAddress}:${packet.message.rendezvousPort})`) } } @@ -1980,6 +1985,7 @@ export class Peer { this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) if (packet.hops <= 1) { + this._onDebug(`<- CONNECTION (source=join)`) this._onConnection(packet, packet.message.requesterPeerId, port, address) } } @@ -2114,9 +2120,13 @@ export class Peer { } this._onDebug(`>> STREAM RELAY (to=${peerTo.address}:${peerTo.port}, id=${peerTo.peerId.slice(0, 6)})`) + // I am the proxy! this.send(await Packet.encode(packet), peerTo.port, peerTo.address) - // Might be interesting to allow retransmission in some cases + // + // What % of packets hit the server. + // + // if (packet.hops === 1 && this.natType === NAT.UNRESTRICTED) { // this.mcast(packet, [{ port, address }, { port: peerFrom.port, address: peerFrom.address }]) // } diff --git a/api/latica/packets.js b/api/latica/packets.js index 87458adb48..50008b963d 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -1,7 +1,6 @@ import { randomBytes } from '../crypto.js' import { isBufferLike } from '../util.js' import { Buffer } from '../buffer.js' -import { isIPv4 } from '../ip.js' import Peer, { Cache, NAT } from './index.js' /** @@ -447,8 +446,8 @@ export class PacketPong extends Packet { requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, responderPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, cacheSummaryHash: { type: 'string', assert: Cache.isValidSummaryHashFormat }, - port: { type: 'number', assert: isValidPort }, - address: { type: 'string', assert: isIPv4 }, + port: { type: 'number' }, + address: { type: 'string' }, uptime: { type: 'number' }, cacheSize: { type: 'number' }, natType: { type: 'number', assert: NAT.isValid }, @@ -473,7 +472,7 @@ export class PacketIntro extends Packet { responderPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, isRendezvous: { type: 'boolean' }, natType: { required: true, type: 'number', assert: NAT.isValid }, - address: { required: true, type: 'string', assert: isIPv4 }, + address: { required: true, type: 'string' }, port: { required: true, type: 'number', assert: isValidPort }, timestamp: { type: 'number' } }) @@ -486,7 +485,7 @@ export class PacketJoin extends Packet { super({ ...args, type: PacketJoin.type }) validateMessage(args.message, { - rendezvousAddress: { type: 'string', assert: isIPv4 }, + rendezvousAddress: { type: 'string' }, rendezvousPort: { type: 'number', assert: isValidPort }, rendezvousType: { type: 'number', assert: NAT.isValid }, rendezvousPeerId: { type: 'string', assert: Peer.isValidPeerId }, @@ -494,7 +493,7 @@ export class PacketJoin extends Packet { rendezvousRequesterPeerId: { type: 'string', assert: Peer.isValidPeerId }, requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, natType: { required: true, type: 'number', assert: NAT.isValid }, - address: { required: true, type: 'string', assert: isIPv4 }, + address: { required: true, type: 'string' }, port: { required: true, type: 'number', assert: isValidPort }, isConnection: { type: 'boolean' } }) From 8c35d17ad47d4a7f9a13a45ed5f0fd642f134017 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 18 Jul 2024 10:34:39 +0200 Subject: [PATCH 0987/1178] chore(api): update protocol to allow specifying ranges in sync --- api/latica/api.js | 5 +++-- api/latica/index.js | 13 ++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index 26728f2d5f..22108cbe50 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -356,7 +356,8 @@ async function api (options = {}, events, dgram) { ee._peer = peer sub.peers.set(peer.peerId, ee) - if (!oldPeer || change) sub._emit('#join', ee, packet) + const isStateChange = !oldPeer || change + sub._emit('#join', ee, packet, isStateChange) }) const handlePacket = async (packet, peer, port, address) => { @@ -378,7 +379,7 @@ async function api (options = {}, events, dgram) { bus._on('#packet', handlePacket) bus._on('#disconnection', peer => { - for (const sub of bus.subclusters) { + for (const sub of [...bus.subclusters.values()]) { sub._emit('#leave', peer) sub.peers.delete(peer.peerId) } diff --git a/api/latica/index.js b/api/latica/index.js index cdad3fb70e..eeba6c8490 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -1106,7 +1106,8 @@ export class Peer { // if we are out of sync send our cache summary const data = await Packet.encode(new PacketSync({ - message: Cache.encodeSummary(summary) + message: Cache.encodeSummary(summary), + usr4: Buffer.from(String(Date.now())) })) this.send(data, rinfo.port, rinfo.address, peer.socket) @@ -1157,7 +1158,12 @@ export class Peer { * */ cachePredicate (packet) { - return packet.version === VERSION && packet.timestamp > Date.now() - Packet.ttl + if (packet.usr4.byteLength < 8 || packet.usr4.byteLength > 16) return + + const timestamp = parseInt(Buffer.from(packet.usr4).toString(), 10) + const ts = Math.min(Packet.ttl, timestamp) + + return packet.version === VERSION && ts > Date.now() - Packet.ttl } /** @@ -1285,7 +1291,8 @@ export class Peer { // const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate) const data = await Packet.encode(new PacketSync({ - message: Cache.encodeSummary(nextLevel) + message: Cache.encodeSummary(nextLevel), + usr4: Buffer.from(String(Date.now())) })) this.send(data, port, address) } From 4a46b8fb97321ac31add43fb9e22f9e584ac55d8 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 18 Jul 2024 10:41:59 +0200 Subject: [PATCH 0988/1178] chore(api): lint --- api/latica/index.js | 14 +++++++------- api/latica/proxy.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index eeba6c8490..e230247ee2 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -962,7 +962,7 @@ export class Peer { const siblings = packet && [...this.cache.data.values()] .filter(Boolean) .filter(p => { - if (!p.previousId || !packet.packetId) return + if (!p.previousId || !packet.packetId) return false return Buffer.from(p.previousId).compare(Buffer.from(packet.packetId)) === 0 }) @@ -1441,7 +1441,7 @@ export class Peer { } if (isConnection && natType) { - this._onDebug(`<- CONNECTION (source=ping)`) + this._onDebug('<- CONNECTION (source=ping)') this._onConnection(packet, requesterPeerId, port, address) message.isConnection = true @@ -1485,7 +1485,7 @@ export class Peer { if (packet.message.isConnection) { if (pingId) peer.pingId = pingId - this._onDebug(`<- CONNECTION (source=pong)`) + this._onDebug('<- CONNECTION (source=pong)') this._onConnection(packet, responderPeerId, port, address) return } @@ -1663,7 +1663,7 @@ export class Peer { const proxyCandidate = this.peers.find(p => p.peerId === packet.message.responderPeerId) if (opts.attempts >= 2) { - this._onDebug(`<- CONNECTION (source=intro)`) + this._onDebug('<- CONNECTION (source=intro)') this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) return false } @@ -1748,7 +1748,7 @@ export class Peer { this._onMessage(msg, rinfo) }) - this._onDebug(`<- CONNECTION (source=intro)`) + this._onDebug('<- CONNECTION (source=intro)') this._onConnection(packet, peer.peerId, rinfo.port, rinfo.address, undefined, pooledSocket) const p = { @@ -1785,7 +1785,7 @@ export class Peer { if (strategy === NAT.STRATEGY_PROXY && !peer.proxy) { // TODO could allow multiple proxies - this._onDebug(`<- CONNECTION (source=proxy)`) + this._onDebug('<- CONNECTION (source=proxy)') this._onConnection(packet, peer.peerId, peerPort, peerAddress, proxyCandidate) this._onDebug('++ INTRO CHOSE PROXY STRATEGY') } @@ -1992,7 +1992,7 @@ export class Peer { this.mcast(packet, [{ port, address }, { port: peerPort, address: peerAddress }]) if (packet.hops <= 1) { - this._onDebug(`<- CONNECTION (source=join)`) + this._onDebug('<- CONNECTION (source=join)') this._onConnection(packet, packet.message.requesterPeerId, port, address) } } diff --git a/api/latica/proxy.js b/api/latica/proxy.js index bab8ee7e5a..92e740f1ab 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -262,7 +262,7 @@ class PeerWorkerProxy { const arg = args[i] if (arg?.constructor.name === 'RemotePeer' || arg?.constructor.name === 'Peer') { - args[i] = { // what details we want to expose outside of the protocol + args[i] = { // what details we want to expose outside of the protocol peerId: arg.peerId, address: arg.address, port: arg.port, From 170cbae43766e10777ec5e50bc14c2cad6980522 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 09:41:05 +0100 Subject: [PATCH 0989/1178] fix(ipc/preload.cc): ensure correct values in webview from preload --- src/ipc/preload.cc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 0ce0d2b96a..83495a001c 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -298,7 +298,31 @@ namespace SSC::IPC { buffers.push_back(R"JAVASCRIPT( for (const key in __RAW_CONFIG__) { let value = __RAW_CONFIG__[key] + try { value = decodeURIComponent(value) } catch {} + + if (value === 'true') { + value = true + } else if (value === 'false') { + value = false + } else if (value === 'null') { + value = null + } else if (value === 'NaN') { + value = Number.NaN + } else if (value.startsWith('0x')) { + const parsed = parseInt(value.slice(2), 16) + if (!Number.isNaN(parsed)) { + value = parsed + } + } else { + const parsed = parseFloat(value) + if (!Number.isNaN(parsed)) { + value = parsed + } + } + + try { value = JSON.parse(value) } catch {} + globalThis.__args.config[key] = value if (key.startsWith('env_')) { globalThis.__args.env[key.slice(4)] = value From 306cfc422c064b4c02e61dd14e65425c99710c79 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 09:42:00 +0100 Subject: [PATCH 0990/1178] refactor(core/modules/conduit): introduce 'isActive()', msleep after bind --- src/core/modules/conduit.cc | 14 ++++++++------ src/core/modules/conduit.hh | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index ac144b216e..660ceb8ce2 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -359,7 +359,7 @@ namespace SSC { } void CoreConduit::start (const Function<void()>& callback) { - if (this->isStarted) { + if (this->isActive()) { if (callback != nullptr) { callback(); } @@ -378,8 +378,6 @@ namespace SSC { uv_tcp_getsockname(&this->socket, (struct sockaddr *)&sockname, &namelen); this->port = ntohs(sockname.sin_port); - this->isStarted = true; - this->core->dispatchEventLoop([=, this]() mutable { int r = uv_listen((uv_stream_t*)&this->socket, 128, [](uv_stream_t *stream, int status) { if (status < 0) { @@ -439,22 +437,21 @@ namespace SSC { }); if (r) { - this->isStarted = false; // debug("Listen error %s\n", uv_strerror(r)); } if (callback != nullptr) { + msleep(256); callback(); } }); } void CoreConduit::stop () { - if (!this->isStarted) { + if (!this->isActive()) { return; } - this->isStarted = false; this->core->dispatchEventLoop([this]() mutable { if (!uv_is_closing((uv_handle_t*)&this->socket)) { uv_close((uv_handle_t*)&this->socket, [](uv_handle_t* handle) { @@ -483,4 +480,9 @@ namespace SSC { this->clients.clear(); }); } + + bool CoreConduit::isActive () { + Lock lock(this->mutex); + return this->port > 0 && uv_is_active(reinterpret_cast<uv_handle_t*>(&this->socket)); + } } diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 238ee3fa99..851ea987ea 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -97,7 +97,6 @@ namespace SSC { // state std::map<uint64_t, Client*> clients; Mutex mutex; - Atomic<bool> isStarted = false; Atomic<int> port = 0; CoreConduit (Core* core) : CoreModule(core) {}; @@ -117,6 +116,7 @@ namespace SSC { // lifecycle void start (const Function<void()>& callback = nullptr); void stop (); + bool isActive (); private: uv_tcp_t socket; From 9496128e5663c98bab361a35d83403f4ad1d56cf Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 09:42:29 +0100 Subject: [PATCH 0991/1178] refactor(core/modules/diagnostics): improve 'conduit' diagnostics --- src/core/modules/diagnostics.cc | 4 +++- src/core/modules/diagnostics.hh | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/modules/diagnostics.cc b/src/core/modules/diagnostics.cc index bb211e4006..0a784305cf 100644 --- a/src/core/modules/diagnostics.cc +++ b/src/core/modules/diagnostics.cc @@ -93,6 +93,7 @@ namespace SSC { do { Lock lock(this->core->conduit.mutex); query.conduit.handles.count = this->core->conduit.clients.size(); + query.conduit.isActive = this->core->conduit.isActive(); for (const auto& entry : this->core->conduit.clients) { query.conduit.handles.ids.push_back(entry.first); } @@ -215,7 +216,8 @@ namespace SSC { JSON::Object CoreDiagnostics::ConduitDiagnostic::json () const { return JSON::Object::Entries { - {"handles", this->handles.json()} + {"handles", this->handles.json()}, + {"isActive", this->isActive} }; } diff --git a/src/core/modules/diagnostics.hh b/src/core/modules/diagnostics.hh index adc1a2ed8d..0e63cd9ef2 100644 --- a/src/core/modules/diagnostics.hh +++ b/src/core/modules/diagnostics.hh @@ -94,6 +94,7 @@ namespace SSC { struct ConduitDiagnostic : public Diagnostic { Handles handles; + bool isActive; JSON::Object json () const override; }; From 19376a2695bc9162b4dbf6afdd93512f05bd6505 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 09:42:54 +0100 Subject: [PATCH 0992/1178] refactor(ipc/routes): include 'active' status in 'internal.conduit.start' --- src/ipc/routes.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index a35bee5d65..735e1c9f4d 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1660,7 +1660,7 @@ static void mapIPCRoutes (Router *router) { */ router->map("internal.conduit.start", [=](auto message, auto router, auto reply) { router->bridge->core->conduit.start([=]() { - if (router->bridge->core->conduit.isStarted) { + if (router->bridge->core->conduit.isActive()) { reply(Result::Data { message, JSON::Object::Entries { @@ -1690,7 +1690,7 @@ static void mapIPCRoutes (Router *router) { reply(Result::Data { message, JSON::Object::Entries { - {"started", router->bridge->core->conduit.isStarted.load()}, + {"started", router->bridge->core->conduit.isActive()}, {"port", router->bridge->core->conduit.port.load()} } }); From 47b3de1f58481d2d4d4717258bfb44417992eada Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 18 Jul 2024 10:43:13 +0200 Subject: [PATCH 0993/1178] chore(api): generate docs --- api/README.md | 540 +++---- api/index.d.ts | 3992 ++++++++++++++++++++++++------------------------ 2 files changed, 2250 insertions(+), 2282 deletions(-) diff --git a/api/README.md b/api/README.md index 25a8707f3c..eaaa18429f 100644 --- a/api/README.md +++ b/api/README.md @@ -15,7 +15,7 @@ External docs: https://nodejs.org/api/events.html <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [application](https://github.com/socketsupply/socket/blob/master/api/application.js#L13) +# [application](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L13) Provides Application level methods @@ -25,17 +25,17 @@ External docs: https://nodejs.org/api/events.html import { createWindow } from 'socket:application' ``` -## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/master/api/application.js#L38) +## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L38) This is a `VariableDeclaration` named `MAX_WINDOWS` in `api/application.js`, it's exported but undocumented. -## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/master/api/application.js#L40) +## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L40) This is a `ClassDeclaration` named `ApplicationWindowList` in `api/application.js`, it's exported but undocumented. -## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L165) +## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L165) Returns the current window index @@ -43,7 +43,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L200) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L200) Creates a new window and returns an instance of ApplicationWindow. @@ -81,15 +81,15 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -### [`radius()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L227) +### [`radius()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L227) -### [`margin()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L232) +### [`margin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L232) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L320) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L320) Returns the current screen size. @@ -97,7 +97,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L351) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L351) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -109,7 +109,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindowList> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L405) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L405) Returns the ApplicationWindow instance for the given index @@ -121,7 +121,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L415) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L415) Returns the ApplicationWindow instance for the current window. @@ -129,7 +129,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L424) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L424) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -141,7 +141,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L521) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L521) Set the native menu for the app. @@ -236,11 +236,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L528) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L528) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L537) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L537) Set the enabled state of the system menu. @@ -252,23 +252,23 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/master/api/application.js#L545) +## [runtimeVersion](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L545) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/master/api/application.js#L551) +## [debug](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L551) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/master/api/application.js#L557) +## [config](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L557) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/master/api/application.js#L562) +## [backend](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L562) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/master/api/application.js#L568) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L568) @@ -281,7 +281,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/application.js#L576) +### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L576) @@ -293,7 +293,7 @@ The application's backend instance. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [bluetooth](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L12) +# [bluetooth](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L12) A high-level, cross-platform API for Bluetooth Pub-Sub @@ -303,11 +303,11 @@ The application's backend instance. import { Bluetooth } from 'socket:bluetooth' ``` -## [`Bluetooth` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L32) +## [`Bluetooth` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L32) Create an instance of a Bluetooth service. -### [`constructor(serviceId)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L40) +### [`constructor(serviceId)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L40) constructor is an example property that is set to `true` Creates a new service with key-value pairs @@ -316,7 +316,7 @@ constructor is an example property that is set to `true` | :--- | :--- | :---: | :---: | :--- | | serviceId | string | | false | Given a default value to determine the type | -### [`start()`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L90) +### [`start()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L90) Start the Bluetooth service. @@ -324,7 +324,7 @@ Start the Bluetooth service. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`subscribe(id)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L119) +### [`subscribe(id)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L119) Start scanning for published values that correspond to a well-known UUID. Once subscribed to a UUID, events that correspond to that UUID will be @@ -347,7 +347,7 @@ Start scanning for published values that correspond to a well-known UUID. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`publish(id, value)`](https://github.com/socketsupply/socket/blob/master/api/bluetooth.js#L142) +### [`publish(id, value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/bluetooth.js#L142) Start advertising a new value for a well-known UUID @@ -364,7 +364,7 @@ Start advertising a new value for a well-known UUID <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [crypto](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L15) +# [crypto](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L15) Some high-level methods around the `crypto.subtle` API for getting @@ -375,16 +375,16 @@ Start advertising a new value for a well-known UUID import { randomBytes } from 'socket:crypto' ``` -## [webcrypto](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L30) +## [webcrypto](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L30) External docs: https://developer.mozilla.org/en-US/docs/Web/API/Crypto WebCrypto API -## [ready](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L59) +## [ready](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L59) A promise that resolves when all internals to be loaded/ready. -## [`getRandomValues(buffer)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L74) +## [`getRandomValues(buffer)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L74) External docs: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues Generate cryptographically strong random values into the `buffer` @@ -397,7 +397,7 @@ Generate cryptographically strong random values into the `buffer` | :--- | :--- | :--- | | Not specified | TypedArray | | -## [`rand64()`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L97) +## [`rand64()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L97) Generate a random 64-bit number. @@ -405,19 +405,19 @@ Generate a random 64-bit number. | :--- | :--- | :--- | | A random 64-bit number. | BigInt | | -## [RANDOM_BYTES_QUOTA](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L105) +## [RANDOM_BYTES_QUOTA](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L105) Maximum total size of random bytes per page -## [MAX_RANDOM_BYTES](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L110) +## [MAX_RANDOM_BYTES](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L110) Maximum total size for random bytes. -## [MAX_RANDOM_BYTES_PAGES](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L115) +## [MAX_RANDOM_BYTES_PAGES](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L115) Maximum total amount of allocated per page of bytes (max/quota) -## [`randomBytes(size)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L123) +## [`randomBytes(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L123) Generate `size` random bytes. @@ -429,7 +429,7 @@ Generate `size` random bytes. | :--- | :--- | :--- | | Not specified | Buffer | A promise that resolves with an instance of socket.Buffer with random bytes. | -## [`createDigest(algorithm, message)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L150) +## [`createDigest(algorithm, message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L150) @@ -442,7 +442,7 @@ Generate `size` random bytes. | :--- | :--- | :--- | | Not specified | Promise<Buffer> | A promise that resolves with an instance of socket.Buffer with the hash. | -## [`murmur3(value, seed)`](https://github.com/socketsupply/socket/blob/master/api/crypto.js#L161) +## [`murmur3(value, seed)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/crypto.js#L161) A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c that works on strings and `ArrayBuffer` views (typed arrays) @@ -460,7 +460,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [dgram](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L13) +# [dgram](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L13) This module provides an implementation of UDP datagram sockets. It does @@ -471,7 +471,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L651) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L651) Creates a `Socket` instance. @@ -490,12 +490,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L657) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L657) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L738) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L738) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -512,7 +512,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L853) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L853) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -532,7 +532,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L890) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L890) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -540,7 +540,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L949) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L949) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -591,7 +591,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1036) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1036) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -603,7 +603,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1108) +### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1108) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -619,7 +619,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1143) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1143) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -634,7 +634,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1174) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1174) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -645,7 +645,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1191) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1191) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -656,12 +656,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1204) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1204) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1212) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1212) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -670,31 +670,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1280) +### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1280) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1286) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1286) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1303) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1303) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1310) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1310) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1318) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1318) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1325) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1325) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/master/api/dgram.js#L1335) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1335) Thrown when a bad port is given. @@ -702,7 +702,7 @@ Thrown when a bad port is given. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [dns](https://github.com/socketsupply/socket/blob/master/api/dns/index.js#L17) +# [dns](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/index.js#L17) This module enables name resolution. For example, use it to look up IP @@ -717,7 +717,7 @@ Thrown when a bad port is given. import { lookup } from 'socket:dns' ``` -## [`lookup(hostname, options, cb)`](https://github.com/socketsupply/socket/blob/master/api/dns/index.js#L60) +## [`lookup(hostname, options, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/index.js#L60) External docs: https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or @@ -753,7 +753,7 @@ Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [dns.promises](https://github.com/socketsupply/socket/blob/master/api/dns/promises.js#L17) +# [dns.promises](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/promises.js#L17) This module enables name resolution. For example, use it to look up IP @@ -768,7 +768,7 @@ Resolves a host name (e.g. `example.org`) into the first found A (IPv4) or import { lookup } from 'socket:dns/promises' ``` -## [`lookup(hostname, opts)`](https://github.com/socketsupply/socket/blob/master/api/dns/promises.js#L37) +## [`lookup(hostname, opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dns/promises.js#L37) External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options @@ -787,7 +787,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [fs](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L26) +# [fs](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L26) This module enables interacting with the file system in a way modeled on @@ -811,7 +811,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L109) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L109) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -823,7 +823,7 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L134) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L134) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` @@ -834,7 +834,7 @@ Synchronously check access a file for a given mode calling `callback` | path | string \| Buffer \| URL | | false | | | mode | string? | F_OK(0) | true | | -## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L151) +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L151) Checks if a path exists @@ -843,7 +843,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L168) +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L168) Checks if a path exists @@ -852,7 +852,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L188) +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L188) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -866,7 +866,7 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L214) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L214) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Synchronously changes the permissions of a file. @@ -877,7 +877,7 @@ Synchronously changes the permissions of a file. | path | string \| Buffer \| URL | | false | | | mode | number | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L238) +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L238) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -888,7 +888,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L267) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L267) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -898,7 +898,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | uid | number | | false | | | gid | number | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L294) +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L294) External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -908,7 +908,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L314) +## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L314) Synchronously close a file descriptor. @@ -916,7 +916,7 @@ Synchronously close a file descriptor. | :--- | :--- | :---: | :---: | :--- | | fd | number | | false | fd | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L331) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L331) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -928,7 +928,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L363) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L363) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -939,7 +939,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L392) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L392) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -953,7 +953,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L437) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L437) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -967,7 +967,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L485) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L485) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -981,7 +981,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L512) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L512) Request that all data for the open file descriptor is flushed to the storage device. @@ -991,7 +991,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L534) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L534) Truncates the file up to `offset` bytes. @@ -1001,7 +1001,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L562) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L562) Chages ownership of link at `path` with `uid` and `gid. @@ -1012,7 +1012,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L592) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L592) Creates a link to `dest` from `src`. @@ -1022,7 +1022,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L680) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L680) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1035,7 +1035,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L733) +## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L733) Synchronously open a file. @@ -1046,7 +1046,7 @@ Synchronously open a file. | mode | string? | 0o666 | true | | | options | object? \| function? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L780) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L780) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1059,7 +1059,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L807) +## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L807) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously open a directory. @@ -1075,7 +1075,7 @@ Synchronously open a directory. | :--- | :--- | :--- | | Not specified | Dir | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L834) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L834) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1089,7 +1089,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L869) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L869) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1103,7 +1103,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L903) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L903) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1116,7 +1116,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L955) +## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L955) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously read all entries in a directory. @@ -1128,7 +1128,7 @@ Synchronously read all entries in a directory. | options.encoding ? utf8 | string? | | true | | | options.withFileTypes ? false | boolean? | | true | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L985) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L985) @@ -1141,7 +1141,7 @@ Synchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1027) +## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1027) @@ -1152,7 +1152,7 @@ Synchronously read all entries in a directory. | options | object? \| function(Error?, Buffer?) | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1090) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1090) Reads link at `path` @@ -1161,7 +1161,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1110) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1110) Computes real path for `path` @@ -1170,7 +1170,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1128) +## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1128) Computes real path for `path` @@ -1178,7 +1178,7 @@ Computes real path for `path` | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1146) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1146) Renames file or directory at `src` to `dest`. @@ -1188,7 +1188,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1172) +## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1172) Renames file or directory at `src` to `dest`, synchronously. @@ -1197,7 +1197,7 @@ Renames file or directory at `src` to `dest`, synchronously. | src | string | | false | | | dest | string | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1196) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1196) Removes directory at `path`. @@ -1206,7 +1206,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1216) +## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1216) Removes directory at `path`, synchronously. @@ -1214,7 +1214,7 @@ Removes directory at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1237) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1237) Synchronously get the stats of a file @@ -1225,7 +1225,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1257) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1257) Get the stats of a file @@ -1238,7 +1238,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1295) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1295) Get the stats of a symbolic link @@ -1251,7 +1251,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1329) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1329) Creates a symlink of `src` at `dest`. @@ -1260,7 +1260,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1372) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1372) Unlinks (removes) file at `path`. @@ -1269,7 +1269,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1392) +## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1392) Unlinks (removes) file at `path`, synchronously. @@ -1277,7 +1277,7 @@ Unlinks (removes) file at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1417) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1417) @@ -1292,7 +1292,7 @@ Unlinks (removes) file at `path`, synchronously. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1462) +## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1462) External docs: https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options Writes data to a file synchronously. @@ -1307,7 +1307,7 @@ Writes data to a file synchronously. | options.flag ? w | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/master/api/fs/index.js#L1497) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1497) Watch for changes at `path` calling `callback` @@ -1326,7 +1326,7 @@ Watch for changes at `path` calling `callback` <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [fs.promises](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L25) +# [fs.promises](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L25) * This module enables interacting with the file system in a way modeled on @@ -1350,7 +1350,7 @@ Watch for changes at `path` calling `callback` import fs from 'socket:fs/promises' ``` -## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L107) +## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L107) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode Asynchronously check access a file. @@ -1361,7 +1361,7 @@ Asynchronously check access a file. | mode | string? | | true | | | options | object? | | true | | -## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L118) +## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L118) External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode @@ -1375,7 +1375,7 @@ External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L143) +## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L143) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -1389,7 +1389,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L172) +## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L172) Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -1403,7 +1403,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | :--- | :--- | :--- | | Not specified | Promise | | -## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L202) +## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L202) Chages ownership of link at `path` with `uid` and `gid. @@ -1417,7 +1417,7 @@ Chages ownership of link at `path` with `uid` and `gid. | :--- | :--- | :--- | | Not specified | Promise | | -## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L230) +## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L230) Creates a link to `dest` from `dest`. @@ -1430,7 +1430,7 @@ Creates a link to `dest` from `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L258) +## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L258) Asynchronously creates a directory. @@ -1446,7 +1446,7 @@ Asynchronously creates a directory. | :--- | :--- | :--- | | Not specified | Promise<any> | Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. | -## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L288) +## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L288) External docs: https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode Asynchronously open a file. @@ -1462,7 +1462,7 @@ Asynchronously open a file. | :--- | :--- | :--- | | Not specified | Promise<FileHandle> | | -## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L301) +## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L301) External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options @@ -1478,7 +1478,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options | :--- | :--- | :--- | | Not specified | Promise<Dir> | | -## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L314) +## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L314) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options @@ -1490,7 +1490,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | options.encoding | string? | utf8 | true | | | options.withFileTypes | boolean? | false | true | | -## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L352) +## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L352) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options @@ -1507,7 +1507,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | :--- | :--- | :--- | | Not specified | Promise<Buffer \| string> | | -## [`readlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L370) +## [`readlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L370) Reads link at `path` @@ -1519,7 +1519,7 @@ Reads link at `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`realpath(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L391) +## [`realpath(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L391) Computes real path for `path` @@ -1531,7 +1531,7 @@ Computes real path for `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L413) +## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L413) Renames file or directory at `src` to `dest`. @@ -1544,7 +1544,7 @@ Renames file or directory at `src` to `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L437) +## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L437) Removes directory at `path`. @@ -1556,7 +1556,7 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L459) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L459) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options Get the stats of a file @@ -1571,7 +1571,7 @@ Get the stats of a file | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L474) +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L474) External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options Get the stats of a symbolic link. @@ -1586,7 +1586,7 @@ Get the stats of a symbolic link. | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L487) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L487) Creates a symlink of `src` at `dest`. @@ -1599,7 +1599,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L523) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L523) Unlinks (removes) file at `path`. @@ -1611,7 +1611,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L548) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L548) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1630,7 +1630,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/master/api/fs/promises.js#L569) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L569) Watch for changes at `path` calling `callback` @@ -1649,7 +1649,7 @@ Watch for changes at `path` calling `callback` <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [ipc](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L37) +# [ipc](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L37) This is a low-level API that you don't need unless you are implementing @@ -1683,17 +1683,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L270) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L270) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1022) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1022) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1183) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1183) Emit event to be dispatched on `window` object. @@ -1704,7 +1704,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1242) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1242) Sends an async IPC command request with parameters. @@ -1720,27 +1720,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1693) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1693) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1725) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1725) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1774) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1774) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1776) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1776) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/master/api/ipc.js#L1981) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1981) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. @@ -1749,7 +1749,7 @@ This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [network](https://github.com/socketsupply/socket/blob/master/api/network.js#L9) +# [network](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/network.js#L9) External docs: https://socketsupply.co/guides/#p2p-guide @@ -1760,7 +1760,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [os](https://github.com/socketsupply/socket/blob/master/api/os.js#L13) +# [os](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L13) This module provides normalized system information from all the major @@ -1771,7 +1771,7 @@ External docs: https://socketsupply.co/guides/#p2p-guide import { arch, platform } from 'socket:os' ``` -## [`arch()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L60) +## [`arch()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L60) Returns the operating system CPU architecture for which Socket was compiled. @@ -1779,7 +1779,7 @@ Returns the operating system CPU architecture for which Socket was compiled. | :--- | :--- | :--- | | Not specified | string | 'arm64', 'ia32', 'x64', or 'unknown' | -## [`cpus()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L78) +## [`cpus()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L78) External docs: https://nodejs.org/api/os.html#os_os_cpus Returns an array of objects containing information about each CPU/core. @@ -1797,7 +1797,7 @@ Returns an array of objects containing information about each CPU/core. | :--- | :--- | :--- | | cpus | Array<object> | An array of objects containing information about each CPU/core. | -## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L102) +## [`networkInterfaces()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L102) External docs: https://nodejs.org/api/os.html#os_os_networkinterfaces Returns an object containing network interfaces that have been assigned a network address. @@ -1815,7 +1815,7 @@ Returns an object containing network interfaces that have been assigned a networ | :--- | :--- | :--- | | Not specified | object | An object containing network interfaces that have been assigned a network address. | -## [`platform()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L190) +## [`platform()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L190) External docs: https://nodejs.org/api/os.html#os_os_platform Returns the operating system platform. @@ -1825,7 +1825,7 @@ Returns the operating system platform. | :--- | :--- | :--- | | Not specified | string | 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' | -## [`type()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L199) +## [`type()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L199) External docs: https://nodejs.org/api/os.html#os_os_type Returns the operating system name. @@ -1834,7 +1834,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' | -## [`isWindows()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L238) +## [`isWindows()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L238) @@ -1842,7 +1842,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | boolean | `true` if the operating system is Windows. | -## [`tmpdir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L250) +## [`tmpdir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L250) @@ -1850,15 +1850,15 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system's default directory for temporary files. | -## [EOL](https://github.com/socketsupply/socket/blob/master/api/os.js#L298) +## [EOL](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L298) The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. -## [`rusage()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L310) +## [`rusage()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L310) Get resource usage. -## [`uptime()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L320) +## [`uptime()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L320) Returns the system uptime in seconds. @@ -1866,7 +1866,7 @@ Returns the system uptime in seconds. | :--- | :--- | :--- | | Not specified | number | The system uptime in seconds. | -## [`uname()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L331) +## [`uname()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L331) Returns the operating system name. @@ -1874,7 +1874,7 @@ Returns the operating system name. | :--- | :--- | :--- | | Not specified | string | The operating system name. | -## [`homedir()`](https://github.com/socketsupply/socket/blob/master/api/os.js#L389) +## [`homedir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/os.js#L389) Returns the home directory of the current user. @@ -1886,7 +1886,7 @@ Returns the home directory of the current user. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L9) +# [path](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L9) Example usage: @@ -1894,7 +1894,7 @@ Returns the home directory of the current user. import { Path } from 'socket:path' ``` -## [`resolve()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L44) +## [`resolve()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L44) External docs: https://nodejs.org/api/path.html#path_path_resolve_paths The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -1907,7 +1907,7 @@ The path.resolve() method resolves a sequence of paths or path segments into an | :--- | :--- | :--- | | Not specified | string | | -## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L82) +## [`cwd(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L82) Computes current working directory for a path @@ -1920,7 +1920,7 @@ Computes current working directory for a path | :--- | :--- | :--- | | Not specified | string | | -## [`origin()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L106) +## [`origin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L106) Computed location origin. Defaults to `socket:///` if not available. @@ -1928,7 +1928,7 @@ Computed location origin. Defaults to `socket:///` if not available. | :--- | :--- | :--- | | Not specified | string | | -## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L117) +## [`relative(options, from, to)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L117) Computes the relative path from `from` to `to`. @@ -1942,7 +1942,7 @@ Computes the relative path from `from` to `to`. | :--- | :--- | :--- | | Not specified | string | | -## [`join(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L164) +## [`join(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L164) Joins path components. This function may not return an absolute path. @@ -1955,7 +1955,7 @@ Joins path components. This function may not return an absolute path. | :--- | :--- | :--- | | Not specified | string | | -## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L221) +## [`dirname(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L221) Computes directory name of path. @@ -1968,7 +1968,7 @@ Computes directory name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L263) +## [`basename(options, components)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L263) Computes base name of path. @@ -1981,7 +1981,7 @@ Computes base name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L277) +## [`extname(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L277) Computes extension name of path. @@ -1994,7 +1994,7 @@ Computes extension name of path. | :--- | :--- | :--- | | Not specified | string | | -## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L288) +## [`normalize(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L288) Computes normalized path @@ -2007,7 +2007,7 @@ Computes normalized path | :--- | :--- | :--- | | Not specified | string | | -## [`format(options, path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L338) +## [`format(options, path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L338) Formats `Path` object into a string. @@ -2020,7 +2020,7 @@ Formats `Path` object into a string. | :--- | :--- | :--- | | Not specified | string | | -## [`parse(path)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L354) +## [`parse(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L354) Parses input `path` into a `Path` instance. @@ -2032,11 +2032,11 @@ Parses input `path` into a `Path` instance. | :--- | :--- | :--- | | Not specified | object | | -## [Path](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L382) +## [Path](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L382) A container for a parsed Path. -### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L388) +### [`from(input, cwd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L388) Creates a `Path` instance from `input` and optional `cwd`. @@ -2045,7 +2045,7 @@ Creates a `Path` instance from `input` and optional `cwd`. | input | PathComponent | | false | | | cwd | string | | true | | -### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L411) +### [`constructor(pathname, cwd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L411) `Path` class constructor. @@ -2054,47 +2054,47 @@ Creates a `Path` instance from `input` and optional `cwd`. | pathname | string | | false | | | cwd | string | Path.cwd() | true | | -### [`isRelative()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L484) +### [`isRelative()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L484) `true` if the path is relative, otherwise `false. -### [`value()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L491) +### [`value()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L491) The working value of this path. -### [`source()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L525) +### [`source()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L525) The original source, unresolved. -### [`parent()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L533) +### [`parent()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L533) Computed parent path. -### [`root()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L552) +### [`root()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L552) Computed root in path. -### [`dir()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L573) +### [`dir()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L573) Computed directory name in path. -### [`base()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L608) +### [`base()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L608) Computed base name in path. -### [`name()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L620) +### [`name()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L620) Computed base name in path without path extension. -### [`ext()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L628) +### [`ext()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L628) Computed extension name in path. -### [`drive()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L648) +### [`drive()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L648) The computed drive, if given in the path. -### [`toURL()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L655) +### [`toURL()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L655) @@ -2102,7 +2102,7 @@ The computed drive, if given in the path. | :--- | :--- | :--- | | Not specified | URL | | -### [`toString()`](https://github.com/socketsupply/socket/blob/master/api/path/path.js#L663) +### [`toString()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/path/path.js#L663) Converts this `Path` instance to a string. @@ -2114,7 +2114,7 @@ Converts this `Path` instance to a string. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [process](https://github.com/socketsupply/socket/blob/master/api/process.js#L9) +# [process](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L9) Example usage: @@ -2122,22 +2122,22 @@ Converts this `Path` instance to a string. import process from 'socket:process' ``` -## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L18) +## [`ProcessEnvironmentEvent` (extends `Event`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L18) This is a `ClassDeclaration` named ``ProcessEnvironmentEvent` (extends `Event`)` in `api/process.js`, it's exported but undocumented. -## [`ProcessEnvironment` (extends `EventTarget`)](https://github.com/socketsupply/socket/blob/master/api/process.js#L29) +## [`ProcessEnvironment` (extends `EventTarget`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L29) This is a `ClassDeclaration` named ``ProcessEnvironment` (extends `EventTarget`)` in `api/process.js`, it's exported but undocumented. -## [env](https://github.com/socketsupply/socket/blob/master/api/process.js#L35) +## [env](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L35) This is a `VariableDeclaration` named `env` in `api/process.js`, it's exported but undocumented. -## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L202) +## [`nextTick(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L202) Adds callback to the 'nextTick' queue. @@ -2145,7 +2145,7 @@ Adds callback to the 'nextTick' queue. | :--- | :--- | :---: | :---: | :--- | | callback | Function | | false | | -## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L235) +## [`hrtime(time)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L235) Computed high resolution time as a `BigInt`. @@ -2157,7 +2157,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :--- | | Not specified | bigint | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/master/api/process.js#L261) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L261) @@ -2165,7 +2165,7 @@ Computed high resolution time as a `BigInt`. | :--- | :--- | :---: | :---: | :--- | | code | number | 0 | true | The exit code. Default: 0. | -## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/master/api/process.js#L273) +## [`memoryUsage()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/process.js#L273) Returns an object describing the memory usage of the Node.js process measured in bytes. @@ -2177,7 +2177,7 @@ Returns an object describing the memory usage of the Node.js process measured in <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [test](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L17) +# [test](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L17) Provides a test runner for Socket Runtime. @@ -2191,7 +2191,7 @@ Returns an object describing the memory usage of the Node.js process measured in }) ``` -## [`getDefaultTestRunnerTimeout()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L54) +## [`getDefaultTestRunnerTimeout()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L54) @@ -2199,11 +2199,11 @@ Returns an object describing the memory usage of the Node.js process measured in | :--- | :--- | :--- | | Not specified | number | The default timeout for tests in milliseconds. | -## [Test](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L69) +## [Test](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L69) -### [`constructor(name, fn, runner)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L127) +### [`constructor(name, fn, runner)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L127) @@ -2213,7 +2213,7 @@ Returns an object describing the memory usage of the Node.js process measured in | fn | TestFn | | false | | | runner | TestRunner | | false | | -### [`comment(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L138) +### [`comment(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L138) @@ -2221,7 +2221,7 @@ Returns an object describing the memory usage of the Node.js process measured in | :--- | :--- | :---: | :---: | :--- | | msg | string | | false | | -### [`plan(n)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L148) +### [`plan(n)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L148) Plan the number of assertions. @@ -2230,7 +2230,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | n | number | | false | | -### [`deepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L159) +### [`deepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L159) @@ -2240,7 +2240,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`notDeepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L174) +### [`notDeepEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L174) @@ -2250,7 +2250,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`equal(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L189) +### [`equal(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L189) @@ -2260,7 +2260,7 @@ Plan the number of assertions. | expected | T | | false | | | msg | string | | true | | -### [`notEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L204) +### [`notEqual(actual, expected, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L204) @@ -2270,7 +2270,7 @@ Plan the number of assertions. | expected | unknown | | false | | | msg | string | | true | | -### [`fail(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L217) +### [`fail(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L217) @@ -2278,7 +2278,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | msg | string | | true | | -### [`ok(actual, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L230) +### [`ok(actual, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L230) @@ -2287,7 +2287,7 @@ Plan the number of assertions. | actual | unknown | | false | | | msg | string | | true | | -### [`pass(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L242) +### [`pass(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L242) @@ -2295,7 +2295,7 @@ Plan the number of assertions. | :--- | :--- | :---: | :---: | :--- | | msg | string | | true | | -### [`ifError(err, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L251) +### [`ifError(err, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L251) @@ -2304,7 +2304,7 @@ Plan the number of assertions. | err | Error \| null \| undefined | | false | | | msg | string | | true | | -### [`throws(fn, expected, message)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L264) +### [`throws(fn, expected, message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L264) @@ -2314,7 +2314,7 @@ Plan the number of assertions. | expected | RegExp \| any | | true | | | message | string | | true | | -### [`sleep(ms, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L313) +### [`sleep(ms, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L313) Sleep for ms with an optional msg @@ -2332,7 +2332,7 @@ Sleep for ms with an optional msg | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`requestAnimationFrame(msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L331) +### [`requestAnimationFrame(msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L331) Request animation frame with an optional msg. Falls back to a 0ms setTimeout when tests are run headlessly. @@ -2350,7 +2350,7 @@ Request animation frame with an optional msg. Falls back to a 0ms setTimeout whe | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`click(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L354) +### [`click(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L354) Dispatch the `click`` method on an element specified by selector. @@ -2368,7 +2368,7 @@ Dispatch the `click`` method on an element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`eventClick(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L380) +### [`eventClick(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L380) Dispatch the click window.MouseEvent on an element specified by selector. @@ -2386,7 +2386,7 @@ Dispatch the click window.MouseEvent on an element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`dispatchEvent(event, target, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L408) +### [`dispatchEvent(event, target, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L408) Dispatch an event on the target. @@ -2405,7 +2405,7 @@ Dispatch an event on the target. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`focus(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L428) +### [`focus(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L428) Call the focus method on element specified by selector. @@ -2423,7 +2423,7 @@ Call the focus method on element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`blur(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L452) +### [`blur(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L452) Call the blur method on element specified by selector. @@ -2441,7 +2441,7 @@ Call the blur method on element specified by selector. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`type(selector, str, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L477) +### [`type(selector, str, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L477) Consecutively set the str value of the element specified by selector to simulate typing. @@ -2460,7 +2460,7 @@ Consecutively set the str value of the element specified by selector to simulate | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`appendChild(parentSelector, el, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L509) +### [`appendChild(parentSelector, el, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L509) appendChild an element el to a parent selector element. @@ -2480,7 +2480,7 @@ appendChild an element el to a parent selector element. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`removeElement(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L529) +### [`removeElement(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L529) Remove an element from the DOM. @@ -2498,7 +2498,7 @@ Remove an element from the DOM. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`elementVisible(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L548) +### [`elementVisible(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L548) Test if an element is visible @@ -2516,7 +2516,7 @@ Test if an element is visible | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`elementInvisible(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L569) +### [`elementInvisible(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L569) Test if an element is invisible @@ -2534,7 +2534,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`waitFor(querySelectorOrFn, opts, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L593) +### [`waitFor(querySelectorOrFn, opts, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L593) Test if an element is invisible @@ -2555,7 +2555,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<HTMLElement \| Element \| void> | | -### [`waitForText(selector, opts, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L655) +### [`waitForText(selector, opts, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L655) Test if an element is invisible @@ -2585,7 +2585,7 @@ Test if an element is invisible | :--- | :--- | :--- | | Not specified | Promise<HTMLElement \| Element \| void> | | -### [`querySelector(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L692) +### [`querySelector(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L692) Run a querySelector as an assert and also get the results @@ -2603,7 +2603,7 @@ Run a querySelector as an assert and also get the results | :--- | :--- | :--- | | Not specified | HTMLElement \| Element | | -### [`querySelectorAll(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L711) +### [`querySelectorAll(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L711) Run a querySelectorAll as an assert and also get the results @@ -2621,7 +2621,7 @@ Run a querySelectorAll as an assert and also get the results | :--- | :--- | :--- | | Not specified | Array<HTMLElement \| Element> | | -### [`getComputedStyle(selector, msg)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L740) +### [`getComputedStyle(selector, msg)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L740) Retrieves the computed styles for a given element. @@ -2646,17 +2646,17 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | CSSStyleDeclaration | The computed styles of the element. | -### [`run()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L837) +### [`run()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L837) pass: number, fail: number }>} -## [TestRunner](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L918) +## [TestRunner](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L918) -### [`constructor(report)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L969) +### [`constructor(report)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L969) @@ -2664,7 +2664,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :---: | :---: | :--- | | report | (lines: string) => void | | true | | -### [`nextId()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L978) +### [`nextId()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L978) @@ -2672,11 +2672,11 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | string | | -### [`length()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L985) +### [`length()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L985) -### [`add(name, fn, only)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L995) +### [`add(name, fn, only)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L995) @@ -2686,7 +2686,7 @@ Retrieves the computed styles for a given element. | fn | TestFn | | false | | | only | boolean | | false | | -### [`run()`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1017) +### [`run()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1017) @@ -2694,7 +2694,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :--- | | Not specified | Promise<void> | | -### [`onFinish())`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1064) +### [`onFinish())`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1064) @@ -2702,7 +2702,7 @@ Retrieves the computed styles for a given element. | :--- | :--- | :---: | :---: | :--- | | ) | (result: { total: number, success: number, fail: number | > void} callback | false | | -## [`only(name, fn)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1092) +## [`only(name, fn)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1092) @@ -2711,7 +2711,7 @@ Retrieves the computed styles for a given element. | name | string | | false | | | fn | TestFn | | true | | -## [`skip(_name, _fn)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1102) +## [`skip(_name, _fn)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1102) @@ -2720,7 +2720,7 @@ Retrieves the computed styles for a given element. | _name | string | | false | | | _fn | TestFn | | true | | -## [`setStrict(strict)`](https://github.com/socketsupply/socket/blob/master/api/test/index.js#L1108) +## [`setStrict(strict)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/test/index.js#L1108) @@ -2732,7 +2732,7 @@ Retrieves the computed styles for a given element. <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [window](https://github.com/socketsupply/socket/blob/master/api/window.js#L12) +# [window](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L12) Provides ApplicationWindow class and methods @@ -2741,15 +2741,15 @@ Retrieves the computed styles for a given element. `socket:application` methods like `getCurrentWindow`, `createWindow`, `getWindow`, and `getWindows`. -## [ApplicationWindow](https://github.com/socketsupply/socket/blob/master/api/window.js#L35) +## [ApplicationWindow](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L35) Represents a window in the application -### [`id()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L69) +### [`id()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L69) The unique ID of this window. -### [`index()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L77) +### [`index()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L77) Get the index of the window @@ -2757,15 +2757,15 @@ Get the index of the window | :--- | :--- | :--- | | Not specified | number | the index of the window | -### [`hotkey()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L84) +### [`hotkey()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L84) -### [`channel()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L92) +### [`channel()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L92) The broadcast channel for this window. -### [`getSize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L100) +### [`getSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L100) Get the size of the window @@ -2773,7 +2773,7 @@ Get the size of the window | :--- | :--- | :--- | | Not specified | { width: number, height: number | } - the size of the window | -### [`getPosition()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L111) +### [`getPosition()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L111) Get the position of the window @@ -2781,7 +2781,7 @@ Get the position of the window | :--- | :--- | :--- | | Not specified | { x: number, y: number | } - the position of the window | -### [`getTitle()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L122) +### [`getTitle()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L122) Get the title of the window @@ -2789,7 +2789,7 @@ Get the title of the window | :--- | :--- | :--- | | Not specified | string | the title of the window | -### [`getStatus()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L130) +### [`getStatus()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L130) Get the status of the window @@ -2797,7 +2797,7 @@ Get the status of the window | :--- | :--- | :--- | | Not specified | string | the status of the window | -### [`close()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L138) +### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L138) Close the window @@ -2805,7 +2805,7 @@ Close the window | :--- | :--- | :--- | | Not specified | Promise<object> | the options of the window | -### [`show()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L153) +### [`show()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L153) Shows the window @@ -2813,7 +2813,7 @@ Shows the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`hide()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L162) +### [`hide()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L162) Hides the window @@ -2821,7 +2821,7 @@ Hides the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`maximize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L171) +### [`maximize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L171) Maximize the window @@ -2829,7 +2829,7 @@ Maximize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`minimize()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L180) +### [`minimize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L180) Minimize the window @@ -2837,7 +2837,7 @@ Minimize the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`restore()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L189) +### [`restore()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L189) Restore the window @@ -2845,7 +2845,7 @@ Restore the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L199) +### [`setTitle(title)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L199) Sets the title of the window @@ -2857,7 +2857,7 @@ Sets the title of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L212) +### [`setSize(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L212) Sets the size of the window @@ -2871,7 +2871,7 @@ Sets the size of the window | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L255) +### [`setPosition(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L255) Sets the position of the window @@ -2885,7 +2885,7 @@ Sets the position of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`navigate(path)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L299) +### [`navigate(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L299) Navigate the window to a given path @@ -2897,7 +2897,7 @@ Navigate the window to a given path | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`showInspector()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L308) +### [`showInspector()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L308) Opens the Web Inspector for the window @@ -2905,7 +2905,7 @@ Opens the Web Inspector for the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L325) +### [`setBackgroundColor(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L325) Sets the background color of the window @@ -2921,7 +2921,7 @@ Sets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/master/api/window.js#L334) +### [`getBackgroundColor()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L334) Gets the background color of the window @@ -2929,7 +2929,7 @@ Gets the background color of the window | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L343) +### [`setContextMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L343) Opens a native context menu. @@ -2941,7 +2941,7 @@ Opens a native context menu. | :--- | :--- | :--- | | Not specified | Promise<object> | | -### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L352) +### [`showOpenFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L352) Shows a native open file dialog. @@ -2953,7 +2953,7 @@ Shows a native open file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L370) +### [`showSaveFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L370) Shows a native save file dialog. @@ -2965,7 +2965,7 @@ Shows a native save file dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L388) +### [`showDirectoryFilePicker(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L388) Shows a native directory dialog. @@ -2977,7 +2977,7 @@ Shows a native directory dialog. | :--- | :--- | :--- | | Not specified | Promise<string[]> | an array of file paths | -### [`send(options)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L413) +### [`send(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L413) This is a high-level API that you should use instead of `ipc.request` when you want to send a message to another window or to the backend. @@ -2991,7 +2991,7 @@ This is a high-level API that you should use instead of `ipc.request` when | options.event | string | | false | the event to send | | options.value | string \| object | | true | the value to send | -### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L454) +### [`postMessage(message)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L454) Post a message to a window TODO(@jwerle): research using `BroadcastChannel` instead @@ -3004,7 +3004,7 @@ Post a message to a window | :--- | :--- | :--- | | Not specified | Promise | | -### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L473) +### [`openExternal(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L473) Opens an URL in the default application associated with the URL protocol, such as 'https:' for the default web browser. @@ -3017,7 +3017,7 @@ Opens an URL in the default application associated with the URL protocol, | :--- | :--- | :--- | | Not specified | Promise<{ url: string | >} | -### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L488) +### [`revealFile(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L488) Opens a file in the default file explorer. @@ -3029,7 +3029,7 @@ Opens a file in the default file explorer. | :--- | :--- | :--- | | Not specified | Promise | | -### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L503) +### [`addListener(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L503) Adds a listener to the window. @@ -3038,7 +3038,7 @@ Adds a listener to the window. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L521) +### [`on(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L521) Adds a listener to the window. An alias for `addListener`. @@ -3047,7 +3047,7 @@ Adds a listener to the window. An alias for `addListener`. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L538) +### [`once(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L538) Adds a listener to the window. The listener is removed after the first call. @@ -3056,7 +3056,7 @@ Adds a listener to the window. The listener is removed after the first call. | event | string | | false | the event to listen to | | cb | function(*): void | | false | the callback to call | -### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L554) +### [`removeListener(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L554) Removes a listener from the window. @@ -3065,7 +3065,7 @@ Removes a listener from the window. | event | string | | false | the event to remove the listener from | | cb | function(*): void | | false | the callback to remove | -### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L567) +### [`removeAllListeners(event)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L567) Removes all listeners from the window. @@ -3073,7 +3073,7 @@ Removes all listeners from the window. | :--- | :--- | :---: | :---: | :--- | | event | string | | false | the event to remove the listeners from | -### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/master/api/window.js#L583) +### [`off(event, cb)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/window.js#L583) Removes a listener from the window. An alias for `removeListener`. diff --git a/api/index.d.ts b/api/index.d.ts index 4d1d6d11b0..057c6ed66d 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -2230,6 +2230,993 @@ declare module "socket:util" { } +declare module "socket:async/wrap" { + /** + * Returns `true` if a given function `fn` has the "async" wrapped tag, + * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this + * function will return `false`. + * @ignore + * @param {function} fn + * @param {boolean} + */ + export function isTagged(fn: Function): boolean; + /** + * Tags a function `fn` as being "async wrapped" so subsequent calls to + * `wrap(fn)` do not wrap an already wrapped function. + * @ignore + * @param {function} fn + * @return {function} + */ + export function tag(fn: Function): Function; + /** + * Wraps a function `fn` that captures a snapshot of the current async + * context. This function is idempotent and will not wrap a function more + * than once. + * @ignore + * @param {function} fn + * @return {function} + */ + export function wrap(fn: Function): Function; + export const symbol: unique symbol; + export default wrap; +} + +declare module "socket:internal/async/hooks" { + export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + export function getNextAsyncResourceId(): number; + export function executionAsyncResource(): any; + export function executionAsyncId(): any; + export function triggerAsyncId(): any; + export function getDefaultExecutionAsyncId(): any; + export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; + export function getTopLevelAsyncResourceName(): any; + /** + * The default top level async resource ID + * @type {number} + */ + export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; + export namespace state { + let defaultExecutionAsyncId: number; + } + export namespace hooks { + let init: any[]; + let before: any[]; + let after: any[]; + let destroy: any[]; + let promiseResolve: any[]; + } + /** + * A base class for the `AsyncResource` class or other higher level async + * resource classes. + */ + export class CoreAsyncResource { + /** + * `CoreAsyncResource` class constructor. + * @param {string} type + * @param {object|number=} [options] + */ + constructor(type: string, options?: (object | number) | undefined); + /** + * The `CoreAsyncResource` type. + * @type {string} + */ + get type(): string; + /** + * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This + * value is only set to `true` if `emitDestroy()` was called, likely from + * destroying the resource manually. + * @type {boolean} + */ + get destroyed(): boolean; + /** + * The unique async resource ID. + * @return {number} + */ + asyncId(): number; + /** + * The trigger async resource ID. + * @return {number} + */ + triggerAsyncId(): number; + /** + * Manually emits destroy hook for the resource. + * @return {CoreAsyncResource} + */ + emitDestroy(): CoreAsyncResource; + /** + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @return {function} + */ + bind(fn: Function, thisArg?: object | undefined): Function; + /** + * Runs function `fn` in the execution context of this `CoreAsyncResource`. + * @param {function} fn + * @param {object=} [thisArg] + * @param {...any} [args] + * @return {any} + */ + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + #private; + } + export class TopLevelAsyncResource extends CoreAsyncResource { + } + export const asyncContextVariable: Variable<any>; + export const topLevelAsyncResource: TopLevelAsyncResource; + export default hooks; + import { Variable } from "socket:async/context"; +} + +declare module "socket:async/resource" { + /** + * @typedef {{ + * triggerAsyncId?: number, + * requireManualDestroy?: boolean + * }} AsyncResourceOptions + */ + /** + * A container that should be extended that represents a resource with + * an asynchronous execution context. + */ + export class AsyncResource extends CoreAsyncResource { + /** + * Binds function `fn` with an optional this `thisArg` binding to run + * in the execution context of an anonymous `AsyncResource`. + * @param {function} fn + * @param {object|string=} [type] + * @param {object=} [thisArg] + * @return {function} + */ + static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; + /** + * `AsyncResource` class constructor. + * @param {string} type + * @param {AsyncResourceOptions|number=} [options] + */ + constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); + } + export default AsyncResource; + export type AsyncResourceOptions = { + triggerAsyncId?: number; + requireManualDestroy?: boolean; + }; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + import { CoreAsyncResource } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; +} + +declare module "socket:async/hooks" { + /** + * Factory for creating a `AsyncHook` instance. + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] + * @return {AsyncHook} + */ + export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; + /** + * A container for `AsyncHooks` callbacks. + * @ignore + */ + export class AsyncHookCallbacks { + /** + * `AsyncHookCallbacks` class constructor. + * @ignore + * @param {AsyncHookCallbackOptions} [options] + */ + constructor(options?: AsyncHookCallbackOptions); + init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; + before(asyncId: any): void; + after(asyncId: any): void; + destroy(asyncId: any): void; + promiseResolve(asyncId: any): void; + } + /** + * A container for registering various callbacks for async resource hooks. + */ + export class AsyncHook { + /** + * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] + */ + constructor(callbacks?: any); + /** + * @type {boolean} + */ + get enabled(): boolean; + /** + * Enable the async hook. + * @return {AsyncHook} + */ + enable(): AsyncHook; + /** + * Disables the async hook + * @return {AsyncHook} + */ + disable(): AsyncHook; + #private; + } + export default createHook; + import { executionAsyncResource } from "socket:internal/async/hooks"; + import { executionAsyncId } from "socket:internal/async/hooks"; + import { triggerAsyncId } from "socket:internal/async/hooks"; + export { executionAsyncResource, executionAsyncId, triggerAsyncId }; +} + +declare module "socket:async/storage" { + /** + * A container for storing values that remain present during + * asynchronous operations. + */ + export class AsyncLocalStorage { + /** + * Binds function `fn` to run in the execution context of an + * anonymous `AsyncResource`. + * @param {function} fn + * @return {function} + */ + static bind(fn: Function): Function; + /** + * Captures the current async context and returns a function that runs + * a function in that execution context. + * @return {function} + */ + static snapshot(): Function; + /** + * @type {boolean} + */ + get enabled(): boolean; + /** + * Disables the `AsyncLocalStorage` instance. When disabled, + * `getStore()` will always return `undefined`. + */ + disable(): void; + /** + * Enables the `AsyncLocalStorage` instance. + */ + enable(): void; + /** + * Enables and sets the `AsyncLocalStorage` instance default store value. + * @param {any} store + */ + enterWith(store: any): void; + /** + * Runs function `fn` in the current asynchronous execution context with + * a given `store` value and arguments given to `fn`. + * @param {any} store + * @param {function} fn + * @param {...any} args + * @return {any} + */ + run(store: any, fn: Function, ...args: any[]): any; + exit(fn: any, ...args: any[]): any; + /** + * If the `AsyncLocalStorage` instance is enabled, it returns the current + * store value for this asynchronous execution context. + * @return {any|undefined} + */ + getStore(): any | undefined; + #private; + } + export default AsyncLocalStorage; +} + +declare module "socket:async/deferred" { + /** + * Dispatched when a `Deferred` internal promise is resolved. + */ + export class DeferredResolveEvent extends Event { + /** + * `DeferredResolveEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {any=} [result] + */ + constructor(type?: string | undefined, result?: any | undefined); + /** + * The `Deferred` promise result value. + * @type {any?} + */ + result: any | null; + } + /** + * Dispatched when a `Deferred` internal promise is rejected. + */ + export class DeferredRejectEvent { + /** + * `DeferredRejectEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {Error=} [error] + */ + constructor(type?: string | undefined, error?: Error | undefined); + } + /** + * A utility class for creating deferred promises. + */ + export class Deferred extends EventTarget { + /** + * `Deferred` class constructor. + * @param {Deferred|Promise?} [promise] + */ + constructor(promise?: Deferred | (Promise<any> | null)); + /** + * Function to resolve the associated promise. + * @type {function} + */ + resolve: Function; + /** + * Function to reject the associated promise. + * @type {function} + */ + reject: Function; + /** + * Attaches a fulfillment callback and a rejection callback to the promise, + * and returns a new promise resolving to the return value of the called + * callback. + * @param {function(any)=} [resolve] + * @param {function(Error)=} [reject] + */ + then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise<any>; + /** + * Attaches a rejection callback to the promise, and returns a new promise + * resolving to the return value of the callback if it is called, or to its + * original fulfillment value if the promise is instead fulfilled. + * @param {function(Error)=} [callback] + */ + catch(callback?: ((arg0: Error) => any) | undefined): Promise<any>; + /** + * Attaches a callback for when the promise is settled (fulfilled or rejected). + * @param {function(any?)} [callback] + */ + finally(callback?: (arg0: any | null) => any): Promise<any>; + /** + * The promise associated with this Deferred instance. + * @type {Promise<any>} + */ + get promise(): Promise<any>; + /** + * A string representation of this Deferred instance. + * @type {string} + * @ignore + */ + get [Symbol.toStringTag](): string; + #private; + } + export default Deferred; +} + +declare module "socket:async" { + export default exports; + import AsyncLocalStorage from "socket:async/storage"; + import AsyncResource from "socket:async/resource"; + import AsyncContext from "socket:async/context"; + import Deferred from "socket:async/deferred"; + import { executionAsyncResource } from "socket:async/hooks"; + import { executionAsyncId } from "socket:async/hooks"; + import { triggerAsyncId } from "socket:async/hooks"; + import { createHook } from "socket:async/hooks"; + import { AsyncHook } from "socket:async/hooks"; + import * as exports from "socket:async"; + + export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; +} + +declare module "socket:application/menu" { + /** + * Internal IPC for setting an application menu + * @ignore + */ + export function setMenu(options: any, type: any): Promise<ipc.Result>; + /** + * Internal IPC for setting an application context menu + * @ignore + */ + export function setContextMenu(options: any): Promise<any>; + /** + * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + */ + export class Menu extends EventTarget { + /** + * `Menu` class constructor. + * @ignore + * @param {string} type + */ + constructor(type: string); + /** + * The broadcast channel for this menu. + * @ignore + * @type {BroadcastChannel} + */ + get channel(): BroadcastChannel; + /** + * The `Menu` instance type. + * @type {('context'|'system'|'tray')?} + */ + get type(): "tray" | "system" | "context"; + /** + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} + */ + set onerror(onerror: (arg0: ErrorEvent) => any); + /** + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} + */ + get onerror(): (arg0: ErrorEvent) => any; + /** + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} + */ + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + /** + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} + */ + get onmenuitem(): (arg0: menuitemEvent) => any; + /** + * Set the menu layout for this `Menu` instance. + * @param {string|object} layoutOrOptions + * @param {object=} [options] + */ + set(layoutOrOptions: string | object, options?: object | undefined): Promise<any>; + #private; + } + /** + * A container for various `Menu` instances. + */ + export class MenuContainer extends EventTarget { + /** + * `MenuContainer` class constructor. + * @param {EventTarget} [sourceEventTarget] + * @param {object=} [options] + */ + constructor(sourceEventTarget?: EventTarget, options?: object | undefined); + /** + * Setter for the level 1 'error'` event listener. + * @ignore + * @type {function(ErrorEvent)?} + */ + set onerror(onerror: (arg0: ErrorEvent) => any); + /** + * Level 1 'error'` event listener. + * @type {function(ErrorEvent)?} + */ + get onerror(): (arg0: ErrorEvent) => any; + /** + * Setter for the level 1 'menuitem'` event listener. + * @ignore + * @type {function(MenuItemEvent)?} + */ + set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); + /** + * Level 1 'menuitem'` event listener. + * @type {function(menuitemEvent)?} + */ + get onmenuitem(): (arg0: menuitemEvent) => any; + /** + * The `TrayMenu` instance for the application. + * @type {TrayMenu} + */ + get tray(): TrayMenu; + /** + * The `SystemMenu` instance for the application. + * @type {SystemMenu} + */ + get system(): SystemMenu; + /** + * The `ContextMenu` instance for the application. + * @type {ContextMenu} + */ + get context(): ContextMenu; + #private; + } + /** + * A `Menu` instance that represents a context menu. + */ + export class ContextMenu extends Menu { + constructor(); + } + /** + * A `Menu` instance that represents the system menu. + */ + export class SystemMenu extends Menu { + constructor(); + } + /** + * A `Menu` instance that represents the tray menu. + */ + export class TrayMenu extends Menu { + constructor(); + } + /** + * The application tray menu. + * @type {TrayMenu} + */ + export const tray: TrayMenu; + /** + * The application system menu. + * @type {SystemMenu} + */ + export const system: SystemMenu; + /** + * The application context menu. + * @type {ContextMenu} + */ + export const context: ContextMenu; + /** + * The application menus container. + * @type {MenuContainer} + */ + export const container: MenuContainer; + export default container; + import ipc from "socket:ipc"; +} + +declare module "socket:internal/events" { + /** + * An event dispatched when an application URL is opening the application. + */ + export class ApplicationURLEvent extends Event { + /** + * `ApplicationURLEvent` class constructor. + * @param {string=} [type] + * @param {object=} [options] + */ + constructor(type?: string | undefined, options?: object | undefined); + /** + * `true` if the application URL is valid (parses correctly). + * @type {boolean} + */ + get isValid(): boolean; + /** + * Data associated with the `ApplicationURLEvent`. + * @type {?any} + */ + get data(): any; + /** + * The original source URI + * @type {?string} + */ + get source(): string; + /** + * The `URL` for the `ApplicationURLEvent`. + * @type {?URL} + */ + get url(): URL; + /** + * String tag name for an `ApplicationURLEvent` instance. + * @type {string} + */ + get [Symbol.toStringTag](): string; + #private; + } + /** + * An event dispacted for a registered global hotkey expression. + */ + export class HotKeyEvent extends MessageEvent<any> { + /** + * `HotKeyEvent` class constructor. + * @ignore + * @param {string=} [type] + * @param {object=} [data] + */ + constructor(type?: string | undefined, data?: object | undefined); + /** + * The global unique ID for this hotkey binding. + * @type {number?} + */ + get id(): number; + /** + * The computed hash for this hotkey binding. + * @type {number?} + */ + get hash(): number; + /** + * The normalized hotkey expression as a sequence of tokens. + * @type {string[]} + */ + get sequence(): string[]; + /** + * The original expression of the hotkey binding. + * @type {string?} + */ + get expression(): string; + } + /** + * An event dispacted when a menu item is selected. + */ + export class MenuItemEvent extends MessageEvent<any> { + /** + * `MenuItemEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [data] + * @param {import('../application/menu.js').Menu} menu + */ + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + /** + * The `Menu` this event has been dispatched for. + * @type {import('../application/menu.js').Menu?} + */ + get menu(): import("socket:application/menu").Menu; + /** + * The title of the menu item. + * @type {string?} + */ + get title(): string; + /** + * An optional tag value for the menu item that may also be the + * parent menu item title. + * @type {string?} + */ + get tag(): string; + /** + * The parent title of the menu item. + * @type {string?} + */ + get parent(): string; + #private; + } + /** + * An event dispacted when the application receives an OS signal + */ + export class SignalEvent extends MessageEvent<any> { + /** + * `SignalEvent` class constructor + * @ignore + * @param {string=} [type] + * @param {object=} [options] + */ + constructor(type?: string | undefined, options?: object | undefined); + /** + * The code of the signal. + * @type {import('../process/signal.js').signal} + */ + get code(): any; + /** + * The name of the signal. + * @type {string} + */ + get name(): string; + /** + * An optional message describing the signal + * @type {string} + */ + get message(): string; + #private; + } + namespace _default { + export { ApplicationURLEvent }; + export { MenuItemEvent }; + export { SignalEvent }; + export { HotKeyEvent }; + } + export default _default; +} + +declare module "socket:path/well-known" { + /** + * Well known path to the user's "Downloads" folder. + * @type {?string} + */ + export const DOWNLOADS: string | null; + /** + * Well known path to the user's "Documents" folder. + * @type {?string} + */ + export const DOCUMENTS: string | null; + /** + * Well known path to the user's "Pictures" folder. + * @type {?string} + */ + export const PICTURES: string | null; + /** + * Well known path to the user's "Desktop" folder. + * @type {?string} + */ + export const DESKTOP: string | null; + /** + * Well known path to the user's "Videos" folder. + * @type {?string} + */ + export const VIDEOS: string | null; + /** + * Well known path to the user's "Music" folder. + * @type {?string} + */ + export const MUSIC: string | null; + /** + * Well known path to the application's "resources" folder. + * @type {?string} + */ + export const RESOURCES: string | null; + /** + * Well known path to the application's "config" folder. + * @type {?string} + */ + export const CONFIG: string | null; + /** + * Well known path to the application's "data" folder. + * @type {?string} + */ + export const DATA: string | null; + /** + * Well known path to the application's "log" folder. + * @type {?string} + */ + export const LOG: string | null; + /** + * Well known path to the application's "tmp" folder. + * @type {?string} + */ + export const TMP: string | null; + /** + * Well known path to the application's "home" folder. + * This may be the user's HOME directory or the application container sandbox. + * @type {?string} + */ + export const HOME: string | null; + namespace _default { + export { DOWNLOADS }; + export { DOCUMENTS }; + export { RESOURCES }; + export { PICTURES }; + export { DESKTOP }; + export { VIDEOS }; + export { CONFIG }; + export { MUSIC }; + export { HOME }; + export { DATA }; + export { LOG }; + export { TMP }; + } + export default _default; +} + +declare module "socket:os" { + /** + * Returns the operating system CPU architecture for which Socket was compiled. + * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' + */ + export function arch(): string; + /** + * Returns an array of objects containing information about each CPU/core. + * @returns {Array<object>} cpus - An array of objects containing information about each CPU/core. + * The properties of the objects are: + * - model `<string>` - CPU model name. + * - speed `<number>` - CPU clock speed (in MHz). + * - times `<object>` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. + * - user `<number>` - Time spent by this CPU or core in user mode. + * - nice `<number>` - Time spent by this CPU or core in user mode with low priority (nice). + * - sys `<number>` - Time spent by this CPU or core in system mode. + * - idle `<number>` - Time spent by this CPU or core in idle mode. + * - irq `<number>` - Time spent by this CPU or core in IRQ mode. + * @see {@link https://nodejs.org/api/os.html#os_os_cpus} + */ + export function cpus(): Array<object>; + /** + * Returns an object containing network interfaces that have been assigned a network address. + * @returns {object} - An object containing network interfaces that have been assigned a network address. + * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. + * The properties available on the assigned network address object include: + * - address `<string>` - The assigned IPv4 or IPv6 address. + * - netmask `<string>` - The IPv4 or IPv6 network mask. + * - family `<string>` - The address family ('IPv4' or 'IPv6'). + * - mac `<string>` - The MAC address of the network interface. + * - internal `<boolean>` - Indicates whether the network interface is a loopback interface. + * - scopeid `<number>` - The numeric scope ID (only specified when family is 'IPv6'). + * - cidr `<string>` - The CIDR notation of the interface. + * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} + */ + export function networkInterfaces(): object; + /** + * Returns the operating system platform. + * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_platform} + * The returned value is equivalent to `process.platform`. + */ + export function platform(): string; + /** + * Returns the operating system name. + * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' + * @see {@link https://nodejs.org/api/os.html#os_os_type} + */ + export function type(): string; + /** + * @returns {boolean} - `true` if the operating system is Windows. + */ + export function isWindows(): boolean; + /** + * @returns {string} - The operating system's default directory for temporary files. + */ + export function tmpdir(): string; + /** + * Get resource usage. + */ + export function rusage(): any; + /** + * Returns the system uptime in seconds. + * @returns {number} - The system uptime in seconds. + */ + export function uptime(): number; + /** + * Returns the operating system name. + * @returns {string} - The operating system name. + */ + export function uname(): string; + /** + * It's implemented in process.hrtime.bigint() + * @ignore + */ + export function hrtime(): any; + /** + * Node.js doesn't have this method. + * @ignore + */ + export function availableMemory(): any; + /** + * The host operating system. This value can be one of: + * - android + * - android-emulator + * - iphoneos + * - iphone-simulator + * - linux + * - macosx + * - unix + * - unknown + * - win32 + * @ignore + * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} + */ + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + /** + * Returns the home directory of the current user. + * @return {string} + */ + export function homedir(): string; + export { constants }; + /** + * @type {string} + * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. + */ + export const EOL: string; + export default exports; + import constants from "socket:os/constants"; + import * as exports from "socket:os"; + +} + +declare module "socket:process/signal" { + /** + * Converts an `signal` code to its corresponding string message. + * @param {import('./os/constants.js').signal} {code} + * @return {string} + */ + export function toString(code: any): string; + /** + * Gets the code for a given 'signal' name. + * @param {string|number} name + * @return {signal} + */ + export function getCode(name: string | number): signal; + /** + * Gets the name for a given 'signal' code + * @return {string} + * @param {string|number} code + */ + export function getName(code: string | number): string; + /** + * Gets the message for a 'signal' code. + * @param {number|string} code + * @return {string} + */ + export function getMessage(code: number | string): string; + /** + * Add a signal event listener. + * @param {string|number} signal + * @param {function(SignalEvent)} callback + * @param {{ once?: boolean }=} [options] + */ + export function addEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { + once?: boolean; + } | undefined): void; + /** + * Remove a signal event listener. + * @param {string|number} signal + * @param {function(SignalEvent)} callback + * @param {{ once?: boolean }=} [options] + */ + export function removeEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { + once?: boolean; + } | undefined): void; + export { constants }; + export const channel: BroadcastChannel; + export const SIGHUP: any; + export const SIGINT: any; + export const SIGQUIT: any; + export const SIGILL: any; + export const SIGTRAP: any; + export const SIGABRT: any; + export const SIGIOT: any; + export const SIGBUS: any; + export const SIGFPE: any; + export const SIGKILL: any; + export const SIGUSR1: any; + export const SIGSEGV: any; + export const SIGUSR2: any; + export const SIGPIPE: any; + export const SIGALRM: any; + export const SIGTERM: any; + export const SIGCHLD: any; + export const SIGCONT: any; + export const SIGSTOP: any; + export const SIGTSTP: any; + export const SIGTTIN: any; + export const SIGTTOU: any; + export const SIGURG: any; + export const SIGXCPU: any; + export const SIGXFSZ: any; + export const SIGVTALRM: any; + export const SIGPROF: any; + export const SIGWINCH: any; + export const SIGIO: any; + export const SIGINFO: any; + export const SIGSYS: any; + export const strings: { + [x: number]: string; + }; + namespace _default { + export { addEventListener }; + export { removeEventListener }; + export { constants }; + export { channel }; + export { strings }; + export { toString }; + export { getName }; + export { getCode }; + export { getMessage }; + export { SIGHUP }; + export { SIGINT }; + export { SIGQUIT }; + export { SIGILL }; + export { SIGTRAP }; + export { SIGABRT }; + export { SIGIOT }; + export { SIGBUS }; + export { SIGFPE }; + export { SIGKILL }; + export { SIGUSR1 }; + export { SIGSEGV }; + export { SIGUSR2 }; + export { SIGPIPE }; + export { SIGALRM }; + export { SIGTERM }; + export { SIGCHLD }; + export { SIGCONT }; + export { SIGSTOP }; + export { SIGTSTP }; + export { SIGTTIN }; + export { SIGTTOU }; + export { SIGURG }; + export { SIGXCPU }; + export { SIGXFSZ }; + export { SIGVTALRM }; + export { SIGPROF }; + export { SIGWINCH }; + export { SIGIO }; + export { SIGINFO }; + export { SIGSYS }; + } + export default _default; + export type signal = any; + import { SignalEvent } from "socket:internal/events"; + import { signal as constants } from "socket:os/constants"; +} + declare module "socket:internal/streams/web" { export class ByteLengthQueuingStrategy { constructor(e: any); @@ -2584,217 +3571,25 @@ declare module "socket:stream" { PassThrough: typeof PassThrough; pipeline: typeof pipeline & { [x: symbol]: typeof pipelinePromise; - }; - }; - export default _default; - import web from "socket:stream/web"; - import { EventEmitter } from "socket:events"; -} - -declare module "socket:tty" { - export function WriteStream(fd: any): Writable; - export function ReadStream(fd: any): Readable; - export function isatty(fd: any): boolean; - namespace _default { - export { WriteStream }; - export { ReadStream }; - export { isatty }; - } - export default _default; - import { Writable } from "socket:stream"; - import { Readable } from "socket:stream"; -} - -declare module "socket:path/well-known" { - /** - * Well known path to the user's "Downloads" folder. - * @type {?string} - */ - export const DOWNLOADS: string | null; - /** - * Well known path to the user's "Documents" folder. - * @type {?string} - */ - export const DOCUMENTS: string | null; - /** - * Well known path to the user's "Pictures" folder. - * @type {?string} - */ - export const PICTURES: string | null; - /** - * Well known path to the user's "Desktop" folder. - * @type {?string} - */ - export const DESKTOP: string | null; - /** - * Well known path to the user's "Videos" folder. - * @type {?string} - */ - export const VIDEOS: string | null; - /** - * Well known path to the user's "Music" folder. - * @type {?string} - */ - export const MUSIC: string | null; - /** - * Well known path to the application's "resources" folder. - * @type {?string} - */ - export const RESOURCES: string | null; - /** - * Well known path to the application's "config" folder. - * @type {?string} - */ - export const CONFIG: string | null; - /** - * Well known path to the application's "data" folder. - * @type {?string} - */ - export const DATA: string | null; - /** - * Well known path to the application's "log" folder. - * @type {?string} - */ - export const LOG: string | null; - /** - * Well known path to the application's "tmp" folder. - * @type {?string} - */ - export const TMP: string | null; - /** - * Well known path to the application's "home" folder. - * This may be the user's HOME directory or the application container sandbox. - * @type {?string} - */ - export const HOME: string | null; - namespace _default { - export { DOWNLOADS }; - export { DOCUMENTS }; - export { RESOURCES }; - export { PICTURES }; - export { DESKTOP }; - export { VIDEOS }; - export { CONFIG }; - export { MUSIC }; - export { HOME }; - export { DATA }; - export { LOG }; - export { TMP }; - } + }; + }; export default _default; + import web from "socket:stream/web"; + import { EventEmitter } from "socket:events"; } -declare module "socket:os" { - /** - * Returns the operating system CPU architecture for which Socket was compiled. - * @returns {string} - 'arm64', 'ia32', 'x64', or 'unknown' - */ - export function arch(): string; - /** - * Returns an array of objects containing information about each CPU/core. - * @returns {Array<object>} cpus - An array of objects containing information about each CPU/core. - * The properties of the objects are: - * - model `<string>` - CPU model name. - * - speed `<number>` - CPU clock speed (in MHz). - * - times `<object>` - An object containing the fields user, nice, sys, idle, irq representing the number of milliseconds the CPU has spent in each mode. - * - user `<number>` - Time spent by this CPU or core in user mode. - * - nice `<number>` - Time spent by this CPU or core in user mode with low priority (nice). - * - sys `<number>` - Time spent by this CPU or core in system mode. - * - idle `<number>` - Time spent by this CPU or core in idle mode. - * - irq `<number>` - Time spent by this CPU or core in IRQ mode. - * @see {@link https://nodejs.org/api/os.html#os_os_cpus} - */ - export function cpus(): Array<object>; - /** - * Returns an object containing network interfaces that have been assigned a network address. - * @returns {object} - An object containing network interfaces that have been assigned a network address. - * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. - * The properties available on the assigned network address object include: - * - address `<string>` - The assigned IPv4 or IPv6 address. - * - netmask `<string>` - The IPv4 or IPv6 network mask. - * - family `<string>` - The address family ('IPv4' or 'IPv6'). - * - mac `<string>` - The MAC address of the network interface. - * - internal `<boolean>` - Indicates whether the network interface is a loopback interface. - * - scopeid `<number>` - The numeric scope ID (only specified when family is 'IPv6'). - * - cidr `<string>` - The CIDR notation of the interface. - * @see {@link https://nodejs.org/api/os.html#os_os_networkinterfaces} - */ - export function networkInterfaces(): object; - /** - * Returns the operating system platform. - * @returns {string} - 'android', 'cygwin', 'freebsd', 'linux', 'darwin', 'ios', 'openbsd', 'win32', or 'unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_platform} - * The returned value is equivalent to `process.platform`. - */ - export function platform(): string; - /** - * Returns the operating system name. - * @returns {string} - 'CYGWIN_NT', 'Mac', 'Darwin', 'FreeBSD', 'Linux', 'OpenBSD', 'Windows_NT', 'Win32', or 'Unknown' - * @see {@link https://nodejs.org/api/os.html#os_os_type} - */ - export function type(): string; - /** - * @returns {boolean} - `true` if the operating system is Windows. - */ - export function isWindows(): boolean; - /** - * @returns {string} - The operating system's default directory for temporary files. - */ - export function tmpdir(): string; - /** - * Get resource usage. - */ - export function rusage(): any; - /** - * Returns the system uptime in seconds. - * @returns {number} - The system uptime in seconds. - */ - export function uptime(): number; - /** - * Returns the operating system name. - * @returns {string} - The operating system name. - */ - export function uname(): string; - /** - * It's implemented in process.hrtime.bigint() - * @ignore - */ - export function hrtime(): any; - /** - * Node.js doesn't have this method. - * @ignore - */ - export function availableMemory(): any; - /** - * The host operating system. This value can be one of: - * - android - * - android-emulator - * - iphoneos - * - iphone-simulator - * - linux - * - macosx - * - unix - * - unknown - * - win32 - * @ignore - * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} - */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; - /** - * Returns the home directory of the current user. - * @return {string} - */ - export function homedir(): string; - export { constants }; - /** - * @type {string} - * The operating system's end-of-line marker. `'\r\n'` on Windows and `'\n'` on POSIX. - */ - export const EOL: string; - export default exports; - import constants from "socket:os/constants"; - import * as exports from "socket:os"; - +declare module "socket:tty" { + export function WriteStream(fd: any): Writable; + export function ReadStream(fd: any): Readable; + export function isatty(fd: any): boolean; + namespace _default { + export { WriteStream }; + export { ReadStream }; + export { isatty }; + } + export default _default; + import { Writable } from "socket:stream"; + import { Readable } from "socket:stream"; } declare module "socket:process" { @@ -3125,140 +3920,22 @@ declare module "socket:path/win32" { * @param {string} path */ export function normalize(path: string): any; - /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} - */ - export function relative(from: string, to: string): string; - export default exports; - export namespace win32 { - let sep: "\\"; - let delimiter: ";"; - } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as mounts from "socket:path/mounts"; - import * as posix from "socket:path/posix"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/win32"; - - export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path/posix" { - /** - * Computes current working directory for a path - * @param {string} - * @return {string} - */ - export function cwd(): string; - /** - * Resolves path components to an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function resolve(...components: PathComponent[]): string; - /** - * Joins path components. This function may not return an absolute path. - * @param {...PathComponent} components - * @return {string} - */ - export function join(...components: PathComponent[]): string; - /** - * Computes directory name of path. - * @param {PathComponent} path - * @return {string} - */ - export function dirname(path: PathComponent): string; - /** - * Computes base name of path. - * @param {PathComponent} path - * @param {string=} [suffix] - * @return {string} - */ - export function basename(path: PathComponent, suffix?: string | undefined): string; - /** - * Computes extension name of path. - * @param {PathComponent} path - * @return {string} - */ - export function extname(path: PathComponent): string; - /** - * Predicate helper to determine if path is absolute. - * @param {PathComponent} path - * @return {boolean} - */ - export function isAbsolute(path: PathComponent): boolean; - /** - * Parses input `path` into a `Path` instance. - * @param {PathComponent} path - * @return {Path} - */ - export function parse(path: PathComponent): Path; - /** - * Formats `Path` object into a string. - * @param {object|Path} path - * @return {string} - */ - export function format(path: object | Path): string; - /** - * Normalizes `path` resolving `..` and `./` preserving trailing - * slashes. - * @param {string} path - */ - export function normalize(path: string): any; - /** - * Computes the relative path from `from` to `to`. - * @param {string} from - * @param {string} to - * @return {string} - */ - export function relative(from: string, to: string): string; - export default exports; - export namespace posix { - let sep: "/"; - let delimiter: ":"; - } - export type PathComponent = import("socket:path/path").PathComponent; - import { Path } from "socket:path/path"; - import * as mounts from "socket:path/mounts"; - import * as win32 from "socket:path/win32"; - import { DOWNLOADS } from "socket:path/well-known"; - import { DOCUMENTS } from "socket:path/well-known"; - import { RESOURCES } from "socket:path/well-known"; - import { PICTURES } from "socket:path/well-known"; - import { DESKTOP } from "socket:path/well-known"; - import { VIDEOS } from "socket:path/well-known"; - import { CONFIG } from "socket:path/well-known"; - import { MUSIC } from "socket:path/well-known"; - import { HOME } from "socket:path/well-known"; - import { DATA } from "socket:path/well-known"; - import { LOG } from "socket:path/well-known"; - import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/posix"; - - export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path/index" { + /** + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} + */ + export function relative(from: string, to: string): string; export default exports; + export namespace win32 { + let sep: "\\"; + let delimiter: ";"; + } + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; import * as mounts from "socket:path/mounts"; import * as posix from "socket:path/posix"; - import * as win32 from "socket:path/win32"; - import { Path } from "socket:path/path"; import { DOWNLOADS } from "socket:path/well-known"; import { DOCUMENTS } from "socket:path/well-known"; import { RESOURCES } from "socket:path/well-known"; @@ -3271,584 +3948,329 @@ declare module "socket:path/index" { import { DATA } from "socket:path/well-known"; import { LOG } from "socket:path/well-known"; import { TMP } from "socket:path/well-known"; - import * as exports from "socket:path/index"; - - export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:path" { - export const sep: "\\" | "/"; - export const delimiter: ":" | ";"; - export const resolve: typeof posix.win32.resolve; - export const join: typeof posix.win32.join; - export const dirname: typeof posix.win32.dirname; - export const basename: typeof posix.win32.basename; - export const extname: typeof posix.win32.extname; - export const cwd: typeof posix.win32.cwd; - export const isAbsolute: typeof posix.win32.isAbsolute; - export const parse: typeof posix.win32.parse; - export const format: typeof posix.win32.format; - export const normalize: typeof posix.win32.normalize; - export const relative: typeof posix.win32.relative; - const _default: typeof posix | typeof posix.win32; - export default _default; - import { posix } from "socket:path/index"; - import { Path } from "socket:path/index"; - import { win32 } from "socket:path/index"; - import { mounts } from "socket:path/index"; - import { DOWNLOADS } from "socket:path/index"; - import { DOCUMENTS } from "socket:path/index"; - import { RESOURCES } from "socket:path/index"; - import { PICTURES } from "socket:path/index"; - import { DESKTOP } from "socket:path/index"; - import { VIDEOS } from "socket:path/index"; - import { CONFIG } from "socket:path/index"; - import { MUSIC } from "socket:path/index"; - import { HOME } from "socket:path/index"; - import { DATA } from "socket:path/index"; - import { LOG } from "socket:path/index"; - import { TMP } from "socket:path/index"; - export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; -} - -declare module "socket:fs/stream" { - export const DEFAULT_STREAM_HIGH_WATER_MARK: number; - /** - * @typedef {import('./handle.js').FileHandle} FileHandle - */ - /** - * A `Readable` stream for a `FileHandle`. - */ - export class ReadStream extends Readable { - end: any; - start: any; - handle: any; - buffer: ArrayBuffer; - signal: any; - timeout: any; - bytesRead: number; - shouldEmitClose: boolean; - /** - * Sets file handle for the ReadStream. - * @param {FileHandle} handle - */ - setHandle(handle: FileHandle): void; - /** - * The max buffer size for the ReadStream. - */ - get highWaterMark(): number; - /** - * Relative or absolute path of the underlying `FileHandle`. - */ - get path(): any; - /** - * `true` if the stream is in a pending state. - */ - get pending(): boolean; - _open(callback: any): Promise<any>; - _read(callback: any): Promise<any>; - } - export namespace ReadStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; - } - /** - * A `Writable` stream for a `FileHandle`. - */ - export class WriteStream extends Writable { - start: any; - handle: any; - signal: any; - timeout: any; - bytesWritten: number; - shouldEmitClose: boolean; - /** - * Sets file handle for the WriteStream. - * @param {FileHandle} handle - */ - setHandle(handle: FileHandle): void; - /** - * The max buffer size for the Writetream. - */ - get highWaterMark(): number; - /** - * Relative or absolute path of the underlying `FileHandle`. - */ - get path(): any; - /** - * `true` if the stream is in a pending state. - */ - get pending(): boolean; - _open(callback: any): Promise<any>; - _write(buffer: any, callback: any): any; - } - export namespace WriteStream { - export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; - } - export const FileReadStream: typeof exports.ReadStream; - export const FileWriteStream: typeof exports.WriteStream; - export default exports; - export type FileHandle = import("socket:fs/handle").FileHandle; - import { Readable } from "socket:stream"; - import { Writable } from "socket:stream"; - import * as exports from "socket:fs/stream"; + import * as exports from "socket:path/win32"; + export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; } -declare module "socket:fs/constants" { - /** - * This flag can be used with uv_fs_copyfile() to return an error if the - * destination already exists. - */ - export const COPYFILE_EXCL: 1; +declare module "socket:path/posix" { /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, a fallback copy mechanism is used. + * Computes current working directory for a path + * @param {string} + * @return {string} */ - export const COPYFILE_FICLONE: 2; + export function cwd(): string; /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, an error is returned. + * Resolves path components to an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export const COPYFILE_FICLONE_FORCE: 4; - export const UV_DIRENT_UNKNOWN: any; - export const UV_DIRENT_FILE: any; - export const UV_DIRENT_DIR: any; - export const UV_DIRENT_LINK: any; - export const UV_DIRENT_FIFO: any; - export const UV_DIRENT_SOCKET: any; - export const UV_DIRENT_CHAR: any; - export const UV_DIRENT_BLOCK: any; - export const UV_FS_SYMLINK_DIR: any; - export const UV_FS_SYMLINK_JUNCTION: any; - export const UV_FS_O_FILEMAP: any; - export const O_RDONLY: any; - export const O_WRONLY: any; - export const O_RDWR: any; - export const O_APPEND: any; - export const O_ASYNC: any; - export const O_CLOEXEC: any; - export const O_CREAT: any; - export const O_DIRECT: any; - export const O_DIRECTORY: any; - export const O_DSYNC: any; - export const O_EXCL: any; - export const O_LARGEFILE: any; - export const O_NOATIME: any; - export const O_NOCTTY: any; - export const O_NOFOLLOW: any; - export const O_NONBLOCK: any; - export const O_NDELAY: any; - export const O_PATH: any; - export const O_SYNC: any; - export const O_TMPFILE: any; - export const O_TRUNC: any; - export const S_IFMT: any; - export const S_IFREG: any; - export const S_IFDIR: any; - export const S_IFCHR: any; - export const S_IFBLK: any; - export const S_IFIFO: any; - export const S_IFLNK: any; - export const S_IFSOCK: any; - export const S_IRWXU: any; - export const S_IRUSR: any; - export const S_IWUSR: any; - export const S_IXUSR: any; - export const S_IRWXG: any; - export const S_IRGRP: any; - export const S_IWGRP: any; - export const S_IXGRP: any; - export const S_IRWXO: any; - export const S_IROTH: any; - export const S_IWOTH: any; - export const S_IXOTH: any; - export const F_OK: any; - export const R_OK: any; - export const W_OK: any; - export const X_OK: any; - export default exports; - import * as exports from "socket:fs/constants"; - -} - -declare module "socket:fs/flags" { - export function normalizeFlags(flags: any): any; - export default exports; - import * as exports from "socket:fs/flags"; - -} - -declare module "socket:async/wrap" { + export function resolve(...components: PathComponent[]): string; /** - * Returns `true` if a given function `fn` has the "async" wrapped tag, - * meaning it was "tagged" in a `wrap(fn)` call before, otherwise this - * function will return `false`. - * @ignore - * @param {function} fn - * @param {boolean} + * Joins path components. This function may not return an absolute path. + * @param {...PathComponent} components + * @return {string} */ - export function isTagged(fn: Function): boolean; + export function join(...components: PathComponent[]): string; /** - * Tags a function `fn` as being "async wrapped" so subsequent calls to - * `wrap(fn)` do not wrap an already wrapped function. - * @ignore - * @param {function} fn - * @return {function} + * Computes directory name of path. + * @param {PathComponent} path + * @return {string} */ - export function tag(fn: Function): Function; + export function dirname(path: PathComponent): string; /** - * Wraps a function `fn` that captures a snapshot of the current async - * context. This function is idempotent and will not wrap a function more - * than once. - * @ignore - * @param {function} fn - * @return {function} + * Computes base name of path. + * @param {PathComponent} path + * @param {string=} [suffix] + * @return {string} */ - export function wrap(fn: Function): Function; - export const symbol: unique symbol; - export default wrap; -} - -declare module "socket:internal/async/hooks" { - export function dispatch(hook: any, asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - export function getNextAsyncResourceId(): number; - export function executionAsyncResource(): any; - export function executionAsyncId(): any; - export function triggerAsyncId(): any; - export function getDefaultExecutionAsyncId(): any; - export function wrap(callback: any, type: any, asyncId?: number, triggerAsyncId?: any, resource?: any): (...args: any[]) => any; - export function getTopLevelAsyncResourceName(): any; + export function basename(path: PathComponent, suffix?: string | undefined): string; /** - * The default top level async resource ID - * @type {number} + * Computes extension name of path. + * @param {PathComponent} path + * @return {string} */ - export const TOP_LEVEL_ASYNC_RESOURCE_ID: number; - export namespace state { - let defaultExecutionAsyncId: number; - } - export namespace hooks { - let init: any[]; - let before: any[]; - let after: any[]; - let destroy: any[]; - let promiseResolve: any[]; - } + export function extname(path: PathComponent): string; /** - * A base class for the `AsyncResource` class or other higher level async - * resource classes. + * Predicate helper to determine if path is absolute. + * @param {PathComponent} path + * @return {boolean} */ - export class CoreAsyncResource { - /** - * `CoreAsyncResource` class constructor. - * @param {string} type - * @param {object|number=} [options] - */ - constructor(type: string, options?: (object | number) | undefined); - /** - * The `CoreAsyncResource` type. - * @type {string} - */ - get type(): string; - /** - * `true` if the `CoreAsyncResource` was destroyed, otherwise `false`. This - * value is only set to `true` if `emitDestroy()` was called, likely from - * destroying the resource manually. - * @type {boolean} - */ - get destroyed(): boolean; - /** - * The unique async resource ID. - * @return {number} - */ - asyncId(): number; - /** - * The trigger async resource ID. - * @return {number} - */ - triggerAsyncId(): number; - /** - * Manually emits destroy hook for the resource. - * @return {CoreAsyncResource} - */ - emitDestroy(): CoreAsyncResource; - /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @return {function} - */ - bind(fn: Function, thisArg?: object | undefined): Function; - /** - * Runs function `fn` in the execution context of this `CoreAsyncResource`. - * @param {function} fn - * @param {object=} [thisArg] - * @param {...any} [args] - * @return {any} - */ - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; - #private; - } - export class TopLevelAsyncResource extends CoreAsyncResource { - } - export const asyncContextVariable: Variable<any>; - export const topLevelAsyncResource: TopLevelAsyncResource; - export default hooks; - import { Variable } from "socket:async/context"; -} - -declare module "socket:async/resource" { + export function isAbsolute(path: PathComponent): boolean; /** - * @typedef {{ - * triggerAsyncId?: number, - * requireManualDestroy?: boolean - * }} AsyncResourceOptions + * Parses input `path` into a `Path` instance. + * @param {PathComponent} path + * @return {Path} */ + export function parse(path: PathComponent): Path; /** - * A container that should be extended that represents a resource with - * an asynchronous execution context. + * Formats `Path` object into a string. + * @param {object|Path} path + * @return {string} */ - export class AsyncResource extends CoreAsyncResource { - /** - * Binds function `fn` with an optional this `thisArg` binding to run - * in the execution context of an anonymous `AsyncResource`. - * @param {function} fn - * @param {object|string=} [type] - * @param {object=} [thisArg] - * @return {function} - */ - static bind(fn: Function, type?: (object | string) | undefined, thisArg?: object | undefined): Function; - /** - * `AsyncResource` class constructor. - * @param {string} type - * @param {AsyncResourceOptions|number=} [options] - */ - constructor(type: string, options?: (AsyncResourceOptions | number) | undefined); - } - export default AsyncResource; - export type AsyncResourceOptions = { - triggerAsyncId?: number; - requireManualDestroy?: boolean; - }; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - import { CoreAsyncResource } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/hooks" { + export function format(path: object | Path): string; /** - * Factory for creating a `AsyncHook` instance. - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [callbacks] - * @return {AsyncHook} + * Normalizes `path` resolving `..` and `./` preserving trailing + * slashes. + * @param {string} path */ - export function createHook(callbacks?: (AsyncHookCallbackOptions | AsyncHookCallbacks) | undefined): AsyncHook; + export function normalize(path: string): any; /** - * A container for `AsyncHooks` callbacks. - * @ignore + * Computes the relative path from `from` to `to`. + * @param {string} from + * @param {string} to + * @return {string} */ - export class AsyncHookCallbacks { - /** - * `AsyncHookCallbacks` class constructor. - * @ignore - * @param {AsyncHookCallbackOptions} [options] - */ - constructor(options?: AsyncHookCallbackOptions); - init(asyncId: any, type: any, triggerAsyncId: any, resource: any): void; - before(asyncId: any): void; - after(asyncId: any): void; - destroy(asyncId: any): void; - promiseResolve(asyncId: any): void; + export function relative(from: string, to: string): string; + export default exports; + export namespace posix { + let sep: "/"; + let delimiter: ":"; } + export type PathComponent = import("socket:path/path").PathComponent; + import { Path } from "socket:path/path"; + import * as mounts from "socket:path/mounts"; + import * as win32 from "socket:path/win32"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/posix"; + + export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path/index" { + export default exports; + import * as mounts from "socket:path/mounts"; + import * as posix from "socket:path/posix"; + import * as win32 from "socket:path/win32"; + import { Path } from "socket:path/path"; + import { DOWNLOADS } from "socket:path/well-known"; + import { DOCUMENTS } from "socket:path/well-known"; + import { RESOURCES } from "socket:path/well-known"; + import { PICTURES } from "socket:path/well-known"; + import { DESKTOP } from "socket:path/well-known"; + import { VIDEOS } from "socket:path/well-known"; + import { CONFIG } from "socket:path/well-known"; + import { MUSIC } from "socket:path/well-known"; + import { HOME } from "socket:path/well-known"; + import { DATA } from "socket:path/well-known"; + import { LOG } from "socket:path/well-known"; + import { TMP } from "socket:path/well-known"; + import * as exports from "socket:path/index"; + + export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:path" { + export const sep: "\\" | "/"; + export const delimiter: ":" | ";"; + export const resolve: typeof posix.win32.resolve; + export const join: typeof posix.win32.join; + export const dirname: typeof posix.win32.dirname; + export const basename: typeof posix.win32.basename; + export const extname: typeof posix.win32.extname; + export const cwd: typeof posix.win32.cwd; + export const isAbsolute: typeof posix.win32.isAbsolute; + export const parse: typeof posix.win32.parse; + export const format: typeof posix.win32.format; + export const normalize: typeof posix.win32.normalize; + export const relative: typeof posix.win32.relative; + const _default: typeof posix | typeof posix.win32; + export default _default; + import { posix } from "socket:path/index"; + import { Path } from "socket:path/index"; + import { win32 } from "socket:path/index"; + import { mounts } from "socket:path/index"; + import { DOWNLOADS } from "socket:path/index"; + import { DOCUMENTS } from "socket:path/index"; + import { RESOURCES } from "socket:path/index"; + import { PICTURES } from "socket:path/index"; + import { DESKTOP } from "socket:path/index"; + import { VIDEOS } from "socket:path/index"; + import { CONFIG } from "socket:path/index"; + import { MUSIC } from "socket:path/index"; + import { HOME } from "socket:path/index"; + import { DATA } from "socket:path/index"; + import { LOG } from "socket:path/index"; + import { TMP } from "socket:path/index"; + export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; +} + +declare module "socket:fs/stream" { + export const DEFAULT_STREAM_HIGH_WATER_MARK: number; /** - * A container for registering various callbacks for async resource hooks. + * @typedef {import('./handle.js').FileHandle} FileHandle */ - export class AsyncHook { + /** + * A `Readable` stream for a `FileHandle`. + */ + export class ReadStream extends Readable { + end: any; + start: any; + handle: any; + buffer: ArrayBuffer; + signal: any; + timeout: any; + bytesRead: number; + shouldEmitClose: boolean; /** - * @param {AsyncHookCallbackOptions|AsyncHookCallbacks=} [options] + * Sets file handle for the ReadStream. + * @param {FileHandle} handle */ - constructor(callbacks?: any); + setHandle(handle: FileHandle): void; /** - * @type {boolean} + * The max buffer size for the ReadStream. */ - get enabled(): boolean; + get highWaterMark(): number; /** - * Enable the async hook. - * @return {AsyncHook} + * Relative or absolute path of the underlying `FileHandle`. */ - enable(): AsyncHook; + get path(): any; /** - * Disables the async hook - * @return {AsyncHook} + * `true` if the stream is in a pending state. */ - disable(): AsyncHook; - #private; + get pending(): boolean; + _open(callback: any): Promise<any>; + _read(callback: any): Promise<any>; + } + export namespace ReadStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; } - export default createHook; - import { executionAsyncResource } from "socket:internal/async/hooks"; - import { executionAsyncId } from "socket:internal/async/hooks"; - import { triggerAsyncId } from "socket:internal/async/hooks"; - export { executionAsyncResource, executionAsyncId, triggerAsyncId }; -} - -declare module "socket:async/storage" { /** - * A container for storing values that remain present during - * asynchronous operations. + * A `Writable` stream for a `FileHandle`. */ - export class AsyncLocalStorage { - /** - * Binds function `fn` to run in the execution context of an - * anonymous `AsyncResource`. - * @param {function} fn - * @return {function} - */ - static bind(fn: Function): Function; - /** - * Captures the current async context and returns a function that runs - * a function in that execution context. - * @return {function} - */ - static snapshot(): Function; - /** - * @type {boolean} - */ - get enabled(): boolean; - /** - * Disables the `AsyncLocalStorage` instance. When disabled, - * `getStore()` will always return `undefined`. - */ - disable(): void; + export class WriteStream extends Writable { + start: any; + handle: any; + signal: any; + timeout: any; + bytesWritten: number; + shouldEmitClose: boolean; /** - * Enables the `AsyncLocalStorage` instance. + * Sets file handle for the WriteStream. + * @param {FileHandle} handle */ - enable(): void; + setHandle(handle: FileHandle): void; /** - * Enables and sets the `AsyncLocalStorage` instance default store value. - * @param {any} store + * The max buffer size for the Writetream. */ - enterWith(store: any): void; + get highWaterMark(): number; /** - * Runs function `fn` in the current asynchronous execution context with - * a given `store` value and arguments given to `fn`. - * @param {any} store - * @param {function} fn - * @param {...any} args - * @return {any} + * Relative or absolute path of the underlying `FileHandle`. */ - run(store: any, fn: Function, ...args: any[]): any; - exit(fn: any, ...args: any[]): any; + get path(): any; /** - * If the `AsyncLocalStorage` instance is enabled, it returns the current - * store value for this asynchronous execution context. - * @return {any|undefined} + * `true` if the stream is in a pending state. */ - getStore(): any | undefined; - #private; + get pending(): boolean; + _open(callback: any): Promise<any>; + _write(buffer: any, callback: any): any; } - export default AsyncLocalStorage; + export namespace WriteStream { + export { DEFAULT_STREAM_HIGH_WATER_MARK as highWaterMark }; + } + export const FileReadStream: typeof exports.ReadStream; + export const FileWriteStream: typeof exports.WriteStream; + export default exports; + export type FileHandle = import("socket:fs/handle").FileHandle; + import { Readable } from "socket:stream"; + import { Writable } from "socket:stream"; + import * as exports from "socket:fs/stream"; + } -declare module "socket:async/deferred" { +declare module "socket:fs/constants" { /** - * Dispatched when a `Deferred` internal promise is resolved. + * This flag can be used with uv_fs_copyfile() to return an error if the + * destination already exists. */ - export class DeferredResolveEvent extends Event { - /** - * `DeferredResolveEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {any=} [result] - */ - constructor(type?: string | undefined, result?: any | undefined); - /** - * The `Deferred` promise result value. - * @type {any?} - */ - result: any | null; - } + export const COPYFILE_EXCL: 1; /** - * Dispatched when a `Deferred` internal promise is rejected. + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, a fallback copy mechanism is used. */ - export class DeferredRejectEvent { - /** - * `DeferredRejectEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {Error=} [error] - */ - constructor(type?: string | undefined, error?: Error | undefined); - } + export const COPYFILE_FICLONE: 2; /** - * A utility class for creating deferred promises. + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, an error is returned. */ - export class Deferred extends EventTarget { - /** - * `Deferred` class constructor. - * @param {Deferred|Promise?} [promise] - */ - constructor(promise?: Deferred | (Promise<any> | null)); - /** - * Function to resolve the associated promise. - * @type {function} - */ - resolve: Function; - /** - * Function to reject the associated promise. - * @type {function} - */ - reject: Function; - /** - * Attaches a fulfillment callback and a rejection callback to the promise, - * and returns a new promise resolving to the return value of the called - * callback. - * @param {function(any)=} [resolve] - * @param {function(Error)=} [reject] - */ - then(resolve?: ((arg0: any) => any) | undefined, reject?: ((arg0: Error) => any) | undefined): Promise<any>; - /** - * Attaches a rejection callback to the promise, and returns a new promise - * resolving to the return value of the callback if it is called, or to its - * original fulfillment value if the promise is instead fulfilled. - * @param {function(Error)=} [callback] - */ - catch(callback?: ((arg0: Error) => any) | undefined): Promise<any>; - /** - * Attaches a callback for when the promise is settled (fulfilled or rejected). - * @param {function(any?)} [callback] - */ - finally(callback?: (arg0: any | null) => any): Promise<any>; - /** - * The promise associated with this Deferred instance. - * @type {Promise<any>} - */ - get promise(): Promise<any>; - /** - * A string representation of this Deferred instance. - * @type {string} - * @ignore - */ - get [Symbol.toStringTag](): string; - #private; - } - export default Deferred; + export const COPYFILE_FICLONE_FORCE: 4; + export const UV_DIRENT_UNKNOWN: any; + export const UV_DIRENT_FILE: any; + export const UV_DIRENT_DIR: any; + export const UV_DIRENT_LINK: any; + export const UV_DIRENT_FIFO: any; + export const UV_DIRENT_SOCKET: any; + export const UV_DIRENT_CHAR: any; + export const UV_DIRENT_BLOCK: any; + export const UV_FS_SYMLINK_DIR: any; + export const UV_FS_SYMLINK_JUNCTION: any; + export const UV_FS_O_FILEMAP: any; + export const O_RDONLY: any; + export const O_WRONLY: any; + export const O_RDWR: any; + export const O_APPEND: any; + export const O_ASYNC: any; + export const O_CLOEXEC: any; + export const O_CREAT: any; + export const O_DIRECT: any; + export const O_DIRECTORY: any; + export const O_DSYNC: any; + export const O_EXCL: any; + export const O_LARGEFILE: any; + export const O_NOATIME: any; + export const O_NOCTTY: any; + export const O_NOFOLLOW: any; + export const O_NONBLOCK: any; + export const O_NDELAY: any; + export const O_PATH: any; + export const O_SYNC: any; + export const O_TMPFILE: any; + export const O_TRUNC: any; + export const S_IFMT: any; + export const S_IFREG: any; + export const S_IFDIR: any; + export const S_IFCHR: any; + export const S_IFBLK: any; + export const S_IFIFO: any; + export const S_IFLNK: any; + export const S_IFSOCK: any; + export const S_IRWXU: any; + export const S_IRUSR: any; + export const S_IWUSR: any; + export const S_IXUSR: any; + export const S_IRWXG: any; + export const S_IRGRP: any; + export const S_IWGRP: any; + export const S_IXGRP: any; + export const S_IRWXO: any; + export const S_IROTH: any; + export const S_IWOTH: any; + export const S_IXOTH: any; + export const F_OK: any; + export const R_OK: any; + export const W_OK: any; + export const X_OK: any; + export default exports; + import * as exports from "socket:fs/constants"; + } -declare module "socket:async" { +declare module "socket:fs/flags" { + export function normalizeFlags(flags: any): any; export default exports; - import AsyncLocalStorage from "socket:async/storage"; - import AsyncResource from "socket:async/resource"; - import AsyncContext from "socket:async/context"; - import Deferred from "socket:async/deferred"; - import { executionAsyncResource } from "socket:async/hooks"; - import { executionAsyncId } from "socket:async/hooks"; - import { triggerAsyncId } from "socket:async/hooks"; - import { createHook } from "socket:async/hooks"; - import { AsyncHook } from "socket:async/hooks"; - import * as exports from "socket:async"; + import * as exports from "socket:fs/flags"; - export { AsyncLocalStorage, AsyncResource, AsyncContext, Deferred, executionAsyncResource, executionAsyncId, triggerAsyncId, createHook, AsyncHook }; } declare module "socket:diagnostics/channels" { @@ -5729,1268 +6151,848 @@ declare module "socket:fs/watcher" { declare module "socket:fs/promises" { /** - * Asynchronously check access a file. - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode} - * @param {string | Buffer | URL} path - * @param {string?} [mode] - * @param {object?} [options] - */ - export function access(path: string | Buffer | URL, mode?: string | null, options?: object | null): Promise<boolean>; - /** - * @see {@link https://nodejs.org/api/fs.html#fspromiseschmodpath-mode} - * @param {string | Buffer | URL} path - * @param {number} mode - * @returns {Promise<void>} - */ - export function chmod(path: string | Buffer | URL, mode: number): Promise<void>; - /** - * Changes ownership of file or directory at `path` with `uid` and `gid`. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @return {Promise} - */ - export function chown(path: string, uid: number, gid: number): Promise<any>; - /** - * Asynchronously copies `src` to `dest` calling `callback` upon success or error. - * @param {string} src - The source file path. - * @param {string} dest - The destination file path. - * @param {number} flags - Modifiers for copy operation. - * @return {Promise} - */ - export function copyFile(src: string, dest: string, flags?: number): Promise<any>; - /** - * Chages ownership of link at `path` with `uid` and `gid. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @return {Promise} - */ - export function lchown(path: string, uid: number, gid: number): Promise<any>; - /** - * Creates a link to `dest` from `dest`. - * @param {string} src - * @param {string} dest - * @return {Promise} - */ - export function link(src: string, dest: string): Promise<any>; - /** - * Asynchronously creates a directory. - * - * @param {string} path - The path to create - * @param {object} [options] - The optional options argument can be an integer specifying mode (permission and sticky bits), or an object with a mode property and a recursive property indicating whether parent directories should be created. Calling fs.mkdir() when path is a directory that exists results in an error only when recursive is false. - * @param {boolean} [options.recursive=false] - Recursively create missing path segments. - * @param {number} [options.mode=0o777] - Set the mode of directory, or missing path segments when recursive is true. - * @return {Promise<any>} - Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. - */ - export function mkdir(path: string, options?: { - recursive?: boolean; - mode?: number; - }): Promise<any>; - /** - * Asynchronously open a file. - * @see {@link https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode } - * - * @param {string | Buffer | URL} path - * @param {string=} flags - default: 'r' - * @param {number=} mode - default: 0o666 - * @return {Promise<FileHandle>} - */ - export function open(path: string | Buffer | URL, flags?: string | undefined, mode?: number | undefined): Promise<FileHandle>; - /** - * @see {@link https://nodejs.org/api/fs.html#fspromisesopendirpath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @param {string?} [options.encoding = 'utf8'] - * @param {number?} [options.bufferSize = 32] - * @return {Promise<Dir>} - */ - export function opendir(path: string | Buffer | URL, options?: object | null): Promise<Dir>; - /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options} - * @param {string | Buffer | URL} path - * @param {object?} options - * @param {string?} [options.encoding = 'utf8'] - * @param {boolean?} [options.withFileTypes = false] - */ - export function readdir(path: string | Buffer | URL, options: object | null): Promise<(string | Dirent)[]>; - /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options} - * @param {string} path - * @param {object?} [options] - * @param {(string|null)?} [options.encoding = null] - * @param {string?} [options.flag = 'r'] - * @param {AbortSignal?} [options.signal] - * @return {Promise<Buffer | string>} - */ - export function readFile(path: string, options?: object | null): Promise<Buffer | string>; - /** - * Reads link at `path` - * @param {string} path - * @return {Promise<string>} - */ - export function readlink(path: string): Promise<string>; - /** - * Computes real path for `path` - * @param {string} path - * @return {Promise<string>} - */ - export function realpath(path: string): Promise<string>; - /** - * Renames file or directory at `src` to `dest`. - * @param {string} src - * @param {string} dest - * @return {Promise} - */ - export function rename(src: string, dest: string): Promise<any>; - /** - * Removes directory at `path`. - * @param {string} path - * @return {Promise} - */ - export function rmdir(path: string): Promise<any>; - /** - * Get the stats of a file - * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @param {boolean?} [options.bigint = false] - * @return {Promise<Stats>} - */ - export function stat(path: string | Buffer | URL, options?: object | null): Promise<Stats>; - /** - * Get the stats of a symbolic link. - * @see {@link https://nodejs.org/api/fs.html#fspromiseslstatpath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @param {boolean?} [options.bigint = false] - * @return {Promise<Stats>} - */ - export function lstat(path: string | Buffer | URL, options?: object | null): Promise<Stats>; - /** - * Creates a symlink of `src` at `dest`. - * @param {string} src - * @param {string} dest - * @return {Promise} - */ - export function symlink(src: string, dest: string, type?: any): Promise<any>; - /** - * Unlinks (removes) file at `path`. - * @param {string} path - * @return {Promise} - */ - export function unlink(path: string): Promise<any>; - /** - * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options} - * @param {string | Buffer | URL | FileHandle} path - filename or FileHandle - * @param {string|Buffer|Array|DataView|TypedArray} data - * @param {object?} [options] - * @param {string|null} [options.encoding = 'utf8'] - * @param {number} [options.mode = 0o666] - * @param {string} [options.flag = 'w'] - * @param {AbortSignal?} [options.signal] - * @return {Promise<void>} - */ - export function writeFile(path: string | Buffer | URL | FileHandle, data: string | Buffer | any[] | DataView | TypedArray, options?: object | null): Promise<void>; - /** - * Watch for changes at `path` calling `callback` - * @param {string} - * @param {function|object=} [options] - * @param {string=} [options.encoding = 'utf8'] - * @param {AbortSignal=} [options.signal] - * @return {Watcher} - */ - export function watch(path: any, options?: (Function | object) | undefined): Watcher; - export type Stats = any; - export default exports; - export type Buffer = import("socket:buffer").Buffer; - export type TypedArray = Uint8Array | Int8Array; - import { FileHandle } from "socket:fs/handle"; - import { Dir } from "socket:fs/dir"; - import { Dirent } from "socket:fs/dir"; - import { Stats } from "socket:fs/stats"; - import { Watcher } from "socket:fs/watcher"; - import bookmarks from "socket:fs/bookmarks"; - import * as constants from "socket:fs/constants"; - import { DirectoryHandle } from "socket:fs/handle"; - import fds from "socket:fs/fds"; - import { ReadStream } from "socket:fs/stream"; - import { WriteStream } from "socket:fs/stream"; - import * as exports from "socket:fs/promises"; - - export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, ReadStream, Watcher, WriteStream }; -} - -declare module "socket:fs/index" { - /** - * Asynchronously check access a file for a given mode calling `callback` - * upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} - * @param {string | Buffer | URL} path - * @param {string?|function(Error?)?} [mode = F_OK(0)] - * @param {function(Error?)?} [callback] - */ - export function access(path: string | Buffer | URL, mode: any, callback?: ((arg0: Error | null) => any) | null): void; - /** - * Synchronously check access a file for a given mode calling `callback` - * upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} - * @param {string | Buffer | URL} path - * @param {string?} [mode = F_OK(0)] - */ - export function accessSync(path: string | Buffer | URL, mode?: string | null): boolean; - /** - * Checks if a path exists - * @param {string | Buffer | URL} path - * @param {function(Boolean)?} [callback] - */ - export function exists(path: string | Buffer | URL, callback?: ((arg0: boolean) => any) | null): void; - /** - * Checks if a path exists - * @param {string | Buffer | URL} path - * @param {function(Boolean)?} [callback] - */ - export function existsSync(path: string | Buffer | URL): boolean; - /** - * Asynchronously changes the permissions of a file. - * No arguments other than a possible exception are given to the completion callback - * - * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} - * + * Asynchronously check access a file. + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode} * @param {string | Buffer | URL} path - * @param {number} mode - * @param {function(Error?)} callback + * @param {string?} [mode] + * @param {object?} [options] */ - export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): void; + export function access(path: string | Buffer | URL, mode?: string | null, options?: object | null): Promise<boolean>; /** - * Synchronously changes the permissions of a file. - * - * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * @see {@link https://nodejs.org/api/fs.html#fspromiseschmodpath-mode} * @param {string | Buffer | URL} path * @param {number} mode + * @returns {Promise<void>} */ - export function chmodSync(path: string | Buffer | URL, mode: number): void; - /** - * Changes ownership of file or directory at `path` with `uid` and `gid`. - * @param {string} path - * @param {number} uid - * @param {number} gid - * @param {function} callback - */ - export function chown(path: string, uid: number, gid: number, callback: Function): void; + export function chmod(path: string | Buffer | URL, mode: number): Promise<void>; /** * Changes ownership of file or directory at `path` with `uid` and `gid`. * @param {string} path * @param {number} uid * @param {number} gid + * @return {Promise} */ - export function chownSync(path: string, uid: number, gid: number): void; - /** - * Asynchronously close a file descriptor calling `callback` upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsclosefd-callback} - * @param {number} fd - * @param {function(Error?)?} [callback] - */ - export function close(fd: number, callback?: ((arg0: Error | null) => any) | null): void; - /** - * Synchronously close a file descriptor. - * @param {number} fd - fd - */ - export function closeSync(fd: number): void; + export function chown(path: string, uid: number, gid: number): Promise<any>; /** * Asynchronously copies `src` to `dest` calling `callback` upon success or error. * @param {string} src - The source file path. * @param {string} dest - The destination file path. * @param {number} flags - Modifiers for copy operation. - * @param {function(Error=)=} [callback] - The function to call after completion. - * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} - */ - export function copyFile(src: string, dest: string, flags?: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; - /** - * Synchronously copies `src` to `dest` calling `callback` upon success or error. - * @param {string} src - The source file path. - * @param {string} dest - The destination file path. - * @param {number} flags - Modifiers for copy operation. - * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} - */ - export function copyFileSync(src: string, dest: string, flags?: number): void; - /** - * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @returns {ReadStream} - */ - export function createReadStream(path: string | Buffer | URL, options?: object | null): ReadStream; - /** - * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} - * @param {string | Buffer | URL} path - * @param {object?} [options] - * @returns {WriteStream} - */ - export function createWriteStream(path: string | Buffer | URL, options?: object | null): WriteStream; - /** - * Invokes the callback with the <fs.Stats> for the file descriptor. See - * the POSIX fstat(2) documentation for more detail. - * - * @see {@link https://nodejs.org/api/fs.html#fsfstatfd-options-callback} - * - * @param {number} fd - A file descriptor. - * @param {object?|function?} [options] - An options object. - * @param {function?} callback - The function to call after completion. - */ - export function fstat(fd: number, options: any, callback: Function | null): void; - /** - * Request that all data for the open file descriptor is flushed - * to the storage device. - * @param {number} fd - A file descriptor. - * @param {function} callback - The function to call after completion. - */ - export function fsync(fd: number, callback: Function): void; - /** - * Truncates the file up to `offset` bytes. - * @param {number} fd - A file descriptor. - * @param {number=|function} [offset = 0] - * @param {function?} callback - The function to call after completion. + * @return {Promise} */ - export function ftruncate(fd: number, offset: any, callback: Function | null): void; + export function copyFile(src: string, dest: string, flags?: number): Promise<any>; /** * Chages ownership of link at `path` with `uid` and `gid. * @param {string} path * @param {number} uid * @param {number} gid - * @param {function} callback + * @return {Promise} */ - export function lchown(path: string, uid: number, gid: number, callback: Function): void; + export function lchown(path: string, uid: number, gid: number): Promise<any>; /** - * Creates a link to `dest` from `src`. + * Creates a link to `dest` from `dest`. * @param {string} src * @param {string} dest - * @param {function} - */ - export function link(src: string, dest: string, callback: any): void; - /** - * @ignore - */ - export function mkdir(path: any, options: any, callback: any): void; - /** - * @ignore - * @param {string|URL} path - * @param {object=} [options] + * @return {Promise} */ - export function mkdirSync(path: string | URL, options?: object | undefined): void; + export function link(src: string, dest: string): Promise<any>; /** - * Asynchronously open a file calling `callback` upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} - * @param {string | Buffer | URL} path - * @param {string?} [flags = 'r'] - * @param {string?} [mode = 0o666] - * @param {object?|function?} [options] - * @param {function(Error?, number?)?} [callback] + * Asynchronously creates a directory. + * + * @param {string} path - The path to create + * @param {object} [options] - The optional options argument can be an integer specifying mode (permission and sticky bits), or an object with a mode property and a recursive property indicating whether parent directories should be created. Calling fs.mkdir() when path is a directory that exists results in an error only when recursive is false. + * @param {boolean} [options.recursive=false] - Recursively create missing path segments. + * @param {number} [options.mode=0o777] - Set the mode of directory, or missing path segments when recursive is true. + * @return {Promise<any>} - Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. */ - export function open(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any, callback?: ((arg0: Error | null, arg1: number | null) => any) | null): void; + export function mkdir(path: string, options?: { + recursive?: boolean; + mode?: number; + }): Promise<any>; /** - * Synchronously open a file. + * Asynchronously open a file. + * @see {@link https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode } + * * @param {string | Buffer | URL} path - * @param {string?} [flags = 'r'] - * @param {string?} [mode = 0o666] - * @param {object?|function?} [options] + * @param {string=} flags - default: 'r' + * @param {number=} mode - default: 0o666 + * @return {Promise<FileHandle>} */ - export function openSync(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any): any; + export function open(path: string | Buffer | URL, flags?: string | undefined, mode?: number | undefined): Promise<FileHandle>; /** - * Asynchronously open a directory calling `callback` upon success or error. - * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @see {@link https://nodejs.org/api/fs.html#fspromisesopendirpath-options} * @param {string | Buffer | URL} path - * @param {object?|function(Error?, Dir?)} [options] + * @param {object?} [options] * @param {string?} [options.encoding = 'utf8'] - * @param {boolean?} [options.withFileTypes = false] - * @param {function(Error?, Dir?)?} callback + * @param {number?} [options.bufferSize = 32] + * @return {Promise<Dir>} */ - export function opendir(path: string | Buffer | URL, options: {}, callback: ((arg0: Error | null, arg1: Dir | null) => any) | null): void; + export function opendir(path: string | Buffer | URL, options?: object | null): Promise<Dir>; /** - * Synchronously open a directory. - * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options} * @param {string | Buffer | URL} path - * @param {object?|function(Error?, Dir?)} [options] + * @param {object?} options * @param {string?} [options.encoding = 'utf8'] * @param {boolean?} [options.withFileTypes = false] - * @return {Dir} - */ - export function opendirSync(path: string | Buffer | URL, options?: {}): Dir; - /** - * Asynchronously read from an open file descriptor. - * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} - * @param {number} fd - * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. - * @param {number} offset - The position in buffer to write the data to. - * @param {number} length - The number of bytes to read. - * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. - * @param {function(Error?, number?, Buffer?)} callback - */ - export function read(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; - /** - * Asynchronously write to an open file descriptor. - * @see {@link https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback} - * @param {number} fd - * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. - * @param {number} offset - The position in buffer to write the data to. - * @param {number} length - The number of bytes to read. - * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. - * @param {function(Error?, number?, Buffer?)} callback - */ - export function write(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; - /** - * Asynchronously read all entries in a directory. - * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} - * @param {string | Buffer | URL } path - * @param {object?|function(Error?, object[])} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {boolean?} [options.withFileTypes ? false] - * @param {function(Error?, object[])} callback - */ - export function readdir(path: string | Buffer | URL, options: {}, callback: (arg0: Error | null, arg1: object[]) => any): void; - /** - * Synchronously read all entries in a directory. - * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} - * @param {string | Buffer | URL } path - * @param {object?|function(Error?, object[])} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {boolean?} [options.withFileTypes ? false] - */ - export function readdirSync(path: string | Buffer | URL, options?: {}): any[]; - /** - * @param {string | Buffer | URL | number } path - * @param {object?|function(Error?, Buffer?)} [options] - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Buffer?)} callback */ - export function readFile(path: string | Buffer | URL | number, options: {}, callback: (arg0: Error | null, arg1: Buffer | null) => any): void; + export function readdir(path: string | Buffer | URL, options: object | null): Promise<(string | Dirent)[]>; /** - * @param {string|Buffer|URL|number} path - * @param {{ encoding?: string = 'utf8', flags?: string = 'r'}} [options] - * @param {object?|function(Error?, Buffer?)} [options] + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options} + * @param {string} path + * @param {object?} [options] + * @param {(string|null)?} [options.encoding = null] + * @param {string?} [options.flag = 'r'] * @param {AbortSignal?} [options.signal] + * @return {Promise<Buffer | string>} */ - export function readFileSync(path: string | Buffer | URL | number, options?: { - encoding?: string; - flags?: string; - }): any; + export function readFile(path: string, options?: object | null): Promise<Buffer | string>; /** * Reads link at `path` * @param {string} path - * @param {function(err, string)} callback - */ - export function readlink(path: string, callback: (arg0: err, arg1: string) => any): void; - /** - * Computes real path for `path` - * @param {string} path - * @param {function(err, string)} callback + * @return {Promise<string>} */ - export function realpath(path: string, callback: (arg0: err, arg1: string) => any): void; + export function readlink(path: string): Promise<string>; /** * Computes real path for `path` * @param {string} path + * @return {Promise<string>} */ - export function realpathSync(path: string): void; - /** - * Renames file or directory at `src` to `dest`. - * @param {string} src - * @param {string} dest - * @param {function} callback - */ - export function rename(src: string, dest: string, callback: Function): void; - /** - * Renames file or directory at `src` to `dest`, synchronously. - * @param {string} src - * @param {string} dest - */ - export function renameSync(src: string, dest: string): void; - /** - * Removes directory at `path`. - * @param {string} path - * @param {function} callback - */ - export function rmdir(path: string, callback: Function): void; + export function realpath(path: string): Promise<string>; /** - * Removes directory at `path`, synchronously. - * @param {string} path + * Renames file or directory at `src` to `dest`. + * @param {string} src + * @param {string} dest + * @return {Promise} */ - export function rmdirSync(path: string): void; + export function rename(src: string, dest: string): Promise<any>; /** - * Synchronously get the stats of a file - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] + * Removes directory at `path`. + * @param {string} path + * @return {Promise} */ - export function statSync(path: string | Buffer | URL | number, options?: object | null): promises.Stats; + export function rmdir(path: string): Promise<any>; /** * Get the stats of a file - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Stats?)} callback + * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {boolean?} [options.bigint = false] + * @return {Promise<Stats>} */ - export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + export function stat(path: string | Buffer | URL, options?: object | null): Promise<Stats>; /** - * Get the stats of a symbolic link - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.flag ? 'r'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?, Stats?)} callback + * Get the stats of a symbolic link. + * @see {@link https://nodejs.org/api/fs.html#fspromiseslstatpath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @param {boolean?} [options.bigint = false] + * @return {Promise<Stats>} */ - export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + export function lstat(path: string | Buffer | URL, options?: object | null): Promise<Stats>; /** * Creates a symlink of `src` at `dest`. * @param {string} src * @param {string} dest + * @return {Promise} */ - export function symlink(src: string, dest: string, type: any, callback: any): void; + export function symlink(src: string, dest: string, type?: any): Promise<any>; /** * Unlinks (removes) file at `path`. * @param {string} path - * @param {function} callback - */ - export function unlink(path: string, callback: Function): void; - /** - * Unlinks (removes) file at `path`, synchronously. - * @param {string} path - */ - export function unlinkSync(path: string): void; - /** - * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {string | Buffer | TypedArray | DataView | object } data - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.mode ? 0o666] - * @param {string?} [options.flag ? 'w'] - * @param {AbortSignal?} [options.signal] - * @param {function(Error?)} callback + * @return {Promise} */ - export function writeFile(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null, callback: (arg0: Error | null) => any): void; + export function unlink(path: string): Promise<any>; /** - * Writes data to a file synchronously. - * @param {string | Buffer | URL | number } path - filename or file descriptor - * @param {string | Buffer | TypedArray | DataView | object } data - * @param {object?} options - * @param {string?} [options.encoding ? 'utf8'] - * @param {string?} [options.mode ? 0o666] - * @param {string?} [options.flag ? 'w'] + * @see {@link https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options} + * @param {string | Buffer | URL | FileHandle} path - filename or FileHandle + * @param {string|Buffer|Array|DataView|TypedArray} data + * @param {object?} [options] + * @param {string|null} [options.encoding = 'utf8'] + * @param {number} [options.mode = 0o666] + * @param {string} [options.flag = 'w'] * @param {AbortSignal?} [options.signal] - * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} + * @return {Promise<void>} */ - export function writeFileSync(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null): void; + export function writeFile(path: string | Buffer | URL | FileHandle, data: string | Buffer | any[] | DataView | TypedArray, options?: object | null): Promise<void>; /** * Watch for changes at `path` calling `callback` * @param {string} * @param {function|object=} [options] * @param {string=} [options.encoding = 'utf8'] - * @param {?function} [callback] + * @param {AbortSignal=} [options.signal] * @return {Watcher} */ - export function watch(path: any, options?: (Function | object) | undefined, callback?: Function | null): Watcher; + export function watch(path: any, options?: (Function | object) | undefined): Watcher; + export type Stats = any; export default exports; export type Buffer = import("socket:buffer").Buffer; export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - import { ReadStream } from "socket:fs/stream"; - import { WriteStream } from "socket:fs/stream"; + import { FileHandle } from "socket:fs/handle"; import { Dir } from "socket:fs/dir"; - import * as promises from "socket:fs/promises"; + import { Dirent } from "socket:fs/dir"; import { Stats } from "socket:fs/stats"; import { Watcher } from "socket:fs/watcher"; import bookmarks from "socket:fs/bookmarks"; import * as constants from "socket:fs/constants"; import { DirectoryHandle } from "socket:fs/handle"; - import { Dirent } from "socket:fs/dir"; import fds from "socket:fs/fds"; - import { FileHandle } from "socket:fs/handle"; - import * as exports from "socket:fs/index"; + import { ReadStream } from "socket:fs/stream"; + import { WriteStream } from "socket:fs/stream"; + import * as exports from "socket:fs/promises"; - export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; -} - -declare module "socket:fs" { - export * from "socket:fs/index"; - export default exports; - import * as exports from "socket:fs/index"; -} - -declare module "socket:external/libsodium/index" { - const _default: any; - export default _default; -} - -declare module "socket:crypto/sodium" { - export {}; + export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, ReadStream, Watcher, WriteStream }; } -declare module "socket:crypto" { +declare module "socket:fs/index" { + /** + * Asynchronously check access a file for a given mode calling `callback` + * upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?|function(Error?)?} [mode = F_OK(0)] + * @param {function(Error?)?} [callback] + */ + export function access(path: string | Buffer | URL, mode: any, callback?: ((arg0: Error | null) => any) | null): void; + /** + * Synchronously check access a file for a given mode calling `callback` + * upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?} [mode = F_OK(0)] + */ + export function accessSync(path: string | Buffer | URL, mode?: string | null): boolean; + /** + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] + */ + export function exists(path: string | Buffer | URL, callback?: ((arg0: boolean) => any) | null): void; + /** + * Checks if a path exists + * @param {string | Buffer | URL} path + * @param {function(Boolean)?} [callback] + */ + export function existsSync(path: string | Buffer | URL): boolean; + /** + * Asynchronously changes the permissions of a file. + * No arguments other than a possible exception are given to the completion callback + * + * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * + * @param {string | Buffer | URL} path + * @param {number} mode + * @param {function(Error?)} callback + */ + export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): void; + /** + * Synchronously changes the permissions of a file. + * + * @see {@link https://nodejs.org/api/fs.html#fschmodpath-mode-callback} + * @param {string | Buffer | URL} path + * @param {number} mode + */ + export function chmodSync(path: string | Buffer | URL, mode: number): void; + /** + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid + * @param {function} callback + */ + export function chown(path: string, uid: number, gid: number, callback: Function): void; + /** + * Changes ownership of file or directory at `path` with `uid` and `gid`. + * @param {string} path + * @param {number} uid + * @param {number} gid + */ + export function chownSync(path: string, uid: number, gid: number): void; + /** + * Asynchronously close a file descriptor calling `callback` upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsclosefd-callback} + * @param {number} fd + * @param {function(Error?)?} [callback] + */ + export function close(fd: number, callback?: ((arg0: Error | null) => any) | null): void; + /** + * Synchronously close a file descriptor. + * @param {number} fd - fd + */ + export function closeSync(fd: number): void; + /** + * Asynchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @param {function(Error=)=} [callback] - The function to call after completion. + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} + */ + export function copyFile(src: string, dest: string, flags?: number, callback?: ((arg0: Error | undefined) => any) | undefined): void; + /** + * Synchronously copies `src` to `dest` calling `callback` upon success or error. + * @param {string} src - The source file path. + * @param {string} dest - The destination file path. + * @param {number} flags - Modifiers for copy operation. + * @see {@link https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback} + */ + export function copyFileSync(src: string, dest: string, flags?: number): void; + /** + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @returns {ReadStream} + */ + export function createReadStream(path: string | Buffer | URL, options?: object | null): ReadStream; + /** + * @see {@link https://nodejs.org/api/fs.html#fscreatewritestreampath-options} + * @param {string | Buffer | URL} path + * @param {object?} [options] + * @returns {WriteStream} + */ + export function createWriteStream(path: string | Buffer | URL, options?: object | null): WriteStream; + /** + * Invokes the callback with the <fs.Stats> for the file descriptor. See + * the POSIX fstat(2) documentation for more detail. + * + * @see {@link https://nodejs.org/api/fs.html#fsfstatfd-options-callback} + * + * @param {number} fd - A file descriptor. + * @param {object?|function?} [options] - An options object. + * @param {function?} callback - The function to call after completion. + */ + export function fstat(fd: number, options: any, callback: Function | null): void; + /** + * Request that all data for the open file descriptor is flushed + * to the storage device. + * @param {number} fd - A file descriptor. + * @param {function} callback - The function to call after completion. + */ + export function fsync(fd: number, callback: Function): void; + /** + * Truncates the file up to `offset` bytes. + * @param {number} fd - A file descriptor. + * @param {number=|function} [offset = 0] + * @param {function?} callback - The function to call after completion. + */ + export function ftruncate(fd: number, offset: any, callback: Function | null): void; + /** + * Chages ownership of link at `path` with `uid` and `gid. + * @param {string} path + * @param {number} uid + * @param {number} gid + * @param {function} callback + */ + export function lchown(path: string, uid: number, gid: number, callback: Function): void; /** - * Generate cryptographically strong random values into the `buffer` - * @param {TypedArray} buffer - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues} - * @return {TypedArray} + * Creates a link to `dest` from `src`. + * @param {string} src + * @param {string} dest + * @param {function} */ - export function getRandomValues(buffer: TypedArray, ...args: any[]): TypedArray; + export function link(src: string, dest: string, callback: any): void; /** - * Generate a random 64-bit number. - * @returns {BigInt} - A random 64-bit number. + * @ignore */ - export function rand64(): BigInt; + export function mkdir(path: any, options: any, callback: any): void; /** - * Generate `size` random bytes. - * @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1. - * @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes. + * @ignore + * @param {string|URL} path + * @param {object=} [options] */ - export function randomBytes(size: number): Buffer; + export function mkdirSync(path: string | URL, options?: object | undefined): void; /** - * @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512` - * @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview. - * @returns {Promise<Buffer>} - A promise that resolves with an instance of socket.Buffer with the hash. + * Asynchronously open a file calling `callback` upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback} + * @param {string | Buffer | URL} path + * @param {string?} [flags = 'r'] + * @param {string?} [mode = 0o666] + * @param {object?|function?} [options] + * @param {function(Error?, number?)?} [callback] */ - export function createDigest(algorithm: string, buf: any): Promise<Buffer>; + export function open(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any, callback?: ((arg0: Error | null, arg1: number | null) => any) | null): void; /** - * A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c - * that works on strings and `ArrayBuffer` views (typed arrays) - * @param {string|Uint8Array|ArrayBuffer} value - * @param {number=} [seed = 0] - * @return {number} + * Synchronously open a file. + * @param {string | Buffer | URL} path + * @param {string?} [flags = 'r'] + * @param {string?} [mode = 0o666] + * @param {object?|function?} [options] */ - export function murmur3(value: string | Uint8Array | ArrayBuffer, seed?: number | undefined): number; + export function openSync(path: string | Buffer | URL, flags?: string | null, mode?: string | null, options?: any): any; /** - * @typedef {Uint8Array|Int8Array} TypedArray + * Asynchronously open a directory calling `callback` upon success or error. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL} path + * @param {object?|function(Error?, Dir?)} [options] + * @param {string?} [options.encoding = 'utf8'] + * @param {boolean?} [options.withFileTypes = false] + * @param {function(Error?, Dir?)?} callback */ + export function opendir(path: string | Buffer | URL, options: {}, callback: ((arg0: Error | null, arg1: Dir | null) => any) | null): void; /** - * WebCrypto API - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto} + * Synchronously open a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL} path + * @param {object?|function(Error?, Dir?)} [options] + * @param {string?} [options.encoding = 'utf8'] + * @param {boolean?} [options.withFileTypes = false] + * @return {Dir} */ - export let webcrypto: any; + export function opendirSync(path: string | Buffer | URL, options?: {}): Dir; /** - * A promise that resolves when all internals to be loaded/ready. - * @type {Promise} + * Asynchronously read from an open file descriptor. + * @see {@link https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback} + * @param {number} fd + * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. + * @param {number} offset - The position in buffer to write the data to. + * @param {number} length - The number of bytes to read. + * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. + * @param {function(Error?, number?, Buffer?)} callback */ - export const ready: Promise<any>; + export function read(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; /** - * Maximum total size of random bytes per page + * Asynchronously write to an open file descriptor. + * @see {@link https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback} + * @param {number} fd + * @param {object | Buffer | TypedArray} buffer - The buffer that the data will be written to. + * @param {number} offset - The position in buffer to write the data to. + * @param {number} length - The number of bytes to read. + * @param {number | BigInt | null} position - Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. + * @param {function(Error?, number?, Buffer?)} callback */ - export const RANDOM_BYTES_QUOTA: number; + export function write(fd: number, buffer: object | Buffer | TypedArray, offset: number, length: number, position: number | BigInt | null, options: any, callback: (arg0: Error | null, arg1: number | null, arg2: Buffer | null) => any): void; /** - * Maximum total size for random bytes. + * Asynchronously read all entries in a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL } path + * @param {object?|function(Error?, object[])} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {boolean?} [options.withFileTypes ? false] + * @param {function(Error?, object[])} callback */ - export const MAX_RANDOM_BYTES: 281474976710655; + export function readdir(path: string | Buffer | URL, options: {}, callback: (arg0: Error | null, arg1: object[]) => any): void; /** - * Maximum total amount of allocated per page of bytes (max/quota) + * Synchronously read all entries in a directory. + * @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback} + * @param {string | Buffer | URL } path + * @param {object?|function(Error?, object[])} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {boolean?} [options.withFileTypes ? false] */ - export const MAX_RANDOM_BYTES_PAGES: number; - export default exports; - export type TypedArray = Uint8Array | Int8Array; - import { Buffer } from "socket:buffer"; - export namespace sodium { - let ready: Promise<any>; - } - import * as exports from "socket:crypto"; - -} - -declare module "socket:ai" { + export function readdirSync(path: string | Buffer | URL, options?: {}): any[]; /** - * A class to interact with large language models (using llama.cpp) + * @param {string | Buffer | URL | number } path + * @param {object?|function(Error?, Buffer?)} [options] + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Buffer?)} callback */ - export class LLM extends EventEmitter { - /** - * Constructs an LLM instance. Each parameter is designed to configure and control - * the behavior of the underlying large language model provided by llama.cpp. - * @param {Object} options - Configuration options for the LLM instance. - * @param {string} options.path - The file path to the model in .gguf format. This model file contains - * the weights and configuration necessary for initializing the language model. - * @param {string} options.prompt - The initial input text to the model, setting the context or query - * for generating responses. The model uses this as a starting point for text generation. - * @param {string} [options.id] - An optional unique identifier for this specific instance of the model, - * useful for tracking or referencing the model in multi-model setups. - * @param {number} [options.n_ctx=1024] - Specifies the maximum number of tokens that the model can consider - * for a single query. This is crucial for managing memory and computational - * efficiency. Exceeding the model's configuration may lead to errors or truncated outputs. - * @param {number} [options.n_threads=8] - The number of threads allocated for the model's computation, - * affecting performance and speed of response generation. - * @param {number} [options.temp=1.1] - Sampling temperature controls the randomness of predictions. - * Higher values increase diversity, potentially at the cost of coherence. - * @param {number} [options.max_tokens=512] - The upper limit on the number of tokens that the model can generate - * in response to a single prompt. This prevents runaway generations. - * @param {number} [options.n_gpu_layers=32] - The number of GPU layers dedicated to the model processing. - * More layers can increase accuracy and complexity of the outputs. - * @param {number} [options.n_keep=0] - Determines how many of the top generated responses are retained after - * the initial generation phase. Useful for models that generate multiple outputs. - * @param {number} [options.n_batch=0] - The size of processing batches. Larger batch sizes can reduce - * the time per token generation by parallelizing computations. - * @param {number} [options.n_predict=0] - Specifies how many forward predictions the model should make - * from the current state. This can pre-generate responses or calculate probabilities. - * @param {number} [options.grp_attn_n=0] - Group attention parameter 'N' modifies how attention mechanisms - * within the model are grouped and interact, affecting the model’s focus and accuracy. - * @param {number} [options.grp_attn_w=0] - Group attention parameter 'W' adjusts the width of each attention group, - * influencing the breadth of context considered by each attention group. - * @param {number} [options.seed=0] - A seed for the random number generator used in the model. Setting this ensures - * consistent results in model outputs, important for reproducibility in experiments. - * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, - * reducing the risk of less likely, potentially nonsensical outputs. - * @param {number} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool - * to only those whose cumulative probability exceeds this value, enhancing output relevance. - * @param {number} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring - * that generated tokens have at least this likelihood of being relevant or coherent. - * @param {number} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how - * the model weights novel or unseen prompts during generation. - * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. - */ - constructor(options?: { - path: string; - prompt: string; - id?: string; - n_ctx?: number; - n_threads?: number; - temp?: number; - max_tokens?: number; - n_gpu_layers?: number; - n_keep?: number; - n_batch?: number; - n_predict?: number; - grp_attn_n?: number; - grp_attn_w?: number; - seed?: number; - top_k?: number; - tok_p?: number; - min_p?: number; - tfs_z?: number; - }); - path: string; - prompt: string; - id: string | BigInt; - /** - * Tell the LLM to stop after the next token. - * @returns {Promise<void>} A promise that resolves when the LLM stops. - */ - stop(): Promise<void>; - /** - * Send a message to the chat. - * @param {string} message - The message to send to the chat. - * @returns {Promise<any>} A promise that resolves with the response from the chat. - */ - chat(message: string): Promise<any>; - } - export default exports; - import { EventEmitter } from "socket:events"; - import * as exports from "socket:ai"; - -} - -declare module "socket:window/constants" { - export const WINDOW_ERROR: -1; - export const WINDOW_NONE: 0; - export const WINDOW_CREATING: 10; - export const WINDOW_CREATED: 11; - export const WINDOW_HIDING: 20; - export const WINDOW_HIDDEN: 21; - export const WINDOW_SHOWING: 30; - export const WINDOW_SHOWN: 31; - export const WINDOW_CLOSING: 40; - export const WINDOW_CLOSED: 41; - export const WINDOW_EXITING: 50; - export const WINDOW_EXITED: 51; - export const WINDOW_KILLING: 60; - export const WINDOW_KILLED: 61; - export default exports; - import * as exports from "socket:window/constants"; - -} - -declare module "socket:application/client" { + export function readFile(path: string | Buffer | URL | number, options: {}, callback: (arg0: Error | null, arg1: Buffer | null) => any): void; + /** + * @param {string|Buffer|URL|number} path + * @param {{ encoding?: string = 'utf8', flags?: string = 'r'}} [options] + * @param {object?|function(Error?, Buffer?)} [options] + * @param {AbortSignal?} [options.signal] + */ + export function readFileSync(path: string | Buffer | URL | number, options?: { + encoding?: string; + flags?: string; + }): any; + /** + * Reads link at `path` + * @param {string} path + * @param {function(err, string)} callback + */ + export function readlink(path: string, callback: (arg0: err, arg1: string) => any): void; /** - * @typedef {{ - * id?: string | null, - * type?: 'window' | 'worker', - * parent?: object | null, - * top?: object | null, - * frameType?: 'top-level' | 'nested' | 'none' - * }} ClientState + * Computes real path for `path` + * @param {string} path + * @param {function(err, string)} callback */ - export class Client { - /** - * `Client` class constructor - * @private - * @param {ClientState} state - */ - private constructor(); - /** - * The unique ID of the client. - * @type {string|null} - */ - get id(): string; - /** - * The frame type of the client. - * @type {'top-level'|'nested'|'none'} - */ - get frameType(): "none" | "top-level" | "nested"; - /** - * The type of the client. - * @type {'window'|'worker'} - */ - get type(): "window" | "worker"; - /** - * The parent client of the client. - * @type {Client|null} - */ - get parent(): Client; - /** - * The top client of the client. - * @type {Client|null} - */ - get top(): Client; - /** - * A readonly `URL` of the current location of this client. - * @type {URL} - */ - get location(): URL; - /** - * Converts this `Client` instance to JSON. - * @return {object} - */ - toJSON(): object; - #private; - } - const _default: any; - export default _default; - export type ClientState = { - id?: string | null; - type?: "window" | "worker"; - parent?: object | null; - top?: object | null; - frameType?: "top-level" | "nested" | "none"; - }; -} - -declare module "socket:application/menu" { + export function realpath(path: string, callback: (arg0: err, arg1: string) => any): void; /** - * Internal IPC for setting an application menu - * @ignore + * Computes real path for `path` + * @param {string} path */ - export function setMenu(options: any, type: any): Promise<ipc.Result>; + export function realpathSync(path: string): void; /** - * Internal IPC for setting an application context menu - * @ignore + * Renames file or directory at `src` to `dest`. + * @param {string} src + * @param {string} dest + * @param {function} callback */ - export function setContextMenu(options: any): Promise<any>; + export function rename(src: string, dest: string, callback: Function): void; /** - * A `Menu` is base class for a `ContextMenu`, `SystemMenu`, or `TrayMenu`. + * Renames file or directory at `src` to `dest`, synchronously. + * @param {string} src + * @param {string} dest */ - export class Menu extends EventTarget { - /** - * `Menu` class constructor. - * @ignore - * @param {string} type - */ - constructor(type: string); - /** - * The broadcast channel for this menu. - * @ignore - * @type {BroadcastChannel} - */ - get channel(): BroadcastChannel; - /** - * The `Menu` instance type. - * @type {('context'|'system'|'tray')?} - */ - get type(): "tray" | "system" | "context"; - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * Set the menu layout for this `Menu` instance. - * @param {string|object} layoutOrOptions - * @param {object=} [options] - */ - set(layoutOrOptions: string | object, options?: object | undefined): Promise<any>; - #private; - } + export function renameSync(src: string, dest: string): void; /** - * A container for various `Menu` instances. + * Removes directory at `path`. + * @param {string} path + * @param {function} callback */ - export class MenuContainer extends EventTarget { - /** - * `MenuContainer` class constructor. - * @param {EventTarget} [sourceEventTarget] - * @param {object=} [options] - */ - constructor(sourceEventTarget?: EventTarget, options?: object | undefined); - /** - * Setter for the level 1 'error'` event listener. - * @ignore - * @type {function(ErrorEvent)?} - */ - set onerror(onerror: (arg0: ErrorEvent) => any); - /** - * Level 1 'error'` event listener. - * @type {function(ErrorEvent)?} - */ - get onerror(): (arg0: ErrorEvent) => any; - /** - * Setter for the level 1 'menuitem'` event listener. - * @ignore - * @type {function(MenuItemEvent)?} - */ - set onmenuitem(onmenuitem: (arg0: menuitemEvent) => any); - /** - * Level 1 'menuitem'` event listener. - * @type {function(menuitemEvent)?} - */ - get onmenuitem(): (arg0: menuitemEvent) => any; - /** - * The `TrayMenu` instance for the application. - * @type {TrayMenu} - */ - get tray(): TrayMenu; - /** - * The `SystemMenu` instance for the application. - * @type {SystemMenu} - */ - get system(): SystemMenu; - /** - * The `ContextMenu` instance for the application. - * @type {ContextMenu} - */ - get context(): ContextMenu; - #private; - } + export function rmdir(path: string, callback: Function): void; /** - * A `Menu` instance that represents a context menu. + * Removes directory at `path`, synchronously. + * @param {string} path */ - export class ContextMenu extends Menu { - constructor(); - } + export function rmdirSync(path: string): void; /** - * A `Menu` instance that represents the system menu. + * Synchronously get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + */ + export function statSync(path: string | Buffer | URL | number, options?: object | null): promises.Stats; + /** + * Get the stats of a file + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback + */ + export function stat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + /** + * Get the stats of a symbolic link + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.flag ? 'r'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?, Stats?)} callback + */ + export function lstat(path: string | Buffer | URL | number, options: object | null, callback: (arg0: Error | null, arg1: Stats | null) => any): void; + /** + * Creates a symlink of `src` at `dest`. + * @param {string} src + * @param {string} dest + */ + export function symlink(src: string, dest: string, type: any, callback: any): void; + /** + * Unlinks (removes) file at `path`. + * @param {string} path + * @param {function} callback + */ + export function unlink(path: string, callback: Function): void; + /** + * Unlinks (removes) file at `path`, synchronously. + * @param {string} path + */ + export function unlinkSync(path: string): void; + /** + * @see {@url https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback} + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @param {function(Error?)} callback + */ + export function writeFile(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null, callback: (arg0: Error | null) => any): void; + /** + * Writes data to a file synchronously. + * @param {string | Buffer | URL | number } path - filename or file descriptor + * @param {string | Buffer | TypedArray | DataView | object } data + * @param {object?} options + * @param {string?} [options.encoding ? 'utf8'] + * @param {string?} [options.mode ? 0o666] + * @param {string?} [options.flag ? 'w'] + * @param {AbortSignal?} [options.signal] + * @see {@link https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options} */ - export class SystemMenu extends Menu { - constructor(); - } + export function writeFileSync(path: string | Buffer | URL | number, data: string | Buffer | TypedArray | DataView | object, options: object | null): void; /** - * A `Menu` instance that represents the tray menu. + * Watch for changes at `path` calling `callback` + * @param {string} + * @param {function|object=} [options] + * @param {string=} [options.encoding = 'utf8'] + * @param {?function} [callback] + * @return {Watcher} */ - export class TrayMenu extends Menu { - constructor(); - } + export function watch(path: any, options?: (Function | object) | undefined, callback?: Function | null): Watcher; + export default exports; + export type Buffer = import("socket:buffer").Buffer; + export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; + import { ReadStream } from "socket:fs/stream"; + import { WriteStream } from "socket:fs/stream"; + import { Dir } from "socket:fs/dir"; + import * as promises from "socket:fs/promises"; + import { Stats } from "socket:fs/stats"; + import { Watcher } from "socket:fs/watcher"; + import bookmarks from "socket:fs/bookmarks"; + import * as constants from "socket:fs/constants"; + import { DirectoryHandle } from "socket:fs/handle"; + import { Dirent } from "socket:fs/dir"; + import fds from "socket:fs/fds"; + import { FileHandle } from "socket:fs/handle"; + import * as exports from "socket:fs/index"; + + export { bookmarks, constants, Dir, DirectoryHandle, Dirent, fds, FileHandle, promises, ReadStream, Stats, Watcher, WriteStream }; +} + +declare module "socket:fs" { + export * from "socket:fs/index"; + export default exports; + import * as exports from "socket:fs/index"; +} + +declare module "socket:external/libsodium/index" { + const _default: any; + export default _default; +} + +declare module "socket:crypto/sodium" { + export {}; +} + +declare module "socket:crypto" { /** - * The application tray menu. - * @type {TrayMenu} + * Generate cryptographically strong random values into the `buffer` + * @param {TypedArray} buffer + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues} + * @return {TypedArray} */ - export const tray: TrayMenu; + export function getRandomValues(buffer: TypedArray, ...args: any[]): TypedArray; /** - * The application system menu. - * @type {SystemMenu} + * Generate a random 64-bit number. + * @returns {BigInt} - A random 64-bit number. */ - export const system: SystemMenu; + export function rand64(): BigInt; /** - * The application context menu. - * @type {ContextMenu} + * Generate `size` random bytes. + * @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1. + * @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes. */ - export const context: ContextMenu; + export function randomBytes(size: number): Buffer; /** - * The application menus container. - * @type {MenuContainer} + * @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512` + * @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview. + * @returns {Promise<Buffer>} - A promise that resolves with an instance of socket.Buffer with the hash. */ - export const container: MenuContainer; - export default container; - import ipc from "socket:ipc"; -} - -declare module "socket:process/signal" { + export function createDigest(algorithm: string, buf: any): Promise<Buffer>; /** - * Converts an `signal` code to its corresponding string message. - * @param {import('./os/constants.js').signal} {code} - * @return {string} + * A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c + * that works on strings and `ArrayBuffer` views (typed arrays) + * @param {string|Uint8Array|ArrayBuffer} value + * @param {number=} [seed = 0] + * @return {number} */ - export function toString(code: any): string; + export function murmur3(value: string | Uint8Array | ArrayBuffer, seed?: number | undefined): number; /** - * Gets the code for a given 'signal' name. - * @param {string|number} name - * @return {signal} + * @typedef {Uint8Array|Int8Array} TypedArray */ - export function getCode(name: string | number): signal; /** - * Gets the name for a given 'signal' code - * @return {string} - * @param {string|number} code + * WebCrypto API + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto} */ - export function getName(code: string | number): string; + export let webcrypto: any; /** - * Gets the message for a 'signal' code. - * @param {number|string} code - * @return {string} + * A promise that resolves when all internals to be loaded/ready. + * @type {Promise} */ - export function getMessage(code: number | string): string; + export const ready: Promise<any>; /** - * Add a signal event listener. - * @param {string|number} signal - * @param {function(SignalEvent)} callback - * @param {{ once?: boolean }=} [options] + * Maximum total size of random bytes per page */ - export function addEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { - once?: boolean; - } | undefined): void; + export const RANDOM_BYTES_QUOTA: number; /** - * Remove a signal event listener. - * @param {string|number} signal - * @param {function(SignalEvent)} callback - * @param {{ once?: boolean }=} [options] + * Maximum total size for random bytes. */ - export function removeEventListener(signalName: any, callback: (arg0: SignalEvent) => any, options?: { - once?: boolean; - } | undefined): void; - export { constants }; - export const channel: BroadcastChannel; - export const SIGHUP: any; - export const SIGINT: any; - export const SIGQUIT: any; - export const SIGILL: any; - export const SIGTRAP: any; - export const SIGABRT: any; - export const SIGIOT: any; - export const SIGBUS: any; - export const SIGFPE: any; - export const SIGKILL: any; - export const SIGUSR1: any; - export const SIGSEGV: any; - export const SIGUSR2: any; - export const SIGPIPE: any; - export const SIGALRM: any; - export const SIGTERM: any; - export const SIGCHLD: any; - export const SIGCONT: any; - export const SIGSTOP: any; - export const SIGTSTP: any; - export const SIGTTIN: any; - export const SIGTTOU: any; - export const SIGURG: any; - export const SIGXCPU: any; - export const SIGXFSZ: any; - export const SIGVTALRM: any; - export const SIGPROF: any; - export const SIGWINCH: any; - export const SIGIO: any; - export const SIGINFO: any; - export const SIGSYS: any; - export const strings: { - [x: number]: string; - }; - namespace _default { - export { addEventListener }; - export { removeEventListener }; - export { constants }; - export { channel }; - export { strings }; - export { toString }; - export { getName }; - export { getCode }; - export { getMessage }; - export { SIGHUP }; - export { SIGINT }; - export { SIGQUIT }; - export { SIGILL }; - export { SIGTRAP }; - export { SIGABRT }; - export { SIGIOT }; - export { SIGBUS }; - export { SIGFPE }; - export { SIGKILL }; - export { SIGUSR1 }; - export { SIGSEGV }; - export { SIGUSR2 }; - export { SIGPIPE }; - export { SIGALRM }; - export { SIGTERM }; - export { SIGCHLD }; - export { SIGCONT }; - export { SIGSTOP }; - export { SIGTSTP }; - export { SIGTTIN }; - export { SIGTTOU }; - export { SIGURG }; - export { SIGXCPU }; - export { SIGXFSZ }; - export { SIGVTALRM }; - export { SIGPROF }; - export { SIGWINCH }; - export { SIGIO }; - export { SIGINFO }; - export { SIGSYS }; - } - export default _default; - export type signal = any; -} - -declare module "socket:internal/events" { + export const MAX_RANDOM_BYTES: 281474976710655; /** - * An event dispatched when an application URL is opening the application. + * Maximum total amount of allocated per page of bytes (max/quota) */ - export class ApplicationURLEvent extends Event { - /** - * `ApplicationURLEvent` class constructor. - * @param {string=} [type] - * @param {object=} [options] - */ - constructor(type?: string | undefined, options?: object | undefined); - /** - * `true` if the application URL is valid (parses correctly). - * @type {boolean} - */ - get isValid(): boolean; - /** - * Data associated with the `ApplicationURLEvent`. - * @type {?any} - */ - get data(): any; - /** - * The original source URI - * @type {?string} - */ - get source(): string; - /** - * The `URL` for the `ApplicationURLEvent`. - * @type {?URL} - */ - get url(): URL; - /** - * String tag name for an `ApplicationURLEvent` instance. - * @type {string} - */ - get [Symbol.toStringTag](): string; - #private; + export const MAX_RANDOM_BYTES_PAGES: number; + export default exports; + export type TypedArray = Uint8Array | Int8Array; + import { Buffer } from "socket:buffer"; + export namespace sodium { + let ready: Promise<any>; } + import * as exports from "socket:crypto"; + +} + +declare module "socket:ai" { /** - * An event dispacted for a registered global hotkey expression. + * A class to interact with large language models (using llama.cpp) */ - export class HotKeyEvent extends MessageEvent<any> { - /** - * `HotKeyEvent` class constructor. - * @ignore - * @param {string=} [type] - * @param {object=} [data] - */ - constructor(type?: string | undefined, data?: object | undefined); - /** - * The global unique ID for this hotkey binding. - * @type {number?} - */ - get id(): number; + export class LLM extends EventEmitter { /** - * The computed hash for this hotkey binding. - * @type {number?} + * Constructs an LLM instance. Each parameter is designed to configure and control + * the behavior of the underlying large language model provided by llama.cpp. + * @param {Object} options - Configuration options for the LLM instance. + * @param {string} options.path - The file path to the model in .gguf format. This model file contains + * the weights and configuration necessary for initializing the language model. + * @param {string} options.prompt - The initial input text to the model, setting the context or query + * for generating responses. The model uses this as a starting point for text generation. + * @param {string} [options.id] - An optional unique identifier for this specific instance of the model, + * useful for tracking or referencing the model in multi-model setups. + * @param {number} [options.n_ctx=1024] - Specifies the maximum number of tokens that the model can consider + * for a single query. This is crucial for managing memory and computational + * efficiency. Exceeding the model's configuration may lead to errors or truncated outputs. + * @param {number} [options.n_threads=8] - The number of threads allocated for the model's computation, + * affecting performance and speed of response generation. + * @param {number} [options.temp=1.1] - Sampling temperature controls the randomness of predictions. + * Higher values increase diversity, potentially at the cost of coherence. + * @param {number} [options.max_tokens=512] - The upper limit on the number of tokens that the model can generate + * in response to a single prompt. This prevents runaway generations. + * @param {number} [options.n_gpu_layers=32] - The number of GPU layers dedicated to the model processing. + * More layers can increase accuracy and complexity of the outputs. + * @param {number} [options.n_keep=0] - Determines how many of the top generated responses are retained after + * the initial generation phase. Useful for models that generate multiple outputs. + * @param {number} [options.n_batch=0] - The size of processing batches. Larger batch sizes can reduce + * the time per token generation by parallelizing computations. + * @param {number} [options.n_predict=0] - Specifies how many forward predictions the model should make + * from the current state. This can pre-generate responses or calculate probabilities. + * @param {number} [options.grp_attn_n=0] - Group attention parameter 'N' modifies how attention mechanisms + * within the model are grouped and interact, affecting the model’s focus and accuracy. + * @param {number} [options.grp_attn_w=0] - Group attention parameter 'W' adjusts the width of each attention group, + * influencing the breadth of context considered by each attention group. + * @param {number} [options.seed=0] - A seed for the random number generator used in the model. Setting this ensures + * consistent results in model outputs, important for reproducibility in experiments. + * @param {number} [options.top_k=0] - Limits the model's output choices to the top 'k' most probable next words, + * reducing the risk of less likely, potentially nonsensical outputs. + * @param {number} [options.tok_p=0.0] - Top-p (nucleus) sampling threshold, filtering the token selection pool + * to only those whose cumulative probability exceeds this value, enhancing output relevance. + * @param {number} [options.min_p=0.0] - Sets a minimum probability filter for token generation, ensuring + * that generated tokens have at least this likelihood of being relevant or coherent. + * @param {number} [options.tfs_z=0.0] - Temperature factor scale for zero-shot learning scenarios, adjusting how + * the model weights novel or unseen prompts during generation. + * @throws {Error} Throws an error if the model path is not provided, as the model cannot initialize without it. */ - get hash(): number; + constructor(options?: { + path: string; + prompt: string; + id?: string; + n_ctx?: number; + n_threads?: number; + temp?: number; + max_tokens?: number; + n_gpu_layers?: number; + n_keep?: number; + n_batch?: number; + n_predict?: number; + grp_attn_n?: number; + grp_attn_w?: number; + seed?: number; + top_k?: number; + tok_p?: number; + min_p?: number; + tfs_z?: number; + }); + path: string; + prompt: string; + id: string | BigInt; /** - * The normalized hotkey expression as a sequence of tokens. - * @type {string[]} + * Tell the LLM to stop after the next token. + * @returns {Promise<void>} A promise that resolves when the LLM stops. */ - get sequence(): string[]; + stop(): Promise<void>; /** - * The original expression of the hotkey binding. - * @type {string?} + * Send a message to the chat. + * @param {string} message - The message to send to the chat. + * @returns {Promise<any>} A promise that resolves with the response from the chat. */ - get expression(): string; + chat(message: string): Promise<any>; } + export default exports; + import { EventEmitter } from "socket:events"; + import * as exports from "socket:ai"; + +} + +declare module "socket:window/constants" { + export const WINDOW_ERROR: -1; + export const WINDOW_NONE: 0; + export const WINDOW_CREATING: 10; + export const WINDOW_CREATED: 11; + export const WINDOW_HIDING: 20; + export const WINDOW_HIDDEN: 21; + export const WINDOW_SHOWING: 30; + export const WINDOW_SHOWN: 31; + export const WINDOW_CLOSING: 40; + export const WINDOW_CLOSED: 41; + export const WINDOW_EXITING: 50; + export const WINDOW_EXITED: 51; + export const WINDOW_KILLING: 60; + export const WINDOW_KILLED: 61; + export default exports; + import * as exports from "socket:window/constants"; + +} + +declare module "socket:application/client" { /** - * An event dispacted when a menu item is selected. + * @typedef {{ + * id?: string | null, + * type?: 'window' | 'worker', + * parent?: object | null, + * top?: object | null, + * frameType?: 'top-level' | 'nested' | 'none' + * }} ClientState */ - export class MenuItemEvent extends MessageEvent<any> { - /** - * `MenuItemEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [data] - * @param {import('../application/menu.js').Menu} menu - */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + export class Client { /** - * The `Menu` this event has been dispatched for. - * @type {import('../application/menu.js').Menu?} + * `Client` class constructor + * @private + * @param {ClientState} state */ - get menu(): import("socket:application/menu").Menu; + private constructor(); /** - * The title of the menu item. - * @type {string?} + * The unique ID of the client. + * @type {string|null} */ - get title(): string; + get id(): string; /** - * An optional tag value for the menu item that may also be the - * parent menu item title. - * @type {string?} + * The frame type of the client. + * @type {'top-level'|'nested'|'none'} */ - get tag(): string; + get frameType(): "none" | "top-level" | "nested"; /** - * The parent title of the menu item. - * @type {string?} + * The type of the client. + * @type {'window'|'worker'} */ - get parent(): string; - #private; - } - /** - * An event dispacted when the application receives an OS signal - */ - export class SignalEvent extends MessageEvent<any> { + get type(): "window" | "worker"; /** - * `SignalEvent` class constructor - * @ignore - * @param {string=} [type] - * @param {object=} [options] + * The parent client of the client. + * @type {Client|null} */ - constructor(type?: string | undefined, options?: object | undefined); + get parent(): Client; /** - * The code of the signal. - * @type {import('../process/signal.js').signal} + * The top client of the client. + * @type {Client|null} */ - get code(): any; + get top(): Client; /** - * The name of the signal. - * @type {string} + * A readonly `URL` of the current location of this client. + * @type {URL} */ - get name(): string; + get location(): URL; /** - * An optional message describing the signal - * @type {string} + * Converts this `Client` instance to JSON. + * @return {object} */ - get message(): string; + toJSON(): object; #private; } - namespace _default { - export { ApplicationURLEvent }; - export { MenuItemEvent }; - export { SignalEvent }; - export { HotKeyEvent }; - } + const _default: any; export default _default; + export type ClientState = { + id?: string | null; + type?: "window" | "worker"; + parent?: object | null; + top?: object | null; + frameType?: "top-level" | "nested" | "none"; + }; } declare module "socket:window/hotkey" { @@ -12001,11 +12003,8 @@ declare module "socket:latica/packets" { * The maximum distance that a packet can be replicated. */ export const MAX_HOPS: 16; - export function validatePacket(o: any, constraints: { - [key: string]: { - required: boolean; - type: string; - }; + export function validateMessage(o: object, constraints: { + [key: string]: constraint; }): void; /** * Computes a SHA-256 hash of input returning a hex encoded string. @@ -12054,97 +12053,37 @@ declare module "socket:latica/packets" { } export class PacketPing extends Packet { static type: number; - constructor({ message, clusterId, subclusterId }: { - message: any; - clusterId: any; - subclusterId: any; - }); } export class PacketPong extends Packet { static type: number; - constructor({ message, clusterId, subclusterId }: { - message: any; - clusterId: any; - subclusterId: any; - }); } export class PacketIntro extends Packet { static type: number; - constructor({ clock, hops, clusterId, subclusterId, usr1, message }: { - clock: any; - hops: any; - clusterId: any; - subclusterId: any; - usr1: any; - message: any; - }); } export class PacketJoin extends Packet { static type: number; - constructor({ clock, hops, clusterId, subclusterId, message }: { - clock: any; - hops: any; - clusterId: any; - subclusterId: any; - message: any; - }); } export class PacketPublish extends Packet { static type: number; - constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, hops, usr1, usr2, ttl, previousId }: { - message: any; - sig: any; - packetId: any; - clusterId: any; - subclusterId: any; - nextId: any; - clock: any; - hops: any; - usr1: any; - usr2: any; - ttl: any; - previousId: any; - }); } export class PacketStream extends Packet { static type: number; - constructor({ message, sig, packetId, clusterId, subclusterId, nextId, clock, ttl, usr1, usr2, usr3, usr4, previousId }: { - message: any; - sig: any; - packetId: any; - clusterId: any; - subclusterId: any; - nextId: any; - clock: any; - ttl: any; - usr1: any; - usr2: any; - usr3: any; - usr4: any; - previousId: any; - }); } export class PacketSync extends Packet { static type: number; - constructor({ packetId, message }: { - packetId: any; - message?: any; - }); } export class PacketQuery extends Packet { static type: number; - constructor({ packetId, previousId, subclusterId, usr1, usr2, usr3, usr4, message }: { - packetId: any; - previousId: any; - subclusterId: any; - usr1: any; - usr2: any; - usr3: any; - usr4: any; - message?: {}; - }); } export default Packet; + export type constraint = { + type: string; + required?: boolean; + /** + * optional validator fn returning boolean + */ + assert?: Function; + }; import { Buffer } from "socket:buffer"; } @@ -12288,7 +12227,6 @@ declare module "socket:latica/cache" { * @return {number} */ export function defaultSiblingResolver(a: CacheEntry, b: CacheEntry): number; - export function trim(buf: Buffer): any; /** * Default max size of a `Cache` instance. */ @@ -12339,6 +12277,7 @@ declare module "socket:latica/cache" { */ export class Cache { static HASH_SIZE_BYTES: number; + static HASH_EMPTY: string; /** * The encodeSummary method provides a compact binary encoding of the output * of summary() @@ -12354,6 +12293,13 @@ declare module "socket:latica/cache" { * @return {Object} summary **/ static decodeSummary(bin: Buffer): any; + /** + * Test a summary hash format is valid + * + * @param {string} hash + * @returns boolean + */ + static isValidSummaryHashFormat(hash: string): boolean; /** * `Cache` class constructor. * @param {CacheData?} [data] @@ -12450,8 +12396,8 @@ declare module "socket:latica/cache" { export default Cache; export type CacheEntry = Packet; export type CacheEntrySiblingResolver = (arg0: CacheEntry, arg1: CacheEntry) => number; - import { Buffer } from "socket:buffer"; import { Packet } from "socket:latica/packets"; + import { Buffer } from "socket:buffer"; } declare module "socket:latica/nat" { @@ -12653,6 +12599,28 @@ declare module "socket:latica/index" { * @returns boolean */ static isValidPeerId(pid: string): boolean; + /** + * Test a reflectionID is valid + * + * @param {string} rid + * @returns boolean + */ + static isValidReflectionId(rid: string): boolean; + /** + * Test a pingID is valid + * + * @param {string} pid + * @returns boolean + */ + static isValidPingId(pid: string): boolean; + /** + * Returns the online status of the browser, else true. + * + * note: globalThis.navigator was added to node in v22. + * + * @returns boolean + */ + static onLine(): boolean; /** * `Peer` class constructor. * @param {object=} opts - Options @@ -16837,11 +16805,11 @@ declare module "socket:internal/promise" { */ constructor(resolver: ResolverFunction); [resourceSymbol]: { - "__#16@#type": any; - "__#16@#destroyed": boolean; - "__#16@#asyncId": number; - "__#16@#triggerAsyncId": any; - "__#16@#requireManualDestroy": boolean; + "__#15@#type": any; + "__#15@#destroyed": boolean; + "__#15@#asyncId": number; + "__#15@#triggerAsyncId": any; + "__#15@#requireManualDestroy": boolean; readonly type: string; readonly destroyed: boolean; asyncId(): number; From 288d6ae98f7b32b58611eacbee2f0f7aeea204f9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 11:01:35 +0100 Subject: [PATCH 0994/1178] fix(core/modules/platform): fix 'CorePlatform::openExternal' --- src/core/modules/platform.cc | 215 ++++++++++++++++++----------------- 1 file changed, 108 insertions(+), 107 deletions(-) diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index 8f56dbae3f..76aeea456b 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -161,125 +161,126 @@ namespace SSC { const String& value, const CoreModule::Callback& callback ) const { - #if SOCKET_RUNTIME_PLATFORM_APPLE - const auto url = [NSURL URLWithString: @(value.c_str())]; + #if SOCKET_RUNTIME_PLATFORM_APPLE + this->core->dispatchEventLoop([=]() { + __block const auto url = [NSURL URLWithString: @(value.c_str())]; + + #if SOCKET_RUNTIME_PLATFORM_IOS + [UIApplication.sharedApplication openURL: url options: @{} completionHandler: ^(BOOL success) { + JSON::Object json; + if (!success) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", "Failed to open external URL"} + }} + }; + } else { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"data", JSON::Object::Entries {{"url", url.absoluteString.UTF8String}}} + }; + } - #if SOCKET_RUNTIME_PLATFORM_IOS - [UIApplication.sharedApplication openURL: url options: @{} completionHandler: ^(BOOL success) { - auto json = JSON::Object {}; + callback(seq, json, Post{}); + }]; + #else + auto workspace = [NSWorkspace sharedWorkspace]; + auto configuration = [NSWorkspaceOpenConfiguration configuration]; + [workspace openURL: url + configuration: configuration + completionHandler: ^(NSRunningApplication *app, NSError *error) + { + JSON::Object json; + if (error) { + if (error.debugDescription.UTF8String) { + debug("%s", error.debugDescription.UTF8String); + } - if (!success) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", error.localizedDescription.UTF8String} + }} + }; + } else { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"data", JSON::Object::Entries {{ "url", url.absoluteString.UTF8String}}} + }; + } + + callback(seq, json, Post{}); + }]; + #endif + }); + #elif SOCKET_RUNTIME_PLATFORM_LINUX + auto list = gtk_window_list_toplevels(); + auto json = JSON::Object {}; + + // initial state is a failure + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", "Failed to open external URL"} + }} + }; + + if (list != nullptr) { + for (auto entry = list; entry != nullptr; entry = entry->next) { + auto window = GTK_WINDOW(entry->data); + + if (window != nullptr && gtk_window_is_active(window)) { + auto err = (GError*) nullptr; + auto uri = value.c_str(); + auto ts = GDK_CURRENT_TIME; + + /** + * GTK may print a error in the terminal that looks like: + * + * libva error: vaGetDriverNameByIndex() failed with unknown libva error, driver_name = (null) + * + * It doesn't prevent the URI from being opened. + * See https://github.com/intel/media-driver/issues/1349 for more info + */ + auto success = gtk_show_uri_on_window(window, uri, ts, &err); + + if (success) { json = JSON::Object::Entries { {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", "Failed to open external URL"} - }} + {"data", JSON::Object::Entries {}} }; - } else { + } else if (err != nullptr) { json = JSON::Object::Entries { {"source", "platform.openExternal"}, - {"data", JSON::Object::Entries {{"url", value}}} + {"err", JSON::Object::Entries { + {"message", err->message} + }} }; } - callback(seq, json, Post{}); - }]; - #else - auto workspace = [NSWorkspace sharedWorkspace]; - auto configuration = [NSWorkspaceOpenConfiguration configuration]; - [workspace openURL: url - configuration: configuration - completionHandler: ^(NSRunningApplication *app, NSError *error) - { - auto json = JSON::Object {}; - if (error) { - if (error.debugDescription.UTF8String) { - debug("%s", error.debugDescription.UTF8String); - } - - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", error.localizedDescription.UTF8String} - }} - }; - } else { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"data", JSON::Object::Entries {{ "url", value}}} - }; - } - - callback(seq, json, Post{}); - }]; - #endif - #elif SOCKET_RUNTIME_PLATFORM_LINUX - auto list = gtk_window_list_toplevels(); - auto json = JSON::Object {}; - - // initial state is a failure - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", "Failed to open external URL"} - }} - }; - - if (list != nullptr) { - for (auto entry = list; entry != nullptr; entry = entry->next) { - auto window = GTK_WINDOW(entry->data); - - if (window != nullptr && gtk_window_is_active(window)) { - auto err = (GError*) nullptr; - auto uri = value.c_str(); - auto ts = GDK_CURRENT_TIME; - - /** - * GTK may print a error in the terminal that looks like: - * - * libva error: vaGetDriverNameByIndex() failed with unknown libva error, driver_name = (null) - * - * It doesn't prevent the URI from being opened. - * See https://github.com/intel/media-driver/issues/1349 for more info - */ - auto success = gtk_show_uri_on_window(window, uri, ts, &err); - - if (success) { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"data", JSON::Object::Entries {}} - }; - } else if (err != nullptr) { - json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"message", err->message} - }} - }; - } - - break; - } + break; } - - g_list_free(list); } - callback(seq, json, Post{}); - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS - auto uri = value.c_str(); - ShellExecute(nullptr, "Open", uri, nullptr, nullptr, SW_SHOWNORMAL); - // TODO how to detect success here. do we care? - callback(seq, JSON::Object{}, Post{}); - #else - const auto json = JSON::Object::Entries { - {"source", "platform.openExternal"}, - {"err", JSON::Object::Entries { - {"type", "NotSupportedError"}, - {"message", "Operation not supported"} - }} - }; - callback(seq, json, Post{}); - #endif + g_list_free(list); + } + + callback(seq, json, Post{}); + #elif SOCKET_RUNTIME_PLATFORM_WINDOWS + auto uri = value.c_str(); + ShellExecute(nullptr, "Open", uri, nullptr, nullptr, SW_SHOWNORMAL); + // TODO how to detect success here. do we care? + callback(seq, JSON::Object{}, Post{}); + #else + const auto json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"type", "NotSupportedError"}, + {"message", "Operation not supported"} + }} + }; + callback(seq, json, Post{}); + #endif } } From 51d39d207ebbac9383381125a675446af6a36933 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 11:37:23 +0100 Subject: [PATCH 0995/1178] refactor(platform/android): clean up, more android platform types --- src/platform/android.hh | 10 ++++++++++ src/platform/android/environment.cc | 4 ++-- src/platform/android/environment.hh | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/platform/android.hh b/src/platform/android.hh index 0bc81d788f..e032e38c74 100644 --- a/src/platform/android.hh +++ b/src/platform/android.hh @@ -25,6 +25,16 @@ namespace SSC::Android { */ using AssetDirectory = ::AAssetDir; + /** + * An opaque `Activity` instance. + */ + using Activity = ::jobject; + + /** + * An opaque `Application` instance. + */ + using Application = ::jobject; + /** * A container that holds Android OS build information. */ diff --git a/src/platform/android/environment.cc b/src/platform/android/environment.cc index e84622329c..5e9078ed20 100644 --- a/src/platform/android/environment.cc +++ b/src/platform/android/environment.cc @@ -90,11 +90,11 @@ namespace SSC::Android { } } - inline bool JNIEnvironmentAttachment::hasException () { + bool JNIEnvironmentAttachment::hasException () const { return this->env != nullptr && this->env->ExceptionCheck(); } - inline void JNIEnvironmentAttachment::printException () { + void JNIEnvironmentAttachment::printException () const { if (this->env != nullptr) { this->env->ExceptionDescribe(); } diff --git a/src/platform/android/environment.hh b/src/platform/android/environment.hh index 27b0ba7155..43c7d40ced 100644 --- a/src/platform/android/environment.hh +++ b/src/platform/android/environment.hh @@ -140,8 +140,8 @@ namespace SSC::Android { void attach (JavaVM *jvm, int version); void detach (); - inline bool hasException (); - inline void printException (); + bool hasException () const; + void printException () const; }; } #endif From 49ce80f3fa1b43f35c4761f5a8fac94866696117 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 11:38:00 +0100 Subject: [PATCH 0996/1178] refactor(core/modules/platform): call 'openExternal' for android --- src/core/modules/platform.cc | 29 ++++++++++++++++++++++++++++- src/core/modules/platform.hh | 9 +++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index 76aeea456b..dde8bd38ac 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -208,7 +208,7 @@ namespace SSC { {"source", "platform.openExternal"}, {"data", JSON::Object::Entries {{ "url", url.absoluteString.UTF8String}}} }; - } + } callback(seq, json, Post{}); }]; @@ -272,6 +272,33 @@ namespace SSC { ShellExecute(nullptr, "Open", uri, nullptr, nullptr, SW_SHOWNORMAL); // TODO how to detect success here. do we care? callback(seq, JSON::Object{}, Post{}); + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + JSON::Object json; + const auto attachment = Android::JNIEnvironmentAttachment(this->jvm); + // `activity.openExternal(url)` + CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->activity, + "openExternal", + "(Ljava/lang/String;)Z" + ); + + if (attachment.hasException()) { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"err", JSON::Object::Entries { + {"message", "Failed to open external URL"} + }} + }; + } else { + json = JSON::Object::Entries { + {"source", "platform.openExternal"}, + {"data", JSON::Object::Entries {{ "url", value}}} + }; + } + + callback(seq, json, Post{}); #else const auto json = JSON::Object::Entries { {"source", "platform.openExternal"}, diff --git a/src/core/modules/platform.hh b/src/core/modules/platform.hh index 7db2ff3509..4bc724ab74 100644 --- a/src/core/modules/platform.hh +++ b/src/core/modules/platform.hh @@ -3,12 +3,21 @@ #include "../module.hh" +#if SOCKET_RUNTIME_PLATFORM_ANDROID +#include "../../platform/android.hh" +#endif + namespace SSC { class Core; class CorePlatform : public CoreModule { public: Atomic<bool> wasFirstDOMContentLoadedEventDispatched = false; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + Android::JVMEnvironment jvm; + Android::Activity activity = nullptr; + #endif + CorePlatform (Core* core) : CoreModule(core) {} From 6b37ad6e1ce0b4660f4f81ed47d02f6ac924459f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 18 Jul 2024 11:38:26 +0100 Subject: [PATCH 0997/1178] refactor(app): introduce 'openExternal' on app activity --- src/app/app.cc | 19 +++++++++++-------- src/app/app.hh | 4 ++-- src/app/app.kt | 8 ++++++++ src/window/android.cc | 16 ++++++++-------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 0dbc5e34b8..216c3cef41 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -1125,7 +1125,7 @@ extern "C" { void ANDROID_EXTERNAL(app, App, onCreateAppActivity)( JNIEnv *env, jobject self, - jobject appActivity + jobject activity ) { auto app = App::sharedApplication(); @@ -1133,11 +1133,13 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - if (app->appActivity) { - app->jni->DeleteGlobalRef(app->appActivity); + if (app->activity) { + app->jni->DeleteGlobalRef(app->activity); } - app->appActivity = env->NewGlobalRef(appActivity); + app->activity = env->NewGlobalRef(activity); + app->core->platform.jvm = Android::JVMEnvironment(app->jni); + app->core->platform.activity = app->activity; app->run(); if (app->windowManager.getWindowStatus(0) == WindowManager::WINDOW_NONE) { @@ -1218,7 +1220,7 @@ extern "C" { void ANDROID_EXTERNAL(app, App, onDestroyAppActivity)( JNIEnv *env, jobject self, - jobject appActivity + jobject activity ) { auto app = App::sharedApplication(); @@ -1226,9 +1228,10 @@ extern "C" { ANDROID_THROW(env, "Missing 'App' in environment"); } - if (app->appActivity) { - env->DeleteGlobalRef(app->appActivity); - app->appActivity = nullptr; + if (app->activity) { + env->DeleteGlobalRef(app->activity); + app->activity = nullptr; + app->core->platform.activity = nullptr; } } diff --git a/src/app/app.hh b/src/app/app.hh index 7de352694b..42f4702020 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -82,9 +82,9 @@ namespace SSC { Android::BuildInformation androidBuildInformation; Android::Looper androidLooper; Android::JVMEnvironment jvm; + Android::Activity activity = nullptr; + Android::Application self = nullptr; JNIEnv* jni = nullptr; - jobject self = nullptr; - jobject appActivity = nullptr; bool isAndroidEmulator = false; #endif diff --git a/src/app/app.kt b/src/app/app.kt index d13508d26e..6171aea143 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -11,6 +11,7 @@ import android.app.NotificationManager import android.content.Intent import android.content.pm.PackageManager import android.content.res.AssetManager +import android.net.Uri import android.os.Build import android.os.Bundle import android.webkit.MimeTypeMap @@ -63,6 +64,13 @@ open class AppActivity : WindowManagerActivity() { ) } + fun openExternal (value: String) { + val uri = Uri.parse(value) + val action = Intent.ACTION_VIEW + val intent = Intent(action, uri) + this.startActivity(intent) + } + override fun onCreate (savedInstanceState: Bundle?) { this.supportActionBar?.hide() this.getWindow()?.statusBarColor = android.graphics.Color.TRANSPARENT diff --git a/src/window/android.cc b/src/window/android.cc index 4bd9632368..fe8a32379a 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -44,7 +44,7 @@ namespace SSC { // `activity.createWindow(index, shouldExitApplicationOnClose): Unit` CallVoidClassMethodFromAndroidEnvironment( attachment.env, - app->appActivity, + app->activity, "createWindow", "(IZZ)V", options.index, @@ -84,7 +84,7 @@ namespace SSC { CallClassMethodFromAndroidEnvironment( attachment.env, Boolean, - app->appActivity, + app->activity, "evaluateJavaScript", "(ILjava/lang/String;)Z", this->index, @@ -102,7 +102,7 @@ namespace SSC { CallClassMethodFromAndroidEnvironment( attachment.env, Boolean, - app->appActivity, + app->activity, "showWindow", "(I)Z", this->index @@ -117,7 +117,7 @@ namespace SSC { CallClassMethodFromAndroidEnvironment( attachment.env, Boolean, - app->appActivity, + app->activity, "hideWindow", "(I)Z", this->index @@ -140,7 +140,7 @@ namespace SSC { CallClassMethodFromAndroidEnvironment( attachment.env, Boolean, - app->appActivity, + app->activity, "closeWindow", "(I)Z", this->index @@ -168,7 +168,7 @@ namespace SSC { const auto result = CallClassMethodFromAndroidEnvironment( attachment.env, Boolean, - app->appActivity, + app->activity, "navigateWindow", "(ILjava/lang/String;)Z", this->index, @@ -188,7 +188,7 @@ namespace SSC { // `activity.getWindowTitle(index): String` const auto titleString = (jstring) CallObjectClassMethodFromAndroidEnvironment( attachment.env, - app->appActivity, + app->activity, "getWindowTitle", "(I)Ljava/lang/String;", this->index @@ -206,7 +206,7 @@ namespace SSC { CallClassMethodFromAndroidEnvironment( attachment.env, Boolean, - app->appActivity, + app->activity, "setWindowTitle", "(ILjava/lang/String;)Z", this->index, From f6939116c3847e9b93c40c48bd242e698e2b145b Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 18 Jul 2024 20:57:38 +0200 Subject: [PATCH 0998/1178] chore(api): update protocol --- api/latica/api.js | 3 +++ api/latica/index.js | 38 +++++++++++++++++++++++--------------- api/latica/packets.js | 1 + api/latica/proxy.js | 10 ++++++---- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/api/latica/api.js b/api/latica/api.js index 22108cbe50..9c28f0a63f 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -357,6 +357,9 @@ async function api (options = {}, events, dgram) { sub.peers.set(peer.peerId, ee) const isStateChange = !oldPeer || change + + _peer.onDebug(_peer.peerId, `<-- API CONNECTION JOIN (scid=${scid}, peerId=${peer.peerId.slice(0, 6)})`) + sub._emit('#join', ee, packet, isStateChange) }) diff --git a/api/latica/index.js b/api/latica/index.js index e230247ee2..7dbd0e4629 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -935,7 +935,8 @@ export class Peer { requesterPeerId: this.peerId, natType: this.natType, address: this.address, - port: this.port + port: this.port, + key: [cid, scid].join(':') } }) @@ -1091,7 +1092,7 @@ export class Peer { /** * @return {undefined} */ - async sync (peer) { + async sync (peer, ptime = Date.now()) { if (typeof peer === 'string') { peer = this.peers.find(p => p.peerId === peer) } @@ -1099,7 +1100,7 @@ export class Peer { const rinfo = peer?.proxy || peer this.lastSync = Date.now() - const summary = await this.cache.summarize('', this.cachePredicate) + const summary = await this.cache.summarize('', this.cachePredicate(ptime)) this._onDebug(`-> SYNC START (dest=${peer.peerId.slice(0, 8)}, to=${rinfo.address}:${rinfo.port})`) if (this.onSyncStart) this.onSyncStart(peer, rinfo.port, rinfo.address) @@ -1107,7 +1108,7 @@ export class Peer { // if we are out of sync send our cache summary const data = await Packet.encode(new PacketSync({ message: Cache.encodeSummary(summary), - usr4: Buffer.from(String(Date.now())) + usr4: Buffer.from(String(ptime)) })) this.send(data, rinfo.port, rinfo.address, peer.socket) @@ -1157,13 +1158,13 @@ export class Peer { * from the cache when receiving a request to sync. that can be overridden * */ - cachePredicate (packet) { - if (packet.usr4.byteLength < 8 || packet.usr4.byteLength > 16) return + cachePredicate (ts) { + const max = Date.now() - Packet.ttl + const T = Math.min(ts || max, max) - const timestamp = parseInt(Buffer.from(packet.usr4).toString(), 10) - const ts = Math.min(Packet.ttl, timestamp) - - return packet.version === VERSION && ts > Date.now() - Packet.ttl + return packet => { + return packet.version === VERSION && packet.timestamp > T + } } /** @@ -1246,13 +1247,20 @@ export class Peer { this.lastSync = Date.now() const pid = packet.packetId.toString('hex') + let ptime = Date.now() + + if (packet.usr4.byteLength > 8 || packet.usr4.byteLength < 16) { + const usr4 = parseInt(Buffer.from(packet.usr4).toString(), 10) + ptime = Math.min(ptime - Packet.ttl, usr4) + } + if (!isBufferLike(packet.message)) return if (this.gate.has(pid)) return this.gate.set(pid, 1) const remote = Cache.decodeSummary(packet.message) - const local = await this.cache.summarize(remote.prefix, this.cachePredicate) + const local = await this.cache.summarize(remote.prefix, this.cachePredicate(ptime)) if (!remote || !remote.hash || !local || !local.hash || local.hash === remote.hash) { if (this.onSyncFinished) this.onSyncFinished(packet, port, address) @@ -1278,7 +1286,7 @@ export class Peer { if (!key.startsWith(local.prefix + i.toString(16))) continue const packet = Packet.from(p) - if (!this.cachePredicate(packet)) continue + if (!this.cachePredicate(ptime)(packet)) continue const pid = packet.packetId.toString('hex') this._onDebug(`-> SYNC SEND PACKET (type=data, packetId=${pid.slice(0, 8)}, to=${address}:${port})`) @@ -1289,7 +1297,7 @@ export class Peer { // // need more details about what exactly isn't synce'd // - const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate) + const nextLevel = await this.cache.summarize(local.prefix + i.toString(16), this.cachePredicate(ptime)) const data = await Packet.encode(new PacketSync({ message: Cache.encodeSummary(nextLevel), usr4: Buffer.from(String(Date.now())) @@ -1450,7 +1458,7 @@ export class Peer { delete message.isProbe } - const { hash } = await this.cache.summarize('', this.cachePredicate) + const { hash } = await this.cache.summarize('', this.cachePredicate()) message.cacheSummaryHash = hash const packetPong = new PacketPong({ message }) @@ -1645,7 +1653,7 @@ export class Peer { if (this.onIntro) this.onIntro(packet, peer, peerPort, peerAddress) const pingId = randomBytes(6).toString('hex').padStart(12, '0') - const { hash } = await this.cache.summarize('', this.cachePredicate) + const { hash } = await this.cache.summarize('', this.cachePredicate()) const props = { clusterId, diff --git a/api/latica/packets.js b/api/latica/packets.js index 50008b963d..6e0dae1c5a 100644 --- a/api/latica/packets.js +++ b/api/latica/packets.js @@ -494,6 +494,7 @@ export class PacketJoin extends Packet { requesterPeerId: { required: true, type: 'string', assert: Peer.isValidPeerId }, natType: { required: true, type: 'number', assert: NAT.isValid }, address: { required: true, type: 'string' }, + key: { type: 'string' }, port: { required: true, type: 'number', assert: isValidPort }, isConnection: { type: 'boolean' } }) diff --git a/api/latica/proxy.js b/api/latica/proxy.js index 92e740f1ab..4ea1ebe7dc 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -263,12 +263,14 @@ class PeerWorkerProxy { if (arg?.constructor.name === 'RemotePeer' || arg?.constructor.name === 'Peer') { args[i] = { // what details we want to expose outside of the protocol - peerId: arg.peerId, address: arg.address, - port: arg.port, - natType: arg.natType, clusters: arg.clusters, - connected: arg.connected + connected: arg.connected, + lastRequest: arg.lastRequest, + lastUpdate: arg.lastUpdate, + natType: arg.natType, + peerId: arg.peerId, + port: arg.port } delete args[i].localPeer // don't copy this over From 1214fac8f517b934b7d249c3221be28aa2efec1b Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Fri, 19 Jul 2024 17:29:32 +0200 Subject: [PATCH 0999/1178] feature(api): expose max cache time in protocol --- api/latica/api.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/latica/api.js b/api/latica/api.js index 9c28f0a63f..9e40f86012 100644 --- a/api/latica/api.js +++ b/api/latica/api.js @@ -136,6 +136,8 @@ async function api (options = {}, events, dgram) { bus.query = (...args) => _peer.query(...args) + bus.MAX_CACHE_TTL = CACHE_TTL + const pack = async (eventName, value, opts = {}) => { if (typeof eventName !== 'string') throw new Error('event name must be a string') if (eventName.length === 0) throw new Error('event name too short') From 658b3581d2e92f03299f713dd36e702becb1dadb Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:13:54 +0100 Subject: [PATCH 1000/1178] refactor(window): introduce 'WindowManager::emit' --- src/window/manager.cc | 21 +++++++++++++++++++++ src/window/window.hh | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/window/manager.cc b/src/window/manager.cc index 15a5051cc5..dc5000cd8a 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -313,6 +313,23 @@ namespace SSC { return result; } + bool WindowManager::emit (const String& event, const JSON::Any& json) { + bool status = false; + for (const auto& window : this->windows) { + if ( + window != nullptr && + window->status >= WINDOW_SHOWING && + window->status < WINDOW_CLOSING + ) { + if (window->emit(event, json)) { + status = true; + } + } + } + + return status; + } + WindowManager::ManagedWindow::ManagedWindow ( WindowManager &manager, SharedPointer<Core> core, @@ -415,4 +432,8 @@ namespace SSC { } } } + + bool WindowManager::ManagedWindow::emit (const String& event, const JSON::Any& json) { + return this->bridge.emit(event, json); + } } diff --git a/src/window/window.hh b/src/window/window.hh index d60e341107..74b2c18f7b 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -550,6 +550,7 @@ namespace SSC { JSON::Object json () const; void handleApplicationURL (const String& url); void onReadyStateChange (const ReadyState& readyState); + bool emit (const String& event, const JSON::Any& json = {}); }; Vector<SharedPointer<ManagedWindow>> windows; @@ -580,6 +581,7 @@ namespace SSC { void destroyWindow (int index); JSON::Array json (const Vector<int>& indices); + bool emit (const String& event, const JSON::Any& json = {}); }; } #endif From f3295b03ffa76de14a607c23fcd9c1a12b82b431 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:14:16 +0100 Subject: [PATCH 1001/1178] refactor(app): emit events for 'pause' and 'resume' on windows --- src/app/app.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/app.cc b/src/app/app.cc index 216c3cef41..c37f7e093b 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -852,12 +852,14 @@ namespace SSC { void App::resume () { if (this->core != nullptr) { this->core->resume(); + this->windowManager.emit("applicationresume"); } } void App::pause () { if (this->core != nullptr) { this->core->pause(); + this->windowManager.emit("applicationpause"); } } @@ -866,6 +868,7 @@ namespace SSC { return; } + this->windowManager.emit("applicationstop"); this->pause(); SSC::applicationInstance = nullptr; From ae2b4893a4d73220f79928f15bf5e66986912a9e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:16:34 +0100 Subject: [PATCH 1002/1178] refactor(core/modules/conduit): msleep in start if already active --- src/core/modules/conduit.cc | 3 ++- src/core/modules/conduit.hh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index 660ceb8ce2..6f56b1d8d9 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -358,9 +358,10 @@ namespace SSC { return true; } - void CoreConduit::start (const Function<void()>& callback) { + void CoreConduit::start (const StartCallback& callback) { if (this->isActive()) { if (callback != nullptr) { + msleep(256); callback(); } return; diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 851ea987ea..8baf4ef2b1 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -10,6 +10,7 @@ namespace SSC { class CoreConduit : public CoreModule { public: using Options = std::unordered_map<String, String>; + using StartCallback = Function<void()>; struct EncodedMessage { Options options; @@ -114,7 +115,7 @@ namespace SSC { CoreConduit::Client* get (uint64_t id); // lifecycle - void start (const Function<void()>& callback = nullptr); + void start (const StartCallback& callback = nullptr); void stop (); bool isActive (); From 8564451123903b0cda54cbe38dcc675f3bb3203c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:16:55 +0100 Subject: [PATCH 1003/1178] fix(ipc/routes): fix 'internal.conduit.*' return values --- src/ipc/routes.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 735e1c9f4d..021917e014 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1664,13 +1664,13 @@ static void mapIPCRoutes (Router *router) { reply(Result::Data { message, JSON::Object::Entries { - {"started", true}, + {"isActive", true}, {"port", router->bridge->core->conduit.port.load()} } }); } else { const auto err = JSON::Object::Entries {{ "message", "Failed to start Conduit"}}; - reply(Result::Err { message, err }); + reply(Result::Err { message, err }); } }); }); @@ -1690,7 +1690,7 @@ static void mapIPCRoutes (Router *router) { reply(Result::Data { message, JSON::Object::Entries { - {"started", router->bridge->core->conduit.isActive()}, + {"isActive", router->bridge->core->conduit.isActive()}, {"port", router->bridge->core->conduit.port.load()} } }); From d5b31ae61ccd63d21e74eef5684765b648961b50 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:17:48 +0100 Subject: [PATCH 1004/1178] refactor(api/application.js): handle pause/resume state, dispatch app events --- api/application.js | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/api/application.js b/api/application.js index e0d5370156..66bbf07aed 100644 --- a/api/application.js +++ b/api/application.js @@ -1,3 +1,4 @@ +/* global Event, MessageEvent */ // @ts-check /** * @module application @@ -10,15 +11,38 @@ * ``` */ +import { ApplicationURLEvent } from './internal/events.js' import ApplicationWindow, { formatURL } from './window.js' import { isValidPercentageValue } from './util.js' import ipc, { primordials } from './ipc.js' import menu, { setMenu } from './application/menu.js' import client from './application/client.js' +import hooks from './hooks.js' import os from './os.js' import * as exports from './application.js' +const eventTarget = new EventTarget() + +let isApplicationPaused = false +hooks.onApplicationResume((event) => { + isApplicationPaused = false + eventTarget.dispatchEvent(new Event(event.type, event)) +}) + +hooks.onApplicationPause((event) => { + isApplicationPaused = true + eventTarget.dispatchEvent(new Event(event.type, event)) +}) + +hooks.onApplicationURL((event) => { + eventTarget.dispatchEvent(new ApplicationURLEvent(event.type, event)) +}) + +hooks.onMessage((event) => { + eventTarget.dispatchEvent(new MessageEvent(event.type, event)) +}) + function serializeConfig (config) { if (!config || typeof config !== 'object') { return '' @@ -158,6 +182,25 @@ export class ApplicationWindowList { } } +/** + * Add an application event `type` callback `listener` with `options`. + * @param {string} type + * @param {function(Event|MessageEvent|CustomEvent|ApplicationURLEvent): boolean} listener + * @param {{ once?: boolean }|boolean=} [options] + */ +export function addEventListener (type, listener, options = null) { + return eventTarget.addEventListener(type, listener, options) +} + +/** + * Remove an application event `type` callback `listener` with `options`. + * @param {string} type + * @param {function(Event|MessageEvent|CustomEvent|ApplicationURLEvent): boolean} listener + */ +export function removeEventListener (type, listener) { + return eventTarget.removeEventListener(type, listener) +} + /** * Returns the current window index * @return {number} @@ -538,6 +581,14 @@ export async function setSystemMenuItemEnabled (value) { return await ipc.request('application.setSystemMenuItemEnabled', value) } +/** + * Predicate function to determine if application is in a "paused" state. + * @return {boolean} + */ +export function isPaused () { + return isApplicationPaused +} + /** * Socket Runtime version. * @type {object} - an object containing the version information From 544fc608426f2430a8509e1b204f3e67666076fb Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:18:04 +0100 Subject: [PATCH 1005/1178] fix(api/dgram.js): check if conduit exists before creating --- api/dgram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dgram.js b/api/dgram.js index bc943b4eb5..971b53dd8d 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -781,7 +781,7 @@ export class Socket extends EventEmitter { }) } - if (!this.legacy) { + if (!this.legacy && !this.conduit) { this.conduit = new Conduit({ id: this.id }) this.conduit.receive((_, decoded) => { From 725b78a4291990083b5d8c257b26d26a0c9535be Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:18:31 +0100 Subject: [PATCH 1006/1178] feat(api/hooks.js): introduce 'onApplicationResume' and 'onApplicationPause' hooks --- api/hooks.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/api/hooks.js b/api/hooks.js index 9dda903d5d..42e504725b 100644 --- a/api/hooks.js +++ b/api/hooks.js @@ -62,6 +62,14 @@ * hooks.onApplicationURL((event) => { * // called when 'applicationurl' events are dispatched on the global object * }) + * + * hooks.onApplicationResume((event) => { + * // called when 'applicationresume' events are dispatched on the global object + * }) + * + * hooks.onApplicationPause((event) => { + * // called when 'applicationpause' events are dispatched on the global object + * }) * ``` */ import { Event, CustomEvent, ErrorEvent, MessageEvent } from './events.js' @@ -149,6 +157,8 @@ export const RUNTIME_INIT_EVENT_NAME = '__runtime_init__' export const GLOBAL_EVENTS = [ RUNTIME_INIT_EVENT_NAME, 'applicationurl', + 'applicationpause', + 'applicationresume', 'data', 'error', 'init', @@ -166,6 +176,8 @@ export const GLOBAL_EVENTS = [ const GLOBAL_TOP_LEVEL_EVENTS = [ 'applicationurl', + 'applicationpause', + 'applicationresume', 'data', 'languagechange', 'notificationpresented', @@ -579,6 +591,26 @@ export class Hooks extends EventTarget { this.addEventListener('applicationurl', callback) return () => this.removeEventListener('applicationurl', callback) } + + /** + * Calls callback when an `ApplicationPause` is dispatched. + * @param {function} callback + * @return {function} + */ + onApplicationPause (callback) { + this.addEventListener('applicationpause', callback) + return () => this.removeEventListener('applicationpause', callback) + } + + /** + * Calls callback when an `ApplicationResume` is dispatched. + * @param {function} callback + * @return {function} + */ + onApplicationResume (callback) { + this.addEventListener('applicationresume', callback) + return () => this.removeEventListener('applicationresume', callback) + } } /** @@ -720,4 +752,22 @@ export function onApplicationURL (callback) { return hooks.onApplicationURL(callback) } +/** + * Calls callback when a `ApplicationPause` is dispatched. + * @param {function} callback + * @return {function} + */ +export function onApplicationPause (callback) { + return hooks.onApplicationPause(callback) +} + +/** + * Calls callback when a `ApplicationResume` is dispatched. + * @param {function} callback + * @return {function} + */ +export function onApplicationResume (callback) { + return hooks.onApplicationResume(callback) +} + export default hooks From 024901b3789a781911cff2e27421dbeb82090470 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:20:34 +0100 Subject: [PATCH 1007/1178] refactor(api/internal/conduit.js): improve reconnect logic, handle pause/resume app state --- api/internal/conduit.js | 155 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 143 insertions(+), 12 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index a480c419e8..255fda3a71 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -1,23 +1,73 @@ /* global CloseEvent, ErrorEvent, MessageEvent, WebSocket */ +import diagnostics from '../diagnostics.js' +import { sleep } from '../timers.js' import client from '../application/client.js' +import hooks from '../hooks.js' import ipc from '../ipc.js' +import gc from '../gc.js' -export const DEFALUT_MAX_RECONNECT_RETRIES = 32 -export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 +/** + * Predicate state to determine if application is paused. + * @type {boolean} + */ +let isApplicationPaused = false -let defaultConduitPort = globalThis.__args.conduit +/** + * The default Conduit port + * @type {number} + */ +let defaultConduitPort = globalThis.__args.conduit || 0 /** * @typedef {{ options: object, payload: Uint8Array }} ReceiveMessage * @typedef {function(Error?, ReceiveCallback | undefined)} ReceiveCallback * @typedef {{ id?: string|BigInt|number, reconnect?: {} }} ConduitOptions + * @typedef {{ isActive: boolean, handles: { ids: string[], count: number }}} ConduitDiagnostics + * @typedef {{ isActive: boolean, port: number }} ConduitStatus */ +export const DEFALUT_MAX_RECONNECT_RETRIES = 32 +export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 + +/** + * A pool of known `Conduit` instances. + * @type {Set<WeakRef<Conduit>>} + */ +export const pool = new Set() + +// reconnect when application resumes +hooks.onApplicationResume(() => { + isApplicationPaused = false + const refs = Array.from(pool) + for (const ref of refs) { + // @ts-ignore + const conduit = /** @type {WeakRef<Conduit>} */ (ref).deref() + if (conduit?.shouldReconnect) { + conduit.reconnect() + } else { + pool.delete(ref) + } + } +}) + +hooks.onApplicationPause(() => { + isApplicationPaused = true + const refs = Array.from(pool) + for (const ref of refs) { + // @ts-ignore + const conduit = /** @type {WeakRef<Conduit>} */ (ref).deref() + if (conduit) { + conduit.isConnecting = false + conduit.isActive = false + if (conduit.socket) { + conduit.socket.close() + } + } + } +}) /** - * @class Conduit - * @ignore - * - * @classdesc A class for managing WebSocket connections with custom options and payload encoding. + * A container for managing a WebSocket connection to the internal runtime + * Conduit WebSocket server. */ export class Conduit extends EventTarget { /** @@ -31,6 +81,52 @@ export class Conduit extends EventTarget { } } + /** + * Returns diagnostics information about the conduit server + * @return {Promise<ConduitDiagnostics>} + */ + static async diagnostics () { + const query = await diagnostics.runtime.query() + // @ts-ignore + return query.conduit + } + + /** + * Returns the current Conduit server status + * @return {Promise<ConduitStatus>} + */ + static async status () { + const result = await ipc.request('internal.conduit.status') + + if (result.err) { + throw result.err + } + + return { + port: result.data.port || 0, + isActive: result.data.isActive || false + } + } + + /** + * Waits for conduit to be active + * @param {{ maxQueriesForStatus?: number }=} [options] + * @return {Promise} + */ + static async waitForActiveState (options = null) { + const maxQueriesForStatus = options?.maxQueriesForStatus ?? Infinity + let queriesForStatus = 0 + while (queriesForStatus++ < maxQueriesForStatus) { + const status = await Conduit.status() + + if (status.isActive) { + break + } + + await sleep(256) + } + } + /** * @type {boolean} */ @@ -90,6 +186,11 @@ export class Conduit extends EventTarget { */ #onopen = null + /** + * @type {WeakRef<Conduit>} + */ + #ref = null + /** * Creates an instance of Conduit. * @@ -110,13 +211,18 @@ export class Conduit extends EventTarget { retries: DEFALUT_MAX_RECONNECT_RETRIES } - this.#loop = setInterval(() => { + this.#loop = setInterval(async () => { if (!this.isActive && !this.isConnecting && this.shouldReconnect) { - this.reconnect({ + await this.reconnect({ retries: --reconnectState.retries }) + } else { + reconnectState.retries = DEFALUT_MAX_RECONNECT_RETRIES } }, 256) + + this.#ref = new WeakRef(this) + pool.add(this.#ref) } /** @@ -201,7 +307,7 @@ export class Conduit extends EventTarget { * @return {Promise<Conduit>} */ async connect (callback = null) { - if (this.isConnecting) { + if (this.isConnecting || isApplicationPaused) { return this } @@ -210,12 +316,13 @@ export class Conduit extends EventTarget { } // reset + this.socket = null this.isActive = false this.isConnecting = true // @ts-ignore const resolvers = Promise.withResolvers() - const result = await ipc.send('internal.conduit.start') + const result = await ipc.request('internal.conduit.start') if (result.err) { if (typeof callback === 'function') { @@ -226,7 +333,10 @@ export class Conduit extends EventTarget { } } + await Conduit.waitForActiveState() + this.port = result.data.port + this.socket = new WebSocket(this.url) this.socket.binaryType = 'arraybuffer' this.socket.onerror = (e) => { @@ -246,12 +356,15 @@ export class Conduit extends EventTarget { this.isActive = false this.isConnecting = false this.dispatchEvent(new CloseEvent('close', e)) + if (this.shouldReconnect) { + this.reconnect() + } } this.socket.onopen = (e) => { this.isActive = true this.isConnecting = false - this.dispatchEvent(new Event('open')) + this.dispatchEvent(new Event('open', e)) if (typeof callback === 'function') { callback(null) @@ -435,13 +548,31 @@ export class Conduit extends EventTarget { */ close () { this.shouldReconnect = false + if (this.#loop) { clearInterval(this.#loop) this.#loop = 0 } + if (this.socket) { this.socket.close() this.socket = null } + + pool.delete(this.#ref) + } + + /** + * Implements `gc.finalizer` for gc'd resource cleanup. + * @ignore + * @return {gc.Finalizer} + */ + [gc.finalizer] () { + return { + args: [this.#ref], + handle (ref) { + pool.delete(ref) + } + } } } From f0693feecbc152098ef3c63695bfe4d5d4ead47b Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:21:34 +0100 Subject: [PATCH 1008/1178] refactor(api/internal/init.js): propagate application events to workers --- api/internal/init.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/api/internal/init.js b/api/internal/init.js index 5a2aed8c81..bc69d4d529 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -715,6 +715,46 @@ import { config } from '../application.js' import globals from './globals.js' import '../console.js' +hooks.onApplicationResume((e) => { + for (const ref of RuntimeWorker.pool.values()) { + const worker = ref.deref() + if (worker) { + worker.postMessage({ + __runtime_worker_event: { + type: 'applicationresume' + } + }) + } + } +}) + +hooks.onApplicationPause((e) => { + for (const ref of RuntimeWorker.pool.values()) { + const worker = ref.deref() + if (worker) { + worker.postMessage({ + __runtime_worker_event: { + type: 'applicationpause' + } + }) + } + } +}) + +hooks.onApplicationURL((e) => { + for (const ref of RuntimeWorker.pool.values()) { + const worker = ref.deref() + if (worker) { + worker.postMessage({ + __runtime_worker_event: { + type: 'applicationurl', + detail: { data: e.data, url: String(e.url ?? '') } + } + }) + } + } +}) + ipc.sendSync('platform.event', { value: 'load', 'location.href': globalThis.location.href From f8653818f6a9c8174f113917fd3955f9fa5719ff Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:24:15 +0100 Subject: [PATCH 1009/1178] refactor(api/internal/worker.js): improve event proxies --- api/internal/worker.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/internal/worker.js b/api/internal/worker.js index a6b9bfb24c..dcada6ccf7 100644 --- a/api/internal/worker.js +++ b/api/internal/worker.js @@ -1,4 +1,4 @@ -/* global reportError, EventTarget, CustomEvent, MessageEvent */ +/* global reportError, EventTarget, CustomEvent, MessageEvent, ApplicationURLEvent */ import { rand64 } from '../crypto.js' import { Loader } from '../commonjs/loader.js' import globals from './globals.js' @@ -184,8 +184,12 @@ export async function onWorkerMessage (event) { return false } else if (typeof data?.__runtime_worker_event === 'object') { const { type, detail } = data?.__runtime_worker_event || {} - if (type) { + if (type === 'applicationurl') { + globalThis.dispatchEvent(new ApplicationURLEvent(type, detail)) + } else if (type && detail) { globalThis.dispatchEvent(new CustomEvent(type, { detail })) + } else if (type) { + globalThis.dispatchEvent(new Event(type)) } event.stopImmediatePropagation() return false From fbc8c082d73f7ed874afae630f8d9c9e9feab51a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 22 Jul 2024 14:24:32 +0100 Subject: [PATCH 1010/1178] chore(api): generate types + docs --- api/README.md | 69 +++++--- api/index.d.ts | 454 +++++++++++++++++++++++++++++-------------------- 2 files changed, 314 insertions(+), 209 deletions(-) diff --git a/api/README.md b/api/README.md index eaaa18429f..e9b80473d0 100644 --- a/api/README.md +++ b/api/README.md @@ -15,7 +15,7 @@ External docs: https://nodejs.org/api/events.html <!-- This file is generated by bin/docs-generator/api-module.js --> <!-- Do not edit this file directly. --> -# [application](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L13) +# [application](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L14) Provides Application level methods @@ -25,17 +25,36 @@ External docs: https://nodejs.org/api/events.html import { createWindow } from 'socket:application' ``` -## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L38) +## [MAX_WINDOWS](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L62) This is a `VariableDeclaration` named `MAX_WINDOWS` in `api/application.js`, it's exported but undocumented. -## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L40) +## [ApplicationWindowList](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L64) This is a `ClassDeclaration` named `ApplicationWindowList` in `api/application.js`, it's exported but undocumented. -## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L165) +## [`addEventListener(type, listener, |boolean)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L191) + +Add an application event `type` callback `listener` with `options`. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| type | string | | false | | +| listener | function(Event \| MessageEvent \| CustomEvent \| ApplicationURLEvent): boolean | | false | | +| |boolean | { once?: boolean | } options | false | | + +## [`removeEventListener(type, listener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L200) + +Remove an application event `type` callback `listener` with `options`. + +| Argument | Type | Default | Optional | Description | +| :--- | :--- | :---: | :---: | :--- | +| type | string | | false | | +| listener | function(Event \| MessageEvent \| CustomEvent \| ApplicationURLEvent): boolean | | false | | + +## [`getCurrentWindowIndex()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L208) Returns the current window index @@ -43,7 +62,7 @@ Returns the current window index | :--- | :--- | :--- | | Not specified | number | | -## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L200) +## [`createWindow(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L243) Creates a new window and returns an instance of ApplicationWindow. @@ -81,15 +100,15 @@ Creates a new window and returns an instance of ApplicationWindow. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -### [`radius()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L227) +### [`radius()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L270) -### [`margin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L232) +### [`margin()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L275) -## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L320) +## [`getScreenSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L363) Returns the current screen size. @@ -97,7 +116,7 @@ Returns the current screen size. | :--- | :--- | :--- | | Not specified | Promise<{ width: number, height: number | >} | -## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L351) +## [`getWindows(indices)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L394) Returns the ApplicationWindow instances for the given indices or all windows if no indices are provided. @@ -109,7 +128,7 @@ Returns the ApplicationWindow instances for the given indices or all windows if | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindowList> | | -## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L405) +## [`getWindow(index)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L448) Returns the ApplicationWindow instance for the given index @@ -121,7 +140,7 @@ Returns the ApplicationWindow instance for the given index | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | the ApplicationWindow instance or null if the window does not exist | -## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L415) +## [`getCurrentWindow()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L458) Returns the ApplicationWindow instance for the current window. @@ -129,7 +148,7 @@ Returns the ApplicationWindow instance for the current window. | :--- | :--- | :--- | | Not specified | Promise<ApplicationWindow> | | -## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L424) +## [`exit(code)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L467) Quits the backend process and then quits the render process, the exit code used is the final exit code to the OS. @@ -141,7 +160,7 @@ Quits the backend process and then quits the render process, the exit code used | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L521) +## [`setSystemMenu(options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L564) Set the native menu for the app. @@ -236,11 +255,11 @@ Set the native menu for the app. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L528) +## [`setTrayMenu()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L571) An alias to setSystemMenu for creating a tary menu -## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L537) +## [`setSystemMenuItemEnabled(value)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L580) Set the enabled state of the system menu. @@ -252,23 +271,31 @@ Set the enabled state of the system menu. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -## [runtimeVersion](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L545) +## [`isPaused()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L588) + +Predicate function to determine if application is in a "paused" state. + +| Return Value | Type | Description | +| :--- | :--- | :--- | +| Not specified | boolean | | + +## [runtimeVersion](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L596) Socket Runtime version. -## [debug](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L551) +## [debug](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L602) Runtime debug flag. -## [config](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L557) +## [config](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L608) Application configuration. -## [backend](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L562) +## [backend](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L613) The application's backend instance. -### [`open(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L568) +### [`open(opts)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L619) @@ -281,7 +308,7 @@ The application's backend instance. | :--- | :--- | :--- | | Not specified | Promise<ipc.Result> | | -### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L576) +### [`close()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/application.js#L627) diff --git a/api/index.d.ts b/api/index.d.ts index 057c6ed66d..697ebc4bd3 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T>(key: Variable<T>): boolean; + get<T_1>(key: Variable<T_1>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T>(key: Variable<T>, value: T): Mapping; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T>(key: Variable<T>): Mapping; + delete<T_3>(key: Variable<T_3>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T>(key: Variable<T>): T | undefined; + static get<T_1>(key: Variable<T_1>): T_1; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T>(revert: Revert<T> | FrozenRevert): void; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_1>(): T_1 | undefined; + get<T_2>(): T_2; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F>(fn: F): F; + static wrap<F_1>(fn: F_1): F_1; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent; + prototype: CustomEvent<any>; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent; + prototype: MessageEvent<any>; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2161,7 +2161,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): value is symbol; + export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2837,7 +2837,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3072,7 +3072,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3102,7 +3102,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): signal; + export function getCode(name: string | number): any; /** * Gets the name for a given 'signal' code * @return {string} @@ -3246,7 +3246,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3627,7 +3627,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: ProcessEnvironment; + export const env: any; export default process; const process: any; } @@ -4533,9 +4533,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + send: (body?: Document | XMLHttpRequestBodyInit) => void; }; } export class WorkerMetric extends Metric { @@ -5263,7 +5263,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; + static from(stat?: object | Stats, fromBigInt?: any): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -5862,6 +5862,18 @@ declare module "socket:hooks" { * @return {function} */ export function onApplicationURL(callback: Function): Function; + /** + * Calls callback when a `ApplicationPause` is dispatched. + * @param {function} callback + * @return {function} + */ + export function onApplicationPause(callback: Function): Function; + /** + * Calls callback when a `ApplicationResume` is dispatched. + * @param {function} callback + * @return {function} + */ + export function onApplicationResume(callback: Function): Function; export const RUNTIME_INIT_EVENT_NAME: "__runtime_init__"; export const GLOBAL_EVENTS: string[]; /** @@ -6059,6 +6071,18 @@ declare module "socket:hooks" { * @return {function} */ onApplicationURL(callback: Function): Function; + /** + * Calls callback when an `ApplicationPause` is dispatched. + * @param {function} callback + * @return {function} + */ + onApplicationPause(callback: Function): Function; + /** + * Calls callback when an `ApplicationResume` is dispatched. + * @param {function} callback + * @return {function} + */ + onApplicationResume(callback: Function): Function; #private; } export default hooks; @@ -6112,7 +6136,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: "utf8" | "buffer"; + encoding: 'utf8' | 'buffer'; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6956,7 +6980,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "top-level" | "nested"; + get frameType(): "none" | "nested" | "top-level"; /** * The type of the client. * @type {'window'|'worker'} @@ -6988,10 +7012,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: "window" | "worker"; + type?: 'window' | 'worker'; parent?: object | null; top?: object | null; - frameType?: "top-level" | "nested" | "none"; + frameType?: 'top-level' | 'nested' | 'none'; }; } @@ -7065,7 +7089,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7278,7 +7302,6 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; - import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -7556,6 +7579,21 @@ declare module "socket:window" { } declare module "socket:application" { + /** + * Add an application event `type` callback `listener` with `options`. + * @param {string} type + * @param {function(Event|MessageEvent|CustomEvent|ApplicationURLEvent): boolean} listener + * @param {{ once?: boolean }|boolean=} [options] + */ + export function addEventListener(type: string, listener: (arg0: Event | MessageEvent | CustomEvent | ApplicationURLEvent) => boolean, options?: ({ + once?: boolean; + } | boolean) | undefined): void; + /** + * Remove an application event `type` callback `listener` with `options`. + * @param {string} type + * @param {function(Event|MessageEvent|CustomEvent|ApplicationURLEvent): boolean} listener + */ + export function removeEventListener(type: string, listener: (arg0: Event | MessageEvent | CustomEvent | ApplicationURLEvent) => boolean): void; /** * Returns the current window index * @return {number} @@ -7753,6 +7791,11 @@ declare module "socket:application" { * @return {Promise<ipc.Result>} */ export function setSystemMenuItemEnabled(value: object): Promise<ipc.Result>; + /** + * Predicate function to determine if application is in a "paused" state. + * @return {boolean} + */ + export function isPaused(): boolean; export const MAX_WINDOWS: 32; export class ApplicationWindowList { static from(...args: any[]): exports.ApplicationWindowList; @@ -7801,6 +7844,7 @@ declare module "socket:application" { function close(): Promise<ipc.Result>; } export default exports; + import { ApplicationURLEvent } from "socket:internal/events"; import ApplicationWindow from "socket:window"; import ipc from "socket:ipc"; import client from "socket:application/client"; @@ -8578,7 +8622,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -8947,19 +8991,134 @@ declare module "socket:constants" { export default _default; } +declare module "socket:timers/platform" { + export namespace platform { + let setTimeout: any; + let setInterval: any; + let setImmediate: any; + let clearTimeout: any; + let clearInterval: any; + let clearImmediate: any; + let postTask: any; + } + export default platform; +} + +declare module "socket:timers/timer" { + export class Timer extends AsyncResource { + static from(...args: any[]): Timer; + constructor(type: any, create: any, destroy: any); + get id(): number; + init(...args: any[]): this; + close(): boolean; + [Symbol.toPrimitive](): number; + #private; + } + export class Timeout extends Timer { + constructor(); + } + export class Interval extends Timer { + constructor(); + } + export class Immediate extends Timer { + constructor(); + } + namespace _default { + export { Timer }; + export { Immediate }; + export { Timeout }; + export { Interval }; + } + export default _default; + import { AsyncResource } from "socket:async/resource"; +} + +declare module "socket:timers/promises" { + export function setTimeout(delay?: number, value?: any, options?: any): Promise<any>; + export function setInterval(delay?: number, value?: any, options?: any): AsyncGenerator<any, void, unknown>; + export function setImmediate(value?: any, options?: any): Promise<any>; + namespace _default { + export { setImmediate }; + export { setInterval }; + export { setTimeout }; + } + export default _default; +} + +declare module "socket:timers/scheduler" { + export function wait(delay: any, options?: any): Promise<any>; + export function postTask(callback: any, options?: any): Promise<any>; + namespace _default { + export { postTask }; + export { setImmediate as yield }; + export { wait }; + } + export default _default; + import { setImmediate } from "socket:timers/promises"; +} + +declare module "socket:timers/index" { + export function setTimeout(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; + export function clearTimeout(timeout: any): void; + export function setInterval(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; + export function clearInterval(interval: any): void; + export function setImmediate(callback: any, ...args: any[]): import("socket:timers/timer").Timer; + export function clearImmediate(immediate: any): void; + /** + * Pause async execution for `timeout` milliseconds. + * @param {number} timeout + * @return {Promise} + */ + export function sleep(timeout: number): Promise<any>; + export namespace sleep { + /** + * Pause sync execution for `timeout` milliseconds. + * @param {number} timeout + */ + function sync(timeout: number): void; + } + export { platform }; + namespace _default { + export { platform }; + export { promises }; + export { scheduler }; + export { setTimeout }; + export { clearTimeout }; + export { setInterval }; + export { clearInterval }; + export { setImmediate }; + export { clearImmediate }; + } + export default _default; + import platform from "socket:timers/platform"; + import promises from "socket:timers/promises"; + import scheduler from "socket:timers/scheduler"; +} + +declare module "socket:timers" { + export * from "socket:timers/index"; + export default exports; + import * as exports from "socket:timers/index"; +} + declare module "socket:internal/conduit" { - export const DEFALUT_MAX_RECONNECT_RETRIES: 32; - export const DEFAULT_MAX_RECONNECT_TIMEOUT: 256; /** * @typedef {{ options: object, payload: Uint8Array }} ReceiveMessage * @typedef {function(Error?, ReceiveCallback | undefined)} ReceiveCallback * @typedef {{ id?: string|BigInt|number, reconnect?: {} }} ConduitOptions + * @typedef {{ isActive: boolean, handles: { ids: string[], count: number }}} ConduitDiagnostics + * @typedef {{ isActive: boolean, port: number }} ConduitStatus */ + export const DEFALUT_MAX_RECONNECT_RETRIES: 32; + export const DEFAULT_MAX_RECONNECT_TIMEOUT: 256; /** - * @class Conduit - * @ignore - * - * @classdesc A class for managing WebSocket connections with custom options and payload encoding. + * A pool of known `Conduit` instances. + * @type {Set<WeakRef<Conduit>>} + */ + export const pool: Set<WeakRef<Conduit>>; + /** + * A container for managing a WebSocket connection to the internal runtime + * Conduit WebSocket server. */ export class Conduit extends EventTarget { static set port(port: number); @@ -8968,6 +9127,24 @@ declare module "socket:internal/conduit" { * @type {number} */ static get port(): number; + /** + * Returns diagnostics information about the conduit server + * @return {Promise<ConduitDiagnostics>} + */ + static diagnostics(): Promise<ConduitDiagnostics>; + /** + * Returns the current Conduit server status + * @return {Promise<ConduitStatus>} + */ + static status(): Promise<ConduitStatus>; + /** + * Waits for conduit to be active + * @param {{ maxQueriesForStatus?: number }=} [options] + * @return {Promise} + */ + static waitForActiveState(options?: { + maxQueriesForStatus?: number; + } | undefined): Promise<any>; /** * Creates an instance of Conduit. * @@ -9098,6 +9275,17 @@ declare module "socket:internal/conduit" { id?: string | BigInt | number; reconnect?: {}; }; + export type ConduitDiagnostics = { + isActive: boolean; + handles: { + ids: string[]; + count: number; + }; + }; + export type ConduitStatus = { + isActive: boolean; + port: number; + }; } declare module "socket:ip" { @@ -9643,7 +9831,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9718,7 +9906,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: "shared" | "wasm32"; + type?: 'shared' | 'wasm32'; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10214,7 +10402,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: "strict" | "relaxed" | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10278,7 +10466,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: "set" | "delete", entry?: object | undefined); + constructor(type: 'set' | 'delete', entry?: object | undefined); entry: any; } /** @@ -10410,7 +10598,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import("socket:service-worker/events").ExtendableEvent); + constructor(event: import('./events.js').ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10451,7 +10639,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import("socket:service-worker/clients").Client>; + client(): Promise<import('./clients.js').Client>; #private; } namespace _default { @@ -10638,7 +10826,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10673,13 +10861,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -12861,7 +13049,7 @@ declare module "socket:latica/index" { /** * @return {undefined} */ - sync(peer: any): undefined; + sync(peer: any, ptime?: number): undefined; close(): void; /** * Deploy a query into the network @@ -12875,7 +13063,7 @@ declare module "socket:latica/index" { * from the cache when receiving a request to sync. that can be overridden * */ - cachePredicate(packet: any): boolean; + cachePredicate(ts: any): (packet: any) => boolean; /** * A connection was made, add the peer to the local list of known * peers and call the onConnection if it is defined by the user. @@ -13370,7 +13558,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T>(actual: T, expected: T, msg?: string): void; + notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; /** * @template T * @param {T} actual @@ -13378,7 +13566,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T>(actual: T, expected: T, msg?: string): void; + equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13627,7 +13815,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: { + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { /** * - The text to wait for */ @@ -13638,7 +13826,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; + }, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -13810,116 +13998,6 @@ declare module "socket:test" { import test from "socket:test/index"; } -declare module "socket:timers/platform" { - export namespace platform { - let setTimeout: any; - let setInterval: any; - let setImmediate: any; - let clearTimeout: any; - let clearInterval: any; - let clearImmediate: any; - let postTask: any; - } - export default platform; -} - -declare module "socket:timers/timer" { - export class Timer extends AsyncResource { - static from(...args: any[]): Timer; - constructor(type: any, create: any, destroy: any); - get id(): number; - init(...args: any[]): this; - close(): boolean; - [Symbol.toPrimitive](): number; - #private; - } - export class Timeout extends Timer { - constructor(); - } - export class Interval extends Timer { - constructor(); - } - export class Immediate extends Timer { - constructor(); - } - namespace _default { - export { Timer }; - export { Immediate }; - export { Timeout }; - export { Interval }; - } - export default _default; - import { AsyncResource } from "socket:async/resource"; -} - -declare module "socket:timers/promises" { - export function setTimeout(delay?: number, value?: any, options?: any): Promise<any>; - export function setInterval(delay?: number, value?: any, options?: any): AsyncGenerator<any, void, unknown>; - export function setImmediate(value?: any, options?: any): Promise<any>; - namespace _default { - export { setImmediate }; - export { setInterval }; - export { setTimeout }; - } - export default _default; -} - -declare module "socket:timers/scheduler" { - export function wait(delay: any, options?: any): Promise<any>; - export function postTask(callback: any, options?: any): Promise<any>; - namespace _default { - export { postTask }; - export { setImmediate as yield }; - export { wait }; - } - export default _default; - import { setImmediate } from "socket:timers/promises"; -} - -declare module "socket:timers/index" { - export function setTimeout(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; - export function clearTimeout(timeout: any): void; - export function setInterval(callback: any, delay: any, ...args: any[]): import("socket:timers/timer").Timer; - export function clearInterval(interval: any): void; - export function setImmediate(callback: any, ...args: any[]): import("socket:timers/timer").Timer; - export function clearImmediate(immediate: any): void; - /** - * Pause async execution for `timeout` milliseconds. - * @param {number} timeout - * @return {Promise} - */ - export function sleep(timeout: number): Promise<any>; - export namespace sleep { - /** - * Pause sync execution for `timeout` milliseconds. - * @param {number} timeout - */ - function sync(timeout: number): void; - } - export { platform }; - namespace _default { - export { platform }; - export { promises }; - export { scheduler }; - export { setTimeout }; - export { clearTimeout }; - export { setInterval }; - export { clearInterval }; - export { setImmediate }; - export { clearImmediate }; - } - export default _default; - import platform from "socket:timers/platform"; - import promises from "socket:timers/promises"; - import scheduler from "socket:timers/scheduler"; -} - -declare module "socket:timers" { - export * from "socket:timers/index"; - export default exports; - import * as exports from "socket:timers/index"; -} - declare module "socket:commonjs/builtins" { /** * Defines a builtin module by name making a shallow copy of the @@ -15028,7 +15106,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } export default Package; @@ -15039,13 +15117,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; prefix?: string; }; export type ParsedPackageName = { @@ -15128,7 +15206,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import("socket:commonjs/module").Module); + constructor(module: import('./module.js').Module); /** * The referrer (parent) of this module. * @type {string} @@ -15172,7 +15250,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15532,7 +15610,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions | undefined): any; + require(url: any, options?: RequireOptions): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15570,9 +15648,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import process from "socket:process"; } declare module "socket:module" { @@ -15912,7 +15990,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; + static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; /** * `Notification` class constructor. * @param {string} title @@ -16098,9 +16176,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; export default createServiceWorker; @@ -16768,7 +16846,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -16814,9 +16892,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): CoreAsyncResource; - bind(fn: Function, thisArg?: object | undefined): Function; - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; }; } export namespace Promise { @@ -16888,7 +16966,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16908,7 +16986,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -16930,8 +17008,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: "read" | "readwrite"; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + mode?: 'read' | 'readwrite'; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; }; /** * ]?: string[] @@ -16941,10 +17019,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; types?: Array<{ description?: string; - [keyof]: any; + [keyof]; }>; }; } @@ -17045,7 +17123,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17054,7 +17132,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: "commonjs" | "module"; + type: 'commonjs' | 'module'; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17135,8 +17213,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17150,7 +17228,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17599,12 +17677,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; + export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends Harness> { + export class TapeHarness<T extends exports.Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17657,7 +17735,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends Harness> = { + export type TapeTestFn<T extends exports.Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17674,8 +17752,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 40cb7ab24b4afad16cf70b6aa92989768c624841 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:28:10 +0200 Subject: [PATCH 1011/1178] refactor(window): more window manager APIs on android --- api/commonjs/loader.js | 3 +- api/commonjs/package.js | 1 + api/commonjs/require.js | 1 + api/internal/geolocation.js | 8 +- api/internal/globals.js | 7 + api/internal/init.js | 15 +- api/ipc.js | 15 +- src/app/app.kt | 33 ++++ src/core/color.cc | 336 +++++++++++++++++++++++++++++++++++ src/core/color.hh | 302 +++++++++++++++++++++++++++++++ src/core/core.hh | 6 + src/core/modules/platform.cc | 3 +- src/core/resource.cc | 2 +- src/ipc/bridge.kt | 8 +- src/ipc/message.cc | 4 + src/ipc/message.hh | 1 + src/ipc/preload.cc | 3 + src/ipc/routes.cc | 174 ++---------------- src/ipc/scheme_handlers.cc | 1 + src/ipc/scheme_handlers.kt | 84 +++++++++ src/window/android.cc | 158 +++++++++++++++- src/window/apple.mm | 10 +- src/window/dialog.kt | 4 + src/window/linux.cc | 2 +- src/window/manager.cc | 33 +++- src/window/manager.kt | 143 ++++++++++++++- src/window/win.cc | 4 +- src/window/window.hh | 14 +- src/window/window.kt | 12 +- 29 files changed, 1185 insertions(+), 202 deletions(-) create mode 100644 src/core/color.cc create mode 100644 src/core/color.hh diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 340d35c773..50fa1cbefa 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -8,6 +8,7 @@ import InternalSymbols from '../internal/symbols.js' import { Headers } from '../ipc.js' import location from '../location.js' import path from '../path.js' +import URL from '../url.js' const RUNTIME_SERVICE_WORKER_FETCH_MODE = 'Runtime-ServiceWorker-Fetch-Mode' const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' @@ -199,7 +200,7 @@ export class RequestStatus { request.open('HEAD', this.#request.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') - request.withCredentials = true + //request.withCredentials = true if (globalThis.isServiceWorkerScope) { request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 3be9bdb762..451ffffec4 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -7,6 +7,7 @@ import { isESMSource } from '../util.js' import { Loader } from './loader.js' import location from '../location.js' import path from '../path.js' +import URL from '../url.js' /** * `true` if in a worker scope. diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 9644a447a4..0f9cd874f6 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -3,6 +3,7 @@ import { DEFAULT_PACKAGE_PREFIX, Package } from './package.js' import { ModuleNotFoundError } from '../errors.js' import { isFunction } from '../util/types.js' import location from '../location.js' +import URL from '../url.js' /** * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver diff --git a/api/internal/geolocation.js b/api/internal/geolocation.js index 9826985248..7c247cba8c 100644 --- a/api/internal/geolocation.js +++ b/api/internal/geolocation.js @@ -140,7 +140,7 @@ export async function getCurrentPosition ( if (isAndroid) { await new Promise((resolve) => hooks.onReady(resolve)) - const result = await ipc.send('permissions.request', { name: 'geolocation' }) + const result = await ipc.request('permissions.request', { name: 'geolocation' }) if (result.err) { if (typeof onError === 'function') { @@ -187,7 +187,7 @@ export async function getCurrentPosition ( }, options.timeout) } - const result = await ipc.send('geolocation.getCurrentPosition') + const result = await ipc.request('geolocation.getCurrentPosition') if (didTimeout) { return @@ -276,7 +276,7 @@ export function watchPosition ( }, options.timeout) } - ipc.send('geolocation.watchPosition', { id: identifier }).then((result) => { + ipc.request('geolocation.watchPosition', { id: identifier }).then((result) => { if (result.err) { if (typeof onError === 'function') { onError(result.err) @@ -317,7 +317,7 @@ export function clearWatch (id) { watchers[id]?.stop() delete watchers[id] - ipc.send('geolocation.clearWatch', { id }) + ipc.request('geolocation.clearWatch', { id }) } export default { diff --git a/api/internal/globals.js b/api/internal/globals.js index 8c8377f418..ccaecd8a1f 100644 --- a/api/internal/globals.js +++ b/api/internal/globals.js @@ -1,3 +1,5 @@ +import Promise from './promise.js' + /** * Symbolic global registry * @ignore @@ -23,9 +25,14 @@ export class GlobalsRegistry { const registry = ( globalThis.top?.__globals ?? + globalThis.__globals ?? new GlobalsRegistry() ) +const RuntimeReadyPromiseResolvers = Promise.withResolvers() +registry.register('RuntimeReadyPromiseResolvers', RuntimeReadyPromiseResolvers) +registry.register('RuntimeReadyPromise', RuntimeReadyPromiseResolvers.promise) + /** * Gets a runtime global value by name. * @ignore diff --git a/api/internal/init.js b/api/internal/init.js index bc69d4d529..849e9ade0e 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -647,10 +647,12 @@ if (typeof globalThis.XMLHttpRequest === 'function') { const value = open.call(this, method, url.toString(), isAsyncRequest !== false, ...args) if ( - globalThis.__args?.config?.webview_fetch_allow_runtime_headers === true || - (url.protocol && /(socket|ipc|node|npm):/.test(url.protocol)) || - (url.protocol && protocols.handlers.has(url.protocol.slice(0, -1))) || - url.hostname === globalThis.__args.config.meta_bundle_identifier + method != 'OPTIONS' && ( + globalThis.__args?.config?.webview_fetch_allow_runtime_headers === true || + (url.protocol && /(socket|ipc|node|npm):/.test(url.protocol)) || + (url.protocol && protocols.handlers.has(url.protocol.slice(0, -1))) || + url.hostname === globalThis.__args.config.meta_bundle_identifier + ) ) { for (const key in additionalHeaders) { this.setRequestHeader(key, additionalHeaders[key]) @@ -986,7 +988,10 @@ Object.defineProperty(globalThis, '__globals', { value: globals }) -ipc.send('platform.event', 'runtimeinit').catch(reportError) +ipc.send('platform.event', 'runtimeinit') + .then(() => { + globals.get('RuntimeReadyPromiseResolvers')?.resolve() + }, reportError) export default { location diff --git a/api/ipc.js b/api/ipc.js index b748e81f0b..738d312c63 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -419,10 +419,19 @@ export class Headers extends globalThis.Headers { * @ignore */ static from (input) { - if (input?.headers) return this.from(input.headers) + if (input?.headers && typeof input.headers === 'object') { + input = input.headers + } - if (typeof input?.entries === 'function') { - return new this(input.entries()) + if (Array.isArray(input) && !Array.isArray(input[0])) { + input = input.join('\n') + } else if (typeof input?.entries === 'function') { + try { + return new this(input.entries()) + } catch (err) { + console.log({input}) + throw err + } } else if (isPlainObject(input) || isArrayLike(input)) { return new this(input) } else if (typeof input?.getAllResponseHeaders === 'function') { diff --git a/src/app/app.kt b/src/app/app.kt index 6171aea143..25b03e1b50 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -8,12 +8,16 @@ import android.app.Activity import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.AssetManager +import android.graphics.Insets import android.net.Uri import android.os.Build import android.os.Bundle +import android.view.WindowInsets +import android.view.WindowManager import android.webkit.MimeTypeMap import android.webkit.WebView @@ -41,6 +45,7 @@ open class AppPermissionRequest (callback: (Boolean) -> Unit) { open class AppActivity : WindowManagerActivity() { open protected val TAG = "AppActivity" open lateinit var notificationChannel: NotificationChannel + open lateinit var notificationManager: NotificationManager val permissionRequests = mutableListOf<AppPermissionRequest>() @@ -71,6 +76,28 @@ open class AppActivity : WindowManagerActivity() { this.startActivity(intent) } + fun getScreenInsets (): Insets { + val windowManager = this.applicationContext.getSystemService( + Context.WINDOW_SERVICE + ) as WindowManager + val metrics = windowManager.getCurrentWindowMetrics() + val windowInsets = metrics.windowInsets + return windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() or + WindowInsets.Type.displayCutout() + ) + } + + fun getScreenSizeWidth (): Int { + val insets = this.getScreenInsets() + return insets.right + insets.left + } + + fun getScreenSizeHeight (): Int { + val insets = this.getScreenInsets() + return insets.top + insets.bottom + } + override fun onCreate (savedInstanceState: Bundle?) { this.supportActionBar?.hide() this.getWindow()?.statusBarColor = android.graphics.Color.TRANSPARENT @@ -87,6 +114,8 @@ open class AppActivity : WindowManagerActivity() { NotificationManager.IMPORTANCE_DEFAULT ) + this.notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager + app.apply { setMimeTypeMap(MimeTypeMap.getSingleton()) setAssetManager(assetManager) @@ -104,6 +133,10 @@ open class AppActivity : WindowManagerActivity() { app.onCreateAppActivity(this) + if (app.hasRuntimePermission("notifications")) { + this.notificationManager.createNotificationChannel(this.notificationChannel) + } + if (savedInstanceState == null) { WebView.setWebContentsDebuggingEnabled(app.isDebugEnabled()) diff --git a/src/core/color.cc b/src/core/color.cc new file mode 100644 index 0000000000..704510cf4d --- /dev/null +++ b/src/core/color.cc @@ -0,0 +1,336 @@ +#include "../platform/string.hh" +#include "color.hh" + +namespace SSC { + template <typename T> + static inline T clamp (T v, T x, T y) { + if (v < x) { + return x; + } + + if (v > y) { + return y; + } + + return x; + } + + ColorComponents::ColorComponents (const ColorComponents& components) { + this->red = components.red; + this->green = components.green; + this->blue = components.blue; + this->alpha = components.alpha; + } + + ColorComponents::ColorComponents (ColorComponents&& components) { + this->red = components.red; + this->green = components.green; + this->blue = components.blue; + this->alpha = components.alpha; + + components.red = 0; + components.green = 0; + components.blue = 0; + components.alpha = 0.0f; + } + + ColorComponents::ColorComponents (const String& string) { + auto buffer = replace(trim(string), "none", "0"); + Vector<String> values; + + if (buffer.starts_with("rgba(")) { + buffer = buffer.substr(5); + if (buffer.ends_with(")")) { + buffer = trim(buffer.substr(0, buffer.size() - 1)); + const auto components = split(buffer, ','); + if (components.size() == 4) { + values.push_back(trim(components[0])); + values.push_back(trim(components[1])); + values.push_back(trim(components[2])); + values.push_back(trim(components[3])); + } + } + } else if (buffer.starts_with("rgb(")) { + buffer = buffer.substr(4); + if (buffer.ends_with(")")) { + buffer = trim(buffer.substr(0, buffer.size() - 1)); + const auto components = split(buffer, ','); + if (components.size() == 3) { + values.push_back(trim(components[0])); + values.push_back(trim(components[1])); + values.push_back(trim(components[2])); + } + } + } else if (buffer.starts_with("#")) { + buffer = buffer.substr(1); + if (buffer.size() == 6) { + try { + values.push_back(std::to_string(std::stoul(buffer.substr(0, 2), 0, 16))); + values.push_back(std::to_string(std::stoul(buffer.substr(2, 2), 0, 16))); + values.push_back(std::to_string(std::stoul(buffer.substr(4, 2), 0, 16))); + } catch (...) {} + } + } + + if (values.size() == 3 || values.size() == 4) { + try { + if (values[0].ends_with("%")) { + this->red = 255 * ( + std::stoi(values[0].substr(0, values[0].size() - 1)) / 100.0f + ); + } else { + this->red = std::stoi(values[0]); + } + + if (values[1].ends_with("%")) { + this->green = 255 * ( + std::stoi(values[1].substr(0, values[1].size() - 1)) / 100.0f + ); + } else { + this->green = std::stoi(values[1]); + } + + if (values[2].ends_with("%")) { + this->blue = 255 * ( + std::stoi(values[2].substr(0, values[2].size() - 1)) / 100.0f + ); + } else { + this->blue = std::stoi(values[2]); + } + + if (values.size() == 4) { + if (values[3].ends_with("%")) { + this->alpha = std::stoi(values[2].substr(0, values[2].size() - 1)) / 100.0f; + } else { + this->alpha = std::stoi(values[2]); + if (this->alpha > 1.0f) { + this->alpha = this->alpha / 255.0f; + } + } + } + } catch (...) {} + } + } + + ColorComponents::ColorComponents ( + unsigned int red, + unsigned int green, + unsigned int blue, + float alpha + ) : red(red & 0xFF), + green(green & 0xFF), + blue(blue & 0xFF), + alpha(clamp(alpha, MIN_FLOAT_CHANNEL, MAX_FLOAT_CHANNEL)) + {} + + ColorComponents::ColorComponents ( + float red, + float green, + float blue, + float alpha + ) { + this->red = static_cast<int>(red * MAX_INT_CHANNEL) & 0xFF; + this->green = static_cast<int>(green * MAX_INT_CHANNEL) & 0xFF; + this->blue = static_cast<int>(blue * MAX_INT_CHANNEL) & 0xFF; + this->alpha = clamp(alpha, MIN_FLOAT_CHANNEL, MAX_FLOAT_CHANNEL); + } + + ColorComponents::ColorComponents ( + unsigned int red, + unsigned int green, + unsigned int blue, + unsigned int alpha + ) { + this->red = red & 0xFF; + this->green = green & 0xFF; + this->blue = blue & 0xFF; + this->alpha = clamp( + alpha / static_cast<float>(MAX_INT_CHANNEL), + MIN_FLOAT_CHANNEL, + MAX_FLOAT_CHANNEL + ); + } + + ColorComponents::ColorComponents ( + uint32_t value, + const Pack& pack + ) { + switch (pack) { + case Pack::RGB: { + this->red = (value >> 24) & 0xFF; + this->green = (value >> 16) & 0xFF; + this->blue = (value >> 8) & 0xFF; + this->alpha = 1.0f; + break; + } + + case Pack::RGBA: { + this->red = (value >> 24) & 0xFF; + this->green = (value >> 16) & 0xFF; + this->blue = (value >> 8) & 0xFF; + this->alpha = (value & 0xFF) / static_cast<float>(MAX_INT_CHANNEL); + break; + } + + case Pack::ARGB: { + this->alpha = ((value >> 24) & 0xFF) / static_cast<float>(MAX_INT_CHANNEL); + this->red = (value >> 16) & 0xFF; + this->green = (value >> 8) & 0xFF; + this->blue = value & 0xFF; + break; + } + } + } + + ColorComponents& ColorComponents::operator = (const ColorComponents& components) { + this->red = components.red; + this->green = components.green; + this->blue = components.blue; + this->alpha = components.alpha; + return *this; + } + + ColorComponents& ColorComponents::operator = (ColorComponents&& components) { + this->red = components.red; + this->green = components.green; + this->blue = components.blue; + this->alpha = components.alpha; + + components.red = 0; + components.green = 0; + components.blue = 0; + components.alpha = 0.0f; + return *this; + } + + JSON::Object ColorComponents::json () const { + return JSON::Object::Entries { + {"red", this->red}, + {"green", this->green}, + {"blue", this->blue}, + {"alpha", this->alpha} + }; + } + + String ColorComponents::str (const Pack& pack) const { + String output; + + switch (pack) { + case Pack::RGB: + output = "rgb({{red}}, {{green}}, {{blue}})"; + break; + case Pack::RGBA: + output = "rgba({{red}}, {{green}}, {{blue}}, {{alpha}})"; + break; + case Pack::ARGB: + output = "argb({{alpha}}, {{red}}, {{green}}, {{blue}})"; + break; + } + + return tmpl(output, Map { + {"red", std::to_string(this->red)}, + {"green", std::to_string(this->green)}, + {"blue", std::to_string(this->blue)}, + {"alpha", std::to_string(this->alpha)}, + }); + } + + uint32_t ColorComponents::pack (const Pack& pack) const { + switch (pack) { + case Pack::RGB: { + return ( + (this->red & 0xFF) << 24 | + (this->green & 0xFF) << 16 | + (this->blue & 0xFF) << 8 | + (255) // asume full alpha channel value + ); + } + + case Pack::RGBA: { + const auto alpha = static_cast<int>( + 255.0f * clamp(this->alpha, MIN_FLOAT_CHANNEL, MAX_FLOAT_CHANNEL) + ); + return ( + (this->red & 0xFF) << 24 | + (this->green & 0xFF) << 16 | + (this->blue & 0xFF) << 8 | + (alpha & 0xFF) + ); + } + + case Pack::ARGB: { + const auto alpha = static_cast<int>( + 255.0f * clamp(this->alpha, MIN_FLOAT_CHANNEL, MAX_FLOAT_CHANNEL) + ); + return ( + (alpha & 0xFF) << 24 | + (this->red & 0xFF) << 16 | + (this->green & 0xFF) << 8 | + (this->blue & 0xFF) + ); + } + } + + return 0; + } + + Color::Color (const ColorComponents&) { + } + + Color::Color ( + unsigned int red, + unsigned int green, + unsigned int blue, + float alpha + ) : ColorComponents(red, green, blue, alpha) + {} + + Color::Color (const Color& color) + : Color(color.red, color.green, color.blue, color.alpha) + {} + + Color::Color (Color&& color) + : Color(color.red, color.green, color.blue, color.alpha) + { + color.red = 0; + color.green = 0; + color.blue = 0; + color.alpha = 0.0f; + } + + Color& Color::operator = (const Color& color) { + this->red = color.red; + this->green = color.green; + this->blue = color.blue; + this->alpha = color.alpha; + return *this; + } + + Color& Color::operator = (Color&& color) { + this->red = color.red; + this->green = color.green; + this->blue = color.blue; + this->alpha = color.alpha; + color.red = 0; + color.green = 0; + color.blue = 0; + color.alpha = 0.0f; + return *this; + } + + bool Color::operator == (const Color& color) const { + return this->pack() == color.pack(); + } + + bool Color::operator != (const Color& color) const { + return this->pack() != color.pack(); + } + + bool Color::operator > (const Color& color) const { + return this->pack() > color.pack(); + } + + bool Color::operator < (const Color& color) const { + return this->pack() < color.pack(); + } +} diff --git a/src/core/color.hh b/src/core/color.hh new file mode 100644 index 0000000000..e742dfb953 --- /dev/null +++ b/src/core/color.hh @@ -0,0 +1,302 @@ +#ifndef SOCKET_RUNTIME_CORE_COLOR_H +#define SOCKET_RUNTIME_CORE_COLOR_H + +#include "json.hh" + +namespace SSC { + /** + * A container for RGB color components with an alpha channel + */ + class ColorComponents { + public: + /** + * Various pack formats for color components represented as a + * 32 bit unsigned integer. + */ + enum class Pack { RGB, RGBA, ARGB }; + + /** + * The minimum "int" color component value. + */ + static constexpr unsigned int MIN_INT_CHANNEL = 0; + + /** + * The maximum "int" color component value. + */ + static constexpr unsigned int MAX_INT_CHANNEL = 255; + + /** + * The minimum "float" color component value. + */ + static constexpr float MIN_FLOAT_CHANNEL = 0.0f; + + /** + * The maximum "float" color component value. + */ + static constexpr float MAX_FLOAT_CHANNEL = 1.0f; + + /** + * The "red" color component. + */ + unsigned int red = MIN_INT_CHANNEL; + + /** + * The "green" color component. + */ + unsigned int green = MIN_INT_CHANNEL; + + /** + * The "blue" color component. + */ + unsigned int blue = MIN_INT_CHANNEL; + + /** + * The alpha channel component. + */ + float alpha = MAX_FLOAT_CHANNEL; + + /** + * Default `ColorComponents` constructor. + */ + ColorComponents () = default; + + /** + * `ColorComponents` "copy" constructor. + */ + ColorComponents (const ColorComponents&); + + /** + * `ColorComponents` "move" constructor. + */ + ColorComponents (ColorComponents&&); + + /** + * `ColorComponents` constructor from a "rgba()", "rgb()", + * or "#RRGGBB" syntax DSL. + */ + ColorComponents (const String&); + + /** + * `ColorComponents` constructor for "int" color components + * with a "float" alpha channel. + */ + ColorComponents ( + unsigned int red, + unsigned int green, + unsigned int blue, + float alpha = MAX_FLOAT_CHANNEL + ); + + /** + * `ColorComponents` constructor for "float" color components + * with a "float" alpha channel. + */ + ColorComponents ( + float red, + float green, + float blue, + float alpha = MAX_FLOAT_CHANNEL + ); + + /** + * `ColorComponents` constructor for "float" color components + * with a "float" alpha channel. + */ + ColorComponents ( + unsigned int red, + unsigned int green, + unsigned int blue, + unsigned int alpha = MAX_INT_CHANNEL + ); + + /** + * `ColorComponents` constructor "packed" color components + * with a pack type. + */ + ColorComponents ( + uint32_t value, + const Pack& pack = Pack::RGBA + ); + + /** + * `ColorComponents` "copy" assignment. + */ + ColorComponents& operator = (const ColorComponents&); + + /** + * `ColorComponents` "move" assignment. + */ + ColorComponents& operator = (ColorComponents&&); + + /** + * Returns a JSON object of color components. + */ + JSON::Object json () const; + + /** + * Returns a string representation of the color components. + */ + String str (const Pack& pack = Pack::RGBA) const; + + /** + * Returns a packed representation of the color components. + */ + uint32_t pack (const Pack& pack = Pack::RGBA) const; + }; + + /** + * A container for RGBA based color space. + */ + class Color : public ColorComponents { + public: + /** + * An alias to `ColorComponents::Pack` + */ + using Pack = ColorComponents::Pack; + + /** + * An alias for `ColorComponents::MIN_INT_CHANNEL` + */ + static constexpr unsigned int MIN_INT_CHANNEL = ColorComponents::MIN_INT_CHANNEL; + + /** + * An alias for `ColorComponents::MIN_INT_CHANNEL` + */ + static constexpr unsigned int MAX_INT_CHANNEL = ColorComponents::MAX_INT_CHANNEL; + + /** + * An alias for `ColorComponents::MIN_INT_CHANNEL` + */ + static constexpr float MIN_FLOAT_CHANNEL = ColorComponents::MIN_FLOAT_CHANNEL; + + /** + * An alias for `ColorComponents::MIN_INT_CHANNEL` + */ + static constexpr float MAX_FLOAT_CHANNEL = ColorComponents::MAX_FLOAT_CHANNEL; + + /** + * Creates a packed rgba value from `red`, `green`, `blue` "int" + * color components and an `alpha` "float" channel. + */ + static uint32_t rgba ( + unsigned int red = MIN_INT_CHANNEL, + unsigned int green = MIN_INT_CHANNEL, + unsigned int blue = MIN_INT_CHANNEL, + float alpha = MAX_FLOAT_CHANNEL + ) { + return Color(red, green, blue, alpha).pack(Pack::RGBA); + } + + /** + * Creates a packed rgba value from `red`, `green`, `blue` "float" + * color components and an `alpha` "float" channel. + */ + static uint32_t rgba ( + float red = MIN_FLOAT_CHANNEL, + float green = MIN_FLOAT_CHANNEL, + float blue = MIN_FLOAT_CHANNEL, + float alpha = MAX_FLOAT_CHANNEL + ) { + return Color(red, green, blue, alpha).pack(Pack::RGBA); + } + + /** + * Creates a packed rgba value from `red`, `green`, `blue` "int" + * color components and an `alpha` "int" channel. + */ + static uint32_t rgba ( + unsigned int red = MIN_INT_CHANNEL, + unsigned int green = MIN_INT_CHANNEL, + unsigned int blue = MIN_INT_CHANNEL, + unsigned int alpha = MAX_INT_CHANNEL + ) { + return Color(red, green, blue, alpha).pack(Pack::RGBA); + } + + /** + * Creates a packed rgb value from `red`, `green`, and `blue` "int" + * color components. + */ + static uint32_t rgb ( + unsigned int red = MIN_INT_CHANNEL, + unsigned int green = MIN_INT_CHANNEL, + unsigned int blue = MIN_INT_CHANNEL + ) { + return Color(red, green, blue).pack(Pack::RGB); + } + + /** + * Creates a packed rgb value from `red`, `green`, and `blue` "float" + * color components. + */ + static uint32_t rgb ( + float red = MIN_FLOAT_CHANNEL, + float green = MIN_FLOAT_CHANNEL, + float blue = MIN_FLOAT_CHANNEL + ) { + return Color(red, green, blue).pack(Pack::RGB); + } + + /** + * Default `Color` constructor. + */ + Color () = default; + + /** + * `Color` constructor from `ColorComponents` + */ + Color (const ColorComponents&); + + /** + * `Color` constructor with "int" color components and + * a "float" alpha channel. + */ + Color ( + unsigned int red, + unsigned int green, + unsigned int blue, + float alpha = MIN_FLOAT_CHANNEL + ); + + /** + * `Color` "copy" constructor. + */ + Color (const Color&); + + /** + * `Color` "move" constructor. + */ + Color (Color&&); + + /** + * `Color` "copy" assignment. + */ + Color& operator = (const Color&); + + /** + * `Color` "move" assignment. + */ + Color& operator = (Color&&); + + /** + * `Color` equality operator. + */ + bool operator == (const Color&) const; + + /** + * `Color` inequality operator. + */ + bool operator != (const Color&) const; + + /** + * `Color` greater than inequality operator. + */ + bool operator > (const Color&) const; + + /** + * `Color` less than inequality operator. + */ + bool operator < (const Color&) const; + }; +} +#endif diff --git a/src/core/core.hh b/src/core/core.hh index 609ea6fd5d..c27bf58af5 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -5,6 +5,7 @@ #include "bluetooth.hh" #include "codec.hh" +#include "color.hh" #include "config.hh" #include "debug.hh" #include "env.hh" @@ -34,6 +35,7 @@ #include "modules/network_status.hh" #include "modules/notifications.hh" #include "modules/os.hh" +#include "modules/permissions.hh" #include "modules/platform.hh" #include "modules/timers.hh" #include "modules/udp.hh" @@ -60,6 +62,7 @@ namespace SSC { using NetworkStatus = CoreNetworkStatus; using Notifications = CoreNotifications; using OS = CoreOS; + using Permissions = CorePermissions; using Platform = CorePlatform; using Timers = CoreTimers; using UDP = CoreUDP; @@ -78,6 +81,7 @@ namespace SSC { bool useNotifications = true; bool useConduit = true; bool useOS = true; + bool usePermissions = true; bool usePlatform = true; bool useTimers = true; bool useUDP = true; @@ -111,6 +115,7 @@ namespace SSC { NetworkStatus networkStatus; Notifications notifications; OS os; + Permissions permissions; Platform platform; Timers timers; UDP udp; @@ -165,6 +170,7 @@ namespace SSC { networkStatus(this), notifications(this), os(this), + permissions(this), platform(this), timers(this), udp(this) diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index dde8bd38ac..23065a53b7 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -281,7 +281,8 @@ namespace SSC { Boolean, this->activity, "openExternal", - "(Ljava/lang/String;)Z" + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF(value.c_str()) ); if (attachment.hasException()) { diff --git a/src/core/resource.cc b/src/core/resource.cc index 5a364e9428..871c7c081c 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -426,7 +426,7 @@ namespace SSC { this->data = Path(Env::get("APPDATA")) / bundleIdentifier; this->log = this->config; #elif SOCKET_RUNTIME_PLATFORM_ANDROID - // TODO + // TODO(@jwerle) #endif } diff --git a/src/ipc/bridge.kt b/src/ipc/bridge.kt index 0adaa0abf3..f35ce2dcee 100644 --- a/src/ipc/bridge.kt +++ b/src/ipc/bridge.kt @@ -2,6 +2,7 @@ package socket.runtime.ipc import android.content.Intent +import android.net.Uri import android.webkit.WebView import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse @@ -9,12 +10,12 @@ import android.webkit.WebResourceResponse import androidx.appcompat.app.AppCompatActivity import socket.runtime.app.App -import socket.runtime.ipc.Navigator -import socket.runtime.ipc.SchemeHandlers import socket.runtime.core.console import socket.runtime.core.WebViewClient +import socket.runtime.ipc.Navigator +import socket.runtime.ipc.SchemeHandlers -private fun isAndroidAssetsUri (uri: android.net.Uri): Boolean { +private fun isAndroidAssetsUri (uri: Uri): Boolean { if (uri.pathSegments.size == 0) { return false } @@ -47,7 +48,6 @@ open class Bridge ( view: WebView, request: WebResourceRequest ): Boolean { - console.log("request.url.scheme: ${request.url.scheme}") if (isAndroidAssetsUri(request.url)) { return false } diff --git a/src/ipc/message.cc b/src/ipc/message.cc index 0b791a320b..0b514ea4de 100644 --- a/src/ipc/message.cc +++ b/src/ipc/message.cc @@ -88,4 +88,8 @@ namespace SSC::IPC { ? decodeURIComponent(args.at(key)) : fallback; } + + const Map Message::dump () const { + return this->args; + } } diff --git a/src/ipc/message.hh b/src/ipc/message.hh index a99d612d28..3b42962fd3 100644 --- a/src/ipc/message.hh +++ b/src/ipc/message.hh @@ -58,6 +58,7 @@ namespace SSC::IPC { String get (const String& key) const; String get (const String& key, const String& fallback) const; String str () const { return this->uri; } + const Map dump () const; const char* c_str () const { return this->uri.c_str(); } }; } diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 83495a001c..2e8831f9a5 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -522,6 +522,9 @@ namespace SSC::IPC { buffers.push_back(R"JAVASCRIPT( if (globalThis.document) { ;(async function GlobalCommonJSScope () { + const globals = await import('socket:internal/globals') + await globals.get('RuntimeReadyPromise') + const href = encodeURIComponent(globalThis.location.href) const source = `socket:module?ref=${href}` diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 021917e014..c7366bde5f 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -689,8 +689,12 @@ static void mapIPCRoutes (Router *router) { {"abi", SOCKET_RUNTIME_EXTENSION_ABI_VERSION}, {"name", name}, {"type", type}, + #if SOCKET_RUNTIME_PLATFORM_ANDROID + {"path", FileResource::getResourcePath(path).string()} + #else // `path` is absolute to the location of the resources {"path", String("/") + std::filesystem::relative(path, getcwd()).string()} + #endif }} }; @@ -1853,172 +1857,26 @@ static void mapIPCRoutes (Router *router) { return reply(Result::Err { message, err }); } - auto name = message.get("name"); - - #if SOCKET_RUNTIME_PLATFORM_APPLE - if (name == "geolocation") { - if (router->bridge->core->geolocation.locationObserver.isAuthorized) { - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - } else if (router->bridge->core->geolocation.locationObserver.locationManager) { - auto authorizationStatus = ( - router->bridge->core->geolocation.locationObserver.locationManager.authorizationStatus - ); - - if (authorizationStatus == kCLAuthorizationStatusDenied) { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } else { - auto data = JSON::Object::Entries {{"state", "prompt"}}; - return reply(Result::Data { message, data }); - } - } - - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } - - if (name == "notifications") { - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - if (!settings) { - auto err = JSON::Object::Entries {{ "message", "Failed to reach user notification settings" }}; - return reply(Result::Err { message, err }); - } - - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - auto data = JSON::Object::Entries {{"state", "prompt"}}; - return reply(Result::Data { message, data }); - } - - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - }]; - } - #endif + return router->bridge->core->permissions.query( + message.seq, + message.get("name"), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); }); router->map("permissions.request", [=](auto message, auto router, auto reply) { - #if SOCKET_RUNTIME_PLATFORM_APPLE - __block auto userConfig = router->bridge->userConfig; - #else - auto userConfig = router->bridge->userConfig; - #endif - auto err = validateMessageParameters(message, {"name"}); if (err.type != JSON::Type::Null) { return reply(Result::Err { message, err }); } - auto name = message.get("name"); - - if (name == "geolocation") { - #if SOCKET_RUNTIME_PLATFORM_APPLE - auto performedActivation = [router->bridge->core->geolocation.locationObserver attemptActivationWithCompletion: ^(BOOL isAuthorized) { - if (!isAuthorized) { - auto reason = @("Location observer could not be activated"); - - if (!router->bridge->core->geolocation.locationObserver.locationManager) { - reason = @("Location observer manager is not initialized"); - } else if (!router->bridge->core->geolocation.locationObserver.locationManager.location) { - reason = @("Location observer manager could not provide location"); - } - - auto error = [NSError - errorWithDomain: @(userConfig["meta_bundle_identifier"].c_str()) - code: -1 - userInfo: @{ - NSLocalizedDescriptionKey: reason - } - ]; - } - - if (isAuthorized) { - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - } else if (router->bridge->core->geolocation.locationObserver.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { - auto data = JSON::Object::Entries {{"state", "prompt"}}; - return reply(Result::Data { message, data }); - } else { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } - }]; - - if (!performedActivation) { - auto err = JSON::Object::Entries {{ "message", "Location observer could not be activated" }}; - err["type"] = "GeolocationPositionError"; - return reply(Result::Err { message, err }); - } - - return; - #endif - } - - if (name == "notifications") { - #if SOCKET_RUNTIME_PLATFORM_APPLE - UNAuthorizationOptions options = UNAuthorizationOptionProvisional; - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - auto requestAlert = message.get("alert") == "true"; - auto requestBadge = message.get("badge") == "true"; - auto requestSound = message.get("sound") == "true"; - - if (requestAlert) { - options |= UNAuthorizationOptionAlert; - } - - if (requestBadge) { - options |= UNAuthorizationOptionBadge; - } - - if (requestSound) { - options |= UNAuthorizationOptionSound; - } - - if (requestAlert && requestSound) { - options |= UNAuthorizationOptionCriticalAlert; - } - - [notificationCenter - requestAuthorizationWithOptions: options - completionHandler: ^(BOOL granted, NSError *error) { - [notificationCenter - getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - auto data = JSON::Object::Entries {{"state", "denied"}}; - return reply(Result::Data { message, data }); - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - if (error) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); - - auto err = JSON::Object::Entries { - { "message", message } - }; - - return reply(Result::Err { message, err }); - } - - auto data = JSON::Object::Entries { - {"state", granted ? "granted" : "denied" } - }; - - return reply(Result::Data { message, data }); - } - - auto data = JSON::Object::Entries {{"state", "granted"}}; - return reply(Result::Data { message, data }); - }]; - }]; - #endif - } + return router->bridge->core->permissions.request( + message.seq, + message.get("name"), + message.dump(), + RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) + ); }); /** @@ -3474,7 +3332,7 @@ static void mapIPCRoutes (Router *router) { window->maximize(); #else const auto screen = window->getScreenSize(); - window->setSize(screen.height, screen.width); + window->setSize(screen.width, screen.height); window->show(); #endif reply(Result::Data { message, window->json() }); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 42d85da16b..292698ecf5 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -993,6 +993,7 @@ namespace SSC::IPC { this->setHeader("access-control-allow-origin", "*"); this->setHeader("access-control-allow-headers", "*"); this->setHeader("access-control-allow-methods", "*"); + this->setHeader("access-control-allow-credentials", "true"); for (const auto& entry : defaultHeaders) { const auto parts = split(trim(entry), ':'); diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index 002eef8657..87d2c6e8cf 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -11,6 +11,10 @@ import kotlin.concurrent.thread import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse +import androidx.core.app.NotificationManagerCompat + +import socket.runtime.app.App +import socket.runtime.app.AppActivity import socket.runtime.core.console import socket.runtime.ipc.Message @@ -161,6 +165,86 @@ open class SchemeHandlers (val bridge: Bridge) { } fun handleRequest (webResourceRequest: WebResourceRequest): WebResourceResponse? { + if (webResourceRequest.url.scheme == "ipc") { + val activity = bridge.activity as AppActivity + val message = Message(webResourceRequest.url.toString()) + + if (message.name == "permissions.request") { + val request = Request(this.bridge, webResourceRequest) + val name = message.get("name") + } + + if (message.name == "permissions.query") { + val request = Request(this.bridge, webResourceRequest) + val name = message.get("name") + + if (!App.getInstance().hasRuntimePermission(name)) { + request.response.setHeader("content-type", "application/json") + request.response.setStatus(200, "OK") + request.response.write("""{ + "data": { + "state": "denied", + "reason": "Runtime permission is disabled for '${name}'" + } + }""") + + request.response.finish() + } else if (name == "geolocation") { + if ( + !activity.checkPermission("android.permission.ACCESS_COARSE_LOCATION") || + !activity.checkPermission("android.permission.ACCESS_FINE_LOCATION") + ) { + request.response.setHeader("content-type", "application/json") + request.response.setStatus(200, "OK") + request.response.write("""{ + "data": { + "state": "prompt" + } + }""") + + request.response.finish() + } else { + request.response.setHeader("content-type", "application/json") + request.response.setStatus(200, "OK") + request.response.write("""{ + "data": { + "state": "granted" + } + }""") + + request.response.finish() + } + } else if (name == "notifications" || name == "push") { + if ( + !activity.checkPermission("android.permission.POST_NOTIFICATIONS") || + !NotificationManagerCompat.from(activity).areNotificationsEnabled() + ) { + request.response.setHeader("content-type", "application/json") + request.response.setStatus(200, "OK") + request.response.write("""{ + "data": { + "state": "prompt" + } + }""") + + request.response.finish() + } else { + request.response.setHeader("content-type", "application/json") + request.response.setStatus(200, "OK") + request.response.write("""{ + "data": { + "state": "granted" + } + }""") + + request.response.finish() + } + } + + return request.getWebResourceResponse() + } + } + val request = Request(this.bridge, webResourceRequest) if (this.handleRequest(this.bridge.index, request)) { diff --git a/src/window/android.cc b/src/window/android.cc index fe8a32379a..371dee2660 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -69,10 +69,31 @@ namespace SSC { } ScreenSize Window::getScreenSize () { - return ScreenSize {0, 0}; + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.getWindowWidth(index): String` + const auto width = CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getScreenSizeWidth", + "()I" + ); + + const auto height = CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getScreenSizeHeight", + "()I" + ); + + return ScreenSize {width, height}; } void Window::about () { + // XXX(@jwerle): not supported + // TODO(@jwerle): figure out we'll go about making this possible } void Window::eval (const String& source) { @@ -217,51 +238,178 @@ namespace SSC { } Window::Size Window::getSize () { - return Size {0, 0}; + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.getWindowWidth(index): String` + const auto width = CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getWindowWidth", + "(I)I", + this->index + ); + + const auto height = CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getWindowHeight", + "(I)I", + this->index + ); + this->size.width = width; + this->size.height = height; + return Size {width, height}; } const Window::Size Window::getSize () const { - return Size {0, 0}; + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.getWindowWidth(index): Int` + const auto width = CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getWindowWidth", + "(I)I", + this->index + ); + + // `activity.getWindowHeight(index): Int` + const auto height = CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getWindowHeight", + "(I)I", + this->index + ); + return Size {width, height}; } - void Window::setSize (int height, int width, int _) { + void Window::setSize (int width, int height, int _) { + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.setWindowSize(index, w): Int` + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + app->activity, + "setWindowSize", + "(III)Z", + this->index, + width, + height + ); } void Window::setPosition (float x, float y) { + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + this->position.x = x; + this->position.y = y; + // `activity.setWindowBackgroundColor(index, color)` + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + app->activity, + "setWindowPosition", + "(IFF)Z", + this->index, + x, + y + ); } void Window::setContextMenu (const String&, const String&) { + // XXX(@jwerle): not supported } void Window::closeContextMenu (const String&) { + // XXX(@jwerle): not supported } void Window::closeContextMenu () { + // XXX(@jwerle): not supported } void Window::setBackgroundColor (int r, int g, int b, float a) { + const auto app = App::sharedApplication(); + const auto color = Color(r, g, b, a); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.setWindowBackgroundColor(index, color)` + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + app->activity, + "setWindowBackgroundColor", + "(IJ)Z", + this->index, + color.pack() + ); } void Window::setBackgroundColor (const String& rgba) { + const auto app = App::sharedApplication(); + const auto color = Color(rgba); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.setWindowBackgroundColor(index, color)` + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + app->activity, + "setWindowBackgroundColor", + "(IJ)Z", + this->index, + color.pack() + ); + } + + void Window::setBackgroundColor (const Color& color) { + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.setWindowBackgroundColor(index, color)` + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + app->activity, + "setWindowBackgroundColor", + "(I)Z", + this->index, + color.pack() + ); } String Window::getBackgroundColor () { - return ""; + const auto app = App::sharedApplication(); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + // `activity.getWindowBackgroundColor(index): Int` + const auto color = Color(CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + app->activity, + "getWindowBackgroundColor", + "(I)I", + this->index + )); + + return color.str(); } void Window::setSystemMenuItemEnabled (bool enabled, int barPos, int menuPos) { + // XXX(@jwerle): not supported } void Window::setSystemMenu (const String& dsl) { + // XXX(@jwerle): not supported } void Window::setMenu (const String& dsl, const bool& isTrayMenu) { + // XXX(@jwerle): not supported } void Window::setTrayMenu (const String& dsl) { + // XXX(@jwerle): not supported } void Window::showInspector () { + // XXX(@jwerle): not supported } void Window::handleApplicationURL (const String& url) { diff --git a/src/window/apple.mm b/src/window/apple.mm index c5f905259f..a34d297971 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -416,11 +416,11 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } if (options.width > 0 && options.height > 0) { - this->setSize(options.height, options.width); + this->setSize(options.width, options.height); } else if (options.width > 0) { - this->setSize(window.frame.size.height, options.width); + this->setSize(options.width, window.frame.size.height); } else if (options.height > 0) { - this->setSize(options.height, window.frame.size.width); + this->setSize(window.frame.size.width, options.height); } this->bridge.configureWebView(this->webview); @@ -669,8 +669,8 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { const auto frame = UIScreen.mainScreen.bounds; #endif return ScreenSize { - .height = (int) frame.size.height, - .width = (int) frame.size.width + .width = (int) frame.size.width, + .height = (int) frame.size.height }; } diff --git a/src/window/dialog.kt b/src/window/dialog.kt index eb3cf54bcf..543f372268 100644 --- a/src/window/dialog.kt +++ b/src/window/dialog.kt @@ -66,21 +66,25 @@ open class Dialog (val window: Window) { { uris -> this.resolve(uris) } ) + // XXX(@jwerle): unused at the moment val launcherForSingleDocument = activity.registerForActivityResult( ActivityResultContracts.OpenDocument(), { uri -> this.resolve(uri) } ) + // XXX(@jwerle): unused at the moment val launcherForMulitpleDocuments = activity.registerForActivityResult( ActivityResultContracts.OpenMultipleDocuments(), { uris -> this.resolve(uris) } ) + // XXX(@jwerle): unused at the moment val launcherForSingleVisualMedia = activity.registerForActivityResult( ActivityResultContracts.PickVisualMedia(), { uri -> this.resolve(uri) } ) + // XXX(@jwerle): unused at the moment val launcherForMultipleVisualMedia = activity.registerForActivityResult( ActivityResultContracts.PickMultipleVisualMedia(), { uris -> this.resolve(uris) } diff --git a/src/window/linux.cc b/src/window/linux.cc index 9b900379ea..12596400cc 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1038,7 +1038,7 @@ namespace SSC { width = (int) DEFAULT_MONITOR_WIDTH; } - return ScreenSize { height, width }; + return ScreenSize { width, height }; } void Window::eval (const String& source) { diff --git a/src/window/manager.cc b/src/window/manager.cc index dc5000cd8a..1e70416e5a 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -252,6 +252,14 @@ namespace SSC { this->windows[options.index] = window; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (window->options.headless) { + window->status = WindowStatus::WINDOW_HIDDEN; + } else { + window->status = WindowStatus::WINDOW_SHOWN; + } + #endif + return this->windows.at(options.index); } @@ -318,7 +326,9 @@ namespace SSC { for (const auto& window : this->windows) { if ( window != nullptr && - window->status >= WINDOW_SHOWING && + // only "shown" or "hidden" managed windows will + // have events dispatched to them + window->status >= WINDOW_HIDDEN && window->status < WINDOW_CLOSING ) { if (window->emit(event, json)) { @@ -343,6 +353,7 @@ namespace SSC { void WindowManager::ManagedWindow::show () { auto index = std::to_string(this->index); + this->backgroundColor = Color(Window::getBackgroundColor()); status = WindowStatus::WINDOW_SHOWING; Window::show(); status = WindowStatus::WINDOW_SHOWN; @@ -404,6 +415,7 @@ namespace SSC { {"height", size.height}, {"status", this->status}, {"readyState", readyState}, + {"backgroundColor", this->backgroundColor.json()}, {"position", JSON::Object::Entries { {"x", this->position.x}, {"y", this->position.y} @@ -436,4 +448,23 @@ namespace SSC { bool WindowManager::ManagedWindow::emit (const String& event, const JSON::Any& json) { return this->bridge.emit(event, json); } + + void WindowManager::ManagedWindow::setBackgroundColor (int r, int g, int b, float a) { + this->backgroundColor = Color(r, g, b, a); + Window::setBackgroundColor(r, g, b, a); + } + + void WindowManager::ManagedWindow::setBackgroundColor (const String& rgba) { + this->backgroundColor = Color(rgba); + Window::setBackgroundColor(rgba); + } + + void WindowManager::ManagedWindow::setBackgroundColor (const Color& color) { + this->backgroundColor = color; + Window::setBackgroundColor(color.str()); + } + + String WindowManager::ManagedWindow::getBackgroundColor () { + return this->backgroundColor.str(); + } } diff --git a/src/window/manager.kt b/src/window/manager.kt index 8e4cc297aa..f45e55447c 100644 --- a/src/window/manager.kt +++ b/src/window/manager.kt @@ -3,6 +3,7 @@ package socket.runtime.window import android.content.Context import android.content.Intent +import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.view.WindowManager @@ -163,8 +164,9 @@ open class WindowFragmentManager (protected val activity: WindowManagerActivity) } /** + * Evaluates a JavaScript `source` string at a given window `index`. */ - fun evaluateJavaScriptInWindowFragment (index: Int, source: String): Boolean { + fun evaluateJavaScriptInWindowFragmentView (index: Int, source: String): Boolean { val fragments = this.fragments if (this.hasWindowFragment(index)) { kotlin.concurrent.thread { @@ -178,6 +180,90 @@ open class WindowFragmentManager (protected val activity: WindowManagerActivity) return false } + + /** + * Gets the window fragment width. + */ + fun getWindowFragmentWidth (index: Int): Int { + val fragment = this.fragments.find { it.index == index } + + if (fragment != null) { + return fragment.webview.measuredWidth + } + + return 0 + } + + /** + * Gets the window fragment height. + */ + fun getWindowFragmentHeight (index: Int): Int { + val fragment = this.fragments.find { it.index == index } + + if (fragment != null) { + return fragment.webview.measuredHeight + } + + return 0 + } + + /** + * Sets the window fragment width and height. + */ + fun setWindowFragmentSize (index: Int, width: Int, height: Int): Boolean { + val fragment = this.fragments.find { it.index == index } ?: return false + val window = fragment.window ?: return false + window.setSize(width, height) + return true + } + + /** + * Gets the window fragment title + */ + fun getWindowFragmentTitle (index: Int): String { + val fragment = this.fragments.find { it.index == index } ?: return "" + val window = fragment.window ?: return "" + return window.title + } + + /** + * Sets the window fragment title + */ + fun setWindowFragmentTitle (index: Int, title: String): Boolean { + val fragment = this.fragments.find { it.index == index } ?: return false + val window = fragment.window ?: return false + window.title = title + return true + } + + /** + * XXX + */ + fun setWindowFragmentViewBackgroundColor (index: Int, color: Long): Boolean { + val fragment = this.fragments.find { it.index == index } ?: return false + fragment.webview.setBackgroundColor(color.toInt()) + return true + } + + /** + * XXX + */ + fun getWindowFragmentBackgroundColor (index: Int): Int { + val fragment = this.fragments.find { it.index == index } ?: return 0 + val drawable = fragment.webview.background as ColorDrawable + val color = drawable.color + return 0xFFFFFF and color + } + + /** + * XXX + */ + fun setWindowFragmentViewPosition(index: Int, x: Float, y: Float): Boolean { + val fragment = this.fragments.find { it.index == index } ?: return false + fragment.webview.setX(x) + fragment.webview.setY(y) + return true + } } /** @@ -188,7 +274,7 @@ open class WindowManagerActivity : AppCompatActivity(R.layout.window_container_v open val windowFragmentManager = WindowFragmentManager(this) override fun onBackPressed () { - this.windowFragmentManager.popWindowFragment() + // this.windowFragmentManager.popWindowFragment() } /** @@ -235,22 +321,65 @@ open class WindowManagerActivity : AppCompatActivity(R.layout.window_container_v } /** + * Get the measured window width at a given `index`. + */ + fun getWindowWidth (index: Int): Int { + return this.windowFragmentManager.getWindowFragmentWidth(index) + } + + /** + * Get the measured window height at a given `index`. + */ + fun getWindowHeight (index: Int): Int { + return this.windowFragmentManager.getWindowFragmentHeight(index) + } + + /** + * Sets the window `width` and `height` at a given `index`. + */ + fun setWindowFragmentSize (index: Int, width: Int, height: Int): Boolean { + return this.windowFragmentManager.setWindowFragmentSize(index, width, height) + } + + /** + * XXX + */ + fun getWindowBackgroundColor (index: Int): Int { + return this.windowFragmentManager.getWindowFragmentBackgroundColor(index) + } + + /** + * Get the window title at a given `index`. */ fun getWindowTitle (index: Int): String { - // TODO(@jwerle) - return "" + return this.windowFragmentManager.getWindowFragmentTitle(index) } /** + * Sets the window title at a given `index`. */ fun setWindowTitle (index: Int, title: String): Boolean { - // TODO(@jwerle) - return false + return this.windowFragmentManager.setWindowFragmentTitle(index, title) + } + + /** + * Sets the window background color at a given `index`. + */ + fun setWindowBackgroundColor (index: Int, color: Long): Boolean { + return this.windowFragmentManager.setWindowFragmentViewBackgroundColor(index, color) + } + + /** + * XXX + */ + fun setWindowPosition (index: Int, x: Float, y: Float): Boolean { + return this.windowFragmentManager.setWindowFragmentViewPosition(index, x, y) } /** + * Evaluates JavaScript source in a window at a given `index`. */ fun evaluateJavaScript (index: Int, source: String): Boolean { - return this.windowFragmentManager.evaluateJavaScriptInWindowFragment(index, source) + return this.windowFragmentManager.evaluateJavaScriptInWindowFragmentView(index, source) } } diff --git a/src/window/win.cc b/src/window/win.cc index cbaf04bc5c..35f4e4c20c 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1151,8 +1151,8 @@ namespace SSC { ScreenSize Window::getScreenSize () { return ScreenSize { - .height = GetSystemMetrics(SM_CYFULLSCREEN), - .width = GetSystemMetrics(SM_CXFULLSCREEN) + .width = GetSystemMetrics(SM_CXFULLSCREEN), + .height = GetSystemMetrics(SM_CYFULLSCREEN) }; } diff --git a/src/window/window.hh b/src/window/window.hh index 74b2c18f7b..3a19bfd916 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -78,8 +78,8 @@ namespace SSC { * A container for holding an application's screen size */ struct ScreenSize { - int height = 0; int width = 0; + int height = 0; }; /** @@ -355,6 +355,11 @@ namespace SSC { */ Dialog dialog; + /** + * The current background color of the window + */ + Color backgroundColor; + #if SOCKET_RUNTIME_PLATFORM_IOS SSCWebViewController* viewController = nullptr; #endif @@ -440,7 +445,7 @@ namespace SSC { void setTitle (const String&); Size getSize (); const Size getSize () const; - void setSize (int height, int width, int hints = 0); + void setSize (int width, int height, int hints = 0); void setPosition (float, float); void setContextMenu (const String&, const String&); void closeContextMenu (const String&); @@ -450,6 +455,7 @@ namespace SSC { #endif void setBackgroundColor (int r, int g, int b, float a); void setBackgroundColor (const String& rgba); + void setBackgroundColor (const Color& color); String getBackgroundColor (); void setSystemMenuItemEnabled (bool enabled, int barPos, int menuPos); void setSystemMenu (const String& dsl); @@ -551,6 +557,10 @@ namespace SSC { void handleApplicationURL (const String& url); void onReadyStateChange (const ReadyState& readyState); bool emit (const String& event, const JSON::Any& json = {}); + void setBackgroundColor (int r, int g, int b, float a); + void setBackgroundColor (const String& rgba); + void setBackgroundColor (const Color& color); + String getBackgroundColor (); }; Vector<SharedPointer<ManagedWindow>> windows; diff --git a/src/window/window.kt b/src/window/window.kt index f1f03ab30b..d5946f2be4 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -200,13 +200,21 @@ open class Window (val fragment: WindowFragment) { val activity = fragment.activity val webview = fragment.webview activity?.runOnUiThread { - webview.loadUrl(url.replace("socket:", "https:")) + if (url.startsWith("socket://__BUNDLE_IDENTIFIER__")) { + webview.loadUrl(url.replace("socket:", "https:")) + } else { + webview.loadUrl(url) + } } } fun getSize (): Size { val webview = this.fragment.webview - return Size(webview.width, webview.height) + return Size(webview.measuredWidth, webview.measuredHeight) + } + + fun setSize (width: Int, height: Int) { + return this.setSize(Size(width, height)) } fun setSize (size: Size) { From 73a29fde3fe30c5f616aae7e8267c1ca567a4710 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:29:09 +0200 Subject: [PATCH 1012/1178] feat(core/modules/permissions): consolidate permissions APIs --- src/core/modules/permissions.cc | 432 ++++++++++++++++++++++++++++++++ src/core/modules/permissions.hh | 29 +++ 2 files changed, 461 insertions(+) create mode 100644 src/core/modules/permissions.cc create mode 100644 src/core/modules/permissions.hh diff --git a/src/core/modules/permissions.cc b/src/core/modules/permissions.cc new file mode 100644 index 0000000000..234d4435cf --- /dev/null +++ b/src/core/modules/permissions.cc @@ -0,0 +1,432 @@ +#include "../core.hh" +#include "permissions.hh" + +namespace SSC { + bool CorePermissions::hasRuntimePermission (const String& permission) const { + static const auto userConfig = getUserConfig(); + const auto key = String("permissions_allow_") + replace(permission, "-", "_"); + + if (!userConfig.contains(key)) { + return true; + } + + return userConfig.at(key) != "false"; + } + + void CorePermissions::query ( + const String& seq, + const String& name, + const Callback& callback + ) const { + this->core->dispatchEventLoop([=, this]() { + if (!this->hasRuntimePermission(name)) { + JSON::Object json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"}, + {"reason", "Runtime permission is disabled for '" + name + "'"} + }} + }; + callback(seq, json, Post{}); + return; + } + + if (name == "geolocation") { + JSON::Object json; + + #if SOCKET_RUNTIME_PLATFORM_APPLE + if (this->core->geolocation.locationObserver.isAuthorized) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + } else if (this->core->geolocation.locationObserver.locationManager) { + const auto authorizationStatus = ( + this->core->geolocation.locationObserver.locationManager.authorizationStatus + ); + + if (authorizationStatus == kCLAuthorizationStatusDenied) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"}} + } + }; + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + } + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasCoarseLocation = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.ACCESS_COARSE_LOCATION") + ); + + // `activity.checkPermission(permission)` + const auto hasFineLocation = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.ACCESS_FINE_LOCATION") + ); + + if (!hasCoarseLocation || !hasFineLocation) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + } + #endif + + callback(seq, json, Post{}); + } + + if (name == "notifications") { + JSON::Object json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"} + }} + }; + + #if SOCKET_RUNTIME_PLATFORM_APPLE + const auto notificationCenter = UNUserNotificationCenter.currentNotificationCenter; + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings* settings) { + JSON::Object json; + + if (!settings) { + json["err"] = JSON::Object::Entries {{ "message", "Failed to reach user notification settings" }}; + } else if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + json["data"] = JSON::Object::Entries {{"state", "denied"}}; + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + json["data"] = JSON::Object::Entries {{"state", "prompt"}}; + } else { + json["data"] = JSON::Object::Entries {{"state", "granted"}}; + } + + callback(seq, json, Post{}); + }]; + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasPostNotifications = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.POST_NOTIFICATIONS") + ); + + // `activity.isNotificationManagerEnabled()` + const auto isNotificationManagerEnabled = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "isNotificationManagerEnabled", + "()Z" + ); + + if (!hasPostNotifications || !isNotificationManagerEnabled) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + } + + callback(seq, json, Post{}); + #else + callback(seq, json, Post{}); + #endif + } + }); + } + + void CorePermissions::request ( + const String& seq, + const String& name, + const Map& options, + const Callback& callback + ) const { + this->core->dispatchEventLoop([=, this]() { + if (!this->hasRuntimePermission(name)) { + JSON::Object json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"}} + } + }; + callback(seq, json, Post{}); + return; + } + + if (name == "geolocation") { + JSON::Object json; + + #if SOCKET_RUNTIME_PLATFORM_APPLE + const auto performedActivation = [router->bridge->core->geolocation.locationObserver attemptActivationWithCompletion: ^(BOOL isAuthorized) { + if (!isAuthorized) { + auto reason = @("Location observer could not be activated"); + + if (!router->bridge->core->geolocation.locationObserver.locationManager) { + reason = @("Location observer manager is not initialized"); + } else if (!router->bridge->core->geolocation.locationObserver.locationManager.location) { + reason = @("Location observer manager could not provide location"); + } + + debug("%s", reason.UTF8String); + } + + if (isAuthorized) { + json["data"] = JSON::Object::Entries {{"state", "granted"}}; + } else if (router->bridge->core->geolocation.locationObserver.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { + json["data"] = JSON::Object::Entries {{"state", "prompt"}}; + } else { + json["data"] = JSON::Object::Entries {{"state", "denied"}}; + } + + callback(seq, json, Post{}); + }]; + + if (!performedActivation) { + auto err = JSON::Object::Entries {{ "message", "Location observer could not be activated" }}; + err["type"] = "GeolocationPositionError"; + json["err"] = err; + return callback(seq, json, Post{}); + } + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasCoarseLocation = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.ACCESS_COARSE_LOCATION") + ); + + // `activity.checkPermission(permission)` + const auto hasFineLocation = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.ACCESS_FINE_LOCATION") + ); + + if (!hasCoarseLocation || !hasFineLocation) { + CoreGeolocation::PermissionChangeObserver observer; + auto permissions = attachment.env->NewObjectArray( + 2, + attachment.env->FindClass("java/lang/String"), + 0 + ); + + attachment.env->SetObjectArrayElement( + permissions, + 0, + attachment.env->NewStringUTF("android.permission.ACCESS_COARSE_LOCATION") + ); + + attachment.env->SetObjectArrayElement( + permissions, + 1, + attachment.env->NewStringUTF("android.permission.ACCESS_FINE_LOCATION") + ); + + this->core->geolocation.addPermissionChangeObserver(observer, [seq, observer, callback, this](auto result) mutable { + JSON::Object json = JSON::Object::Entries { + {"data", result} + }; + callback(seq, json, Post{}); + this->core->dispatchEventLoop([=] () { + this->core->geolocation.removePermissionChangeObserver(observer); + }); + }); + + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + this->core->platform.activity, + "requestPermissions", + "([Ljava/lang/String;)V", + permissions + ); + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + callback(seq, json, Post{}); + } + #endif + } + + if (name == "notifications") { + JSON::Object json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"} + }} + }; + + #if SOCKET_RUNTIME_PLATFORM_APPLE + UNAuthorizationOptions requestOptions = UNAuthorizationOptionProvisional; + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + auto requestAlert = options.contains("alert") && options.at("alert") == "true"; + auto requestBadge = options.contains("badge") && options.at("badge") == "true"; + auto requestSound = options.contains("sound") && options.at("sound") == "true"; + + if (requestAlert) { + requestOptions |= UNAuthorizationOptionAlert; + } + + if (requestBadge) { + requestOoptions |= UNAuthorizationOptionBadge; + } + + if (requestSound) { + requestOptions |= UNAuthorizationOptionSound; + } + + if (requestAlert && requestSound) { + requestOptions |= UNAuthorizationOptionCriticalAlert; + } + + [notificationCenter + requestAuthorizationWithOptions: requestOptions + completionHandler: ^(BOOL granted, NSError *error) + { + [notificationCenter + getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) + { + JSON::Object json; + + if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + json["data"] = JSON::Object::Entries {{"state", "denied"}}; + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + if (error) { + const auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + json["err"] = JSON::Object::Entries {{"message", message}}; + } else { + json["data"] = JSON::Object::Entries { + {"state", granted ? "granted" : "denied" } + }; + } + } else { + json["data"] = JSON::Object::Entries {{"state", "granted"}}; + } + + callback(seq, json, Post{}); + }]; + }]; + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasPostNotifications = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.POST_NOTIFICATIONS") + ); + + // `activity.isNotificationManagerEnabled()` + const auto isNotificationManagerEnabled = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "isNotificationManagerEnabled", + "()Z" + ); + + if (!hasPostNotifications || !isNotificationManagerEnabled) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + } + + if (!hasPostNotifications || !isNotificationManagerEnabled) { + CoreNotifications::PermissionChangeObserver observer; + auto permissions = attachment.env->NewObjectArray( + 1, + attachment.env->FindClass("java/lang/String"), + 0 + ); + + attachment.env->SetObjectArrayElement( + permissions, + 0, + attachment.env->NewStringUTF("android.permission.POST_NOTIFICATIONS") + ); + + this->core->notifications.addPermissionChangeObserver(observer, [=](auto result) mutable { + JSON::Object json = JSON::Object::Entries { + {"data", result} + }; + callback(seq, json, Post{}); + this->core->dispatchEventLoop([=]() { + this->core->notifications.removePermissionChangeObserver(observer); + }); + }); + + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + this->core->platform.activity, + "requestPermissions", + "([Ljava/lang/String;)V", + permissions + ); + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + callback(seq, json, Post{}); + } + #else + callback(seq, json, Post{}); + #endif + } + }); + } +} diff --git a/src/core/modules/permissions.hh b/src/core/modules/permissions.hh new file mode 100644 index 0000000000..b43616f880 --- /dev/null +++ b/src/core/modules/permissions.hh @@ -0,0 +1,29 @@ +#ifndef SOCKET_RUNTIME_CORE_MODULE_PERMISSIONS_H +#define SOCKET_RUNTIME_CORE_MODULE_PERMISSIONS_H + +#include "../module.hh" + +namespace SSC { + class CorePermissions : public CoreModule { + public: + CorePermissions (Core* core) + : CoreModule(core) + {} + + bool hasRuntimePermission (const String& permission) const; + + void query ( + const String& seq, + const String& name, + const Callback& callback + ) const; + + void request ( + const String& seq, + const String& name, + const Map& options, + const Callback& callback + ) const; + }; +} +#endif From 720537c8a9ad9d86aceda564627ea73860d48b7f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:29:39 +0200 Subject: [PATCH 1013/1178] refactor(ipc): clean up permissions/notifications routing --- src/ipc/bridge.cc | 36 ++++++++++++++++- src/ipc/routes.cc | 37 +++++++---------- src/ipc/scheme_handlers.cc | 4 ++ src/ipc/scheme_handlers.kt | 82 -------------------------------------- 4 files changed, 53 insertions(+), 106 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index b23539671c..f4beeadb4d 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -529,6 +529,13 @@ export * from '{{url}}' return; } + if (request->method == "OPTIONS") { + auto response = SchemeHandlers::Response(request); + response.writeHead(204); + callback(response); + return; + } + // the location of static application resources const auto applicationResources = FileResource::getResourcesPath().string(); // default response is 404 @@ -701,7 +708,11 @@ export * from '{{url}}' if (resource.exists()) { const auto url = ( + #if SOCKET_RUNTIME_PLATFORM_ANDROID + "https://" + + #else "socket://" + + #endif bundleIdentifier + contentLocation + (request->query.size() > 0 ? "?" + request->query : "") @@ -750,6 +761,13 @@ export * from '{{url}}' auto callbacks, auto callback ) { + if (request->method == "OPTIONS") { + auto response = SchemeHandlers::Response(request); + response.writeHead(204); + callback(response); + return; + } + auto userConfig = this->userConfig; auto bundleIdentifier = userConfig["meta_bundle_identifier"]; // the location of static application resources @@ -812,7 +830,16 @@ export * from '{{url}}' } if (resource.exists()) { - const auto url = "socket://" + bundleIdentifier + "/socket" + pathname; + const auto url = ( + #if SOCKET_RUNTIME_PLATFORM_ANDROID + "https://" + + #else + "socket://" + + #endif + bundleIdentifier + + "/socket" + + pathname + ); const auto moduleImportProxy = tmpl( String(resource.read()).find("export default") != String::npos ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT @@ -932,6 +959,13 @@ export * from '{{url}}' return; } + if (request->method == "OPTIONS") { + auto response = SchemeHandlers::Response(request); + response.writeHead(204); + callback(response); + return; + } + if (this->navigator.serviceWorker.registrations.size() > 0) { auto hostname = request->hostname; auto pathname = request->pathname; diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index c7366bde5f..4d7cb1e897 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1728,13 +1728,20 @@ static void mapIPCRoutes (Router *router) { const auto options = Core::Notifications::ShowOptions { message.get("id"), - message.get("title"), + message.get("title", "Notification"), message.get("tag"), message.get("lang"), message.get("silent") == "true", message.get("icon"), message.get("image"), - message.get("body") + message.get("body"), + replace( + message.get("channel", "default"), + "default", + router->bridge->userConfig["meta_bundle_identifier"] + ), + message.get("category"), + message.get("vibrate") }; router->bridge->core->notifications.show(options, [=] (const auto result) { @@ -1756,7 +1763,11 @@ static void mapIPCRoutes (Router *router) { } - const auto notification = Core::Notifications::Notification { message.get("id") }; + const auto notification = Core::Notifications::Notification { + message.get("id"), + message.get("tag") + }; + router->bridge->core->notifications.close(notification); reply(Result { message.seq, message, JSON::Object::Entries { @@ -1972,26 +1983,6 @@ static void mapIPCRoutes (Router *router) { ); }); - /** - * Requests a notification with `title` and `body` to be shown. - * @param title - * @param body - */ - router->map("platform.notify", [=](auto message, auto router, auto reply) { - auto err = validateMessageParameters(message, {"body", "title"}); - - if (err.type != JSON::Type::Null) { - return reply(Result { message.seq, message, err }); - } - - router->bridge->core->platform.notify( - message.seq, - message.get("title"), - message.get("body"), - RESULT_CALLBACK_FROM_CORE_CALLBACK(message, reply) - ); - }); - /** * Reveal a file in the native operating system file system explorer. * @param value diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 292698ecf5..5d0339a730 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -995,6 +995,10 @@ namespace SSC::IPC { this->setHeader("access-control-allow-methods", "*"); this->setHeader("access-control-allow-credentials", "true"); + if (request->method == "OPTIONS") { + this->setHeader("allow", "GET, POST, PATCH, PUT, DELETE, HEAD"); + } + for (const auto& entry : defaultHeaders) { const auto parts = split(trim(entry), ':'); this->setHeader(parts[0], parts[1]); diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index 87d2c6e8cf..a1c533ee57 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -11,8 +11,6 @@ import kotlin.concurrent.thread import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse -import androidx.core.app.NotificationManagerCompat - import socket.runtime.app.App import socket.runtime.app.AppActivity import socket.runtime.core.console @@ -165,86 +163,6 @@ open class SchemeHandlers (val bridge: Bridge) { } fun handleRequest (webResourceRequest: WebResourceRequest): WebResourceResponse? { - if (webResourceRequest.url.scheme == "ipc") { - val activity = bridge.activity as AppActivity - val message = Message(webResourceRequest.url.toString()) - - if (message.name == "permissions.request") { - val request = Request(this.bridge, webResourceRequest) - val name = message.get("name") - } - - if (message.name == "permissions.query") { - val request = Request(this.bridge, webResourceRequest) - val name = message.get("name") - - if (!App.getInstance().hasRuntimePermission(name)) { - request.response.setHeader("content-type", "application/json") - request.response.setStatus(200, "OK") - request.response.write("""{ - "data": { - "state": "denied", - "reason": "Runtime permission is disabled for '${name}'" - } - }""") - - request.response.finish() - } else if (name == "geolocation") { - if ( - !activity.checkPermission("android.permission.ACCESS_COARSE_LOCATION") || - !activity.checkPermission("android.permission.ACCESS_FINE_LOCATION") - ) { - request.response.setHeader("content-type", "application/json") - request.response.setStatus(200, "OK") - request.response.write("""{ - "data": { - "state": "prompt" - } - }""") - - request.response.finish() - } else { - request.response.setHeader("content-type", "application/json") - request.response.setStatus(200, "OK") - request.response.write("""{ - "data": { - "state": "granted" - } - }""") - - request.response.finish() - } - } else if (name == "notifications" || name == "push") { - if ( - !activity.checkPermission("android.permission.POST_NOTIFICATIONS") || - !NotificationManagerCompat.from(activity).areNotificationsEnabled() - ) { - request.response.setHeader("content-type", "application/json") - request.response.setStatus(200, "OK") - request.response.write("""{ - "data": { - "state": "prompt" - } - }""") - - request.response.finish() - } else { - request.response.setHeader("content-type", "application/json") - request.response.setStatus(200, "OK") - request.response.write("""{ - "data": { - "state": "granted" - } - }""") - - request.response.finish() - } - } - - return request.getWebResourceResponse() - } - } - val request = Request(this.bridge, webResourceRequest) if (this.handleRequest(this.bridge.index, request)) { From 768d01cffbf9c2d18e190ef1221de4ff11756dc9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:31:35 +0200 Subject: [PATCH 1014/1178] refactor(core): various core module improvements and android impls --- src/core/module.hh | 63 ++++++++++++++++----- src/core/modules/notifications.cc | 91 +++++++++++++++++++++++++++++- src/core/modules/notifications.hh | 4 ++ src/core/modules/platform.cc | 93 ------------------------------- src/core/modules/platform.hh | 7 --- src/core/modules/udp.cc | 12 ++-- src/core/resource.cc | 47 +++++++++++++++- src/core/resource.hh | 9 +++ 8 files changed, 202 insertions(+), 124 deletions(-) diff --git a/src/core/module.hh b/src/core/module.hh index e38fc97ce3..2cb4c02c4d 100644 --- a/src/core/module.hh +++ b/src/core/module.hh @@ -33,6 +33,18 @@ namespace SSC { this->id = rand64(); } + Observer (const Observer& observer) { + this->id = observer.id; + this->callback = observer.callback; + } + + Observer (Observer&& observer) { + this->id = observer.id; + this->callback = observer.callback; + observer.id = 0; + observer.callback = nullptr; + } + Observer (const Callback& callback) : callback(callback) { @@ -42,7 +54,20 @@ namespace SSC { Observer (uint64_t id, const Callback& callback) : id(id), callback(callback) - {} + {} + + Observer& operator = (const Observer& observer) { + this->id = observer.id; + this->callback = observer.callback; + } + + Observer& operator = (Observer&& observer) { + this->id = observer.id; + this->callback = observer.callback; + observer.id = 0; + observer.callback = nullptr; + return *this; + } }; template <class Observer> @@ -70,17 +95,22 @@ namespace SSC { bool remove (const Observer& observer) { Lock lock(this->mutex); - int cursor = -1; - while (cursor++ < this->observers.size()) { - if (this->observers.at(cursor).id == observer.id) { - break; + auto iterator = this->observers.begin(); + + do { + if (iterator->id == 0) { + iterator = this->observers.erase(iterator); } - } - if (cursor >= 0 && cursor < this->observers.size()) { - this->observers.erase(this->observers.begin() + cursor); - return true; - } + if (iterator->id == observer.id) { + iterator = this->observers.erase(iterator); + return true; + } + + } while ( + iterator != this->observers.end() && + ++iterator != this->observers.end() + ); return false; } @@ -112,12 +142,19 @@ namespace SSC { bool dispatch (Types... arguments) { Lock lock(this->mutex); bool dispatched = false; - for (auto& observer : this->observers) { - if (observer.callback != nullptr) { - observer.callback(arguments...); + auto iterator = this->observers.begin(); + + while (iterator != this->observers.end()) { + if (iterator->id == 0) { + iterator = this->observers.erase(iterator); + } else if (iterator->callback != nullptr) { + iterator->callback(arguments...); dispatched = true; } + + iterator++; } + return dispatched; } }; diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index 910bce2031..f0fef82f71 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -190,12 +190,17 @@ namespace SSC { } bool CoreNotifications::show (const ShowOptions& options, const ShowCallback& callback) { - #if SOCKET_RUNTIME_PLATFORM_APPLE if (options.id.size() == 0) { callback(ShowResult { "Missing 'id' in CoreNotifications::ShowOptions" }); return false; } + if (!this->core->permissions.hasRuntimePermission("notifications")) { + callback(ShowResult { "Runtime permission is disabled for 'notifications'" }); + return false; + } + + #if SOCKET_RUNTIME_PLATFORM_APPLE auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; auto attachments = [NSMutableArray array]; auto userInfo = [NSMutableDictionary dictionary]; @@ -382,11 +387,64 @@ namespace SSC { }]; return true; + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.showNotification( + // id, + // title, + // body, + // tag, + // channel, + // category, + // silent, + // iconURL, + // imageURL, + // vibratePattern + // )` + const auto success = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "showNotification", + "(" + "Ljava/lang/String;" // id + "Ljava/lang/String;" // title + "Ljava/lang/String;" // body + "Ljava/lang/String;" // tag + "Ljava/lang/String;" // channel + "Ljava/lang/String;" // category + "Z" // silent + "Ljava/lang/String;" // iconURL + "Ljava/lang/String;" // imageURL + "Ljava/lang/String;" // vibratePattern + ")Z", + attachment.env->NewStringUTF(options.id.c_str()), + attachment.env->NewStringUTF(options.title.c_str()), + attachment.env->NewStringUTF(options.body.c_str()), + attachment.env->NewStringUTF(options.tag.c_str()), + attachment.env->NewStringUTF(options.channel.c_str()), + attachment.env->NewStringUTF(options.category.c_str()), + options.silent, + attachment.env->NewStringUTF(options.icon.c_str()), + attachment.env->NewStringUTF(options.image.c_str()), + attachment.env->NewStringUTF(options.vibrate.c_str()) + ); + + this->core->dispatchEventLoop([=, this] () { + callback(ShowResult { "", options.id }); + this->notificationPresentedObservers.dispatch(JSON::Object::Entries { + {"id", options.id} + }); + }); + return success; #endif return false; } bool CoreNotifications::close (const Notification& notification) { + if (!this->core->permissions.hasRuntimePermission("notifications")) { + return false; + } #if SOCKET_RUNTIME_PLATFORM_APPLE const auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; const auto identifiers = @[@(notification.identifier.c_str())]; @@ -400,6 +458,34 @@ namespace SSC { this->notificationResponseObservers.dispatch(json); return true; + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.showNotification( + // id, + // tag, + // )` + const auto success = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "closeNotification", + "(" + "Ljava/lang/String;" // id + "Ljava/lang/String;" // tag + ")Z", + attachment.env->NewStringUTF(notification.identifier.c_str()), + attachment.env->NewStringUTF(notification.tag.c_str()) + ); + + this->core->dispatchEventLoop([=, this] () { + const auto json = JSON::Object::Entries { + {"id", notification.identifier}, + {"action", "dismiss"} + }; + + this->notificationResponseObservers.dispatch(json); + }); + return success; #endif return false; @@ -417,6 +503,9 @@ namespace SSC { callback(entries); }]; + #else + Vector<Notification> entries; + callback(entries); #endif } } diff --git a/src/core/modules/notifications.hh b/src/core/modules/notifications.hh index 0169cc7e8c..9e9869e35c 100644 --- a/src/core/modules/notifications.hh +++ b/src/core/modules/notifications.hh @@ -32,6 +32,7 @@ namespace SSC { struct Notification { String identifier; + String tag; const JSON::Object json () const; }; @@ -44,6 +45,9 @@ namespace SSC { String icon; String image; String body; + String channel; + String category; + String vibrate; }; struct ShowResult { diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index 23065a53b7..f289ae495a 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -25,99 +25,6 @@ namespace SSC { callback(seq, json, Post{}); } - void CorePlatform::notify ( - const String& seq, - const String& title, - const String& body, - const CoreModule::Callback& callback - ) const { - #if SOCKET_RUNTIME_PLATFORM_APPLE - auto center = [UNUserNotificationCenter currentNotificationCenter]; - auto content = [[UNMutableNotificationContent alloc] init]; - content.body = [NSString stringWithUTF8String: body.c_str()]; - content.title = [NSString stringWithUTF8String: title.c_str()]; - content.sound = [UNNotificationSound defaultSound]; - - auto trigger = [UNTimeIntervalNotificationTrigger - triggerWithTimeInterval: 1.0f - repeats: NO - ]; - - auto request = [UNNotificationRequest - requestWithIdentifier: @"LocalNotification" - content: content - trigger: trigger - ]; - - auto options = ( - UNAuthorizationOptionAlert | - UNAuthorizationOptionBadge | - UNAuthorizationOptionSound - ); - - [center requestAuthorizationWithOptions: options - completionHandler: ^(BOOL granted, NSError* error) - { - #if !__has_feature(objc_arc) - [content release]; - [trigger release]; - #endif - - if (granted) { - auto json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"data", JSON::Object::Entries {}} - }; - - callback(seq, json, Post{}); - } else if (error) { - [center addNotificationRequest: request - withCompletionHandler: ^(NSError* error) - { - auto json = JSON::Object {}; - - #if !__has_feature(objc_arc) - [request release]; - #endif - - if (error) { - json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"err", JSON::Object::Entries { - {"message", [error.debugDescription UTF8String]} - }} - }; - - debug("Unable to create notification: %@", error.debugDescription); - } else { - json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"data", JSON::Object::Entries {}} - }; - } - - callback(seq, json, Post{}); - }]; - } else { - auto json = JSON::Object::Entries { - {"source", "platform.notify"}, - {"err", JSON::Object::Entries { - {"message", "Failed to create notification"} - }} - }; - - callback(seq, json, Post{}); - } - - if (!error || granted) { - #if !__has_feature(objc_arc) - [request release]; - #endif - } - }]; - #endif - } - void CorePlatform::revealFile ( const String& seq, const String& value, diff --git a/src/core/modules/platform.hh b/src/core/modules/platform.hh index 4bc724ab74..5d0099594d 100644 --- a/src/core/modules/platform.hh +++ b/src/core/modules/platform.hh @@ -31,13 +31,6 @@ namespace SSC { const CoreModule::Callback& callback ); - void notify ( - const String& seq, - const String& title, - const String& body, - const CoreModule::Callback& callback - ) const; - void openExternal ( const String& seq, const String& value, diff --git a/src/core/modules/udp.cc b/src/core/modules/udp.cc index 25903b5356..01e6b60a2b 100644 --- a/src/core/modules/udp.cc +++ b/src/core/modules/udp.cc @@ -106,14 +106,12 @@ namespace SSC { } void CoreUDP::pauseAllSockets () { - this->core->dispatchEventLoop([=, this]() { - for (auto const &tuple : this->sockets) { - auto socket = tuple.second; - if (socket != nullptr && (socket->isBound() || socket->isConnected())) { - socket->pause(); - } + for (auto const &tuple : this->sockets) { + auto socket = tuple.second; + if (socket != nullptr && (socket->isBound() || socket->isConnected())) { + socket->pause(); } - }); + } } bool CoreUDP::hasSocket (ID id) { diff --git a/src/core/resource.cc b/src/core/resource.cc index 871c7c081c..eeb18db33d 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -15,7 +15,10 @@ namespace SSC { static Mutex mutex; #if SOCKET_RUNTIME_PLATFORM_ANDROID - Android::AssetManager* sharedAndroidAssetManager = nullptr; + static Android::AssetManager* sharedAndroidAssetManager = nullptr; + static Path externalAndroidStorageDirectory; + static Path externalAndroidFilesDirectory; + static Path externalAndroidCacheDirectory; #endif std::map<String, Set<String>> FileResource::mimeTypes = { @@ -74,6 +77,30 @@ namespace SSC { Android::AssetManager* FileResource::getSharedAndroidAssetManager () { return sharedAndroidAssetManager; } + + void FileResource::setExternalAndroidStorageDirectory (const Path& directory) { + externalAndroidStorageDirectory = directory; + } + + Path FileResource::getExternalAndroidStorageDirectory () { + return externalAndroidStorageDirectory; + } + + void FileResource::setExternalAndroidFilesDirectory (const Path& directory) { + externalAndroidFilesDirectory = directory; + } + + Path FileResource::getExternalAndroidFilesDirectory () { + return externalAndroidFilesDirectory; + } + + void FileResource::setExternalAndroidCacheDirectory (const Path& directory) { + externalAndroidCacheDirectory = directory; + } + + Path FileResource::getExternalAndroidCacheDirectory () { + return externalAndroidCacheDirectory; + } #endif bool FileResource::isFile (const String& resourcePath) { @@ -417,8 +444,8 @@ namespace SSC { static const auto USERPROFILE = Env::get("USERPROFILE", HOME); this->downloads = Path(USERPROFILE) / "Downloads"; this->documents = Path(USERPROFILE) / "Documents"; - this->desktop = Path(USERPROFILE) / "Desktop"; this->pictures = Path(USERPROFILE) / "Pictures"; + this->desktop = Path(USERPROFILE) / "Desktop"; this->videos = Path(USERPROFILE) / "Videos"; this->music = Path(USERPROFILE) / "Music"; this->config = Path(Env::get("APPDATA")) / bundleIdentifier; @@ -426,7 +453,21 @@ namespace SSC { this->data = Path(Env::get("APPDATA")) / bundleIdentifier; this->log = this->config; #elif SOCKET_RUNTIME_PLATFORM_ANDROID - // TODO(@jwerle) + const auto storage = FileResource::getExternalAndroidStorageDirectory(); + const auto files = FileResource::getExternalAndroidFilesDirectory(); + const auto cache = FileResource::getExternalAndroidCacheDirectory(); + this->resources = "socket://" + bundleIdentifier; + this->downloads = storage / "Downloads"; + this->documents = storage / "Documents"; + this->pictures = storage / "Pictures"; + this->desktop = !files.empty() ? files : storage / "Desktop"; + this->videos = storage / "DCIM" / "Videos"; + this->music = storage / "Music"; + this->config = storage; + this->home = this->desktop; + this->data = storage; + this->log = storage / "logs"; + this->tmp = !cache.empty() ? cache : storage / "tmp"; #endif } diff --git a/src/core/resource.hh b/src/core/resource.hh index 61f670ecbd..10b3df6b15 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -138,6 +138,15 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_ANDROID static void setSharedAndroidAssetManager (Android::AssetManager*); static Android::AssetManager* getSharedAndroidAssetManager (); + + static void setExternalAndroidStorageDirectory (const Path&); + static Path getExternalAndroidStorageDirectory (); + + static void setExternalAndroidFilesDirectory (const Path&); + static Path getExternalAndroidFilesDirectory (); + + static void setExternalAndroidCacheDirectory (const Path&); + static Path getExternalAndroidCacheDirectory (); #endif Path path; From b8284e05aca548d6261428024c39808d5b2f03bb Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:31:44 +0200 Subject: [PATCH 1015/1178] refactor(cli): clean up --- src/cli/cli.cc | 24 ++++++++++++------------ src/cli/templates.hh | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 232f264fac..60d8124399 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -3367,41 +3367,41 @@ int main (const int argc, const char* argv[]) { Map manifestContext; - manifestContext["android_manifest_xml_permissions"] = ""; + manifestContext["android_manifest_xml_permissions"] = "\n"; if (settings["permissions_allow_notifications"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n"; } if (settings["permissions_allow_geolocation"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n"; - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n"; if (settings["permissions_allow_geolocation_in_background"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" />\n"; } } if (settings["permissions_allow_user_media"] != "false") { if (settings["permissions_allow_camera"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.CAMERA\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.CAMERA\" />\n"; } if (settings["permissions_allow_microphone"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.CAPTURE_AUDIO_OUTPUT\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.CAPTURE_AUDIO_OUTPUT\" />\n"; } } if (settings["permissions_allow_read_media"] != "false") { if (settings["permissions_allow_read_media_images"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" />\n"; } if (settings["permissions_allow_read_media_video"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\" />\n"; } if (settings["permissions_allow_read_media_audio"] != "false") { - manifestContext["android_manifest_xml_permissions"] += "<uses-permission android:name=\"android.permission.READ_MEDIA_AUDIO\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.READ_MEDIA_AUDIO\" />\n"; } } @@ -3414,7 +3414,7 @@ int main (const int argc, const char* argv[]) { std::transform(permission.begin(), permission.end(), permission.begin(), ::toupper); xml - << "<uses-permission android:name=" + << " <uses-permission android:name=" << "\"android.permission." << permission << "\"" << " />"; @@ -3476,7 +3476,7 @@ int main (const int argc, const char* argv[]) { if (androidIcon.size() > 0) { settings["android_application_icon_config"] = ( - String(" android:roundIcon=\"@mipmap/ic_launcher_round\"\n") + + String(" android:roundIcon=\"@mipmap/ic_launcher_round\"\n") + String(" android:icon=\"@mipmap/ic_launcher\"\n") ); diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 3e96c55855..4e8b11cfe1 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -487,7 +487,6 @@ constexpr auto gAndroidManifest = R"XML( <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - {{android_manifest_xml_permissions}} <application From 99b42e75250aad252c018c95f6b56eedaa41a2a9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:32:13 +0200 Subject: [PATCH 1016/1178] refactor(app): introduce android notifications API again, improve lifecycles --- src/app/app.cc | 40 ++++++++++-- src/app/app.hh | 1 + src/app/app.kt | 165 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 198 insertions(+), 8 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index c37f7e093b..07af39d9ed 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -850,14 +850,16 @@ namespace SSC { } void App::resume () { - if (this->core != nullptr) { + if (this->core != nullptr && this->paused) { + this->paused = false; this->core->resume(); this->windowManager.emit("applicationresume"); } } void App::pause () { - if (this->core != nullptr) { + if (this->core != nullptr && !this->paused) { + this->paused = true; this->core->pause(); this->windowManager.emit("applicationpause"); } @@ -868,12 +870,12 @@ namespace SSC { return; } + this->stopped = true; this->windowManager.emit("applicationstop"); this->pause(); SSC::applicationInstance = nullptr; - this->stopped = true; this->shouldExit = true; this->core->shutdown(); @@ -952,7 +954,7 @@ namespace SSC { const auto key = String("permissions_allow_") + replace(permission, "-", "_"); if (!userConfig.contains(key)) { - return false; + return true; } return userConfig.at(key) != "false"; @@ -990,6 +992,36 @@ extern "C" { return true; } + jboolean ANDROID_EXTERNAL(app, App, setExternalStorageDirectory)( + JNIEnv *env, + jobject self, + jstring externalStorageDirectoryString + ) { + const auto directory = Android::StringWrap(env, externalStorageDirectoryString).str(); + FileResource::setExternalAndroidStorageDirectory(Path(directory)); + return true; + } + + jboolean ANDROID_EXTERNAL(app, App, setExternalFilesDirectory)( + JNIEnv *env, + jobject self, + jstring externalFilesDirectoryString + ) { + const auto directory = Android::StringWrap(env, externalFilesDirectoryString).str(); + FileResource::setExternalAndroidFilesDirectory(Path(directory)); + return true; + } + + jboolean ANDROID_EXTERNAL(app, App, setExternalCacheDirectory)( + JNIEnv *env, + jobject self, + jstring externalCacheDirectoryString + ) { + const auto directory = Android::StringWrap(env, externalCacheDirectoryString).str(); + FileResource::setExternalAndroidCacheDirectory(Path(directory)); + return true; + } + jboolean ANDROID_EXTERNAL(app, App, setAssetManager)( JNIEnv *env, jobject self, diff --git a/src/app/app.hh b/src/app/app.hh index 42f4702020..9280290ddc 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -91,6 +91,7 @@ namespace SSC { ExitCallback onExit = nullptr; AtomicBool shouldExit = false; AtomicBool stopped = false; + AtomicBool paused = false; bool wasLaunchedFromCli = false; WindowManager windowManager; diff --git a/src/app/app.kt b/src/app/app.kt index 25b03e1b50..abc74a4357 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -8,14 +8,18 @@ import android.app.Activity import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.AssetManager import android.graphics.Insets +import android.graphics.drawable.Icon import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.Environment + import android.view.WindowInsets import android.view.WindowManager import android.webkit.MimeTypeMap @@ -24,6 +28,9 @@ import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner @@ -31,6 +38,8 @@ import androidx.lifecycle.ProcessLifecycleOwner import socket.runtime.core.console import socket.runtime.window.WindowManagerActivity +import __BUNDLE_IDENTIFIER__.R + open class AppPermissionRequest (callback: (Boolean) -> Unit) { val id: Int = (0..16384).random().toInt() val callback = callback @@ -59,6 +68,10 @@ open class AppActivity : WindowManagerActivity() { return status == PackageManager.PERMISSION_GRANTED } + fun requestPermissions (permissions: Array<String>) { + return this.requestPermissions(permissions, fun (_: Boolean) {}) + } + fun requestPermissions (permissions: Array<String>, callback: (Boolean) -> Unit) { val request = AppPermissionRequest(callback) this.permissionRequests.add(request) @@ -98,12 +111,142 @@ open class AppActivity : WindowManagerActivity() { return insets.top + insets.bottom } + fun isNotificationManagerEnabled (): Boolean { + return NotificationManagerCompat.from(this).areNotificationsEnabled() + } + + fun showNotification ( + id: String, + title: String, + body: String, + tag: String, + channel: String, + category: String, + silent: Boolean, + iconURL: String, + imageURL: String, + vibratePattern: String + ): Boolean { + // paramters + val identifier = id.toLongOrNull()?.toInt() ?: (0..16384).random().toInt() + val vibration = vibratePattern + .split(",") + .filter({ it.length > 0 }) + .map({ it.toInt().toLong() }) + .toTypedArray() + + // intents + val intentFlags = ( + Intent.FLAG_ACTIVITY_SINGLE_TOP + ) + + val contentIntent = Intent(this, __BUNDLE_IDENTIFIER__.MainActivity::class.java) + val deleteIntent = Intent(this, __BUNDLE_IDENTIFIER__.MainActivity::class.java) + + // TODO(@jwerle): move 'action' to a constant + contentIntent.addCategory(Intent.CATEGORY_LAUNCHER) + contentIntent.setAction("notification.response.default") + contentIntent.putExtra("id", id) + contentIntent.flags = intentFlags + + // TODO(@jwerle): move 'action' to a constant + deleteIntent.setAction("notification.response.dismiss") + deleteIntent.putExtra("id", id) + deleteIntent.flags = intentFlags + + // pending intents + val pendingContentIntent: PendingIntent = PendingIntent.getActivity( + this, + identifier, + contentIntent, + ( + PendingIntent.FLAG_UPDATE_CURRENT or + PendingIntent.FLAG_IMMUTABLE or + PendingIntent.FLAG_ONE_SHOT + ) + ) + + val pendingDeleteIntent: PendingIntent = PendingIntent.getActivity( + this, + identifier, + deleteIntent, + ( + PendingIntent.FLAG_UPDATE_CURRENT or + PendingIntent.FLAG_IMMUTABLE + ) + ) + + // build notification + val builder = NotificationCompat.Builder(this, channel).apply { + setPriority(NotificationCompat.PRIORITY_DEFAULT) + setContentTitle(title) + setContentIntent(pendingContentIntent) + setDeleteIntent(pendingDeleteIntent) + setAutoCancel(true) + setSilent(silent) + if (vibration.size > 0) { + setVibrate(vibration.toLongArray()) + } + + if (body.length > 0) { + setContentText(body) + } + } + + if (iconURL.length > 0) { + val icon = IconCompat.createWithContentUri(iconURL) + builder.setSmallIcon(icon) + } else { + val icon = IconCompat.createWithResource( + this, + R.mipmap.ic_launcher_round + ) + + builder.setSmallIcon(icon) + } + + if (imageURL.length > 0) { + val icon = Icon.createWithContentUri(imageURL) + builder.setLargeIcon(icon) + } + + if (category.length > 0) { + builder.setCategory( + category + .replace("msg", "message") + .replace("-", "_") + ) + } + + val notification = builder.build() + with (NotificationManagerCompat.from(this)) { + notify( + tag, + identifier, + notification + ) + } + + return true + } + + fun closeNotification (id: String, tag: String): Boolean { + val identifier = id.toLongOrNull()?.toInt() ?: (0..16384).random().toInt() + with (NotificationManagerCompat.from(this)) { + cancel(tag, identifier) + } + return true + } + override fun onCreate (savedInstanceState: Bundle?) { this.supportActionBar?.hide() this.getWindow()?.statusBarColor = android.graphics.Color.TRANSPARENT super.onCreate(savedInstanceState) + val externalStorageDirectory = Environment.getExternalStorageDirectory().absolutePath + val externalFilesDirectory = this.getExternalFilesDir(null)?.absolutePath ?: "$externalStorageDirectory/Desktop" + val cacheDirectory = this.applicationContext.getCacheDir().absolutePath val rootDirectory = this.getRootDirectory() val assetManager = this.applicationContext.resources.assets val app = App.getInstance() @@ -119,7 +262,14 @@ open class AppActivity : WindowManagerActivity() { app.apply { setMimeTypeMap(MimeTypeMap.getSingleton()) setAssetManager(assetManager) + + // directories setRootDirectory(rootDirectory) + setExternalCacheDirectory(cacheDirectory) + setExternalFilesDirectory(externalFilesDirectory) + setExternalStorageDirectory(externalStorageDirectory) + + // build info setBuildInformation( Build.BRAND, Build.DEVICE, @@ -274,10 +424,8 @@ open class AppActivity : WindowManagerActivity() { seen.add(name) - this.runOnUiThread { - val state = if (granted) "granted" else "denied" - App.getInstance().onPermissionChange(name, state) - } + val state = if (granted) "granted" else "denied" + App.getInstance().onPermissionChange(name, state) } } } @@ -342,6 +490,15 @@ open class App : Application() { @Throws(Exception::class) external fun setRootDirectory (rootDirectory: String): Boolean + @Throws(Exception::class) + external fun setExternalCacheDirectory (cacheDirectory: String): Boolean + + @Throws(Exception::class) + external fun setExternalStorageDirectory (directory: String): Boolean + + @Throws(Exception::class) + external fun setExternalFilesDirectory (directory: String): Boolean + @Throws(Exception::class) external fun setAssetManager (assetManager: AssetManager): Boolean From 2c9754016d0b61dd3e9bfe4c9ea738b488fcb2bb Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:37:56 +0200 Subject: [PATCH 1017/1178] refactor(api/commonjs/loader.js): disable 'withCredentials' for android --- api/commonjs/loader.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 50fa1cbefa..e18d2bd67e 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -9,6 +9,7 @@ import { Headers } from '../ipc.js' import location from '../location.js' import path from '../path.js' import URL from '../url.js' +import os from '../os.js' const RUNTIME_SERVICE_WORKER_FETCH_MODE = 'Runtime-ServiceWorker-Fetch-Mode' const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' @@ -200,7 +201,10 @@ export class RequestStatus { request.open('HEAD', this.#request.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') - //request.withCredentials = true + + if (os.platform() !== 'android') { + request.withCredentials = true + } if (globalThis.isServiceWorkerScope) { request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') @@ -412,7 +416,10 @@ export class Request { const request = new XMLHttpRequest() request.open('GET', this.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') - request.withCredentials = true + + if (os.platform() !== 'android') { + request.withCredentials = true + } if (globalThis.isServiceWorkerScope) { request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') From 286045076dd02b00815bf1c3d2bdb00b7f87d4c3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:38:14 +0200 Subject: [PATCH 1018/1178] refactor(api/ipc.js): clean up --- api/ipc.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 738d312c63..2df820648f 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -426,12 +426,7 @@ export class Headers extends globalThis.Headers { if (Array.isArray(input) && !Array.isArray(input[0])) { input = input.join('\n') } else if (typeof input?.entries === 'function') { - try { - return new this(input.entries()) - } catch (err) { - console.log({input}) - throw err - } + return new this(input.entries()) } else if (isPlainObject(input) || isArrayLike(input)) { return new this(input) } else if (typeof input?.getAllResponseHeaders === 'function') { From 35d5168f3100ce4df04eac00ad11e8f244a66023 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:41:34 +0200 Subject: [PATCH 1019/1178] refactor(api/internal/init.js): init shared worker context window --- api/internal/init.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/internal/init.js b/api/internal/init.js index 849e9ade0e..cd2adb2369 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -23,6 +23,7 @@ import * as asyncHooks from './async/hooks.js' import { Deferred } from '../async.js' import { rand64 } from '../crypto.js' import location from '../location.js' +import * as sw from '../shared-worker/index.js' import mime from '../mime.js' import path from '../path.js' import os from '../os.js' @@ -34,6 +35,9 @@ import { const GlobalWorker = globalThis.Worker || class Worker extends EventTarget {} +// init `SharedWorker` window context +sw.getContextWindow() + // only patch a webview or worker context if ((globalThis.window || globalThis.self) === globalThis) { if (typeof globalThis.queueMicrotask === 'function') { From dd8876315e305700c922b3c937233a1821614110 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:41:51 +0200 Subject: [PATCH 1020/1178] refactor(api/internal/conduit.js): more internal conduit state imporovements --- api/internal/conduit.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 255fda3a71..aa6634d764 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -307,7 +307,13 @@ export class Conduit extends EventTarget { * @return {Promise<Conduit>} */ async connect (callback = null) { - if (this.isConnecting || isApplicationPaused) { + if (this.isConnecting) { + callback(new Error('Application is connecting')) + return this + } + + if (isApplicationPaused) { + callback(new Error('Application is paused')) return this } @@ -335,6 +341,11 @@ export class Conduit extends EventTarget { await Conduit.waitForActiveState() + if (isApplicationPaused) { + callback(new Error('Application is paused')) + return this + } + this.port = result.data.port this.socket = new WebSocket(this.url) @@ -349,7 +360,9 @@ export class Conduit extends EventTarget { callback = null } - resolvers.reject(e.error ?? new Error()) + if (!isApplicationPaused) { + resolvers.reject(e.error ?? new Error()) + } } this.socket.onclose = (e) => { From d2e82357428395d907431bbbafb5c96959d5fb52 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:42:55 +0200 Subject: [PATCH 1021/1178] refactor(api/internal/init.js): clean up lint --- api/internal/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/init.js b/api/internal/init.js index cd2adb2369..ff8ca8e5e2 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -651,7 +651,7 @@ if (typeof globalThis.XMLHttpRequest === 'function') { const value = open.call(this, method, url.toString(), isAsyncRequest !== false, ...args) if ( - method != 'OPTIONS' && ( + method !== 'OPTIONS' && ( globalThis.__args?.config?.webview_fetch_allow_runtime_headers === true || (url.protocol && /(socket|ipc|node|npm):/.test(url.protocol)) || (url.protocol && protocols.handlers.has(url.protocol.slice(0, -1))) || From 0bacf08cc583a88c426bf4ebb17fa65dafda0fad Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 16:43:08 +0200 Subject: [PATCH 1022/1178] chore(api): generate types + docs --- api/README.md | 16 +-- api/index.d.ts | 279 +++++++++++++++++++++++++------------------------ 2 files changed, 149 insertions(+), 146 deletions(-) diff --git a/api/README.md b/api/README.md index e9b80473d0..6d5b52c04a 100644 --- a/api/README.md +++ b/api/README.md @@ -1715,12 +1715,12 @@ Watch for changes at `path` calling `callback` This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1022) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1026) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1183) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1187) Emit event to be dispatched on `window` object. @@ -1731,7 +1731,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1242) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1246) Sends an async IPC command request with parameters. @@ -1747,27 +1747,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1693) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1697) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1725) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1729) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1774) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1778) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1776) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1780) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1981) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1985) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index 697ebc4bd3..cec8d6b417 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -887,7 +887,7 @@ declare module "socket:ipc" { /** * @ignore */ - static from(input: any): any; + static from(input: any): Headers; /** * @ignore */ @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2161,7 +2161,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2837,7 +2837,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3072,7 +3072,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3102,7 +3102,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): any; + export function getCode(name: string | number): signal; /** * Gets the name for a given 'signal' code * @return {string} @@ -3246,7 +3246,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3627,7 +3627,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } @@ -4533,9 +4533,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -5263,7 +5263,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6136,7 +6136,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6980,7 +6980,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "nested" | "top-level"; + get frameType(): "none" | "top-level" | "nested"; /** * The type of the client. * @type {'window'|'worker'} @@ -7012,10 +7012,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: 'window' | 'worker'; + type?: "window" | "worker"; parent?: object | null; top?: object | null; - frameType?: 'top-level' | 'nested' | 'none'; + frameType?: "top-level" | "nested" | "none"; }; } @@ -7089,7 +7089,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7302,6 +7302,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8022,6 +8023,78 @@ declare module "socket:shared-worker/index" { import ipc from "socket:ipc"; } +declare module "socket:internal/promise" { + export const NativePromise: PromiseConstructor; + export namespace NativePromisePrototype { + export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + export { _catch as catch }; + let _finally: (onfinally?: () => void) => globalThis.Promise<any>; + export { _finally as finally }; + } + export const NativePromiseAll: any; + export const NativePromiseAny: any; + /** + * @typedef {function(any): void} ResolveFunction + */ + /** + * @typedef {function(Error|string|null): void} RejectFunction + */ + /** + * @typedef {function(ResolveFunction, RejectFunction): void} ResolverFunction + */ + /** + * @typedef {{ + * promise: Promise, + * resolve: ResolveFunction, + * reject: RejectFunction + * }} PromiseResolvers + */ + export class Promise extends globalThis.Promise<any> { + /** + * Creates a new `Promise` with resolver functions. + * @see {https://github.com/tc39/proposal-promise-with-resolvers} + * @return {PromiseResolvers} + */ + static withResolvers(): PromiseResolvers; + /** + * `Promise` class constructor. + * @ignore + * @param {ResolverFunction} resolver + */ + constructor(resolver: ResolverFunction); + [resourceSymbol]: { + "__#15@#type": any; + "__#15@#destroyed": boolean; + "__#15@#asyncId": number; + "__#15@#triggerAsyncId": any; + "__#15@#requireManualDestroy": boolean; + readonly type: string; + readonly destroyed: boolean; + asyncId(): number; + triggerAsyncId(): number; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + }; + } + export namespace Promise { + function all(iterable: any): any; + function any(iterable: any): any; + } + export default Promise; + export type ResolveFunction = (arg0: any) => void; + export type RejectFunction = (arg0: Error | string | null) => void; + export type ResolverFunction = (arg0: ResolveFunction, arg1: RejectFunction) => void; + export type PromiseResolvers = { + promise: Promise; + resolve: ResolveFunction; + reject: RejectFunction; + }; + const resourceSymbol: unique symbol; + import * as asyncHooks from "socket:internal/async/hooks"; +} + declare module "socket:internal/globals" { /** * Gets a runtime global value by name. @@ -8622,7 +8695,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9831,7 +9904,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9906,7 +9979,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10402,7 +10475,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10466,7 +10539,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -10598,7 +10671,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10639,7 +10712,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -10826,7 +10899,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10861,13 +10934,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13558,7 +13631,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -13566,7 +13639,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13815,7 +13888,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -13826,7 +13899,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -14767,6 +14840,7 @@ declare module "socket:commonjs/loader" { text?: string; }; import { Headers } from "socket:ipc"; + import URL from "socket:url"; import { Cache } from "socket:commonjs/cache"; } @@ -15106,7 +15180,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -15117,13 +15191,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -15147,6 +15221,7 @@ declare module "socket:commonjs/package" { worker?: string | string[]; browser?: string | string[]; }; + import URL from "socket:url"; import { Loader } from "socket:commonjs/loader"; } @@ -15206,7 +15281,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -15250,7 +15325,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15610,7 +15685,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15648,9 +15723,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:module" { @@ -15990,7 +16065,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -16176,9 +16251,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -16842,78 +16917,6 @@ declare module "socket:internal/scheduler" { import scheduler from "socket:timers/scheduler"; } -declare module "socket:internal/promise" { - export const NativePromise: PromiseConstructor; - export namespace NativePromisePrototype { - export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; - export { _catch as catch }; - let _finally: (onfinally?: () => void) => globalThis.Promise<any>; - export { _finally as finally }; - } - export const NativePromiseAll: any; - export const NativePromiseAny: any; - /** - * @typedef {function(any): void} ResolveFunction - */ - /** - * @typedef {function(Error|string|null): void} RejectFunction - */ - /** - * @typedef {function(ResolveFunction, RejectFunction): void} ResolverFunction - */ - /** - * @typedef {{ - * promise: Promise, - * resolve: ResolveFunction, - * reject: RejectFunction - * }} PromiseResolvers - */ - export class Promise extends globalThis.Promise<any> { - /** - * Creates a new `Promise` with resolver functions. - * @see {https://github.com/tc39/proposal-promise-with-resolvers} - * @return {PromiseResolvers} - */ - static withResolvers(): PromiseResolvers; - /** - * `Promise` class constructor. - * @ignore - * @param {ResolverFunction} resolver - */ - constructor(resolver: ResolverFunction); - [resourceSymbol]: { - "__#15@#type": any; - "__#15@#destroyed": boolean; - "__#15@#asyncId": number; - "__#15@#triggerAsyncId": any; - "__#15@#requireManualDestroy": boolean; - readonly type: string; - readonly destroyed: boolean; - asyncId(): number; - triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; - }; - } - export namespace Promise { - function all(iterable: any): any; - function any(iterable: any): any; - } - export default Promise; - export type ResolveFunction = (arg0: any) => void; - export type RejectFunction = (arg0: Error | string | null) => void; - export type ResolverFunction = (arg0: ResolveFunction, arg1: RejectFunction) => void; - export type PromiseResolvers = { - promise: Promise; - resolve: ResolveFunction; - reject: RejectFunction; - }; - const resourceSymbol: unique symbol; - import * as asyncHooks from "socket:internal/async/hooks"; -} - declare module "socket:internal/timers" { export function setTimeout(callback: any, ...args: any[]): number; export function clearTimeout(timeout: any): any; @@ -16966,7 +16969,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16986,7 +16989,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17008,8 +17011,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -17019,10 +17022,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -17123,7 +17126,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17132,7 +17135,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17213,8 +17216,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17228,7 +17231,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17677,12 +17680,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17735,7 +17738,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17752,8 +17755,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 4f1c0f53e2e33df1d902b379ffeac073ff13e294 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 17:42:48 +0200 Subject: [PATCH 1023/1178] fix(window/linux): fix 'Notifcations' API --- src/window/linux.cc | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 12596400cc..7943e4e8f0 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -46,14 +46,14 @@ namespace SSC { webkit_security_origin_ref(origin); - if (origin && allowed && disallowed) { + if (origin) { if (areNotificationsAllowed) { disallowed = g_list_append(disallowed, (gpointer) origin); } else { allowed = g_list_append(allowed, (gpointer) origin); } - if (allowed && disallowed) { + if (allowed || disallowed) { webkit_web_context_initialize_notification_permissions( webContext, allowed, @@ -370,14 +370,19 @@ namespace SSC { WebKitNotification* notification, gpointer userData ) -> bool { - const auto window = reinterpret_cast<Window*>(userData); + const auto app = App::sharedApplication(); + const auto window = app->windowManager.getWindowForWebView(webview); if (window == nullptr) { - return false; + return true; } auto userConfig = window->bridge.userConfig; - return userConfig["permissions_allow_notifications"] != "false"; + if (userConfig["permissions_allow_notifications"] == "false") { + return true; + } + + return false; }), this ); @@ -391,31 +396,30 @@ namespace SSC { WebKitPermissionStateQuery* query, gpointer user_data ) -> bool { - static auto userConfig = SSC::getUserConfig(); - auto name = String(webkit_permission_state_query_get_name(query)); + const auto app = App::sharedApplication(); + const auto window = app->windowManager.getWindowForWebView(webview); + const auto name = String(webkit_permission_state_query_get_name(query)); if (name == "geolocation") { webkit_permission_state_query_finish( query, - userConfig["permissions_allow_geolocation"] == "false" + window->bridge.userConfig["permissions_allow_geolocation"] == "false" ? WEBKIT_PERMISSION_STATE_DENIED - : WEBKIT_PERMISSION_STATE_PROMPT + : WEBKIT_PERMISSION_STATE_GRANTED ); - } - - if (name == "notifications") { + } else if (name == "notifications") { webkit_permission_state_query_finish( query, - userConfig["permissions_allow_notifications"] == "false" + window->bridge.userConfig["permissions_allow_notifications"] == "false" ? WEBKIT_PERMISSION_STATE_DENIED - : WEBKIT_PERMISSION_STATE_PROMPT + : WEBKIT_PERMISSION_STATE_GRANTED + ); + } else { + webkit_permission_state_query_finish( + query, + WEBKIT_PERMISSION_STATE_PROMPT ); } - - webkit_permission_state_query_finish( - query, - WEBKIT_PERMISSION_STATE_PROMPT - ); return true; })), this From 4c90cf26f798bb502a4571b3c60b153aca963dfd Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 23 Jul 2024 18:14:53 +0100 Subject: [PATCH 1024/1178] refactor(permissions): fix typos and introduce more permissions to query for --- api/internal/permissions.js | 20 +++++++++++++++----- src/core/modules/permissions.cc | 24 +++++++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/api/internal/permissions.js b/api/internal/permissions.js index 2ad366cbc2..6f7a64e709 100644 --- a/api/internal/permissions.js +++ b/api/internal/permissions.js @@ -63,7 +63,9 @@ export const types = Enumeration.from([ 'push', 'persistent-storage', 'midi', - 'storage-access' + 'storage-access', + 'camera', + 'microphone' ]) /** @@ -244,13 +246,21 @@ export async function query (descriptor, options) { delete options.signal } - const result = await ipc.request('permissions.query', { name }, { signal }) + if (name === 'notifications' || name === 'geolocation') { + const result = await ipc.request('permissions.query', { name }, { signal }) - if (result.err) { - throw result.err + if (result.err) { + throw result.err + } + + return new PermissionStatus(name, result.data?.state, options) + } + + if (typeof platform.query === 'function') { + return platform.query(descriptor) } - return new PermissionStatus(name, result.data?.state, options) + throw new TypeError('Not supported') } /** diff --git a/src/core/modules/permissions.cc b/src/core/modules/permissions.cc index 234d4435cf..7e2f71d1c1 100644 --- a/src/core/modules/permissions.cc +++ b/src/core/modules/permissions.cc @@ -163,6 +163,13 @@ namespace SSC { callback(seq, json, Post{}); #endif } + + if (name == "camera" || name == "microphone") { + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #else + #endif + } }); } @@ -187,13 +194,13 @@ namespace SSC { JSON::Object json; #if SOCKET_RUNTIME_PLATFORM_APPLE - const auto performedActivation = [router->bridge->core->geolocation.locationObserver attemptActivationWithCompletion: ^(BOOL isAuthorized) { + const auto performedActivation = [this->core->geolocation.locationObserver attemptActivationWithCompletion: ^(BOOL isAuthorized) { if (!isAuthorized) { auto reason = @("Location observer could not be activated"); - if (!router->bridge->core->geolocation.locationObserver.locationManager) { + if (!this->core->geolocation.locationObserver.locationManager) { reason = @("Location observer manager is not initialized"); - } else if (!router->bridge->core->geolocation.locationObserver.locationManager.location) { + } else if (!this->core->geolocation.locationObserver.locationManager.location) { reason = @("Location observer manager could not provide location"); } @@ -202,7 +209,7 @@ namespace SSC { if (isAuthorized) { json["data"] = JSON::Object::Entries {{"state", "granted"}}; - } else if (router->bridge->core->geolocation.locationObserver.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { + } else if (this->core->geolocation.locationObserver.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { json["data"] = JSON::Object::Entries {{"state", "prompt"}}; } else { json["data"] = JSON::Object::Entries {{"state", "denied"}}; @@ -306,7 +313,7 @@ namespace SSC { } if (requestBadge) { - requestOoptions |= UNAuthorizationOptionBadge; + requestOptions |= UNAuthorizationOptionBadge; } if (requestSound) { @@ -427,6 +434,13 @@ namespace SSC { callback(seq, json, Post{}); #endif } + + if (name == "camera" || name == "microphone") { + #if SOCKET_RUNTIME_PLATFORM_APPLE + #elif SOCKET_RUNTIME_PLATFORM_ANDROID + #else + #endif + } }); } } From c3c726b50d9d3d225ae9bec365594833b1135438 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 24 Jul 2024 11:15:14 +0100 Subject: [PATCH 1025/1178] chore(core/webview): clenan up --- src/core/webview.cc | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/core/webview.cc b/src/core/webview.cc index 8dfe2eeb0b..42c6f58280 100644 --- a/src/core/webview.cc +++ b/src/core/webview.cc @@ -255,26 +255,28 @@ int lastY = 0; self.shouldDrag = false; draggablePayload.clear(); - const auto location = [self convertPoint: event.locationInWindow fromView: nil]; - const auto x = std::to_string(location.x); - const auto y = std::to_string(location.y); + const auto point = [self convertPoint: event.locationInWindow fromView: nil]; + const auto x = std::to_string(point.x); + const auto y = std::to_string(point.y); - self.initialWindowPos = location; + self.initialWindowPos = point; - lastX = (int) location.x; - lastY = (int) location.y; + lastX = (int) point.x; + lastY = (int) point.y; String js( - "(() => { " - " const v = '--app-region'; " - " let el = document.elementFromPoint(" + x + "," + y + "); " - " " - " while (el) { " - " if (getComputedStyle(el).getPropertyValue(v) == 'drag') return 'movable'; " - " el = el.parentElement; " - " } " - " return '' " - "})() " + "(() => { " + " const v = '--app-region'; " + " let el = document.elementFromPoint(" + x + "," + y + "); " + " " + " while (el) { " + " if (getComputedStyle(el).getPropertyValue(v) === 'drag') { " + " return 'movable'; " + " } " + " el = el.parentElement; " + " } " + " return '' " + "})(); " ); [self From f5b9e1af4cfc2b4ea87edd73996aa375cc099886 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 24 Jul 2024 11:16:00 +0100 Subject: [PATCH 1026/1178] refactor(api/internal/permissions.js): handle 'microphone' and 'camera' permissions --- api/internal/permissions.js | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/api/internal/permissions.js b/api/internal/permissions.js index 6f7a64e709..428838efad 100644 --- a/api/internal/permissions.js +++ b/api/internal/permissions.js @@ -228,6 +228,20 @@ export async function query (descriptor, options) { ) } + if (name === 'camera' || name === 'microphone') { + if (!globalThis.navigator.mediaDevices) { + if (globalThis.isServiceWorkerScope) { + throw new TypeError('MediaDevices are not supported in ServiceWorkerGlobalScope.') + } else if (globalThis.isSharedWorkerScope) { + throw new TypeError('MediaDevices are not supported in SharedWorkerGlobalScope.') + } else if (globalThis.isWorkerScope) { + throw new TypeError('MediaDevices are not supported in WorkerGlobalScope.') + } else { + throw new TypeError('MediaDevices are not supported.') + } + } + } + if (!isAndroid && !isApple) { if (isLinux) { if (name === 'notifications' || name === 'push') { @@ -303,6 +317,39 @@ export async function request (descriptor, options) { ) } + if (name === 'camera' || name === 'microphone') { + // will throw if `MediaDevices` are not supported + const status = await query({ name }, options) + + if (status.state === 'granted') { + return new PermissionStatus(name, 'granted', options) + } + + const constraints = { video: false, audio: false } + if (name === 'camera') { + constraints.video = true + delete constraints.audio + } else if (name === 'microphone') { + constraints.audio = true + delete constraints.video + } + + try { + const stream = await globalThis.navigator.mediaDevices.getUserMedia(constraints) + const tracks = await stream.getTracks() + for (const track of tracks) { + await track.stop() + } + return new PermissionStatus(name, 'granted', options) + } catch (err) { + if (err.name === 'NotAllowedError') { + return new PermissionStatus(name, 'denied', options) + } else { + throw err + } + } + } + if (isLinux) { if (name === 'notifications' || name === 'push') { const currentState = Notification.permission From 25abe2e121ea0bb9034ed4b0e437d51a581e8e39 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 24 Jul 2024 11:16:07 +0100 Subject: [PATCH 1027/1178] chore(api): generate types --- api/index.d.ts | 139 ++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index cec8d6b417..e7394b3543 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T>(key: Variable<T>): boolean; + get<T_1>(key: Variable<T_1>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T>(key: Variable<T>, value: T): Mapping; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T>(key: Variable<T>): Mapping; + delete<T_3>(key: Variable<T_3>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T>(key: Variable<T>): T | undefined; + static get<T_1>(key: Variable<T_1>): T_1; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T>(revert: Revert<T> | FrozenRevert): void; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_1>(): T_1 | undefined; + get<T_2>(): T_2; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F>(fn: F): F; + static wrap<F_1>(fn: F_1): F_1; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent; + prototype: CustomEvent<any>; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent; + prototype: MessageEvent<any>; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2161,7 +2161,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): value is symbol; + export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2837,7 +2837,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3072,7 +3072,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3102,7 +3102,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): signal; + export function getCode(name: string | number): any; /** * Gets the name for a given 'signal' code * @return {string} @@ -3246,7 +3246,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3627,7 +3627,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: ProcessEnvironment; + export const env: any; export default process; const process: any; } @@ -4533,9 +4533,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + send: (body?: Document | XMLHttpRequestBodyInit) => void; }; } export class WorkerMetric extends Metric { @@ -5263,7 +5263,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; + static from(stat?: object | Stats, fromBigInt?: any): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6136,7 +6136,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: "utf8" | "buffer"; + encoding: 'utf8' | 'buffer'; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6980,7 +6980,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "top-level" | "nested"; + get frameType(): "none" | "nested" | "top-level"; /** * The type of the client. * @type {'window'|'worker'} @@ -7012,10 +7012,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: "window" | "worker"; + type?: 'window' | 'worker'; parent?: object | null; top?: object | null; - frameType?: "top-level" | "nested" | "none"; + frameType?: 'top-level' | 'nested' | 'none'; }; } @@ -7089,7 +7089,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7302,7 +7302,6 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; - import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8027,7 +8026,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8073,9 +8072,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): CoreAsyncResource; - bind(fn: Function, thisArg?: object | undefined): Function; - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; }; } export namespace Promise { @@ -8695,7 +8694,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9904,7 +9903,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9979,7 +9978,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: "shared" | "wasm32"; + type?: 'shared' | 'wasm32'; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10475,7 +10474,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: "strict" | "relaxed" | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10539,7 +10538,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: "set" | "delete", entry?: object | undefined); + constructor(type: 'set' | 'delete', entry?: object | undefined); entry: any; } /** @@ -10671,7 +10670,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import("socket:service-worker/events").ExtendableEvent); + constructor(event: import('./events.js').ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10712,7 +10711,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import("socket:service-worker/clients").Client>; + client(): Promise<import('./clients.js').Client>; #private; } namespace _default { @@ -10899,7 +10898,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10934,13 +10933,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13631,7 +13630,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T>(actual: T, expected: T, msg?: string): void; + notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; /** * @template T * @param {T} actual @@ -13639,7 +13638,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T>(actual: T, expected: T, msg?: string): void; + equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13888,7 +13887,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: { + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { /** * - The text to wait for */ @@ -13899,7 +13898,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; + }, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15180,7 +15179,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } export default Package; @@ -15191,13 +15190,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; prefix?: string; }; export type ParsedPackageName = { @@ -15281,7 +15280,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import("socket:commonjs/module").Module); + constructor(module: import('./module.js').Module); /** * The referrer (parent) of this module. * @type {string} @@ -15325,7 +15324,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15685,7 +15684,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions | undefined): any; + require(url: any, options?: RequireOptions): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15723,9 +15722,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import process from "socket:process"; } declare module "socket:module" { @@ -16065,7 +16064,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; + static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; /** * `Notification` class constructor. * @param {string} title @@ -16251,9 +16250,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; export default createServiceWorker; @@ -16969,7 +16968,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16989,7 +16988,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17011,8 +17010,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: "read" | "readwrite"; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + mode?: 'read' | 'readwrite'; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; }; /** * ]?: string[] @@ -17022,10 +17021,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; types?: Array<{ description?: string; - [keyof]: any; + [keyof]; }>; }; } @@ -17126,7 +17125,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17135,7 +17134,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: "commonjs" | "module"; + type: 'commonjs' | 'module'; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17216,8 +17215,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17231,7 +17230,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17680,12 +17679,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; + export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends Harness> { + export class TapeHarness<T extends exports.Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17738,7 +17737,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends Harness> = { + export type TapeTestFn<T extends exports.Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17755,8 +17754,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 3c49816c3eb93914d5d586d39cf5b1c78e172647 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 24 Jul 2024 12:13:47 +0100 Subject: [PATCH 1028/1178] refactor(cli/templates): use 'DayNight' theme on android --- src/cli/templates.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 4e8b11cfe1..ecb866d0ad 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -493,7 +493,7 @@ constexpr auto gAndroidManifest = R"XML( android:name="{{android_application}}" android:allowBackup="true" android:label="{{meta_title}}" - android:theme="@style/Theme.AppCompat.Light" + android:theme="@style/Theme.AppCompat.DayNight" android:supportsRtl="true" {{android_application_icon_config}} {{android_allow_cleartext}} From 0bf46daaf4da9135f7d3ad9dcf92f6b9b5f5528a Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Wed, 24 Jul 2024 13:43:39 +0200 Subject: [PATCH 1029/1178] chore(api): update protocol --- api/latica/index.js | 54 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index 7dbd0e4629..8c4fff8f1b 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -213,6 +213,7 @@ export class Peer { natType = NAT.UNKNOWN nextNatType = NAT.UNKNOWN clusters = {} + syncs = {} reflectionId = null reflectionTimeout = null reflectionStage = 0 @@ -283,7 +284,7 @@ export class Peer { // // The purpose of this.config is to seperate transitioned state from initial state. // - this.config = { + this.config = { // TODO(@heapwolf): Object.freeze this maybe keepalive: DEFAULT_KEEP_ALIVE, ...config } @@ -585,6 +586,7 @@ export class Peer { return { peers, + syncs: this.syncs, config: this.config, data: [...this.cache.data.entries()], unpublished: this.unpublished @@ -617,8 +619,11 @@ export class Peer { } async reconnect () { - this.lastUpdate = 0 - this.requestReflection() + for (const cluster of Object.values(this.clusters)) { + for (const subcluster of Object.values(cluster)) { + this.join(subcluster.sharedKey, subcluster) + } + } } async disconnect () { @@ -1145,7 +1150,7 @@ export class Peer { if (this.gate.has(pid)) return this.returnRoutes.set(p.usr3.toString('hex'), {}) - this.gate.set(pid, 1) // don't accidentally spam + this.gate.set(pid, 1) // prevent accidental spam this._onDebug(`-> QUERY (type=question, query=${query}, packet=${pid.slice(0, 8)})`) @@ -1233,6 +1238,30 @@ export class Peer { if (firstContact && this.onConnection) { this.onConnection(packet, peer, port, address) + + const now = Date.now() + const key = [peer.address, peer.port].join(':') + let first = false + + // + // If you've never sync'd before, you can ask for 6 hours of data from + // other peers. If we have synced with a peer before we can just ask for + // data that they have seen since then, this will avoid the risk of + // spamming them and getting rate-limited. + // + if (!this.syncs[key]) { + this.syncs[key] = now - Packet.ttl + first = true + } + + const lastSyncSeconds = (now - this.syncs[key]) / 1000 + const syncWindow = this.config.syncWindow ?? 6000 + + if (first || now - this.syncs[key] > syncWindow) { + this.sync(peer.peerId, this.syncs[key]) + this._onDebug(`-> SYNC SEND (peerId=${peer.peerId.slice(0, 6)}, address=${key}, since=${lastSyncSeconds} seconds ago)`) + this.syncs[key] = now + } } } @@ -1332,11 +1361,8 @@ export class Peer { this.metrics.i[packet.type]++ const pid = packet.packetId.toString('hex') - if (this.gate.has(pid)) return - this.gate.set(pid, 1) - - const queryTimestamp = parseInt(packet.usr1.toString(), 10) const queryId = packet.usr3.toString('hex') + const queryTimestamp = parseInt(packet.usr1.toString(), 10) const queryType = parseInt(packet.usr4.toString(), 10) // if the timestamp in usr1 is older than now - 2s, bail @@ -1350,7 +1376,7 @@ export class Peer { // // receiving an answer // - if (this.returnRoutes.has(queryId)) { + if (this.returnRoutes.has(queryId) && type === 'answer') { rinfo = this.returnRoutes.get(queryId) let p = packet.copy() @@ -1365,7 +1391,8 @@ export class Peer { } if (!rinfo.address) return - } else { + } else if (type === 'question') { + if (this.gate.has(pid)) return // // receiving a query // @@ -1398,14 +1425,17 @@ export class Peer { p.usr3 = packet.usr3 // ensure the packet has the queryId p.usr4 = Buffer.from(String(2)) // mark it as an answer packet this.send(await Packet.encode(p), rinfo.port, rinfo.address) + this.gate.set(pid, 1) } return } } if (packet.hops >= this.maxHops) return + if (this.gate.has(pid)) return + this._onDebug('>> QUERY RELAY', port, address) - return await this.mcast(packet) + await this.mcast(packet) } /** @@ -1570,7 +1600,7 @@ export class Peer { }) } - this._mainLoop(Date.now()) + this._setTimeout(() => this._mainLoop(Date.now()), 1024) if (this.onNat) this.onNat(this.natType) From 793b721cfe128474247ab9ed0a7c7135bbc163d6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 15:04:15 +0200 Subject: [PATCH 1030/1178] fix(cli): fix race on shutdown from cli --- src/cli/cli.cc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 60d8124399..b287df6565 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -765,9 +765,9 @@ Vector<Path> handleBuildPhaseForCopyMappedFiles ( } void signalHandler (int signum) { - #if !defined(_WIN32) + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS if (signum == SIGUSR1) { - #if defined(__APPLE__) + #if SOCKET_RUNTIME_PLATFORM_APPLE checkLogStore = true; #endif return; @@ -776,7 +776,7 @@ void signalHandler (int signum) { Lock lock(signalHandlerMutex); - #if !defined(_WIN32) + #if !SOCKET_RUNTIME_PLATFORM_WINDOWS if (appPid > 0) { kill(appPid, signum); } @@ -789,12 +789,6 @@ void signalHandler (int signum) { appPid = 0; - #if defined(__linux__) && !defined(__ANDROID__) - if (gtk_main_level() > 0) { - gtk_main_quit(); - } - #endif - if (sourcesWatcher != nullptr) { sourcesWatcher->stop(); delete sourcesWatcher; @@ -821,6 +815,14 @@ void signalHandler (int signum) { log("App result: " + std::to_string(signum)); } + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (gtk_main_level() > 0) { + // FIXME(@jwerle): calling `msleep()` from a signal handler is definitely bad practice + msleep(4); + gtk_main_quit(); + } + #endif + if (signum == SIGTERM || signum == SIGINT) { signal(signum, SIG_DFL); raise(signum); From 55fedc7cb8be5e92f6809c2c909ccb9767dc0c86 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 15:04:57 +0200 Subject: [PATCH 1031/1178] fix(core/module): fix remove/dispatch crash --- src/core/module.hh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/module.hh b/src/core/module.hh index 2cb4c02c4d..2fb95c4b9f 100644 --- a/src/core/module.hh +++ b/src/core/module.hh @@ -95,6 +95,11 @@ namespace SSC { bool remove (const Observer& observer) { Lock lock(this->mutex); + + if (this->observers.begin() == this->observers.end()) { + return false; + } + auto iterator = this->observers.begin(); do { @@ -141,6 +146,11 @@ namespace SSC { template <typename... Types> bool dispatch (Types... arguments) { Lock lock(this->mutex); + + if (this->observers.begin() == this->observers.end()) { + return false; + } + bool dispatched = false; auto iterator = this->observers.begin(); From 0ac8810bd81d72360a261dc90e4c35e794a431f8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 15:06:34 +0200 Subject: [PATCH 1032/1178] fix(window/linux): various fixes --- src/window/linux.cc | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 7943e4e8f0..db1cc22564 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -150,6 +150,10 @@ namespace SSC { webkit_settings_set_enable_smooth_scrolling(this->settings, true); webkit_settings_set_enable_developer_extras(this->settings, options.debug); webkit_settings_set_enable_back_forward_navigation_gestures(this->settings, true); + webkit_settings_set_media_content_types_requiring_hardware_support( + this->settings, + nullptr + ); auto userAgent = String(webkit_settings_get_user_agent(settings)); @@ -448,19 +452,19 @@ namespace SSC { result = userConfig["permissions_allow_notifications"] != "false"; description = "{{meta_title}} would like display notifications."; } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) { - if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { - name = "microphone"; - result = userConfig["permissions_allow_microphone"] != "false"; - description = "{{meta_title}} would like access to your microphone."; - } + if (userConfig["permissions_allow_user_media"] != "false") { + if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { + name = "microphone"; + result = userConfig["permissions_allow_microphone"] != "false"; + description = "{{meta_title}} would like access to your microphone."; + } - if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { - name = "camera"; - result = userConfig["permissions_allow_camera"] != "false"; - description = "{{meta_title}} would like access to your camera."; + if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { + name = "camera"; + result = userConfig["permissions_allow_camera"] != "false"; + description = "{{meta_title}} would like access to your camera."; + } } - - result = userConfig["permissions_allow_user_media"] != "false"; } else if (WEBKIT_IS_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST(request)) { name = "storage-access"; result = userConfig["permissions_allow_data_access"] != "false"; @@ -853,7 +857,7 @@ namespace SSC { "destroy", G_CALLBACK((+[](GtkWidget* object, gpointer arg) { auto app = App::sharedApplication(); - if (app == nullptr) { + if (app == nullptr || app->shouldExit) { return FALSE; } @@ -1348,8 +1352,8 @@ namespace SSC { GtkStyleContext* context = gtk_widget_get_style_context(this->window); - GdkRGBA color; - gtk_style_context_get_background_color(context, gtk_widget_get_state_flags(this->window), &color); + GdkRGBA color = {0.0, 0.0, 0.0, 0.0}; + webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(this->webview), &color); gtk_widget_override_background_color(menubar, GTK_STATE_FLAG_NORMAL, &color); auto menus = split(menuSource, ';'); @@ -1364,6 +1368,7 @@ namespace SSC { // if this is a tray menu, append directly to the tray instead of a submenu. auto* ctx = isTrayMenu ? menutray : gtk_menu_new(); GtkWidget* menuItem = gtk_menu_item_new_with_label(menuTitle.c_str()); + gtk_widget_override_background_color(menuItem, GTK_STATE_FLAG_NORMAL, &color); if (isTrayMenu && menuSource.size() == 1) { if (menuParts.size() > 1) { @@ -1491,6 +1496,7 @@ namespace SSC { } gtk_widget_set_name(item, menuTitle.c_str()); + gtk_widget_override_background_color(menuItem, GTK_STATE_FLAG_NORMAL, &color); gtk_menu_shell_append(GTK_MENU_SHELL(ctx), item); } From 2fe05fa97610474dcf1f89a8e8db69a1f41f2029 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 18:48:04 +0200 Subject: [PATCH 1033/1178] chore(app): clean up --- src/app/app.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/app.kt b/src/app/app.kt index abc74a4357..161265004b 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -372,14 +372,6 @@ open class AppActivity : WindowManagerActivity() { } } - override fun onActivityResult ( - requestCode: Int, - resultCode: Int, - intent: Intent? - ) { - super.onActivityResult(requestCode, resultCode, intent) - } - override fun onRequestPermissionsResult ( requestCode: Int, permissions: Array<String>, From 50c495db974d18e6eb6b2f2742dc449f35155e44 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 18:48:20 +0200 Subject: [PATCH 1034/1178] refactor(cli): clean up 'AndroidManifest.xml' template --- src/cli/cli.cc | 6 ++---- src/cli/templates.hh | 17 +---------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index b287df6565..a40b5a6458 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -3183,10 +3183,6 @@ int main (const int argc, const char* argv[]) { settings["android_main_activity"] = String(DEFAULT_ANDROID_MAIN_ACTIVITY_NAME); } - if (settings["android_webview_window_activity"].size() == 0) { - settings["android_webview_window_activity"] = String(DEFAULT_ANDROID_WEBVIEW_WINDOW_ACTIVITY_NAME); - } - if (settings["android_application"].size() == 0) { settings["android_application"] = String(DEFAULT_ANDROID_APPLICATION_NAME); } @@ -3390,6 +3386,8 @@ int main (const int argc, const char* argv[]) { if (settings["permissions_allow_microphone"] != "false") { manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.CAPTURE_AUDIO_OUTPUT\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n"; + manifestContext["android_manifest_xml_permissions"] += " <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n"; } } diff --git a/src/cli/templates.hh b/src/cli/templates.hh index ecb866d0ad..a73e160a98 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -468,8 +468,6 @@ constexpr auto gCredits = R"HTML( constexpr auto DEFAULT_ANDROID_APPLICATION_NAME = ".App"; constexpr auto DEFAULT_ANDROID_MAIN_ACTIVITY_NAME = ".MainActivity"; -//constexpr auto DEFAULT_ANDROID_WEBVIEW_WINDOW_ACTIVITY_NAME = "socket.runtime.window.WebViewWindowActivity"; -constexpr auto DEFAULT_ANDROID_WEBVIEW_WINDOW_ACTIVITY_NAME = "socket.runtime.window.WindowActivity"; // // Android Manifest @@ -508,6 +506,7 @@ constexpr auto gAndroidManifest = R"XML( android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:launchMode="singleTop" android:enableOnBackInvokedCallback="true" + android:hardwareAccelerated="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -524,20 +523,6 @@ constexpr auto gAndroidManifest = R"XML( {{android_activity_intent_filters}} </activity> - <activity - android:name="{{android_webview_window_activity}}" - android:exported="true" - android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" - android:launchMode="standard" - android:documentLaunchMode="intoExisting" - android:hardwareAccelerated="true" - > - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.LAUNCHER" /> - <category android:name="android.intent.category.BROWSABLE" /> - </intent-filter> - </activity> </application> </manifest> )XML"; From 52ac4ac1f435a9c08eec6d46f5db1c48f74be364 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 18:48:42 +0200 Subject: [PATCH 1035/1178] fix(window): fix dialog impl on android --- src/window/dialog.cc | 145 +++++++++++++++++++++++++--------------- src/window/dialog.kt | 150 +++++++++++++++++++++++------------------- src/window/manager.kt | 20 ++++-- src/window/window.kt | 33 ++++++++-- 4 files changed, 217 insertions(+), 131 deletions(-) diff --git a/src/window/dialog.cc b/src/window/dialog.cc index 9e39a0af4b..365376224a 100644 --- a/src/window/dialog.cc +++ b/src/window/dialog.cc @@ -19,9 +19,7 @@ using namespace SSC; paths.push_back(url.path.UTF8String); } } - debug("before callback"); self.dialog->callback(paths); - debug("after callback"); } - (void) documentPickerWasCancelled: (UIDocumentPickerViewController*) controller { @@ -136,6 +134,8 @@ namespace SSC { Vector<String> paths; + this->callback = callback; + #if SOCKET_RUNTIME_PLATFORM_APPLE // state NSMutableArray<UTType *>* contentTypes = [NSMutableArray new]; @@ -225,7 +225,6 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_IOS UIWindow* window = nullptr; - this->callback = callback; if (this->window) { window = this->window->window; @@ -788,65 +787,103 @@ namespace SSC { }); return true; #elif SOCKET_RUNTIME_PLATFORM_ANDROID - if (this->window->androidWindowRef) { - const auto app = App::sharedApplication(); - const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - const auto dialogRef = CallObjectClassMethodFromAndroidEnvironment( - attachment.env, - this->window->androidWindowRef, - "getDialog", - "()Lsocket/runtime/window/Dialog;" - ); + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + const auto dialog = CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + app->core->platform.activity, + "getDialog", + "()Lsocket/runtime/window/Dialog;" + ); - String mimeTypes; - // <mime>:<ext>,<ext>|<mime>:<ext>|... - for (const auto& contentTypeSpec : split(options.contentTypes, "|")) { - const auto parts = split(contentTypeSpec, ":"); - const auto mime = trim(parts[0]); - const auto classes = split(mime, "/"); - if (classes.size() == 2) { - if (mimeTypes.size() == 0) { - mimeTypes = mime; - } else { - mimeTypes += "|" + mime; - } + // construct the mime types into a packed string + String mimeTypes; + // <mime>:<ext>,<ext>|<mime>:<ext>|... + for (const auto& contentTypeSpec : split(options.contentTypes, "|")) { + const auto parts = split(contentTypeSpec, ":"); + const auto mime = trim(parts[0]); + const auto classes = split(mime, "/"); + if (classes.size() == 2) { + if (mimeTypes.size() == 0) { + mimeTypes = mime; + } else { + mimeTypes += "|" + mime; } } + } - const auto mimeTypesRef = attachment.env->NewStringUTF(mimeTypes.c_str()); - const auto results = (jobjectArray) CallObjectClassMethodFromAndroidEnvironment( - attachment.env, - dialogRef, - "showFileSystemPicker", - "(Ljava/lang/String;ZZZ)[Landroid/net/Uri;", - mimeTypesRef, - allowDirectories, - allowMultiple, - allowFiles + // we'll set the pointer from this instance in this call so + // the `onResults` can reinterpret the `jlong` back into a `Dialog*` + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + dialog, + "showFileSystemPicker", + "(Ljava/lang/String;ZZZJ)V", + attachment.env->NewStringUTF(mimeTypes.c_str()), + allowDirectories, + allowMultiple, + allowFiles, + reinterpret_cast<jlong>(this) + ); + + return true; + #endif + + return false; + } +} + +#if SOCKET_RUNTIME_PLATFORM_ANDROID +extern "C" { + void ANDROID_EXTERNAL(window, Dialog, onResults) ( + JNIEnv* env, + jobject self, + jlong pointer, + jobjectArray results + ) { + const auto app = App::sharedApplication(); + + if (!app) { + return ANDROID_THROW(env, "Missing 'App' in environment"); + } + + const auto dialog = reinterpret_cast<Dialog*>(pointer); + + if (!dialog) { + return ANDROID_THROW( + env, + "Missing 'Dialog' in results callback from 'showFileSystemPicker'" ); + } - const auto length = attachment.env->GetArrayLength(results); - for (int i = 0; i < length; ++i) { - const auto uri = (jstring) attachment.env->GetObjectArrayElement(results, i); - if (uri) { - const auto string = Android::StringWrap(attachment.env, CallObjectClassMethodFromAndroidEnvironment( - attachment.env, - uri, - "toString", - "()Ljava/lang/String;" - )).str(); - - paths.push_back(string); - } - } + if (dialog->callback == nullptr) { + return ANDROID_THROW( + env, + "Missing 'Dialog' callback in results callback from 'showFileSystemPicker'" + ); + } + + const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); + const auto length = attachment.env->GetArrayLength(results); + + Vector<String> paths; - app->dispatch([=]() { - callback(paths); - }); - return true; + for (int i = 0; i < length; ++i) { + const auto uri = (jstring) attachment.env->GetObjectArrayElement(results, i); + if (uri) { + const auto string = Android::StringWrap(attachment.env, CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + uri, + "toString", + "()Ljava/lang/String;" + )).str(); + + paths.push_back(string); + } } - #endif - return false; + const auto callback = dialog->callback; + dialog->callback = nullptr; + callback(paths); } } +#endif diff --git a/src/window/dialog.kt b/src/window/dialog.kt index 543f372268..3c0cf83ab2 100644 --- a/src/window/dialog.kt +++ b/src/window/dialog.kt @@ -1,18 +1,21 @@ package socket.runtime.window import java.lang.Runtime -import java.util.concurrent.Semaphore import android.net.Uri import android.webkit.WebChromeClient import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity + +import socket.runtime.core.console +import socket.runtime.window.WindowManagerActivity /** + * XXX */ -open class Dialog (val window: Window) { +open class Dialog (val activity: WindowManagerActivity) { /** + * XXX */ open class FileSystemPickerOptions ( val params: WebChromeClient.FileChooserParams? = null, @@ -28,95 +31,104 @@ open class Dialog (val window: Window) { } } - var results = arrayOf<Uri>() - val activity = window.activity as AppCompatActivity - val semaphore = Semaphore(1) + var callback: ((results: Array<Uri>) -> Unit)? = null + + val launcherForSingleItem = activity.registerForActivityResult( + ActivityResultContracts.GetContent(), + fun (uri: Uri?) { this.resolve(uri) } + ) + + val launcherForMulitpleItems = activity.registerForActivityResult( + ActivityResultContracts.GetMultipleContents(), + { uris -> this.resolve(uris) } + ) + + // XXX(@jwerle): unused at the moment + val launcherForSingleDocument = activity.registerForActivityResult( + ActivityResultContracts.OpenDocument(), + { uri -> this.resolve(uri) } + ) + + // XXX(@jwerle): unused at the moment + val launcherForMulitpleDocuments = activity.registerForActivityResult( + ActivityResultContracts.OpenMultipleDocuments(), + { uris -> this.resolve(uris) } + ) + + // XXX(@jwerle): unused at the moment + val launcherForSingleVisualMedia = activity.registerForActivityResult( + ActivityResultContracts.PickVisualMedia(), + { uri -> this.resolve(uri) } + ) + + // XXX(@jwerle): unused at the moment + val launcherForMultipleVisualMedia = activity.registerForActivityResult( + ActivityResultContracts.PickMultipleVisualMedia(), + { uris -> this.resolve(uris) } + ) fun resolve (uri: Uri?) { if (uri != null) { - this.results = arrayOf(uri) + return this.resolve(arrayOf(uri)) } - this.semaphore.release() - } - fun resolve (uris: Array<Uri>) { - this.results = uris - this.semaphore.release() + return this.resolve(arrayOf<Uri>()) } fun resolve (uris: List<Uri>) { - this.results = Array<Uri>(uris.size, { i -> uris[i] }) - this.semaphore.release() + this.resolve(Array<Uri>(uris.size, { i -> uris[i] })) } - fun showFileSystemPicker (options: FileSystemPickerOptions): Array<Uri> { - val mimeType = - if (options.mimeTypes.size > 0) { options.mimeTypes[0] } - else { "*/*" } - - this.semaphore.acquireUninterruptibly() - - val launcherForSingleItem = activity.registerForActivityResult( - ActivityResultContracts.GetContent(), - fun (uri: Uri?) { this.resolve(uri) } - ) - - val launcherForMulitpleItems = activity.registerForActivityResult( - ActivityResultContracts.GetMultipleContents(), - { uris -> this.resolve(uris) } - ) - - // XXX(@jwerle): unused at the moment - val launcherForSingleDocument = activity.registerForActivityResult( - ActivityResultContracts.OpenDocument(), - { uri -> this.resolve(uri) } - ) - - // XXX(@jwerle): unused at the moment - val launcherForMulitpleDocuments = activity.registerForActivityResult( - ActivityResultContracts.OpenMultipleDocuments(), - { uris -> this.resolve(uris) } - ) - - // XXX(@jwerle): unused at the moment - val launcherForSingleVisualMedia = activity.registerForActivityResult( - ActivityResultContracts.PickVisualMedia(), - { uri -> this.resolve(uri) } - ) - - // XXX(@jwerle): unused at the moment - val launcherForMultipleVisualMedia = activity.registerForActivityResult( - ActivityResultContracts.PickMultipleVisualMedia(), - { uris -> this.resolve(uris) } - ) - - // TODO(@jwerle): support the other launcher types above - // through the `showFileSystemPicker()` method some how - - if (options.multiple) { - launcherForMulitpleItems.launch(mimeType) - } else { - launcherForSingleItem.launch(mimeType) + fun resolve (uris: Array<Uri>) { + val callback = this.callback + if (callback != null) { + this.callback = null + callback(uris) } + } - this.semaphore.acquireUninterruptibly() - val results = this.results - this.semaphore.release() - return results + fun showFileSystemPicker ( + options: FileSystemPickerOptions, + callback: ((Array<Uri>) -> Unit)? = null + ) { + val mimeType = + if (options.mimeTypes.size > 0 && options.mimeTypes[0].length > 0) { + options.mimeTypes[0] + } else { "*/*" } + + this.callback = callback + + this.activity.runOnUiThread { + // TODO(@jwerle): support the other launcher types above + // through the `showFileSystemPicker()` method some how + if (options.multiple) { + launcherForMulitpleItems.launch(mimeType) + } else { + launcherForSingleItem.launch(mimeType) + } + } } fun showFileSystemPicker ( mimeTypes: String, directories: Boolean = false, multiple: Boolean = false, - files: Boolean = true - ): Array<Uri> { + files: Boolean = true, + pointer: Long = 0 + ) { return this.showFileSystemPicker(FileSystemPickerOptions( null, mimeTypes.split("|").toMutableList(), directories, multiple, files - )) + ), fun (uris: Array<Uri>) { + if (pointer != 0L) { + this.onResults(pointer, uris) + } + }) } + + @Throws(Exception::class) + external fun onResults (pointer: Long, results: Array<Uri>): Unit } diff --git a/src/window/manager.kt b/src/window/manager.kt index f45e55447c..3eef56adf3 100644 --- a/src/window/manager.kt +++ b/src/window/manager.kt @@ -22,7 +22,9 @@ import __BUNDLE_IDENTIFIER__.R /** * A `WindowFragmentManager` manages `WindowFragment` instances. */ -open class WindowFragmentManager (protected val activity: WindowManagerActivity) { +open class WindowFragmentManager ( + protected val activity: WindowManagerActivity +) { open val fragments = mutableListOf<WindowFragment>() open val manager = activity.supportFragmentManager @@ -40,9 +42,10 @@ open class WindowFragmentManager (protected val activity: WindowManagerActivity) // .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right) setReorderingAllowed(true) add(R.id.window, fragment) - if (!options.headless) { - //addToBackStack("window#${options.index}") - addToBackStack(null) + if (options.headless) { + hide(fragment) + } else { + addToBackStack("window#${options.index}") } } } @@ -272,11 +275,20 @@ open class WindowFragmentManager (protected val activity: WindowManagerActivity) */ open class WindowManagerActivity : AppCompatActivity(R.layout.window_container_view) { open val windowFragmentManager = WindowFragmentManager(this) + open val dialog = Dialog(this) override fun onBackPressed () { // this.windowFragmentManager.popWindowFragment() } + override fun onActivityResult ( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + } + /** * Creates a new window at a given `index`. */ diff --git a/src/window/window.kt b/src/window/window.kt index d5946f2be4..a8319c3dd3 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -7,6 +7,7 @@ import kotlin.concurrent.thread import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import android.util.AttributeSet import android.view.LayoutInflater @@ -29,7 +30,7 @@ import socket.runtime.core.WebChromeClient import socket.runtime.ipc.Bridge import socket.runtime.ipc.Message import socket.runtime.window.WindowFragment -import socket.runtime.window.Dialog +import socket.runtime.window.WebViewFilePickerOptions import __BUNDLE_IDENTIFIER__.R @@ -76,7 +77,7 @@ open class WindowWebViewUserMessageHandler (window: Window) { this.window.bridge.buffers[message.seq] = bytes } - this.window.onMessage(this.window.index, value) + this.window.onMessage(value) return true } } @@ -131,8 +132,15 @@ open class WindowWebChromeClient (val window: Window) : WebChromeClient() { callback: android.webkit.ValueCallback<Array<android.net.Uri>>, params: android.webkit.WebChromeClient.FileChooserParams ): Boolean { + if (!super.onShowFileChooser(webview, callback, params)) { + return false + } val fragment = this.window.fragment val activity = fragment.requireActivity() as AppCompatActivity + val options = WebViewFilePickerOptions(params) + activity.dialog.showFileSystemPicker(options, fun (uris: Array<Uri>) { + callback.onReceiveValue(uris) + }) return true } } @@ -144,7 +152,6 @@ open class Window (val fragment: WindowFragment) { val activity = fragment.requireActivity() val bridge = Bridge(fragment.index, activity as AppCompatActivity) val client = WindowWebChromeClient(this) - val dialog = Dialog(this) val index = fragment.index var title = "" @@ -193,6 +200,7 @@ open class Window (val fragment: WindowFragment) { } fun close () { + // TODO } fun navigate (url: String) { @@ -227,6 +235,19 @@ open class Window (val fragment: WindowFragment) { return this.handleApplicationURL(this.index, url) } + fun onReady () { + this.bridge.activity.runOnUiThread { + this.onReady(this.index) + } + } + + fun onMessage (value: String, bytes: ByteArray? = null) { + this.onMessage(this.index, value, bytes) + } + + @Throws(Exception::class) + external fun onReady (index: Int): Unit + @Throws(Exception::class) external fun onMessage (index: Int, value: String, bytes: ByteArray? = null): Unit @@ -273,10 +294,12 @@ open class WindowFragment : Fragment(R.layout.web_view) { open val options = WindowOptions() /** + * XXX(@jwerle) */ open var window: Window? = null /** + * XXX(@jwerle) */ open val index: Int get () = this.options.index @@ -351,6 +374,8 @@ open class WindowFragment : Fragment(R.layout.web_view) { settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW } - this.window = Window(this) + this.window = Window(this).apply { + onReady() + } } } From be8d49bce2abc0f2aaadd35dff347e3ae7967c4c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 18:51:51 +0200 Subject: [PATCH 1036/1178] refactor(api/dgram.js): consider detached array buffers --- api/README.md | 40 +++++++------- api/dgram.js | 13 +++++ api/index.d.ts | 140 +++++++++++++++++++++++++------------------------ 3 files changed, 104 insertions(+), 89 deletions(-) diff --git a/api/README.md b/api/README.md index 6d5b52c04a..4341c59cd4 100644 --- a/api/README.md +++ b/api/README.md @@ -498,7 +498,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L651) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L658) Creates a `Socket` instance. @@ -517,12 +517,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L657) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L664) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L738) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L745) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -539,7 +539,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L853) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L860) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -559,7 +559,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L890) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L897) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -567,7 +567,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L949) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L956) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -618,7 +618,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1036) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1049) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -630,7 +630,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1108) +### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1121) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -646,7 +646,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1143) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1156) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -661,7 +661,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1174) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1187) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -672,7 +672,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1191) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1204) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -683,12 +683,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1204) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1217) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1212) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1225) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -697,31 +697,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1280) +### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1293) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1286) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1299) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1303) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1316) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1310) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1323) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1318) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1331) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1325) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1338) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1335) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1348) Thrown when a bad port is given. diff --git a/api/dgram.js b/api/dgram.js index 971b53dd8d..88bf4a4c10 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -77,6 +77,13 @@ function createDataListener (socket, resource) { if (!data || BigInt(data.id) !== socket.id) return if (source === 'udp.readStart') { + if (buffer && buffer instanceof ArrayBuffer) { + // @ts-ignore + if (buffer.detached) { + return + } + } + const message = Buffer.from(buffer) const info = { ...data, @@ -1015,6 +1022,12 @@ export class Socket extends EventEmitter { buffer = buffer.slice(0, length) + if (buffer?.buffer?.detacted) { + // XXX(@jwerle,@heapwolf): this is likely during a paused application state + // how should handle this? + return + } + return send(this, { id, port, address, buffer }, (...args) => { if (typeof cb === 'function') { this.#resource.runInAsyncScope(() => { diff --git a/api/index.d.ts b/api/index.d.ts index e7394b3543..3eb2342603 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2161,7 +2161,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2837,7 +2837,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3072,7 +3072,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3102,7 +3102,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): any; + export function getCode(name: string | number): signal; /** * Gets the name for a given 'signal' code * @return {string} @@ -3246,7 +3246,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3627,7 +3627,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } @@ -4533,9 +4533,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -5263,7 +5263,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6136,7 +6136,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6980,7 +6980,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "nested" | "top-level"; + get frameType(): "none" | "top-level" | "nested"; /** * The type of the client. * @type {'window'|'worker'} @@ -7012,10 +7012,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: 'window' | 'worker'; + type?: "window" | "worker"; parent?: object | null; top?: object | null; - frameType?: 'top-level' | 'nested' | 'none'; + frameType?: "top-level" | "nested" | "none"; }; } @@ -7089,7 +7089,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7302,6 +7302,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8026,7 +8027,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8072,9 +8073,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; }; } export namespace Promise { @@ -8694,7 +8695,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9903,7 +9904,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9978,7 +9979,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10474,7 +10475,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10538,7 +10539,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -10670,7 +10671,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10711,7 +10712,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -10898,7 +10899,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10933,13 +10934,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -12901,6 +12902,7 @@ declare module "socket:latica/index" { natType: number; nextNatType: number; clusters: {}; + syncs: {}; reflectionId: any; reflectionTimeout: any; reflectionStage: number; @@ -13630,7 +13632,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -13638,7 +13640,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13887,7 +13889,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -13898,7 +13900,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15179,7 +15181,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -15190,13 +15192,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -15280,7 +15282,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -15324,7 +15326,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15684,7 +15686,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15722,9 +15724,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:module" { @@ -16064,7 +16066,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -16250,9 +16252,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -16968,7 +16970,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16988,7 +16990,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17010,8 +17012,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -17021,10 +17023,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -17125,7 +17127,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17134,7 +17136,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17215,8 +17217,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17230,7 +17232,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17679,12 +17681,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17737,7 +17739,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17754,8 +17756,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 7ce24007903f4e5434e4a5e3da6c2d2565e96114 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 25 Jul 2024 19:06:38 +0200 Subject: [PATCH 1037/1178] chore(api/dgram.js): fix typo --- api/dgram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dgram.js b/api/dgram.js index 88bf4a4c10..238b581b38 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -1022,7 +1022,7 @@ export class Socket extends EventEmitter { buffer = buffer.slice(0, length) - if (buffer?.buffer?.detacted) { + if (buffer?.buffer?.detached) { // XXX(@jwerle,@heapwolf): this is likely during a paused application state // how should handle this? return From 2fd96299dd812e012b35e6d7bb505f83799c3253 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 26 Jul 2024 16:06:33 +0200 Subject: [PATCH 1038/1178] feat(core/modules/media_devices): introduce 'CoreMediaDevices' module --- src/core/modules/media_devices.cc | 31 +++++++++++++++++++++++++++++++ src/core/modules/media_devices.hh | 27 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/core/modules/media_devices.cc create mode 100644 src/core/modules/media_devices.hh diff --git a/src/core/modules/media_devices.cc b/src/core/modules/media_devices.cc new file mode 100644 index 0000000000..4344cfc9d1 --- /dev/null +++ b/src/core/modules/media_devices.cc @@ -0,0 +1,31 @@ +#include "media_devices.hh" +#include "../debug.hh" + +namespace SSC { + CoreMediaDevices::CoreMediaDevices (Core* core) + : CoreModule(core), + permissionChangeObservers() + {} + + CoreMediaDevices::~CoreMediaDevices () {} + + template<> bool CoreModule::template Observers<CoreModule::template Observer<JSON::Object>>::add( + const CoreModule::Observer<JSON::Object>&, + CoreModule::Observer<JSON::Object>::Callback + ); + + template<> bool CoreMediaDevices::PermissionChangeObservers::remove( + const CoreMediaDevices::PermissionChangeObserver& + ); + + bool CoreMediaDevices::addPermissionChangeObserver ( + const PermissionChangeObserver& observer, + const PermissionChangeObserver::Callback callback + ) { + return this->permissionChangeObservers.add(observer, callback); + } + + bool CoreMediaDevices::removePermissionChangeObserver (const PermissionChangeObserver& observer) { + return this->permissionChangeObservers.remove(observer); + } +} diff --git a/src/core/modules/media_devices.hh b/src/core/modules/media_devices.hh new file mode 100644 index 0000000000..119bc26c2d --- /dev/null +++ b/src/core/modules/media_devices.hh @@ -0,0 +1,27 @@ +#ifndef SOCKET_RUNTIME_CORE_MODULE_MEDIA_DEVICES_H +#define SOCKET_RUNTIME_CORE_MODULE_MEDIA_DEVICES_H + +#include "../module.hh" + +namespace SSC { + class CoreMediaDevices : public CoreModule { + public: + using PermissionChangeObserver = CoreModule::Observer<JSON::Object>; + using PermissionChangeObservers = CoreModule::Observers<PermissionChangeObserver>; + + PermissionChangeObservers permissionChangeObservers; + + CoreMediaDevices (Core* core); + ~CoreMediaDevices (); + + bool removePermissionChangeObserver ( + const PermissionChangeObserver& observer + ); + + bool addPermissionChangeObserver ( + const PermissionChangeObserver& observer, + const PermissionChangeObserver::Callback callback + ); + }; +} +#endif From b5c409fbf30f28180f88a5c8e9e988c3d00c7342 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 26 Jul 2024 16:06:54 +0200 Subject: [PATCH 1039/1178] refactor(core/modules/permissions): handle 'camera' and 'microphone' permissions on android --- src/core/core.hh | 10 +- src/core/modules/permissions.cc | 204 +++++++++++++++++++++++++++++++- 2 files changed, 205 insertions(+), 9 deletions(-) diff --git a/src/core/core.hh b/src/core/core.hh index c27bf58af5..fc43773a39 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -32,6 +32,7 @@ #include "modules/dns.hh" #include "modules/fs.hh" #include "modules/geolocation.hh" +#include "modules/media_devices.hh" #include "modules/network_status.hh" #include "modules/notifications.hh" #include "modules/os.hh" @@ -51,14 +52,15 @@ namespace SSC { class Core { public: - #if !SOCKET_RUNTIME_PLATFORM_IOS - using ChildProcess = CoreChildProcess; - #endif + #if !SOCKET_RUNTIME_PLATFORM_IOS + using ChildProcess = CoreChildProcess; + #endif using DNS = CoreDNS; using Diagnostics = CoreDiagnostics; using FS = CoreFS; using Conduit = CoreConduit; using Geolocation = CoreGeolocation; + using MediaDevices = CoreMediaDevices; using NetworkStatus = CoreNetworkStatus; using Notifications = CoreNotifications; using OS = CoreOS; @@ -112,6 +114,7 @@ namespace SSC { FS fs; Conduit conduit; Geolocation geolocation; + MediaDevices mediaDevices; NetworkStatus networkStatus; Notifications notifications; OS os; @@ -167,6 +170,7 @@ namespace SSC { dns(this), fs(this), geolocation(this), + mediaDevices(this), networkStatus(this), notifications(this), os(this), diff --git a/src/core/modules/permissions.cc b/src/core/modules/permissions.cc index 7e2f71d1c1..50e26c77b3 100644 --- a/src/core/modules/permissions.cc +++ b/src/core/modules/permissions.cc @@ -164,10 +164,81 @@ namespace SSC { #endif } - if (name == "camera" || name == "microphone") { - #if SOCKET_RUNTIME_PLATFORM_APPLE - #elif SOCKET_RUNTIME_PLATFORM_ANDROID + if (name == "camera") { + JSON::Object json; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasCameraPermission = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.CAMERA") + ); + + if (!hasCameraPermission) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + } + + callback(seq, json, Post{}); + #else + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + callback(seq, json, Post{}); + #endif + } + + if (name == "microphone") { + JSON::Object json; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasRecordAudioPermission = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.RECORD_AUDIO") + ); + + if (!hasRecordAudioPermission) { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + } + + callback(seq, json, Post{}); #else + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "prompt"}} + } + }; + callback(seq, json, Post{}); #endif } }); @@ -435,10 +506,131 @@ namespace SSC { #endif } - if (name == "camera" || name == "microphone") { - #if SOCKET_RUNTIME_PLATFORM_APPLE - #elif SOCKET_RUNTIME_PLATFORM_ANDROID + if (name == "camera") { + JSON::Object json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"} + }} + }; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasCameraPermission = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.CAMERA") + ); + + if (!hasCameraPermission) { + CoreMediaDevices::PermissionChangeObserver observer; + auto permissions = attachment.env->NewObjectArray( + 1, + attachment.env->FindClass("java/lang/String"), + 0 + ); + + attachment.env->SetObjectArrayElement( + permissions, + 0, + attachment.env->NewStringUTF("android.permission.CAMERA") + ); + + this->core->mediaDevices.addPermissionChangeObserver(observer, [=](JSON::Object result) mutable { + if (result.get("name").str() == "camera") { + JSON::Object json = JSON::Object::Entries { + {"data", result} + }; + callback(seq, json, Post{}); + this->core->dispatchEventLoop([=]() { + this->core->mediaDevices.removePermissionChangeObserver(observer); + }); + } + }); + + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + this->core->platform.activity, + "requestPermissions", + "([Ljava/lang/String;)V", + permissions + ); + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + callback(seq, json, Post{}); + } + #else + callback(seq, json, Post{}); + #endif + } + + if (name == "microphone") { + JSON::Object json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "denied"} + }} + }; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + const auto attachment = Android::JNIEnvironmentAttachment(this->core->platform.jvm); + // `activity.checkPermission(permission)` + const auto hasRecordAudioPermission = CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + this->core->platform.activity, + "checkPermission", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF("android.permission.RECORD_AUDIO") + ); + + if (!hasRecordAudioPermission) { + CoreMediaDevices::PermissionChangeObserver observer; + auto permissions = attachment.env->NewObjectArray( + 1, + attachment.env->FindClass("java/lang/String"), + 0 + ); + + attachment.env->SetObjectArrayElement( + permissions, + 0, + attachment.env->NewStringUTF("android.permission.RECORD_AUDIO") + ); + + this->core->mediaDevices.addPermissionChangeObserver(observer, [=](JSON::Object result) mutable { + if (result.get("name").str() == "microphone") { + JSON::Object json = JSON::Object::Entries { + {"data", result} + }; + callback(seq, json, Post{}); + this->core->dispatchEventLoop([=]() { + this->core->mediaDevices.removePermissionChangeObserver(observer); + }); + } + }); + + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + this->core->platform.activity, + "requestPermissions", + "([Ljava/lang/String;)V", + permissions + ); + } else { + json = JSON::Object::Entries { + {"data", JSON::Object::Entries { + {"state", "granted"}} + } + }; + callback(seq, json, Post{}); + } #else + callback(seq, json, Post{}); #endif } }); From 76523a33a475bd6b3a619029cddff0db6de82406 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 26 Jul 2024 16:07:23 +0200 Subject: [PATCH 1040/1178] refactor(app): handle 'camera' and 'microphone' permissions on android application --- src/app/app.cc | 9 +++++++++ src/app/app.kt | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/app/app.cc b/src/app/app.cc index 07af39d9ed..ba28f9e551 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -1339,12 +1339,21 @@ extern "C" { if (name == "geolocation") { app->core->geolocation.permissionChangeObservers.dispatch(JSON::Object::Entries { + {"name", name}, {"state", state} }); } if (name == "notification") { app->core->notifications.permissionChangeObservers.dispatch(JSON::Object::Entries { + {"name", name}, + {"state", state} + }); + } + + if (name == "camera" || name == "microphone") { + app->core->mediaDevices.permissionChangeObservers.dispatch(JSON::Object::Entries { + {"name", name}, {"state", state} }); } diff --git a/src/app/app.kt b/src/app/app.kt index 161265004b..089d6253a5 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -404,6 +404,14 @@ open class AppActivity : WindowManagerActivity() { "android.permission.POST_NOTIFICATIONS" -> { name = "notifications" } + + "android.permission.CAMERA" -> { + name = "camera" + } + + "android.permission.RECORD_AUDIO" -> { + name = "microphone" + } } if (seen.contains(name)) { From 1ee214e8e01ce91a2eced320b1c29e31af959e59 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 26 Jul 2024 16:07:57 +0200 Subject: [PATCH 1041/1178] refactor(api/internal/permissions.js): handle 'camera' and 'microphone' on android for Permissions API --- api/internal/permissions.js | 48 +++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/api/internal/permissions.js b/api/internal/permissions.js index 428838efad..c67ad072e9 100644 --- a/api/internal/permissions.js +++ b/api/internal/permissions.js @@ -260,7 +260,11 @@ export async function query (descriptor, options) { delete options.signal } - if (name === 'notifications' || name === 'geolocation') { + if ( + name === 'notifications' || + name === 'geolocation' || + (isAndroid && (name === 'camera' || name === 'microphone')) + ) { const result = await ipc.request('permissions.query', { name }, { signal }) if (result.err) { @@ -325,27 +329,29 @@ export async function request (descriptor, options) { return new PermissionStatus(name, 'granted', options) } - const constraints = { video: false, audio: false } - if (name === 'camera') { - constraints.video = true - delete constraints.audio - } else if (name === 'microphone') { - constraints.audio = true - delete constraints.video - } - - try { - const stream = await globalThis.navigator.mediaDevices.getUserMedia(constraints) - const tracks = await stream.getTracks() - for (const track of tracks) { - await track.stop() + if (!isAndroid) { + const constraints = { video: false, audio: false } + if (name === 'camera') { + constraints.video = true + delete constraints.audio + } else if (name === 'microphone') { + constraints.audio = true + delete constraints.video } - return new PermissionStatus(name, 'granted', options) - } catch (err) { - if (err.name === 'NotAllowedError') { - return new PermissionStatus(name, 'denied', options) - } else { - throw err + + try { + const stream = await globalThis.navigator.mediaDevices.getUserMedia(constraints) + const tracks = await stream.getTracks() + for (const track of tracks) { + await track.stop() + } + return new PermissionStatus(name, 'granted', options) + } catch (err) { + if (err.name === 'NotAllowedError') { + return new PermissionStatus(name, 'denied', options) + } else { + throw err + } } } } From e19628f250cf97ba7dee888d00392b7a6f62db83 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:48:06 +0200 Subject: [PATCH 1042/1178] feat(platform/android): introduce 'ContentResolver' platform API --- src/platform/android.hh | 47 +-- src/platform/android/content_resolver.cc | 374 +++++++++++++++++++++++ src/platform/android/content_resolver.hh | 46 +++ src/platform/android/environment.hh | 3 +- src/platform/android/looper.hh | 3 +- src/platform/android/mime.hh | 2 +- src/platform/android/native.hh | 12 + src/platform/android/string_wrap.hh | 5 +- src/platform/android/types.hh | 46 +++ src/platform/platform.hh | 12 +- 10 files changed, 492 insertions(+), 58 deletions(-) create mode 100644 src/platform/android/content_resolver.cc create mode 100644 src/platform/android/content_resolver.hh create mode 100644 src/platform/android/native.hh create mode 100644 src/platform/android/types.hh diff --git a/src/platform/android.hh b/src/platform/android.hh index e032e38c74..a56f10b69c 100644 --- a/src/platform/android.hh +++ b/src/platform/android.hh @@ -1,54 +1,12 @@ #ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_H #define SOCKET_RUNTIME_PLATFORM_ANDROID_H -#include "platform.hh" - -#if SOCKET_RUNTIME_PLATFORM_ANDROID +#include "android/content_resolver.hh" #include "android/environment.hh" #include "android/looper.hh" #include "android/mime.hh" #include "android/string_wrap.hh" - -namespace SSC::Android { - /** - * An Android AssetManager NDK type. - */ - using AssetManager = ::AAssetManager; - - /** - * An Android AssetManager Asset NDK type. - */ - using Asset = ::AAsset; - - /** - * An Android AssetManager AssetDirectory NDK type. - */ - using AssetDirectory = ::AAssetDir; - - /** - * An opaque `Activity` instance. - */ - using Activity = ::jobject; - - /** - * An opaque `Application` instance. - */ - using Application = ::jobject; - - /** - * A container that holds Android OS build information. - */ - struct BuildInformation { - String brand; - String device; - String fingerprint; - String hardware; - String model; - String manufacturer; - String product; - }; -} -#endif +#include "android/types.hh" /** * A macro to help define an Android external package class method @@ -73,5 +31,4 @@ namespace SSC::Android { ); \ (void) 0; \ }) - #endif diff --git a/src/platform/android/content_resolver.cc b/src/platform/android/content_resolver.cc new file mode 100644 index 0000000000..2d20829014 --- /dev/null +++ b/src/platform/android/content_resolver.cc @@ -0,0 +1,374 @@ +#include "../../core/resource.hh" +#include "../../core/url.hh" + +#include "content_resolver.hh" + +namespace SSC::Android { + bool ContentResolver::isDocumentURI (const String& uri) { + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + return CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + platform, + "isDocumentURI", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF(uri.c_str()) + ); + } + + bool ContentResolver::isContentURI (const String& uri) { + const auto url = URL(uri); + return url.scheme == "content" || url.scheme == "android.resource"; + } + + bool ContentResolver::isExternalStorageDocumentURI (const String& uri) { + const auto url = URL(uri); + return url.hostname == "com.android.externalstorage.documents"; + } + + bool ContentResolver::isDownloadsDocumentURI (const String& uri) { + const auto url = URL(uri); + return url.hostname == "com.android.providers.downloads.documents"; + } + + bool ContentResolver::isMediaDocumentURI (const String& uri) { + const auto url = URL(uri); + return url.hostname == "com.android.providers.media.documents"; + } + + bool ContentResolver::isPhotosURI (const String& uri) { + const auto url = URL(uri); + return url.hostname == "com.android.providers.media.documents"; + } + + String ContentResolver::getContentType (const String& uri) { + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + const auto contentTypeString = (jstring) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "getContentType", + "(Ljava/lang/String;)Ljava/lang/String;", + attachment.env->NewStringUTF(uri.c_str()) + ); + + return StringWrap(attachment.env, contentTypeString).str(); + } + + String ContentResolver::getPathnameFromURI (const String& uri) { + const auto url = URL(uri); + + if (url.scheme == "file") { + return url.pathname; + } + + if (this->isDocumentURI(uri)) { + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + const auto documentIDString = (jstring) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "getDocumentID", + "(Ljava/lang/String;)Ljava/lang/String;", + attachment.env->NewStringUTF(uri.c_str()) + ); + + const auto documentID = StringWrap(attachment.env, documentIDString).str(); + + if (this->isExternalStorageDocumentURI(uri)) { + const auto externalStorage = FileResource::getExternalAndroidStorageDirectory(); + const auto parts = split(documentID, ":"); + const auto type = parts[0]; + + if (type == "primary" && parts.size() > 1) { + return externalStorage.string() + "/" + parts[1]; + } + } + + if (this->isDownloadsDocumentURI(uri)) { + const auto contentURIString = (jstring) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "getContentURI", + "(Ljava/lang/String;J)Ljava/lang/String;", + attachment.env->NewStringUTF("content://downloads/public_downloads"), + std::stol(documentID) + ); + + const auto contentURI = StringWrap(attachment.env, contentURIString).str(); + + return this->getPathnameFromContentURIDataColumn(contentURI); + } + + if (this->isMediaDocumentURI(uri)) { + const auto parts = split(documentID, ":"); + const auto type = parts[0]; + + if (parts.size() > 1) { + const auto id = parts[1]; + const auto contentURI = this->getExternalContentURIForType(type); + + if (contentURI.size() > 0) { + return this->getPathnameFromContentURIDataColumn(contentURI, id); + } + } + } + } + + if (url.scheme == "content") { + if (this->isPhotosURI(uri)) { + const auto parts = split(url.pathname, '/'); + if (parts.size() > 0) { + return parts[parts.size() - 1]; + } + } + + return this->getPathnameFromContentURIDataColumn(uri); + } + + + return ""; + } + + String ContentResolver::getPathnameFromContentURIDataColumn ( + const String& uri, + const String& id + ) { + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + const auto result = (jstring) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "getPathnameFromContentURIDataColumn", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + attachment.env->NewStringUTF(uri.c_str()), + attachment.env->NewStringUTF(id.c_str()) + ); + + return StringWrap(attachment.env, result).str(); + } + + String ContentResolver::getExternalContentURIForType (const String& type) { + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + const auto result = (jstring) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "getExternalContentURIForType", + "(Ljava/lang/String;)Ljava/lang/String;", + attachment.env->NewStringUTF(type.c_str()) + ); + + return StringWrap(attachment.env, result).str(); + } + + Vector<String> ContentResolver::getPathnameEntriesFromContentURI ( + const String& uri + ) { + Vector<String> entries; + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + const auto results = (jobjectArray) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "getPathnameEntriesFromContentURI", + "(Ljava/lang/String;)[Ljava/lang/String;", + attachment.env->NewStringUTF(uri.c_str()) + ); + + const auto length = attachment.env->GetArrayLength(results); + + for (int i = 0; i < length; ++i) { + const auto result = (jstring) attachment.env->GetObjectArrayElement(results, i); + const auto pathname = attachment.env->GetStringUTFChars(result, nullptr); + if (pathname != nullptr) { + entries.push_back(pathname); + attachment.env->ReleaseStringUTFChars(result, pathname); + } + } + + return entries; + } + + bool ContentResolver::hasAccess (const String& uri) { + if (!uri.starts_with("content:") & !uri.starts_with("android.resource:")) { + return false; + } + + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + return CallClassMethodFromAndroidEnvironment( + attachment.env, + Boolean, + platform, + "hasContentResolverAccess", + "(Ljava/lang/String;)Z", + attachment.env->NewStringUTF(uri.c_str()) + ); + } + + ContentResolver::FileDescriptor ContentResolver::openFileDescriptor ( + const String& uri, + off_t* offset, + off_t* length + ) { + if (!uri.starts_with("content:") & !uri.starts_with("android.resource:")) { + return nullptr; + } + + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->activity, + "getAppPlatform", + "()Lsocket/runtime/app/AppPlatform;" + ); + + const auto fileDescriptor = CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + platform, + "openContentResolverFileDescriptor", + "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;", + attachment.env->NewStringUTF(uri.c_str()) + ); + + if (fileDescriptor != nullptr) { + if (offset != nullptr) { + *offset = CallClassMethodFromAndroidEnvironment( + attachment.env, + Long, + fileDescriptor, + "getStartOffset", + "()J" + ); + } + + if (length != nullptr) { + *length = CallClassMethodFromAndroidEnvironment( + attachment.env, + Long, + fileDescriptor, + "getLength", + "()J" + ); + } + + return attachment.env->NewGlobalRef(fileDescriptor); + } + + return nullptr; + } + + bool ContentResolver::closeFileDescriptor (ContentResolver::FileDescriptor fileDescriptor) { + if (fileDescriptor == nullptr) { + return false; + } + + const auto attachment = JNIEnvironmentAttachment(this->jvm); + CallVoidClassMethodFromAndroidEnvironment( + attachment.env, + fileDescriptor, + "close", + "()V" + ); + + attachment.env->DeleteGlobalRef(fileDescriptor); + + return true; + } + + size_t ContentResolver::getFileDescriptorLength (FileDescriptor fileDescriptor) { + if (fileDescriptor == nullptr) { + return -EINVAL; + } + + const auto attachment = JNIEnvironmentAttachment(this->jvm); + return CallClassMethodFromAndroidEnvironment( + attachment.env, + Long, + fileDescriptor, + "getLength", + "()J" + ); + } + + size_t ContentResolver::getFileDescriptorOffset (FileDescriptor fileDescriptor) { + if (fileDescriptor == nullptr) { + return -EINVAL; + } + + const auto attachment = JNIEnvironmentAttachment(this->jvm); + return CallClassMethodFromAndroidEnvironment( + attachment.env, + Long, + fileDescriptor, + "getOffset", + "()J" + ); + } + + int ContentResolver::getFileDescriptorFD (FileDescriptor fileDescriptor) { + if (fileDescriptor == nullptr) { + return -EINVAL; + } + + const auto attachment = JNIEnvironmentAttachment(this->jvm); + const auto parcel = CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + fileDescriptor, + "getParcelFileDescriptor", + "()Landroid/os/ParcelFileDescriptor;" + ); + + return CallClassMethodFromAndroidEnvironment( + attachment.env, + Int, + parcel, + "getFd", + "()I" + ); + } +} diff --git a/src/platform/android/content_resolver.hh b/src/platform/android/content_resolver.hh new file mode 100644 index 0000000000..d966ba2ad5 --- /dev/null +++ b/src/platform/android/content_resolver.hh @@ -0,0 +1,46 @@ +#ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_CONTENT_RESOLVER_H +#define SOCKET_RUNTIME_PLATFORM_ANDROID_CONTENT_RESOLVER_H + +#include "environment.hh" +#include "types.hh" + +namespace SSC::Android { + class ContentResolver { + public: + using FileDescriptor = jobject; + + Activity activity; + JVMEnvironment jvm; + ContentResolver () = default; + + bool isDocumentURI (const String& uri); + bool isContentURI (const String& uri); + bool isExternalStorageDocumentURI (const String& uri); + bool isDownloadsDocumentURI (const String& uri); + bool isMediaDocumentURI (const String& uri); + bool isPhotosURI (const String& uri); + + String getContentType (const String& url); + String getExternalContentURIForType (const String& type); + String getPathnameFromURI (const String& uri); + String getPathnameFromContentURIDataColumn ( + const String& uri, + const String& id = nullptr + ); + + Vector<String> getPathnameEntriesFromContentURI (const String& uri); + + bool hasAccess (const String& uri); + FileDescriptor openFileDescriptor ( + const String& uri, + off_t* offset = nullptr, + off_t* length = nullptr + ); + + bool closeFileDescriptor (FileDescriptor fileDescriptor); + size_t getFileDescriptorLength (FileDescriptor fileDescriptor); + size_t getFileDescriptorOffset (FileDescriptor fileDescriptor); + int getFileDescriptorFD (FileDescriptor fileDescriptor); + }; +} +#endif diff --git a/src/platform/android/environment.hh b/src/platform/android/environment.hh index 43c7d40ced..045ef17d12 100644 --- a/src/platform/android/environment.hh +++ b/src/platform/android/environment.hh @@ -1,7 +1,8 @@ #ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_ENVIRONMENT_H #define SOCKET_RUNTIME_PLATFORM_ANDROID_ENVIRONMENT_H -#include "../platform.hh" +#include "../types.hh" +#include "native.hh" /** * Gets class for object for `self` from `env`. diff --git a/src/platform/android/looper.hh b/src/platform/android/looper.hh index 1a0bfe6109..a934427298 100644 --- a/src/platform/android/looper.hh +++ b/src/platform/android/looper.hh @@ -1,8 +1,9 @@ #ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_LOOPER_H #define SOCKET_RUNTIME_PLATFORM_ANDROID_LOOPER_H -#include "../platform.hh" +#include "../types.hh" #include "environment.hh" +#include "native.hh" namespace SSC::Android { struct Looper { diff --git a/src/platform/android/mime.hh b/src/platform/android/mime.hh index 58a5f049f6..c7cbe2a3d1 100644 --- a/src/platform/android/mime.hh +++ b/src/platform/android/mime.hh @@ -1,7 +1,7 @@ #ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_MIME_H #define SOCKET_RUNTIME_PLATFORM_ANDROID_MIME_H -#include "../platform.hh" +#include "../types.hh" #include "environment.hh" namespace SSC::Android { diff --git a/src/platform/android/native.hh b/src/platform/android/native.hh new file mode 100644 index 0000000000..86382d9b6f --- /dev/null +++ b/src/platform/android/native.hh @@ -0,0 +1,12 @@ +#ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_NATIVE_H +#define SOCKET_RUNTIME_PLATFORM_ANDROID_NATIVE_H +#if defined(__ANDROID__) +// Java Native Interface +// @see https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html +#include <jni.h> +#include <android/asset_manager.h> +#include <android/asset_manager_jni.h> +#include <android/log.h> +#include <android/looper.h> +#endif +#endif diff --git a/src/platform/android/string_wrap.hh b/src/platform/android/string_wrap.hh index 6553d433ec..dcff37dc24 100644 --- a/src/platform/android/string_wrap.hh +++ b/src/platform/android/string_wrap.hh @@ -1,9 +1,9 @@ #ifndef SOCKET_RUNTIME_ANDROID_STRING_WRAP_H #define SOCKET_RUNTIME_ANDROID_STRING_WRAP_H -#include "../platform.hh" +#include "../types.hh" +#include "native.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID namespace SSC::Android { /** * A container for a JNI string (jstring). @@ -40,4 +40,3 @@ namespace SSC::Android { }; } #endif -#endif diff --git a/src/platform/android/types.hh b/src/platform/android/types.hh new file mode 100644 index 0000000000..e24e0bb10a --- /dev/null +++ b/src/platform/android/types.hh @@ -0,0 +1,46 @@ +#ifndef SOCKET_RUNTIME_PLATFORM_ANDROID_TYPES_H +#define SOCKET_RUNTIME_PLATFORM_ANDROID_TYPES_H + +#include "../types.hh" +#include "native.hh" + +namespace SSC::Android { + /** + * An Android AssetManager NDK type. + */ + using AssetManager = ::AAssetManager; + + /** + * An Android AssetManager Asset NDK type. + */ + using Asset = ::AAsset; + + /** + * An Android AssetManager AssetDirectory NDK type. + */ + using AssetDirectory = ::AAssetDir; + + /** + * An opaque `Activity` instance. + */ + using Activity = ::jobject; + + /** + * An opaque `Application` instance. + */ + using Application = ::jobject; + + /** + * A container that holds Android OS build information. + */ + struct BuildInformation { + String brand; + String device; + String fingerprint; + String hardware; + String model; + String manufacturer; + String product; + }; +} +#endif diff --git a/src/platform/platform.hh b/src/platform/platform.hh index 2f3d6998ba..f09e395ac5 100644 --- a/src/platform/platform.hh +++ b/src/platform/platform.hh @@ -52,13 +52,7 @@ // Android (Linux) #if defined(__ANDROID__) -// Java Native Interface -// @see https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html -#include <jni.h> -#include <android/asset_manager.h> -#include <android/asset_manager_jni.h> -#include <android/log.h> -#include <android/looper.h> +#include "android/native.hh" #endif // `__ANDROID__` // Windows @@ -136,6 +130,10 @@ #include "string.hh" #include "types.hh" +#if SOCKET_RUNTIME_PLATFORM_ANDROID +#include "android.hh" +#endif + namespace SSC { struct RuntimePlatform { const String arch; From 73d57d90a309cd812242d13fe3b2946643a887e5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:49:01 +0200 Subject: [PATCH 1043/1178] refactor(core/modules/{fs,platform}): handle android assets/content in CoreFS --- src/core/modules/fs.cc | 751 ++++++++++++++++++++++++++++++----- src/core/modules/fs.hh | 33 +- src/core/modules/platform.cc | 12 + src/core/modules/platform.hh | 12 +- 4 files changed, 704 insertions(+), 104 deletions(-) diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index 0daef20a39..072ba3c5ad 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -1,7 +1,9 @@ +#include "../core.hh" #include "../headers.hh" -#include "../trace.hh" #include "../json.hh" -#include "../core.hh" +#include "../resource.hh" +#include "../trace.hh" + #include "fs.hh" namespace SSC { @@ -215,16 +217,26 @@ namespace SSC { } CoreFS::Descriptor::Descriptor (CoreFS* fs, ID id, const String& filename) - : resource(filename), + : resource(filename, { false, fs->core }), fs(fs), id(id) {} bool CoreFS::Descriptor::isDirectory () const { + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (this->isAndroidAssetDirectory) { + return true; + } + #endif return this->dir != nullptr; } bool CoreFS::Descriptor::isFile () const { + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (this->androidAsset != nullptr) { + return true; + } + #endif return this->fd > 0 && this->dir == nullptr; } @@ -299,24 +311,103 @@ namespace SSC { const String& path, int mode, const CoreModule::Callback& callback - ) const { - this->core->dispatchEventLoop([=, this]() { + ) { + this->core->dispatchEventLoop([=, this]() mutable { auto filename = path.c_str(); auto loop = &this->core->eventLoop; - auto ctx = new RequestContext(seq, callback); + auto desc = std::make_shared<Descriptor>(this, 0, filename); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->resource.url.scheme == "socket") { + if (desc->resource.access(mode) == mode) { + const auto json = JSON::Object::Entries { + {"source", "fs.access"}, + {"data", JSON::Object::Entries { + {"mode", mode}, + }} + }; + + return callback(seq, json, Post{}); + } else if (mode == R_OK || mode == F_OK) { + auto name = desc->resource.name; + + if (name.starts_with("/")) { + name = name.substr(1); + } else if (name.starts_with("./")) { + name = name.substr(2); + } + + const auto attachment = Android::JNIEnvironmentAttachment(desc->fs->core->platform.jvm); + const auto assetManager = CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + this->core->platform.activity, + "getAssetManager", + "()Landroid/content/res/AssetManager;" + ); + + const auto entries = (jobjectArray) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + assetManager, + "list", + "(Ljava/lang/String;)[Ljava/lang/String;", + attachment.env->NewStringUTF(name.c_str()) + ); + + const auto length = attachment.env->GetArrayLength(entries); + if (length > 0) { + const auto json = JSON::Object::Entries { + {"source", "fs.access"}, + {"data", JSON::Object::Entries { + {"mode", mode}, + }} + }; + + return callback(seq, json, Post{}); + } + } + } else if ( + desc->resource.url.scheme == "content" || + desc->resource.url.scheme == "android.resource" + ) { + if (this->core->platform.contentResolver.hasAccess(desc->resource.url.str())) { + const auto json = JSON::Object::Entries { + {"source", "fs.access"}, + {"data", JSON::Object::Entries { + {"mode", mode}, + }} + }; + + return callback(seq, json, Post{}); + } + } + #endif + + auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; auto err = uv_fs_access(loop, req, filename, mode, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; if (uv_fs_get_result(req) < 0) { - json = JSON::Object::Entries { - {"source", "fs.access"}, - {"err", JSON::Object::Entries { - {"code", req->result}, - {"message", String(uv_strerror((int) req->result))} - }} - }; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (ctx->descriptor->resource.access(req->flags) == req->flags) { + json = JSON::Object::Entries { + {"source", "fs.access"}, + {"data", JSON::Object::Entries { + {"mode", req->flags}, + }} + }; + } else + #endif + { + json = JSON::Object::Entries { + {"source", "fs.access"}, + {"err", JSON::Object::Entries { + {"code", req->result}, + {"message", String(uv_strerror((int) req->result))} + }} + }; + } } else { json = JSON::Object::Entries { {"source", "fs.access"}, @@ -405,7 +496,7 @@ namespace SSC { ) const { core->dispatchEventLoop([=, this]() { const auto ctx = new RequestContext(seq, callback); - const auto uv_callback = [](uv_fs_t* req) { + const auto err = uv_fs_chown(&core->eventLoop, &ctx->req, path.c_str(), uid, gid, [](uv_fs_t* req) { auto ctx = static_cast<RequestContext*>(req->data); auto json = JSON::Object{}; @@ -428,15 +519,7 @@ namespace SSC { ctx->callback(ctx->seq, json, Post {}); delete ctx; - }; - - const auto err = uv_fs_chown( - &core->eventLoop, - &ctx->req, - path.c_str(), - uid, gid, - uv_callback - ); + }); if (err < 0) { auto json = JSON::Object::Entries { @@ -461,7 +544,7 @@ namespace SSC { ) const { core->dispatchEventLoop([=, this]() { const auto ctx = new RequestContext(seq, callback); - const auto uv_callback = [](uv_fs_t* req) { + const auto err = uv_fs_lchown(&core->eventLoop, &ctx->req, path.c_str(), uid, gid, [](uv_fs_t* req) { auto ctx = static_cast<RequestContext*>(req->data); auto json = JSON::Object{}; @@ -484,16 +567,7 @@ namespace SSC { ctx->callback(ctx->seq, json, Post {}); delete ctx; - }; - - const auto err = uv_fs_lchown( - &core->eventLoop, - &ctx->req, - path.c_str(), - uid, - gid, - uv_callback - ); + }); if (err < 0) { auto json = JSON::Object::Entries { @@ -531,6 +605,44 @@ namespace SSC { return callback(seq, json, Post{}); } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->androidAsset != nullptr) { + Lock lock(this->mutex); + AAsset_close(desc->androidAsset); + desc->androidAsset = nullptr; + const auto json = JSON::Object::Entries { + {"source", "fs.close"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"fd", desc->fd} + }} + }; + + this->removeDescriptor(desc->id); + return callback(seq, json, Post{}); + } else if ( + desc->resource.url.scheme == "content" || + desc->resource.url.scheme == "android.resource" + ) { + Lock lock(this->mutex); + this->core->platform.contentResolver.closeFileDescriptor( + desc->androidContent + ); + + desc->androidContent = nullptr; + const auto json = JSON::Object::Entries { + {"source", "fs.close"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"fd", desc->fd} + }} + }; + + this->removeDescriptor(desc->id); + return callback(seq, json, Post{}); + } + #endif + auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; @@ -591,6 +703,78 @@ namespace SSC { this->core->dispatchEventLoop([=, this]() { auto filename = path.c_str(); auto desc = std::make_shared<Descriptor>(this, id, filename); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->resource.url.scheme == "socket") { + auto assetManager = FileResource::getSharedAndroidAssetManager(); + desc->androidAsset = AAssetManager_open( + assetManager, + desc->resource.name.c_str(), + AASSET_MODE_RANDOM + ); + + desc->fd = AAsset_openFileDescriptor( + desc->androidAsset, + &desc->androidAssetOffset, + &desc->androidAssetLength + ); + + const auto json = JSON::Object::Entries { + {"source", "fs.open"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"fd", desc->fd} + }} + }; + + // insert into `descriptors` map + Lock lock(desc->fs->mutex); + this->descriptors.insert_or_assign(desc->id, desc); + return callback(seq, json, Post{}); + } else if ( + desc->resource.url.scheme == "content" || + desc->resource.url.scheme == "android.resource" + ) { + auto fileDescriptor = this->core->platform.contentResolver.openFileDescriptor( + desc->resource.url.str(), + &desc->androidContentOffset, + &desc->androidContentLength + ); + + if (fileDescriptor == nullptr) { + const auto json = JSON::Object::Entries { + {"source", "fs.open"}, + {"err", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"type", "NotFoundError"}, + {"message", "Content does not exist at given URI"} + }} + }; + + return callback(seq, json, Post{}); + } + + desc->fd = this->core->platform.contentResolver.getFileDescriptorFD( + fileDescriptor + ); + + desc->androidContent = fileDescriptor; + + const auto json = JSON::Object::Entries { + {"source", "fs.open"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"fd", desc->fd} + }} + }; + + // insert into `descriptors` map + Lock lock(desc->fs->mutex); + this->descriptors.insert_or_assign(desc->id, desc); + return callback(seq, json, Post{}); + } + #endif + auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; @@ -600,14 +784,43 @@ namespace SSC { auto json = JSON::Object {}; if (uv_fs_get_result(req) < 0) { - json = JSON::Object::Entries { - {"source", "fs.open"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(desc->id)}, - {"code", req->result}, - {"message", String(uv_strerror((int) req->result))} - }} - }; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (ctx->descriptor->resource.isAndroidLocalAsset()) { + auto assetManager = FileResource::getSharedAndroidAssetManager(); + desc->androidAsset = AAssetManager_open( + assetManager, + ctx->descriptor->resource.name.c_str(), + AASSET_MODE_RANDOM + ); + + desc->fd = AAsset_openFileDescriptor( + desc->androidAsset, + &desc->androidAssetOffset, + &desc->androidAssetLength + ); + json = JSON::Object::Entries { + {"source", "fs.open"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"fd", desc->fd} + }} + }; + + // insert into `descriptors` map + Lock lock(desc->fs->mutex); + desc->fs->descriptors.insert_or_assign(desc->id, desc); + } else + #endif + { + json = JSON::Object::Entries { + {"source", "fs.open"}, + {"err", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"code", req->result}, + {"message", String(uv_strerror((int) req->result))} + }} + }; + } } else { json = JSON::Object::Entries { {"source", "fs.open"}, @@ -652,6 +865,87 @@ namespace SSC { this->core->dispatchEventLoop([=, this]() { auto filename = path.c_str(); auto desc = std::make_shared<Descriptor>(this, id, filename); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->resource.url.scheme == "socket") { + auto name = desc->resource.name; + + if (name.starts_with("/")) { + name = name.substr(1); + } else if (name.starts_with("./")) { + name = name.substr(2); + } + + const auto attachment = Android::JNIEnvironmentAttachment(desc->fs->core->platform.jvm); + const auto assetManager = CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + desc->fs->core->platform.activity, + "getAssetManager", + "()Landroid/content/res/AssetManager;" + ); + + const auto entries = (jobjectArray) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + assetManager, + "list", + "(Ljava/lang/String;)[Ljava/lang/String;", + attachment.env->NewStringUTF(name.c_str()) + ); + + const auto length = attachment.env->GetArrayLength(entries); + + for (int i = 0; i < length; ++i) { + const auto entry = (jstring) attachment.env->GetObjectArrayElement(entries, i); + const auto filename = attachment.env->GetStringUTFChars(entry, nullptr); + if (filename != nullptr) { + desc->androidAssetDirectoryEntries.push(filename); + attachment.env->ReleaseStringUTFChars(entry, filename); + } + } + + if (length > 0) { + desc->isAndroidAssetDirectory = true; + const auto json = JSON::Object::Entries { + {"source", "fs.opendir"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)} + }} + }; + + // insert into `descriptors` map + Lock lock(desc->fs->mutex); + desc->fs->descriptors.insert_or_assign(desc->id, desc); + return callback(seq, json, Post{}); + } + } else if ( + desc->resource.url.scheme == "content" || + desc->resource.url.scheme == "android.resource" + ) { + const auto entries = this->core->platform.contentResolver.getPathnameEntriesFromContentURI( + desc->resource.url.str() + ); + + for (const auto& entry : entries) { + desc->androidContentDirectoryEntries.push(entry); + } + + if (entries.size() > 0) { + desc->isAndroidContentDirectory = true; + const auto json = JSON::Object::Entries { + {"source", "fs.opendir"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)} + }} + }; + + // insert into `descriptors` map + Lock lock(desc->fs->mutex); + desc->fs->descriptors.insert_or_assign(desc->id, desc); + return callback(seq, json, Post{}); + } + } + #endif + auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; @@ -661,14 +955,69 @@ namespace SSC { auto json = JSON::Object {}; if (uv_fs_get_result(req) < 0) { - json = JSON::Object::Entries { - {"source", "fs.opendir"}, - {"err", JSON::Object::Entries { - {"id", std::to_string(desc->id)}, - {"code", req->result}, - {"message", String(uv_strerror((int) req->result))} - }} - }; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + auto name = ctx->descriptor->resource.name; + + if (name.starts_with("/")) { + name = name.substr(1); + } else if (name.starts_with("./")) { + name = name.substr(2); + } + + const auto attachment = Android::JNIEnvironmentAttachment(ctx->descriptor->fs->core->platform.jvm); + const auto assetManager = CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + ctx->descriptor->fs->core->platform.activity, + "getAssetManager", + "()Landroid/content/res/AssetManager;" + ); + + const auto entries = (jobjectArray) CallObjectClassMethodFromAndroidEnvironment( + attachment.env, + assetManager, + "list", + "(Ljava/lang/String;)[Ljava/lang/String;", + attachment.env->NewStringUTF(name.c_str()) + ); + + const auto length = attachment.env->GetArrayLength(entries); + + for (int i = 0; i < length; ++i) { + const auto entry = (jstring) attachment.env->GetObjectArrayElement(entries, i); + const auto filename = attachment.env->GetStringUTFChars(entry, nullptr); + if (filename != nullptr) { + desc->androidAssetDirectoryEntries.push(filename); + attachment.env->ReleaseStringUTFChars(entry, filename); + } + } + + if (length > 0) { + desc->isAndroidAssetDirectory = true; + } + + if (desc->isAndroidAssetDirectory) { + json = JSON::Object::Entries { + {"source", "fs.opendir"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)} + }} + }; + + // insert into `descriptors` map + Lock lock(desc->fs->mutex); + desc->fs->descriptors.insert_or_assign(desc->id, desc); + } else + #endif + { + json = JSON::Object::Entries { + {"source", "fs.opendir"}, + {"err", JSON::Object::Entries { + {"id", std::to_string(desc->id)}, + {"code", req->result}, + {"message", String(uv_strerror((int) req->result))} + }} + }; + } } else { json = JSON::Object::Entries { {"source", "fs.opendir"}, @@ -726,6 +1075,66 @@ namespace SSC { return callback(seq, json, Post{}); } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->isAndroidAssetDirectory) { + Vector<JSON::Any> entries; + + for ( + int i = 0; + i < nentries && i < desc->androidAssetDirectoryEntries.size(); + ++i + ) { + const auto name = desc->androidAssetDirectoryEntries.front(); + const auto encodedName = name.ends_with("/") + ? encodeURIComponent(name.substr(0, name.size() - 1)) + : encodeURIComponent(name); + + const auto entry = JSON::Object::Entries { + {"type", name.ends_with("/") ? 2 : 1}, + {"name", encodedName} + }; + + entries.push_back(entry); + desc->androidAssetDirectoryEntries.pop(); + } + + const auto json = JSON::Object::Entries { + {"source", "fs.readdir"}, + {"data", entries} + }; + + return callback(seq, json, Post{}); + } else if (desc->isAndroidContentDirectory) { + Vector<JSON::Any> entries; + + for ( + int i = 0; + i < nentries && i < desc->androidContentDirectoryEntries.size(); + ++i + ) { + const auto name = desc->androidContentDirectoryEntries.front(); + const auto encodedName = name.ends_with("/") + ? encodeURIComponent(name.substr(0, name.size() - 1)) + : encodeURIComponent(name); + + const auto entry = JSON::Object::Entries { + {"type", name.ends_with("/") ? 2 : 1}, + {"name", encodedName} + }; + + entries.push_back(entry); + desc->androidContentDirectoryEntries.pop(); + } + + const auto json = JSON::Object::Entries { + {"source", "fs.readdir"}, + {"data", entries} + }; + + return callback(seq, json, Post{}); + } + #endif + if (!desc->isDirectory()) { auto json = JSON::Object::Entries { {"source", "fs.readdir"}, @@ -821,6 +1230,36 @@ namespace SSC { return callback(seq, json, Post{}); } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->isAndroidAssetDirectory) { + Lock lock(this->mutex); + desc->isAndroidAssetDirectory = false; + desc->androidAssetDirectoryEntries = {}; + const auto json = JSON::Object::Entries { + {"source", "fs.closedir"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)} + }} + }; + + this->removeDescriptor(desc->id); + return callback(seq, json, Post{}); + } else if (desc->isAndroidContentDirectory) { + Lock lock(this->mutex); + desc->isAndroidContentDirectory = false; + desc->androidContentDirectoryEntries = {}; + const auto json = JSON::Object::Entries { + {"source", "fs.closedir"}, + {"data", JSON::Object::Entries { + {"id", std::to_string(desc->id)} + }} + }; + + this->removeDescriptor(desc->id); + return callback(seq, json, Post{}); + } + #endif + if (!desc->isDirectory()) { auto json = JSON::Object::Entries { {"source", "fs.closedir"}, @@ -990,10 +1429,58 @@ namespace SSC { return callback(seq, json, Post{}); } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->androidAsset != nullptr) { + const auto length = AAsset_getLength(desc->androidAsset); + + if (offset >= static_cast<size_t>(length)) { + const auto headers = Headers {{ + {"content-type" ,"application/octet-stream"}, + {"content-length", 0} + }}; + + Post post {0}; + post.id = rand64(); + post.body = std::make_shared<char[]>(size); + post.length = 0; + post.headers = headers.str(); + return callback(seq, JSON::Object{}, post); + } else { + if (size > length - offset) { + size = length - offset; + } + + offset += desc->androidAssetOffset; + } + } else if (desc->androidContent != nullptr) { + const auto length = desc->androidContentLength; + + if (offset >= static_cast<size_t>(length)) { + const auto headers = Headers {{ + {"content-type" ,"application/octet-stream"}, + {"content-length", 0} + }}; + + Post post {0}; + post.id = rand64(); + post.body = std::make_shared<char[]>(size); + post.length = 0; + post.headers = headers.str(); + return callback(seq, JSON::Object{}, post); + } else { + if (size > length - offset) { + size = length - offset; + } + + offset += desc->androidContentOffset; + } + } + #endif + + auto bytes = std::make_shared<char[]>(size); auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - SharedPointer<char[]> bytes = std::make_shared<char[]>(size); ctx->setBuffer(bytes, size); @@ -1051,7 +1538,7 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { - #if defined(__ANDROID__) + #if SOCKET_RUNTIME_PLATFORM_ANDROID auto json = JSON::Object::Entries { {"source", "fs.watch"}, {"err", JSON::Object::Entries { @@ -1153,6 +1640,22 @@ namespace SSC { return callback(seq, json, Post{}); } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->androidAsset != nullptr) { + auto json = JSON::Object::Entries { + {"source", "fs.write"}, + {"err", JSON::Object::Entries { + {"id", std::to_string(id)}, + {"code", "EPERM"}, + {"type", "NotAllowedError"}, + {"message", "Cannot write to an Android Asset file descriptor."} + }} + }; + + return callback(seq, json, Post{}); + } + #endif + auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; @@ -1206,11 +1709,40 @@ namespace SSC { const String& seq, const String& path, const CoreModule::Callback& callback - ) const { + ) { this->core->dispatchEventLoop([=, this]() { auto filename = path.c_str(); + auto desc = std::make_shared<Descriptor>(this, 0, filename); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->resource.isAndroidLocalAsset()) { + const auto json = JSON::Object::Entries { + {"source", "fs.stat"}, + {"data", JSON::Object::Entries { + {"st_mode", R_OK}, + {"st_size", desc->resource.size()} + }} + }; + + return callback(seq, json, Post{}); + } else if ( + desc->resource.url.scheme == "content" || + desc->resource.url.scheme == "android.resource" + ) { + const auto json = JSON::Object::Entries { + {"source", "fs.stat"}, + {"data", JSON::Object::Entries { + {"st_mode", R_OK}, + {"st_size", desc->resource.size()} + }} + }; + + return callback(seq, json, Post{}); + } + #endif + auto loop = &this->core->eventLoop; - auto ctx = new RequestContext(seq, callback); + auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; auto err = uv_fs_stat(loop, req, filename, [](uv_fs_t *req) { auto ctx = (RequestContext *) req->data; @@ -1253,7 +1785,7 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { - #if defined(__ANDROID__) + #if SOCKET_RUNTIME_PLATFORM_ANDROID auto json = JSON::Object::Entries { {"source", "fs.stopWatch"}, {"err", JSON::Object::Entries { @@ -1427,7 +1959,7 @@ namespace SSC { const String& seq, ID id, const CoreModule::Callback& callback - ) const { + ) { this->core->dispatchEventLoop([=, this]() { auto desc = getDescriptor(id); @@ -1445,6 +1977,20 @@ namespace SSC { return callback(seq, json, Post{}); } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->androidAsset != nullptr) { + const auto json = JSON::Object::Entries { + {"source", "fs.stat"}, + {"data", JSON::Object::Entries { + {"st_mode", R_OK}, + {"st_size", desc->resource.size()} + }} + }; + + return callback(seq, json, Post{}); + } + #endif + auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; @@ -1569,7 +2115,7 @@ namespace SSC { ) const { this->core->dispatchEventLoop([=, this]() { auto ctx = new RequestContext(seq, callback); - auto uv_callback = [](uv_fs_t* req) { + auto err = uv_fs_link(&core->eventLoop, &ctx->req, src.c_str(), dest.c_str(), [](uv_fs_t* req) { auto ctx = static_cast<RequestContext*>(req->data); auto json = JSON::Object{}; @@ -1592,10 +2138,7 @@ namespace SSC { ctx->callback(ctx->seq, json, Post {}); delete ctx; - }; - - auto err = uv_fs_link( - &core->eventLoop, &ctx->req, src.c_str(), dest.c_str(), uv_callback); + }); if (err < 0) { auto json = JSON::Object::Entries { @@ -1620,7 +2163,7 @@ namespace SSC { ) const { this->core->dispatchEventLoop([=, this]() { auto ctx = new RequestContext(seq, callback); - auto uv_callback = [](uv_fs_t* req) { + auto err = uv_fs_symlink(&core->eventLoop, &ctx->req, src.c_str(), dest.c_str(), flags, [](uv_fs_t* req) { auto ctx = static_cast<RequestContext*>(req->data); auto json = JSON::Object{}; @@ -1643,10 +2186,7 @@ namespace SSC { ctx->callback(ctx->seq, json, Post {}); delete ctx; - }; - - auto err = uv_fs_symlink( - &core->eventLoop, &ctx->req, src.c_str(), dest.c_str(), flags, uv_callback); + }); if (err < 0) { auto json = JSON::Object::Entries { @@ -1719,7 +2259,7 @@ namespace SSC { ) const { this->core->dispatchEventLoop([=, this]() { auto ctx = new RequestContext(seq, callback); - auto uv_callback = [](uv_fs_t* req) { + auto err = uv_fs_readlink(&core->eventLoop, &ctx->req, path.c_str(), [](uv_fs_t* req) { auto ctx = static_cast<RequestContext*>(req->data); auto json = JSON::Object{}; @@ -1742,14 +2282,7 @@ namespace SSC { ctx->callback(ctx->seq, json, Post {}); delete ctx; - }; - - auto err = uv_fs_readlink( - &core->eventLoop, - &ctx->req, - path.c_str(), - uv_callback - ); + }); if (err < 0) { auto json = JSON::Object::Entries { @@ -1769,21 +2302,41 @@ namespace SSC { const String& seq, const String& path, const CoreModule::Callback& callback - ) const { + ) { this->core->dispatchEventLoop([=, this]() { - auto ctx = new RequestContext(seq, callback); - auto uv_callback = [](uv_fs_t* req) { + auto filename = path.c_str(); auto desc = std::make_shared<Descriptor>(this, 0, filename); + auto ctx = new RequestContext(desc, seq, callback); + auto err = uv_fs_realpath(&core->eventLoop, &ctx->req, path.c_str(), [](uv_fs_t* req) { auto ctx = static_cast<RequestContext*>(req->data); auto json = JSON::Object{}; if (uv_fs_get_result(req) < 0) { - json = JSON::Object::Entries { - {"source", "fs.realpath"}, - {"err", JSON::Object::Entries { - {"code", uv_fs_get_result(req)}, - {"message", String(uv_strerror(uv_fs_get_result(req)))} - }} - }; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (ctx->descriptor->resource.isAndroidLocalAsset()) { + json = JSON::Object::Entries { + {"source", "fs.realpath"}, + {"data", JSON::Object::Entries { + {"path", ctx->descriptor->resource.path} + }} + }; + } else if (ctx->descriptor->resource.isAndroidContent()) { + json = JSON::Object::Entries { + {"source", "fs.realpath"}, + {"data", JSON::Object::Entries { + {"path", ctx->descriptor->resource.url.str()} + }} + }; + } else + #endif + { + json = JSON::Object::Entries { + {"source", "fs.realpath"}, + {"err", JSON::Object::Entries { + {"code", uv_fs_get_result(req)}, + {"message", String(uv_strerror(uv_fs_get_result(req)))} + }} + }; + } } else { json = JSON::Object::Entries { {"source", "fs.realpath"}, @@ -1795,10 +2348,7 @@ namespace SSC { ctx->callback(ctx->seq, json, Post {}); delete ctx; - }; - - auto err = uv_fs_realpath( - &core->eventLoop, &ctx->req, path.c_str(), uv_callback); + }); if (err < 0) { auto json = JSON::Object::Entries { @@ -1872,8 +2422,29 @@ namespace SSC { const String& pathB, int flags, const CoreModule::Callback& callback - ) const { + ) { this->core->dispatchEventLoop([=, this]() { + auto desc = std::make_shared<Descriptor>(this, 0, pathA); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (desc->resource.isAndroidLocalAsset() || desc->resource.isAndroidLocalAsset()) { + auto bytes = desc->resource.read(); + fs::remove(pathB); + std::ofstream stream(pathB); + stream << bytes; + stream.close(); + + auto json = JSON::Object::Entries { + {"source", "fs.copyFile"}, + {"data", JSON::Object::Entries { + {"result", 0} + }} + }; + + return callback(seq, json, Post{}); + } + #endif + auto loop = &this->core->eventLoop; auto ctx = new RequestContext(seq, callback); auto req = &ctx->req; diff --git a/src/core/modules/fs.hh b/src/core/modules/fs.hh index 0a51ec43b2..f629b92596 100644 --- a/src/core/modules/fs.hh +++ b/src/core/modules/fs.hh @@ -2,14 +2,10 @@ #define SOCKET_RUNTIME_CORE_MODULE_FS_H #include "../file_system_watcher.hh" -#include "../resource.hh" #include "../module.hh" +#include "../resource.hh" #include "../trace.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../../platform/android.hh" -#endif - namespace SSC { class Core; class CoreFS : public CoreModule { @@ -26,6 +22,23 @@ namespace SSC { uv_file fd = 0; CoreFS* fs = nullptr; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + // asset state + Android::Asset* androidAsset = nullptr; + Queue<String> androidAssetDirectoryEntries; + Queue<String> androidContentDirectoryEntries; + Android::ContentResolver::FileDescriptor androidContent = nullptr; + // type predicates + bool isAndroidAssetDirectory = false; + bool isAndroidContentDirectory = false; + bool isAndroidContent = false; + // descriptor offsets + off_t androidAssetOffset = 0; + off_t androidAssetLength = 0; + off_t androidContentOffset = 0; + off_t androidContentLength = 0; + #endif + Descriptor (CoreFS* fs, ID id, const String& filename); bool isDirectory () const; bool isFile () const; @@ -101,7 +114,7 @@ namespace SSC { const String& path, int mode, const CoreModule::Callback& callback - ) const; + ); void chmod ( const String& seq, @@ -138,7 +151,7 @@ namespace SSC { const String& dst, int flags, const CoreModule::Callback& callback - ) const; + ); void closedir ( const String& seq, @@ -167,7 +180,7 @@ namespace SSC { const String& seq, ID id, const CoreModule::Callback& callback - ) const; + ); void fsync ( const String& seq, @@ -226,7 +239,7 @@ namespace SSC { const String& seq, const String& path, const CoreModule::Callback& callback - ) const; + ); void open ( const String& seq, @@ -282,7 +295,7 @@ namespace SSC { const String& seq, const String& path, const CoreModule::Callback& callback - ) const; + ); void stopWatch ( const String& seq, diff --git a/src/core/modules/platform.cc b/src/core/modules/platform.cc index f289ae495a..2e36e54d02 100644 --- a/src/core/modules/platform.cc +++ b/src/core/modules/platform.cc @@ -5,6 +5,18 @@ #include "platform.hh" namespace SSC { +#if SOCKET_RUNTIME_PLATFORM_ANDROID + void CorePlatform::configureAndroidContext ( + Android::JVMEnvironment jvm, + Android::Activity activity + ) { + this->jvm = jvm; + this->activity = activity; + this->contentResolver.activity = activity; + this->contentResolver.jvm = jvm; + } + +#endif void CorePlatform::event ( const String& seq, const String& event, diff --git a/src/core/modules/platform.hh b/src/core/modules/platform.hh index 5d0099594d..6fc80bd158 100644 --- a/src/core/modules/platform.hh +++ b/src/core/modules/platform.hh @@ -3,10 +3,6 @@ #include "../module.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../../platform/android.hh" -#endif - namespace SSC { class Core; class CorePlatform : public CoreModule { @@ -16,12 +12,20 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_ANDROID Android::JVMEnvironment jvm; Android::Activity activity = nullptr; + Android::ContentResolver contentResolver; #endif CorePlatform (Core* core) : CoreModule(core) {} + #if SOCKET_RUNTIME_PLATFORM_ANDROID + void configureAndroidContext ( + Android::JVMEnvironment jvm, + Android::Activity activity + ); + #endif + void event ( const String& seq, const String& event, From 42bc77c98ca30ba9139a401643ce037b130f9fe6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:49:38 +0200 Subject: [PATCH 1044/1178] refactor(window): clean up, handle async javascript, imrpove android window impl --- src/window/android.cc | 77 +++++++++++++++++++++++++++++++-------- src/window/apple.mm | 34 ++++++++++++++++- src/window/dialog.cc | 7 +--- src/window/dialog.kt | 40 ++++++++++++++++---- src/window/linux.cc | 85 +++++++++++++++++++++++++++++++++++++++++-- src/window/manager.kt | 13 +++++-- src/window/win.cc | 36 +++++++++++++++++- src/window/window.hh | 5 ++- src/window/window.kt | 12 ++++-- 9 files changed, 266 insertions(+), 43 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index 371dee2660..5c89f1d62f 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -1,5 +1,4 @@ #include "../app/app.hh" -#include "../platform/android.hh" #include "window.hh" @@ -96,10 +95,10 @@ namespace SSC { // TODO(@jwerle): figure out we'll go about making this possible } - void Window::eval (const String& source) { + void Window::eval (const String& source, const EvalCallback& callback) { const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - const auto sourceString = attachment.env->NewStringUTF(source.c_str()); + const auto token = std::to_string(rand64()); // `activity.evaluateJavaScript(index, source): Boolean` CallClassMethodFromAndroidEnvironment( @@ -107,12 +106,13 @@ namespace SSC { Boolean, app->activity, "evaluateJavaScript", - "(ILjava/lang/String;)Z", + "(ILjava/lang/String;Ljava/lang/String;)Z", this->index, - sourceString + attachment.env->NewStringUTF(source.c_str()), + attachment.env->NewStringUTF(token.c_str()) ); - attachment.env->DeleteLocalRef(sourceString); + this->evaluateJavaScriptCallbacks.insert_or_assign(token, callback); } void Window::show () { @@ -183,7 +183,6 @@ namespace SSC { void Window::navigate (const String& url) { const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - const auto urlString = attachment.env->NewStringUTF(url.c_str()); // `activity.navigateWindow(index, url): Boolean` const auto result = CallClassMethodFromAndroidEnvironment( @@ -193,11 +192,9 @@ namespace SSC { "navigateWindow", "(ILjava/lang/String;)Z", this->index, - urlString + attachment.env->NewStringUTF(url.c_str()) ); - attachment.env->DeleteLocalRef(urlString); - if (!result) { this->pendingNavigationLocation = url; } @@ -221,8 +218,6 @@ namespace SSC { void Window::setTitle (const String& title) { const auto app = App::sharedApplication(); const auto attachment = Android::JNIEnvironmentAttachment(app->jvm); - const auto titleString = attachment.env->NewStringUTF(title.c_str()); - // `activity.setWindowTitle(index, url): Boolean` CallClassMethodFromAndroidEnvironment( attachment.env, @@ -231,10 +226,8 @@ namespace SSC { "setWindowTitle", "(ILjava/lang/String;)Z", this->index, - titleString + attachment.env->NewStringUTF(title.c_str()) ); - - attachment.env->DeleteLocalRef(titleString); } Window::Size Window::getSize () { @@ -370,7 +363,7 @@ namespace SSC { attachment.env, app->activity, "setWindowBackgroundColor", - "(I)Z", + "(IJ)Z", this->index, color.pack() ); @@ -474,6 +467,58 @@ extern "C" { window->androidWindowRef = env->NewGlobalRef(self); } + void ANDROID_EXTERNAL(window, Window, onEvaluateJavascriptResult) ( + JNIEnv* env, + jobject self, + jint index, + jstring tokenString, + jstring resultString + ) { + const auto app = App::sharedApplication(); + + if (!app) { + return ANDROID_THROW(env, "Missing 'App' in environment"); + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + return ANDROID_THROW(env, "Invalid window index (%d) requested", index); + } + + const auto token = Android::StringWrap(env, tokenString).str(); + const auto result = Android::StringWrap(env, resultString).str(); + + Lock lock(window->mutex); + + if (!window->evaluateJavaScriptCallbacks.contains(token)) { + return; + } + + const auto callback = window->evaluateJavaScriptCallbacks.at(token); + + if (callback != nullptr) { + if (result == "null" || result == "undefined") { + callback(nullptr); + } else if (result == "true") { + callback(true); + } else if (result == "false") { + callback(false); + } else { + double number = 0.0f; + + try { + number = std::stod(result); + } catch (...) { + callback(result); + return; + } + + callback(number); + } + } + } + jstring ANDROID_EXTERNAL(window, Window, getPendingNavigationLocation) ( JNIEnv* env, jobject self, diff --git a/src/window/apple.mm b/src/window/apple.mm index a34d297971..80b23414ba 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -795,7 +795,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { this->eval(getEmitToRenderProcessJavaScript("window-hidden", "{}")); } - void Window::eval (const String& source) { + void Window::eval (const String& source, const EvalCallback& callback) { if (this->webview != nullptr) { [this->webview evaluateJavaScript: @(source.c_str()) @@ -803,6 +803,38 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { { if (error) { debug("JavaScriptError: %@", error); + + if (callback != nullptr) { + callback(JSON::Error([error UTF8String])); + } + + return; + } + + if (callback != nullptr) { + if ([result isKindOfClass: NSString.class]) { + const auto value = String([result UTF8String]); + if (value == "null" || value == "undefined") { + callback(nullptr); + } else if (value == "true") { + callback(true); + } else if (value == "false") { + callback(value); + } else { + double number = 0.0f; + + try { + number = std::stod(result); + } catch (...) { + callback(value); + return; + } + + callback(number); + } + } else if ([result isKindOfClass: NSNumber.class]) { + callback([result doubleValue]); + } } }]; } diff --git a/src/window/dialog.cc b/src/window/dialog.cc index 365376224a..435aadab6c 100644 --- a/src/window/dialog.cc +++ b/src/window/dialog.cc @@ -1,10 +1,7 @@ -#include "../core/debug.hh" #include "../app/app.hh" -#include "window.hh" +#include "../core/debug.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" -#endif +#include "window.hh" using namespace SSC; diff --git a/src/window/dialog.kt b/src/window/dialog.kt index 3c0cf83ab2..5428848538 100644 --- a/src/window/dialog.kt +++ b/src/window/dialog.kt @@ -2,7 +2,9 @@ package socket.runtime.window import java.lang.Runtime +import android.content.Intent import android.net.Uri +import android.Manifest import android.webkit.WebChromeClient import androidx.activity.result.contract.ActivityResultContracts @@ -81,6 +83,19 @@ open class Dialog (val activity: WindowManagerActivity) { fun resolve (uris: Array<Uri>) { val callback = this.callback + + /* + for (uri in uris) { + this.activity.applicationContext.contentResolver.takePersistableUriPermission( + uri, + ( + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + ) + } + */ + if (callback != null) { this.callback = null callback(uris) @@ -91,6 +106,7 @@ open class Dialog (val activity: WindowManagerActivity) { options: FileSystemPickerOptions, callback: ((Array<Uri>) -> Unit)? = null ) { + val activity = this.activity as socket.runtime.app.AppActivity val mimeType = if (options.mimeTypes.size > 0 && options.mimeTypes[0].length > 0) { options.mimeTypes[0] @@ -98,15 +114,23 @@ open class Dialog (val activity: WindowManagerActivity) { this.callback = callback - this.activity.runOnUiThread { - // TODO(@jwerle): support the other launcher types above - // through the `showFileSystemPicker()` method some how - if (options.multiple) { - launcherForMulitpleItems.launch(mimeType) - } else { - launcherForSingleItem.launch(mimeType) + val permissions = arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + activity.requestPermissions(permissions, { _ -> + activity.runOnUiThread { + // TODO(@jwerle): support the other launcher types above + // through the `showFileSystemPicker()` method some how + if (options.multiple) { + launcherForMulitpleItems.launch(mimeType) + } else { + //launcherForSingleDocument.launch(arrayOf(mimeType)) + launcherForSingleItem.launch(mimeType) + } } - } + }) } fun showFileSystemPicker ( diff --git a/src/window/linux.cc b/src/window/linux.cc index db1cc22564..d6d59a1c14 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -1049,7 +1049,7 @@ namespace SSC { return ScreenSize { width, height }; } - void Window::eval (const String& source) { + void Window::eval (const String& source, const EvalCallback& callback) { auto app = App::sharedApplication(); app->dispatch([=, this] () { if (this->webview) { @@ -1065,9 +1065,88 @@ namespace SSC { GAsyncResult* result, gpointer userData ) { - webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, nullptr); + const auto callback = reinterpret_cast<const EvalCallback*>(userData); + if (callback == nullptr || *callback == nullptr) { + auto value = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, nullptr); + return; + } + + GError* error = nullptr; + auto value = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error); + + if (!value) { + if (error != nullptr) { + (*callback)(JSON::Error(error->message)); + g_error_free(error); + } else { + (*callback)(JSON::Error("An unknown error occurred")); + } + } else if (jsc_value_is_string(value)) { + const auto context = jsc_value_get_context(value); + const auto exception = jsc_context_get_exception(context); + const auto stringValue = jsc_value_to_string(value); + + if (exception) { + const auto message = jsc_exception_get_message(exception); + (*callback)(JSON::Error(message)); + } else { + (*callback)(stringValue); + } + + g_free(stringValue); + } else if (jsc_value_is_boolean(value)) { + const auto context = jsc_value_get_context(value); + const auto exception = jsc_context_get_exception(context); + const auto booleanValue = jsc_value_to_boolean(value); + + if (exception) { + const auto message = jsc_exception_get_message(exception); + (*callback)(JSON::Error(message)); + } else { + (*callback)(booleanValue); + } + } else if (jsc_value_is_null(value)) { + const auto context = jsc_value_get_context(value); + const auto exception = jsc_context_get_exception(context); + + if (exception) { + const auto message = jsc_exception_get_message(exception); + (*callback)(JSON::Error(message)); + } else { + (*callback)(nullptr); + } + } else if (jsc_value_is_number(value)) { + const auto context = jsc_value_get_context(value); + const auto exception = jsc_context_get_exception(context); + const auto numberValue = jsc_value_to_double(value); + + if (exception) { + const auto message = jsc_exception_get_message(exception); + (*callback)(JSON::Error(message)); + } else { + (*callback)(numberValue); + } + } else if (jsc_value_is_undefined(value)) { + const auto context = jsc_value_get_context(value); + const auto exception = jsc_context_get_exception(context); + + if (exception) { + const auto message = jsc_exception_get_message(exception); + (*callback)(JSON::Error(message)); + } else { + (*callback)(nullptr); + } + } + + if (value) { + //webkit_javascript_result_unref(reinterpret_cast<WebKitJavascriptResult*>(value)); + } + + delete callback; }, - nullptr // user data + callback == nullptr + ? nullptr + : new EvalCallback(std::move(callback)) ); } }); diff --git a/src/window/manager.kt b/src/window/manager.kt index 3eef56adf3..4c1d8d215f 100644 --- a/src/window/manager.kt +++ b/src/window/manager.kt @@ -169,13 +169,18 @@ open class WindowFragmentManager ( /** * Evaluates a JavaScript `source` string at a given window `index`. */ - fun evaluateJavaScriptInWindowFragmentView (index: Int, source: String): Boolean { + fun evaluateJavaScriptInWindowFragmentView (index: Int, source: String, token: String): Boolean { val fragments = this.fragments if (this.hasWindowFragment(index)) { kotlin.concurrent.thread { activity.runOnUiThread { val fragment = fragments.find { it.index == index } - fragment?.webview?.evaluateJavascript(source, fun (_) {}) + fragment?.webview?.evaluateJavascript(source, { result -> + val window = fragment.window + if (window != null) { + window.onEvaluateJavascriptResult(index, token, result) + } + }) } } return true @@ -391,7 +396,7 @@ open class WindowManagerActivity : AppCompatActivity(R.layout.window_container_v /** * Evaluates JavaScript source in a window at a given `index`. */ - fun evaluateJavaScript (index: Int, source: String): Boolean { - return this.windowFragmentManager.evaluateJavaScriptInWindowFragmentView(index, source) + fun evaluateJavaScript (index: Int, source: String, token: String): Boolean { + return this.windowFragmentManager.evaluateJavaScriptInWindowFragmentView(index, source, token) } } diff --git a/src/window/win.cc b/src/window/win.cc index 35f4e4c20c..bc3d0fdac2 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1272,7 +1272,7 @@ namespace SSC { controller->put_Bounds(bounds); } - void Window::eval (const String& source) { + void Window::eval (const String& source, const EvalCallback& callback) { auto app = App::sharedApplication(); app->dispatch([=, this] { if (this->webview == nullptr) { @@ -1281,7 +1281,39 @@ namespace SSC { this->webview->ExecuteScript( convertStringToWString(source).c_str(), - nullptr + Microsoft::WRL::Callback<ICoreWebView2ExecuteScriptCompletedHandler>( + [=, this](HRESULT error, LPCWSTR result) { + if (callback != nullptr) { + if (error != S_OK) { + // TODO(@jwerle): figure out how to get the error message here + callback(JSON::Error("An unknown error occurred")) + return; + } + + const auto string = convertWStringToString(result); + if (string == "null" || string == "undefined") { + callback(nullptr); + } else if (string == "true") { + callback(true); + } else if (string == "false") { + callback(false); + } else { + double number = 0.0f; + + try { + number = std::stod(string); + } catch (...) { + callback(string); + return; + } + + callback(number); + } + } + + return S_OK; + } + ) ); }); } diff --git a/src/window/window.hh b/src/window/window.hh index 3a19bfd916..5c823c54fd 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -89,6 +89,8 @@ namespace SSC { */ class Window { public: + using EvalCallback = Function<void(const JSON::Any)>; + /** * A container for representing the window position in a * Cartesian coordinate system (screen coordinates) @@ -423,6 +425,7 @@ namespace SSC { #elif SOCKET_RUNTIME_PLATFORM_ANDROID String pendingNavigationLocation; jobject androidWindowRef; + std::map<String, EvalCallback> evaluateJavaScriptCallbacks; #endif Window (SharedPointer<Core> core, const Window::Options&); @@ -431,7 +434,7 @@ namespace SSC { static ScreenSize getScreenSize (); void about (); - void eval (const String&); + void eval (const String&, const EvalCallback& callback = nullptr); void show (); void hide (); void kill (); diff --git a/src/window/window.kt b/src/window/window.kt index a8319c3dd3..c1caea78ce 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -30,7 +30,7 @@ import socket.runtime.core.WebChromeClient import socket.runtime.ipc.Bridge import socket.runtime.ipc.Message import socket.runtime.window.WindowFragment -import socket.runtime.window.WebViewFilePickerOptions +import socket.runtime.window.Dialog import __BUNDLE_IDENTIFIER__.R @@ -135,12 +135,15 @@ open class WindowWebChromeClient (val window: Window) : WebChromeClient() { if (!super.onShowFileChooser(webview, callback, params)) { return false } + val fragment = this.window.fragment - val activity = fragment.requireActivity() as AppCompatActivity - val options = WebViewFilePickerOptions(params) + val activity = fragment.requireActivity() as WindowManagerActivity + val options = Dialog.FileSystemPickerOptions(params) + activity.dialog.showFileSystemPicker(options, fun (uris: Array<Uri>) { callback.onReceiveValue(uris) }) + return true } } @@ -251,6 +254,9 @@ open class Window (val fragment: WindowFragment) { @Throws(Exception::class) external fun onMessage (index: Int, value: String, bytes: ByteArray? = null): Unit + @Throws(Exception::class) + external fun onEvaluateJavascriptResult (index: Int, token: String, result: String): Unit + @Throws(Exception::class) external fun getPendingNavigationLocation (index: Int): String From a32c567a4fa4d73c0eb2ae38a99891c400b8ba7e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:50:25 +0200 Subject: [PATCH 1045/1178] refactor(core/{resource,url}): improve URL, handle android assets/contents in 'FileResource' --- src/core/core.cc | 37 +++++--- src/core/core.hh | 1 + src/core/resource.cc | 201 ++++++++++++++++++++++++++++++++++--------- src/core/resource.hh | 47 ++++++---- src/core/url.cc | 44 ++++++---- 5 files changed, 241 insertions(+), 89 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 36fca03fef..8698447069 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -64,7 +64,7 @@ namespace SSC { this->conduit.stop(); } - this->stopEventLoop(); + this->pauseEventLoop(); this->isPaused = true; } @@ -292,24 +292,33 @@ namespace SSC { return uv_loop_alive(getEventLoop()); } + void Core::pauseEventLoop() { + isLoopRunning = false; + uv_stop(&eventLoop); + } + void Core::stopEventLoop() { + if (isLoopRunning) { + return; + } + isLoopRunning = false; uv_stop(&eventLoop); #if !SOCKET_RUNTIME_PLATFORM_APPLE - #if SOCKET_RUNTIME_PLATFORM_LINUX - if (this->options.dedicatedLoopThread) { - #endif - if (eventLoopThread != nullptr) { - if (eventLoopThread->joinable()) { - eventLoopThread->join(); - } + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (this->options.dedicatedLoopThread) { + #endif + if (eventLoopThread != nullptr) { + if (eventLoopThread->joinable()) { + eventLoopThread->join(); + } - delete eventLoopThread; - eventLoopThread = nullptr; - } - #if SOCKET_RUNTIME_PLATFORM_LINUX - } - #endif + delete eventLoopThread; + eventLoopThread = nullptr; + } + #if SOCKET_RUNTIME_PLATFORM_LINUX + } + #endif #endif } diff --git a/src/core/core.hh b/src/core/core.hh index fc43773a39..394bb8f067 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -228,6 +228,7 @@ namespace SSC { bool isLoopAlive (); void initEventLoop (); void runEventLoop (); + void pauseEventLoop (); void stopEventLoop (); void dispatchEventLoop (EventLoopDispatchCallback dispatch); void signalDispatchEventLoop (); diff --git a/src/core/resource.cc b/src/core/resource.cc index eeb18db33d..8cf04eaff6 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -1,18 +1,18 @@ -#include "resource.hh" #include "config.hh" -#include "debug.hh" #include "core.hh" +#include "debug.hh" +#include "resource.hh" #include "../platform/platform.hh" #if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" #include <fstream> #endif namespace SSC { static std::map<String, FileResource::Cache> caches; static Mutex mutex; + static FileResource::WellKnownPaths defaultWellKnownPaths; #if SOCKET_RUNTIME_PLATFORM_ANDROID static Android::AssetManager* sharedAndroidAssetManager = nullptr; @@ -60,8 +60,11 @@ namespace SSC { static Path getRelativeAndroidAssetManagerPath (const Path& resourcePath) { auto resourcesPath = FileResource::getResourcesPath(); auto assetPath = replace(resourcePath.string(), resourcesPath.string(), ""); + if (assetPath.starts_with("/")) { assetPath = assetPath.substr(1); + } else if (assetPath.starts_with("./")) { + assetPath = assetPath.substr(2); } return Path(assetPath); @@ -333,6 +336,10 @@ namespace SSC { #endif } + void FileResource::WellKnownPaths::setDefaults (const WellKnownPaths& paths) { + defaultWellKnownPaths = paths; + } + const FileResource::WellKnownPaths& FileResource::getWellKnownPaths () { static const auto paths = WellKnownPaths {}; return paths; @@ -342,6 +349,20 @@ namespace SSC { static auto userConfig = getUserConfig(); static auto bundleIdentifier = userConfig["meta_bundle_identifier"]; + // initialize default values + this->resources = defaultWellKnownPaths.resources; + this->downloads = defaultWellKnownPaths.downloads; + this->documents = defaultWellKnownPaths.documents; + this->pictures = defaultWellKnownPaths.pictures; + this->desktop = defaultWellKnownPaths.desktop; + this->videos = defaultWellKnownPaths.videos; + this->music = defaultWellKnownPaths.music; + this->config = defaultWellKnownPaths.config; + this->home = defaultWellKnownPaths.home; + this->data = defaultWellKnownPaths.data; + this->log = defaultWellKnownPaths.log; + this->tmp = defaultWellKnownPaths.tmp; + this->resources = FileResource::getResourcesPath(); this->tmp = fs::temp_directory_path(); #if SOCKET_RUNTIME_PLATFORM_APPLE @@ -371,6 +392,7 @@ namespace SSC { this->videos = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory)); this->music = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory)); this->config = Path(HOME + "/Library/Application Support/" + bundleIdentifier); + this->music = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSSharedPublicDirectory)); this->home = Path(String(NSHomeDirectory().UTF8String)); this->data = Path(HOME + "/Library/Application Support/" + bundleIdentifier); this->log = Path(HOME + "/Library/Logs/" + bundleIdentifier); @@ -391,6 +413,7 @@ namespace SSC { static const auto XDG_DESKTOP_DIR = Env::get("XDG_DESKTOP_DIR"); static const auto XDG_VIDEOS_DIR = Env::get("XDG_VIDEOS_DIR"); static const auto XDG_MUSIC_DIR = Env::get("XDG_MUSIC_DIR"); + static const auto XDG_PUBLICSHARE_DIR = Env::get("XDG_PUBLICSHARE_DIR"); static const auto XDG_CONFIG_HOME = Env::get("XDG_CONFIG_HOME", HOME + "/.config"); static const auto XDG_DATA_HOME = Env::get("XDG_DATA_HOME", HOME + "/.local/share"); @@ -435,6 +458,10 @@ namespace SSC { this->music = Path(HOME) / "Music"; } + if (XDG_PUBLICSHARE_DIR.size() > 0) { + this->media = Path(XDG_PUBLICSHARE_DIR); + } + this->config = Path(XDG_CONFIG_HOME) / bundleIdentifier; this->home = Path(HOME); this->data = Path(XDG_DATA_HOME) / bundleIdentifier; @@ -454,19 +481,8 @@ namespace SSC { this->log = this->config; #elif SOCKET_RUNTIME_PLATFORM_ANDROID const auto storage = FileResource::getExternalAndroidStorageDirectory(); - const auto files = FileResource::getExternalAndroidFilesDirectory(); const auto cache = FileResource::getExternalAndroidCacheDirectory(); this->resources = "socket://" + bundleIdentifier; - this->downloads = storage / "Downloads"; - this->documents = storage / "Documents"; - this->pictures = storage / "Pictures"; - this->desktop = !files.empty() ? files : storage / "Desktop"; - this->videos = storage / "DCIM" / "Videos"; - this->music = storage / "Music"; - this->config = storage; - this->home = this->desktop; - this->data = storage; - this->log = storage / "logs"; this->tmp = !cache.empty() ? cache : storage / "tmp"; #endif } @@ -479,8 +495,9 @@ namespace SSC { {"pictures", this->pictures}, {"desktop", this->desktop}, {"videos", this->videos}, - {"music", this->music}, {"config", this->config}, + {"media", this->media}, + {"music", this->music}, {"home", this->home}, {"data", this->data}, {"log", this->log}, @@ -497,6 +514,7 @@ namespace SSC { entries.push_back(this->desktop); entries.push_back(this->videos); entries.push_back(this->music); + entries.push_back(this->media); entries.push_back(this->config); entries.push_back(this->home); entries.push_back(this->data); @@ -521,10 +539,35 @@ namespace SSC { FileResource::FileResource ( const Path& resourcePath, const Options& options - ) : Resource("FileResource", resourcePath.string()) { - this->path = fs::absolute(resourcePath); + ) : + Resource("FileResource", resourcePath.string()) + { + this->url = URL(resourcePath.string()); + + if (url.scheme == "socket") { + const auto resourcesPath = FileResource::getResourcesPath(); + this->path = resourcesPath / url.pathname; + #if SOCKET_RUNTIME_PLATFORM_ANDROID + this->path = Path(url.pathname); + this->name = getRelativeAndroidAssetManagerPath(this->path).string(); + #endif + } else if (url.scheme == "content" || url.scheme == "android.resource") { + this->path = resourcePath; + } else if (url.scheme == "file") { + this->path = Path(url.pathname); + } else { + this->path = fs::absolute(resourcePath); + this->url = URL("file://" + this->path.string()); + } + this->options = options; this->startAccessing(); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (this->isAndroidLocalAsset()) { + this->name = getRelativeAndroidAssetManagerPath(this->path).string(); + } + #endif } FileResource::FileResource (const String& resourcePath, const Options& options) @@ -603,8 +646,8 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_APPLE - if (this->url == nullptr) { - this->url = [NSURL fileURLWithPath: @(this->path.string().c_str())]; + if (this->nsURL == nullptr) { + this->nsURL = [NSURL fileURLWithPath: @(this->path.string().c_str())]; } #endif @@ -615,8 +658,8 @@ namespace SSC { #if SOCKET_RUNTIME_PLATFORM_APPLE if (!this->path.string().starts_with(resourcesPath.string())) { - if (![this->url startAccessingSecurityScopedResource]) { - this->url = nullptr; + if (![this->nsURL startAccessingSecurityScopedResource]) { + this->nsURL = nullptr; return false; } } @@ -631,8 +674,8 @@ namespace SSC { return false; } #if SOCKET_RUNTIME_PLATFORM_APPLE - if (this->url != nullptr) { - [this->url stopAccessingSecurityScopedResource]; + if (this->nsURL != nullptr) { + [this->nsURL stopAccessingSecurityScopedResource]; } #endif this->accessing = false; @@ -671,6 +714,24 @@ namespace SSC { #endif } + int FileResource::access (int mode) const noexcept { + if (this->accessing) { + if (mode == ::access(this->path.string().c_str(), mode)) { + return mode; + } + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + if (this->isAndroidLocalAsset() || this->isAndroidContent()) { + if (mode == F_OK || mode == R_OK) { + return mode; + } + } + #endif + } + + return -1; // `EPERM` + } + const String FileResource::mimeType () const noexcept { if (!this->accessing) { return ""; @@ -758,9 +819,18 @@ namespace SSC { } #elif SOCKET_RUNTIME_PLATFORM_ANDROID if (extension.size() > 1) { - return Android::MimeTypeMap::sharedMimeTypeMap()->getMimeTypeFromExtension( + const auto value = Android::MimeTypeMap::sharedMimeTypeMap()->getMimeTypeFromExtension( extension.starts_with(".") ? extension.substr(1) : extension ); + + if (value.size() > 0) { + return value; + } + + if (this->options.core && this->url.scheme == "content") { + auto core = this->options.core; + return core->platform.contentResolver.getContentType(this->url.str()); + } } #endif @@ -781,10 +851,10 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_APPLE - if (this->url != nullptr) { + if (this->nsURL != nullptr) { NSNumber* size = nullptr; NSError* error = nullptr; - [this->url getResourceValue: &size + [this->nsURL getResourceValue: &size forKey: NSURLFileSizeKey error: &error ]; @@ -817,22 +887,38 @@ namespace SSC { #elif SOCKET_RUNTIME_PLATFORM_ANDROID bool success = false; if (sharedAndroidAssetManager) { - const auto assetPath = getRelativeAndroidAssetManagerPath(this->path); - const auto asset = AAssetManager_open( - sharedAndroidAssetManager, - assetPath.c_str(), - AASSET_MODE_BUFFER - ); + if (this->isAndroidLocalAsset()) { + const auto assetPath = getRelativeAndroidAssetManagerPath(this->path); + const auto asset = AAssetManager_open( + sharedAndroidAssetManager, + assetPath.c_str(), + AASSET_MODE_BUFFER + ); - if (asset) { - this->cache.size = AAsset_getLength(asset); - AAsset_close(asset); + if (asset) { + this->cache.size = AAsset_getLength(asset); + AAsset_close(asset); + } } - } - if (!success) { - if (fs::exists(this->path)) { - this->cache.size = fs::file_size(this->path); + if (!success) { + if (fs::exists(this->path)) { + this->cache.size = fs::file_size(this->path); + } + } + } else if (this->url.scheme == "content" || this->url.scheme == "android.resource") { + auto core = this->options.core; + if (core != nullptr) { + off_t offset = 0; + off_t length = 0; + auto fileDescriptor = core->platform.contentResolver.openFileDescriptor ( + this->url.str(), + &offset, + &length + ); + + core->platform.contentResolver.closeFileDescriptor(fileDescriptor); + return length; } } #else @@ -863,11 +949,11 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_APPLE - if (this->url == nullptr) { + if (this->nsURL == nullptr) { return nullptr; } - const auto data = [NSData dataWithContentsOfURL: this->url]; + const auto data = [NSData dataWithContentsOfURL: this->nsURL]; if (data.length > 0) { this->bytes.reset(new char[data.length]{0}); memcpy(this->bytes.get(), data.bytes, data.length); @@ -1264,4 +1350,37 @@ namespace SSC { bool FileResource::ReadStream::Buffer::isEmpty () const { return this->size == 0 || this->bytes == nullptr; } + +#if SOCKET_RUNTIME_PLATFORM_ANDROID + bool FileResource::isAndroidLocalAsset () const noexcept { + if (sharedAndroidAssetManager) { + const auto assetPath = getRelativeAndroidAssetManagerPath(this->path); + const auto asset = AAssetManager_open( + sharedAndroidAssetManager, + assetPath.c_str(), + AASSET_MODE_BUFFER + ); + + if (asset) { + AAsset_close(asset); + return true; + } + } + + return false; + } + + bool FileResource::isAndroidContent () const noexcept { + const auto core = this->options.core; + + if (core != nullptr) { + const auto uri = this->path.string(); + if (core->platform.contentResolver.isContentURI(uri)) { + const auto pathname = core->platform.contentResolver.getPathnameFromURI(uri); + return pathname.size() > 0; + } + } + return false; + } +#endif } diff --git a/src/core/resource.hh b/src/core/resource.hh index 10b3df6b15..525855a287 100644 --- a/src/core/resource.hh +++ b/src/core/resource.hh @@ -3,18 +3,18 @@ #include "../platform/platform.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" -#endif - #include "trace.hh" +#include "url.hh" namespace SSC { + // forward + class Core; + class Resource { public: Atomic<bool> accessing = false; - const String name; - const String type; + String name; + String type; Tracer tracer; Resource (const String& type, const String& name); bool hasAccess () const noexcept; @@ -29,10 +29,6 @@ namespace SSC { size_t size = 0; }; - struct Options { - bool cache; - }; - struct WellKnownPaths { Path resources; Path downloads; @@ -42,11 +38,14 @@ namespace SSC { Path videos; Path config; Path music; + Path media; Path home; Path data; Path log; Path tmp; + static void setDefaults (const WellKnownPaths& paths); + WellKnownPaths (); const Vector<Path> entries () const; JSON::Object json () const; @@ -95,8 +94,6 @@ namespace SSC { #elif SOCKET_RUNTIME_PLATFORM_LINUX GFileInputStream* stream = nullptr; GFile* file = nullptr; - #elif SOCKET_RUNTIME_PLATFORM_ANDROID - #elif SOCKET_RUNTIME_PLATFORM_WINDOWS #endif Options options; @@ -115,6 +112,11 @@ namespace SSC { size_t remaining (off_t offset = -1) const; }; + struct Options { + bool cache; + Core* core; + }; + Cache cache; Options options; SharedPointer<char[]> bytes = nullptr; @@ -150,13 +152,22 @@ namespace SSC { #endif Path path; + URL url; #if SOCKET_RUNTIME_PLATFORM_APPLE - NSURL* url = nullptr; + NSURL* nsURL = nullptr; #endif - FileResource (const Path& resourcePath, const Options& options = {}); - FileResource (const String& resourcePath, const Options& options = {}); + FileResource ( + const Path& resourcePath, + const Options& options = {0} + ); + + FileResource ( + const String& resourcePath, + const Options& options = {0} + ); + ~FileResource (); FileResource (const FileResource&); FileResource (FileResource&&); @@ -166,6 +177,7 @@ namespace SSC { bool startAccessing (); bool stopAccessing (); bool exists () const noexcept; + int access (int mode = F_OK) const noexcept; const String mimeType () const noexcept; size_t size (bool cached = false) noexcept; size_t size () const noexcept; @@ -173,6 +185,11 @@ namespace SSC { const char* read (bool cached = false); const String str (bool cached = false); ReadStream stream (const ReadStream::Options& options = {}); + + #if SOCKET_RUNTIME_PLATFORM_ANDROID + bool isAndroidLocalAsset () const noexcept; + bool isAndroidContent () const noexcept; + #endif }; } #endif diff --git a/src/core/url.cc b/src/core/url.cc index 6042f878ed..141a0bb1eb 100644 --- a/src/core/url.cc +++ b/src/core/url.cc @@ -1,4 +1,5 @@ #include "codec.hh" +#include "debug.hh" #include "url.hh" namespace SSC { @@ -42,36 +43,41 @@ namespace SSC { } else if (fragment != String::npos) { components.authority = input.substr(0, fragment); input = input.substr(fragment, input.size()); + } else { + components.authority = input; + components.pathname = "/"; } } } } } - const auto questionMark = input.find("?"); - const auto fragment = input.find("#"); - - if (questionMark != String::npos && fragment != String::npos) { - if (questionMark < fragment) { + if (components.pathname.size() == 0) { + const auto questionMark = input.find("?"); + const auto fragment = input.find("#"); + + if (questionMark != String::npos && fragment != String::npos) { + if (questionMark < fragment) { + components.pathname = input.substr(0, questionMark); + components.query = input.substr(questionMark + 1, fragment - questionMark - 1); + components.fragment = input.substr(fragment + 1, input.size()); + } else { + components.pathname = input.substr(0, fragment); + components.fragment = input.substr(fragment + 1, input.size()); + } + } else if (questionMark != String::npos) { components.pathname = input.substr(0, questionMark); - components.query = input.substr(questionMark + 1, fragment - questionMark - 1); - components.fragment = input.substr(fragment + 1, input.size()); - } else { + components.query = input.substr(questionMark + 1, input.size()); + } else if (fragment != String::npos) { components.pathname = input.substr(0, fragment); components.fragment = input.substr(fragment + 1, input.size()); + } else { + components.pathname = input; } - } else if (questionMark != String::npos) { - components.pathname = input.substr(0, questionMark); - components.query = input.substr(questionMark + 1, input.size()); - } else if (fragment != String::npos) { - components.pathname = input.substr(0, fragment); - components.fragment = input.substr(fragment + 1, input.size()); - } else { - components.pathname = input; - } - if (!components.pathname.starts_with("/")) { - components.pathname = "/" + components.pathname; + if (!components.pathname.starts_with("/")) { + components.pathname = "/" + components.pathname; + } } return components; From f81e1a5a97b44485567a0cf9dfb9e79cc2115f3c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:51:58 +0200 Subject: [PATCH 1046/1178] refactor(app): configure well known paths correctly on android --- src/app/app.cc | 45 ++++++++- src/app/app.hh | 9 +- src/app/app.kt | 262 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 295 insertions(+), 21 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index ba28f9e551..32e3bd152f 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -1113,6 +1113,44 @@ extern "C" { ); } + void ANDROID_EXTERNAL(app, App, setWellKnownDirectories)( + JNIEnv *env, + jobject self, + jstring downloadsString, + jstring documentsString, + jstring picturesString, + jstring desktopString, + jstring videosString, + jstring configString, + jstring mediaString, + jstring musicString, + jstring homeString, + jstring dataString, + jstring logString, + jstring tmpString + ) { + const auto app = App::sharedApplication(); + + if (!app) { + ANDROID_THROW(env, "Missing 'App' in environment"); + } + + FileResource::WellKnownPaths defaults; + defaults.downloads = Path(Android::StringWrap(env, downloadsString).str()); + defaults.documents = Path(Android::StringWrap(env, documentsString).str()); + defaults.pictures = Path(Android::StringWrap(env, picturesString).str()); + defaults.desktop = Path(Android::StringWrap(env, desktopString).str()); + defaults.videos = Path(Android::StringWrap(env, videosString).str()); + defaults.config = Path(Android::StringWrap(env, configString).str()); + defaults.media = Path(Android::StringWrap(env, mediaString).str()); + defaults.music = Path(Android::StringWrap(env, musicString).str()); + defaults.home = Path(Android::StringWrap(env, homeString).str()); + defaults.data = Path(Android::StringWrap(env, dataString).str()); + defaults.log = Path(Android::StringWrap(env, logString).str()); + defaults.tmp = Path(Android::StringWrap(env, tmpString).str()); + FileResource::WellKnownPaths::setDefaults(defaults); + } + jstring ANDROID_EXTERNAL(app, App, getUserConfigValue)( JNIEnv *env, jobject self, @@ -1173,8 +1211,11 @@ extern "C" { } app->activity = env->NewGlobalRef(activity); - app->core->platform.jvm = Android::JVMEnvironment(app->jni); - app->core->platform.activity = app->activity; + app->core->platform.configureAndroidContext( + Android::JVMEnvironment(app->jni), + app->activity + ); + app->run(); if (app->windowManager.getWindowStatus(0) == WindowManager::WINDOW_NONE) { diff --git a/src/app/app.hh b/src/app/app.hh index 9280290ddc..52df6a8ce8 100644 --- a/src/app/app.hh +++ b/src/app/app.hh @@ -1,14 +1,9 @@ #ifndef SOCKET_RUNTIME_APP_APP_H #define SOCKET_RUNTIME_APP_APP_H -#include "../window/window.hh" -#include "../serviceworker/container.hh" - #include "../core/core.hh" - -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" -#endif +#include "../serviceworker/container.hh" +#include "../window/window.hh" #if SOCKET_RUNTIME_PLATFORM_IOS #import <QuartzCore/QuartzCore.h> diff --git a/src/app/app.kt b/src/app/app.kt index 089d6253a5..a30ab7820d 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -9,17 +9,22 @@ import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent +import android.content.ContentUris import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.AssetManager +import android.content.res.AssetFileDescriptor +import android.content.ContentResolver +import android.database.Cursor import android.graphics.Insets import android.graphics.drawable.Icon import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment - +import android.provider.DocumentsContract +import android.provider.MediaStore import android.view.WindowInsets import android.view.WindowManager import android.webkit.MimeTypeMap @@ -45,6 +50,185 @@ open class AppPermissionRequest (callback: (Boolean) -> Unit) { val callback = callback } +open class AppPlatform (val activity: AppActivity) { + fun isDocumentURI (url: String): Boolean { + return DocumentsContract.isDocumentUri( + this.activity.applicationContext, + Uri.parse(url) + ) + } + + fun getDocumentID (url: String): String { + return DocumentsContract.getDocumentId(Uri.parse(url)) + } + + fun getContentURI (baseURL: String, documentID: Long): String { + val uri = ContentUris.withAppendedId( + Uri.parse(baseURL), + documentID + ) + + return uri.toString() + } + + fun getContentType (url: String): String { + return this.activity.applicationContext.contentResolver.getType(Uri.parse(url)) ?: "" + } + + fun getPathnameEntriesFromContentURI (url: String): Array<String> { + val context = this.activity.applicationContext + val column = MediaStore.MediaColumns.DATA + var cursor: Cursor? = null + val uri = Uri.parse(url) + val results = mutableListOf<String>() + + try { + cursor = context.contentResolver.query( + uri, + arrayOf(column), + null, + null, + null + ) + + if (cursor != null) { + cursor.moveToFirst() + + do { + val result = cursor.getString(cursor.getColumnIndex(column)) + + if (result != null && result.length > 0) { + results += result + } else { + val tmp = try { + cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns._ID)) + } catch (e: Exception) { + null + } + + if (tmp != null) { + results += cursor.getString(cursor.getColumnIndex(column)) + } + } + } while (cursor.moveToNext()) + } + } catch (_: Exception) { + // not handled + } finally { + if (cursor != null) { + cursor.close() + } + } + + return results.toTypedArray() + } + + fun getPathnameFromContentURIDataColumn ( + url: String, + id: String + ): String { + val context = this.activity.applicationContext + val column = MediaStore.MediaColumns.DATA + var cursor: Cursor? = null + var result: String? = null + val uri = Uri.parse(url) + + try { + cursor = context.contentResolver.query( + uri, + arrayOf(column), + null, + null, + null + ) + + if (cursor != null) { + cursor.moveToFirst() + do { + if (id.length > 0 && (result == null || result.length == 0)) { + result = cursor.getString(cursor.getColumnIndex(column)) + break + } else { + var index = cursor.getColumnIndex(MediaStore.MediaColumns._ID) + var tmp: String? = null + + try { + tmp = cursor.getString(index) + } catch (e: Exception) {} + + if (tmp == id) { + index = cursor.getColumnIndex(column) + result = cursor.getString(index) + break + } + } + } while (cursor.moveToNext()) + } + } catch (err: Exception) { + return "" + } finally { + if (cursor != null) { + cursor.close() + } + } + + return result ?: "" + } + + fun getExternalContentURIForType (type: String): String { + val contentURI = ( + if (type == "image") { + MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } else if (type == "video") { + MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } else if (type == "audio") { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } else { + MediaStore.Files.getContentUri("external") + } + ) + + if (contentURI == null) { + return "" + } + + return contentURI.toString() + } + + fun getContentResolver (): ContentResolver { + return this.activity.getContentResolver() + } + + fun openContentResolverFileDescriptor (url: String): AssetFileDescriptor? { + val contentResolver = this.activity.applicationContext.contentResolver + console.log("url: $url") + try { + return contentResolver.openAssetFileDescriptor(Uri.parse(url), "r") + } catch (err: Exception) { + console.log("error: $err") + return null + } + } + + fun hasContentResolverAccess (url: String): Boolean { + val contentResolver = this.activity.applicationContext.contentResolver + + try { + val fd = contentResolver.openAssetFileDescriptor(Uri.parse(url), "r") + + if (fd == null) { + return false + } + + fd.close() + } catch (_: Exception) { + return false + } + + return true + } +} + /** * The `AppActivity` represents the root activity for the application. * It is an extended `WindowManagerActivity` that considers application @@ -56,13 +240,18 @@ open class AppActivity : WindowManagerActivity() { open lateinit var notificationChannel: NotificationChannel open lateinit var notificationManager: NotificationManager - val permissionRequests = mutableListOf<AppPermissionRequest>() + open val platform = AppPlatform(this) + open val permissionRequests = mutableListOf<AppPermissionRequest>() open fun getRootDirectory (): String { return this.getExternalFilesDir(null)?.absolutePath ?: "/sdcard/Android/data/__BUNDLE_IDENTIFIER__/files" } + fun getAppPlatform (): AppPlatform { + return this.platform + } + fun checkPermission (permission: String): Boolean { val status = ContextCompat.checkSelfPermission(this.applicationContext, permission) return status == PackageManager.PERMISSION_GRANTED @@ -111,6 +300,10 @@ open class AppActivity : WindowManagerActivity() { return insets.top + insets.bottom } + fun getAssetManager (): AssetManager { + return this.applicationContext.resources.assets + } + fun isNotificationManagerEnabled (): Boolean { return NotificationManagerCompat.from(this).areNotificationsEnabled() } @@ -279,6 +472,33 @@ open class AppActivity : WindowManagerActivity() { Build.MANUFACTURER, Build.PRODUCT ) + + setWellKnownDirectories( + // 'Downloads/' + this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath ?: "", + // 'Documents/' + this.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath ?: "", + // 'Pictures/' + this.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath ?: "", + // 'Desktop/' + externalFilesDirectory, + // 'Videos/' + this.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath ?: "", + // "configuration directory" + externalFilesDirectory, + // "media directory" + externalStorageDirectory + "Android/media/__BUNDLE_IDENTIFIER__", + // 'Music/' + this.getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath ?: "", + // '~/' + externalStorageDirectory, + // "data directory" + externalFilesDirectory, + // "logs directory" + externalFilesDirectory, + // 'tmp/' (likely the cache directory will be used instead of this value) + externalStorageDirectory + "/tmp/__BUNDLE_IDENTIFIER__" + ) } app.onCreateAppActivity(this) @@ -377,16 +597,6 @@ open class AppActivity : WindowManagerActivity() { permissions: Array<String>, grantResults: IntArray ) { - for (request in this.permissionRequests) { - if (request.id == requestCode) { - this.permissionRequests.remove(request) - request.callback(grantResults.all { r -> - r == PackageManager.PERMISSION_GRANTED - }) - break - } - } - var i = 0 val seen = mutableSetOf<String>() for (permission in permissions) { @@ -427,6 +637,18 @@ open class AppActivity : WindowManagerActivity() { val state = if (granted) "granted" else "denied" App.getInstance().onPermissionChange(name, state) } + + for (request in this.permissionRequests) { + if (request.id == requestCode) { + this.permissionRequests.remove(request) + request.callback(grantResults.all { r -> + r == PackageManager.PERMISSION_GRANTED + }) + return + } + } + + super.onRequestPermissionsResult(requestCode, permissions, grantResults) } } @@ -516,6 +738,22 @@ open class App : Application() { product: String ): Unit + @Throws(Exception::class) + external fun setWellKnownDirectories ( + downloads: String = "", + documents: String = "", + pictures: String = "", + desktop: String = "", + videos: String = "", + config: String = "", + media: String = "", + music: String = "", + home: String = "", + data: String = "", + log: String = "", + tmp: String = "" + ): Unit + @Throws(Exception::class) external fun getUserConfigValue (key: String): String From a3909858637884720f32079ed2309cfb596a424a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:52:08 +0200 Subject: [PATCH 1047/1178] chore(ipc): clean up --- src/ipc/navigator.cc | 8 ++------ src/ipc/scheme_handlers.cc | 4 ---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/ipc/navigator.cc b/src/ipc/navigator.cc index a85bff5bd5..bf8d784eee 100644 --- a/src/ipc/navigator.cc +++ b/src/ipc/navigator.cc @@ -1,12 +1,8 @@ -#include "../window/window.hh" #include "../app/app.hh" +#include "../window/window.hh" -#include "navigator.hh" #include "bridge.hh" - -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" -#endif +#include "navigator.hh" #if SOCKET_RUNTIME_PLATFORM_APPLE @implementation SSCNavigationDelegate diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index 5d0339a730..f57d64dd91 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -2,10 +2,6 @@ #include "scheme_handlers.hh" #include "ipc.hh" -#if SOCKET_RUNTIME_PLATFORM_ANDROID -#include "../platform/android.hh" -#endif - using namespace SSC; using namespace SSC::IPC; From fed82a5f98a3d1126fe180ff1894bb45188499b4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:52:31 +0200 Subject: [PATCH 1048/1178] refctor(bin/publish-npm-modules.sh): symlink kotlin files when requested --- bin/publish-npm-modules.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/publish-npm-modules.sh b/bin/publish-npm-modules.sh index 4faac5f92f..522ed95e80 100755 --- a/bin/publish-npm-modules.sh +++ b/bin/publish-npm-modules.sh @@ -211,6 +211,12 @@ if (( !only_top_level )); then cp -rap "$SOCKET_HOME/bin"/.vs* "$SOCKET_HOME/packages/$package/bin" fi + if (( do_global_link )); then + for file in $(find "$root/src" -name *.kt); do + ln -sf "$file" "$SOCKET_HOME/packages/$package${file/$root/}" + done + fi + cd "$SOCKET_HOME/packages/$package" || exit $? echo "# in directory: '$SOCKET_HOME/packages/$package'" From 40e6cf910f87e7fe6255b48de03215582777c9b0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:52:50 +0200 Subject: [PATCH 1049/1178] fix(api/dgram.js): check if buffer is detached before calling callback --- api/dgram.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/dgram.js b/api/dgram.js index 238b581b38..3bbab41199 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -542,7 +542,9 @@ async function send (socket, options, callback) { }, options.buffer) } - callback(result.err, result.data) + if (result.data?.detached !== true) { + callback(result.err, result.data) + } } catch (err) { callback(err) return { err } From e3e971edfcb42781a46680018c69a7697cb1684e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:54:20 +0200 Subject: [PATCH 1050/1178] feat(api/fs/web.js): support 'File.prototype.slice' --- api/fs/web.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/api/fs/web.js b/api/fs/web.js index cf9dde1fe5..8b7e87682c 100644 --- a/api/fs/web.js +++ b/api/fs/web.js @@ -2,6 +2,7 @@ import { DEFAULT_STREAM_HIGH_WATER_MARK } from './stream.js' import { isBufferLike, toBuffer } from '../util.js' import { NotAllowedError } from '../errors.js' +import { readFileSync } from './index.js' import mime from '../mime.js' import path from '../path.js' import fs from './promises.js' @@ -111,6 +112,7 @@ export async function createFile (filename, options = null) { : Math.min(stats.size, DEFAULT_STREAM_HIGH_WATER_MARK) let fd = options?.fd ?? null + let blobBuffer = null return create(File, class File { get [kFileFullName] () { return filename } @@ -123,10 +125,11 @@ export async function createFile (filename, options = null) { get type () { return type } slice () { - console.warn('socket:fs/web: File.slice() is not supported in implementation') - // This synchronous interface is not supported - // An empty and ephemeral `Blob` is returned instead - return new Blob([], { type }) + if (!blobBuffer) { + blobBuffer = readFileSync(filename) + } + + return new Blob([blobBuffer], { type }) } async arrayBuffer () { From c46eac2f9999adbef0c5730ac88ad67bc7220a68 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:54:40 +0200 Subject: [PATCH 1051/1178] fix(api/internal/error.js): fix error stack building on android --- api/internal/error.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/api/internal/error.js b/api/internal/error.js index b73749a26a..b671268706 100644 --- a/api/internal/error.js +++ b/api/internal/error.js @@ -1,4 +1,5 @@ import { CallSite, createCallSites } from './callsite.js' +import os from '../os.js' /** * The default `Error` class stack trace limit. @@ -17,7 +18,15 @@ export const DefaultPlatformError = globalThis.Error */ function applyPlatforErrorHook (PlatformError, Constructor, target, ...args) { const error = PlatformError.call(target, ...args) - const stack = error.stack.split('\n').slice(2) // slice off the `Error()` + `applyPlatforErrorHook()` call frames + const stack = error.stack.split('\n') + + // slice off the `Error()` + `applyPlatforErrorHook()` call frames + if (os.platform() === 'android') { + stack.splice(1, 2) + } else { + stack.splice(0, 2) + } + const [, callsite] = stack[0].split('@') let stackValue = stack.join('\n') @@ -166,11 +175,16 @@ function installRuntimeError (PlatformError, isBaseError = false) { ) } + const stack = new PlatformError().stack.split('\n') + if (os.platform() === 'android') { + stack.splice(1, 2) + } else { + stack.splice(0, 2) + } // prepareStackTrace is already called there if (target instanceof Error) { - target.stack = new PlatformError().stack.split('\n').slice(2).join('\n') + target.stack = stack } else { - const stack = new PlatformError().stack.split('\n').slice(2).join('\n') const prepareStackTrace = Error.prepareStackTrace || globalThis.Error.prepareStackTrace if (typeof prepareStackTrace === 'function') { const callsites = createCallSites(target, stack) From 59259eff58914fdcc90bef877ecacdbf79158929 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:54:55 +0200 Subject: [PATCH 1052/1178] fix(api/ipc.js): do not set 'responseType' for sync XHR in document mode --- api/ipc.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/ipc.js b/api/ipc.js index 2df820648f..948395ea46 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1142,7 +1142,9 @@ export function sendSync (command, value = '', options = {}, buffer = null) { debug.log('ipc.sendSync: %s', uri) } - request.responseType = options?.responseType ?? '' + if (!(/android/i.test(primordials.platform) && globalThis.document)) { + request.responseType = options?.responseType ?? '' + } if (buffer) { request.open('POST', uri, false) From 1032e0e8b63252f2caad9a135b89e277b7726ef0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:55:38 +0200 Subject: [PATCH 1053/1178] feat(api/path.js): introduce 'path.MEDIA' --- api/path.js | 2 ++ api/path/index.js | 2 ++ api/path/posix.js | 2 ++ api/path/well-known.js | 7 +++++++ api/path/win32.js | 2 ++ 5 files changed, 15 insertions(+) diff --git a/api/path.js b/api/path.js index cf3be66729..eddb29e1d3 100644 --- a/api/path.js +++ b/api/path.js @@ -13,6 +13,7 @@ import { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, @@ -50,6 +51,7 @@ export { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, diff --git a/api/path/index.js b/api/path/index.js index 002e01a6b9..9e8bb2ed47 100644 --- a/api/path/index.js +++ b/api/path/index.js @@ -11,6 +11,7 @@ import { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, @@ -32,6 +33,7 @@ export { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, diff --git a/api/path/posix.js b/api/path/posix.js index 216a8c20df..19aee7dd6a 100644 --- a/api/path/posix.js +++ b/api/path/posix.js @@ -11,6 +11,7 @@ import { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, @@ -35,6 +36,7 @@ export { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, diff --git a/api/path/well-known.js b/api/path/well-known.js index a316c70e56..e3434a7882 100644 --- a/api/path/well-known.js +++ b/api/path/well-known.js @@ -50,6 +50,12 @@ export const RESOURCES = paths.resources || null */ export const CONFIG = paths.config || null +/** + * Well known path to the application's public "media" folder. + * @type {?string} + */ +export const MEDIA = paths.media || null + /** * Well known path to the application's "data" folder. * @type {?string} @@ -83,6 +89,7 @@ export default { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, diff --git a/api/path/win32.js b/api/path/win32.js index fce09290e4..7c514fca8e 100644 --- a/api/path/win32.js +++ b/api/path/win32.js @@ -11,6 +11,7 @@ import { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, @@ -35,6 +36,7 @@ export { DESKTOP, VIDEOS, CONFIG, + MEDIA, MUSIC, HOME, DATA, From fa01fed2bdb573b0e81ac870b52e19705f5c33e8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:55:58 +0200 Subject: [PATCH 1054/1178] refacvtor(api/url.js): support 'content:' and 'android.reource:' schemes by default --- api/url/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/url/index.js b/api/url/index.js index 3d6009ea49..dd1fa3fa70 100644 --- a/api/url/index.js +++ b/api/url/index.js @@ -40,6 +40,10 @@ export const protocols = new Set([ 'npm:', 'ipc:', + // android + 'android.resource:', + 'content:', + // web standard & reserved 'bitcoin:', 'file:', From 2d2e4585bf71af8d49181717dc9c22c63fa2d72d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 18:56:26 +0200 Subject: [PATCH 1055/1178] chore(api): generate types + docs --- api/README.md | 54 +++++++++++++++++++++++++------------------------- api/index.d.ts | 18 +++++++++++++---- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/api/README.md b/api/README.md index 4341c59cd4..e730cc1ebf 100644 --- a/api/README.md +++ b/api/README.md @@ -498,7 +498,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L658) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L660) Creates a `Socket` instance. @@ -517,12 +517,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L664) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L666) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L745) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L747) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -539,7 +539,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L860) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L862) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -559,7 +559,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L897) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L899) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -567,7 +567,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L956) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L958) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -618,7 +618,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1049) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1051) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -630,7 +630,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1121) +### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1123) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -646,7 +646,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1156) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1158) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -661,7 +661,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1187) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1189) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -672,7 +672,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1204) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1206) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -683,12 +683,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1217) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1219) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1225) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1227) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -697,31 +697,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1293) +### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1295) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1299) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1301) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1316) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1318) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1323) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1325) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1331) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1333) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1338) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1340) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1348) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1350) Thrown when a bad port is given. @@ -1720,7 +1720,7 @@ This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exp This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1187) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1189) Emit event to be dispatched on `window` object. @@ -1731,7 +1731,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1246) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1248) Sends an async IPC command request with parameters. @@ -1747,27 +1747,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1697) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1699) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1729) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1731) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1778) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1780) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1780) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1782) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1985) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1987) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index 3eb2342603..f75c180d2c 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -2939,6 +2939,11 @@ declare module "socket:path/well-known" { * @type {?string} */ export const CONFIG: string | null; + /** + * Well known path to the application's public "media" folder. + * @type {?string} + */ + export const MEDIA: string | null; /** * Well known path to the application's "data" folder. * @type {?string} @@ -2968,6 +2973,7 @@ declare module "socket:path/well-known" { export { DESKTOP }; export { VIDEOS }; export { CONFIG }; + export { MEDIA }; export { MUSIC }; export { HOME }; export { DATA }; @@ -3943,6 +3949,7 @@ declare module "socket:path/win32" { import { DESKTOP } from "socket:path/well-known"; import { VIDEOS } from "socket:path/well-known"; import { CONFIG } from "socket:path/well-known"; + import { MEDIA } from "socket:path/well-known"; import { MUSIC } from "socket:path/well-known"; import { HOME } from "socket:path/well-known"; import { DATA } from "socket:path/well-known"; @@ -3950,7 +3957,7 @@ declare module "socket:path/win32" { import { TMP } from "socket:path/well-known"; import * as exports from "socket:path/win32"; - export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export { mounts, posix, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MEDIA, MUSIC, HOME, DATA, LOG, TMP }; } declare module "socket:path/posix" { @@ -4038,6 +4045,7 @@ declare module "socket:path/posix" { import { DESKTOP } from "socket:path/well-known"; import { VIDEOS } from "socket:path/well-known"; import { CONFIG } from "socket:path/well-known"; + import { MEDIA } from "socket:path/well-known"; import { MUSIC } from "socket:path/well-known"; import { HOME } from "socket:path/well-known"; import { DATA } from "socket:path/well-known"; @@ -4045,7 +4053,7 @@ declare module "socket:path/posix" { import { TMP } from "socket:path/well-known"; import * as exports from "socket:path/posix"; - export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export { mounts, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MEDIA, MUSIC, HOME, DATA, LOG, TMP }; } declare module "socket:path/index" { @@ -4061,6 +4069,7 @@ declare module "socket:path/index" { import { DESKTOP } from "socket:path/well-known"; import { VIDEOS } from "socket:path/well-known"; import { CONFIG } from "socket:path/well-known"; + import { MEDIA } from "socket:path/well-known"; import { MUSIC } from "socket:path/well-known"; import { HOME } from "socket:path/well-known"; import { DATA } from "socket:path/well-known"; @@ -4068,7 +4077,7 @@ declare module "socket:path/index" { import { TMP } from "socket:path/well-known"; import * as exports from "socket:path/index"; - export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export { mounts, posix, win32, Path, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MEDIA, MUSIC, HOME, DATA, LOG, TMP }; } declare module "socket:path" { @@ -4098,12 +4107,13 @@ declare module "socket:path" { import { DESKTOP } from "socket:path/index"; import { VIDEOS } from "socket:path/index"; import { CONFIG } from "socket:path/index"; + import { MEDIA } from "socket:path/index"; import { MUSIC } from "socket:path/index"; import { HOME } from "socket:path/index"; import { DATA } from "socket:path/index"; import { LOG } from "socket:path/index"; import { TMP } from "socket:path/index"; - export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MUSIC, HOME, DATA, LOG, TMP }; + export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MEDIA, MUSIC, HOME, DATA, LOG, TMP }; } declare module "socket:fs/stream" { From 050d66f015aa1977e4d7978fc27faeb1cb2f8808 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 21:39:34 +0200 Subject: [PATCH 1056/1178] fix(api/ipc.js): typeof check 'globalThis.primordials' --- api/ipc.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 948395ea46..1868bf2451 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1142,8 +1142,11 @@ export function sendSync (command, value = '', options = {}, buffer = null) { debug.log('ipc.sendSync: %s', uri) } - if (!(/android/i.test(primordials.platform) && globalThis.document)) { - request.responseType = options?.responseType ?? '' + if (typeof globalThis.primordials !== 'undefined') { + if (!(/android/i.test(globalThis.primordials.platform) && globalThis.document)) { + // @ts-ignore + request.responseType = options?.responseType ?? '' + } } if (buffer) { From 54ef3bf939029d64b0f3c2c4308ae627092572af Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 21:39:59 +0200 Subject: [PATCH 1057/1178] fix(window/apple): fix bad var use --- src/window/apple.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 80b23414ba..b01f3b3afb 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -824,7 +824,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { double number = 0.0f; try { - number = std::stod(result); + number = std::stod(value); } catch (...) { callback(value); return; From e234d7ff000ac03ae376615cecae7edda5d6704e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 21:40:20 +0200 Subject: [PATCH 1058/1178] chore(api): generate types --- api/README.md | 14 ++--- api/index.d.ts | 139 ++++++++++++++++++++++++------------------------- 2 files changed, 76 insertions(+), 77 deletions(-) diff --git a/api/README.md b/api/README.md index e730cc1ebf..bce6bd450f 100644 --- a/api/README.md +++ b/api/README.md @@ -1720,7 +1720,7 @@ This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exp This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1189) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1192) Emit event to be dispatched on `window` object. @@ -1731,7 +1731,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1248) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1251) Sends an async IPC command request with parameters. @@ -1747,27 +1747,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1699) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1702) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1731) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1734) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1780) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1783) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1782) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1785) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1987) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1990) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index f75c180d2c..4b32d87f84 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T>(key: Variable<T>): boolean; + get<T_1>(key: Variable<T_1>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T>(key: Variable<T>, value: T): Mapping; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T>(key: Variable<T>): Mapping; + delete<T_3>(key: Variable<T_3>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T>(key: Variable<T>): T | undefined; + static get<T_1>(key: Variable<T_1>): T_1; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T>(revert: Revert<T> | FrozenRevert): void; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_1>(): T_1 | undefined; + get<T_2>(): T_2; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F>(fn: F): F; + static wrap<F_1>(fn: F_1): F_1; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent; + prototype: CustomEvent<any>; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent; + prototype: MessageEvent<any>; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2161,7 +2161,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): value is symbol; + export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2837,7 +2837,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3078,7 +3078,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3108,7 +3108,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): signal; + export function getCode(name: string | number): any; /** * Gets the name for a given 'signal' code * @return {string} @@ -3252,7 +3252,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3633,7 +3633,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: ProcessEnvironment; + export const env: any; export default process; const process: any; } @@ -4543,9 +4543,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + send: (body?: Document | XMLHttpRequestBodyInit) => void; }; } export class WorkerMetric extends Metric { @@ -5273,7 +5273,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; + static from(stat?: object | Stats, fromBigInt?: any): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6146,7 +6146,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: "utf8" | "buffer"; + encoding: 'utf8' | 'buffer'; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6990,7 +6990,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "top-level" | "nested"; + get frameType(): "none" | "nested" | "top-level"; /** * The type of the client. * @type {'window'|'worker'} @@ -7022,10 +7022,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: "window" | "worker"; + type?: 'window' | 'worker'; parent?: object | null; top?: object | null; - frameType?: "top-level" | "nested" | "none"; + frameType?: 'top-level' | 'nested' | 'none'; }; } @@ -7099,7 +7099,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7312,7 +7312,6 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; - import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8037,7 +8036,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8083,9 +8082,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): CoreAsyncResource; - bind(fn: Function, thisArg?: object | undefined): Function; - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; }; } export namespace Promise { @@ -8705,7 +8704,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9914,7 +9913,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9989,7 +9988,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: "shared" | "wasm32"; + type?: 'shared' | 'wasm32'; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10485,7 +10484,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: "strict" | "relaxed" | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10549,7 +10548,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: "set" | "delete", entry?: object | undefined); + constructor(type: 'set' | 'delete', entry?: object | undefined); entry: any; } /** @@ -10681,7 +10680,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import("socket:service-worker/events").ExtendableEvent); + constructor(event: import('./events.js').ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10722,7 +10721,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import("socket:service-worker/clients").Client>; + client(): Promise<import('./clients.js').Client>; #private; } namespace _default { @@ -10909,7 +10908,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10944,13 +10943,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13642,7 +13641,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T>(actual: T, expected: T, msg?: string): void; + notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; /** * @template T * @param {T} actual @@ -13650,7 +13649,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T>(actual: T, expected: T, msg?: string): void; + equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13899,7 +13898,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: { + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { /** * - The text to wait for */ @@ -13910,7 +13909,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; + }, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15191,7 +15190,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } export default Package; @@ -15202,13 +15201,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; prefix?: string; }; export type ParsedPackageName = { @@ -15292,7 +15291,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import("socket:commonjs/module").Module); + constructor(module: import('./module.js').Module); /** * The referrer (parent) of this module. * @type {string} @@ -15336,7 +15335,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15696,7 +15695,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions | undefined): any; + require(url: any, options?: RequireOptions): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15734,9 +15733,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import process from "socket:process"; } declare module "socket:module" { @@ -16076,7 +16075,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; + static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; /** * `Notification` class constructor. * @param {string} title @@ -16262,9 +16261,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; export default createServiceWorker; @@ -16980,7 +16979,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -17000,7 +16999,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17022,8 +17021,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: "read" | "readwrite"; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + mode?: 'read' | 'readwrite'; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; }; /** * ]?: string[] @@ -17033,10 +17032,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; types?: Array<{ description?: string; - [keyof]: any; + [keyof]; }>; }; } @@ -17137,7 +17136,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17146,7 +17145,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: "commonjs" | "module"; + type: 'commonjs' | 'module'; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17227,8 +17226,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17242,7 +17241,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17691,12 +17690,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; + export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends Harness> { + export class TapeHarness<T extends exports.Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17749,7 +17748,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends Harness> = { + export type TapeTestFn<T extends exports.Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17766,8 +17765,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 317bcf99daafbcbac249207b278d5ed3504328c8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:04:20 +0200 Subject: [PATCH 1059/1178] refactor(ipc/preload): handle already defined preload --- src/ipc/preload.cc | 227 +++++++++++++++++++++++---------------------- 1 file changed, 118 insertions(+), 109 deletions(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 2e8831f9a5..60cac499fa 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -158,118 +158,123 @@ namespace SSC::IPC { buffers.push_back(";(() => {"); buffers.push_back(trim(tmpl( R"JAVASCRIPT( - Object.defineProperty(globalThis, '__args', { - configurable: false, - enumerable: false, - writable: false, - value: {} - }) - Object.defineProperties(globalThis.__args, { - argv: { - configurable: false, - enumerable: true, - writable: false, - value: {{argv}} - }, - client: { + if (globalThiss.__RUNTIME_INIT_NOW__) return + if (!globalThis.__args) { + Object.defineProperty(globalThis, '__args', { configurable: false, - enumerable: true, + enumerable: false, writable: false, value: {} - }, - conduit: { - configurable: false, - enumerable: true, - writable: false, - value: {{conduit}} - }, - config: { + }) + Object.defineProperties(globalThis.__args, { + argv: { + configurable: false, + enumerable: true, + writable: false, + value: {{argv}} + }, + client: { + configurable: false, + enumerable: true, + writable: false, + value: {} + }, + conduit: { + configurable: false, + enumerable: true, + writable: false, + value: {{conduit}} + }, + config: { + configurable: false, + enumerable: true, + writable: false, + value: {} + }, + debug: { + configurable: false, + enumerable: true, + writable: false, + value: {{debug}} + }, + env: { + configurable: false, + enumerable: true, + writable: false, + value: {} + }, + headless: { + configurable: false, + enumerable: true, + writable: false, + value: {{headless}} + }, + index: { + configurable: false, + enumerable: true, + writable: false, + value: {{index}} + }, + }) + } + )JAVASCRIPT", + Map { + {"argv", args["argv"].str()}, + {"conduit", args["conduit"].str()}, + {"debug", args["debug"].str()}, + {"headless", args["headless"].str()}, + {"index", args["index"].str()}, + } + ))); + + // 6. compile `globalThis.__args.client` values + buffers.push_back(trim(tmpl( + R"JAVASCRIPT( + if (!globalThis.__args.client?.id) { + Object.defineProperties(globalThis.__args.client, { + id: { configurable: false, enumerable: true, writable: false, - value: {} + value: globalThis.window && globalThis.top !== globalThis.window + ? '{{id}}' + : globalThis.window && globalThis.top + ? '{{clientId}}' + : null }, - debug: { + type: { configurable: false, enumerable: true, - writable: false, - value: {{debug}} + writable: true, + value: globalThis.window ? 'window' : 'worker' }, - env: { + parent: { configurable: false, enumerable: true, writable: false, - value: {} + value: globalThis.parent !== globalThis + ? globalThis.parent?.__args?.client ?? null + : null }, - headless: { + top: { configurable: false, enumerable: true, - writable: false, - value: {{headless}} + get: () => globalThis.top + ? globalThis.top.__args?.client ?? null + : globalThis.__args.client }, - index: { + frameType: { configurable: false, enumerable: true, - writable: false, - value: {{index}} + writable: true, + value: globalThis.window && globalThis.top !== globalThis.window + ? 'nested' + : globalThis.window && globalThis.top + ? 'top-level' + : 'none' }, }) - )JAVASCRIPT", - Map { - {"argv", args["argv"].str()}, - {"conduit", args["conduit"].str()}, - {"debug", args["debug"].str()}, - {"headless", args["headless"].str()}, - {"index", args["index"].str()}, } - ))); - - // 6. compile `globalThis.__args.client` values - buffers.push_back(trim(tmpl( - R"JAVASCRIPT( - Object.defineProperties(globalThis.__args.client, { - id: { - configurable: false, - enumerable: true, - writable: false, - value: globalThis.window && globalThis.top !== globalThis.window - ? '{{id}}' - : globalThis.window && globalThis.top - ? '{{clientId}}' - : null - }, - type: { - configurable: false, - enumerable: true, - writable: true, - value: globalThis.window ? 'window' : 'worker' - }, - parent: { - configurable: false, - enumerable: true, - writable: false, - value: globalThis.parent !== globalThis - ? globalThis.parent?.__args?.client ?? null - : null - }, - top: { - configurable: false, - enumerable: true, - get: () => globalThis.top - ? globalThis.top.__args?.client ?? null - : globalThis.__args.client - }, - frameType: { - configurable: false, - enumerable: true, - writable: true, - value: globalThis.window && globalThis.top !== globalThis.window - ? 'nested' - : globalThis.window && globalThis.top - ? 'top-level' - : 'none' - }, - }) )JAVASCRIPT", Map { {"id", std::to_string(rand64())}, @@ -384,12 +389,14 @@ namespace SSC::IPC { } else { buffers.push_back(tmpl( R"JAVASCRIPT( - Object.defineProperty(globalThis, 'RUNTIME_TEST_FILENAME', { - configurable: false, - enumerable: false, - writable: false, - value: String(new URL('{{pathname}}', globalThis.location.href) - }) + if (!globalThis.RUNTIME_TEST_FILENAME) { + Object.defineProperty(globalThis, 'RUNTIME_TEST_FILENAME', { + configurable: false, + enumerable: false, + writable: false, + value: String(new URL('{{pathname}}', globalThis.location.href) + }) + } )JAVASCRIPT", Map {{"pathname", pathname}} )); @@ -398,7 +405,7 @@ namespace SSC::IPC { // 10. compile listeners for `globalThis` buffers.push_back(R"JAVASCRIPT( - if (globalThis.document) { + if (globalThis.document && !globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG) { Object.defineProperties(globalThis, { RUNTIME_APPLICATION_URL_EVENT_BACKLOG: { configurable: false, @@ -448,10 +455,10 @@ namespace SSC::IPC { // 11. freeze `globalThis.__args` values buffers.push_back(R"JAVASCRIPT( - Object.freeze(globalThis.__args.client) - Object.freeze(globalThis.__args.config) - Object.freeze(globalThis.__args.argv) - Object.freeze(globalThis.__args.env) + try { Object.freeze(globalThis.__args.client) } catch {} + try { Object.freeze(globalThis.__args.config) } catch {} + try { Object.freeze(globalThis.__args.argv) } catch {} + try { Object.freeze(globalThis.__args.env) } catch {} )JAVASCRIPT"); buffers.push_back("})();"); @@ -520,7 +527,7 @@ namespace SSC::IPC { } buffers.push_back(R"JAVASCRIPT( - if (globalThis.document) { + if (globalThis.document && !globalThis.module) { ;(async function GlobalCommonJSScope () { const globals = await import('socket:internal/globals') await globals.get('RuntimeReadyPromise') @@ -577,7 +584,7 @@ namespace SSC::IPC { buffers.push_back(RUNTIME_PRELOAD_JAVASCRIPT_BEGIN_TAG); } buffers.push_back(R"JAVASCRIPT( - if (globalThis.document) { + if (globalThis.document && !globalThis.process) { ;(async function GlobalNodeJSScope () { const process = await import('socket:process') Object.defineProperties(globalThis, { @@ -611,12 +618,14 @@ namespace SSC::IPC { buffers.push_back(tmpl( R"JAVASCRIPT( - Object.defineProperty(globalThis, '__RUNTIME_PRIMORDIAL_OVERRIDES__', { - configurable: false, - enumerable: false, - writable: false, - value: {{RUNTIME_PRIMORDIAL_OVERRIDES}} - }) + if (!globalThis.RUNTIME_PRIMORDIAL_OVERRIDES) { + Object.defineProperty(globalThis, '__RUNTIME_PRIMORDIAL_OVERRIDES__', { + configurable: false, + enumerable: false, + writable: false, + value: {{RUNTIME_PRIMORDIAL_OVERRIDES}} + }) + } )JAVASCRIPT", Map {{"RUNTIME_PRIMORDIAL_OVERRIDES", this->options.RUNTIME_PRIMORDIAL_OVERRIDES}} )); From cd939dfe124d5ef605fd054394a02d4f26cd4df1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:05:57 +0200 Subject: [PATCH 1060/1178] fix(window/apple): ensure runtime preload is injected --- src/window/apple.mm | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index b01f3b3afb..e93a057da5 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -238,6 +238,35 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { name: @"external" ]; + auto preloadUserScript= [WKUserScript alloc]; + auto preloadUserScriptSource = IPC::Preload::compile({ + .features = IPC::Preload::Options::Features { + .useGlobalCommonJS = false, + .useGlobalNodeJS = false, + .useTestScript = false, + .useHTMLMarkup = false, + .useESM = false, + .useGlobalArgs = true + }, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, + .index = options.index, + .conduit = this->core->conduit.port, + .userScript = options.userScript + }); + + [preloadUserScript + initWithSource: @(preloadUserScriptSource.str().c_str()) + injectionTime: WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly: NO + ]; + + [configuration.userContentController + addUserScript: preloadUserScript + ]; + [configuration setValue: @NO forKey: @"crossOriginAccessControlCheckEnabled" @@ -805,7 +834,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { debug("JavaScriptError: %@", error); if (callback != nullptr) { - callback(JSON::Error([error UTF8String])); + callback(JSON::Error(error.localizedDescription.UTF8String)); } return; From e53cef57dfb5891499ec54619355e501d814aa9f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:26:08 +0200 Subject: [PATCH 1061/1178] refactor(ipc/bridge): ensure preload is injected on android ipc bridge --- src/ipc/bridge.kt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ipc/bridge.kt b/src/ipc/bridge.kt index f35ce2dcee..dee275b5b3 100644 --- a/src/ipc/bridge.kt +++ b/src/ipc/bridge.kt @@ -2,18 +2,19 @@ package socket.runtime.ipc import android.content.Intent +import android.graphics.Bitmap import android.net.Uri import android.webkit.WebView import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse -import androidx.appcompat.app.AppCompatActivity - import socket.runtime.app.App import socket.runtime.core.console import socket.runtime.core.WebViewClient import socket.runtime.ipc.Navigator import socket.runtime.ipc.SchemeHandlers +import socket.runtime.window.Window +import socket.runtime.window.WindowManagerActivity private fun isAndroidAssetsUri (uri: Uri): Boolean { if (uri.pathSegments.size == 0) { @@ -38,7 +39,8 @@ private fun isAndroidAssetsUri (uri: Uri): Boolean { open class Bridge ( val index: Int, - val activity: AppCompatActivity + val activity: WindowManagerActivity, + val window: Window ): WebViewClient() { open val schemeHandlers = SchemeHandlers(this) open val navigator = Navigator(this) @@ -102,6 +104,17 @@ open class Bridge ( return this.schemeHandlers.handleRequest(request) } + override fun onPageStarted ( + view: WebView, + url: String, + favicon: Bitmap + ) { + if (url.authority != "__BUNDLE_IDENTIFIER__") { + val preloadUserScript = this.window.getPreloadUserScript() + view.evaluateJavaScript(preloadUserScript, { _ -> }) + } + } + fun emit (event: String, data: String): Boolean { return this.emit(this.index, event, data) } From a2c4b16689b565c8de98a3a32d4835566b3c588c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:27:06 +0200 Subject: [PATCH 1062/1178] refactor(window): ensure preload is injected on android/linux/windows --- src/window/android.cc | 38 ++++++++++++++++++++++++++++++++++++++ src/window/linux.cc | 31 +++++++++++++++++++++++++++++++ src/window/win.cc | 30 ++++++++++++++++++++++++++++++ src/window/window.kt | 9 ++++++--- 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index 5c89f1d62f..f8c9745461 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -544,6 +544,44 @@ extern "C" { return attachment.env->NewStringUTF(pendingNavigationLocation.c_str()); } + jstring ANDROID_EXTERNAL(window, Window, getPreloadUserScript) ( + JNIEnv* env, + jobject self, + jint index + ) { + const auto app = App::sharedApplication(); + + if (!app) { + return ANDROID_THROW(env, "Missing 'App' in environment"); + } + + const auto window = app->windowManager.getWindow(index); + + if (!window) { + return ANDROID_THROW(env, "Invalid window index (%d) requested", index); + } + + auto preloadUserScriptSource = IPC::Preload::compile({ + .features = IPC::Preload::Options::Features { + .useGlobalCommonJS = false, + .useGlobalNodeJS = false, + .useTestScript = false, + .useHTMLMarkup = false, + .useESM = false, + .useGlobalArgs = true + }, + .client = UniqueClient { + .id = window->bridge.client.id, + .index = window->bridge.client.index + }, + .index = window->options.index, + .conduit = window->core->conduit.port, + .userScript = window->options.userScript + }); + + return env->NewStringUTF(preloadUserScriptSource.compile().str().c_str()); + } + void ANDROID_EXTERNAL(window, Window, handleApplicationURL) ( JNIEnv* env, jobject self, diff --git a/src/window/linux.cc b/src/window/linux.cc index d6d59a1c14..c2cec76f91 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -206,6 +206,37 @@ namespace SSC { this->userContentManager = webkit_user_content_manager_new(); webkit_user_content_manager_register_script_message_handler(this->userContentManager, "external"); + auto preloadUserScriptSource = IPC::Preload::compile({ + .features = IPC::Preload::Options::Features { + .useGlobalCommonJS = false, + .useGlobalNodeJS = false, + .useTestScript = false, + .useHTMLMarkup = false, + .useESM = false, + .useGlobalArgs = true + }, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, + .index = options.index, + .conduit = this->core->conduit.port, + .userScript = options.userScript + }); + + auto preloadUserScript = webkit_user_script_new( + preloadUserScriptSource.compile().str().c_str(), + WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, + nullptr, + nullptr + ); + + webkit_user_content_manager_add_script( + this->userContentManager, + preloadUserScript + ); + this->policies = webkit_website_policies_new_with_policies( "autoplay", userConfig["permission_allow_autoplay"] != "false" ? WEBKIT_AUTOPLAY_ALLOW diff --git a/src/window/win.cc b/src/window/win.cc index bc3d0fdac2..335e9a61c2 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -887,6 +887,36 @@ namespace SSC { ); } while (0); + // configure the user script preload + do { + auto preloadUserScriptSource = IPC::Preload::compile({ + .features = IPC::Preload::Options::Features { + .useGlobalCommonJS = false, + .useGlobalNodeJS = false, + .useTestScript = false, + .useHTMLMarkup = false, + .useESM = false, + .useGlobalArgs = true + }, + .client = UniqueClient { + .id = this->bridge.client.id, + .index = this->bridge.client.index + }, + .index = this->options.index, + .conduit = this->core->conduit.port, + .userScript = this->options.userScript + }); + + this->webview->AddScriptToExecuteOnDocumentCreated( + // Note that this may not do anything as preload goes out of scope before event fires + // Consider using w->preloadJavascript, but apps work without this + convertStringToWString(preloadUserScriptSource.compile().str()).c_str(), + Microsoft::WRL::Callback<ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler>( + [&](HRESULT error, PCWSTR id) -> HRESULT { return S_OK; } + ).Get() + ); + } while (0); + // configure webview permission request handler do { EventRegistrationToken token; diff --git a/src/window/window.kt b/src/window/window.kt index c1caea78ce..8135aa8e93 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -29,7 +29,7 @@ import socket.runtime.core.console import socket.runtime.core.WebChromeClient import socket.runtime.ipc.Bridge import socket.runtime.ipc.Message -import socket.runtime.window.WindowFragment +import socket.runtime.window.WindowManagerActivity import socket.runtime.window.Dialog import __BUNDLE_IDENTIFIER__.R @@ -152,8 +152,8 @@ open class WindowWebChromeClient (val window: Window) : WebChromeClient() { */ open class Window (val fragment: WindowFragment) { val userMessageHandler = WindowWebViewUserMessageHandler(this) - val activity = fragment.requireActivity() - val bridge = Bridge(fragment.index, activity as AppCompatActivity) + val activity = fragment.requireActivity() as WindowManagerActivity + val bridge = Bridge(fragment.index, activity as AppCompatActivity, this) val client = WindowWebChromeClient(this) val index = fragment.index var title = "" @@ -260,6 +260,9 @@ open class Window (val fragment: WindowFragment) { @Throws(Exception::class) external fun getPendingNavigationLocation (index: Int): String + @Throws(Exception::class) + external fun getPreloadUserScript (index: Int): String + @Throws(Exception::class) external fun handleApplicationURL (index: Int, url: String): Unit } From 55ef5b6b4036738152cb324021f2ae609446bc3a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:34:35 +0200 Subject: [PATCH 1063/1178] fix(core/resource): fix 'music/media' well known paths --- src/core/resource.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/resource.cc b/src/core/resource.cc index 8cf04eaff6..379a466b83 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -390,9 +390,9 @@ namespace SSC { this->pictures = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSPicturesDirectory)); this->desktop = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSDesktopDirectory)); this->videos = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMoviesDirectory)); - this->music = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory)); this->config = Path(HOME + "/Library/Application Support/" + bundleIdentifier); - this->music = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSSharedPublicDirectory)); + this->media = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSSharedPublicDirectory)); + this->music = Path(DIRECTORY_PATH_FROM_FILE_MANAGER(NSMusicDirectory)); this->home = Path(String(NSHomeDirectory().UTF8String)); this->data = Path(HOME + "/Library/Application Support/" + bundleIdentifier); this->log = Path(HOME + "/Library/Logs/" + bundleIdentifier); From 7baec2a4871c5ebf721db4125ee1297a83be930d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:34:49 +0200 Subject: [PATCH 1064/1178] fix(ipc/preload): fix typo --- src/ipc/preload.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 60cac499fa..8a3052a565 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -158,7 +158,7 @@ namespace SSC::IPC { buffers.push_back(";(() => {"); buffers.push_back(trim(tmpl( R"JAVASCRIPT( - if (globalThiss.__RUNTIME_INIT_NOW__) return + if (globalThis.__RUNTIME_INIT_NOW__) return if (!globalThis.__args) { Object.defineProperty(globalThis, '__args', { configurable: false, From 267abf9769b7cfde80a21198944f2bc1df9b7a4f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 30 Jul 2024 22:59:57 +0200 Subject: [PATCH 1065/1178] fix(window): fix 'IPC::Preload' misuse --- src/window/android.cc | 8 +++++--- src/window/linux.cc | 2 +- src/window/win.cc | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/window/android.cc b/src/window/android.cc index f8c9745461..bf3924a1d7 100644 --- a/src/window/android.cc +++ b/src/window/android.cc @@ -552,13 +552,15 @@ extern "C" { const auto app = App::sharedApplication(); if (!app) { - return ANDROID_THROW(env, "Missing 'App' in environment"); + ANDROID_THROW(env, "Missing 'App' in environment"); + return nullptr; } const auto window = app->windowManager.getWindow(index); if (!window) { - return ANDROID_THROW(env, "Invalid window index (%d) requested", index); + ANDROID_THROW(env, "Invalid window index (%d) requested", index); + return nullptr; } auto preloadUserScriptSource = IPC::Preload::compile({ @@ -579,7 +581,7 @@ extern "C" { .userScript = window->options.userScript }); - return env->NewStringUTF(preloadUserScriptSource.compile().str().c_str()); + return env->NewStringUTF(preloadUserScriptSource.compile().c_str()); } void ANDROID_EXTERNAL(window, Window, handleApplicationURL) ( diff --git a/src/window/linux.cc b/src/window/linux.cc index c2cec76f91..ebdd5e6f1c 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -225,7 +225,7 @@ namespace SSC { }); auto preloadUserScript = webkit_user_script_new( - preloadUserScriptSource.compile().str().c_str(), + preloadUserScriptSource.str().c_str(), WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, diff --git a/src/window/win.cc b/src/window/win.cc index 335e9a61c2..83f4a93b2e 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -910,7 +910,7 @@ namespace SSC { this->webview->AddScriptToExecuteOnDocumentCreated( // Note that this may not do anything as preload goes out of scope before event fires // Consider using w->preloadJavascript, but apps work without this - convertStringToWString(preloadUserScriptSource.compile().str()).c_str(), + convertStringToWString(preloadUserScriptSource.str()).c_str(), Microsoft::WRL::Callback<ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler>( [&](HRESULT error, PCWSTR id) -> HRESULT { return S_OK; } ).Get() From 0b34f0c846e51ebf3136641e83a8e611c948732e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:04:24 +0200 Subject: [PATCH 1066/1178] refator(window/android): introduce 'getPreloadUserScript()' --- src/window/window.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/window/window.kt b/src/window/window.kt index 8135aa8e93..fbf46b02d9 100644 --- a/src/window/window.kt +++ b/src/window/window.kt @@ -153,7 +153,7 @@ open class WindowWebChromeClient (val window: Window) : WebChromeClient() { open class Window (val fragment: WindowFragment) { val userMessageHandler = WindowWebViewUserMessageHandler(this) val activity = fragment.requireActivity() as WindowManagerActivity - val bridge = Bridge(fragment.index, activity as AppCompatActivity, this) + val bridge = Bridge(fragment.index, activity, this) val client = WindowWebChromeClient(this) val index = fragment.index var title = "" @@ -219,6 +219,10 @@ open class Window (val fragment: WindowFragment) { } } + fun getPreloadUserScript (): String { + return this.getPreloadUserScript(this.index) + } + fun getSize (): Size { val webview = this.fragment.webview return Size(webview.measuredWidth, webview.measuredHeight) From 84c309ead756fe1cec7fd1f16634b7a6c555d4e8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:04:50 +0200 Subject: [PATCH 1067/1178] refactor(platform/android/content_resolver): clean up --- src/platform/android/content_resolver.cc | 8 ++++---- src/platform/android/content_resolver.hh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/android/content_resolver.cc b/src/platform/android/content_resolver.cc index 2d20829014..3306234942 100644 --- a/src/platform/android/content_resolver.cc +++ b/src/platform/android/content_resolver.cc @@ -48,7 +48,7 @@ namespace SSC::Android { return url.hostname == "com.android.providers.media.documents"; } - String ContentResolver::getContentType (const String& uri) { + String ContentResolver::getContentMimeType (const String& uri) { const auto attachment = JNIEnvironmentAttachment(this->jvm); const auto platform = (jobject) CallObjectClassMethodFromAndroidEnvironment( attachment.env, @@ -57,15 +57,15 @@ namespace SSC::Android { "()Lsocket/runtime/app/AppPlatform;" ); - const auto contentTypeString = (jstring) CallObjectClassMethodFromAndroidEnvironment( + const auto contentMimeTypeString = (jstring) CallObjectClassMethodFromAndroidEnvironment( attachment.env, platform, - "getContentType", + "getContentMimeType", "(Ljava/lang/String;)Ljava/lang/String;", attachment.env->NewStringUTF(uri.c_str()) ); - return StringWrap(attachment.env, contentTypeString).str(); + return StringWrap(attachment.env, contentMimeTypeString).str(); } String ContentResolver::getPathnameFromURI (const String& uri) { diff --git a/src/platform/android/content_resolver.hh b/src/platform/android/content_resolver.hh index d966ba2ad5..2e4dd8357e 100644 --- a/src/platform/android/content_resolver.hh +++ b/src/platform/android/content_resolver.hh @@ -20,7 +20,7 @@ namespace SSC::Android { bool isMediaDocumentURI (const String& uri); bool isPhotosURI (const String& uri); - String getContentType (const String& url); + String getContentMimeType (const String& url); String getExternalContentURIForType (const String& type); String getPathnameFromURI (const String& uri); String getPathnameFromContentURIDataColumn ( From ca2dbaf17a6333ed73684207f812825a09b250d8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:05:08 +0200 Subject: [PATCH 1068/1178] refactor(ipc): introduce 'mime.lookup' route --- src/ipc/bridge.kt | 9 ++++++--- src/ipc/routes.cc | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/ipc/bridge.kt b/src/ipc/bridge.kt index dee275b5b3..629aca7dde 100644 --- a/src/ipc/bridge.kt +++ b/src/ipc/bridge.kt @@ -107,12 +107,15 @@ open class Bridge ( override fun onPageStarted ( view: WebView, url: String, - favicon: Bitmap + favicon: Bitmap? ) { - if (url.authority != "__BUNDLE_IDENTIFIER__") { + val uri = Uri.parse(url) + if (uri.authority != "__BUNDLE_IDENTIFIER__") { val preloadUserScript = this.window.getPreloadUserScript() - view.evaluateJavaScript(preloadUserScript, { _ -> }) + view.evaluateJavascript(preloadUserScript, { _ -> }) } + + super.onPageStarted(view, url, favicon) } fun emit (event: String, data: String): Boolean { diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 4d7cb1e897..0120bf5ec8 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -1,6 +1,7 @@ #include "../app/app.hh" #include "../cli/cli.hh" #include "../core/json.hh" +#include "../core/resource.hh" #include "../extension/extension.hh" #include "../window/window.hh" #include "ipc.hh" @@ -1716,6 +1717,24 @@ static void mapIPCRoutes (Router *router) { #endif }); + router->map("mime.lookup", [=](auto message, auto router, auto reply) { + auto err = validateMessageParameters(message, { "value" }); + + if (err.type != JSON::Type::Null) { + return reply(Result::Err { message, err }); + } + + auto resource = FileResource(message.value, FileResource::Options { + .cache = false, + .core = router->bridge->core.get() + }); + + reply(Result { message.seq, message, JSON::Object::Entries { + {"url", resource.url.str()}, + {"type", resource.mimeType()} + }}); + }); + router->map("notification.show", [=](auto message, auto router, auto reply) { auto err = validateMessageParameters(message, { "id", From 222c796ae0cc915972743a1168729176b8d3f409 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:05:48 +0200 Subject: [PATCH 1069/1178] fix(core/resource): handle 'url' in copy/move, fix mimeType for content-resolver --- src/core/resource.cc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/core/resource.cc b/src/core/resource.cc index 379a466b83..bccb718778 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -581,6 +581,7 @@ namespace SSC { FileResource::FileResource (const FileResource& resource) : Resource("FileResource", resource.name) { + this->url = resource.url; this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; @@ -591,12 +592,14 @@ namespace SSC { FileResource::FileResource (FileResource&& resource) : Resource("FileResource", resource.name) { + this->url = resource.url; this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; this->options = resource.options; this->accessing = resource.accessing.load(); + resource.url = URL {}; resource.bytes = nullptr; resource.cache.size = 0; resource.cache.bytes = nullptr; @@ -606,6 +609,7 @@ namespace SSC { } FileResource& FileResource::operator= (const FileResource& resource) { + this->url = resource.url; this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; @@ -618,12 +622,14 @@ namespace SSC { } FileResource& FileResource::operator= (FileResource&& resource) { + this->url = resource.url; this->path = resource.path; this->bytes = resource.bytes; this->cache = resource.cache; this->options = resource.options; this->accessing = resource.accessing.load(); + resource.url = URL {}; resource.bytes = nullptr; resource.cache.size = 0; resource.cache.bytes = nullptr; @@ -733,10 +739,6 @@ namespace SSC { } const String FileResource::mimeType () const noexcept { - if (!this->accessing) { - return ""; - } - const auto extension = this->path.extension().string(); if (extension.size() > 0) { // try in memory simle mime db @@ -826,11 +828,11 @@ namespace SSC { if (value.size() > 0) { return value; } + } - if (this->options.core && this->url.scheme == "content") { - auto core = this->options.core; - return core->platform.contentResolver.getContentType(this->url.str()); - } + if (this->options.core && this->url.scheme == "content") { + auto core = this->options.core; + return core->platform.contentResolver.getContentMimeType(this->url.str()); } #endif From ee75431fbfe6d9a4d467aefb4f3b526d306b6668 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:06:01 +0200 Subject: [PATCH 1070/1178] refactor(app): clean up --- src/app/app.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app.kt b/src/app/app.kt index a30ab7820d..2c39caa6fc 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -71,7 +71,7 @@ open class AppPlatform (val activity: AppActivity) { return uri.toString() } - fun getContentType (url: String): String { + fun getContentMimeType (url: String): String { return this.activity.applicationContext.contentResolver.getType(Uri.parse(url)) ?: "" } From c8d7036cc6cb00d9fff5392a8bcebabc0cefbf27 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:06:22 +0200 Subject: [PATCH 1071/1178] fix(api/util.js): handle non-string error 'stack' --- api/util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/util.js b/api/util.js index de25423b61..ea13e49b80 100644 --- a/api/util.js +++ b/api/util.js @@ -659,7 +659,7 @@ export function inspect (value, options) { if (value instanceof Error) { let out = '' - if (value?.message && !value?.stack?.startsWith(`${value?.name}: ${value?.message}`)) { + if (value?.message && !value?.stack?.startsWith?.(`${value?.name}: ${value?.message}`)) { out += `${value.name}: ${value.message}\n` } @@ -709,7 +709,7 @@ export function inspect (value, options) { return output.filter(Boolean).join(' ') } - out += (value.stack || '') + out += (typeof value?.stack === 'string' ? value.stack : '') .split('\n') .map((line) => line.includes(`${value.name}: ${value.message}`) || /^\s*at\s/.test(line) ? line From db30ec7f0dc9b462d017689ce680d34e0e1ae6ca Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:06:55 +0200 Subject: [PATCH 1072/1178] refactor(api/mime): query IPC for more results --- api/mime/index.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/api/mime/index.js b/api/mime/index.js index 89cf76d811..7131bce244 100644 --- a/api/mime/index.js +++ b/api/mime/index.js @@ -1,4 +1,6 @@ /* global XMLHttpRequest */ +import ipc from '../ipc.js' + /** * A container for a database lookup query. */ @@ -13,10 +15,15 @@ export class DatabaseQueryResult { */ mime = '' + /** + * @type {Database?} + */ + database = null + /** * `DatabaseQueryResult` class constructor. * @ignore - * @param {Database} database + * @param {Database|null} database * @param {string} name * @param {string} mime */ @@ -85,6 +92,7 @@ export class Database { for (const [key, value] of Object.entries(json)) { this.map.set(key, value) + // @ts-ignore this.index.set(value.toLowerCase(), key.toLowerCase()) } } @@ -113,6 +121,7 @@ export class Database { for (const [key, value] of Object.entries(json)) { this.map.set(key, value) + // @ts-ignore this.index.set(value.toLowerCase(), key.toLowerCase()) } } @@ -270,6 +279,16 @@ export async function lookup (query) { results.push(...result) } + if (query && results.length === 0) { + const result = await ipc.request('mime.lookup', query) + + if (result.err) { + throw result.err + } + + results.push(new DatabaseQueryResult(null, '', result.data.type)) + } + return results } @@ -286,6 +305,16 @@ export async function lookupSync (query) { results.push(...result) } + if (query && results.length === 0) { + const result = await ipc.sendSync('mime.lookup', query) + + if (result.err) { + throw result.err + } + + results.push(new DatabaseQueryResult(null, '', result.data.type)) + } + return results } @@ -317,6 +346,7 @@ export class MIMEType { const [type, subtype] = types this.#type = type.toLowerCase() + // @ts-ignore this.#params = new MIMEParams(args.map((a) => a.trim().split('=').map((v) => v.trim()))) this.#subtype = subtype } From bdb9c368ebafde6dbc50543cb372e292dfb81629 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:07:09 +0200 Subject: [PATCH 1073/1178] fix(api/internal/error.js): fix 'stack' value --- api/internal/error.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/internal/error.js b/api/internal/error.js index b671268706..107b0f6f77 100644 --- a/api/internal/error.js +++ b/api/internal/error.js @@ -183,14 +183,14 @@ function installRuntimeError (PlatformError, isBaseError = false) { } // prepareStackTrace is already called there if (target instanceof Error) { - target.stack = stack + target.stack = stack.join('\n') } else { const prepareStackTrace = Error.prepareStackTrace || globalThis.Error.prepareStackTrace if (typeof prepareStackTrace === 'function') { - const callsites = createCallSites(target, stack) + const callsites = createCallSites(target, stack.join('\n')) target.stack = prepareStackTrace(target, callsites) } else { - target.stack = stack + target.stack = stack.join('\n') } } } From 187a7d1283fa63ac18de658f7b4e7c371878874c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:07:36 +0200 Subject: [PATCH 1074/1178] refactor(api/fs): handle 'URL' file names --- api/fs/stream.js | 2 +- api/fs/web.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/fs/stream.js b/api/fs/stream.js index c79b0d6761..d3f5516a12 100644 --- a/api/fs/stream.js +++ b/api/fs/stream.js @@ -6,7 +6,7 @@ import { AbortError } from '../errors.js' import * as exports from './stream.js' -export const DEFAULT_STREAM_HIGH_WATER_MARK = 512 * 1024 +export const DEFAULT_STREAM_HIGH_WATER_MARK = 2 * 1024 * 1024 /** * @typedef {import('./handle.js').FileHandle} FileHandle diff --git a/api/fs/web.js b/api/fs/web.js index 8b7e87682c..29ed50ebad 100644 --- a/api/fs/web.js +++ b/api/fs/web.js @@ -104,7 +104,11 @@ export async function createFile (filename, options = null) { const decoder = new TextDecoder() const stats = options?.fd ? await options.fd.stat() : await fs.stat(filename) - const types = await mime.lookup(path.extname(filename).slice(1)) + const types = await mime.lookup( + URL.canParse(filename) + ? filename + : path.extname(filename).slice(1) + ) const type = types[0]?.mime ?? '' const highWaterMark = Number.isFinite(options?.highWaterMark) @@ -114,13 +118,17 @@ export async function createFile (filename, options = null) { let fd = options?.fd ?? null let blobBuffer = null + const name = URL.canParse(filename) + ? filename + : path.basename(filename) + return create(File, class File { get [kFileFullName] () { return filename } get [kFileDescriptor] () { return fd } get lastModifiedDate () { return new Date(stats.mtimeMs) } get lastModified () { return stats.mtimeMs } - get name () { return path.basename(filename) } + get name () { return name } get size () { return stats.size } get type () { return type } @@ -383,7 +391,7 @@ export async function createFileSystemFileHandle (file, options = null) { } async move (nameOrDestinationHandle, name = null) { - if (writable === false) { + if (writable === false || URL.canParse(file?.name)) { throw new NotAllowedError('FileSystemFileHandle is in \'readonly\' mode') } @@ -409,7 +417,7 @@ export async function createFileSystemFileHandle (file, options = null) { } async createWritable (options = null) { - if (writable === false) { + if (writable === false || URL.canParse(file?.name)) { throw new NotAllowedError('FileSystemFileHandle is in \'readonly\' mode') } From a3d355ea7ecc108149fcf01b6768b3f9233e8514 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:07:52 +0200 Subject: [PATCH 1075/1178] fix(api/dgram.js): check for detached array buffer in callback --- api/dgram.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index 3bbab41199..101d452b26 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -1026,11 +1026,16 @@ export class Socket extends EventEmitter { if (buffer?.buffer?.detached) { // XXX(@jwerle,@heapwolf): this is likely during a paused application state - // how should handle this? + // how should handle this? maybe a warning return } - return send(this, { id, port, address, buffer }, (...args) => { + return send(this, { id, port, address, buffer }, () => { + if (buffer.buffer?.detached) { + // XXX(@jwerle,@heapwolf): see above + return + } + if (typeof cb === 'function') { this.#resource.runInAsyncScope(() => { // eslint-disable-next-line From e93807ab15e62d763a9d51c7dd4a40080cfbe509 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 31 Jul 2024 16:08:46 +0200 Subject: [PATCH 1076/1178] chore(api): generate types + docs --- api/README.md | 28 +++++----- api/index.d.ts | 148 +++++++++++++++++++++++++------------------------ 2 files changed, 90 insertions(+), 86 deletions(-) diff --git a/api/README.md b/api/README.md index bce6bd450f..c92fea2621 100644 --- a/api/README.md +++ b/api/README.md @@ -618,7 +618,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1051) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1056) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -630,7 +630,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1123) +### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1128) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -646,7 +646,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1158) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1163) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -661,7 +661,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1189) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1194) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -672,7 +672,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1206) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1211) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -683,12 +683,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1219) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1224) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1227) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1232) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -697,31 +697,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1295) +### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1300) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1301) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1306) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1318) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1323) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1325) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1330) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1333) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1338) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1340) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1345) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1350) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1355) Thrown when a bad port is given. diff --git a/api/index.d.ts b/api/index.d.ts index 4b32d87f84..1f15118e33 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -1976,11 +1976,11 @@ declare module "socket:mime/index" { /** * `DatabaseQueryResult` class constructor. * @ignore - * @param {Database} database + * @param {Database|null} database * @param {string} name * @param {string} mime */ - constructor(database: Database, name: string, mime: string); + constructor(database: Database | null, name: string, mime: string); /** * @type {string} */ @@ -1989,7 +1989,10 @@ declare module "socket:mime/index" { * @type {string} */ mime: string; - database: Database; + /** + * @type {Database?} + */ + database: Database | null; } /** * A container for MIME types by class (audio, video, text, etc) @@ -2161,7 +2164,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2837,7 +2840,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3078,7 +3081,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3108,7 +3111,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): any; + export function getCode(name: string | number): signal; /** * Gets the name for a given 'signal' code * @return {string} @@ -3252,7 +3255,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3633,7 +3636,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } @@ -4543,9 +4546,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -5273,7 +5276,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6146,7 +6149,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6990,7 +6993,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "nested" | "top-level"; + get frameType(): "none" | "top-level" | "nested"; /** * The type of the client. * @type {'window'|'worker'} @@ -7022,10 +7025,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: 'window' | 'worker'; + type?: "window" | "worker"; parent?: object | null; top?: object | null; - frameType?: 'top-level' | 'nested' | 'none'; + frameType?: "top-level" | "nested" | "none"; }; } @@ -7099,7 +7102,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7312,6 +7315,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8036,7 +8040,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8082,9 +8086,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; }; } export namespace Promise { @@ -8704,7 +8708,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9913,7 +9917,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9988,7 +9992,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10484,7 +10488,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10548,7 +10552,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -10680,7 +10684,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10721,7 +10725,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -10908,7 +10912,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10943,13 +10947,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13641,7 +13645,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -13649,7 +13653,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13898,7 +13902,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -13909,7 +13913,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15190,7 +15194,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -15201,13 +15205,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -15291,7 +15295,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -15335,7 +15339,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15695,7 +15699,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15733,9 +15737,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:module" { @@ -16075,7 +16079,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -16261,9 +16265,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -16979,7 +16983,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -16999,7 +17003,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17021,8 +17025,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -17032,10 +17036,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -17136,7 +17140,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17145,7 +17149,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17226,8 +17230,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17241,7 +17245,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17690,12 +17694,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17748,7 +17752,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17765,8 +17769,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 7a875aca487c3e5d392840c7ce8296a3ac472938 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 1 Aug 2024 08:52:18 -0400 Subject: [PATCH 1077/1178] fix(core/modules/conduit): do not use private UV APIs, fix mem leak --- src/core/modules/conduit.cc | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index 6f56b1d8d9..bdd3a220f3 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -193,15 +193,24 @@ namespace SSC { String response = oss.str(); // debug(response.c_str()); - uv_buf_t wrbuf = uv_buf_init(strdup(response.c_str()), response.size()); + const auto size = response.size(); + const auto data = new char[size]{0}; + memcpy(data, response.c_str(), size); + + uv_buf_t wrbuf = uv_buf_init(data, size); uv_write_t *writeRequest = new uv_write_t; - writeRequest->bufs = &wrbuf; + uv_handle_set_data(reinterpret_cast<uv_handle_t*>(writeRequest), data); + //writeRequest->bufs = &wrbuf; uv_write(writeRequest, (uv_stream_t*)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { // if (status) debug(stderr, "write error %s\n", uv_strerror(status)); - if (req->bufs != nullptr) { - free(req->bufs->base); + auto data = reinterpret_cast<char*>( + uv_handle_get_data(reinterpret_cast<uv_handle_t*>(req)) + ); + + if (data != nullptr) { + delete [] data; } free(req); @@ -342,10 +351,11 @@ namespace SSC { frame[0] = 0x82; // FIN and opcode 2 (binary) std::memcpy(frame.data() + frame.size() - encodedLength, encodedMessage.data(), encodedLength); - uv_buf_t wrbuf = uv_buf_init(reinterpret_cast<char*>(frame.data()), frame.size()); auto writeReq = new uv_write_t; - writeReq->bufs = &wrbuf; - writeReq->data = new Vector<unsigned char>(std::move(frame)); + auto data = reinterpret_cast<char*>(frame.data()); + auto size = frame.size(); + + uv_buf_t wrbuf = uv_buf_init(data, size); uv_write(writeReq, (uv_stream_t*)&this->handle, &wrbuf, 1, [](uv_write_t* req, int status) { if (status) { From 235154e679b601c6331f3a90c7d291ac985a4f11 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 1 Aug 2024 08:53:16 -0400 Subject: [PATCH 1078/1178] fix(ipc/bridge): explicit opt-out of auth component on 'npm:' URIs to allow for '@' character --- src/ipc/bridge.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index f4beeadb4d..807074069a 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -1060,7 +1060,9 @@ export * from '{{url}}' ); registration->SetAllowedOrigins(allowedOriginsCount, allowedOrigins); - registration->put_HasAuthorityComponent(true); + if (entry.first != "npm") { + registration->put_HasAuthorityComponent(true); + } registration->put_TreatAsSecure(true); registrations[registrationsCount++] = registration.Get(); registrationsSet.insert(registration); From 18714497a5ce01854606b4d091a8e170e996adae Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 1 Aug 2024 08:53:31 -0400 Subject: [PATCH 1079/1178] fix(window/win): fix typos --- src/window/win.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/window/win.cc b/src/window/win.cc index 83f4a93b2e..e4d30fb38a 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -1312,12 +1312,12 @@ namespace SSC { this->webview->ExecuteScript( convertStringToWString(source).c_str(), Microsoft::WRL::Callback<ICoreWebView2ExecuteScriptCompletedHandler>( - [=, this](HRESULT error, LPCWSTR result) { + [=, this](HRESULT error, PCWSTR result) -> HRESULT { if (callback != nullptr) { if (error != S_OK) { // TODO(@jwerle): figure out how to get the error message here - callback(JSON::Error("An unknown error occurred")) - return; + callback(JSON::Error("An unknown error occurred")); + return error; } const auto string = convertWStringToString(result); @@ -1334,7 +1334,7 @@ namespace SSC { number = std::stod(string); } catch (...) { callback(string); - return; + return S_OK; } callback(number); @@ -1343,7 +1343,7 @@ namespace SSC { return S_OK; } - ) + ).Get() ); }); } From 63d9b2306a936bf383e696bfc66a8395123da421 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 2 Aug 2024 12:46:33 +0200 Subject: [PATCH 1080/1178] chore(clib.json): remove 'src' array --- clib.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/clib.json b/clib.json index 55e440a4ea..cd58ce7db1 100644 --- a/clib.json +++ b/clib.json @@ -3,10 +3,5 @@ "repo": "socketsupply/socket", "version": "0.6.0-next", "description": "Build and package lean, fast, native desktop and mobile applications using the web technologies you already know.", - "install": "install.sh", - "src": [ - "bin/install.sh", - "LICENSE.txt", - "README.md" - ] + "install": "install.sh" } From 54449f63aa41a91ade20d3966e963de35db4166e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 21:05:16 +0200 Subject: [PATCH 1081/1178] refactor(api/commonjs): handle package export fields as arrays --- api/commonjs.js | 23 ++++++++++++++++++ api/commonjs/builtins.js | 1 + api/commonjs/cache.js | 4 ++-- api/commonjs/loader.js | 41 ++++++++++++++++++++++++++++++- api/commonjs/package.js | 52 ++++++++++++++++++++++++++++++---------- api/commonjs/require.js | 6 ++--- 6 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 api/commonjs.js diff --git a/api/commonjs.js b/api/commonjs.js new file mode 100644 index 0000000000..fb3da723db --- /dev/null +++ b/api/commonjs.js @@ -0,0 +1,23 @@ +/** + * @module commonjs + */ +import builtins, { defineBuiltin } from './commonjs/builtins.js' +import createRequire from './commonjs/require.js' +import Package from './commonjs/package.js' +import Module from './commonjs/module.js' +import Loader from './commonjs/loader.js' +import Cache from './commonjs/cache.js' + +import * as exports from './commonjs.js' + +export default exports +export { + builtins, + Cache, + createRequire, + Loader, + Module, + Package +} + +defineBuiltin('commonjs', exports) diff --git a/api/commonjs/builtins.js b/api/commonjs/builtins.js index 322a0dd542..a3351c8b39 100644 --- a/api/commonjs/builtins.js +++ b/api/commonjs/builtins.js @@ -165,6 +165,7 @@ defineBuiltin('window', window) export const runtimeModules = new Set([ 'async', 'application', + 'commonjs', 'commonjs/builtins', 'commonjs/cache', 'commonjs/loader', diff --git a/api/commonjs/cache.js b/api/commonjs/cache.js index 9378a7b3f1..c96acecb44 100644 --- a/api/commonjs/cache.js +++ b/api/commonjs/cache.js @@ -323,9 +323,9 @@ export class CacheCollection { /** * `CacheCollection` class constructor. * @ignore - * @param {Cache[]|Record<string, Cache>} collection + * @param {Cache[]|Record<string, Cache>=} [collection] */ - constructor (collection) { + constructor (collection = null) { if (collection && typeof collection === 'object') { if (Array.isArray(collection)) { for (const value of collection) { diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index e18d2bd67e..0282ec5519 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -5,11 +5,13 @@ import { CacheCollection, Cache } from './cache.js' import { defineBuiltin } from './builtins.js' import InternalSymbols from '../internal/symbols.js' +import application from '../application.js' import { Headers } from '../ipc.js' import location from '../location.js' import path from '../path.js' import URL from '../url.js' import os from '../os.js' +import fs from '../fs.js' const RUNTIME_SERVICE_WORKER_FETCH_MODE = 'Runtime-ServiceWorker-Fetch-Mode' const RUNTIME_REQUEST_SOURCE_HEADER = 'Runtime-Request-Source' @@ -197,8 +199,21 @@ export class RequestStatus { return this } - const request = new XMLHttpRequest() + if ( + this.#request.id.includes(`://${application.config.meta_bundle_identifier}`) + ) { + try { + const id = this.#request.id.replace('https:', 'socket:') + fs.accessSync(id) + } catch { + this.#request.loader.cache.status.set(this.id, this) + this.#headers = new Headers() + this.#status = 404 + return this + } + } + const request = new XMLHttpRequest() request.open('HEAD', this.#request.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') @@ -213,6 +228,7 @@ export class RequestStatus { if (this.#request?.loader) { const entries = this.#request.loader.headers.entries() for (const entry of entries) { + // @ts-ignore request.setRequestHeader(...entry) } } @@ -223,6 +239,7 @@ export class RequestStatus { : Object.entries(options.headers) for (const entry of entries) { + // @ts-ignore request.setRequestHeader(...entry) } } @@ -314,6 +331,7 @@ export class Request { static from (json, options) { return new this(json.url, { status: json.status && typeof json.status === 'object' + // @ts-ignore ? RequestStatus.from(json.status) : options?.status, ...options @@ -413,6 +431,20 @@ export class Request { }) } + if ( + /^(socket:|https:)/.test(this.id) && + this.id.includes(`//${application.config.meta_bundle_identifier}/`) + ) { + try { + const id = this.id.replace('https:', 'socket:') + fs.accessSync(id) + } catch { + return new Response(this, { + status: 404 + }) + } + } + const request = new XMLHttpRequest() request.open('GET', this.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') @@ -432,6 +464,7 @@ export class Request { if (this.#loader) { const entries = this.#loader.headers.entries() for (const entry of entries) { + // @ts-ignore request.setRequestHeader(...entry) } } @@ -442,6 +475,7 @@ export class Request { : Object.entries(options.headers) for (const entry of entries) { + // @ts-ignore request.setRequestHeader(...entry) } } @@ -741,10 +775,12 @@ export class Loader { if (options?.headers && typeof options.headers === 'object') { if (Array.isArray(options.headers)) { for (const entry of options.headers) { + // @ts-ignore this.#headers.set(...entry) } } else if (typeof options.headers.entries === 'function') { for (const entry of options.headers.entries()) { + // @ts-ignore this.#headers.set(...entry) } } else { @@ -861,7 +897,9 @@ export class Loader { url = this.resolve(url, origin) + // @ts-ignore if (this.#cache.status.has(url)) { + // @ts-ignore return this.#cache.status.get(url) } @@ -871,6 +909,7 @@ export class Loader { ...options }) + // @ts-ignore this.#cache.status.set(url, request.status) return request.status } diff --git a/api/commonjs/package.js b/api/commonjs/package.js index 451ffffec4..aac76cdda2 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -773,6 +773,26 @@ export class Package { get entry () { let entry = null + if (Array.isArray(this.#exports['.'])) { + for (const exports of this.#exports['.']) { + if (typeof exports === 'string') { + entry = exports + } else if (exports && typeof exports === 'object') { + if (isWorkerScope && exports.worker) { + entry = exports.worker + } else if (this.type === 'commonjs') { + entry = exports.require || exports.default + } else if (this.type === 'module') { + entry = exports.import || exports.default + } + } + + if (entry) { + break + } + } + } + if (this.type === 'commonjs') { entry = this.#exports['.'].require } else if (this.type === 'module') { @@ -784,8 +804,8 @@ export class Package { } if (isWorkerScope) { - if (!entry && this.#exports['.'].entry) { - entry = this.#exports['.'].entry + if (!entry && this.#exports['.'].worker) { + entry = this.#exports['.'].worker } } @@ -1131,11 +1151,13 @@ export class Package { for (const extension of extensions) { for (const key in this.#exports) { - const exports = this.#exports[key] const query = pathname !== '.' && pathname !== './' ? pathname + extension : pathname + let exports = this.#exports[key] + let filename = null + if ( key === query || key === pathname.replace(extname, '') || @@ -1144,16 +1166,20 @@ export class Package { (pathname === './index.js' && key === '.') ) { if (Array.isArray(exports)) { - for (const filename of exports) { - if (typeof filename === 'string') { - const response = this.loader.load(filename, origin, options) + for (const entry of exports) { + if (typeof entry === 'string') { + const response = this.loader.load(entry, origin, options) if (response.ok) { return interpolateBrowserResolution(response.id) } + } else if (entry && typeof entry === 'object') { + exports = entry + break } } - } else { - let filename = null + } + + if (exports && !Array.isArray(exports) && typeof exports === 'object') { if (isWorkerScope && exports.worker) { filename = exports.worker } if (type === 'commonjs' && exports.require) { @@ -1172,12 +1198,12 @@ export class Package { exports.default ) } + } - if (filename) { - const response = this.loader.load(filename, origin, options) - if (response.ok) { - return interpolateBrowserResolution(response.id) - } + if (filename) { + const response = this.loader.load(filename, origin, options) + if (response.ok) { + return interpolateBrowserResolution(response.id) } } } diff --git a/api/commonjs/require.js b/api/commonjs/require.js index 0f9cd874f6..bd86e33a10 100644 --- a/api/commonjs/require.js +++ b/api/commonjs/require.js @@ -202,7 +202,7 @@ export function createRequire (options) { } const resolved = resolve(input, { - type: module.package.type, + type: 'commonjs', ...options }) @@ -294,7 +294,7 @@ export function createRequire (options) { // A URL was given, try to resolve it as a package if (URL.canParse(input)) { return module.package.resolve(input, { - type: module.package.type, + type: 'commonjs', ...options }) } @@ -318,7 +318,7 @@ export function createRequire (options) { try { return pkg.resolve(pathname, { - type: module.package.type, + type: 'commonjs', ...options }) } catch (err) { From 7fb5520010752124a75909bd2dfde335a547f298 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 21:05:40 +0200 Subject: [PATCH 1082/1178] fix(api/npm/service-worker.js): improve 'export default' detection --- api/npm/service-worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index d5b0364b90..9eb3096cc3 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -126,7 +126,7 @@ export async function onRequest (request, env, ctx) { if (resolved.type === 'module') { const response = await fetch(resolved.url) const text = await response.text() - const proxy = text.includes('export default') + const proxy = /^(export default)/.test(text) ? ` import module from '${resolved.url}' export * from '${resolved.url}' From 32dc2918a75f59c900147b5579e04f5b6afa2665 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 21:05:48 +0200 Subject: [PATCH 1083/1178] chore(api): generate types --- api/index.d.ts | 685 +++++++++++++++++++++++++------------------------ 1 file changed, 349 insertions(+), 336 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 1f15118e33..61cb65bd07 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -9705,62 +9705,6 @@ declare module "socket:dgram" { } -declare module "socket:enumeration" { - /** - * @module enumeration - * This module provides a data structure for enumerated unique values. - */ - /** - * A container for enumerated values. - */ - export class Enumeration extends Set<any> { - /** - * Creates an `Enumeration` instance from arguments. - * @param {...any} values - * @return {Enumeration} - */ - static from(...values: any[]): Enumeration; - /** - * `Enumeration` class constructor. - * @param {any[]} values - * @param {object=} [options = {}] - * @param {number=} [options.start = 0] - */ - constructor(values: any[], options?: object | undefined); - /** - * @type {number} - */ - get length(): number; - /** - * Returns `true` if enumeration contains `value`. An alias - * for `Set.prototype.has`. - * @return {boolean} - */ - contains(value: any): boolean; - /** - * @ignore - */ - add(): void; - /** - * @ignore - */ - delete(): void; - /** - * JSON represenation of a `Enumeration` instance. - * @ignore - * @return {string[]} - */ - toJSON(): string[]; - /** - * Internal inspect function. - * @ignore - * @return {LanguageQueryResult} - */ - inspect(): LanguageQueryResult; - } - export default Enumeration; -} - declare module "socket:fs/web" { /** * Creates a new `File` instance from `filename`. @@ -10072,76 +10016,6 @@ declare module "socket:extension" { const $loaded: unique symbol; } -declare module "socket:fetch/fetch" { - export function Headers(headers: any): void; - export class Headers { - constructor(headers: any); - map: {}; - append(name: any, value: any): void; - delete(name: any): void; - get(name: any): any; - has(name: any): boolean; - set(name: any, value: any): void; - forEach(callback: any, thisArg: any): void; - keys(): { - next: () => { - done: boolean; - value: any; - }; - }; - values(): { - next: () => { - done: boolean; - value: any; - }; - }; - entries(): { - next: () => { - done: boolean; - value: any; - }; - }; - } - export function Request(input: any, options: any): void; - export class Request { - constructor(input: any, options: any); - url: string; - credentials: any; - headers: Headers; - method: any; - mode: any; - signal: any; - referrer: any; - clone(): Request; - } - export function Response(bodyInit: any, options: any): void; - export class Response { - constructor(bodyInit: any, options: any); - type: string; - status: any; - ok: boolean; - statusText: string; - headers: Headers; - url: any; - clone(): Response; - } - export namespace Response { - function error(): Response; - function redirect(url: any, status: any): Response; - } - export function fetch(input: any, init: any): Promise<any>; - export class DOMException { - private constructor(); - } - namespace _default { - export { fetch }; - export { Headers }; - export { Request }; - export { Response }; - } - export default _default; -} - declare module "socket:internal/database" { /** * A typed container for optional options given to the `Database` @@ -11733,21 +11607,6 @@ declare module "socket:http" { } -declare module "socket:fetch/index" { - export default fetch; - import { fetch } from "socket:fetch/fetch"; - import { Headers } from "socket:fetch/fetch"; - import { Request } from "socket:fetch/fetch"; - import { Response } from "socket:fetch/fetch"; - export { fetch, Headers, Request, Response }; -} - -declare module "socket:fetch" { - export * from "socket:fetch/index"; - export default fetch; - import fetch from "socket:fetch/index"; -} - declare module "socket:https" { /** * Makes a HTTPS request, optionally a `socket://` for relative paths when @@ -11914,6 +11773,62 @@ declare module "socket:https" { import * as exports from "socket:http"; } +declare module "socket:enumeration" { + /** + * @module enumeration + * This module provides a data structure for enumerated unique values. + */ + /** + * A container for enumerated values. + */ + export class Enumeration extends Set<any> { + /** + * Creates an `Enumeration` instance from arguments. + * @param {...any} values + * @return {Enumeration} + */ + static from(...values: any[]): Enumeration; + /** + * `Enumeration` class constructor. + * @param {any[]} values + * @param {object=} [options = {}] + * @param {number=} [options.start = 0] + */ + constructor(values: any[], options?: object | undefined); + /** + * @type {number} + */ + get length(): number; + /** + * Returns `true` if enumeration contains `value`. An alias + * for `Set.prototype.has`. + * @return {boolean} + */ + contains(value: any): boolean; + /** + * @ignore + */ + add(): void; + /** + * @ignore + */ + delete(): void; + /** + * JSON represenation of a `Enumeration` instance. + * @ignore + * @return {string[]} + */ + toJSON(): string[]; + /** + * Internal inspect function. + * @ignore + * @return {LanguageQueryResult} + */ + inspect(): LanguageQueryResult; + } + export default Enumeration; +} + declare module "socket:language" { /** * Look up a language name or code by query. @@ -12059,93 +11974,27 @@ declare module "socket:language" { import Enumeration from "socket:enumeration"; } -declare module "socket:i18n" { - /** - * Get messages for `locale` pattern. This function could return many results - * for various locales given a `locale` pattern. such as `fr`, which could - * return results for `fr`, `fr-FR`, `fr-BE`, etc. - * @ignore - * @param {string} locale - * @return {object[]} - */ - export function getMessagesForLocale(locale: string): object[]; - /** - * Returns user preferred ISO 639 language codes or RFC 5646 language tags. - * @return {string[]} - */ - export function getAcceptLanguages(): string[]; - /** - * Returns the current user ISO 639 language code or RFC 5646 language tag. - * @return {?string} - */ - export function getUILanguage(): string | null; +declare module "socket:latica/packets" { /** - * Gets a localized message string for the specified message name. - * @param {string} messageName - * @param {object|string[]=} [substitutions = []] - * @param {object=} [options] - * @param {string=} [options.locale = null] - * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} - * @see {@link https://www.ibm.com/docs/en/rbd/9.5.1?topic=syslib-getmessage} - * @return {?string} + * The magic bytes prefixing every packet. They are the + * 2nd, 3rd, 5th, and 7th, prime numbers. + * @type {number[]} */ - export function getMessage(messageName: string, substitutions?: (object | string[]) | undefined, options?: object | undefined): string | null; + export const MAGIC_BYTES_PREFIX: number[]; /** - * Gets a localized message description string for the specified message name. - * @param {string} messageName - * @param {object=} [options] - * @param {string=} [options.locale = null] - * @return {?string} + * The version of the protocol. */ - export function getMessageDescription(messageName: string, options?: object | undefined): string | null; + export const VERSION: 6; /** - * A cache of loaded locale messages. - * @type {Map} + * The size in bytes of the prefix magic bytes. */ - export const cache: Map<any, any>; + export const MAGIC_BYTES: 4; /** - * Default location of i18n locale messages - * @type {string} + * The maximum size of the user message. */ - export const DEFAULT_LOCALES_LOCATION: string; + export const MESSAGE_BYTES: 1024; /** - * An enumeration of supported ISO 639 language codes or RFC 5646 language tags. - * @type {Enumeration} - * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/LanguageCode} - * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} - */ - export const LanguageCode: Enumeration; - namespace _default { - export { LanguageCode }; - export { getAcceptLanguages }; - export { getMessage }; - export { getUILanguage }; - } - export default _default; - import Enumeration from "socket:enumeration"; -} - -declare module "socket:latica/packets" { - /** - * The magic bytes prefixing every packet. They are the - * 2nd, 3rd, 5th, and 7th, prime numbers. - * @type {number[]} - */ - export const MAGIC_BYTES_PREFIX: number[]; - /** - * The version of the protocol. - */ - export const VERSION: 6; - /** - * The size in bytes of the prefix magic bytes. - */ - export const MAGIC_BYTES: 4; - /** - * The maximum size of the user message. - */ - export const MESSAGE_BYTES: 1024; - /** - * The cache TTL in milliseconds. + * The cache TTL in milliseconds. */ export const CACHE_TTL: number; export namespace PACKET_SPEC { @@ -13315,33 +13164,6 @@ declare module "socket:latica/api" { export function api(options: object, events: object, dgram: object): Promise<events.EventEmitter>; } -declare module "socket:node/index" { - export default network; - export function network(options: any): Promise<events.EventEmitter>; - import { Cache } from "socket:latica/index"; - import { sha256 } from "socket:latica/index"; - import { Encryption } from "socket:latica/index"; - import { Packet } from "socket:latica/index"; - import { NAT } from "socket:latica/index"; - export { Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:index" { - import { network } from "socket:node/index"; - import { Cache } from "socket:node/index"; - import { sha256 } from "socket:node/index"; - import { Encryption } from "socket:node/index"; - import { Packet } from "socket:node/index"; - import { NAT } from "socket:node/index"; - export { network, Cache, sha256, Encryption, Packet, NAT }; -} - -declare module "socket:latica" { - export * from "socket:latica/index"; - export default def; - import def from "socket:latica/index"; -} - declare module "socket:network" { export default network; export function network(options: any): Promise<events.EventEmitter>; @@ -14262,9 +14084,9 @@ declare module "socket:commonjs/cache" { /** * `CacheCollection` class constructor. * @ignore - * @param {Cache[]|Record<string, Cache>} collection + * @param {Cache[]|Record<string, Cache>=} [collection] */ - constructor(collection: Cache[] | Record<string, Cache>); + constructor(collection?: (Cache[] | Record<string, Cache>) | undefined); /** * Adds a `Cache` instance to the collection. * @param {string|Cache} name @@ -15239,94 +15061,6 @@ declare module "socket:commonjs/package" { import { Loader } from "socket:commonjs/loader"; } -declare module "socket:commonjs/require" { - /** - * Factory for creating a `require()` function based on a module context. - * @param {CreateRequireOptions} options - * @return {RequireFunction} - */ - export function createRequire(options: CreateRequireOptions): RequireFunction; - /** - * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver - */ - /** - * @typedef {{ - * module: import('./module.js').Module, - * prefix?: string, - * request?: import('./loader.js').RequestOptions, - * builtins?: object, - * resolvers?: RequireFunction[] - * }} CreateRequireOptions - */ - /** - * @typedef {function(string): any} RequireFunction - */ - /** - * @typedef {import('./package.js').PackageOptions} PackageOptions - */ - /** - * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions - */ - /** - * @typedef { - * PackageResolveOptions & - * PackageOptions & - * { origins?: string[] | URL[] } - * } ResolveOptions - */ - /** - * @typedef {ResolveOptions & { - * resolvers?: RequireResolver[], - * importmap?: import('./module.js').ImportMap, - * cache?: boolean - * }} RequireOptions - */ - /** - * An array of global require paths, relative to the origin. - * @type {string[]} - */ - export const globalPaths: string[]; - /** - * An object attached to a `require()` function that contains metadata - * about the current module context. - */ - export class Meta { - /** - * `Meta` class constructor. - * @param {import('./module.js').Module} module - */ - constructor(module: import("socket:commonjs/module").Module); - /** - * The referrer (parent) of this module. - * @type {string} - */ - get referrer(): string; - /** - * The referrer (parent) of this module. - * @type {string} - */ - get url(): string; - #private; - } - export default createRequire; - export type RequireResolver = (arg0: string, arg1: import("socket:commonjs/module").Module, arg2: (arg0: string) => any) => any; - export type CreateRequireOptions = { - module: import("socket:commonjs/module").Module; - prefix?: string; - request?: import("socket:commonjs/loader").RequestOptions; - builtins?: object; - resolvers?: RequireFunction[]; - }; - export type RequireFunction = (arg0: string) => any; - export type PackageOptions = import("socket:commonjs/package").PackageOptions; - export type PackageResolveOptions = import("socket:commonjs/package").PackageResolveOptions; - export type RequireOptions = ResolveOptions & { - resolvers?: RequireResolver[]; - importmap?: import("socket:commonjs/module").ImportMap; - cache?: boolean; - }; -} - declare module "socket:commonjs/module" { /** * CommonJS module scope with module scoped globals. @@ -15742,6 +15476,285 @@ declare module "socket:commonjs/module" { import process from "socket:process"; } +declare module "socket:commonjs/require" { + /** + * Factory for creating a `require()` function based on a module context. + * @param {CreateRequireOptions} options + * @return {RequireFunction} + */ + export function createRequire(options: CreateRequireOptions): RequireFunction; + /** + * @typedef {function(string, import('./module.js').Module, function(string): any): any} RequireResolver + */ + /** + * @typedef {{ + * module: import('./module.js').Module, + * prefix?: string, + * request?: import('./loader.js').RequestOptions, + * builtins?: object, + * resolvers?: RequireFunction[] + * }} CreateRequireOptions + */ + /** + * @typedef {function(string): any} RequireFunction + */ + /** + * @typedef {import('./package.js').PackageOptions} PackageOptions + */ + /** + * @typedef {import('./package.js').PackageResolveOptions} PackageResolveOptions + */ + /** + * @typedef { + * PackageResolveOptions & + * PackageOptions & + * { origins?: string[] | URL[] } + * } ResolveOptions + */ + /** + * @typedef {ResolveOptions & { + * resolvers?: RequireResolver[], + * importmap?: import('./module.js').ImportMap, + * cache?: boolean + * }} RequireOptions + */ + /** + * An array of global require paths, relative to the origin. + * @type {string[]} + */ + export const globalPaths: string[]; + /** + * An object attached to a `require()` function that contains metadata + * about the current module context. + */ + export class Meta { + /** + * `Meta` class constructor. + * @param {import('./module.js').Module} module + */ + constructor(module: import("socket:commonjs/module").Module); + /** + * The referrer (parent) of this module. + * @type {string} + */ + get referrer(): string; + /** + * The referrer (parent) of this module. + * @type {string} + */ + get url(): string; + #private; + } + export default createRequire; + export type RequireResolver = (arg0: string, arg1: import("socket:commonjs/module").Module, arg2: (arg0: string) => any) => any; + export type CreateRequireOptions = { + module: import("socket:commonjs/module").Module; + prefix?: string; + request?: import("socket:commonjs/loader").RequestOptions; + builtins?: object; + resolvers?: RequireFunction[]; + }; + export type RequireFunction = (arg0: string) => any; + export type PackageOptions = import("socket:commonjs/package").PackageOptions; + export type PackageResolveOptions = import("socket:commonjs/package").PackageResolveOptions; + export type RequireOptions = ResolveOptions & { + resolvers?: RequireResolver[]; + importmap?: import("socket:commonjs/module").ImportMap; + cache?: boolean; + }; +} + +declare module "socket:commonjs" { + export default exports; + import * as exports from "socket:commonjs"; + import builtins from "socket:commonjs/builtins"; + import Cache from "socket:commonjs/cache"; + import createRequire from "socket:commonjs/require"; + import Loader from "socket:commonjs/loader"; + import Module from "socket:commonjs/module"; + import Package from "socket:commonjs/package"; + + export { builtins, Cache, createRequire, Loader, Module, Package }; +} + +declare module "socket:fetch/fetch" { + export function Headers(headers: any): void; + export class Headers { + constructor(headers: any); + map: {}; + append(name: any, value: any): void; + delete(name: any): void; + get(name: any): any; + has(name: any): boolean; + set(name: any, value: any): void; + forEach(callback: any, thisArg: any): void; + keys(): { + next: () => { + done: boolean; + value: any; + }; + }; + values(): { + next: () => { + done: boolean; + value: any; + }; + }; + entries(): { + next: () => { + done: boolean; + value: any; + }; + }; + } + export function Request(input: any, options: any): void; + export class Request { + constructor(input: any, options: any); + url: string; + credentials: any; + headers: Headers; + method: any; + mode: any; + signal: any; + referrer: any; + clone(): Request; + } + export function Response(bodyInit: any, options: any): void; + export class Response { + constructor(bodyInit: any, options: any); + type: string; + status: any; + ok: boolean; + statusText: string; + headers: Headers; + url: any; + clone(): Response; + } + export namespace Response { + function error(): Response; + function redirect(url: any, status: any): Response; + } + export function fetch(input: any, init: any): Promise<any>; + export class DOMException { + private constructor(); + } + namespace _default { + export { fetch }; + export { Headers }; + export { Request }; + export { Response }; + } + export default _default; +} + +declare module "socket:fetch/index" { + export default fetch; + import { fetch } from "socket:fetch/fetch"; + import { Headers } from "socket:fetch/fetch"; + import { Request } from "socket:fetch/fetch"; + import { Response } from "socket:fetch/fetch"; + export { fetch, Headers, Request, Response }; +} + +declare module "socket:fetch" { + export * from "socket:fetch/index"; + export default fetch; + import fetch from "socket:fetch/index"; +} + +declare module "socket:i18n" { + /** + * Get messages for `locale` pattern. This function could return many results + * for various locales given a `locale` pattern. such as `fr`, which could + * return results for `fr`, `fr-FR`, `fr-BE`, etc. + * @ignore + * @param {string} locale + * @return {object[]} + */ + export function getMessagesForLocale(locale: string): object[]; + /** + * Returns user preferred ISO 639 language codes or RFC 5646 language tags. + * @return {string[]} + */ + export function getAcceptLanguages(): string[]; + /** + * Returns the current user ISO 639 language code or RFC 5646 language tag. + * @return {?string} + */ + export function getUILanguage(): string | null; + /** + * Gets a localized message string for the specified message name. + * @param {string} messageName + * @param {object|string[]=} [substitutions = []] + * @param {object=} [options] + * @param {string=} [options.locale = null] + * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + * @see {@link https://www.ibm.com/docs/en/rbd/9.5.1?topic=syslib-getmessage} + * @return {?string} + */ + export function getMessage(messageName: string, substitutions?: (object | string[]) | undefined, options?: object | undefined): string | null; + /** + * Gets a localized message description string for the specified message name. + * @param {string} messageName + * @param {object=} [options] + * @param {string=} [options.locale = null] + * @return {?string} + */ + export function getMessageDescription(messageName: string, options?: object | undefined): string | null; + /** + * A cache of loaded locale messages. + * @type {Map} + */ + export const cache: Map<any, any>; + /** + * Default location of i18n locale messages + * @type {string} + */ + export const DEFAULT_LOCALES_LOCATION: string; + /** + * An enumeration of supported ISO 639 language codes or RFC 5646 language tags. + * @type {Enumeration} + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/LanguageCode} + * @see {@link https://developer.chrome.com/docs/extensions/reference/i18n/#type-LanguageCode} + */ + export const LanguageCode: Enumeration; + namespace _default { + export { LanguageCode }; + export { getAcceptLanguages }; + export { getMessage }; + export { getUILanguage }; + } + export default _default; + import Enumeration from "socket:enumeration"; +} + +declare module "socket:node/index" { + export default network; + export function network(options: any): Promise<events.EventEmitter>; + import { Cache } from "socket:latica/index"; + import { sha256 } from "socket:latica/index"; + import { Encryption } from "socket:latica/index"; + import { Packet } from "socket:latica/index"; + import { NAT } from "socket:latica/index"; + export { Cache, sha256, Encryption, Packet, NAT }; +} + +declare module "socket:index" { + import { network } from "socket:node/index"; + import { Cache } from "socket:node/index"; + import { sha256 } from "socket:node/index"; + import { Encryption } from "socket:node/index"; + import { Packet } from "socket:node/index"; + import { NAT } from "socket:node/index"; + export { network, Cache, sha256, Encryption, Packet, NAT }; +} + +declare module "socket:latica" { + export * from "socket:latica/index"; + export default def; + import def from "socket:latica/index"; +} + declare module "socket:module" { export const builtinModules: any; export default Module; From 4cfcb69bd2b195067a2dfad74040bb78d8071097 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 21:06:05 +0200 Subject: [PATCH 1084/1178] fix(cli): remove sleep in signal handler --- src/cli/cli.cc | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index a40b5a6458..6137e08905 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -815,18 +815,23 @@ void signalHandler (int signum) { log("App result: " + std::to_string(signum)); } - #if SOCKET_RUNTIME_PLATFORM_LINUX - if (gtk_main_level() > 0) { - // FIXME(@jwerle): calling `msleep()` from a signal handler is definitely bad practice - msleep(4); - gtk_main_quit(); - } - #endif - if (signum == SIGTERM || signum == SIGINT) { signal(signum, SIG_DFL); raise(signum); } + + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (gtk_main_level() > 0) { + g_main_context_invoke( + nullptr, + +[](gpointer userData) -> gboolean { + msleep(16); + gtk_main_quit(); + }, + nullptr + ); + } + #endif } void checkIosSimulatorDeviceAvailability (const String& device) { From b5813725d4a16bb3a12e483c0547c10ac4a50935 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 21:06:49 +0200 Subject: [PATCH 1085/1178] fix(core): fix 'fs.*' resources paths - fix resource path resolution in linux desktop extension - improve 'socket://' usage in various 'CoreFS' APIs --- src/core/config.cc | 14 +++++------ src/core/modules/fs.cc | 53 +++++++++++++++++++++--------------------- src/core/modules/fs.hh | 2 +- src/core/resource.cc | 10 +++++--- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/core/config.cc b/src/core/config.cc index 98a2fea483..b0598c45a8 100644 --- a/src/core/config.cc +++ b/src/core/config.cc @@ -11,21 +11,19 @@ namespace SSC { } const Map getUserConfig () { - static const auto bytes = socket_runtime_init_get_user_config_bytes(); - static const auto size = socket_runtime_init_get_user_config_bytes_size(); - static const auto string = String(reinterpret_cast<const char*>(bytes), size); - static const auto userConfig = INI::parse(string); + const auto bytes = socket_runtime_init_get_user_config_bytes(); + const auto size = socket_runtime_init_get_user_config_bytes_size(); + const auto string = String(reinterpret_cast<const char*>(bytes), size); + const auto userConfig = INI::parse(string); return userConfig; } const String getDevHost () { - static const auto devHost = socket_runtime_init_get_dev_host(); - return devHost; + return socket_runtime_init_get_dev_host(); } int getDevPort () { - static const auto devPort = socket_runtime_init_get_dev_port(); - return devPort; + return socket_runtime_init_get_dev_port(); } Config::Config (const String& source) { diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index 072ba3c5ad..1ae25acfe5 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -313,11 +313,9 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() mutable { - auto filename = path.c_str(); auto loop = &this->core->eventLoop; - auto desc = std::make_shared<Descriptor>(this, 0, filename); + auto desc = std::make_shared<Descriptor>(this, 0, path); - #if SOCKET_RUNTIME_PLATFORM_ANDROID if (desc->resource.url.scheme == "socket") { if (desc->resource.access(mode) == mode) { const auto json = JSON::Object::Entries { @@ -328,7 +326,9 @@ namespace SSC { }; return callback(seq, json, Post{}); - } else if (mode == R_OK || mode == F_OK) { + } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + else if (mode == R_OK || mode == F_OK) { auto name = desc->resource.name; if (name.starts_with("/")) { @@ -365,7 +365,10 @@ namespace SSC { return callback(seq, json, Post{}); } } - } else if ( + #endif + } + #if SOCKET_RUNTIME_PLATFORM_ANDROID + else if ( desc->resource.url.scheme == "content" || desc->resource.url.scheme == "android.resource" ) { @@ -384,7 +387,7 @@ namespace SSC { auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_access(loop, req, filename, mode, [](uv_fs_t* req) { + auto err = uv_fs_access(loop, req, desc->resource.path.c_str(), mode, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; @@ -701,8 +704,7 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { - auto filename = path.c_str(); - auto desc = std::make_shared<Descriptor>(this, id, filename); + auto desc = std::make_shared<Descriptor>(this, id, path); #if SOCKET_RUNTIME_PLATFORM_ANDROID if (desc->resource.url.scheme == "socket") { @@ -778,7 +780,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_open(loop, req, filename, flags, mode, [](uv_fs_t* req) { + auto err = uv_fs_open(loop, req, desc->resource.path.c_str(), flags, mode, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto desc = ctx->descriptor; auto json = JSON::Object {}; @@ -863,8 +865,7 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { - auto filename = path.c_str(); - auto desc = std::make_shared<Descriptor>(this, id, filename); + auto desc = std::make_shared<Descriptor>(this, id, path); #if SOCKET_RUNTIME_PLATFORM_ANDROID if (desc->resource.url.scheme == "socket") { @@ -949,7 +950,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_opendir(loop, req, filename, [](uv_fs_t *req) { + auto err = uv_fs_opendir(loop, req, desc->resource.path.c_str(), [](uv_fs_t *req) { auto ctx = (RequestContext *) req->data; auto desc = ctx->descriptor; auto json = JSON::Object {}; @@ -1711,8 +1712,7 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { - auto filename = path.c_str(); - auto desc = std::make_shared<Descriptor>(this, 0, filename); + auto desc = std::make_shared<Descriptor>(this, 0, path); #if SOCKET_RUNTIME_PLATFORM_ANDROID if (desc->resource.isAndroidLocalAsset()) { @@ -1744,7 +1744,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_stat(loop, req, filename, [](uv_fs_t *req) { + auto err = uv_fs_stat(loop, req, desc->resource.path.c_str(), [](uv_fs_t *req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; @@ -2066,13 +2066,13 @@ namespace SSC { const String& seq, const String& path, const CoreModule::Callback& callback - ) const { + ) { this->core->dispatchEventLoop([=, this]() { - auto filename = path.c_str(); + auto desc = std::make_shared<Descriptor>(this, 0, path); auto loop = &this->core->eventLoop; - auto ctx = new RequestContext(seq, callback); + auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_lstat(loop, req, filename, [](uv_fs_t* req) { + auto err = uv_fs_lstat(loop, req, desc->resource.path.c_str(), [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; @@ -2424,13 +2424,14 @@ namespace SSC { const CoreModule::Callback& callback ) { this->core->dispatchEventLoop([=, this]() { - auto desc = std::make_shared<Descriptor>(this, 0, pathA); + auto src = std::make_shared<Descriptor>(this, 0, pathA); + auto dst = std::make_shared<Descriptor>(this, 0, pathB); #if SOCKET_RUNTIME_PLATFORM_ANDROID - if (desc->resource.isAndroidLocalAsset() || desc->resource.isAndroidLocalAsset()) { - auto bytes = desc->resource.read(); - fs::remove(pathB); - std::ofstream stream(pathB); + if (src->resource.isAndroidLocalAsset() || src->resource.isAndroidLocalAsset()) { + auto bytes = src->resource.read(); + fs::remove(dst->resource.path.string()); + std::ofstream stream(dst->resource.path.string()); stream << bytes; stream.close(); @@ -2448,9 +2449,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(seq, callback); auto req = &ctx->req; - auto src = pathA.c_str(); - auto dst = pathB.c_str(); - auto err = uv_fs_copyfile(loop, req, src, dst, flags, [](uv_fs_t* req) { + auto err = uv_fs_copyfile(loop, req, src->resource.path.c_str(), dst->resource.path.c_str(), flags, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; diff --git a/src/core/modules/fs.hh b/src/core/modules/fs.hh index f629b92596..fd0c5afb5a 100644 --- a/src/core/modules/fs.hh +++ b/src/core/modules/fs.hh @@ -204,7 +204,7 @@ namespace SSC { const String& seq, const String& path, const CoreModule::Callback& callback - ) const; + ); void link ( const String& seq, diff --git a/src/core/resource.cc b/src/core/resource.cc index bccb718778..9869da15cc 100644 --- a/src/core/resource.cc +++ b/src/core/resource.cc @@ -280,8 +280,12 @@ namespace SSC { static const auto resourcePath = NSBundle.mainBundle.resourcePath; value = [resourcePath stringByAppendingPathComponent: @"ui"].UTF8String; #elif SOCKET_RUNTIME_PLATFORM_LINUX - static const auto self = fs::canonical("/proc/self/exe"); - value = self.parent_path().string(); + #if SOCKET_RUNTIME_DESKTOP_EXTENSION + value = getcwd(); + #else + static const auto self = fs::canonical("/proc/self/exe"); + value = self.parent_path().string(); + #endif #elif SOCKET_RUNTIME_PLATFORM_WINDOWS static wchar_t filename[MAX_PATH]; GetModuleFileNameW(NULL, filename, MAX_PATH); @@ -546,7 +550,7 @@ namespace SSC { if (url.scheme == "socket") { const auto resourcesPath = FileResource::getResourcesPath(); - this->path = resourcesPath / url.pathname; + this->path = fs::absolute(resourcesPath / url.pathname); #if SOCKET_RUNTIME_PLATFORM_ANDROID this->path = Path(url.pathname); this->name = getRelativeAndroidAssetManagerPath(this->path).string(); From ddc92e3880bff3dafc123ed3567b84486ace0a48 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 21:07:58 +0200 Subject: [PATCH 1086/1178] refactor(desktop/extension/linux): clean up --- src/desktop/extension/linux.cc | 6 +++--- src/window/linux.cc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc index 27a25d0f92..b30e329776 100644 --- a/src/desktop/extension/linux.cc +++ b/src/desktop/extension/linux.cc @@ -256,7 +256,7 @@ extern "C" { if (!context.config.bytes) { context.config.size = g_variant_get_size(const_cast<GVariant*>(userData)); if (context.config.size) { - context.config.bytes = new char[context.config.size]{0}; + context.config.bytes = reinterpret_cast<char*>(new unsigned char[context.config.size]{0}); memcpy( context.config.bytes, @@ -271,12 +271,12 @@ extern "C" { auto userConfig = getUserConfig(); auto cwd = userConfig["web-process-extension_cwd"]; - static App app(App::DEFAULT_INSTANCE_ID, std::move(std::make_shared<Core>(options))); - if (cwd.size() > 0) { setcwd(cwd); uv_chdir(cwd.c_str()); } + + static App app(App::DEFAULT_INSTANCE_ID, std::move(std::make_shared<Core>(options))); } const unsigned char* socket_runtime_init_get_user_config_bytes () { diff --git a/src/window/linux.cc b/src/window/linux.cc index ebdd5e6f1c..fd275599ff 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -89,7 +89,7 @@ namespace SSC { auto bytes = socket_runtime_init_get_user_config_bytes(); auto size = socket_runtime_init_get_user_config_bytes_size(); static auto data = String(reinterpret_cast<const char*>(bytes), size); - data += "[web-process-extension]\n"; + data += "\n[web-process-extension]\n"; data += "cwd = " + cwd + "\n"; data += "host = " + getDevHost() + "\n"; data += "port = " + std::to_string(getDevPort()) + "\n"; From ad032eea307dde3650bb9d7d6680a401f6972ebe Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 4 Aug 2024 23:58:01 +0200 Subject: [PATCH 1087/1178] fix(api/commonjs/package.js): handle implicit entry exports object --- api/commonjs/package.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/api/commonjs/package.js b/api/commonjs/package.js index aac76cdda2..e0b3a51ae1 100644 --- a/api/commonjs/package.js +++ b/api/commonjs/package.js @@ -945,21 +945,25 @@ export class Package { } if (info.exports && typeof info.exports === 'object') { - for (const key in info.exports) { - const exports = info.exports[key] - if (!exports) { - continue - } + if (info.exports.import || info.exports.require || info.exports.default) { + this.#exports['.'] = info.exports + } else { + for (const key in info.exports) { + const exports = info.exports[key] + if (!exports) { + continue + } - if (typeof exports === 'string') { - this.#exports[key] = {} - if (this.#type === 'commonjs') { - this.#exports[key].require = exports - } else if (this.#type === 'module') { - this.#exports[key].import = exports + if (typeof exports === 'string') { + this.#exports[key] = {} + if (this.#type === 'commonjs') { + this.#exports[key].require = exports + } else if (this.#type === 'module') { + this.#exports[key].import = exports + } + } else if (typeof exports === 'object') { + this.#exports[key] = exports } - } else if (typeof exports === 'object') { - this.#exports[key] = exports } } } From 22d75c698660b2d853fc37de13cffa66348c7f63 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 5 Aug 2024 01:40:17 +0200 Subject: [PATCH 1088/1178] refactor(api/fs): handle 'FileSystemFileHandle' and 'FileSystemDirectory' inputs --- api/fs/handle.js | 284 +++++++++++++++++++++++++++++++++++---------- api/fs/index.js | 96 ++++++++++++++- api/fs/promises.js | 4 + api/fs/web.js | 44 +++++-- 4 files changed, 358 insertions(+), 70 deletions(-) diff --git a/api/fs/handle.js b/api/fs/handle.js index 31317e99c9..d2a039de68 100644 --- a/api/fs/handle.js +++ b/api/fs/handle.js @@ -6,9 +6,9 @@ import { clamp } from '../util.js' +import { F_OK, O_APPEND, S_IFREG } from './constants.js' import { ReadStream, WriteStream } from './stream.js' import { normalizeFlags } from './flags.js' -import { F_OK, O_APPEND } from './constants.js' import { AsyncResource } from '../async/resource.js' import { EventEmitter } from '../events.js' import { AbortError } from '../errors.js' @@ -23,6 +23,8 @@ import gc from '../gc.js' import * as exports from './handle.js' +const kFileDescriptor = Symbol.for('socket.runtune.fs.web.FileDescriptor') + /** * @typedef {Uint8Array|Int8Array} TypedArray */ @@ -70,10 +72,21 @@ export class FileHandle extends EventEmitter { /** * Creates a `FileHandle` from a given `id` or `fd` - * @param {string|number|FileHandle|object} id + * @param {string|number|FileHandle|object|FileSystemFileHandle} id * @return {FileHandle} */ static from (id) { + if ( + globalThis.FileSystemFileHandle && + id instanceof globalThis.FileSystemFileHandle + ) { + if (id[kFileDescriptor]) { + return id[kFileDescriptor] + } + + return new this({ handle: id }) + } + if (id?.id) { return this.from(id.id) } else if (id?.fd) { @@ -144,6 +157,12 @@ export class FileHandle extends EventEmitter { mode = FileHandle.DEFAULT_OPEN_MODE } + if (path instanceof globalThis.FileSystemFileHandle) { + const handle = this.from(path, options || mode || flags) + await handle.open(options) + return handle + } + path = normalizePath(path) const handle = new this({ path, flags, mode }) @@ -157,6 +176,7 @@ export class FileHandle extends EventEmitter { } #resource = null + #fileSystemHandle = null /** * `FileHandle` class constructor @@ -178,6 +198,10 @@ export class FileHandle extends EventEmitter { this.#resource = new AsyncResource('FileHandle') this.#resource.handle = this + if (options?.handle) { + this.#fileSystemHandle = options.handle + } + this.flags = normalizeFlags(options?.flags) this.path = options?.path || null this.mode = options?.mode || FileHandle.DEFAULT_OPEN_MODE @@ -200,7 +224,9 @@ export class FileHandle extends EventEmitter { * @type {boolean} */ get opened () { - return this.fd !== null && this.fd === fds.get(this.id) + return this.#fileSystemHandle || ( + this.fd !== null && this.fd === fds.get(this.id) + ) } /** @@ -257,6 +283,12 @@ export class FileHandle extends EventEmitter { * @param {object=} [options.signal] */ async appendFile (data, options) { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -274,9 +306,17 @@ export class FileHandle extends EventEmitter { * @param {object=} [options] */ async chmod (mode, options) { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } + + // TODO(@jwerle) } /** @@ -286,9 +326,17 @@ export class FileHandle extends EventEmitter { * @param {object=} [options] */ async chown (uid, gid, options) { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } + + // TODO(@jwerle) } /** @@ -311,12 +359,14 @@ export class FileHandle extends EventEmitter { this[kClosing] = new Deferred() - const result = await ipc.request('fs.close', { id: this.id }, { - ...options - }) + if (!this.#fileSystemHandle) { + const result = await ipc.request('fs.close', { id: this.id }, { + ...options + }) - if (result.err) { - return this[kClosing].reject(result.err) + if (result.err) { + return this[kClosing].reject(result.err) + } } fds.release(this.id, false) @@ -403,6 +453,12 @@ export class FileHandle extends EventEmitter { * @param {object=} [options] */ async datasync () { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -437,25 +493,29 @@ export class FileHandle extends EventEmitter { this[kOpening] = new Deferred() - const result = await ipc.request('fs.open', { - id, - mode, - path, - flags - }, { - ...options - }) + if (this.#fileSystemHandle) { + fds.set(this.id, this.id, 'file') + } else { + const result = await ipc.request('fs.open', { + id, + mode, + path, + flags + }, { + ...options + }) - if (result.err) { - return this[kOpening].reject(result.err) - } + if (result.err) { + return this[kOpening].reject(result.err) + } - if (result.data?.fd) { - this.fd = result.data.fd - fds.set(this.id, this.fd, 'file') - } else { - this.fd = id - fds.set(this.id, this.id, 'file') + if (result.data?.fd) { + this.fd = result.data.fd + fds.set(this.id, this.fd, 'file') + } else { + this.fd = id + fds.set(this.id, this.id, 'file') + } } this[kOpening].resolve(true) @@ -557,39 +617,47 @@ export class FileHandle extends EventEmitter { ) } - const result = await ipc.request('fs.read', { - id, - size: length, - offset: position - }, { - responseType: 'arraybuffer', - timeout, - signal - }) + if (this.#fileSystemHandle) { + const file = this.#fileSystemHandle.getFile() + const blob = file.slice(position, position + length) + const arrayBuffer = await blob.arrayBuffer() + bytesRead = arrayBuffer.byteLength + Buffer.from(arrayBuffer).copy(Buffer.from(buffer), 0, offset) + } else { + const result = await ipc.request('fs.read', { + id, + size: length, + offset: position + }, { + responseType: 'arraybuffer', + timeout, + signal + }) - if (result.err) { - throw result.err - } + if (result.err) { + throw result.err + } - const contentType = result.headers?.get('content-type') + const contentType = result.headers?.get('content-type') - if (contentType && contentType !== 'application/octet-stream') { - throw new TypeError( - `Invalid response content type from 'fs.read'. Received: ${contentType}` - ) - } + if (contentType && contentType !== 'application/octet-stream') { + throw new TypeError( + `Invalid response content type from 'fs.read'. Received: ${contentType}` + ) + } - if (isTypedArray(result.data) || result.data instanceof ArrayBuffer) { - bytesRead = result.data.byteLength - Buffer.from(result.data).copy(Buffer.from(buffer), 0, offset) - dc.channel('handle.read').publish({ handle: this, bytesRead }) - } else if (isEmptyObject(result.data)) { - // an empty response from mac returns an empty object sometimes - bytesRead = 0 - } else { - throw new TypeError( - `Invalid response buffer from 'fs.read' Received: ${typeof result.data}` - ) + if (isTypedArray(result.data) || result.data instanceof ArrayBuffer) { + bytesRead = result.data.byteLength + Buffer.from(result.data).copy(Buffer.from(buffer), 0, offset) + dc.channel('handle.read').publish({ handle: this, bytesRead }) + } else if (isEmptyObject(result.data)) { + // an empty response from mac returns an empty object sometimes + bytesRead = 0 + } else { + throw new TypeError( + `Invalid response buffer from 'fs.read' Received: ${typeof result.data}` + ) + } } return { bytesRead, buffer } @@ -649,6 +717,17 @@ export class FileHandle extends EventEmitter { throw new Error('FileHandle is not opened') } + if (this.#fileSystemHandle) { + const info = { + st_mode: S_IFREG, + st_size: this.#fileSystemHandle.size + } + + const stats = Stats.from(info, Boolean(options?.bigint)) + stats.handle = this + return stats + } + const result = await ipc.request('fs.fstat', { id: this.id }, { ...options }) @@ -668,6 +747,10 @@ export class FileHandle extends EventEmitter { * @return {Promise<Stats>} */ async lstat (options) { + if (this.#fileSystemHandle) { + return this.stat(options) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -690,6 +773,12 @@ export class FileHandle extends EventEmitter { * @return {Promise} */ async sync () { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -706,6 +795,12 @@ export class FileHandle extends EventEmitter { * @return {Promise} */ async truncate (offset = 0) { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -727,6 +822,12 @@ export class FileHandle extends EventEmitter { * @param {object=} [options] */ async write (buffer, offset, length, position, options) { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -809,6 +910,12 @@ export class FileHandle extends EventEmitter { * @param {object=} [options.signal] */ async writeFile (data, options) { + if (this.#fileSystemHandle) { + return new TypeError( + 'FileHandle underlying FileSystemFileHandle is not writable' + ) + } + if (this.closing || this.closed) { throw new Error('FileHandle is not opened') } @@ -869,12 +976,24 @@ export class DirectoryHandle extends EventEmitter { static get DEFAULT_BUFFER_SIZE () { return 32 } /** - * Creates a `FileHandle` from a given `id` or `fd` - * @param {string|number|DirectoryHandle|object} id + * Creates a `DirectoryHandle` from a given `id` or `fd` + * @param {string|number|DirectoryHandle|object|FileSystemDirectoryHandle} id * @param {object} options * @return {DirectoryHandle} */ static from (id, options) { + if ( + globalThis.FileSystemDirectoryHandle && + id instanceof globalThis.FileSystemDirectoryHandle + ) { + if (id[kFileDescriptor]) { + const dir = id[kFileDescriptor] + return dir.handle ?? dir + } + + return new this({ handle: id }) + } + if (id?.id) { return this.from(id.id) } else if (id?.fd) { @@ -895,6 +1014,17 @@ export class DirectoryHandle extends EventEmitter { * @return {Promise<DirectoryHandle>} */ static async open (path, options) { + if (path instanceof globalThis.FileSystemDirectoryHandle) { + if (path[kFileDescriptor]) { + const dir = path[kFileDescriptor] + return dir.handle ?? dir + } + + const handle = this.from(path, options) + await handle.open(options) + return handle + } + path = normalizePath(path) const handle = new this({ path }) @@ -908,6 +1038,8 @@ export class DirectoryHandle extends EventEmitter { } #resource = null + #fileSystemHandle = null + #fileSystemHandleIterator = null /** * `DirectoryHandle` class constructor @@ -938,6 +1070,10 @@ export class DirectoryHandle extends EventEmitter { this.path = normalizePath(this.path) } + if (options?.handle) { + this.#fileSystemHandle = options.handle + } + // @TODO(jwerle): implement usage of this internally this.bufferSize = Math.min( DirectoryHandle.MAX_BUFFER_SIZE, @@ -1029,10 +1165,12 @@ export class DirectoryHandle extends EventEmitter { this[kOpening] = new Deferred() - const result = await ipc.request('fs.opendir', { id, path }, options) + if (!this.#fileSystemHandle) { + const result = await ipc.request('fs.opendir', { id, path }, options) - if (result.err) { - return this[kOpening].reject(result.err) + if (result.err) { + return this[kOpening].reject(result.err) + } } // directory file descriptors are not accessible because @@ -1076,10 +1214,12 @@ export class DirectoryHandle extends EventEmitter { this[kClosing] = new Deferred() - const result = await ipc.request('fs.closedir', { id }, options) + if (!this.#fileSystemHandle) { + const result = await ipc.request('fs.closedir', { id }, options) - if (result.err) { - return this[kClosing].reject(result.err) + if (result.err) { + return this[kClosing].reject(result.err) + } } fds.release(this.id, false) @@ -1124,6 +1264,26 @@ export class DirectoryHandle extends EventEmitter { DirectoryHandle.MAX_ENTRIES ) + if (this.#fileSystemHandle) { + const results = [] + + if (!this.#fileSystemHandleIterator) { + this.#fileSystemHandleIterator = this.#fileSystemHandle.entries() + } + + for (let i = 0; i < entries; ++i) { + const [name, handle] = await this.#fileSystemHandleIterator.next() + results.push({ + name, + type: handle instanceof globalThis.FileSystemDirectoryHandle + ? 'directory' + : 'file' + }) + } + + return results + } + const { id } = this const result = await ipc.request('fs.readdir', { diff --git a/api/fs/index.js b/api/fs/index.js index a28816addc..9a03f0e953 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -42,6 +42,8 @@ import fds from './fds.js' import * as exports from './index.js' +const kFileDescriptor = Symbol.for('socket.runtune.fs.web.FileDescriptor') + /** * @typedef {import('../buffer.js').Buffer} Buffer * @typedef {Uint8Array|Int8Array} TypedArray @@ -116,6 +118,13 @@ export function access (path, mode, callback) { throw new TypeError('callback must be a function.') } + if ( + path instanceof globalThis.FileSystemFileHandle || + path instanceof globalThis.FileSystemDirectoryHandle + ) { + return queueMicrotask(() => callback(null, mode)) + } + path = normalizePath(path) FileHandle @@ -198,6 +207,15 @@ export function chmod (path, mode, callback) { throw new TypeError('callback must be a function.') } + if ( + path instanceof globalThis.FileSystemFileHandle || + path instanceof globalThis.FileSystemDirectoryHandle + ) { + return new TypeError( + 'FileSystemHandle is not writable' + ) + } + path = normalizePath(path) ipc.request('fs.chmod', { mode, path }).then((result) => { result?.err ? callback(result.err) : callback(null) @@ -253,6 +271,15 @@ export function chown (path, uid, gid, callback) { throw new TypeError('callback must be a function.') } + if ( + path instanceof globalThis.FileSystemFileHandle || + path instanceof globalThis.FileSystemDirectoryHandle + ) { + return new TypeError( + 'FileSystemHandle is not writable' + ) + } + ipc.request('fs.chown', { path, uid, gid }).then((result) => { result?.err ? callback(result.err) : callback(null) }).catch(callback) @@ -348,6 +375,17 @@ export function copyFile (src, dest, flags = 0, callback) { throw new TypeError('callback must be a function.') } + if (src instanceof globalThis.FileSystemFileHandle) { + if (src.getFile()[kFileDescriptor]) { + src = src.getFile()[kFileDescriptor].path + } else { + src.getFile().arrayBuffer.then((arrayBuffer) => { + writeFile(dest, arrayBuffer, { flags }, callback) + }) + return + } + } + ipc.request('fs.copyFile', { src, dest, flags }).then((result) => { result?.err ? callback(result.err) : callback(null) }).catch(callback) @@ -440,6 +478,12 @@ export function createWriteStream (path, options) { path = options?.path || null } + if (path instanceof globalThis.FileSystemHandle) { + return new TypeError( + 'FileSystemHandle is not writable' + ) + } + path = normalizePath(path) let handle = null @@ -560,6 +604,15 @@ export function ftruncate (fd, offset, callback) { * @param {function} callback */ export function lchown (path, uid, gid, callback) { + if ( + path instanceof globalThis.FileSystemFileHandle || + path instanceof globalThis.FileSystemDirectoryHandle + ) { + return new TypeError( + 'FileSystemHandle is not writable' + ) + } + path = normalizePath(path) if (typeof path !== 'string') { @@ -1073,7 +1126,7 @@ export function readFileSync (path, options = null) { throw result.err } - const buffer = Buffer.from(data) + const buffer = data ? Buffer.from(data) : Buffer.alloc(0) if (typeof options?.encoding === 'string') { return buffer.toString(options.encoding) @@ -1264,6 +1317,40 @@ export function stat (path, options, callback) { throw new TypeError('callback must be a function.') } + if ( + path instanceof globalThis.FileSystemFileHandle || + path instanceof globalThis.FileSystemDirectoryHandle + ) { + if (path.getFile()[kFileDescriptor]) { + try { + path.getFile()[kFileDescriptor].stat(options).then( + (stats) => callback(null, stats), + (err) => callback(err) + ) + } catch (err) { + callback(err) + } + + return + } else { + queueMicrotask(() => { + const info = { + st_mode: path instanceof globalThis.FileSystemDirectoryHandle + ? constants.S_IFDIR + : constants.S_IFREG, + st_size: path instanceof globalThis.FileSystemFileHandle + ? path.size + : 0 + } + + const stats = Stats.from(info, Boolean(options?.bigint)) + callback(null, stats) + }) + + return + } + } + visit(path, {}, async (err, handle) => { let stats = null @@ -1302,6 +1389,13 @@ export function lstat (path, options, callback) { throw new TypeError('callback must be a function.') } + if ( + path instanceof globalThis.FileSystemFileHandle || + path instanceof globalThis.FileSystemDirectoryHandle + ) { + return stat(path, options, callback) + } + visit(path, {}, async (err, handle) => { let stats = null diff --git a/api/fs/promises.js b/api/fs/promises.js index 5c13428587..3de41eaec3 100644 --- a/api/fs/promises.js +++ b/api/fs/promises.js @@ -50,6 +50,8 @@ export { WriteStream } +const kFileDescriptor = Symbol.for('socket.runtune.fs.web.FileDescriptor') + function normalizePath (path) { if (path instanceof URL) { if (path.origin === globalThis.location.origin) { @@ -89,6 +91,8 @@ async function visit (path, options, callback) { return await callback(path) } else if (path?.fd) { return await callback(FileHandle.from(path.fd)) + } else if (path && typeof path === 'object' && kFileDescriptor in path) { + return await callback(FileHandle.from(path)) } const handle = await FileHandle.open(path, flags || flag, mode, options) diff --git a/api/fs/web.js b/api/fs/web.js index 29ed50ebad..fd2ed7a6e7 100644 --- a/api/fs/web.js +++ b/api/fs/web.js @@ -7,9 +7,9 @@ import mime from '../mime.js' import path from '../path.js' import fs from './promises.js' -export const kFileSystemHandleFullName = Symbol('kFileSystemHandleFullName') -export const kFileDescriptor = Symbol('kFileDescriptor') -export const kFileFullName = Symbol('kFileFullName') +export const kFileSystemHandleFullName = Symbol.for('socket.runtune.fs.web.FileSystemHandleFullName') +export const kFileDescriptor = Symbol.for('socket.runtune.fs.web.FileDescriptor') +export const kFileFullName = Symbol.for('socket.runtune.fs.web.FileFullName') // @ts-ignore export const File = globalThis.File ?? @@ -23,6 +23,7 @@ export const File = globalThis.File ?? slice () {} async arrayBuffer () {} + async bytes () {} async text () {} stream () {} } @@ -117,11 +118,16 @@ export async function createFile (filename, options = null) { let fd = options?.fd ?? null let blobBuffer = null + let bytesBuffer = null const name = URL.canParse(filename) ? filename : path.basename(filename) + if (!fd) { + fd = await fs.open(filename) + } + return create(File, class File { get [kFileFullName] () { return filename } get [kFileDescriptor] () { return fd } @@ -132,12 +138,33 @@ export async function createFile (filename, options = null) { get size () { return stats.size } get type () { return type } - slice () { + slice (start = 0, end = stats.size, contentType = type) { if (!blobBuffer) { blobBuffer = readFileSync(filename) } - return new Blob([blobBuffer], { type }) + const blob = new Blob([blobBuffer], { + type: contentType + }) + + if (start < 0) { + start = stats.size - start + } + + if (end < 0) { + end = stats.size - end + } + + return blob.slice(start, end) + } + + async bytes () { + if (!bytesBuffer) { + bytesBuffer = await this.arrayBuffer() + blobBuffer = bytesBuffer + } + + return new Uint8Array(bytesBuffer) } async arrayBuffer () { @@ -169,12 +196,11 @@ export async function createFile (filename, options = null) { const stream = new ReadableStream({ async start (controller) { - fd = options?.fd || await fs.open(filename) + const fd = await fs.open(filename) fd.once('close', () => controller.close()) if (highWaterMark === 0) { await fd.close() await controller.close() - fd = null return } @@ -461,6 +487,10 @@ export async function createFileSystemDirectoryHandle (dirname, options = null) // `fd` is opened with `lazyOpen` at on demand let fd = null + if (options?.open === true) { + await lazyOpen() + } + return create(FileSystemDirectoryHandle, class FileSystemDirectoryHandle { get [kFileSystemHandleFullName] () { return dirname } get [kFileDescriptor] () { return fd } From 777346dac797f56918dd4cf5fc8c4218048a72ce Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 5 Aug 2024 01:40:41 +0200 Subject: [PATCH 1089/1178] refactor(api/internal/pickers.js): ensure directory handle is opened --- api/internal/pickers.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/internal/pickers.js b/api/internal/pickers.js index 43c1a4b85b..4d07e09950 100644 --- a/api/internal/pickers.js +++ b/api/internal/pickers.js @@ -172,7 +172,10 @@ export async function showDirectoryPicker (options = null) { bookmarks.temporary.set(dirname, null) // placehold `null` - const result = await createFileSystemDirectoryHandle(dirname) + const result = await createFileSystemDirectoryHandle(dirname, { + open: true + }) + return result } From 9d33479a2ec6dfd22501423beb9c05e3686dcefe Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 5 Aug 2024 01:41:07 +0200 Subject: [PATCH 1090/1178] chore(api): generate types + docs --- api/README.md | 132 +++++++++++++++++------------------ api/index.d.ts | 183 +++++++++++++++++++++++++------------------------ 2 files changed, 158 insertions(+), 157 deletions(-) diff --git a/api/README.md b/api/README.md index c92fea2621..fbde1f2601 100644 --- a/api/README.md +++ b/api/README.md @@ -838,7 +838,7 @@ External docs: https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options import * as fs from 'socket:fs'; ``` -## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L109) +## [`access(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L111) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously check access a file for a given mode calling `callback` @@ -850,7 +850,7 @@ Asynchronously check access a file for a given mode calling `callback` | mode | string? \| function(Error?)? | F_OK(0) | true | | | callback | function(Error?)? | | true | | -## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L134) +## [`accessSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L143) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Synchronously check access a file for a given mode calling `callback` @@ -861,7 +861,7 @@ Synchronously check access a file for a given mode calling `callback` | path | string \| Buffer \| URL | | false | | | mode | string? | F_OK(0) | true | | -## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L151) +## [`exists(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L160) Checks if a path exists @@ -870,7 +870,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L168) +## [`existsSync(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L177) Checks if a path exists @@ -879,7 +879,7 @@ Checks if a path exists | path | string \| Buffer \| URL | | false | | | callback | function(Boolean)? | | true | | -## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L188) +## [`chmod(path, mode, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L197) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Asynchronously changes the permissions of a file. @@ -893,7 +893,7 @@ Asynchronously changes the permissions of a file. | mode | number | | false | | | callback | function(Error?) | | false | | -## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L214) +## [`chmodSync(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L232) External docs: https://nodejs.org/api/fs.html#fschmodpath-mode-callback Synchronously changes the permissions of a file. @@ -904,7 +904,7 @@ Synchronously changes the permissions of a file. | path | string \| Buffer \| URL | | false | | | mode | number | | false | | -## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L238) +## [`chown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L256) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -915,7 +915,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | gid | number | | false | | | callback | function | | false | | -## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L267) +## [`chownSync(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L294) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -925,7 +925,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | uid | number | | false | | | gid | number | | false | | -## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L294) +## [`close(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L321) External docs: https://nodejs.org/api/fs.html#fsclosefd-callback Asynchronously close a file descriptor calling `callback` upon success or error. @@ -935,7 +935,7 @@ Asynchronously close a file descriptor calling `callback` upon success or error. | fd | number | | false | | | callback | function(Error?)? | | true | | -## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L314) +## [`closeSync(fd)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L341) Synchronously close a file descriptor. @@ -943,7 +943,7 @@ Synchronously close a file descriptor. | :--- | :--- | :---: | :---: | :--- | | fd | number | | false | fd | -## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L331) +## [`copyFile(src, dest, flags, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L358) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -955,7 +955,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | flags | number | | false | Modifiers for copy operation. | | callback | function(Error=) | | true | The function to call after completion. | -## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L363) +## [`copyFileSync(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L401) External docs: https://nodejs.org/api/fs.html#fscopyfilesrc-dest-mode-callback Synchronously copies `src` to `dest` calling `callback` upon success or error. @@ -966,7 +966,7 @@ Synchronously copies `src` to `dest` calling `callback` upon success or error. | dest | string | | false | The destination file path. | | flags | number | | false | Modifiers for copy operation. | -## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L392) +## [`createReadStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L430) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -980,7 +980,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | ReadStream | | -## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L437) +## [`createWriteStream(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L475) External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options @@ -994,7 +994,7 @@ External docs: https://nodejs.org/api/fs.html#fscreatewritestreampath-options | :--- | :--- | :--- | | Not specified | WriteStream | | -## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L485) +## [`fstat(fd, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L529) External docs: https://nodejs.org/api/fs.html#fsfstatfd-options-callback Invokes the callback with the <fs.Stats> for the file descriptor. See @@ -1008,7 +1008,7 @@ Invokes the callback with the <fs.Stats> for the file descriptor. See | options | object? \| function? | | true | An options object. | | callback | function? | | false | The function to call after completion. | -## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L512) +## [`fsync(fd, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L556) Request that all data for the open file descriptor is flushed to the storage device. @@ -1018,7 +1018,7 @@ Request that all data for the open file descriptor is flushed | fd | number | | false | A file descriptor. | | callback | function | | false | The function to call after completion. | -## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L534) +## [`ftruncate(fd, offset, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L578) Truncates the file up to `offset` bytes. @@ -1028,7 +1028,7 @@ Truncates the file up to `offset` bytes. | offset | number= \| function | 0 | true | | | callback | function? | | false | The function to call after completion. | -## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L562) +## [`lchown(path, uid, gid, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L606) Chages ownership of link at `path` with `uid` and `gid. @@ -1039,7 +1039,7 @@ Chages ownership of link at `path` with `uid` and `gid. | gid | number | | false | | | callback | function | | false | | -## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L592) +## [`link(src, dest, )`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L645) Creates a link to `dest` from `src`. @@ -1049,7 +1049,7 @@ Creates a link to `dest` from `src`. | dest | string | | false | | | (Position 0) | function | | false | | -## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L680) +## [`open(path, flags, mode, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L733) External docs: https://nodejs.org/api/fs.html#fsopenpath-flags-mode-callback Asynchronously open a file calling `callback` upon success or error. @@ -1062,7 +1062,7 @@ Asynchronously open a file calling `callback` upon success or error. | options | object? \| function? | | true | | | callback | function(Error?, number?)? | | true | | -## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L733) +## [`openSync(path, flags, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L786) Synchronously open a file. @@ -1073,7 +1073,7 @@ Synchronously open a file. | mode | string? | 0o666 | true | | | options | object? \| function? | | true | | -## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L780) +## [`opendir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L833) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously open a directory calling `callback` upon success or error. @@ -1086,7 +1086,7 @@ Asynchronously open a directory calling `callback` upon success or error. | options.withFileTypes | boolean? | false | true | | | callback | function(Error?, Dir?)? | | false | | -## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L807) +## [`opendirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L860) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously open a directory. @@ -1102,7 +1102,7 @@ Synchronously open a directory. | :--- | :--- | :--- | | Not specified | Dir | | -## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L834) +## [`read(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L887) External docs: https://nodejs.org/api/fs.html#fsreadfd-buffer-offset-length-position-callback Asynchronously read from an open file descriptor. @@ -1116,7 +1116,7 @@ Asynchronously read from an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L869) +## [`write(fd, buffer, offset, length, position, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L922) External docs: https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback Asynchronously write to an open file descriptor. @@ -1130,7 +1130,7 @@ Asynchronously write to an open file descriptor. | position | number \| BigInt \| null | | false | Specifies where to begin reading from in the file. If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is an integer, the file position will be unchanged. | | callback | function(Error?, number?, Buffer?) | | false | | -## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L903) +## [`readdir(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L956) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Asynchronously read all entries in a directory. @@ -1143,7 +1143,7 @@ Asynchronously read all entries in a directory. | options.withFileTypes ? false | boolean? | | true | | | callback | function(Error?, object) | | false | | -## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L955) +## [`readdirSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1008) External docs: https://nodejs.org/api/fs.html#fsreaddirpath-options-callback Synchronously read all entries in a directory. @@ -1155,7 +1155,7 @@ Synchronously read all entries in a directory. | options.encoding ? utf8 | string? | | true | | | options.withFileTypes ? false | boolean? | | true | | -## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L985) +## [`readFile(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1038) @@ -1168,7 +1168,7 @@ Synchronously read all entries in a directory. | options.signal | AbortSignal? | | true | | | callback | function(Error?, Buffer?) | | false | | -## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1027) +## [`readFileSync(path, } options, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1080) @@ -1179,7 +1179,7 @@ Synchronously read all entries in a directory. | options | object? \| function(Error?, Buffer?) | | true | | | options.signal | AbortSignal? | | true | | -## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1090) +## [`readlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1143) Reads link at `path` @@ -1188,7 +1188,7 @@ Reads link at `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1110) +## [`realpath(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1163) Computes real path for `path` @@ -1197,7 +1197,7 @@ Computes real path for `path` | path | string | | false | | | callback | function(err, string) | | false | | -## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1128) +## [`realpathSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1181) Computes real path for `path` @@ -1205,7 +1205,7 @@ Computes real path for `path` | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1146) +## [`rename(src, dest, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1199) Renames file or directory at `src` to `dest`. @@ -1215,7 +1215,7 @@ Renames file or directory at `src` to `dest`. | dest | string | | false | | | callback | function | | false | | -## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1172) +## [`renameSync(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1225) Renames file or directory at `src` to `dest`, synchronously. @@ -1224,7 +1224,7 @@ Renames file or directory at `src` to `dest`, synchronously. | src | string | | false | | | dest | string | | false | | -## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1196) +## [`rmdir(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1249) Removes directory at `path`. @@ -1233,7 +1233,7 @@ Removes directory at `path`. | path | string | | false | | | callback | function | | false | | -## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1216) +## [`rmdirSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1269) Removes directory at `path`, synchronously. @@ -1241,7 +1241,7 @@ Removes directory at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1237) +## [`statSync(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1290) Synchronously get the stats of a file @@ -1252,7 +1252,7 @@ Synchronously get the stats of a file | options.encoding ? utf8 | string? | | true | | | options.flag ? r | string? | | true | | -## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1257) +## [`stat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1310) Get the stats of a file @@ -1265,7 +1265,7 @@ Get the stats of a file | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1295) +## [`lstat(path, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1382) Get the stats of a symbolic link @@ -1278,7 +1278,7 @@ Get the stats of a symbolic link | options.signal | AbortSignal? | | true | | | callback | function(Error?, Stats?) | | false | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1329) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1423) Creates a symlink of `src` at `dest`. @@ -1287,7 +1287,7 @@ Creates a symlink of `src` at `dest`. | src | string | | false | | | dest | string | | false | | -## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1372) +## [`unlink(path, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1466) Unlinks (removes) file at `path`. @@ -1296,7 +1296,7 @@ Unlinks (removes) file at `path`. | path | string | | false | | | callback | function | | false | | -## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1392) +## [`unlinkSync(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1486) Unlinks (removes) file at `path`, synchronously. @@ -1304,7 +1304,7 @@ Unlinks (removes) file at `path`, synchronously. | :--- | :--- | :---: | :---: | :--- | | path | string | | false | | -## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1417) +## [`writeFile(path, data, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1511) @@ -1319,7 +1319,7 @@ Unlinks (removes) file at `path`, synchronously. | options.signal | AbortSignal? | | true | | | callback | function(Error?) | | false | | -## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1462) +## [`writeFileSync(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1556) External docs: https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options Writes data to a file synchronously. @@ -1334,7 +1334,7 @@ Writes data to a file synchronously. | options.flag ? w | string? | | true | | | options.signal | AbortSignal? | | true | | -## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1497) +## [`watch(, options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/index.js#L1591) Watch for changes at `path` calling `callback` @@ -1377,7 +1377,7 @@ Watch for changes at `path` calling `callback` import fs from 'socket:fs/promises' ``` -## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L107) +## [`access(path, mode, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L111) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesaccesspath-mode Asynchronously check access a file. @@ -1388,7 +1388,7 @@ Asynchronously check access a file. | mode | string? | | true | | | options | object? | | true | | -## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L118) +## [`chmod(path, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L122) External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode @@ -1402,7 +1402,7 @@ External docs: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L143) +## [`chown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L147) Changes ownership of file or directory at `path` with `uid` and `gid`. @@ -1416,7 +1416,7 @@ Changes ownership of file or directory at `path` with `uid` and `gid`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L172) +## [`copyFile(src, dest, flags)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L176) Asynchronously copies `src` to `dest` calling `callback` upon success or error. @@ -1430,7 +1430,7 @@ Asynchronously copies `src` to `dest` calling `callback` upon success or error. | :--- | :--- | :--- | | Not specified | Promise | | -## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L202) +## [`lchown(path, uid, gid)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L206) Chages ownership of link at `path` with `uid` and `gid. @@ -1444,7 +1444,7 @@ Chages ownership of link at `path` with `uid` and `gid. | :--- | :--- | :--- | | Not specified | Promise | | -## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L230) +## [`link(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L234) Creates a link to `dest` from `dest`. @@ -1457,7 +1457,7 @@ Creates a link to `dest` from `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L258) +## [`mkdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L262) Asynchronously creates a directory. @@ -1473,7 +1473,7 @@ Asynchronously creates a directory. | :--- | :--- | :--- | | Not specified | Promise<any> | Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true. | -## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L288) +## [`open(path, flags, mode)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L292) External docs: https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode Asynchronously open a file. @@ -1489,7 +1489,7 @@ Asynchronously open a file. | :--- | :--- | :--- | | Not specified | Promise<FileHandle> | | -## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L301) +## [`opendir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L305) External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options @@ -1505,7 +1505,7 @@ External docs: https://nodejs.org/api/fs.html#fspromisesopendirpath-options | :--- | :--- | :--- | | Not specified | Promise<Dir> | | -## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L314) +## [`readdir(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L318) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreaddirpath-options @@ -1517,7 +1517,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | options.encoding | string? | utf8 | true | | | options.withFileTypes | boolean? | false | true | | -## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L352) +## [`readFile(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L356) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesreadfilepath-options @@ -1534,7 +1534,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesr | :--- | :--- | :--- | | Not specified | Promise<Buffer \| string> | | -## [`readlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L370) +## [`readlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L374) Reads link at `path` @@ -1546,7 +1546,7 @@ Reads link at `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`realpath(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L391) +## [`realpath(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L395) Computes real path for `path` @@ -1558,7 +1558,7 @@ Computes real path for `path` | :--- | :--- | :--- | | Not specified | Promise<string> | | -## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L413) +## [`rename(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L417) Renames file or directory at `src` to `dest`. @@ -1571,7 +1571,7 @@ Renames file or directory at `src` to `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L437) +## [`rmdir(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L441) Removes directory at `path`. @@ -1583,7 +1583,7 @@ Removes directory at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L459) +## [`stat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L463) External docs: https://nodejs.org/api/fs.html#fspromisesstatpath-options Get the stats of a file @@ -1598,7 +1598,7 @@ Get the stats of a file | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L474) +## [`lstat(path, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L478) External docs: https://nodejs.org/api/fs.html#fspromiseslstatpath-options Get the stats of a symbolic link. @@ -1613,7 +1613,7 @@ Get the stats of a symbolic link. | :--- | :--- | :--- | | Not specified | Promise<Stats> | | -## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L487) +## [`symlink(src, dest)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L491) Creates a symlink of `src` at `dest`. @@ -1626,7 +1626,7 @@ Creates a symlink of `src` at `dest`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`unlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L523) +## [`unlink(path)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L527) Unlinks (removes) file at `path`. @@ -1638,7 +1638,7 @@ Unlinks (removes) file at `path`. | :--- | :--- | :--- | | Not specified | Promise | | -## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L548) +## [`writeFile(path, data, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L552) External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromiseswritefilefile-data-options @@ -1657,7 +1657,7 @@ External docs: https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#fspromisesw | :--- | :--- | :--- | | Not specified | Promise<void> | | -## [`watch(, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L569) +## [`watch(, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/fs/promises.js#L573) Watch for changes at `path` calling `callback` diff --git a/api/index.d.ts b/api/index.d.ts index 61cb65bd07..708c2d0001 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -4119,6 +4119,83 @@ declare module "socket:path" { export { Path, posix, win32, mounts, DOWNLOADS, DOCUMENTS, RESOURCES, PICTURES, DESKTOP, VIDEOS, CONFIG, MEDIA, MUSIC, HOME, DATA, LOG, TMP }; } +declare module "socket:fs/constants" { + /** + * This flag can be used with uv_fs_copyfile() to return an error if the + * destination already exists. + */ + export const COPYFILE_EXCL: 1; + /** + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, a fallback copy mechanism is used. + */ + export const COPYFILE_FICLONE: 2; + /** + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, an error is returned. + */ + export const COPYFILE_FICLONE_FORCE: 4; + export const UV_DIRENT_UNKNOWN: any; + export const UV_DIRENT_FILE: any; + export const UV_DIRENT_DIR: any; + export const UV_DIRENT_LINK: any; + export const UV_DIRENT_FIFO: any; + export const UV_DIRENT_SOCKET: any; + export const UV_DIRENT_CHAR: any; + export const UV_DIRENT_BLOCK: any; + export const UV_FS_SYMLINK_DIR: any; + export const UV_FS_SYMLINK_JUNCTION: any; + export const UV_FS_O_FILEMAP: any; + export const O_RDONLY: any; + export const O_WRONLY: any; + export const O_RDWR: any; + export const O_APPEND: any; + export const O_ASYNC: any; + export const O_CLOEXEC: any; + export const O_CREAT: any; + export const O_DIRECT: any; + export const O_DIRECTORY: any; + export const O_DSYNC: any; + export const O_EXCL: any; + export const O_LARGEFILE: any; + export const O_NOATIME: any; + export const O_NOCTTY: any; + export const O_NOFOLLOW: any; + export const O_NONBLOCK: any; + export const O_NDELAY: any; + export const O_PATH: any; + export const O_SYNC: any; + export const O_TMPFILE: any; + export const O_TRUNC: any; + export const S_IFMT: any; + export const S_IFREG: any; + export const S_IFDIR: any; + export const S_IFCHR: any; + export const S_IFBLK: any; + export const S_IFIFO: any; + export const S_IFLNK: any; + export const S_IFSOCK: any; + export const S_IRWXU: any; + export const S_IRUSR: any; + export const S_IWUSR: any; + export const S_IXUSR: any; + export const S_IRWXG: any; + export const S_IRGRP: any; + export const S_IWGRP: any; + export const S_IXGRP: any; + export const S_IRWXO: any; + export const S_IROTH: any; + export const S_IWOTH: any; + export const S_IXOTH: any; + export const F_OK: any; + export const R_OK: any; + export const W_OK: any; + export const X_OK: any; + export default exports; + import * as exports from "socket:fs/constants"; + +} + declare module "socket:fs/stream" { export const DEFAULT_STREAM_HIGH_WATER_MARK: number; /** @@ -4202,83 +4279,6 @@ declare module "socket:fs/stream" { } -declare module "socket:fs/constants" { - /** - * This flag can be used with uv_fs_copyfile() to return an error if the - * destination already exists. - */ - export const COPYFILE_EXCL: 1; - /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, a fallback copy mechanism is used. - */ - export const COPYFILE_FICLONE: 2; - /** - * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. - * If copy-on-write is not supported, an error is returned. - */ - export const COPYFILE_FICLONE_FORCE: 4; - export const UV_DIRENT_UNKNOWN: any; - export const UV_DIRENT_FILE: any; - export const UV_DIRENT_DIR: any; - export const UV_DIRENT_LINK: any; - export const UV_DIRENT_FIFO: any; - export const UV_DIRENT_SOCKET: any; - export const UV_DIRENT_CHAR: any; - export const UV_DIRENT_BLOCK: any; - export const UV_FS_SYMLINK_DIR: any; - export const UV_FS_SYMLINK_JUNCTION: any; - export const UV_FS_O_FILEMAP: any; - export const O_RDONLY: any; - export const O_WRONLY: any; - export const O_RDWR: any; - export const O_APPEND: any; - export const O_ASYNC: any; - export const O_CLOEXEC: any; - export const O_CREAT: any; - export const O_DIRECT: any; - export const O_DIRECTORY: any; - export const O_DSYNC: any; - export const O_EXCL: any; - export const O_LARGEFILE: any; - export const O_NOATIME: any; - export const O_NOCTTY: any; - export const O_NOFOLLOW: any; - export const O_NONBLOCK: any; - export const O_NDELAY: any; - export const O_PATH: any; - export const O_SYNC: any; - export const O_TMPFILE: any; - export const O_TRUNC: any; - export const S_IFMT: any; - export const S_IFREG: any; - export const S_IFDIR: any; - export const S_IFCHR: any; - export const S_IFBLK: any; - export const S_IFIFO: any; - export const S_IFLNK: any; - export const S_IFSOCK: any; - export const S_IRWXU: any; - export const S_IRUSR: any; - export const S_IWUSR: any; - export const S_IXUSR: any; - export const S_IRWXG: any; - export const S_IRGRP: any; - export const S_IWGRP: any; - export const S_IXGRP: any; - export const S_IRWXO: any; - export const S_IROTH: any; - export const S_IWOTH: any; - export const S_IXOTH: any; - export const F_OK: any; - export const R_OK: any; - export const W_OK: any; - export const X_OK: any; - export default exports; - import * as exports from "socket:fs/constants"; - -} - declare module "socket:fs/flags" { export function normalizeFlags(flags: any): any; export default exports; @@ -5378,10 +5378,10 @@ declare module "socket:fs/handle" { static get DEFAULT_OPEN_MODE(): number; /** * Creates a `FileHandle` from a given `id` or `fd` - * @param {string|number|FileHandle|object} id + * @param {string|number|FileHandle|object|FileSystemFileHandle} id * @return {FileHandle} */ - static from(id: string | number | FileHandle | object): FileHandle; + static from(id: string | number | FileHandle | object | FileSystemFileHandle): FileHandle; /** * Determines if access to `path` for `mode` is possible. * @param {string} path @@ -5438,7 +5438,7 @@ declare module "socket:fs/handle" { * @param {string=} [options.encoding = 'utf8'] * @param {object=} [options.signal] */ - appendFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise<void | { + appendFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise<TypeError | { buffer: any; bytesWritten: number; }>; @@ -5447,14 +5447,14 @@ declare module "socket:fs/handle" { * @param {number} mode * @param {object=} [options] */ - chmod(mode: number, options?: object | undefined): Promise<void>; + chmod(mode: number, options?: object | undefined): Promise<TypeError>; /** * Change ownership of file handle. * @param {number} uid * @param {number} gid * @param {object=} [options] */ - chown(uid: number, gid: number, options?: object | undefined): Promise<void>; + chown(uid: number, gid: number, options?: object | undefined): Promise<TypeError>; /** * Close underlying file handle * @param {object=} [options] @@ -5473,7 +5473,7 @@ declare module "socket:fs/handle" { /** * @param {object=} [options] */ - datasync(): Promise<void>; + datasync(): Promise<TypeError>; /** * Opens the underlying descriptor for the file handle. * @param {object=} [options] @@ -5531,7 +5531,7 @@ declare module "socket:fs/handle" { * @param {number} position * @param {object=} [options] */ - write(buffer: Buffer | object, offset: number, length: number, position: number, options?: object | undefined): Promise<{ + write(buffer: Buffer | object, offset: number, length: number, position: number, options?: object | undefined): Promise<TypeError | { buffer: any; bytesWritten: number; }>; @@ -5542,7 +5542,7 @@ declare module "socket:fs/handle" { * @param {string=} [options.encoding = 'utf8'] * @param {object=} [options.signal] */ - writeFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise<void>; + writeFile(data: string | Buffer | TypedArray | any[], options?: object | undefined): Promise<TypeError>; [exports.kOpening]: any; [exports.kClosing]: any; [exports.kClosed]: boolean; @@ -5565,12 +5565,12 @@ declare module "socket:fs/handle" { */ static get DEFAULT_BUFFER_SIZE(): number; /** - * Creates a `FileHandle` from a given `id` or `fd` - * @param {string|number|DirectoryHandle|object} id + * Creates a `DirectoryHandle` from a given `id` or `fd` + * @param {string|number|DirectoryHandle|object|FileSystemDirectoryHandle} id * @param {object} options * @return {DirectoryHandle} */ - static from(id: string | number | DirectoryHandle | object, options: object): DirectoryHandle; + static from(id: string | number | DirectoryHandle | object | FileSystemDirectoryHandle, options: object): DirectoryHandle; /** * Asynchronously open a directory. * @param {string | Buffer | URL} path @@ -6420,7 +6420,7 @@ declare module "socket:fs/index" { * @param {number} mode * @param {function(Error?)} callback */ - export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): void; + export function chmod(path: string | Buffer | URL, mode: number, callback: (arg0: Error | null) => any): TypeError; /** * Synchronously changes the permissions of a file. * @@ -6436,7 +6436,7 @@ declare module "socket:fs/index" { * @param {number} gid * @param {function} callback */ - export function chown(path: string, uid: number, gid: number, callback: Function): void; + export function chown(path: string, uid: number, gid: number, callback: Function): TypeError; /** * Changes ownership of file or directory at `path` with `uid` and `gid`. * @param {string} path @@ -6519,7 +6519,7 @@ declare module "socket:fs/index" { * @param {number} gid * @param {function} callback */ - export function lchown(path: string, uid: number, gid: number, callback: Function): void; + export function lchown(path: string, uid: number, gid: number, callback: Function): TypeError; /** * Creates a link to `dest` from `src`. * @param {string} src @@ -9753,6 +9753,7 @@ declare module "socket:fs/web" { readonly type: string; slice(): void; arrayBuffer(): Promise<void>; + bytes(): Promise<void>; text(): Promise<void>; stream(): void; }; From 46547fa7d6eb843564320be43ae7bac925b16c42 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 6 Aug 2024 16:18:45 +0200 Subject: [PATCH 1091/1178] fix(api/dgram.js): fix send callback '...args' spread --- api/dgram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dgram.js b/api/dgram.js index 101d452b26..63b3176441 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -1030,7 +1030,7 @@ export class Socket extends EventEmitter { return } - return send(this, { id, port, address, buffer }, () => { + return send(this, { id, port, address, buffer }, (...args) => { if (buffer.buffer?.detached) { // XXX(@jwerle,@heapwolf): see above return From 3145619d038dc2cabf6c4d3e806803ca40623c3f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 7 Aug 2024 10:24:00 -0400 Subject: [PATCH 1092/1178] fix(core/modules/fs): fix path string usage --- src/core/modules/fs.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/modules/fs.cc b/src/core/modules/fs.cc index 1ae25acfe5..cf72b0de7c 100644 --- a/src/core/modules/fs.cc +++ b/src/core/modules/fs.cc @@ -387,7 +387,7 @@ namespace SSC { auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_access(loop, req, desc->resource.path.c_str(), mode, [](uv_fs_t* req) { + auto err = uv_fs_access(loop, req, desc->resource.path.string().c_str(), mode, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; @@ -490,7 +490,7 @@ namespace SSC { }); } - void CoreFS::chown ( + void CoreFS::chown ( const String& seq, const String& path, uv_uid_t uid, @@ -539,7 +539,7 @@ namespace SSC { } void CoreFS::lchown ( - const String& seq, + const String& seq, const String& path, uv_uid_t uid, uv_gid_t gid, @@ -780,7 +780,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_open(loop, req, desc->resource.path.c_str(), flags, mode, [](uv_fs_t* req) { + auto err = uv_fs_open(loop, req, desc->resource.path.string().c_str(), flags, mode, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto desc = ctx->descriptor; auto json = JSON::Object {}; @@ -950,7 +950,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_opendir(loop, req, desc->resource.path.c_str(), [](uv_fs_t *req) { + auto err = uv_fs_opendir(loop, req, desc->resource.path.string().c_str(), [](uv_fs_t *req) { auto ctx = (RequestContext *) req->data; auto desc = ctx->descriptor; auto json = JSON::Object {}; @@ -1744,7 +1744,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_stat(loop, req, desc->resource.path.c_str(), [](uv_fs_t *req) { + auto err = uv_fs_stat(loop, req, desc->resource.path.string().c_str(), [](uv_fs_t *req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; @@ -2072,7 +2072,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(desc, seq, callback); auto req = &ctx->req; - auto err = uv_fs_lstat(loop, req, desc->resource.path.c_str(), [](uv_fs_t* req) { + auto err = uv_fs_lstat(loop, req, desc->resource.path.string().c_str(), [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; @@ -2449,7 +2449,7 @@ namespace SSC { auto loop = &this->core->eventLoop; auto ctx = new RequestContext(seq, callback); auto req = &ctx->req; - auto err = uv_fs_copyfile(loop, req, src->resource.path.c_str(), dst->resource.path.c_str(), flags, [](uv_fs_t* req) { + auto err = uv_fs_copyfile(loop, req, src->resource.path.string().c_str(), dst->resource.path.string().c_str(), flags, [](uv_fs_t* req) { auto ctx = (RequestContext *) req->data; auto json = JSON::Object {}; From 4bb9f0aea01578365a2e667ec4b77b5b98fdeaa6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 7 Aug 2024 11:05:03 -0400 Subject: [PATCH 1093/1178] fix(api/npm/service-worker.js): fix 'export default' syntax detection --- api/npm/service-worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/npm/service-worker.js b/api/npm/service-worker.js index 9eb3096cc3..5873bdfeb8 100644 --- a/api/npm/service-worker.js +++ b/api/npm/service-worker.js @@ -126,7 +126,7 @@ export async function onRequest (request, env, ctx) { if (resolved.type === 'module') { const response = await fetch(resolved.url) const text = await response.text() - const proxy = /^(export default)/.test(text) + const proxy = /^\s*(export\s*default)/gm.test(text) ? ` import module from '${resolved.url}' export * from '${resolved.url}' From e7cdb6413c79ff7e6075d6a84cb64fc5c5a299f5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 8 Aug 2024 13:18:27 +0200 Subject: [PATCH 1094/1178] refactor(bin/clean.sh): clean env by default --- bin/clean.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/clean.sh b/bin/clean.sh index 7916108d6c..f27b0f62da 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -14,6 +14,10 @@ elif (( TARGET_IPHONE_SIMULATOR )); then platform="iPhoneSimulator" fi +if (( $# == 0 )); then + do_clean_env_only=1 +fi + while (( $# > 0 )); do declare arg="$1"; shift if [[ "$arg" = "--arch" ]]; then @@ -23,6 +27,7 @@ while (( $# > 0 )); do if [[ "$arg" = "--full" ]]; then do_full_clean=1 + do_clean_env_only=1 continue elif [[ "$arg" = "--platform" ]]; then if [[ "$1" = "ios" ]] || [[ "$1" = "iPhoneOS" ]] || [[ "$1" = "iphoneos" ]]; then From 55cfe7f30608cce038f8d387b7bc1dc5ae7d76de Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 8 Aug 2024 09:28:38 -0400 Subject: [PATCH 1095/1178] fix(bin/install.sh): advise 'cmake' when needed --- bin/install.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/install.sh b/bin/install.sh index 461b61f130..c2d72f4779 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -146,9 +146,6 @@ if [[ "$host" != "Win32" ]]; then fi fi -quiet command -v cmake -die $? "not ok - missing cmake, \"$(advice 'cmake')\"" - if [[ "$(uname -s)" != *"_NT"* ]]; then quiet command -v make die $? "not ok - missing build tools, try \"$(advice "make")\"" @@ -863,6 +860,9 @@ function _compile_llama { if [ "$platform" == "desktop" ]; then if [[ "$host" != "Win32" ]]; then + quiet command -v cmake + die $? "not ok - missing cmake, \"$(advice 'cmake')\"" + quiet cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/$target-$platform" ${cmake_args[@]} die $? "not ok - libllama.a (desktop)" @@ -877,6 +877,8 @@ function _compile_llama { config="Debug" fi cd "$STAGING_DIR/build/" || exit 1 + quiet command -v cmake + die $? "not ok - missing cmake, \"$(advice 'cmake')\"" quiet cmake -S .. -B . ${cmake_args[@]} quiet cmake --build . --config $config mkdir -p "$BUILD_DIR/$target-$platform/lib$d" @@ -985,6 +987,8 @@ function _compile_libuv { config="Debug" fi cd "$STAGING_DIR/build/" || exit 1 + quiet command -v cmake + die $? "not ok - missing cmake, \"$(advice 'cmake')\"" quiet cmake .. -DBUILD_TESTING=OFF -DLIBUV_BUILD_SHARED=OFF cd "$STAGING_DIR" || exit 1 quiet cmake --build "$STAGING_DIR/build/" --config $config From f93862363de637c96ea1648def3ae1c8105dac3b Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Thu, 8 Aug 2024 17:18:29 +0200 Subject: [PATCH 1096/1178] chore(api): update protocol --- api/latica/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/latica/index.js b/api/latica/index.js index 8c4fff8f1b..ad1b04be9e 100644 --- a/api/latica/index.js +++ b/api/latica/index.js @@ -567,7 +567,7 @@ export class Peer { await this.mcast(packet) this._onDebug(`-> RESEND (packetId=${packetId})`) - if (this.onState) this.onState(this.getState()) + if (this.onState) this.onState() } } @@ -946,7 +946,7 @@ export class Peer { }) this._onDebug(`-> JOIN (clusterId=${cid.slice(0, 6)}, subclusterId=${scid.slice(0, 6)}, clock=${packet.clock}/${this.clock})`) - if (this.onState) this.onState(this.getState()) + if (this.onState) this.onState() this.mcast(packet) this.gate.set(packet.packetId.toString('hex'), 1) From 03dfbbf8477953de08cd75e213566c62c9ec4e0a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 8 Aug 2024 20:51:59 +0200 Subject: [PATCH 1097/1178] fix(core/modules/notifications): fix start/resume state and block lambda capture crash --- src/core/modules/notifications.cc | 102 +++++++++++++++++------------- src/core/modules/notifications.hh | 2 + 2 files changed, 60 insertions(+), 44 deletions(-) diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index f0fef82f71..9990a4eb92 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -93,47 +93,7 @@ namespace SSC { permissionChangeObservers(), notificationResponseObservers(), notificationPresentedObservers() - { - #if SOCKET_RUNTIME_PLATFORM_APPLE - if (!core->options.features.useNotifications) return; - auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - - this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; - this->userNotificationCenterDelegate.notifications = this; - - if (!notificationCenter.delegate) { - notificationCenter.delegate = this->userNotificationCenterDelegate; - } - - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; - this->userNotificationCenterPollTimer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { - // look for authorization status changes - [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { - JSON::Object json; - if (this->currentUserNotificationAuthorizationStatus != settings.authorizationStatus) { - this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; - - if (settings.authorizationStatus == UNAuthorizationStatusDenied) { - json = JSON::Object::Entries {{"state", "denied"}}; - } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { - json = JSON::Object::Entries {{"state", "prompt"}}; - } else { - json = JSON::Object::Entries {{"state", "granted"}}; - } - - this->permissionChangeObservers.dispatch(json); - } - }]; - }]; - - [NSRunLoop.mainRunLoop - addTimer: this->userNotificationCenterPollTimer - forMode: NSDefaultRunLoopMode - ]; - }]; - #endif - } + {} CoreNotifications::~CoreNotifications () { #if SOCKET_RUNTIME_PLATFORM_APPLE @@ -156,6 +116,59 @@ namespace SSC { #endif } + void CoreNotifications::start () { + if (!this->core->options.features.useNotifications) return; + this->stop(); + #if SOCKET_RUNTIME_PLATFORM_APPLE + auto notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + + this->userNotificationCenterDelegate = [SSCUserNotificationCenterDelegate new]; + this->userNotificationCenterDelegate.notifications = this; + + if (!notificationCenter.delegate) { + notificationCenter.delegate = this->userNotificationCenterDelegate; + } + + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; + this->userNotificationCenterPollTimer = [NSTimer timerWithTimeInterval: 2 repeats: YES block: ^(NSTimer* timer) { + // look for authorization status changes + [notificationCenter getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings *settings) { + JSON::Object json; + if (this->currentUserNotificationAuthorizationStatus != settings.authorizationStatus) { + this->currentUserNotificationAuthorizationStatus = settings.authorizationStatus; + + if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + json = JSON::Object::Entries {{"state", "denied"}}; + } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + json = JSON::Object::Entries {{"state", "prompt"}}; + } else { + json = JSON::Object::Entries {{"state", "granted"}}; + } + + this->permissionChangeObservers.dispatch(json); + } + }]; + }]; + + [NSRunLoop.mainRunLoop + addTimer: this->userNotificationCenterPollTimer + forMode: NSDefaultRunLoopMode + ]; + }]; + #endif + } + + void CoreNotifications::stop () { + if (!this->core->options.features.useNotifications) return; + #if SOCKET_RUNTIME_PLATFORM_APPLE + if (this->userNotificationCenterPollTimer) { + [this->userNotificationCenterPollTimer invalidate]; + this->userNotificationCenterPollTimer = nullptr; + } + #endif + } + bool CoreNotifications::addPermissionChangeObserver ( const PermissionChangeObserver& observer, const PermissionChangeObserver::Callback& callback @@ -208,7 +221,7 @@ namespace SSC { NSError* error = nullptr; - auto __block id = options.id; + auto id = options.id; if (options.tag.size() > 0) { userInfo[@"tag"] = @(options.tag.c_str()); @@ -364,6 +377,7 @@ namespace SSC { trigger: nil ]; + auto cb = callback; [notificationCenter addNotificationRequest: request withCompletionHandler: ^(NSError* error) { if (error != nullptr) { const auto message = String( @@ -373,7 +387,7 @@ namespace SSC { ); this->core->dispatchEventLoop([=] () { - callback(ShowResult { message }); + cb(ShowResult { message }); }); #if !__has_feature(objc_arc) [content release]; @@ -382,7 +396,7 @@ namespace SSC { } this->core->dispatchEventLoop([=] () { - callback(ShowResult { "", options.id }); + cb(ShowResult { "", id }); }); }]; diff --git a/src/core/modules/notifications.hh b/src/core/modules/notifications.hh index 9e9869e35c..b2c8b2d77a 100644 --- a/src/core/modules/notifications.hh +++ b/src/core/modules/notifications.hh @@ -72,6 +72,8 @@ namespace SSC { CoreNotifications (Core* core); ~CoreNotifications (); + void start (); + void stop (); bool removePermissionChangeObserver (const PermissionChangeObserver& observer); bool addPermissionChangeObserver ( From 3ecdbefe6bf0b4adf3373f8f0a5e8e1a7c8342b9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 8 Aug 2024 20:52:20 +0200 Subject: [PATCH 1098/1178] fix(core): start/stop notification timers --- src/core/core.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 8698447069..a8d62907de 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -31,6 +31,8 @@ namespace SSC { return; } + this->runEventLoop(); + if (this->options.features.useUDP) { this->udp.resumeAllSockets(); } @@ -43,7 +45,10 @@ namespace SSC { this->conduit.start(); } - this->runEventLoop(); + if (options.features.useNotifications) { + this->notifications.start(); + } + this->isPaused = false; } @@ -52,6 +57,8 @@ namespace SSC { return; } + this->pauseEventLoop(); + if (this->options.features.useUDP) { this->udp.pauseAllSockets(); } @@ -64,7 +71,10 @@ namespace SSC { this->conduit.stop(); } - this->pauseEventLoop(); + if (options.features.useNotifications) { + this->notifications.stop(); + } + this->isPaused = true; } From c05a259912c290246c0ce5b286a34b8b378e17b0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 8 Aug 2024 20:52:41 +0200 Subject: [PATCH 1099/1178] fix(cli): fix watcher crash --- src/cli/cli.cc | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 6137e08905..10a7a97542 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -71,8 +71,6 @@ Process* buildAfterScriptProcess = nullptr; FileSystemWatcher* sourcesWatcher = nullptr; Thread* sourcesWatcherSupportThread = nullptr; -Mutex signalHandlerMutex; - Path targetPath; String settingsSource = ""; Map settings; @@ -85,16 +83,8 @@ bool flagVerboseMode = true; bool flagQuietMode = false; Map defaultTemplateAttrs; -#if defined(__APPLE__) -std::atomic<bool> checkLogStore = true; -static dispatch_queue_t queue = dispatch_queue_create( - "socket.runtime.cli.queue", - dispatch_queue_attr_make_with_qos_class( - DISPATCH_QUEUE_CONCURRENT, - QOS_CLASS_USER_INITIATED, - -1 - ) -); +#if SOCKET_RUNTIME_PLATFORM_APPLE +Atomic<bool> checkLogStore = true; #endif void log (const String s) { @@ -774,8 +764,6 @@ void signalHandler (int signum) { } #endif - Lock lock(signalHandlerMutex); - #if !SOCKET_RUNTIME_PLATFORM_WINDOWS if (appPid > 0) { kill(appPid, signum); @@ -789,12 +777,6 @@ void signalHandler (int signum) { appPid = 0; - if (sourcesWatcher != nullptr) { - sourcesWatcher->stop(); - delete sourcesWatcher; - sourcesWatcher = nullptr; - } - if (sourcesWatcherSupportThread != nullptr) { if (sourcesWatcherSupportThread->joinable()) { sourcesWatcherSupportThread->join(); From 931932a42da47d843bf2eabb5e00dc5cfb0f2183 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 9 Aug 2024 14:15:19 +0200 Subject: [PATCH 1100/1178] fix(core/modules/notifications): check if default icon exists --- src/core/modules/notifications.cc | 57 ++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/core/modules/notifications.cc b/src/core/modules/notifications.cc index 9990a4eb92..1e8668e9df 100644 --- a/src/core/modules/notifications.cc +++ b/src/core/modules/notifications.cc @@ -285,36 +285,39 @@ namespace SSC { } else { // using an asset from the resources directory will require a code signed application const auto path = FileResource::getResourcePath(String("icon.png")); - const auto iconURL = [NSURL fileURLWithPath: @(path.string().c_str())]; - const auto types = [UTType - typesWithTag: iconURL.pathExtension - tagClass: UTTagClassFilenameExtension - conformingToType: nullptr - ]; - - auto options = [NSMutableDictionary dictionary]; - auto attachment = [UNNotificationAttachment - attachmentWithIdentifier: @("") - URL: iconURL - options: options - error: &error - ]; - if (error != nullptr) { - auto message = String( - error.localizedDescription.UTF8String != nullptr - ? error.localizedDescription.UTF8String - : "An unknown error occurred" - ); + if (FileResource(path).exists()) { + const auto iconURL = [NSURL fileURLWithPath: @(path.string().c_str())]; + const auto types = [UTType + typesWithTag: iconURL.pathExtension + tagClass: UTTagClassFilenameExtension + conformingToType: nullptr + ]; + + auto options = [NSMutableDictionary dictionary]; + auto attachment = [UNNotificationAttachment + attachmentWithIdentifier: @("") + URL: iconURL + options: options + error: &error + ]; + + if (error != nullptr) { + auto message = String( + error.localizedDescription.UTF8String != nullptr + ? error.localizedDescription.UTF8String + : "An unknown error occurred" + ); + + callback(ShowResult { message }); + #if !__has_feature(objc_arc) + [content release]; + #endif + return false; + } - callback(ShowResult { message }); - #if !__has_feature(objc_arc) - [content release]; - #endif - return false; + [attachments addObject: attachment]; } - - [attachments addObject: attachment]; } if (options.image.size() > 0) { From 1fdc9ae5c01d1b54e05985deb78c5d1b2c26cb8c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 9 Aug 2024 14:15:35 +0200 Subject: [PATCH 1101/1178] refactor(ipc/bridge): set cache control for resource assets --- src/ipc/bridge.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index 807074069a..b64145e473 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -622,6 +622,7 @@ export * from '{{url}}' if (request->method == "GET") { if (resource.mimeType() != "text/html") { + response.setHeader("cache-control", "public"); response.send(resource); } else { const auto html = this->client.preload.insertIntoHTML(resource.str(), { From 737051345ebd99dbf88d58761ce3a0dcf348e6e2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 9 Aug 2024 14:16:01 +0200 Subject: [PATCH 1102/1178] refactor(api/notification.js): improve global Symbol usage --- api/notification.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/notification.js b/api/notification.js index 803a122f4d..e0ea968d66 100644 --- a/api/notification.js +++ b/api/notification.js @@ -703,7 +703,7 @@ export class Notification extends EventTarget { }) this.#proxy = proxy - this[Symbol.for('Notification.request')] = request + this[Symbol.for('socket.runtime.Notification.request')] = request } else { const request = ipc.request('notification.show', { body: this.body, @@ -716,7 +716,7 @@ export class Notification extends EventTarget { silent: this.silent }) - this[Symbol.for('Notification.request')] = request + this[Symbol.for('socket.runtime.Notification.request')] = request } state.pending.set(this.id, this) @@ -747,7 +747,7 @@ export class Notification extends EventTarget { }) // propagate error to caller - this[Symbol.for('Notification.request')].then((result) => { + this[Symbol.for('socket.runtime.Notification.request')].then((result) => { if (result?.err) { // @ts-ignore state.pending.delete(this.id, this) From 3716cc9da52c5e92ddd66384bf131a9ebef7304c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 9 Aug 2024 14:16:55 +0200 Subject: [PATCH 1103/1178] chore(api): generate types --- api/index.d.ts | 139 ++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/api/index.d.ts b/api/index.d.ts index 708c2d0001..b70a36af51 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T>(key: Variable<T>): boolean; + get<T_1>(key: Variable<T_1>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T>(key: Variable<T>, value: T): Mapping; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T>(key: Variable<T>): Mapping; + delete<T_3>(key: Variable<T_3>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T>(key: Variable<T>): T | undefined; + static get<T_1>(key: Variable<T_1>): T_1; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T>(revert: Revert<T> | FrozenRevert): void; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_1>(): T_1 | undefined; + get<T_2>(): T_2; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F>(fn: F): F; + static wrap<F_1>(fn: F_1): F_1; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent; + prototype: CustomEvent<any>; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent; + prototype: MessageEvent<any>; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1397,7 +1397,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2164,7 +2164,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): value is symbol; + export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2840,7 +2840,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3081,7 +3081,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3111,7 +3111,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): signal; + export function getCode(name: string | number): any; /** * Gets the name for a given 'signal' code * @return {string} @@ -3255,7 +3255,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3636,7 +3636,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: ProcessEnvironment; + export const env: any; export default process; const process: any; } @@ -4546,9 +4546,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + send: (body?: Document | XMLHttpRequestBodyInit) => void; }; } export class WorkerMetric extends Metric { @@ -5276,7 +5276,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; + static from(stat?: object | Stats, fromBigInt?: any): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6149,7 +6149,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: "utf8" | "buffer"; + encoding: 'utf8' | 'buffer'; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6993,7 +6993,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "top-level" | "nested"; + get frameType(): "none" | "nested" | "top-level"; /** * The type of the client. * @type {'window'|'worker'} @@ -7025,10 +7025,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: "window" | "worker"; + type?: 'window' | 'worker'; parent?: object | null; top?: object | null; - frameType?: "top-level" | "nested" | "none"; + frameType?: 'top-level' | 'nested' | 'none'; }; } @@ -7102,7 +7102,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7315,7 +7315,6 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; - import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8040,7 +8039,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8086,9 +8085,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): CoreAsyncResource; - bind(fn: Function, thisArg?: object | undefined): Function; - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; }; } export namespace Promise { @@ -8708,7 +8707,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9862,7 +9861,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9937,7 +9936,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: "shared" | "wasm32"; + type?: 'shared' | 'wasm32'; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10363,7 +10362,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: "strict" | "relaxed" | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10427,7 +10426,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: "set" | "delete", entry?: object | undefined); + constructor(type: 'set' | 'delete', entry?: object | undefined); entry: any; } /** @@ -10559,7 +10558,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import("socket:service-worker/events").ExtendableEvent); + constructor(event: import('./events.js').ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10600,7 +10599,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import("socket:service-worker/clients").Client>; + client(): Promise<import('./clients.js').Client>; #private; } namespace _default { @@ -10787,7 +10786,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10822,13 +10821,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13468,7 +13467,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T>(actual: T, expected: T, msg?: string): void; + notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; /** * @template T * @param {T} actual @@ -13476,7 +13475,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T>(actual: T, expected: T, msg?: string): void; + equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13725,7 +13724,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: { + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { /** * - The text to wait for */ @@ -13736,7 +13735,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; + }, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15017,7 +15016,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } export default Package; @@ -15028,13 +15027,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; prefix?: string; }; export type ParsedPackageName = { @@ -15074,7 +15073,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15434,7 +15433,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions | undefined): any; + require(url: any, options?: RequireOptions): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15472,9 +15471,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import process from "socket:process"; } declare module "socket:commonjs/require" { @@ -15533,7 +15532,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import("socket:commonjs/module").Module); + constructor(module: import('./module.js').Module); /** * The referrer (parent) of this module. * @type {string} @@ -16093,7 +16092,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; + static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; /** * `Notification` class constructor. * @param {string} title @@ -16279,9 +16278,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; export default createServiceWorker; @@ -16997,7 +16996,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -17017,7 +17016,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17039,8 +17038,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: "read" | "readwrite"; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + mode?: 'read' | 'readwrite'; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; }; /** * ]?: string[] @@ -17050,10 +17049,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; types?: Array<{ description?: string; - [keyof]: any; + [keyof]; }>; }; } @@ -17154,7 +17153,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17163,7 +17162,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: "commonjs" | "module"; + type: 'commonjs' | 'module'; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17244,8 +17243,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17259,7 +17258,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17708,12 +17707,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; + export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends Harness> { + export class TapeHarness<T extends exports.Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17766,7 +17765,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends Harness> = { + export type TapeTestFn<T extends exports.Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17783,8 +17782,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 91b47840264a95f60aef1485cd0319ea69bce5a5 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 9 Aug 2024 18:37:59 +0200 Subject: [PATCH 1104/1178] fix(core/webview,window/apple): fix fullscreen API on apple --- src/core/webview.cc | 15 +++++++++------ src/window/apple.mm | 2 ++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/webview.cc b/src/core/webview.cc index 42c6f58280..a3d0d3a22b 100644 --- a/src/core/webview.cc +++ b/src/core/webview.cc @@ -26,18 +26,20 @@ int lastY = 0; - (void) viewDidChangeEffectiveAppearance { [super viewDidChangeEffectiveAppearance]; + const auto window = (Window*) objc_getAssociatedObject(self, "window"); - if ([self.window.effectiveAppearance.name containsString: @"Dark"]) { - [self.window setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.1 alpha: 1.0]]; // Dark mode color + if ([window->window.effectiveAppearance.name containsString: @"Dark"]) { + [window->window setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.1 alpha: 1.0]]; // Dark mode color } else { - [self.window setBackgroundColor: [NSColor colorWithCalibratedWhite: 1.0 alpha: 1.0]]; // Light mode color + [window->window setBackgroundColor: [NSColor colorWithCalibratedWhite: 1.0 alpha: 1.0]]; // Light mode color } } - (void) resizeSubviewsWithOldSize: (NSSize) oldSize { [super resizeSubviewsWithOldSize: oldSize]; - const auto w = reinterpret_cast<SSCWindow*>(self.window); + const auto window = (Window*) objc_getAssociatedObject(self, "window"); + const auto w = reinterpret_cast<SSCWindow*>(window->window); const auto viewWidth = w.titleBarView.frame.size.width; const auto viewHeight = w.titleBarView.frame.size.height; const auto newX = w.windowControlOffsets.x; @@ -308,16 +310,17 @@ int lastY = 0; - (void) mouseDragged: (NSEvent*) event { NSPoint currentLocation = [self convertPoint:event.locationInWindow fromView:nil]; + const auto window = (Window*) objc_getAssociatedObject(self, "window"); if (self.shouldDrag) { CGFloat deltaX = currentLocation.x - self.initialWindowPos.x; CGFloat deltaY = currentLocation.y - self.initialWindowPos.y; - NSRect frame = self.window.frame; + NSRect frame = window->window.frame; frame.origin.x += deltaX; frame.origin.y -= deltaY; - [self.window setFrame:frame display:YES]; + [window->window setFrame:frame display:YES]; } [super mouseDragged:event]; diff --git a/src/window/apple.mm b/src/window/apple.mm index e93a057da5..98905a6db6 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -273,6 +273,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { ]; preferences.javaScriptCanOpenWindowsAutomatically = YES; + preferences.elementFullscreenEnabled = userConfig["permissions_allow_fullscreen"] != "false"; @try { if (userConfig["permissions_allow_fullscreen"] == "false") { @@ -406,6 +407,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { ]; this->webview.scrollView.delegate = this->windowDelegate; + this->webview.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; this->webview.autoresizingMask = ( UIViewAutoresizingFlexibleWidth | From ae0bf75aa643b97f4905a3fe41eddc3bbb4149fe Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 12 Aug 2024 16:59:50 +0200 Subject: [PATCH 1105/1178] fix(core/webview): fix resetting layout after fullscreen exit --- src/core/webview.cc | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/core/webview.cc b/src/core/webview.cc index a3d0d3a22b..b33fcf88b1 100644 --- a/src/core/webview.cc +++ b/src/core/webview.cc @@ -38,7 +38,7 @@ int lastY = 0; - (void) resizeSubviewsWithOldSize: (NSSize) oldSize { [super resizeSubviewsWithOldSize: oldSize]; - const auto window = (Window*) objc_getAssociatedObject(self, "window"); + auto window = (Window*) objc_getAssociatedObject(self, "window"); const auto w = reinterpret_cast<SSCWindow*>(window->window); const auto viewWidth = w.titleBarView.frame.size.width; const auto viewHeight = w.titleBarView.frame.size.height; @@ -77,6 +77,18 @@ int lastY = 0; - (void) layout { [super layout]; + auto window = (Window*) objc_getAssociatedObject(self, "window"); + + #if SOCKET_RUNTIME_PLATFORM_MACOS + self.autoresizesSubviews = YES; + self.autoresizingMask = ( + NSViewHeightSizable | + NSViewWidthSizable | + NSViewMaxXMargin | + NSViewMinYMargin + ); + self.translatesAutoresizingMaskIntoConstraints = YES; + #endif NSRect bounds = self.superview.bounds; @@ -90,7 +102,15 @@ int lastY = 0; if (self.margin > 0.0) { CGFloat borderWidth = self.margin; - self.frame = NSInsetRect(bounds, borderWidth, borderWidth); + #if SOCKET_RUNTIME_PLATFORM_MACOS + [window->window + setFrame: NSInsetRect(bounds, borderWidth, borderWidth) + display: YES + animate: NO + ]; + #elif SOCKET_RUNTIME_PLATFORM_IOS + window->window.frame = NSInsetRect(bounds, borderWidth, borderWidth); + #endif } } From 5c94a38d5b543d1f20cf27b7b024dc44e4dd49ab Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 12 Aug 2024 17:01:04 +0200 Subject: [PATCH 1106/1178] refactor(cli): clean up --- src/cli/cli.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 10a7a97542..89f3e30ff1 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -592,7 +592,7 @@ Vector<Path> handleBuildPhaseForCopyMappedFiles ( if (flagVerboseMode) { debug( - "[build.copy-map] %s = %s", + "copy %s ~> %s", fs::relative(src, targetPath).c_str(), fs::relative(dst, targetPath).c_str() ); @@ -4047,7 +4047,7 @@ int main (const int argc, const char* argv[]) { auto schemeName = (settings["build_name"] + ".xcscheme"); auto pathToProject = paths.platformSpecificOutputPath / projectName; auto pathToScheme = pathToProject / "xcshareddata" / "xcschemes"; - auto pathToProfile = targetPath / settings["ios_provisioning_profile"]; + auto pathToProfile = fs::absolute(targetPath / settings["ios_provisioning_profile"]); fs::create_directories(pathToProject); fs::create_directories(pathToScheme); From 853a0dd181ae106e8b7876f0dd4f8dbfd6718889 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 12 Aug 2024 17:01:28 +0200 Subject: [PATCH 1107/1178] refactor(app): emit pause/resume events right away, dispatch core pause/resume --- src/app/app.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 32e3bd152f..68e19a4931 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -852,16 +852,18 @@ namespace SSC { void App::resume () { if (this->core != nullptr && this->paused) { this->paused = false; - this->core->resume(); this->windowManager.emit("applicationresume"); + this->dispatch([this]() { + this->core->resume(); + }); } } void App::pause () { if (this->core != nullptr && !this->paused) { this->paused = true; - this->core->pause(); this->windowManager.emit("applicationpause"); + this->core->pause(); } } @@ -872,7 +874,9 @@ namespace SSC { this->stopped = true; this->windowManager.emit("applicationstop"); - this->pause(); + this->dispatch([this]() { + this->pause(); + }); SSC::applicationInstance = nullptr; From 17e1134c0ab2992995d01cc76c60c725df0f30ef Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 12 Aug 2024 19:22:46 +0200 Subject: [PATCH 1108/1178] fix(window/apple): fix window style constraints and bad 'eval()' --- src/window/apple.mm | 74 +++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index 98905a6db6..b87f46198f 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -457,7 +457,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { this->bridge.configureWebView(this->webview); #if SOCKET_RUNTIME_PLATFORM_MACOS // Window style: titled, closable, minimizable - uint style = NSWindowStyleMaskTitled; + uint style = NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; // Set window to be resizable if (options.resizable) { @@ -827,48 +827,50 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { } void Window::eval (const String& source, const EvalCallback& callback) { - if (this->webview != nullptr) { - [this->webview - evaluateJavaScript: @(source.c_str()) - completionHandler: ^(id result, NSError *error) - { - if (error) { - debug("JavaScriptError: %@", error); + App::sharedApplication()->dispatch([=, this]() { + if (this->webview != nullptr) { + [this->webview + evaluateJavaScript: @(source.c_str()) + completionHandler: ^(id result, NSError *error) + { + if (error) { + debug("JavaScriptError: %@", error); + + if (callback != nullptr) { + callback(JSON::Error(error.localizedDescription.UTF8String)); + } - if (callback != nullptr) { - callback(JSON::Error(error.localizedDescription.UTF8String)); + return; } - return; - } - - if (callback != nullptr) { - if ([result isKindOfClass: NSString.class]) { - const auto value = String([result UTF8String]); - if (value == "null" || value == "undefined") { - callback(nullptr); - } else if (value == "true") { - callback(true); - } else if (value == "false") { - callback(value); - } else { - double number = 0.0f; - - try { - number = std::stod(value); - } catch (...) { + if (callback != nullptr) { + if ([result isKindOfClass: NSString.class]) { + const auto value = String([result UTF8String]); + if (value == "null" || value == "undefined") { + callback(nullptr); + } else if (value == "true") { + callback(true); + } else if (value == "false") { callback(value); - return; - } + } else { + double number = 0.0f; + + try { + number = std::stod(value); + } catch (...) { + callback(value); + return; + } - callback(number); + callback(number); + } + } else if ([result isKindOfClass: NSNumber.class]) { + callback([result doubleValue]); } - } else if ([result isKindOfClass: NSNumber.class]) { - callback([result doubleValue]); } - } - }]; - } + }]; + } + }); } void Window::setSystemMenuItemEnabled (bool enabled, int barPos, int menuPos) { From f2a34a2239e8662aa9f4b1da6ce442d66765211a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 12 Aug 2024 19:23:28 +0200 Subject: [PATCH 1109/1178] fix(app): fix pause dispatch --- src/app/app.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index 68e19a4931..e0378a85c0 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -290,7 +290,10 @@ didFailToContinueUserActivityWithType: (NSString*) userActivityType } - (void) applicationWillTerminate: (UIApplication*) application { - // TODO(@jwerle): what should we do here? + dispatch_async(queue, ^{ + // TODO(@jwerle): what should we do here? + self.app->stop(); + }); } - (void) applicationDidBecomeActive: (UIApplication*) application { @@ -863,7 +866,9 @@ namespace SSC { if (this->core != nullptr && !this->paused) { this->paused = true; this->windowManager.emit("applicationpause"); - this->core->pause(); + this->dispatch([this]() { + this->core->pause(); + }); } } @@ -874,9 +879,7 @@ namespace SSC { this->stopped = true; this->windowManager.emit("applicationstop"); - this->dispatch([this]() { - this->pause(); - }); + this->pause(); SSC::applicationInstance = nullptr; From d0da96732ef60cbebcd0c8b8e4a53575756c248b Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:34:46 +0200 Subject: [PATCH 1110/1178] refactor(window/apple): clean up --- src/window/apple.mm | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/window/apple.mm b/src/window/apple.mm index b87f46198f..90a8022713 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -190,7 +190,6 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { auto userConfig = options.userConfig; this->index = options.index; - //this->processPool = [WKProcessPool new]; this->windowDelegate = [SSCWindowDelegate new]; this->bridge.navigateFunction = [this] (const auto url) { @@ -198,9 +197,7 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { }; this->bridge.evaluateJavaScriptFunction = [this](auto source) { - dispatch_async(dispatch_get_main_queue(), ^{ - this->eval(source); - }); + this->eval(source); }; this->bridge.client.preload = IPC::Preload::compile({ @@ -224,7 +221,6 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/3585117-limitsnavigationstoappbounddomai configuration.limitsNavigationsToAppBoundDomains = YES; configuration.websiteDataStore = WKWebsiteDataStore.defaultDataStore; - //configuration.processPool = this->processPool; if (@available(macOS 14.0, iOS 17.0, *)) { [configuration.websiteDataStore.httpCookieStore From c4cfb18e4fc7767b69ce91466a8a637aa3f09920 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:35:22 +0200 Subject: [PATCH 1111/1178] refaactor(ipc/scheme_handlers): remove unused mutex --- src/ipc/routes.cc | 1 - src/ipc/scheme_handlers.cc | 5 ----- src/ipc/scheme_handlers.hh | 1 - 3 files changed, 7 deletions(-) diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index 0120bf5ec8..e557b90985 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -3519,7 +3519,6 @@ static void mapIPCRoutes (Router *router) { } app->dispatch([=]() { - debug("eval: %s", getEmitToRenderProcessJavaScript(event, value).c_str()); targetWindow->eval(getEmitToRenderProcessJavaScript(event, value)); reply(Result { message.seq, message }); }); diff --git a/src/ipc/scheme_handlers.cc b/src/ipc/scheme_handlers.cc index f57d64dd91..c885aeb96a 100644 --- a/src/ipc/scheme_handlers.cc +++ b/src/ipc/scheme_handlers.cc @@ -97,7 +97,6 @@ using Task = id<WKURLSchemeTask>; auto response = IPC::SchemeHandlers::Response(request, 404); response.finish(); [self finalizeTask: task]; - return; } } @end @@ -366,7 +365,6 @@ namespace SSC::IPC { } SchemeHandlers::~SchemeHandlers () { - Lock lock(this->mutex); for (auto& entry : this->activeRequests) { entry.second->handlers = nullptr; } @@ -509,7 +507,6 @@ namespace SSC::IPC { auto span = request->tracer.span("handler"); this->bridge->dispatch([=, this] () mutable { - Lock lock(this->mutex); if (request != nullptr && request->isActive() && !request->isCancelled()) { handler(request, this->bridge, &request->callbacks, [=, this](auto& response) mutable { // make sure the response was finished before @@ -1220,8 +1217,6 @@ namespace SSC::IPC { size_t size, SharedPointer<char[]> bytes ) { - Lock lock(this->mutex); - if ( !this->handlers->isRequestActive(this->id) || this->handlers->isRequestCancelled(this->id) diff --git a/src/ipc/scheme_handlers.hh b/src/ipc/scheme_handlers.hh index 9dcfe18317..f1de33e67a 100644 --- a/src/ipc/scheme_handlers.hh +++ b/src/ipc/scheme_handlers.hh @@ -185,7 +185,6 @@ namespace SSC::IPC { Headers headers; Client client; - Mutex mutex; Atomic<bool> finished = false; Vector<SharedPointer<char[]>> buffers; From c7a21ce4bea7dff5682f9d1c4a89a9894dfdae0a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:35:36 +0200 Subject: [PATCH 1112/1178] refactor(desktop/main): clean up --- src/desktop/main.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 50f23c04c0..0f29565efb 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -116,7 +116,6 @@ void signalHandler (int signum) { defaultWindowSignalHandler(signum); } - msleep(32); if (signum == SIGTERM || signum == SIGINT) { signal(signum, SIG_DFL); if (shutdownHandler != nullptr) { From f22613d72e15a1184b463ba38e5317bab08d4226 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:35:58 +0200 Subject: [PATCH 1113/1178] refactor(core/socket): improve close logic --- src/core/socket.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/core/socket.cc b/src/core/socket.cc index 93cab232a0..f9357fb0fd 100644 --- a/src/core/socket.cc +++ b/src/core/socket.cc @@ -441,15 +441,12 @@ namespace SSC { } int Socket::recvstop () { - int err = 0; - if (this->hasState(SOCKET_STATE_UDP_RECV_STARTED)) { this->removeState(SOCKET_STATE_UDP_RECV_STARTED); - Lock lock(this->core->loopMutex); - err = uv_udp_recv_stop((uv_udp_t *) &this->handle); + return uv_udp_recv_stop((uv_udp_t *) &this->handle); } - return err; + return 0; } int Socket::resume () { @@ -485,7 +482,11 @@ namespace SSC { this->addState(SOCKET_STATE_UDP_PAUSED); if (this->isBound()) { Lock lock(this->mutex); - uv_close((uv_handle_t *) &this->handle, nullptr); + if ( + !uv_is_closing(reinterpret_cast<uv_handle_t*>(&this->handle)) + ) { + uv_close((uv_handle_t *) &this->handle, nullptr); + } } else if (this->isConnected()) { // TODO } @@ -499,6 +500,8 @@ namespace SSC { } void Socket::close (Function<void()> onclose) { + Lock lock(this->mutex); + if (this->isClosed()) { this->core->udp.removeSocket(this->id); if (onclose != nullptr) { @@ -515,12 +518,10 @@ namespace SSC { } if (onclose != nullptr) { - Lock lock(this->mutex); this->onclose.push_back(onclose); } if (this->type == SOCKET_TYPE_UDP) { - Lock lock(this->mutex); // reset state and set to CLOSED uv_close((uv_handle_t*) &this->handle, [](uv_handle_t *handle) { auto socket = (Socket *) handle->data; From f82e753e2f7be5d12d29d7505503915235269a0d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:36:45 +0200 Subject: [PATCH 1114/1178] refactor(core): consolidate mutexes --- src/core/core.hh | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/core.hh b/src/core/core.hh index 394bb8f067..e7f5c9ea2a 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -129,9 +129,6 @@ namespace SSC { Posts posts; Mutex mutex; - Mutex loopMutex; - Mutex postsMutex; - Mutex timersMutex; Atomic<bool> didLoopInit = false; Atomic<bool> didTimersInit = false; From 733c6240230b5bbca4e5013d5bb10974badd09a6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:37:53 +0200 Subject: [PATCH 1115/1178] refactor(core): wait for async dispatch to dequeue, close loop in 'stop()' --- src/core/core.cc | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index a8d62907de..7b37815dd0 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -57,8 +57,6 @@ namespace SSC { return; } - this->pauseEventLoop(); - if (this->options.features.useUDP) { this->udp.pauseAllSockets(); } @@ -76,6 +74,7 @@ namespace SSC { } this->isPaused = true; + this->pauseEventLoop(); } bool Core::hasPost (uint64_t id) { @@ -92,7 +91,7 @@ namespace SSC { } void Core::expirePosts () { - Lock lock(this->postsMutex); + Lock lock(this->mutex); const auto now = std::chrono::system_clock::now() .time_since_epoch() .count(); @@ -113,7 +112,7 @@ namespace SSC { } void Core::putPost (uint64_t id, Post p) { - Lock lock(this->postsMutex); + Lock lock(this->mutex); p.ttl = std::chrono::time_point_cast<std::chrono::milliseconds>( std::chrono::system_clock::now() + std::chrono::milliseconds(32 * 1024) @@ -125,7 +124,7 @@ namespace SSC { } void Core::removePost (uint64_t id) { - Lock lock(this->postsMutex); + Lock lock(this->mutex); if (this->posts.find(id) != this->posts.end()) { // debug("remove post %ld", this->posts.at(id).body.use_count()); @@ -172,8 +171,8 @@ namespace SSC { } void Core::removeAllPosts () { - Lock lock(this->postsMutex); - std::vector<uint64_t> ids; + Lock lock(this->mutex); + Vector<uint64_t> ids; for (auto const &tuple : posts) { auto id = tuple.first; @@ -243,7 +242,7 @@ namespace SSC { } didLoopInit = true; - Lock lock(this->loopMutex); + Lock lock(this->mutex); uv_loop_init(&this->eventLoop); uv_loop_set_data(&this->eventLoop, reinterpret_cast<void*>(this)); this->eventLoopAsync.data = reinterpret_cast<void*>(this); @@ -255,7 +254,7 @@ namespace SSC { Function<void()> dispatch = nullptr; do { - Lock lock(core->loopMutex); + Lock lock(core->mutex); if (core->eventLoopDispatchQueue.size() > 0) { dispatch = core->eventLoopDispatchQueue.front(); core->eventLoopDispatchQueue.pop(); @@ -303,7 +302,16 @@ namespace SSC { } void Core::pauseEventLoop() { + // wait for drain of event loop dispatch queue + while (true) { + Lock lock(this->mutex); + if (eventLoopDispatchQueue.size() == 0) { + break; + } + } + isLoopRunning = false; + uv_stop(&eventLoop); } @@ -330,6 +338,8 @@ namespace SSC { } #endif #endif + + uv_loop_close(&eventLoop); } void Core::sleepEventLoop (int64_t ms) { @@ -347,13 +357,13 @@ namespace SSC { void Core::signalDispatchEventLoop () { initEventLoop(); runEventLoop(); - Lock lock(this->loopMutex); + Lock lock(this->mutex); uv_async_send(&eventLoopAsync); } void Core::dispatchEventLoop (EventLoopDispatchCallback callback) { { - Lock lock(this->loopMutex); + Lock lock(this->mutex); eventLoopDispatchQueue.push(callback); } @@ -364,10 +374,8 @@ namespace SSC { auto loop = core->getEventLoop(); while (core->isLoopRunning) { - core->sleepEventLoop(EVENT_LOOP_POLL_TIMEOUT); - do { - uv_run(loop, UV_RUN_DEFAULT); + while (uv_run(loop, UV_RUN_DEFAULT) != 0); } while (core->isLoopRunning && core->isLoopAlive()); } @@ -388,13 +396,13 @@ namespace SSC { }); #if SOCKET_RUNTIME_PLATFORM_APPLE - Lock lock(this->loopMutex); + Lock lock(this->mutex); dispatch_async(eventLoopQueue, ^{ pollEventLoop(this); }); #else #if SOCKET_RUNTIME_PLATFORM_LINUX if (this->options.dedicatedLoopThread) { #endif - Lock lock(this->loopMutex); + Lock lock(this->mutex); // clean up old thread if still running if (eventLoopThread != nullptr) { if (eventLoopThread->joinable()) { @@ -511,7 +519,7 @@ namespace SSC { return; } - Lock lock(this->timersMutex); + Lock lock(this->mutex); auto loop = getEventLoop(); @@ -529,7 +537,7 @@ namespace SSC { } void Core::startTimers () { - Lock lock(this->timersMutex); + Lock lock(this->mutex); Vector<Timer *> timersToStart = { &releaseStrongReferenceDescriptors, @@ -561,7 +569,7 @@ namespace SSC { return; } - Lock lock(this->timersMutex); + Lock lock(this->mutex); Vector<Timer *> timersToStop = { &releaseStrongReferenceDescriptors, From 4cfdff596fb72f6f0b0bab8884ebab0ec51bce36 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:38:23 +0200 Subject: [PATCH 1116/1178] refactor(core/modules/diagnostics): include handles in 'uv' diagnostic --- src/core/modules/diagnostics.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/modules/diagnostics.cc b/src/core/modules/diagnostics.cc index 0a784305cf..6e63024ade 100644 --- a/src/core/modules/diagnostics.cc +++ b/src/core/modules/diagnostics.cc @@ -19,7 +19,7 @@ namespace SSC { // posts diagnostics do { - Lock lock(this->core->postsMutex); + Lock lock(this->core->mutex); query.posts.handles.count = this->core->posts.size(); for (const auto& entry : this->core->posts) { query.posts.handles.ids.push_back(entry.first); @@ -101,7 +101,7 @@ namespace SSC { // uv do { - Lock lock(this->core->loopMutex); + Lock lock(this->core->mutex); uv_metrics_info(&this->core->eventLoop, &query.uv.metrics); query.uv.idleTime = uv_metrics_idle_time(&this->core->eventLoop); query.uv.handles.count = this->core->eventLoop.active_handles; @@ -135,7 +135,8 @@ namespace SSC { {"eventsWaiting", this->metrics.events_waiting}, }}, {"idleTime", this->idleTime}, - {"activeRequests", this->activeRequests} + {"activeRequests", this->activeRequests}, + {"handles", this->handles.json()} }; } From 0f09705a38c965f3a4ed0e4dc30c3ad15091a557 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:39:47 +0200 Subject: [PATCH 1117/1178] fix(cli): fix asset compilation target for ios --- src/cli/cli.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 89f3e30ff1..6d78e30d99 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2810,7 +2810,7 @@ int main (const int argc, const char* argv[]) { auto binaryPath = paths.pathBin / executable; auto configPath = targetPath / "socket.ini"; - if (!fs::exists(binaryPath) && !flagBuildForAndroid && !flagBuildForAndroidEmulator) { + if (!fs::exists(binaryPath) && !flagBuildForAndroid && !flagBuildForAndroidEmulator && !flagBuildForIOS && !flagBuildForSimulator) { flagRunUserBuildOnly = false; } else { struct stat stats; @@ -2966,7 +2966,7 @@ int main (const int argc, const char* argv[]) { << "xcrun " << "actool \"" << assetsPath.string() << "\" " << "--compile \"" << dest.string() << "\" " - << "--platform " << (platform.mac ? "macosx" : "iphone") << " " + << "--platform " << (targetPlatform == "ios" || targetPlatform == "ios-simulator" ? "iphoneos" : "macosx") << " " << "--minimum-deployment-target 10.15 " << "--app-icon AppIcon " << "--output-partial-info-plist " @@ -5025,7 +5025,11 @@ int main (const int argc, const char* argv[]) { // // Copy and or create the source files we need for the build. // - fs::copy(trim(prefixFile("src/init.cc")), pathToDist); + fs::copy( + trim(prefixFile("src/init.cc")), + pathToDist, + fs::copy_options::overwrite_existing + ); auto pathBase = pathToDist / "Base.lproj"; fs::create_directories(pathBase); From 354a83dfa83d58615d5fc958e645e1d341cdf6c3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:40:11 +0200 Subject: [PATCH 1118/1178] chore(app): clean up --- src/app/app.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/app.cc b/src/app/app.cc index e0378a85c0..afe4256504 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -856,6 +856,7 @@ namespace SSC { if (this->core != nullptr && this->paused) { this->paused = false; this->windowManager.emit("applicationresume"); + this->dispatch([this]() { this->core->resume(); }); @@ -866,6 +867,7 @@ namespace SSC { if (this->core != nullptr && !this->paused) { this->paused = true; this->windowManager.emit("applicationpause"); + this->dispatch([this]() { this->core->pause(); }); From 48389bba15a193a1141cd4fbc3fa1b9f327c8315 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:43:03 +0200 Subject: [PATCH 1119/1178] refactor(core/modules/conduit): close server and clients correctly --- src/core/modules/conduit.cc | 526 ++++++++++++++++++++++++++---------- src/core/modules/conduit.hh | 38 +-- 2 files changed, 408 insertions(+), 156 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index bdd3a220f3..c46836062d 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -1,8 +1,9 @@ -#include "conduit.hh" -#include "../core.hh" #include "../../app/app.hh" +#include "../core.hh" #include "../codec.hh" +#include "conduit.hh" + #define SHA_DIGEST_LENGTH 20 namespace SSC { @@ -116,6 +117,24 @@ namespace SSC { return this->clients.find(id) != this->clients.end(); } + CoreConduit::Client::~Client () { + auto handle = reinterpret_cast<uv_handle_t*>(&this->handle); + + if (frameBuffer) { + delete [] frameBuffer; + } + + if ( + this->isClosing == false && + this->isClosed == false && + handle->loop != nullptr && + !uv_is_closing(handle) && + uv_is_active(handle) + ) { + // XXX(@jwerle): figure out a gracefull close + } + } + CoreConduit::Client* CoreConduit::get (uint64_t id) { Lock lock(this->mutex); const auto it = clients.find(id); @@ -128,12 +147,12 @@ namespace SSC { } void CoreConduit::handshake (CoreConduit::Client *client, const char *request) { - String req(request); + String requestString(request); - size_t reqeol = req.find("\r\n"); + auto reqeol = requestString.find("\r\n"); if (reqeol == String::npos) return; // nope - std::istringstream iss(req.substr(0, reqeol)); + std::istringstream iss(requestString.substr(0, reqeol)); String method; String url; String version; @@ -168,9 +187,22 @@ namespace SSC { client->id = socketId; client->clientId = clientId; - this->clients.emplace(socketId, client); + do { + Lock lock(this->mutex); + if (this->clients.contains(socketId)) { + auto existingClient = this->clients.at(socketId); + this->clients.erase(socketId); + existingClient->close([existingClient]() { + if (existingClient->isClosed) { + delete existingClient; + } + }); + } + + this->clients.emplace(socketId, client); - std::cout << "added client " << this->clients.size() << std::endl; + std::cout << "added client " << this->clients.size() << std::endl; + } while (0); // debug("Received key: %s", keyHeader.c_str()); @@ -183,38 +215,43 @@ namespace SSC { // debug("Generated Accept Key: %s\n", base64_accept_key); // Debugging statement - std::ostringstream oss; - - oss << "HTTP/1.1 101 Switching Protocols\r\n" - << "Upgrade: websocket\r\n" - << "Connection: Upgrade\r\n" - << "Sec-WebSocket-Accept: " << base64_accept_key << "\r\n\r\n"; - - String response = oss.str(); - // debug(response.c_str()); + StringStream oss; + oss + << "HTTP/1.1 101 Switching Protocols\r\n" + << "Upgrade: websocket\r\n" + << "Connection: Upgrade\r\n" + << "Sec-WebSocket-Accept: " << base64_accept_key << "\r\n\r\n"; + const auto response = oss.str(); const auto size = response.size(); const auto data = new char[size]{0}; memcpy(data, response.c_str(), size); - uv_buf_t wrbuf = uv_buf_init(data, size); - uv_write_t *writeRequest = new uv_write_t; - uv_handle_set_data(reinterpret_cast<uv_handle_t*>(writeRequest), data); - //writeRequest->bufs = &wrbuf; - - uv_write(writeRequest, (uv_stream_t*)&client->handle, &wrbuf, 1, [](uv_write_t *req, int status) { - // if (status) debug(stderr, "write error %s\n", uv_strerror(status)); - - auto data = reinterpret_cast<char*>( - uv_handle_get_data(reinterpret_cast<uv_handle_t*>(req)) - ); + const auto buf = uv_buf_init(data, size); + auto req = new uv_write_t; + + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(req), + data + ); + + uv_write( + req, + reinterpret_cast<uv_stream_t*>(&client->handle), + &buf, + 1, + [](uv_write_t *req, int status) { + const auto data = reinterpret_cast<char*>( + uv_handle_get_data(reinterpret_cast<uv_handle_t*>(req)) + ); + + if (data != nullptr) { + delete [] data; + } - if (data != nullptr) { - delete [] data; + delete req; } - - free(req); - }); + ); free(base64_accept_key); @@ -235,6 +272,11 @@ namespace SSC { uint64_t payload_len = data[1] & 0x7F; size_t pos = 2; + if (opcode == 0x08) { + client->close(); + return; + } + if (payload_len == 126) { if (len < 4) return; // too short to be valid payload_len = (data[2] << 8) | data[3]; @@ -256,7 +298,8 @@ namespace SSC { pos += 4; if (payload_len > client->frameBufferSize) { - client->frameBuffer = (unsigned char *)realloc(client->frameBuffer, payload_len); + // TODO(@jwerle): refactor to drop usage of `realloc()` + client->frameBuffer = static_cast<unsigned char *>(realloc(client->frameBuffer, payload_len)); client->frameBufferSize = payload_len; } @@ -295,26 +338,44 @@ namespace SSC { ss << "&" << key << "=" << value; } + const auto uri = ss.str(); const auto app = App::sharedApplication(); const auto window = app->windowManager.getWindowForClient({ .id = client->clientId }); if (window != nullptr) { - const auto invoked = window->bridge.router.invoke(ss.str(), vectorToSharedPointer(decoded.payload), decoded.payload.size()); - if (!invoked) { - // TODO(@jwerle,@heapwolf): handle this - // debug("there was a problem invoking the router %s", ss.str().c_str()); - } + const auto bytes = vectorToSharedPointer(decoded.payload); + const auto size = decoded.payload.size(); + app->dispatch([app, window, uri, client, bytes, size]() { + const auto invoked = window->bridge.router.invoke( + uri, + bytes, + size + ); + + if (!invoked) { + // TODO(@jwerle,@heapwolf): handle this + // debug("there was a problem invoking the router %s", ss.str().c_str()); + } + }); } else { // TODO(@jwerle,@heapwolf): handle this } } - bool SSC::CoreConduit::Client::emit ( + struct ClientWriteContext { + CoreConduit::Client* client = nullptr; + const Function<void()> callback = nullptr; + }; + + bool CoreConduit::Client::emit ( const CoreConduit::Options& options, SharedPointer<char[]> bytes, - size_t length + size_t length, + int opcode, + const Function<void()> callback ) { + auto handle = reinterpret_cast<uv_handle_t*>(&this->handle); + if (!this->conduit) { - // std::cout << "Error: 'conduit' is a null pointer." << std::endl; return false; } @@ -324,11 +385,11 @@ namespace SSC { try { encodedMessage = this->conduit->encodeMessage(options, payload); } catch (const std::exception& e) { - // std::cerr << "Error in encodeMessage: " << e.what() << std::endl; + debug("CoreConduit::Client: Error - Failed to encode message payload: %s", e.what()); return false; } - this->conduit->core->dispatchEventLoop([this, encodedMessage = std::move(encodedMessage)]() mutable { + this->conduit->core->dispatchEventLoop([this, opcode, callback, handle, encodedMessage = std::move(encodedMessage)]() mutable { size_t encodedLength = encodedMessage.size(); Vector<unsigned char> frame; @@ -348,114 +409,268 @@ namespace SSC { } } - frame[0] = 0x82; // FIN and opcode 2 (binary) + frame[0] = 0x80 | opcode; // FIN and opcode 2 (binary) std::memcpy(frame.data() + frame.size() - encodedLength, encodedMessage.data(), encodedLength); - auto writeReq = new uv_write_t; + auto req = new uv_write_t; auto data = reinterpret_cast<char*>(frame.data()); auto size = frame.size(); - uv_buf_t wrbuf = uv_buf_init(data, size); + uv_buf_t buf = uv_buf_init(data, size); - uv_write(writeReq, (uv_stream_t*)&this->handle, &wrbuf, 1, [](uv_write_t* req, int status) { - if (status) { - // debug("Write error: %s", uv_strerror(status)); + if (callback != nullptr) { + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(req), + new ClientWriteContext { this, callback } + ); + } else { + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(req), + nullptr + ); + } + + uv_write( + req, + reinterpret_cast<uv_stream_t*>(&this->handle), + &buf, + 1, + [](uv_write_t* req, int status) { + const auto data = uv_handle_get_data(reinterpret_cast<uv_handle_t*>(req)); + const auto context = static_cast<ClientWriteContext*>(data); + + delete req; + + if (context != nullptr) { + context->client->conduit->core->dispatchEventLoop([=]() mutable { + context->callback(); + delete context; + }); + } } - delete req; - }); + ); }); return true; } + struct ClientCloseContext { + CoreConduit::Client* client = nullptr; + CoreConduit::Client::CloseCallback callback = nullptr; + }; + + void CoreConduit::Client::close (const CloseCallback& callback) { + auto handle = reinterpret_cast<uv_handle_t*>(&this->handle); + + if (this->isClosing || this->isClosed || !uv_is_active(handle)) { + if (callback != nullptr) { + this->conduit->core->dispatchEventLoop(callback); + } + return; + } + + this->isClosing = true; + + if (handle->loop == nullptr || uv_is_closing(handle)) { + this->isClosed = true; + this->isClosing = false; + + if (uv_is_active(handle)) { + uv_read_stop(reinterpret_cast<uv_stream_t*>(handle)); + } + + if (callback != nullptr) { + this->conduit->core->dispatchEventLoop(callback); + } + return; + } + + const auto closeHandle = [=, this]() { + if (uv_is_closing(handle)) { + this->isClosed = true; + this->isClosing = false; + + if (callback != nullptr) { + this->conduit->core->dispatchEventLoop(callback); + } + return; + } + + do { + Lock lock(this->conduit->mutex); + if (this->conduit->clients.contains(this->id)) { + conduit->clients.erase(this->id); + } + } while (0); + + auto shutdown = new uv_shutdown_t; + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(shutdown), + new ClientCloseContext { this, callback } + ); + + if (uv_is_active(handle)) { + uv_read_stop(reinterpret_cast<uv_stream_t*>(handle)); + } + + uv_shutdown(shutdown, reinterpret_cast<uv_stream_t*>(handle), [](uv_shutdown_t* shutdown, int status) { + auto data = uv_handle_get_data(reinterpret_cast<uv_handle_t*>(shutdown)); + auto context = static_cast<ClientCloseContext*>(data); + auto client = context->client; + auto handle = reinterpret_cast<uv_handle_t*>(&client->handle); + auto callback = context->callback; + + delete shutdown; + + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(handle), + context + ); + + uv_close(handle, [](uv_handle_t* handle) { + auto data = uv_handle_get_data(reinterpret_cast<uv_handle_t*>(handle)); + auto context = static_cast<ClientCloseContext*>(data); + auto client = context->client; + auto conduit = client->conduit; + auto callback = context->callback; + + client->isClosed = true; + client->isClosing = false; + + delete context; + + if (callback != nullptr) { + client->conduit->core->dispatchEventLoop(callback); + } + }); + }); + }; + + this->emit({}, vectorToSharedPointer({ 0x00}), 1, 0x08, closeHandle); + } + void CoreConduit::start (const StartCallback& callback) { - if (this->isActive()) { + if (this->isActive() || this->isStarting) { if (callback != nullptr) { - msleep(256); - callback(); + this->core->dispatchEventLoop(callback); } return; } auto loop = this->core->getEventLoop(); + this->isStarting = true; + + auto ip = Env::get("SOCKET_RUNTIME_CONDUIT_HOSTNAME", "0.0.0.0"); + auto port = this->port.load(); + + if (Env::has("SOCKET_RUNTIME_CONDUIT_PORT")) { + try { + port = std::stoi(Env::get("SOCKET_RUNTIME_CONDUIT_PORT")); + } catch (...) {} + } + + uv_ip4_addr(ip.c_str(), port, &this->addr); uv_tcp_init(loop, &this->socket); - uv_ip4_addr("0.0.0.0", this->port.load(), &addr); - uv_tcp_bind(&this->socket, (const struct sockaddr *)&this->addr, 0); + uv_tcp_bind( + &this->socket, + reinterpret_cast<const struct sockaddr*>(&this->addr), + 0 + ); - this->socket.data = (void*)this; struct sockaddr_in sockname; int namelen = sizeof(sockname); - uv_tcp_getsockname(&this->socket, (struct sockaddr *)&sockname, &namelen); + uv_tcp_getsockname( + &this->socket, + reinterpret_cast<struct sockaddr *>(&sockname), + reinterpret_cast<int*>(&namelen) + ); + + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(&this->socket), + this + ); this->port = ntohs(sockname.sin_port); - this->core->dispatchEventLoop([=, this]() mutable { - int r = uv_listen((uv_stream_t*)&this->socket, 128, [](uv_stream_t *stream, int status) { - if (status < 0) { - // debug("New connection error %s\n", uv_strerror(status)); - return; - } + const auto result = uv_listen(reinterpret_cast<uv_stream_t*>(&this->socket), 128, [](uv_stream_t* stream, int status) { + if (status < 0) { + // debug("New connection error %s\n", uv_strerror(status)); + return; + } - auto conduit = static_cast<CoreConduit*>(stream->data); - auto client = new CoreConduit::Client(conduit); - - client->isHandshakeDone = false; - client->frameBuffer = nullptr; - client->frameBufferSize = 0; - client->handle.data = client; - - uv_loop_t *loop = uv_handle_get_loop((uv_handle_t*)stream); - uv_tcp_init(loop, &client->handle); - - auto accepted = uv_accept(stream, (uv_stream_t*)&client->handle); - - if (accepted == 0) { - uv_read_start( - (uv_stream_t *)&client->handle, - [](uv_handle_t *handle, size_t size, uv_buf_t *buf) { - buf->base = new char[size]{0}; - buf->len = size; - }, - [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - if (nread > 0) { - auto handle = uv_handle_get_data((uv_handle_t*)stream); - auto client = (CoreConduit::Client*)(handle); - - if (!client->isHandshakeDone) { - client->conduit->handshake(client, buf->base); - } else { - client->conduit->processFrame(client, buf->base, nread); - } - } else if (nread < 0) { - if (nread != UV_EOF) { - // debug("Read error %s\n", uv_err_name(nread)); - } - uv_close((uv_handle_t *)stream, nullptr); - } + auto data = uv_handle_get_data(reinterpret_cast<uv_handle_t*>(stream)); + auto conduit = static_cast<CoreConduit*>(data); + auto client = new CoreConduit::Client(conduit); + auto loop = uv_handle_get_loop(reinterpret_cast<uv_handle_t*>(stream)); - if (buf->base) { - delete buf->base; - } - } - ); - return; - } else { - // debug("uv_accept error: %s\n", uv_strerror(accepted)); - // delete static_cast<SharedPointer<CoreConduit::Client>*>(client->handle.data); - } + uv_tcp_init( + loop, + &client->handle + ); - uv_close((uv_handle_t *)&client->handle, nullptr); - }); + const auto accepted = uv_accept( + stream, + reinterpret_cast<uv_stream_t*>(&client->handle) + ); - if (r) { - // debug("Listen error %s\n", uv_strerror(r)); + if (accepted != 0) { + return uv_close( + reinterpret_cast<uv_handle_t *>(&client->handle), + nullptr + ); } - if (callback != nullptr) { - msleep(256); - callback(); - } + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(&client->handle), + client + ); + + uv_read_start( + reinterpret_cast<uv_stream_t*>(&client->handle), + [](uv_handle_t* handle, size_t size, uv_buf_t* buf) { + buf->base = new char[size]{0}; + buf->len = size; + }, + [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + auto data = uv_handle_get_data(reinterpret_cast<uv_handle_t*>(stream)); + auto client = static_cast<CoreConduit::Client*>(data); + + if (client && !client->isClosing && nread > 0) { + if (client->isHandshakeDone) { + client->conduit->processFrame(client, buf->base, nread); + } else { + client->conduit->handshake(client, buf->base); + } + } else if (nread < 0) { + if (nread != UV_EOF) { + // debug("Read error %s\n", uv_err_name(nread)); + } + + if (!client->isClosing && !client->isClosed) { + client->close([client]() { + if (client->isClosed) { + delete client; + } + }); + } + } + + if (buf->base) { + delete [] buf->base; + } + } + ); }); + + if (result) { + debug("CoreConduit: Listen error %s\n", uv_strerror(result)); + } + + this->isStarting = false; + + if (callback != nullptr) { + this->core->dispatchEventLoop(callback); + } } void CoreConduit::stop () { @@ -463,37 +678,72 @@ namespace SSC { return; } - this->core->dispatchEventLoop([this]() mutable { - if (!uv_is_closing((uv_handle_t*)&this->socket)) { - uv_close((uv_handle_t*)&this->socket, [](uv_handle_t* handle) { - auto conduit = static_cast<CoreConduit*>(handle->data); - }); - } + this->core->dispatchEventLoop([this]() { + Lock lock(this->mutex); + auto handle = reinterpret_cast<uv_handle_t*>(&this->socket); + const auto closeHandle = [=, this] () { + if (!uv_is_closing(handle)) { + auto shutdown = new uv_shutdown_t; + uv_handle_set_data( + reinterpret_cast<uv_handle_t*>(shutdown), + this + ); - for (auto& clientPair : this->clients) { - auto client = clientPair.second; + uv_shutdown( + shutdown, + reinterpret_cast<uv_stream_t*>(&this->socket), + [](uv_shutdown_t* shutdown, int status) { + auto data = uv_handle_get_data(reinterpret_cast<uv_handle_t*>(shutdown)); + auto conduit = reinterpret_cast<CoreConduit*>(data); + + delete shutdown; + + conduit->core->dispatchEventLoop([=]() { + uv_close( + reinterpret_cast<uv_handle_t*>(&conduit->socket), + nullptr + ); + }); + } + ); + } + }; - if (client && !uv_is_closing((uv_handle_t*)&client->handle)) { - uv_close((uv_handle_t*)&client->handle, [](uv_handle_t* handle) { - auto client = static_cast<CoreConduit::Client*>(handle->data); + if (this->clients.size() == 0) { + if (!uv_is_closing(handle)) { + closeHandle(); + } + } else { + for (const auto& entry : this->clients) { + auto client = entry.second; + + client->close([=, this] () { + Lock lock(this->mutex); - if (client->frameBuffer) { - free(client->frameBuffer); - client->frameBuffer = nullptr; - client->frameBufferSize = 0; + for (auto& entry : this->clients) { + if (entry.second && !entry.second->isClosed) { + return; + } } - client->handle.loop = nullptr; - delete client; + + for (auto& entry : this->clients) { + delete entry.second; + } + + this->clients.clear(); + closeHandle(); }); } } - - this->clients.clear(); }); } bool CoreConduit::isActive () { Lock lock(this->mutex); - return this->port > 0 && uv_is_active(reinterpret_cast<uv_handle_t*>(&this->socket)); + return ( + this->port > 0 && + uv_is_active(reinterpret_cast<uv_handle_t*>(&this->socket)) && + !uv_is_closing(reinterpret_cast<uv_handle_t*>(&this->socket)) + ); } } diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 8baf4ef2b1..5cddcb62a8 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -2,6 +2,7 @@ #define SOCKET_RUNTIME_CORE_CONDUIT_H #include "../module.hh" +#include "timers.hh" #include <iostream> namespace SSC { @@ -54,20 +55,26 @@ namespace SSC { class Client { public: + using CloseCallback = Function<void()>; + using ID = uint64_t; + // client state - uint64_t id; - uint64_t clientId; - Atomic<bool> isHandshakeDone; + ID id = 0; + ID clientId = 0; + Atomic<bool> isHandshakeDone = false; + Atomic<bool> isClosing = false; + Atomic<bool> isClosed = false; - // uv statae + // uv state uv_tcp_t handle; uv_buf_t buffer; + uv_stream_t* stream = nullptr; // websocket frame buffer state unsigned char *frameBuffer; size_t frameBufferSize; - CoreConduit* conduit; + CoreConduit* conduit = nullptr; Client (CoreConduit* conduit) : conduit(conduit), @@ -76,29 +83,24 @@ namespace SSC { isHandshakeDone(0) {} - ~Client () { - auto handle = reinterpret_cast<uv_handle_t*>(&this->handle); - - if (frameBuffer) { - delete [] frameBuffer; - } - - if (handle->loop != nullptr && !uv_is_closing(handle)) { - uv_close(handle, nullptr); - } - } + ~Client (); bool emit ( const CoreConduit::Options& options, SharedPointer<char[]> payload, - size_t length + size_t length, + int opcode = 2, + const Function<void()> callback = nullptr ); + + void close (const CloseCallback& callback = nullptr); }; // state std::map<uint64_t, Client*> clients; - Mutex mutex; + Atomic<bool> isStarting = false; Atomic<int> port = 0; + Mutex mutex; CoreConduit (Core* core) : CoreModule(core) {}; ~CoreConduit (); From 4eb02cf72c30068244548518c5586343b90d9ad9 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:43:29 +0200 Subject: [PATCH 1120/1178] refactor(api/dgram.js): improve conduit usage lifecycles --- api/dgram.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/api/dgram.js b/api/dgram.js index 63b3176441..965fe05460 100644 --- a/api/dgram.js +++ b/api/dgram.js @@ -166,7 +166,9 @@ async function startReading (socket, callback) { const opts = { route: 'udp.readStart' } - socket.conduit.send(opts, Buffer.from('')) + if (!socket.conduit.send(opts, Buffer.from(''))) { + console.warn('socket:dgram: Failed to send conduit payload') + } result = { data: true } } else { result = await ipc.send('udp.readStart', { @@ -720,13 +722,16 @@ export class Socket extends EventEmitter { */ [gc.finalizer] (options) { return { - args: [this.id, options], - async handle (id) { + args: [this.id, this.conduit, options], + async handle (id, conduit) { if (process.env.DEBUG) { console.warn('Closing Socket on garbage collection') } await ipc.request('udp.close', { id }, options) + if (conduit) { + conduit.close() + } } } } @@ -811,7 +816,7 @@ export class Socket extends EventEmitter { dc.channel('message').publish({ socket: this, buffer: message, info }) }) - this.conduit.onopen = () => { + const onopen = () => { startReading(this, (err) => { this.#resource.runInAsyncScope(() => { if (err) { @@ -824,6 +829,12 @@ export class Socket extends EventEmitter { }) } + if (!this.conduit.isActive) { + this.conduit.addEventListener('open', onopen, { once: true }) + } else { + onopen() + } + return } @@ -1100,6 +1111,10 @@ export class Socket extends EventEmitter { return } + if (this.conduit) { + this.conduit.close() + } + this.#resource.runInAsyncScope(() => { if (isFunction(cb)) { cb(null) From b702a5f2f59c21ea9a8515430f2ffff446a2ea8e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:43:52 +0200 Subject: [PATCH 1121/1178] refactor(api/diagnostics/runtime.js): allow selector syntax in 'query()' --- api/diagnostics/runtime.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/diagnostics/runtime.js b/api/diagnostics/runtime.js index f1d9ef0844..282afe2bfa 100644 --- a/api/diagnostics/runtime.js +++ b/api/diagnostics/runtime.js @@ -192,7 +192,7 @@ export class QueryDiagnostic { * Queries runtime diagnostics. * @return {Promise<QueryDiagnostic>} */ -export async function query () { +export async function query (type) { const result = await ipc.request('diagnostics.query') if (result.err) { @@ -227,6 +227,15 @@ export async function query () { } } + if (typeof type === 'string') { + return type + .trim() + .split(/\.|\[|\]/g) + .map((key) => key.trim()) + .filter((key) => key.length > 0) + .reduce((q, k) => q ? q[k] : null, query) + } + return query } From 54ea7e312d051e7b09c434d052dac303093d6338 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 14:44:34 +0200 Subject: [PATCH 1122/1178] refactor(api/internal/conduit.js): improve conduit lifecycles and reconnect logic --- api/internal/conduit.js | 120 +++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index aa6634d764..03e7b5a41d 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -30,37 +30,40 @@ export const DEFAULT_MAX_RECONNECT_TIMEOUT = 256 /** * A pool of known `Conduit` instances. - * @type {Set<WeakRef<Conduit>>} + * @type {Set<Conduit>} */ export const pool = new Set() // reconnect when application resumes hooks.onApplicationResume(() => { isApplicationPaused = false - const refs = Array.from(pool) - for (const ref of refs) { + for (const conduit of pool) { // @ts-ignore - const conduit = /** @type {WeakRef<Conduit>} */ (ref).deref() if (conduit?.shouldReconnect) { + // @ts-ignore conduit.reconnect() } else { - pool.delete(ref) + pool.delete(conduit) } } }) hooks.onApplicationPause(() => { isApplicationPaused = true - const refs = Array.from(pool) - for (const ref of refs) { - // @ts-ignore - const conduit = /** @type {WeakRef<Conduit>} */ (ref).deref() + for (const conduit of pool) { if (conduit) { + // @ts-ignore conduit.isConnecting = false + // @ts-ignore conduit.isActive = false - if (conduit.socket) { - conduit.socket.close() + // @ts-ignore + if (conduit.socket && conduit.socket.readyState == WebSocket.OPEN) { + // @ts-ignore + conduit.socket?.close() } + + // @ts-ignore + conduit.socket = null } } }) @@ -157,12 +160,6 @@ export class Conduit extends EventTarget { */ id = null - /** - * @private - * @type {number} - */ - #loop = 0 - /** * @private * @type {function(MessageEvent)} @@ -186,11 +183,6 @@ export class Conduit extends EventTarget { */ #onopen = null - /** - * @type {WeakRef<Conduit>} - */ - #ref = null - /** * Creates an instance of Conduit. * @@ -206,23 +198,8 @@ export class Conduit extends EventTarget { this.port = this.constructor.port this.connect() - const reconnectState = { - // TODO(@jwerle): eventually consume from 'options' when it exists - retries: DEFALUT_MAX_RECONNECT_RETRIES - } - - this.#loop = setInterval(async () => { - if (!this.isActive && !this.isConnecting && this.shouldReconnect) { - await this.reconnect({ - retries: --reconnectState.retries - }) - } else { - reconnectState.retries = DEFALUT_MAX_RECONNECT_RETRIES - } - }, 256) - - this.#ref = new WeakRef(this) - pool.add(this.#ref) + pool.add(this) + gc.ref(this) } /** @@ -351,6 +328,7 @@ export class Conduit extends EventTarget { this.socket = new WebSocket(this.url) this.socket.binaryType = 'arraybuffer' this.socket.onerror = (e) => { + this.socket = null this.isActive = false this.isConnecting = false this.dispatchEvent(new ErrorEvent('error', e)) @@ -366,10 +344,11 @@ export class Conduit extends EventTarget { } this.socket.onclose = (e) => { - this.isActive = false + this.socket = null this.isConnecting = false + this.isActive = false this.dispatchEvent(new CloseEvent('close', e)) - if (this.shouldReconnect) { + if (this.shouldReconnect && !isApplicationPaused) { this.reconnect() } } @@ -410,16 +389,22 @@ export class Conduit extends EventTarget { const retries = options?.retries ?? DEFALUT_MAX_RECONNECT_RETRIES const timeout = options?.timeout ?? DEFAULT_MAX_RECONNECT_TIMEOUT - return await this.connect((err) => { - if (err) { - this.isActive = false - if (retries > 0) { - setTimeout(() => this.reconnect({ - retries: retries - 1, - timeout - }), timeout) - } - } + return await new Promise((resolve, reject) => { + queueMicrotask(() => { + const promise = this.connect((err) => { + if (err) { + this.isActive = false + if (retries > 0) { + setTimeout(() => this.reconnect({ + retries: retries - 1, + timeout + }), timeout) + } + } + }) + + return promise.then(resolve, reject) + }) }) } @@ -544,11 +529,23 @@ export class Conduit extends EventTarget { * Sends a message with the specified options and payload over the * WebSocket connection. * @param {object} options - The options to send. - * @param {Uint8Array} payload - The payload to send. + * @param {Uint8Array=} [payload] - The payload to send. * @return {boolean} */ - send (options, payload) { - if (this.isActive) { + send (options, payload = null) { + if (isApplicationPaused || !this.isActive) { + return false + } + + if (!payload) { + payload = new Uint8Array(0) + } + + if ( + this.socket !== null && + this.socket instanceof WebSocket && + this.socket.readyState === WebSocket.OPEN + ) { this.socket.send(this.encodeMessage(options, payload)) return true } @@ -562,17 +559,12 @@ export class Conduit extends EventTarget { close () { this.shouldReconnect = false - if (this.#loop) { - clearInterval(this.#loop) - this.#loop = 0 - } - if (this.socket) { this.socket.close() this.socket = null } - pool.delete(this.#ref) + pool.delete(this) } /** @@ -582,9 +574,11 @@ export class Conduit extends EventTarget { */ [gc.finalizer] () { return { - args: [this.#ref], - handle (ref) { - pool.delete(ref) + args: [this.socket], + handle (socket) { + if (socket?.readyState === WebSocket.OPEN) { + socket.close() + } } } } From 38e58d128f6e57327dc0c914417bfb0f409481b0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:17:15 +0200 Subject: [PATCH 1123/1178] refactor(core): improve loop lifecycles --- src/core/core.cc | 192 +++++++++++++++++++++++++++++++---------------- src/core/core.hh | 10 ++- 2 files changed, 138 insertions(+), 64 deletions(-) diff --git a/src/core/core.cc b/src/core/core.cc index 7b37815dd0..1b1a4b48da 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -2,6 +2,14 @@ #include "modules/fs.hh" namespace SSC { +#if SOCKET_RUNTIME_PLATFORM_LINUX + struct UVSource { + GSource base; // should ALWAYS be first member + gpointer tag; + Core *core; + }; +#endif + Post Core::getPost (uint64_t id) { if (this->posts.find(id) == this->posts.end()) { return Post{}; @@ -11,19 +19,19 @@ namespace SSC { } void Core::shutdown () { - if (this->shuttingDown) { + if (this->isShuttingDown || this->isPaused) { return; } + this->isShuttingDown = true; this->pause(); - this->shuttingDown = true; #if !SOCKET_RUNTIME_PLATFORM_IOS this->childProcess.shutdown(); #endif this->stopEventLoop(); - this->shuttingDown = false; + this->isShuttingDown = false; } void Core::resume () { @@ -31,6 +39,7 @@ namespace SSC { return; } + this->isPaused = false; this->runEventLoop(); if (this->options.features.useUDP) { @@ -48,8 +57,6 @@ namespace SSC { if (options.features.useNotifications) { this->notifications.start(); } - - this->isPaused = false; } void Core::pause () { @@ -57,6 +64,8 @@ namespace SSC { return; } + this->isPaused = true; + if (this->options.features.useUDP) { this->udp.pauseAllSockets(); } @@ -73,8 +82,26 @@ namespace SSC { this->notifications.stop(); } - this->isPaused = true; + #if !SOCKET_RUNTIME_PLATFORM_ANDROID this->pauseEventLoop(); + #endif + } + + void Core::stop () { + Lock lock(this->mutex); + this->stopEventLoop(); + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (this->gsource) { + const auto id = g_source_get_id(this->gsource); + if (id > 0) { + g_source_remove(id); + } + + g_object_unref(this->gsource); + this->gsource = nullptr; + this->didInitGSource = false; + } + #endif } bool Core::hasPost (uint64_t id) { @@ -185,13 +212,7 @@ namespace SSC { } #if SOCKET_RUNTIME_PLATFORM_LINUX - struct UVSource { - GSource base; // should ALWAYS be first member - gpointer tag; - Core *core; - }; - - // @see https://api.gtkd.org/glib.c.types.GSourceFuncs.html + // @see https://api.gtkd.org/glib.c.types.GSourceFuncs.html static GSourceFuncs loopSourceFunctions = { .prepare = [](GSource *source, gint *timeout) -> gboolean { auto core = reinterpret_cast<UVSource *>(source)->core; @@ -270,64 +291,96 @@ namespace SSC { }); #if SOCKET_RUNTIME_PLATFORM_LINUX - if (!this->options.dedicatedLoopThread) { - GSource *source = g_source_new(&loopSourceFunctions, sizeof(UVSource)); - UVSource *uvSource = (UVSource *) source; - uvSource->core = this; - uvSource->tag = g_source_add_unix_fd( - source, - uv_backend_fd(&eventLoop), + if (!this->options.dedicatedLoopThread && !this->didInitGSource) { + if (this->gsource) { + const auto id = g_source_get_id(this->gsource); + if (id > 0) { + g_source_remove(id); + } + + g_object_unref(this->gsource); + this->gsource = nullptr; + } + + this->gsource = g_source_new(&loopSourceFunctions, sizeof(UVSource)); + + UVSource *uvsource = reinterpret_cast<UVSource*>(gsource); + uvsource->core = this; + uvsource->tag = g_source_add_unix_fd( + this->gsource, + uv_backend_fd(&this->eventLoop), (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR) ); - g_source_set_priority(source, G_PRIORITY_HIGH); - g_source_attach(source, nullptr); + g_source_set_priority(this->gsource, G_PRIORITY_HIGH); + g_source_attach(this->gsource, nullptr); + this->didInitGSource = true; } #endif } uv_loop_t* Core::getEventLoop () { - initEventLoop(); - return &eventLoop; + this->initEventLoop(); + return &this->eventLoop; } int Core::getEventLoopTimeout () { - auto loop = getEventLoop(); + auto loop = this->getEventLoop(); uv_update_time(loop); return uv_backend_timeout(loop); } bool Core::isLoopAlive () { - return uv_loop_alive(getEventLoop()); + return uv_loop_alive(this->getEventLoop()); } void Core::pauseEventLoop() { // wait for drain of event loop dispatch queue while (true) { Lock lock(this->mutex); - if (eventLoopDispatchQueue.size() == 0) { + if (this->eventLoopDispatchQueue.size() == 0) { break; } } - isLoopRunning = false; + this->isLoopRunning = false; + do { + Lock lock(this->mutex); + uv_stop(&this->eventLoop); + } while (0); - uv_stop(&eventLoop); + #if !SOCKET_RUNTIME_PLATFORM_APPLE + #if SOCKET_RUNTIME_PLATFORM_LINUX + if (this->options.dedicatedLoopThread) { + #endif + if (this->eventLoopThread != nullptr) { + if (this->isPollingEventLoop && eventLoopThread->joinable()) { + this->eventLoopThread->join(); + } + + delete this->eventLoopThread; + this->eventLoopThread = nullptr; + } + #if SOCKET_RUNTIME_PLATFORM_LINUX + } + #endif + #endif } void Core::stopEventLoop() { - if (isLoopRunning) { + if (this->isLoopRunning) { return; } - isLoopRunning = false; + this->isLoopRunning = false; + Lock lock(this->mutex); uv_stop(&eventLoop); #if !SOCKET_RUNTIME_PLATFORM_APPLE #if SOCKET_RUNTIME_PLATFORM_LINUX if (this->options.dedicatedLoopThread) { #endif if (eventLoopThread != nullptr) { - if (eventLoopThread->joinable()) { + if (this->isPollingEventLoop && eventLoopThread->joinable()) { eventLoopThread->join(); } @@ -344,76 +397,89 @@ namespace SSC { void Core::sleepEventLoop (int64_t ms) { if (ms > 0) { - auto timeout = getEventLoopTimeout(); + auto timeout = this->getEventLoopTimeout(); ms = timeout > ms ? timeout : ms; msleep(ms); } } void Core::sleepEventLoop () { - sleepEventLoop(getEventLoopTimeout()); + this->sleepEventLoop(this->getEventLoopTimeout()); } void Core::signalDispatchEventLoop () { - initEventLoop(); - runEventLoop(); Lock lock(this->mutex); - uv_async_send(&eventLoopAsync); + this->initEventLoop(); + this->runEventLoop(); + uv_async_send(&this->eventLoopAsync); } void Core::dispatchEventLoop (EventLoopDispatchCallback callback) { { Lock lock(this->mutex); - eventLoopDispatchQueue.push(callback); + this->eventLoopDispatchQueue.push(callback); } - signalDispatchEventLoop(); + this->signalDispatchEventLoop(); } - void pollEventLoop (Core *core) { + static void pollEventLoop (Core *core) { + core->isPollingEventLoop = true; auto loop = core->getEventLoop(); while (core->isLoopRunning) { + core->sleepEventLoop(EVENT_LOOP_POLL_TIMEOUT); + do { - while (uv_run(loop, UV_RUN_DEFAULT) != 0); + uv_run(loop, UV_RUN_DEFAULT); } while (core->isLoopRunning && core->isLoopAlive()); } + core->isPollingEventLoop = false; core->isLoopRunning = false; } void Core::runEventLoop () { - if (isLoopRunning) { + if ( + this->isShuttingDown || + this->isLoopRunning || + this->isPaused + ) { return; } - isLoopRunning = true; + this->isLoopRunning = true; - initEventLoop(); - dispatchEventLoop([=, this]() { - initTimers(); - startTimers(); + this->initEventLoop(); + this->dispatchEventLoop([=, this]() { + this->initTimers(); + this->startTimers(); }); #if SOCKET_RUNTIME_PLATFORM_APPLE Lock lock(this->mutex); - dispatch_async(eventLoopQueue, ^{ pollEventLoop(this); }); + dispatch_async(this->eventLoopQueue, ^{ + pollEventLoop(this); + }); #else #if SOCKET_RUNTIME_PLATFORM_LINUX if (this->options.dedicatedLoopThread) { #endif Lock lock(this->mutex); // clean up old thread if still running - if (eventLoopThread != nullptr) { - if (eventLoopThread->joinable()) { - eventLoopThread->join(); + if (this->eventLoopThread != nullptr) { + if (!this->isPollingEventLoop && this->eventLoopThread->joinable()) { + this->eventLoopThread->join(); } - delete eventLoopThread; - eventLoopThread = nullptr; + delete this->eventLoopThread; + this->eventLoopThread = nullptr; } - eventLoopThread = new std::thread(&pollEventLoop, this); + this->eventLoopThread = new std::thread( + &pollEventLoop, + this + ); #if SOCKET_RUNTIME_PLATFORM_LINUX } #endif @@ -515,15 +581,15 @@ namespace SSC { }; void Core::initTimers () { - if (didTimersInit) { + if (this->didTimersInit) { return; } Lock lock(this->mutex); - auto loop = getEventLoop(); + auto loop = this->getEventLoop(); - Vector<Timer *> timersToInit = { + Vector<Timer*> timersToInit = { &releaseStrongReferenceDescriptors, &releaseStrongReferenceSharedPointerBuffers }; @@ -533,13 +599,13 @@ namespace SSC { timer->handle.data = (void *) this; } - didTimersInit = true; + this->didTimersInit = true; } void Core::startTimers () { Lock lock(this->mutex); - Vector<Timer *> timersToStart = { + Vector<Timer*> timersToStart = { &releaseStrongReferenceDescriptors, &releaseStrongReferenceSharedPointerBuffers }; @@ -561,17 +627,17 @@ namespace SSC { } } - didTimersStart = true; + this->didTimersStart = true; } void Core::stopTimers () { - if (didTimersStart == false) { + if (this->didTimersStart == false) { return; } Lock lock(this->mutex); - Vector<Timer *> timersToStop = { + Vector<Timer*> timersToStop = { &releaseStrongReferenceDescriptors, &releaseStrongReferenceSharedPointerBuffers }; @@ -582,7 +648,7 @@ namespace SSC { } } - didTimersStart = false; + this->didTimersStart = false; } const CoreTimers::ID Core::setTimeout ( diff --git a/src/core/core.hh b/src/core/core.hh index e7f5c9ea2a..f33ebfcb4a 100644 --- a/src/core/core.hh +++ b/src/core/core.hh @@ -133,8 +133,10 @@ namespace SSC { Atomic<bool> didLoopInit = false; Atomic<bool> didTimersInit = false; Atomic<bool> didTimersStart = false; + + Atomic<bool> isPollingEventLoop = false; + Atomic<bool> isShuttingDown = false; Atomic<bool> isLoopRunning = false; - Atomic<bool> shuttingDown = false; Atomic<bool> isPaused = true; uv_loop_t eventLoop; @@ -156,6 +158,11 @@ namespace SSC { Thread *eventLoopThread = nullptr; #endif + #if SOCKET_RUNTIME_PLATFORM_LINUX + Atomic<bool> didInitGSource = false; + GSource* gsource = nullptr; + #endif + Core (const Options& options) : options(options), #if !SOCKET_RUNTIME_PLATFORM_IOS @@ -192,6 +199,7 @@ namespace SSC { void shutdown (); void resume (); void pause (); + void stop (); int logSeq{0}; From 034860fbacfc5eaea634128624d974b1761b7700 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:18:06 +0200 Subject: [PATCH 1124/1178] refactor(cli): improve signal handler --- src/cli/cli.cc | 80 ++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 6d78e30d99..8d70b82b7b 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -755,65 +755,69 @@ Vector<Path> handleBuildPhaseForCopyMappedFiles ( } void signalHandler (int signum) { - #if !SOCKET_RUNTIME_PLATFORM_WINDOWS - if (signum == SIGUSR1) { - #if SOCKET_RUNTIME_PLATFORM_APPLE - checkLogStore = true; - #endif - return; - } - #endif - - #if !SOCKET_RUNTIME_PLATFORM_WINDOWS - if (appPid > 0) { - kill(appPid, signum); - } +#if !SOCKET_RUNTIME_PLATFORM_WINDOWS + if (signum == SIGUSR1) { + #if SOCKET_RUNTIME_PLATFORM_APPLE + checkLogStore = true; #endif + return; + } +#endif - if (appProcess != nullptr) { - appProcess->kill(); - appProcess = nullptr; +#if !SOCKET_RUNTIME_PLATFORM_WINDOWS + if (appPid > 0) { + kill(appPid, signum); } +#endif + + if (signum == SIGINT || signum == SIGTERM) { + if (appProcess != nullptr) { + appProcess->kill(signum); + appProcess = nullptr; + } - appPid = 0; + appPid = 0; - if (sourcesWatcherSupportThread != nullptr) { - if (sourcesWatcherSupportThread->joinable()) { - sourcesWatcherSupportThread->join(); + if (sourcesWatcherSupportThread != nullptr) { + if (sourcesWatcherSupportThread->joinable()) { + sourcesWatcherSupportThread->join(); + } + delete sourcesWatcherSupportThread; + sourcesWatcherSupportThread = nullptr; } - delete sourcesWatcherSupportThread; - sourcesWatcherSupportThread = nullptr; - } - if (buildAfterScriptProcess != nullptr) { - buildAfterScriptProcess->kill(); - buildAfterScriptProcess->wait(); - delete buildAfterScriptProcess; - buildAfterScriptProcess = nullptr; - } + if (buildAfterScriptProcess != nullptr) { + buildAfterScriptProcess->kill(); + buildAfterScriptProcess->wait(); + delete buildAfterScriptProcess; + buildAfterScriptProcess = nullptr; + } - if (appStatus == -1) { - appStatus = signum; - log("App result: " + std::to_string(signum)); - } + if (appStatus == -1) { + appStatus = signum; + log("App result: " + std::to_string(signum)); + } - if (signum == SIGTERM || signum == SIGINT) { - signal(signum, SIG_DFL); - raise(signum); - } + if (signum == SIGTERM || signum == SIGINT) { + signal(signum, SIG_DFL); + raise(signum); + } #if SOCKET_RUNTIME_PLATFORM_LINUX + msleep(500); if (gtk_main_level() > 0) { g_main_context_invoke( nullptr, +[](gpointer userData) -> gboolean { msleep(16); gtk_main_quit(); + return true; }, nullptr ); } #endif + } } void checkIosSimulatorDeviceAvailability (const String& device) { @@ -3464,7 +3468,7 @@ int main (const int argc, const char* argv[]) { if (androidIcon.size() > 0) { settings["android_application_icon_config"] = ( String(" android:roundIcon=\"@mipmap/ic_launcher_round\"\n") + - String(" android:icon=\"@mipmap/ic_launcher\"\n") + String(" android:icon=\"@mipmap/ic_launcher\"\n") ); fs::copy(targetPath / androidIcon, res / "mipmap" / "icon.png", fs::copy_options::overwrite_existing); From 4be14271b3c50805efda51aaf7ee167ac159d3bf Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:18:31 +0200 Subject: [PATCH 1125/1178] refactor(app/app.kt): handle default image icon --- src/app/app.cc | 2 +- src/app/app.kt | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/app.cc b/src/app/app.cc index afe4256504..bc2f72acfa 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -846,7 +846,7 @@ namespace SSC { #endif if (shouldExit) { - this->core->shuttingDown = true; + this->core->isShuttingDown = true; } return shouldExit ? 1 : 0; diff --git a/src/app/app.kt b/src/app/app.kt index 2c39caa6fc..9d83aac292 100644 --- a/src/app/app.kt +++ b/src/app/app.kt @@ -401,6 +401,13 @@ open class AppActivity : WindowManagerActivity() { if (imageURL.length > 0) { val icon = Icon.createWithContentUri(imageURL) builder.setLargeIcon(icon) + } else { + val icon = IconCompat.createWithResource( + this, + R.mipmap.ic_launcher + ) + + builder.setSmallIcon(icon) } if (category.length > 0) { From 51d19087e689940a8dae54ff4846b0a9683e8c14 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:18:52 +0200 Subject: [PATCH 1126/1178] refactor(ipc): use better core predicate names --- src/ipc/bridge.cc | 10 +++++----- src/ipc/router.cc | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ipc/bridge.cc b/src/ipc/bridge.cc index b64145e473..0a538a47a3 100644 --- a/src/ipc/bridge.cc +++ b/src/ipc/bridge.cc @@ -291,7 +291,7 @@ export * from '{{url}}' } bool Bridge::evaluateJavaScript (const String& source) { - if (this->core->shuttingDown) { + if (this->core->isShuttingDown) { return false; } @@ -304,7 +304,7 @@ export * from '{{url}}' } bool Bridge::dispatch (const DispatchCallback& callback) { - if (!this->core || this->core->shuttingDown) { + if (!this->core || this->core->isShuttingDown) { return false; } @@ -317,7 +317,7 @@ export * from '{{url}}' } bool Bridge::navigate (const String& url) { - if (!this->core || this->core->shuttingDown) { + if (!this->core || this->core->isShuttingDown) { return false; } @@ -351,7 +351,7 @@ export * from '{{url}}' const String& data, const Post& post ) { - if (this->core->shuttingDown) { + if (this->core->isShuttingDown) { return false; } @@ -375,7 +375,7 @@ export * from '{{url}}' } bool Bridge::emit (const String& name, const String& data) { - if (this->core->shuttingDown) { + if (this->core->isShuttingDown) { return false; } diff --git a/src/ipc/router.cc b/src/ipc/router.cc index 340f27e557..e158fe352b 100644 --- a/src/ipc/router.cc +++ b/src/ipc/router.cc @@ -94,7 +94,7 @@ namespace SSC::IPC { size_t size, const ResultCallback& callback ) { - if (this->bridge->core->shuttingDown) { + if (this->bridge->core->isShuttingDown) { return false; } @@ -108,7 +108,7 @@ namespace SSC::IPC { size_t size, const ResultCallback& callback ) { - if (this->bridge->core->shuttingDown) { + if (this->bridge->core->isShuttingDown) { return false; } From 50b547c1d95166269c19cff2f8e7e5620566378e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:19:14 +0200 Subject: [PATCH 1127/1178] fix(core/modules/conduit): set default frame buffer values --- src/core/modules/conduit.cc | 2 +- src/core/modules/conduit.hh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/modules/conduit.cc b/src/core/modules/conduit.cc index c46836062d..aa5e289c70 100644 --- a/src/core/modules/conduit.cc +++ b/src/core/modules/conduit.cc @@ -201,7 +201,7 @@ namespace SSC { this->clients.emplace(socketId, client); - std::cout << "added client " << this->clients.size() << std::endl; + // std::cout << "added client " << this->clients.size() << std::endl; } while (0); // debug("Received key: %s", keyHeader.c_str()); diff --git a/src/core/modules/conduit.hh b/src/core/modules/conduit.hh index 5cddcb62a8..5754f87c8a 100644 --- a/src/core/modules/conduit.hh +++ b/src/core/modules/conduit.hh @@ -71,8 +71,8 @@ namespace SSC { uv_stream_t* stream = nullptr; // websocket frame buffer state - unsigned char *frameBuffer; - size_t frameBufferSize; + unsigned char *frameBuffer = nullptr; + size_t frameBufferSize = 0; CoreConduit* conduit = nullptr; From 5e062a372d36c9b6c0da5d003dcb7629b29f5cab Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:19:35 +0200 Subject: [PATCH 1128/1178] chore(package.json): introduce 'clean' script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4892f63ee0..e10c83216b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "type": "module", "scripts": { + "clean": "./bin/clean.sh", "gen": "npm run gen:docs && npm run gen:tsc", "gen:docs": "node ./bin/generate-docs.js", "gen:tsc": "./bin/generate-typescript-typings.sh", From 60cc12b429d9e285f6388353f23a2ec65dcf9a2f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sat, 17 Aug 2024 20:19:39 +0200 Subject: [PATCH 1129/1178] chore(): clean up --- src/platform/android/looper.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/android/looper.cc b/src/platform/android/looper.cc index 838577e3b1..18429e216c 100644 --- a/src/platform/android/looper.cc +++ b/src/platform/android/looper.cc @@ -47,7 +47,6 @@ namespace SSC::Android { if (dispatch->callback != nullptr) { dispatch->callback(); } - } delete dispatch; From 7247af217a728248353bfe2d561c871d607e8cc3 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 18 Aug 2024 21:43:30 +0200 Subject: [PATCH 1130/1178] refactor(app): use 'https:' window navigation on android --- src/app/app.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/app.cc b/src/app/app.cc index bc2f72acfa..6d635ddc97 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -1266,7 +1266,7 @@ extern "C" { auto serviceWorkerWindow = app->windowManager.createWindow(serviceWorkerWindowOptions); app->serviceWorkerContainer.init(&serviceWorkerWindow->bridge); serviceWorkerWindow->navigate( - "socket://" + app->userConfig["meta_bundle_identifier"] + "/socket/service-worker/index.html" + "https://" + app->userConfig["meta_bundle_identifier"] + "/socket/service-worker/index.html" ); app->core->setTimeout(256, [=](){ @@ -1291,11 +1291,11 @@ extern "C" { defaultWindow->navigate(host + ":" + std::to_string(port)); } else if (app->userConfig["webview_root"].size() != 0) { defaultWindow->navigate( - "socket://" + app->userConfig["meta_bundle_identifier"] + app->userConfig["webview_root"] + "https://" + app->userConfig["meta_bundle_identifier"] + app->userConfig["webview_root"] ); } else { defaultWindow->navigate( - "socket://" + app->userConfig["meta_bundle_identifier"] + "/index.html" + "https://" + app->userConfig["meta_bundle_identifier"] + "/index.html" ); } }); From ff3741ecace6f6002e3a14b24ab8339ac70204d1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Sun, 18 Aug 2024 21:43:55 +0200 Subject: [PATCH 1131/1178] refactor(ipc/scheme_handlers): handle 'runtime-xhr-seq' header --- src/ipc/scheme_handlers.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ipc/scheme_handlers.kt b/src/ipc/scheme_handlers.kt index a1c533ee57..e4362a9d3e 100644 --- a/src/ipc/scheme_handlers.kt +++ b/src/ipc/scheme_handlers.kt @@ -21,7 +21,8 @@ open class SchemeHandlers (val bridge: Bridge) { val response = Response(this) val body: ByteArray? by lazy { try { - val seq = this.request.url.getQueryParameter("seq") + val seq = this.getHeader("runtime-xhr-seq") + ?: this.request.url.getQueryParameter("seq") if (seq != null && this.bridge.buffers.contains(seq)) { val buffer = this.bridge.buffers[seq] @@ -73,6 +74,10 @@ open class SchemeHandlers (val bridge: Bridge) { return headers } + fun getHeader (name: String): String? { + return request.requestHeaders.get(name) + } + fun getUrl (): String { return this.request.url.toString().replace("https:", "socket:") } From 644c8297607a92b6a2e3c74fac26aa58546027b4 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:11:14 +0200 Subject: [PATCH 1132/1178] refactor(api/vm.js): use pathname instead of URL for window --- api/vm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/vm.js b/api/vm.js index 0178c42ef3..92483b37c5 100644 --- a/api/vm.js +++ b/api/vm.js @@ -46,7 +46,7 @@ const kContextTag = Symbol('socket.vm.Context') const VM_WINDOW_INDEX = 47 const VM_WINDOW_TITLE = 'socket:vm' -const VM_WINDOW_PATH = `${globalThis.origin}/socket/vm/index.html` +const VM_WINDOW_PATH = '/socket/vm/index.html' let contextWorker = null let contextWindow = null From fb62083ac2fc97de011453caf27d865770ef8932 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:11:29 +0200 Subject: [PATCH 1133/1178] chore(api/url/index.js): clean up jsdoc --- api/url/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/url/index.js b/api/url/index.js index dd1fa3fa70..53022f8c8f 100644 --- a/api/url/index.js +++ b/api/url/index.js @@ -34,6 +34,9 @@ URL.prototype[Symbol.for('socket.runtime.util.inspect.custom')] = function () { ].join('\n') } +/** + * @type {Set & { handlers: Set<string> }} + */ export const protocols = new Set([ 'socket:', 'node:', From f13e10eb7d451c11687516dc8139e460f20eb29f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:11:41 +0200 Subject: [PATCH 1134/1178] refactor(api/shared-worker/index.js): use pathname instead of URL for window --- api/shared-worker/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/shared-worker/index.js b/api/shared-worker/index.js index 6c869fea06..5683cf5d8e 100644 --- a/api/shared-worker/index.js +++ b/api/shared-worker/index.js @@ -9,7 +9,7 @@ let contextWindow = null export const SHARED_WORKER_WINDOW_INDEX = 46 export const SHARED_WORKER_WINDOW_TITLE = 'socket:shared-worker' -export const SHARED_WORKER_WINDOW_PATH = `${location.origin}/socket/shared-worker/index.html` +export const SHARED_WORKER_WINDOW_PATH = '/socket/shared-worker/index.html' export const channel = new BroadcastChannel('socket.runtime.sharedWorker') export const workers = new Map() From 80a9ec52f808594b53026aa1c85d8661189d91df Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:11:56 +0200 Subject: [PATCH 1135/1178] refactor(api/service-worker/worker.js): cache protocol data --- api/service-worker/worker.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 44c995da14..69be8cfc94 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -57,6 +57,8 @@ globals.register('ServiceWorker.stages', stages) globals.register('ServiceWorker.events', events) globals.register('ServiceWorker.module', module) +let protocolData = null + export function onReady () { globalThis.postMessage(SERVICE_WORKER_READY_TOKEN) } @@ -427,7 +429,9 @@ export async function onMessage (event) { }) events.add(fetchEvent) - if (url.protocol !== 'socket:') { + if (protocolData) { + fetchEvent.context.data = protocolData + } else if (url.protocol !== 'socket:' && url.protocol !== 'npm') { const result = await ipc.request('protocol.getData', { scheme: url.protocol.replace(':', '') }) @@ -438,6 +442,8 @@ export async function onMessage (event) { } catch { fetchEvent.context.data = result.data } + + protocolData = fetchEvent.context.data } } From 0de5f84f461a3ea0774195396f3dce900385f32c Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:12:08 +0200 Subject: [PATCH 1136/1178] fix(api/location.js): handle 'null' origins --- api/location.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/api/location.js b/api/location.js index cd0e9faa3a..180fecd224 100644 --- a/api/location.js +++ b/api/location.js @@ -1,8 +1,23 @@ export class Location { get url () { - return globalThis.location.href.startsWith('blob:') - ? new URL(globalThis.RUNTIME_WORKER_LOCATION || globalThis.location.pathname) - : new URL(globalThis.location.href) + if (globalThis.location === this) { + return null + } + + if (globalThis.location.href.startsWith('blob:')) { + return new URL(globalThis.RUNTIME_WORKER_LOCATION || globalThis.location.pathname) + } + + if (globalThis.location.origin === 'null') { + return new URL( + globalThis.location.pathname + + globalThis.location.search + + globalThis.location.hash, + globalThis.__args?.config?.meta_bundle_identifier ?? 'null' + ) + } + + return new URL(globalThis.location.href) } get protocol () { From 6ff6df1331d3ba529ff2cfd7d019cb311b3cf9f7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:12:23 +0200 Subject: [PATCH 1137/1178] fix(api/commonjs/loader.js): fix ref cycle --- api/commonjs/loader.js | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 0282ec5519..05404da8ee 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -84,18 +84,23 @@ export class RequestStatus { * @param {RequestStatusOptions} [options] */ constructor (request, options = null) { + if (!options && request && !(request instanceof Request)) { + options = request + request = options.requesst + } + if (request && !(request instanceof Request)) { throw new TypeError( `Expecting 'request' to be a Request object. Received: ${request}` ) } + this.#headers = options?.headers ? Headers.from(options.headers) : this.#headers + this.#status = options?.status ? options.status : undefined + if (request) { this.request = request } - - this.#headers = options?.headers ? Headers.from(options.headers) : this.#headers - this.#status = options?.status ? options.status : undefined } /** @@ -110,7 +115,11 @@ export class RequestStatus { !this.#status && request?.loader?.cache?.status?.has?.(request?.id) ) { - this.#status = request.loader.cache.status.get(request.id)?.value ?? null + this.#status = ( + request.status?.value ?? + request.loader.cache.status.get(request.id)?.value ?? + null + ) } } @@ -330,10 +339,14 @@ export class Request { */ static from (json, options) { return new this(json.url, { - status: json.status && typeof json.status === 'object' - // @ts-ignore - ? RequestStatus.from(json.status) - : options?.status, + status: ( + json.status && + typeof json.status === 'object' && + !(json.status instanceof RequestStatus) + // @ts-ignore + ? RequestStatus.from(json.status) + : options?.status + ), ...options }) } @@ -366,7 +379,7 @@ export class Request { this.#loader = options?.loader ?? null this.#status = options?.status instanceof RequestStatus ? options.status - : new RequestStatus(this) + : new RequestStatus(options.status) this.#status.request = this } From d9dcd6b56c5ebd91369c12f6a6b57f6f2ad82eec Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:13:12 +0200 Subject: [PATCH 1138/1178] fix(api/ipc.js): allow POST/PUT/PATCH for android protocol handlers --- api/ipc.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 1868bf2451..544fb54605 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -50,12 +50,13 @@ import { parseJSON } from './util.js' +import { URL, protocols } from './url.js' import * as errors from './errors.js' import { Buffer } from './buffer.js' import { rand64 } from './crypto.js' import bookmarks from './fs/bookmarks.js' import serialize from './internal/serialize.js' -import { URL } from './url.js' +import location from './location.js' import gc from './gc.js' let nextSeq = 1 @@ -79,26 +80,35 @@ function initializeXHRIntercept () { this.readyState = globalThis.XMLHttpRequest.OPENED } catch (_) {} this.method = method - this.url = new URL(url, globalThis.location.origin) + this.url = new URL(url, location.origin) this.seq = this.url.searchParams.get('seq') return open.call(this, method, url, ...args) }, async send (body) { - const { method, seq, url } = this + let { method, seq, url } = this - if (url?.protocol === 'ipc:') { + if ( + url?.protocol && ( + url.protocol === 'ipc:' || + protocols.handlers.has(url.protocol.slice(0, -1)) + ) + ) { if ( /put|post|patch/i.test(method) && - typeof body !== 'undefined' && - typeof seq !== 'undefined' + typeof body !== 'undefined' ) { if (typeof body === 'string') { body = encoder.encode(body) } if (/android/i.test(primordials.platform)) { + if (!seq) { + seq = 'R' + Math.random().toString().slice(2, 8) + 'X' + } + + this.setRequestHeader('runtime-xhr-seq', seq) await postMessage(`ipc://buffer.map?seq=${seq}`, body) if (!globalThis.window && globalThis.self) { await new Promise((resolve) => setTimeout(resolve, 200)) @@ -426,13 +436,13 @@ export class Headers extends globalThis.Headers { if (Array.isArray(input) && !Array.isArray(input[0])) { input = input.join('\n') } else if (typeof input?.entries === 'function') { - return new this(input.entries()) + return new this(Array.from(input.entries())) } else if (isPlainObject(input) || isArrayLike(input)) { return new this(input) } else if (typeof input?.getAllResponseHeaders === 'function') { input = input.getAllResponseHeaders() } else if (typeof input?.headers?.entries === 'function') { - return new this(input.headers.entries()) + return new this(Array.from(input.headers.entries())) } return new this(parseHeaders(String(input))) From 4aaf1dbe968561037f5c720a0ae0af51be5643c7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:13:57 +0200 Subject: [PATCH 1139/1178] chore(api/internal/conduit.js): clean up lint --- api/internal/conduit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/internal/conduit.js b/api/internal/conduit.js index 03e7b5a41d..f9351f88d0 100644 --- a/api/internal/conduit.js +++ b/api/internal/conduit.js @@ -50,14 +50,14 @@ hooks.onApplicationResume(() => { hooks.onApplicationPause(() => { isApplicationPaused = true - for (const conduit of pool) { + for (const conduit of pool) { if (conduit) { // @ts-ignore conduit.isConnecting = false // @ts-ignore conduit.isActive = false // @ts-ignore - if (conduit.socket && conduit.socket.readyState == WebSocket.OPEN) { + if (conduit.socket && conduit.socket.readyState === WebSocket.OPEN) { // @ts-ignore conduit.socket?.close() } From 157db5682f7a998bdfe39282bf5cd881cf534c3b Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 19 Aug 2024 13:14:16 +0200 Subject: [PATCH 1140/1178] chore(api): generate types + docs --- api/README.md | 58 +++++----- api/index.d.ts | 298 +++++++++++++++++++++++++------------------------ 2 files changed, 181 insertions(+), 175 deletions(-) diff --git a/api/README.md b/api/README.md index fbde1f2601..aecf253bb2 100644 --- a/api/README.md +++ b/api/README.md @@ -498,7 +498,7 @@ A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c import { createSocket } from 'socket:dgram' ``` -## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L660) +## [`createSocket(options, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L662) Creates a `Socket` instance. @@ -517,12 +517,12 @@ Creates a `Socket` instance. | :--- | :--- | :--- | | Not specified | Socket | | -## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L666) +## [`Socket` (extends `EventEmitter`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L668) New instances of dgram.Socket are created using dgram.createSocket(). The new keyword is not to be used to create dgram.Socket instances. -### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L747) +### [`bind(port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L752) External docs: https://nodejs.org/api/dgram.html#socketbindport-address-callback Listen for datagram messages on a named port and optional address @@ -539,7 +539,7 @@ Listen for datagram messages on a named port and optional address | address | string | | false | The address to bind to (0.0.0.0) | | callback | function | | false | With no parameters. Called when binding is complete. | -### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L862) +### [`connect(port, host, connectListener)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L873) External docs: https://nodejs.org/api/dgram.html#socketconnectport-address-callback Associates the dgram.Socket to a remote address and port. Every message sent @@ -559,7 +559,7 @@ Associates the dgram.Socket to a remote address and port. Every message sent | host | string | | true | Host the client should connect to. | | connectListener | function | | true | Common parameter of socket.connect() methods. Will be added as a listener for the 'connect' event once. | -### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L899) +### [`disconnect()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L910) External docs: https://nodejs.org/api/dgram.html#socketdisconnect A synchronous function that disassociates a connected dgram.Socket from @@ -567,7 +567,7 @@ A synchronous function that disassociates a connected dgram.Socket from disconnected socket will result in an ERR_SOCKET_DGRAM_NOT_CONNECTED exception. -### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L958) +### [`send(msg, offset, length, port, address, callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L969) External docs: https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback Broadcasts a datagram on the socket. For connectionless sockets, the @@ -618,7 +618,7 @@ Broadcasts a datagram on the socket. For connectionless sockets, the | address | string | | true | Destination host name or IP address. | | callback | Function | | true | Called when the message has been sent. | -### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1056) +### [`close(callback)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1067) External docs: https://nodejs.org/api/dgram.html#socketclosecallback Close the underlying socket and stop listening for data on it. If a @@ -630,7 +630,7 @@ Close the underlying socket and stop listening for data on it. If a | :--- | :--- | :---: | :---: | :--- | | callback | function | | true | Called when the connection is completed or on error. | -### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1128) +### [`address()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1143) External docs: https://nodejs.org/api/dgram.html#socketaddress Returns an object containing the address information for a socket. For @@ -646,7 +646,7 @@ Returns an object containing the address information for a socket. For | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1163) +### [`remoteAddress()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1178) External docs: https://nodejs.org/api/dgram.html#socketremoteaddress Returns an object containing the address, family, and port of the remote @@ -661,7 +661,7 @@ Returns an object containing the address, family, and port of the remote | socketInfo.port | string | The port of the socket | | socketInfo.family | string | The IP family of the socket | -### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1194) +### [`setRecvBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1209) External docs: https://nodejs.org/api/dgram.html#socketsetrecvbuffersizesize Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in @@ -672,7 +672,7 @@ Sets the SO_RCVBUF socket option. Sets the maximum socket receive buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new receive buffer | -### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1211) +### [`setSendBufferSize(size)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1226) External docs: https://nodejs.org/api/dgram.html#socketsetsendbuffersizesize Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in @@ -683,12 +683,12 @@ Sets the SO_SNDBUF socket option. Sets the maximum socket send buffer in | :--- | :--- | :---: | :---: | :--- | | size | number | | false | The size of the new send buffer | -### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1224) +### [`getRecvBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1239) External docs: https://nodejs.org/api/dgram.html#socketgetrecvbuffersize -### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1232) +### [`getSendBufferSize()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1247) External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize @@ -697,31 +697,31 @@ External docs: https://nodejs.org/api/dgram.html#socketgetsendbuffersize | :--- | :--- | :--- | | Not specified | number | the SO_SNDBUF socket send buffer size in bytes. | -### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1300) +### [`code()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1315) -## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1306) +## [`ERR_SOCKET_ALREADY_BOUND` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1321) Thrown when a socket is already bound. -## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1323) +## [`ERR_SOCKET_DGRAM_IS_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1338) Thrown when the socket is already connected. -## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1330) +## [`ERR_SOCKET_DGRAM_NOT_CONNECTED` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1345) Thrown when the socket is not connected. -## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1338) +## [`ERR_SOCKET_DGRAM_NOT_RUNNING` (extends `SocketError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1353) Thrown when the socket is not running (not bound or connected). -## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1345) +## [`ERR_SOCKET_BAD_TYPE` (extends `TypeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1360) Thrown when a bad socket type is used in an argument. -## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1355) +## [`ERR_SOCKET_BAD_PORT` (extends `RangeError`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/dgram.js#L1370) Thrown when a bad port is given. @@ -1710,17 +1710,17 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L270) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L280) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1026) +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1036) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1192) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1202) Emit event to be dispatched on `window` object. @@ -1731,7 +1731,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1251) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1261) Sends an async IPC command request with parameters. @@ -1747,27 +1747,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1702) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1712) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1734) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1744) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1783) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1793) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1785) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1795) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1990) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2000) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index b70a36af51..5bcf959953 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -402,6 +402,39 @@ declare module "socket:events" { export function once(emitter: any, name: any): Promise<any>; } +declare module "socket:url/urlpattern/urlpattern" { + export { me as URLPattern }; + var me: { + new (t: {}, r: any, n: any): { + "__#11@#i": any; + "__#11@#n": {}; + "__#11@#t": {}; + "__#11@#e": {}; + "__#11@#s": {}; + "__#11@#l": boolean; + test(t: {}, r: any): boolean; + exec(t: {}, r: any): { + inputs: any[] | {}[]; + }; + readonly protocol: any; + readonly username: any; + readonly password: any; + readonly hostname: any; + readonly port: any; + readonly pathname: any; + readonly search: any; + readonly hash: any; + readonly hasRegExpGroups: boolean; + }; + compareComponent(t: any, r: any, n: any): number; + }; +} + +declare module "socket:url/url/url" { + const _default: any; + export default _default; +} + declare module "socket:buffer" { export default Buffer; export const File: { @@ -565,58 +598,6 @@ declare module "socket:buffer" { function byteLength(string: any, encoding: any, ...args: any[]): any; } -declare module "socket:fs/bookmarks" { - /** - * A map of known absolute file paths to file IDs that - * have been granted access outside of the sandbox. - * XXX(@jwerle): this is currently only used on linux, but valaues may - * be added for all platforms, likely from a file system picker dialog. - * @type {Map<string, string>} - */ - export const temporary: Map<string, string>; - namespace _default { - export { temporary }; - } - export default _default; -} - -declare module "socket:internal/serialize" { - export default function serialize(value: any): any; -} - -declare module "socket:url/urlpattern/urlpattern" { - export { me as URLPattern }; - var me: { - new (t: {}, r: any, n: any): { - "__#11@#i": any; - "__#11@#n": {}; - "__#11@#t": {}; - "__#11@#e": {}; - "__#11@#s": {}; - "__#11@#l": boolean; - test(t: {}, r: any): boolean; - exec(t: {}, r: any): { - inputs: any[] | {}[]; - }; - readonly protocol: any; - readonly username: any; - readonly password: any; - readonly hostname: any; - readonly port: any; - readonly pathname: any; - readonly search: any; - readonly hash: any; - readonly hasRegExpGroups: boolean; - }; - compareComponent(t: any, r: any, n: any): number; - }; -} - -declare module "socket:url/url/url" { - const _default: any; - export default _default; -} - declare module "socket:querystring" { export function unescapeBuffer(s: any, decodeSpaces: any): any; export function unescape(s: any, decodeSpaces: any): any; @@ -656,7 +637,12 @@ declare module "socket:url/index" { export function resolve(from: any, to: any): any; export function format(input: any): any; export function fileURLToPath(url: any): any; - export const protocols: Set<string>; + /** + * @type {Set & { handlers: Set<string> }} + */ + export const protocols: Set<any> & { + handlers: Set<string>; + }; export default URL; export class URL { private constructor(); @@ -673,6 +659,43 @@ declare module "socket:url" { import URL from "socket:url/index"; } +declare module "socket:fs/bookmarks" { + /** + * A map of known absolute file paths to file IDs that + * have been granted access outside of the sandbox. + * XXX(@jwerle): this is currently only used on linux, but valaues may + * be added for all platforms, likely from a file system picker dialog. + * @type {Map<string, string>} + */ + export const temporary: Map<string, string>; + namespace _default { + export { temporary }; + } + export default _default; +} + +declare module "socket:internal/serialize" { + export default function serialize(value: any): any; +} + +declare module "socket:location" { + export class Location { + get url(): URL; + get protocol(): string; + get host(): string; + get hostname(): string; + get port(): string; + get pathname(): string; + get search(): string; + get origin(): string; + get href(): string; + get hash(): string; + toString(): string; + } + const _default: Location; + export default _default; +} + declare module "socket:internal/symbols" { export const dispose: any; export const serialize: any; @@ -1397,7 +1420,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2164,7 +2187,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2840,7 +2863,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3081,7 +3104,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3111,7 +3134,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): any; + export function getCode(name: string | number): signal; /** * Gets the name for a given 'signal' code * @return {string} @@ -3255,7 +3278,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3636,29 +3659,11 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } -declare module "socket:location" { - export class Location { - get url(): URL; - get protocol(): string; - get host(): string; - get hostname(): string; - get port(): string; - get pathname(): string; - get search(): string; - get origin(): string; - get href(): string; - get hash(): string; - toString(): string; - } - const _default: Location; - export default _default; -} - declare module "socket:path/path" { /** * The path.resolve() method resolves a sequence of paths or path segments into an absolute path. @@ -4546,9 +4551,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -4587,7 +4592,7 @@ declare module "socket:diagnostics/runtime" { * Queries runtime diagnostics. * @return {Promise<QueryDiagnostic>} */ - export function query(): Promise<QueryDiagnostic>; + export function query(type: any): Promise<QueryDiagnostic>; /** * A base container class for diagnostic information. */ @@ -5276,7 +5281,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6149,7 +6154,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -6993,7 +6998,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "nested" | "top-level"; + get frameType(): "none" | "top-level" | "nested"; /** * The type of the client. * @type {'window'|'worker'} @@ -7025,10 +7030,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: 'window' | 'worker'; + type?: "window" | "worker"; parent?: object | null; top?: object | null; - frameType?: 'top-level' | 'nested' | 'none'; + frameType?: "top-level" | "nested" | "none"; }; } @@ -7102,7 +7107,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7315,6 +7320,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8012,7 +8018,7 @@ declare module "socket:shared-worker/index" { export function getContextWindow(): Promise<any>; export const SHARED_WORKER_WINDOW_INDEX: 46; export const SHARED_WORKER_WINDOW_TITLE: "socket:shared-worker"; - export const SHARED_WORKER_WINDOW_PATH: string; + export const SHARED_WORKER_WINDOW_PATH: "/socket/shared-worker/index.html"; export const channel: BroadcastChannel; export const workers: Map<any, any>; export class SharedWorkerMessagePort extends ipc.IPCMessagePort { @@ -8039,7 +8045,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8085,9 +8091,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; }; } export namespace Promise { @@ -8707,7 +8713,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9198,9 +9204,9 @@ declare module "socket:internal/conduit" { export const DEFAULT_MAX_RECONNECT_TIMEOUT: 256; /** * A pool of known `Conduit` instances. - * @type {Set<WeakRef<Conduit>>} + * @type {Set<Conduit>} */ - export const pool: Set<WeakRef<Conduit>>; + export const pool: Set<Conduit>; /** * A container for managing a WebSocket connection to the internal runtime * Conduit WebSocket server. @@ -9341,10 +9347,10 @@ declare module "socket:internal/conduit" { * Sends a message with the specified options and payload over the * WebSocket connection. * @param {object} options - The options to send. - * @param {Uint8Array} payload - The payload to send. + * @param {Uint8Array=} [payload] - The payload to send. * @return {boolean} */ - send(options: object, payload: Uint8Array): boolean; + send(options: object, payload?: Uint8Array | undefined): boolean; /** * Closes the WebSocket connection, preventing reconnects. */ @@ -9861,7 +9867,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9936,7 +9942,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10362,7 +10368,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10426,7 +10432,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -10558,7 +10564,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10599,7 +10605,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -10786,7 +10792,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10821,13 +10827,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13467,7 +13473,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -13475,7 +13481,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13724,7 +13730,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -13735,7 +13741,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15016,7 +15022,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -15027,13 +15033,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -15073,7 +15079,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15433,7 +15439,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15471,9 +15477,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:commonjs/require" { @@ -15532,7 +15538,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -16092,7 +16098,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -16278,9 +16284,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -16996,7 +17002,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -17016,7 +17022,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17038,8 +17044,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -17049,10 +17055,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -17153,7 +17159,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17162,7 +17168,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17243,8 +17249,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17258,7 +17264,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17707,12 +17713,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17765,7 +17771,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17782,8 +17788,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 3a299cda29b48a9c092b961a38ebbed5e32478da Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:16:47 +0200 Subject: [PATCH 1141/1178] fix(api/ipc.js): improve ipc port/channel impl --- api/ipc.js | 131 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 32 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 544fb54605..9ea0173a95 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -123,7 +123,7 @@ function initializeXHRIntercept () { }) } -function getErrorClass (type, fallback) { +function getErrorClass (type, fallback = null) { if (typeof globalThis !== 'undefined' && typeof globalThis[type] === 'function') { // eslint-disable-next-line return new Function(`return function ${type} () { @@ -226,6 +226,7 @@ function getRequestResponse (request, options) { } const { status, responseURL, statusText } = request + // @ts-ignore const message = Message.from(responseURL) const source = message.command @@ -338,9 +339,11 @@ export function maybeMakeError (error, caller) { } if ( + // @ts-ignore typeof Error.captureStackTrace === 'function' && typeof caller === 'function' ) { + // @ts-ignore Error.captureStackTrace(err, caller) } @@ -399,6 +402,10 @@ export function debug (enable) { return debug.enabled } +/** + * @type {boolean} + */ +debug.enabled = false Object.defineProperty(debug, 'enabled', { enumerable: false, set (value) { @@ -436,15 +443,18 @@ export class Headers extends globalThis.Headers { if (Array.isArray(input) && !Array.isArray(input[0])) { input = input.join('\n') } else if (typeof input?.entries === 'function') { + // @ts-ignore return new this(Array.from(input.entries())) } else if (isPlainObject(input) || isArrayLike(input)) { return new this(input) } else if (typeof input?.getAllResponseHeaders === 'function') { input = input.getAllResponseHeaders() } else if (typeof input?.headers?.entries === 'function') { + // @ts-ignore return new this(Array.from(input.headers.entries())) } + // @ts-ignore return new this(parseHeaders(String(input))) } @@ -497,10 +507,14 @@ export function findMessageTransfers (transfers, object) { */ export function postMessage (message, ...args) { if (globalThis?.webkit?.messageHandlers?.external?.postMessage) { + // @ts-ignore return webkit.messageHandlers.external.postMessage(message, ...args) } else if (globalThis?.chrome?.webview?.postMessage) { + // @ts-ignore return chrome.webview.postMessage(message, ...args) + // @ts-ignore } else if (globalThis?.external?.postMessage) { + // @ts-ignore return external.postMessage(message, ...args) } else if (globalThis.postMessage) { const transfer = [] @@ -767,11 +781,11 @@ export class Message extends URL { /** * Get a parameter value by `key`. * @param {string} key - * @param {any} defaultValue + * @param {any=} [defaultValue] * @return {any} * @ignore */ - get (key, defaultValue) { + get (key, defaultValue = undefined) { if (!this.has(key)) { return defaultValue } @@ -1034,7 +1048,7 @@ export async function ready () { const { toString } = Object.prototype export class IPCSearchParams extends URLSearchParams { - constructor (params, nonce) { + constructor (params, nonce = null) { let value if (params !== undefined && toString.call(params) !== '[object Object]') { value = params @@ -1064,9 +1078,11 @@ export class IPCSearchParams extends URLSearchParams { } const runtimeFrameSource = globalThis.document + // @ts-ignore ? globalThis.document.querySelector('meta[name=runtime-frame-source]')?.content : '' + // @ts-ignore if (globalThis.top && globalThis.top !== globalThis) { this.set('runtime-frame-type', 'nested') } else if (!globalThis.window && globalThis.self === globalThis) { @@ -1103,7 +1119,7 @@ export class IPCSearchParams extends URLSearchParams { * @return {Result} * @ignore */ -export function sendSync (command, value = '', options = {}, buffer = null) { +export function sendSync (command, value = '', options = null, buffer = null) { if (!globalThis.XMLHttpRequest) { const err = new Error('XMLHttpRequest is not supported in environment') return Result.from(err) @@ -1118,7 +1134,7 @@ export function sendSync (command, value = '', options = {}, buffer = null) { const uri = `ipc://${command}?${params}` if ( - typeof __global_ipc_extension_handler === 'function' && + typeof globalThis.__global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { // eslint-disable-next-line @@ -1131,7 +1147,7 @@ export function sendSync (command, value = '', options = {}, buffer = null) { let response = null try { - response = __global_ipc_extension_handler(uri) + response = globalThis.__global_ipc_extension_handler(uri) } catch (err) { return Result.from(null, err) } @@ -1273,7 +1289,7 @@ export async function send (command, value, options = null) { const uri = `ipc://${command}?${params}` if ( - typeof __global_ipc_extension_handler === 'function' && + typeof globalThis.__global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { // eslint-disable-next-line @@ -1286,7 +1302,7 @@ export async function send (command, value, options = null) { let response = null try { - response = await __global_ipc_extension_handler(uri) + response = await globalThis.__global_ipc_extension_handler(uri) } catch (err) { return Result.from(null, err) } @@ -1355,7 +1371,7 @@ export async function write (command, value, buffer, options) { const uri = `ipc://${command}?${params}` if ( - typeof __global_ipc_extension_handler === 'function' && + typeof globalThis.__global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { // eslint-disable-next-line @@ -1368,7 +1384,7 @@ export async function write (command, value, buffer, options) { let response = null try { - response = await __global_ipc_extension_handler(uri, buffer) + response = await globalThis.__global_ipc_extension_handler(uri, buffer) } catch (err) { return Result.from(null, err) } @@ -1482,7 +1498,7 @@ export async function request (command, value, options) { const uri = `ipc://${command}?${params}` if ( - typeof __global_ipc_extension_handler === 'function' && + typeof globalThis.__global_ipc_extension_handler === 'function' && (options?.useExtensionIPCIfAvailable || command.startsWith('fs.')) ) { // eslint-disable-next-line @@ -1495,7 +1511,7 @@ export async function request (command, value, options) { let response = null try { - response = await __global_ipc_extension_handler(uri) + response = await globalThis.__global_ipc_extension_handler(uri) } catch (err) { return Result.from(null, err) } @@ -1748,7 +1764,7 @@ export function findIPCMessageTransfers (transfers, object) { add(object) } else if (Array.isArray(object)) { for (let i = 0; i < object.length; ++i) { - object[i] = findMessageTransfers(transfers, object[i]) + object[i] = findIPCMessageTransfers(transfers, object[i]) } } else if (object && typeof object === 'object') { if ( @@ -1758,10 +1774,26 @@ export function findIPCMessageTransfers (transfers, object) { ) ) { const port = IPCMessagePort.create(object) - object.addEventListener('message', (event) => { + object.addEventListener('message', function onMessage (event) { + if (port.closed === true) { + port.onmessage = null + event.preventDefault() + event.stopImmediatePropagation() + object.removeEventListener('message', onMessage) + return false + } + port.dispatchEvent(new MessageEvent('message', event)) }) + port.onmessage = (event) => { + if (port.closed === true) { + port.onmessage = null + event.preventDefault() + event.stopImmediatePropagation() + return false + } + const transfers = new Set() findIPCMessageTransfers(transfers, event.data) object.postMessage(event.data, { @@ -1772,7 +1804,7 @@ export function findIPCMessageTransfers (transfers, object) { return port } else { for (const key in object) { - object[key] = findMessageTransfers(transfers, object[key]) + object[key] = findIPCMessageTransfers(transfers, object[key]) } } } @@ -1800,6 +1832,7 @@ export class IPCMessagePort extends MessagePort { static create (options = null) { const id = String(options?.id ?? rand64()) const port = Object.create(this.prototype) + const token = String(rand64()) const channel = typeof options?.channel === 'string' ? new BroadcastChannel(options.channel) : options.channel ?? new BroadcastChannel(id) @@ -1807,6 +1840,7 @@ export class IPCMessagePort extends MessagePort { port[Symbol.for('socket.runtime.IPCMessagePort.id')] = id ports.set(id, Object.create(null, { id: { writable: true, value: id }, + token: { writable: false, value: token }, closed: { writable: true, value: false }, started: { writable: true, value: false }, channel: { writable: true, value: channel }, @@ -1815,9 +1849,21 @@ export class IPCMessagePort extends MessagePort { eventTarget: { writable: true, value: new EventTarget() } })) - channel.addEventListener('message', (event) => { - if (ports.get(id)?.started) { - port.dispatchEvent(new MessageEvent('message', event)) + channel.addEventListener('message', function onMessage (event) { + const state = ports.get(id) + + if (!state || state?.closed === true) { + event.preventDefault() + event.stopImmediatePropagation() + channel.removeEventListener('message', onMessage) + return false + } + + if (state?.started && event.data?.token !== state.token) { + port.dispatchEvent(new MessageEvent('message', { + ...event, + data: event.data?.data + })) } }) @@ -1899,11 +1945,15 @@ export class IPCMessagePort extends MessagePort { } } - close () { + close (purge = true) { const port = ports.get(this.id) if (port) { port.closed = true } + + if (purge) { + ports.delete(this.id) + } } postMessage (message, optionsOrTransferList) { @@ -1932,7 +1982,10 @@ export class IPCMessagePort extends MessagePort { } } - port.channel.postMessage(serializedMessage, options) + port.channel.postMessage({ + token: port.token, + data: serializedMessage + }, options) } addEventListener (...args) { @@ -1999,30 +2052,44 @@ export class IPCMessagePort extends MessagePort { export class IPCMessageChannel extends MessageChannel { #id = null + #port1 = null + #port2 = null #channel = null - port1 = null - port2 = null - constructor (options = null) { super() this.#id = String(options?.id ?? rand64()) this.#channel = options?.channel ?? new BroadcastChannel(this.#id) - this.port1 = IPCMessagePort.create({ - channel: this.#channel, - ...options?.port1 - }) + this.#port1 = IPCMessagePort.create(options?.port1) + this.#port2 = IPCMessagePort.create(options?.port2) - this.port2 = IPCMessagePort.create({ - channel: this.#channel, - ...options?.port2 - }) + this.port1[Symbol.for('socket.runtime.ipc.MessagePort.handlePostMessage')] = (message, options) => { + this.port2.channel.postMessage(message, options) + return false + } + + this.port2[Symbol.for('socket.runtime.ipc.MessagePort.handlePostMessage')] = (message, options) => { + this.port2.channel.postMessage(message, options) + return false + } } get id () { return this.#id } + + get port1 () { + return this.#port1 + } + + get port2 () { + return this.#port2 + } + + get channel () { + return this.#channel + } } // eslint-disable-next-line From bdbf6e6f1a7004a1cc97be92da848352227278ea Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:17:21 +0200 Subject: [PATCH 1142/1178] fix(api/vm): fix context argument and shared worker impl --- api/vm.js | 32 +++++++++++++++++++++----------- api/vm/worker.js | 3 +-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/api/vm.js b/api/vm.js index 92483b37c5..55b09de77d 100644 --- a/api/vm.js +++ b/api/vm.js @@ -43,6 +43,7 @@ const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) const TypedArray = TypedArrayPrototype.constructor const kContextTag = Symbol('socket.vm.Context') +const kWorkerContextReady = Symbol('socket.vm.ContextWorker.ready') const VM_WINDOW_INDEX = 47 const VM_WINDOW_TITLE = 'socket:vm' @@ -1253,14 +1254,16 @@ export async function getContextWindow () { */ export async function getContextWorker () { if (contextWorker) { - return await contextWorker.ready + await contextWorker.ready + await contextWorker[kWorkerContextReady] + return contextWorker } if (os.platform() === 'win32' && !process.env.COREWEBVIEW2_22_AVAILABLE) { if (globalThis.window && globalThis.top === globalThis.window) { // inside global top window contextWorker = new ContextWorkerInterface() - contextWorker.ready = Promise.resolve(contextWorker) + contextWorker[kWorkerContextReady] = Promise.resolve(contextWorker) globals.register('vm.contextWorker', contextWorker) } else if ( globalThis.window && @@ -1270,7 +1273,7 @@ export async function getContextWorker () { // inside realm frame // @ts-ignore contextWorker = new ContextWorkerInterfaceProxy(globalThis.top.__globals) - contextWorker.ready = Promise.resolve(contextWorker) + contextWorker[kWorkerContextReady] = Promise.resolve(contextWorker) } else { throw new TypeError('Unable to determine VM context worker') } @@ -1279,7 +1282,7 @@ export async function getContextWorker () { type: 'module' }) - contextWorker.ready = new Promise((resolve, reject) => { + contextWorker[kWorkerContextReady] = new Promise((resolve, reject) => { contextWorker.addEventListener('error', (event) => { reject(new Error('Failed to initialize VM Context SharedWorker', { cause: event.error ?? event @@ -1315,7 +1318,9 @@ export async function getContextWorker () { } }) - return await contextWorker.ready + await contextWorker.ready + await contextWorker[kWorkerContextReady] + return contextWorker } /** @@ -1758,13 +1763,18 @@ export function compileFunction (source, options = null) { * garbage collected or there are no longer any references to it and its * associated `Script` instance. * @param {string|object|function} source - * @param {ScriptOptions=} [options] * @param {object=} [context] + * @param {ScriptOptions=} [options] * @return {Promise<any>} */ -export async function runInContext (source, options, context) { +export async function runInContext (source, context, options) { source = convertSourceToString(source) - context = options?.context ?? context ?? sharedContext + context = ( + context?.context ?? + options?.context ?? + context ?? + sharedContext + ) const script = scripts.get(context) ?? new Script(source, options) @@ -1782,13 +1792,13 @@ export async function runInContext (source, options, context) { * Run `source` JavaScript in new context. The script context is destroyed after * execution. This is typically a "one off" isolated run. * @param {string} source - * @param {ScriptOptions=} [options] * @param {object=} [context] + * @param {ScriptOptions=} [options] * @return {Promise<any>} */ -export async function runInNewContext (source, options, context) { +export async function runInNewContext (source, context, options) { source = convertSourceToString(source) - context = options?.context ?? context ?? {} + context = options?.context ?? context?.context ?? context ?? {} const script = new Script(source, options) scripts.set(script.context, script) const result = await script.runInNewContext(context, options) diff --git a/api/vm/worker.js b/api/vm/worker.js index 639e875e97..9a629336cb 100644 --- a/api/vm/worker.js +++ b/api/vm/worker.js @@ -145,8 +145,7 @@ class State { } onPortMessage (port, event) { - // debug echo - // port.postMessage(event.data) + // port.postMessage(event.data) // debug echo if (event.data?.type === 'terminate-worker') { for (const port of this.ports) { From f03c470465ab66ee6c0303563f1f1fff903266d2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:18:23 +0200 Subject: [PATCH 1143/1178] fix(api/shared-worker/worker.js): tracker active connections, replace duplicates --- api/shared-worker/worker.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/shared-worker/worker.js b/api/shared-worker/worker.js index f050c5b6f4..ffd4f78d1d 100644 --- a/api/shared-worker/worker.js +++ b/api/shared-worker/worker.js @@ -28,6 +28,7 @@ export const SHARED_WORKER_READY_TOKEN = { __shared_worker_ready: true } // state export const module = { exports: {} } +export const connections = new Set() // event listeners hooks.onReady(onReady) @@ -200,6 +201,15 @@ export async function onMessage (event) { SharedWorkerMessagePort }))) + for (const entry of connections) { + if (entry.id === connection.port.id) { + entry.close(false) + connections.delete(entry) + break + } + } + + connections.add(connection.port) const connectEvent = new MessageEvent('connect') Object.defineProperty(connectEvent, 'ports', { configurable: false, From d4427993f92a27fd37676b891dac65d83d7238d2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:18:29 +0200 Subject: [PATCH 1144/1178] chore(api): generate types + docs --- api/README.md | 34 +++++++++++++++++++++++++--------- api/index.d.ts | 23 +++++++++++++---------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/api/README.md b/api/README.md index aecf253bb2..97c421e502 100644 --- a/api/README.md +++ b/api/README.md @@ -1710,17 +1710,33 @@ Watch for changes at `path` calling `callback` import { send } from 'socket:ipc' ``` -## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L280) +## [`maybeMakeError()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L281) This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exported but undocumented. -## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1036) +### [debug](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L408) + + + +### [undefined](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L408) + + + +### [undefined](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L408) + + + +### [undefined](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L408) + + + +## [`IPCSearchParams` (extends `URLSearchParams`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1050) This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1202) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1218) Emit event to be dispatched on `window` object. @@ -1731,7 +1747,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1261) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1277) Sends an async IPC command request with parameters. @@ -1747,27 +1763,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1712) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1728) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1744) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1760) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1793) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1825) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1795) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1827) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2000) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2053) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index 5bcf959953..0478c420cb 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -800,7 +800,7 @@ declare module "socket:ipc" { */ export function debug(enable?: (boolean)): boolean; export namespace debug { - let enabled: any; + let enabled: boolean; function log(...args: any[]): any; } /** @@ -1038,11 +1038,11 @@ declare module "socket:ipc" { /** * Get a parameter value by `key`. * @param {string} key - * @param {any} defaultValue + * @param {any=} [defaultValue] * @return {any} * @ignore */ - get(key: string, defaultValue: any): any; + get(key: string, defaultValue?: any | undefined): any; /** * Delete a parameter by `key`. * @param {string} key @@ -1160,7 +1160,7 @@ declare module "socket:ipc" { [Symbol.iterator](): Generator<any, void, unknown>; } export class IPCSearchParams extends URLSearchParams { - constructor(params: any, nonce: any); + constructor(params: any, nonce?: any); } /** * @ignore @@ -1177,6 +1177,7 @@ declare module "socket:ipc" { get onmessage(): any; set onmessageerror(onmessageerror: any); get onmessageerror(): any; + close(purge?: boolean): void; postMessage(message: any, optionsOrTransferList: any): void; addEventListener(...args: any[]): any; removeEventListener(...args: any[]): any; @@ -1184,9 +1185,10 @@ declare module "socket:ipc" { } export class IPCMessageChannel extends MessageChannel { constructor(options?: any); - port1: any; - port2: any; get id(): any; + get port1(): any; + get port2(): any; + get channel(): any; #private; } export default exports; @@ -8298,20 +8300,20 @@ declare module "socket:vm" { * garbage collected or there are no longer any references to it and its * associated `Script` instance. * @param {string|object|function} source - * @param {ScriptOptions=} [options] * @param {object=} [context] + * @param {ScriptOptions=} [options] * @return {Promise<any>} */ - export function runInContext(source: string | object | Function, options?: ScriptOptions | undefined, context?: object | undefined): Promise<any>; + export function runInContext(source: string | object | Function, context?: object | undefined, options?: ScriptOptions | undefined): Promise<any>; /** * Run `source` JavaScript in new context. The script context is destroyed after * execution. This is typically a "one off" isolated run. * @param {string} source - * @param {ScriptOptions=} [options] * @param {object=} [context] + * @param {ScriptOptions=} [options] * @return {Promise<any>} */ - export function runInNewContext(source: string, options?: ScriptOptions | undefined, context?: object | undefined): Promise<any>; + export function runInNewContext(source: string, context?: object | undefined, options?: ScriptOptions | undefined): Promise<any>; /** * Run `source` JavaScript in this current context (`globalThis`). * @param {string} source @@ -17664,6 +17666,7 @@ declare module "socket:shared-worker/worker" { export namespace module { let exports: {}; } + export const connections: Set<any>; } declare module "socket:test/harness" { From e43d1b4e5b7108a2f3abfca61361aafd40bddc31 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:42:40 +0200 Subject: [PATCH 1145/1178] chore(api/ipc.js): clean up lint --- api/ipc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/ipc.js b/api/ipc.js index 9ea0173a95..1bb4598f58 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -33,7 +33,7 @@ * ``` */ -/* global webkit, chrome, external, reportError, __global_ipc_extension_handler */ +/* global webkit, chrome, external, reportError */ import { AbortError, InternalError, From ce1c87c840b0e174eede322fa6f9af7543c9367d Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:43:16 +0200 Subject: [PATCH 1146/1178] fix(core): do not wait for dispatch queue drain on linux --- src/core/core.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/core.cc b/src/core/core.cc index 1b1a4b48da..63d294728e 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -335,6 +335,7 @@ namespace SSC { } void Core::pauseEventLoop() { + #if !SOCKET_RUNTIME_PLATFORM_LINUX // wait for drain of event loop dispatch queue while (true) { Lock lock(this->mutex); @@ -342,6 +343,7 @@ namespace SSC { break; } } + #endif this->isLoopRunning = false; do { From ea51055d2a531fa2143c6e1b44ebf23a78f4e50a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:44:07 +0200 Subject: [PATCH 1147/1178] refactor(desktop/main): improve signal handling dispatch --- src/desktop/main.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/desktop/main.cc b/src/desktop/main.cc index 0f29565efb..26026c99ef 100644 --- a/src/desktop/main.cc +++ b/src/desktop/main.cc @@ -113,7 +113,7 @@ void signalHandler (int signum) { #endif if (!signalsDisabled || std::find(signals.begin(), signals.end(), name) != signals.end()) { - defaultWindowSignalHandler(signum); + app->dispatch([signum]() { defaultWindowSignalHandler(signum); }); } if (signum == SIGTERM || signum == SIGINT) { @@ -651,7 +651,7 @@ MAIN { unlink(appInstanceLock.c_str()); #endif if (process != nullptr) { - process->kill(); + process->kill(signum); } exit(signum); }; @@ -903,7 +903,7 @@ MAIN { unlink(appInstanceLock.c_str()); #endif if (process != nullptr) { - process->kill(); + process->kill(code); process = nullptr; } From 493fafcdde837f0466f761b6cc22534b6f86b025 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 14:44:33 +0200 Subject: [PATCH 1148/1178] fix(window/linux): check if application exists before destruction --- src/window/linux.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index fd275599ff..91c3bae12d 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -958,7 +958,6 @@ namespace SSC { aspectWidth = std::stof(trim(parts[0])); aspectHeight = std::stof(trim(parts[1])); } catch (...) { - debug("invalid aspect ratio"); return FALSE; } @@ -979,6 +978,12 @@ namespace SSC { } Window::~Window () { + auto app = App::sharedApplication(); + + if (!app || app->shouldExit) { + return; + } + if (this->policies) { g_object_unref(this->policies); this->policies = nullptr; From c9c6d919d23743356752f10722aef890c6173fd0 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:13:54 +0200 Subject: [PATCH 1149/1178] fix(api/fs/web.js): make 'getFile' async --- api/fs/web.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/api/fs/web.js b/api/fs/web.js index fd2ed7a6e7..4218bc4525 100644 --- a/api/fs/web.js +++ b/api/fs/web.js @@ -38,7 +38,7 @@ export const FileSystemHandle = globalThis.FileSystemHandle ?? // @ts-ignore export const FileSystemFileHandle = globalThis.FileSystemFileHandle ?? class FileSystemFileHandle extends FileSystemHandle { - getFile () {} + async getFile () {} async createWritable (options = null) {} async createSyncAccessHandle () {} } @@ -280,7 +280,7 @@ export async function createFileSystemWritableFileStream (handle, options) { console.warn('socket:fs/web: Missing platform \'FileSystemWritableFileStream\' implementation') } - const file = handle.getFile() + const file = await handle.getFile() let offset = 0 let fd = null @@ -375,10 +375,6 @@ export async function createFileSystemFileHandle (file, options = null) { console.warn('socket:fs/web: Missing platform \'FileSystemFileHandle\' implementation') } - if (typeof file === 'string') { - file = await createFile(file) - } - return create(FileSystemFileHandle, class FileSystemFileHandle { get [kFileSystemHandleFullName] () { return file[kFileFullName] } get [kFileDescriptor] () { return file[kFileDescriptor] } @@ -391,7 +387,11 @@ export async function createFileSystemFileHandle (file, options = null) { return 'file' } - getFile () { + async getFile () { + if (typeof file === 'string') { + file = await createFile(file) + } + return file } @@ -417,10 +417,14 @@ export async function createFileSystemFileHandle (file, options = null) { } async move (nameOrDestinationHandle, name = null) { - if (writable === false || URL.canParse(file?.name)) { + if (writable === false || URL.canParse(file?.name ?? file)) { throw new NotAllowedError('FileSystemFileHandle is in \'readonly\' mode') } + if (typeof file === 'string') { + file = await createFile(file) + } + let destination = null if (typeof nameOrDestinationHandle === 'string') { name = nameOrDestinationHandle @@ -443,10 +447,14 @@ export async function createFileSystemFileHandle (file, options = null) { } async createWritable (options = null) { - if (writable === false || URL.canParse(file?.name)) { + if (writable === false || URL.canParse(file?.name ?? file)) { throw new NotAllowedError('FileSystemFileHandle is in \'readonly\' mode') } + if (typeof file === 'string') { + file = await createFile(file) + } + return await createFileSystemWritableFileStream(this, options) } }) From 7526c7f36e9109a8846df84b57106e38fee81d4f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:14:34 +0200 Subject: [PATCH 1150/1178] fix(api/internal/primitives.js): fix 'FormData' append/set for 'Object.create' 'File' instances --- api/internal/primitives.js | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 646fb496e8..8c29a5d02b 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -326,6 +326,52 @@ export function init () { // WebAssembly install(WebAssembly, globalThis.WebAssembly, 'WebAssembly') + // quirks + if (typeof globalThis.FormData === 'function') { + const { append, set } = FormData.prototype + Object.defineProperties(FormData.prototype, { + append: { + configurable: true, + enumerable: true, + value (name, value, filename) { + if ( // check for 'File' + typeof value === 'object' && + value instanceof Blob && + typeof value.name === 'string' + ) { + if (!filename) { + filename = value.name + } + + value = value.slice() + } + + return append.call(this, name, value, filename) + } + }, + + set: { + configurable: true, + enumerable: true, + value (name, value, filename) { + if ( // check for 'File' + typeof value === 'object' && + value instanceof Blob && + typeof value.name === 'string' + ) { + if (!filename) { + filename = value.name + } + + value = value.slice() + } + + return set.call(this, name, value, filename) + } + }, + }) + } + applied = true if (!Error.captureStackTrace) { From c75bfaa5200074dd9f0cff866699deaed997723e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:14:50 +0200 Subject: [PATCH 1151/1178] fix(window/manager.cc): allow 'http' requests on linux again --- src/window/manager.cc | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/window/manager.cc b/src/window/manager.cc index 1e70416e5a..36ab905350 100644 --- a/src/window/manager.cc +++ b/src/window/manager.cc @@ -291,22 +291,7 @@ namespace SSC { windowOptions.userConfig = options.userConfig; } - const auto window = this->createWindow(windowOptions); - - if (isDebugEnabled() && window->index == 0) { - #if SOCKET_RUNTIME_PLATFORM_LINUX - if (devHost.starts_with("http:")) { - auto webContext = webkit_web_context_get_default(); - auto security = webkit_web_context_get_security_manager(webContext); - webkit_security_manager_register_uri_scheme_as_display_isolated(security, "http"); - webkit_security_manager_register_uri_scheme_as_cors_enabled(security, "http"); - webkit_security_manager_register_uri_scheme_as_secure(security, "http"); - webkit_security_manager_register_uri_scheme_as_local(security, "http"); - } - #endif - } - - return window; + return this->createWindow(windowOptions); } JSON::Array WindowManager::json (const Vector<int>& indices) { From 1ce44c95bd431bb180c0c9dfcc7ed6bf06c23c97 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:37:39 +0200 Subject: [PATCH 1152/1178] fix(api/ipc.js): fix early primordials check --- api/ipc.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/ipc.js b/api/ipc.js index 1bb4598f58..456846feca 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1168,10 +1168,10 @@ export function sendSync (command, value = '', options = null, buffer = null) { debug.log('ipc.sendSync: %s', uri) } - if (typeof globalThis.primordials !== 'undefined') { - if (!(/android/i.test(globalThis.primordials.platform) && globalThis.document)) { + if (options?.responseType && typeof primordials !== 'undefined') { + if (!(/android/i.test(primordials.platform) && globalThis.document)) { // @ts-ignore - request.responseType = options?.responseType ?? '' + request.responseType = options.responseType } } From 58697456d2bf68f694da8d856d59076dc44ae008 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:59:02 +0200 Subject: [PATCH 1153/1178] fix(api/fs/web.js): fix 'slice()' for 'File' factory impl --- api/fs/web.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fs/web.js b/api/fs/web.js index 4218bc4525..02cd142b92 100644 --- a/api/fs/web.js +++ b/api/fs/web.js @@ -143,7 +143,7 @@ export async function createFile (filename, options = null) { blobBuffer = readFileSync(filename) } - const blob = new Blob([blobBuffer], { + const blob = new Blob([blobBuffer.buffer], { type: contentType }) From 062f993a155c090326f65966ff6f0a01968dc543 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:59:27 +0200 Subject: [PATCH 1154/1178] fix(api/fs): fix incorrect 'readFileSync' default encoding --- api/fs/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fs/index.js b/api/fs/index.js index 9a03f0e953..f5045fab9e 100644 --- a/api/fs/index.js +++ b/api/fs/index.js @@ -1085,7 +1085,7 @@ export function readFileSync (path, options = null) { path = normalizePath(path) options = { flags: 'r', - encoding: options?.encoding ?? 'utf8', + encoding: options?.encoding, ...options } From bb48605c38974956b055fe827c0c8c5635236ca1 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 20 Aug 2024 19:59:57 +0200 Subject: [PATCH 1155/1178] chore(api): generate types + docs --- api/README.md | 14 +++++++------- api/index.d.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/README.md b/api/README.md index 97c421e502..fa4527b133 100644 --- a/api/README.md +++ b/api/README.md @@ -1736,7 +1736,7 @@ This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exp This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1218) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1219) Emit event to be dispatched on `window` object. @@ -1747,7 +1747,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1277) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1278) Sends an async IPC command request with parameters. @@ -1763,27 +1763,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1728) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1729) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1760) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1761) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1825) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1826) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1827) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1828) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2053) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2054) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index 0478c420cb..3fb9ce277f 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -9776,7 +9776,7 @@ declare module "socket:fs/web" { prototype: FileSystemFileHandle; } | { new (): { - getFile(): void; + getFile(): Promise<void>; createWritable(options?: any): Promise<void>; createSyncAccessHandle(): Promise<void>; readonly name: any; From 4642235b621565723e55b25184860f855726c00f Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 21 Aug 2024 00:34:02 +0200 Subject: [PATCH 1156/1178] chore(api/internal/primitives.js): lint --- api/internal/primitives.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/primitives.js b/api/internal/primitives.js index 8c29a5d02b..13579ca09c 100644 --- a/api/internal/primitives.js +++ b/api/internal/primitives.js @@ -368,7 +368,7 @@ export function init () { return set.call(this, name, value, filename) } - }, + } }) } From 829fa047d116789ed597adf2bd5a541df4c84569 Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Wed, 21 Aug 2024 13:58:57 +0200 Subject: [PATCH 1157/1178] chore(api): update protocol --- api/latica/proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/latica/proxy.js b/api/latica/proxy.js index 4ea1ebe7dc..6868f118fc 100644 --- a/api/latica/proxy.js +++ b/api/latica/proxy.js @@ -81,7 +81,7 @@ class PeerWorkerProxy { constructor (options, port, fn) { if (!options.isWorkerThread) { this.#channel = new MessageChannel() - this.#worker = new window.Worker(new URL('./worker.js', import.meta.url)) + this.#worker = new globalThis.Worker(new URL('./worker.js', import.meta.url)) this.#worker.addEventListener('error', err => { throw err From 17409a4f07973127c8a1d5e5d0b2f62bda6f1bd7 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 21 Aug 2024 15:27:40 +0200 Subject: [PATCH 1158/1178] fix(cli): set verbose output default to false --- src/cli/cli.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 8d70b82b7b..7d1c3a9b89 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -79,7 +79,7 @@ Map rc; auto start = system_clock::now(); bool flagDebugMode = true; -bool flagVerboseMode = true; +bool flagVerboseMode = false; bool flagQuietMode = false; Map defaultTemplateAttrs; From 71fa3b230b57cb8035e4e1c27acc50bfb6d0a05a Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Wed, 21 Aug 2024 15:43:12 +0200 Subject: [PATCH 1159/1178] fix(cli): fix '--verbose' flag --- src/cli/cli.cc | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index 7d1c3a9b89..c546ae45af 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -1869,7 +1869,7 @@ optionsAndEnv parseCommandLineOptions ( return result; } -int main (const int argc, const char* argv[]) { +int main (int argc, char* argv[]) { defaultTemplateAttrs = { { "ssc_version", SSC::VERSION_FULL_STRING }, { "project_name", "beepboop" } @@ -1906,7 +1906,11 @@ int main (const int argc, const char* argv[]) { exit(0); } - if (subcommand[0] == '-') { + if (equal(subcommand, "--verbose")) { + flagVerboseMode = true; + argc--; + argv++; + } else if (subcommand[0] == '-') { log("unknown option: " + String(subcommand)); printHelp("ssc"); exit(0); @@ -2015,7 +2019,7 @@ int main (const int argc, const char* argv[]) { std::function<void(Map, std::unordered_set<String>)> subcommandHandler ) -> void { if (argv[1] == subcommand) { - auto commandlineOptions = std::span(argv, argc).subspan(2, numberOfOptions); + auto commandlineOptions = std::span(const_cast<const char**>(argv), argc).subspan(2, numberOfOptions); auto optionsAndEnv = parseCommandLineOptions(commandlineOptions, availableOptions, subcommand); auto envs = optionsAndEnv.envs; @@ -2213,7 +2217,8 @@ int main (const int argc, const char* argv[]) { // second flag indicating whether option should be followed by a value CommandLineOptions initOptions = { { { "--config", "-C" }, true, false }, - { { "--name", "-n" }, true, true } + { { "--name", "-n" }, true, true }, + { { "--vebose", "-V" }, true, false } }; createSubcommand("init", initOptions, false, [&](Map optionsWithValue, std::unordered_set<String> optionsWithoutValue) -> void { auto isTargetPathEmpty = fs::exists(targetPath) ? fs::is_empty(targetPath) : true; @@ -2274,7 +2279,8 @@ int main (const int argc, const char* argv[]) { { { "--platform" }, false, true }, { { "--ecid" }, true, false }, { { "--udid" }, true, false }, - { { "--only" }, true, false } + { { "--only" }, true, false }, + { { "--vebose", "-V" }, true, false } }; createSubcommand("list-devices", listDevicesOptions, false, [&](Map optionsWithValue, std::unordered_set<String> optionsWithoutValue) -> void { bool isUdid = @@ -2596,7 +2602,8 @@ int main (const int argc, const char* argv[]) { // first flag indicating whether option is optional // second flag indicating whether option should be followed by a value CommandLineOptions printBuildDirOptions = { - { { "--platform" }, true, true }, { { "--root" }, true, false} + { { "--platform" }, true, true }, { { "--root" }, true, false}, + { { "--vebose", "-V" }, true, false } }; createSubcommand("print-build-dir", printBuildDirOptions, true, [&](Map optionsWithValue, std::unordered_set<String> optionsWithoutValue) -> void { @@ -2636,7 +2643,8 @@ int main (const int argc, const char* argv[]) { { { "--package", "-p" }, true, false }, { { "--package-format", "-f" }, true, true }, { { "--codesign", "-c" }, true, false }, - { { "--notarize", "-n" }, true, false } + { { "--notarize", "-n" }, true, false }, + { { "--vebose", "-V" }, true, false } }; // Insert the elements of runOptions into buildOptions From 88114f382e6468607c268e93e802b5ba8f7fb612 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 22 Aug 2024 12:34:09 +0200 Subject: [PATCH 1160/1178] refactor(window/linux): make back/forward gesture navigation configurable --- src/window/linux.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/window/linux.cc b/src/window/linux.cc index 91c3bae12d..47d3199cca 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -149,7 +149,12 @@ namespace SSC { webkit_settings_set_enable_dns_prefetching(this->settings, true); webkit_settings_set_enable_smooth_scrolling(this->settings, true); webkit_settings_set_enable_developer_extras(this->settings, options.debug); - webkit_settings_set_enable_back_forward_navigation_gestures(this->settings, true); + + webkit_settings_set_enable_back_forward_navigation_gestures( + this->settings, + userConfig["webview_navigator_enable_navigation_gestures"] == "true" + ); + webkit_settings_set_media_content_types_requiring_hardware_support( this->settings, nullptr From 4caf2a41d30976272918092ce11b405e13b603f8 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 22 Aug 2024 12:37:47 +0200 Subject: [PATCH 1161/1178] refactor(window/apple): make back/forward gesture navigation configurable --- src/window/apple.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/window/apple.mm b/src/window/apple.mm index 90a8022713..ef07e7bdbd 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -419,6 +419,10 @@ - (void) scrollViewDidScroll: (UIScrollView*) scrollView { ]; #endif + this->webview.allowsBackForwardNavigationGestures = ( + userConfig["webview_navigator_enable_navigation_gestures"] == "true" + ); + this->webview.UIDelegate = webview; this->webview.layer.opaque = NO; From 1af17c7d8e30f6726dcda628b4de9a128416f5ff Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Thu, 22 Aug 2024 06:43:30 -0400 Subject: [PATCH 1162/1178] refactor(window/win): make back/forward gesture navigation configurable --- src/window/win.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/window/win.cc b/src/window/win.cc index e4d30fb38a..cee504f077 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -832,7 +832,9 @@ namespace SSC { settings3->put_AreBrowserAcceleratorKeysEnabled(wantsDebugMode); settings6->put_IsPinchZoomEnabled(false); - settings6->put_IsSwipeNavigationEnabled(false); + settings6->put_IsSwipeNavigationEnabled( + this->bridge.userConfig["webview_navigator_enable_navigation_destures"] == "true" + ); settings9->put_IsNonClientRegionSupportEnabled(true); } while (0); From f216eb5615a13edd66c638abe450e7916788b714 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 26 Aug 2024 15:58:52 +0200 Subject: [PATCH 1163/1178] fix(api/internal/init.js): fix blob imie type --- api/internal/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/init.js b/api/internal/init.js index ff8ca8e5e2..27fff0b5e8 100644 --- a/api/internal/init.js +++ b/api/internal/init.js @@ -437,7 +437,7 @@ class RuntimeWorker extends GlobalWorker { `.trim() const objectURL = URL.createObjectURL( - new Blob([preload.trim()], { type: 'application/javascript' }) + new Blob([preload.trim()], { type: 'text/javascript' }) ) // level 1 worker `EventTarget` 'message' listener From c67c917ec01d27cbef0ceb2037f7714a8a379352 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 26 Aug 2024 15:59:31 +0200 Subject: [PATCH 1164/1178] fix(api/location.js): fix location URL --- api/location.js | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/api/location.js b/api/location.js index 180fecd224..baebeaaff7 100644 --- a/api/location.js +++ b/api/location.js @@ -1,23 +1,47 @@ export class Location { get url () { + let url = null + + // XXX(@jwerle): should never be true... + // @ts-ignore if (globalThis.location === this) { - return null + return url } if (globalThis.location.href.startsWith('blob:')) { - return new URL(globalThis.RUNTIME_WORKER_LOCATION || globalThis.location.pathname) + url = new URL( + globalThis.RUNTIME_WORKER_LOCATION || ( + globalThis.location.pathname + + globalThis.location.search + + globalThis.location.hash + ) + ) + } else if (globalThis.location.origin === 'null') { + try { + url = new URL( + globalThis.location.pathname + + globalThis.location.search + + globalThis.location.hash, + globalThis.__args?.config?.meta_bundle_identifier ?? 'null' + ) + } catch {} + } else if (globalThis.location.hostname === globalThis.__args?.config?.meta_bundle_identifier) { + url = new URL(globalThis.location.href) + } else if (globalThis.__args.client.host === globalThis.location.hostname) { + url = new URL(globalThis.location.href) } - if (globalThis.location.origin === 'null') { - return new URL( - globalThis.location.pathname + - globalThis.location.search + - globalThis.location.hash, - globalThis.__args?.config?.meta_bundle_identifier ?? 'null' - ) + if (!url || url.hostname !== globalThis.__args?.config?.meta_bundle_identifier) { + if (globalThis.__args?.config?.meta_bundle_identifier) { + if (globalThis.__args.config.platform === 'android') { + url = new URL(`https://${globalThis.__args.config.meta_bundle_identifier}`) + } else { + url = new URL(`socket://${globalThis.__args.config.meta_bundle_identifier}`) + } + } } - return new URL(globalThis.location.href) + return url } get protocol () { From 28297cd14d49975bf9c99061abbf76728ad6adef Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 26 Aug 2024 16:04:02 +0200 Subject: [PATCH 1165/1178] refactor(ipc/preload): include client host/port and platform --- src/ipc/preload.cc | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 8a3052a565..b7642b530c 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -228,6 +228,9 @@ namespace SSC::IPC { ))); // 6. compile `globalThis.__args.client` values + static std::regex platformPattern("^mac$", std::regex_constants::icase); + static const auto platformHost = getDevHost(); + static const auto platformPort = getDevPort(); buffers.push_back(trim(tmpl( R"JAVASCRIPT( if (!globalThis.__args.client?.id) { @@ -248,6 +251,24 @@ namespace SSC::IPC { writable: true, value: globalThis.window ? 'window' : 'worker' }, + platform: { + configurable: false, + enumerable: true, + writable: true, + value: '{{platform}}' + }, + host: { + configurable: false, + enumerable: true, + writable: true, + value: '{{host}}' || null + }, + port: { + configurable: false, + enumerable: true, + writable: true, + value: {{port}} || null + }, parent: { configurable: false, enumerable: true, @@ -278,7 +299,10 @@ namespace SSC::IPC { )JAVASCRIPT", Map { {"id", std::to_string(rand64())}, - {"clientId", std::to_string(this->options.client.id)} + {"clientId", std::to_string(this->options.client.id)}, + {"platform", std::regex_replace(platform.os, platformPattern, "darwin")}, + {"host", platformPort > 0 ? platformHost : ""}, + {"port", std::to_string(platformPort)} } ))); From 1d5b6a1117fbf6c46b93495893a1192ba26581bf Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 26 Aug 2024 16:04:43 +0200 Subject: [PATCH 1166/1178] fix(api/worker_threads.js): fix worker threads URL --- api/worker_threads.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/worker_threads.js b/api/worker_threads.js index 5a3ae499b1..65ada17d4d 100644 --- a/api/worker_threads.js +++ b/api/worker_threads.js @@ -4,6 +4,7 @@ import init, { SHARE_ENV } from './worker_threads/init.js' import { maybeMakeError } from './ipc.js' import { AsyncResource } from './async/resource.js' import { EventEmitter } from './events.js' +import location from './location.js' import { env } from './process.js' /** @@ -182,7 +183,7 @@ export class Worker extends EventEmitter { options = { ...options } - const url = '/socket/worker_threads/init.js' + const url = new URL('/socket/worker_threads/init.js', location.origin).toString() this.#resource = new AsyncResource('WorkerThread') this.onWorkerMessage = this.onWorkerMessage.bind(this) From 0fe3a5dc8ece1edef33d87f341796407dee333ed Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 26 Aug 2024 16:04:58 +0200 Subject: [PATCH 1167/1178] fix(api/shared-worker/index.js): ensure 'process' is imported --- api/shared-worker/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/shared-worker/index.js b/api/shared-worker/index.js index 5683cf5d8e..95b19b96d3 100644 --- a/api/shared-worker/index.js +++ b/api/shared-worker/index.js @@ -1,6 +1,7 @@ /* global XMLHttpRequest, ErrorEvent */ import application from '../application.js' import location from '../location.js' +import process from '../process.js' import crypto from '../crypto.js' import client from '../application/client.js' import ipc from '../ipc.js' From 576d326d0d69f5c2f19b913cd3138864031af3a2 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 26 Aug 2024 16:05:07 +0200 Subject: [PATCH 1168/1178] chore(api): generate types + docs --- api/README.md | 14 ++--- api/index.d.ts | 139 ++++++++++++++++++++++++------------------------- 2 files changed, 76 insertions(+), 77 deletions(-) diff --git a/api/README.md b/api/README.md index fa4527b133..97c421e502 100644 --- a/api/README.md +++ b/api/README.md @@ -1736,7 +1736,7 @@ This is a `FunctionDeclaration` named `maybeMakeError` in `api/ipc.js`, it's exp This is a `ClassDeclaration` named ``IPCSearchParams` (extends `URLSearchParams`)` in `api/ipc.js`, it's exported but undocumented. -## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1219) +## [`emit(name, value, target, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1218) Emit event to be dispatched on `window` object. @@ -1747,7 +1747,7 @@ Emit event to be dispatched on `window` object. | target | EventTarget | window | true | | | options | Object | | true | | -## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1278) +## [`send(command, value, options)`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1277) Sends an async IPC command request with parameters. @@ -1763,27 +1763,27 @@ Sends an async IPC command request with parameters. | :--- | :--- | :--- | | Not specified | Promise<Result> | | -## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1729) +## [`inflateIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1728) This is a `FunctionDeclaration` named `inflateIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1761) +## [`findIPCMessageTransfers()`](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1760) This is a `FunctionDeclaration` named `findIPCMessageTransfers` in `api/ipc.js`, it's exported but undocumented. -## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1826) +## [ports](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1825) This is a `VariableDeclaration` named `ports` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1828) +## [`IPCMessagePort` (extends `MessagePort`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L1827) This is a `ClassDeclaration` named ``IPCMessagePort` (extends `MessagePort`)` in `api/ipc.js`, it's exported but undocumented. -## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2054) +## [`IPCMessageChannel` (extends `MessageChannel`)](https://github.com/socketsupply/socket/blob/v0.6.0-next/api/ipc.js#L2053) This is a `ClassDeclaration` named ``IPCMessageChannel` (extends `MessageChannel`)` in `api/ipc.js`, it's exported but undocumented. diff --git a/api/index.d.ts b/api/index.d.ts index 3fb9ce277f..ad8a04b0d0 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T>(key: Variable<T>): boolean; + get<T_1>(key: Variable<T_1>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T>(key: Variable<T>, value: T): Mapping; + set<T_2>(key: Variable<T_2>, value: T_2): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T>(key: Variable<T>): Mapping; + delete<T_3>(key: Variable<T_3>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T>(key: Variable<T>): T | undefined; + static get<T_1>(key: Variable<T_1>): T_1; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; + static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T>(revert: Revert<T> | FrozenRevert): void; + static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_1>(): T_1 | undefined; + get<T_2>(): T_2; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F>(fn: F): F; + static wrap<F_1>(fn: F_1): F_1; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent; + prototype: CustomEvent<any>; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent; + prototype: MessageEvent<any>; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1422,7 +1422,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); + constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2189,7 +2189,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): value is symbol; + export function isSymbol(value: any): boolean; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2865,7 +2865,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3106,7 +3106,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): "android" | "android-emulator" | "iphoneos" | iphone; + export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3136,7 +3136,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): signal; + export function getCode(name: string | number): any; /** * Gets the name for a given 'signal' code * @return {string} @@ -3280,7 +3280,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; + getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3661,7 +3661,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: ProcessEnvironment; + export const env: any; export default process; const process: any; } @@ -4553,9 +4553,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; + (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; }; - send: (body?: Document | XMLHttpRequestBodyInit | null) => void; + send: (body?: Document | XMLHttpRequestBodyInit) => void; }; } export class WorkerMetric extends Metric { @@ -5283,7 +5283,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; + static from(stat?: object | Stats, fromBigInt?: any): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6156,7 +6156,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: "utf8" | "buffer"; + encoding: 'utf8' | 'buffer'; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -7000,7 +7000,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "top-level" | "nested"; + get frameType(): "none" | "nested" | "top-level"; /** * The type of the client. * @type {'window'|'worker'} @@ -7032,10 +7032,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: "window" | "worker"; + type?: 'window' | 'worker'; parent?: object | null; top?: object | null; - frameType?: "top-level" | "nested" | "none"; + frameType?: 'top-level' | 'nested' | 'none'; }; } @@ -7109,7 +7109,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; + onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7322,7 +7322,6 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; - import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8047,7 +8046,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8093,9 +8092,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): CoreAsyncResource; - bind(fn: Function, thisArg?: object | undefined): Function; - runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; + emitDestroy(): asyncHooks.CoreAsyncResource; + bind(fn: Function, thisArg?: any): Function; + runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; }; } export namespace Promise { @@ -8715,7 +8714,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9869,7 +9868,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; + static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9944,7 +9943,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: "shared" | "wasm32"; + type?: 'shared' | 'wasm32'; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10370,7 +10369,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: "strict" | "relaxed" | undefined; + durability?: 'strict' | 'relaxed' | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10434,7 +10433,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: "set" | "delete", entry?: object | undefined); + constructor(type: 'set' | 'delete', entry?: object | undefined); entry: any; } /** @@ -10566,7 +10565,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import("socket:service-worker/events").ExtendableEvent); + constructor(event: import('./events.js').ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10607,7 +10606,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import("socket:service-worker/clients").Client>; + client(): Promise<import('./clients.js').Client>; #private; } namespace _default { @@ -10794,7 +10793,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); + constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10829,13 +10828,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; + onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13475,7 +13474,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T>(actual: T, expected: T, msg?: string): void; + notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; /** * @template T * @param {T} actual @@ -13483,7 +13482,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T>(actual: T, expected: T, msg?: string): void; + equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13732,7 +13731,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: { + waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { /** * - The text to wait for */ @@ -13743,7 +13742,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; + }, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15024,7 +15023,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; + resolve(pathname: string | URL, options?: PackageResolveOptions): string; #private; } export default Package; @@ -15035,13 +15034,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; prefix?: string; }; export type ParsedPackageName = { @@ -15081,7 +15080,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15441,7 +15440,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions | undefined): any; + require(url: any, options?: RequireOptions): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15479,9 +15478,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; + import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; - import process from "socket:process"; } declare module "socket:commonjs/require" { @@ -15540,7 +15539,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import("socket:commonjs/module").Module); + constructor(module: import('./module.js').Module); /** * The referrer (parent) of this module. * @type {string} @@ -16100,7 +16099,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; + static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; /** * `Notification` class constructor. * @param {string} title @@ -16286,9 +16285,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; }; }; export default createServiceWorker; @@ -17004,7 +17003,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -17024,7 +17023,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17046,8 +17045,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: "read" | "readwrite"; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + mode?: 'read' | 'readwrite'; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; }; /** * ]?: string[] @@ -17057,10 +17056,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; + startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; types?: Array<{ description?: string; - [keyof]: any; + [keyof]; }>; }; } @@ -17161,7 +17160,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: "commonjs" | "module"; + type?: 'commonjs' | 'module'; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17170,7 +17169,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: "commonjs" | "module"; + type: 'commonjs' | 'module'; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17251,8 +17250,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17266,7 +17265,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; + export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17716,12 +17715,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; + export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends Harness> { + export class TapeHarness<T extends exports.Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17774,7 +17773,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends Harness> = { + export type TapeTestFn<T extends exports.Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17791,8 +17790,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): object is ArrayBuffer; -declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): boolean; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; From 33242a156d2b66a193be6123a303b865653420c6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Fri, 30 Aug 2024 20:46:34 +0200 Subject: [PATCH 1169/1178] fix(cli): fix zip package paths for linux --- src/cli/cli.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cli/cli.cc b/src/cli/cli.cc index c546ae45af..658ff01042 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2560,8 +2560,8 @@ int main (int argc, char* argv[]) { if (flagVerboseMode) { log(r.output); } - } else { log("ERROR: Unable to determine macOS application or package to install."); + } else { exit(1); } } else if (platform.linux) { @@ -6194,14 +6194,16 @@ int main (int argc, char* argv[]) { zipCommand << "zip -r" - << " " << (paths.platformSpecificOutputPath / (settings["build_name"] + ".zip")).string() - << " " << pathResourcesRelativeToUserBuild.string() + << " " << (paths.platformSpecificOutputPath / (paths.pathPackage.filename().string() + ".zip")).string() + << " " << paths.pathPackage.filename() ; if (debugEnv || verboseEnv) { log(zipCommand.str()); } + auto cwd = fs::current_path(); + fs::current_path(paths.platformSpecificOutputPath); auto r = exec(zipCommand.str()); if (r.exitCode != 0) { @@ -6211,6 +6213,7 @@ int main (int argc, char* argv[]) { } exit(r.exitCode); } + fs::current_path(cwd); } else { log("ERROR: Unknown package format given in '[build.linux.package] format = \"" + packageFormat + "\""); exit(1); From f23d919698b59965b97e8e44235158cc8cd32a4e Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 2 Sep 2024 22:34:00 +0200 Subject: [PATCH 1170/1178] fix(ipc/preload): verify origin before doing some preload calls --- src/ipc/preload.cc | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index b7642b530c..b04acee321 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -273,7 +273,7 @@ namespace SSC::IPC { configurable: false, enumerable: true, writable: false, - value: globalThis.parent !== globalThis + value: globalThis.parent !== globalThis && globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) ? globalThis.parent?.__args?.client ?? null : null }, @@ -429,7 +429,11 @@ namespace SSC::IPC { // 10. compile listeners for `globalThis` buffers.push_back(R"JAVASCRIPT( - if (globalThis.document && !globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG) { + if ( + globalThis.document && + !globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG && + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) + ) { Object.defineProperties(globalThis, { RUNTIME_APPLICATION_URL_EVENT_BACKLOG: { configurable: false, @@ -503,8 +507,10 @@ namespace SSC::IPC { if (this->options.features.useHTMLMarkup && this->options.features.useESM) { buffers.push_back(tmpl( R"JAVASCRIPT( - import 'socket:internal/init' - {{userScript}} + if (globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) { + await import('socket:internal/init') + {{userScript}} + } )JAVASCRIPT", Map {{"userScript", this->options.userScript}} )); @@ -516,7 +522,11 @@ namespace SSC::IPC { {{userScript}} } - if (globalThis.document && globalThis.document.readyState !== 'complete') { + if ( + globalThis.document && + globalThis.document.readyState !== 'complete' && + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) + ) { globalThis.document.addEventListener('readystatechange', () => { if(/interactive|complete/.test(globalThis.document.readyState)) { import('socket:internal/init') @@ -524,7 +534,7 @@ namespace SSC::IPC { .catch(console.error) } }) - } else { + } else if (globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier)) { import('socket:internal/init') .then(userScriptCallback) .catch(console.error) From d8dcbc449af59b0ac44d70635a8161805342bd57 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Mon, 2 Sep 2024 22:38:27 +0200 Subject: [PATCH 1171/1178] refactor(ipc/preload): check client dev host --- src/ipc/preload.cc | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index b04acee321..5136cc3605 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -273,7 +273,11 @@ namespace SSC::IPC { configurable: false, enumerable: true, writable: false, - value: globalThis.parent !== globalThis && globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) + value: + globalThis.parent !== globalThis && ( + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || + globalThis.origin.includes(globalThis.__args.client.host) + ) ? globalThis.parent?.__args?.client ?? null : null }, @@ -432,7 +436,10 @@ namespace SSC::IPC { if ( globalThis.document && !globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG && - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) + ( + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || + globalThis.origin.includes(globalThis.__args.client.host) + ) ) { Object.defineProperties(globalThis, { RUNTIME_APPLICATION_URL_EVENT_BACKLOG: { @@ -507,7 +514,10 @@ namespace SSC::IPC { if (this->options.features.useHTMLMarkup && this->options.features.useESM) { buffers.push_back(tmpl( R"JAVASCRIPT( - if (globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) { + if ( + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier || + globalThis.origin.includes(globalThis.__args.client.host) + ) { await import('socket:internal/init') {{userScript}} } @@ -525,7 +535,10 @@ namespace SSC::IPC { if ( globalThis.document && globalThis.document.readyState !== 'complete' && - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) + ( + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || + globalThis.origin.includes(globalThis.__args.client.host) + ) ) { globalThis.document.addEventListener('readystatechange', () => { if(/interactive|complete/.test(globalThis.document.readyState)) { @@ -534,7 +547,10 @@ namespace SSC::IPC { .catch(console.error) } }) - } else if (globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier)) { + } else if ( + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || + globalThis.origin.includes(globalThis.__args.client.host) + ) { import('socket:internal/init') .then(userScriptCallback) .catch(console.error) From fdef531d942e68d855bf7d3ef2c166672dbbd98e Mon Sep 17 00:00:00 2001 From: heapwolf <paolo@socketsupply.co> Date: Mon, 2 Sep 2024 22:51:25 +0200 Subject: [PATCH 1172/1178] fix(preload): fix missing paren --- src/ipc/preload.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 5136cc3605..9d015f86ff 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -515,7 +515,7 @@ namespace SSC::IPC { buffers.push_back(tmpl( R"JAVASCRIPT( if ( - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier || + globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || globalThis.origin.includes(globalThis.__args.client.host) ) { await import('socket:internal/init') From 3fde2cb5ae9785f9a0b867572e94bdf0fb058fd6 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 3 Sep 2024 13:18:58 +0200 Subject: [PATCH 1173/1178] fix(api/commonjs/loader.js): remove 'withCredentials' --- api/commonjs/loader.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 05404da8ee..3e118006f2 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -226,10 +226,6 @@ export class RequestStatus { request.open('HEAD', this.#request.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') - if (os.platform() !== 'android') { - request.withCredentials = true - } - if (globalThis.isServiceWorkerScope) { request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') } @@ -462,10 +458,6 @@ export class Request { request.open('GET', this.id, false) request.setRequestHeader(RUNTIME_REQUEST_SOURCE_HEADER, 'module') - if (os.platform() !== 'android') { - request.withCredentials = true - } - if (globalThis.isServiceWorkerScope) { request.setRequestHeader(RUNTIME_SERVICE_WORKER_FETCH_MODE, 'ignore') } From 5942876f99d174aa5d84c716bf887df4bd242e76 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 3 Sep 2024 13:19:18 +0200 Subject: [PATCH 1174/1178] fix(api/internal/serialize.js): track 'seen' later --- api/internal/serialize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/serialize.js b/api/internal/serialize.js index ddec16e158..8ffc33e2f2 100644 --- a/api/internal/serialize.js +++ b/api/internal/serialize.js @@ -25,7 +25,6 @@ function map (object, callback, seen = new Set()) { return object } - seen.add(object) if (Array.isArray(object)) { for (let i = 0; i < object.length; ++i) { object[i] = map(object[i], callback, seen) @@ -40,6 +39,7 @@ function map (object, callback, seen = new Set()) { } } + seen.add(object) if (object && typeof object === 'object') { return callback(object) } else { From 2c21d375a5d1d7ad0c79af2079399164171e5947 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 3 Sep 2024 13:19:39 +0200 Subject: [PATCH 1175/1178] refactor(core): set 'isPaused = false' in 'runEventLoop' too --- src/core/core.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/core.cc b/src/core/core.cc index 63d294728e..f864727bd4 100644 --- a/src/core/core.cc +++ b/src/core/core.cc @@ -451,6 +451,7 @@ namespace SSC { } this->isLoopRunning = true; + this->isPaused = false; this->initEventLoop(); this->dispatchEventLoop([=, this]() { From 7517c7d2147065b4ee710e8326a251b5ca2ecc50 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 3 Sep 2024 13:19:54 +0200 Subject: [PATCH 1176/1178] chore(desktop/extension/linux): clean up --- src/desktop/extension/linux.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/desktop/extension/linux.cc b/src/desktop/extension/linux.cc index b30e329776..2656928dba 100644 --- a/src/desktop/extension/linux.cc +++ b/src/desktop/extension/linux.cc @@ -276,7 +276,10 @@ extern "C" { uv_chdir(cwd.c_str()); } - static App app(App::DEFAULT_INSTANCE_ID, std::move(std::make_shared<Core>(options))); + static App app( + App::DEFAULT_INSTANCE_ID, + std::move(std::make_shared<Core>(options)) + ); } const unsigned char* socket_runtime_init_get_user_config_bytes () { From a04459263f759b0eed0221c36cde5f0ca6542b47 Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 3 Sep 2024 13:30:53 +0200 Subject: [PATCH 1177/1178] fix(ipc): fix '__args.client.parent' security error --- src/ipc/preload.cc | 52 +++++++++++++++------------------------------- src/ipc/routes.cc | 1 + 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/src/ipc/preload.cc b/src/ipc/preload.cc index 9d015f86ff..1b9177a1ad 100644 --- a/src/ipc/preload.cc +++ b/src/ipc/preload.cc @@ -272,14 +272,18 @@ namespace SSC::IPC { parent: { configurable: false, enumerable: true, - writable: false, - value: - globalThis.parent !== globalThis && ( + get: () => { + if ( globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || - globalThis.origin.includes(globalThis.__args.client.host) - ) - ? globalThis.parent?.__args?.client ?? null - : null + globalThis.origin.includes(globalThis.__args.client.host + ':' + globalThis.__args.client.port) + ) { + return globalThis.parent !== globalThis + ? globalThis.parent?.__args?.client ?? null + : null + } + + return null + } }, top: { configurable: false, @@ -433,14 +437,7 @@ namespace SSC::IPC { // 10. compile listeners for `globalThis` buffers.push_back(R"JAVASCRIPT( - if ( - globalThis.document && - !globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG && - ( - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || - globalThis.origin.includes(globalThis.__args.client.host) - ) - ) { + if (globalThis.document && !globalThis.RUNTIME_APPLICATION_URL_EVENT_BACKLOG) { Object.defineProperties(globalThis, { RUNTIME_APPLICATION_URL_EVENT_BACKLOG: { configurable: false, @@ -514,13 +511,8 @@ namespace SSC::IPC { if (this->options.features.useHTMLMarkup && this->options.features.useESM) { buffers.push_back(tmpl( R"JAVASCRIPT( - if ( - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || - globalThis.origin.includes(globalThis.__args.client.host) - ) { - await import('socket:internal/init') - {{userScript}} - } + import 'socket:internal/init' + {{userScript}} )JAVASCRIPT", Map {{"userScript", this->options.userScript}} )); @@ -532,14 +524,7 @@ namespace SSC::IPC { {{userScript}} } - if ( - globalThis.document && - globalThis.document.readyState !== 'complete' && - ( - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || - globalThis.origin.includes(globalThis.__args.client.host) - ) - ) { + if (globalThis.document && globalThis.document.readyState !== 'complete') { globalThis.document.addEventListener('readystatechange', () => { if(/interactive|complete/.test(globalThis.document.readyState)) { import('socket:internal/init') @@ -547,10 +532,7 @@ namespace SSC::IPC { .catch(console.error) } }) - } else if ( - globalThis.origin.includes(globalThis.__args.config.meta_bundle_identifier) || - globalThis.origin.includes(globalThis.__args.client.host) - ) { + } else { import('socket:internal/init') .then(userScriptCallback) .catch(console.error) @@ -577,7 +559,7 @@ namespace SSC::IPC { } buffers.push_back(R"JAVASCRIPT( - if (globalThis.document && !globalThis.module) { + if (false && globalThis.document && !globalThis.module) { ;(async function GlobalCommonJSScope () { const globals = await import('socket:internal/globals') await globals.get('RuntimeReadyPromise') diff --git a/src/ipc/routes.cc b/src/ipc/routes.cc index e557b90985..5e705ad11d 100644 --- a/src/ipc/routes.cc +++ b/src/ipc/routes.cc @@ -3169,6 +3169,7 @@ static void mapIPCRoutes (Router *router) { if (options.index >= SOCKET_RUNTIME_MAX_WINDOWS) { options.features.useGlobalCommonJS = false; + options.features.useGlobalNodeJS = false; } auto createdWindow = app->windowManager.createWindow(options); From 8f9977604cddb93f7cf4da5663921afd06c391cc Mon Sep 17 00:00:00 2001 From: Joseph Werle <joseph.werle@gmail.com> Date: Tue, 3 Sep 2024 13:31:43 +0200 Subject: [PATCH 1178/1178] chore(api): generate types --- api/commonjs/loader.js | 1 - api/index.d.ts | 139 +++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/api/commonjs/loader.js b/api/commonjs/loader.js index 3e118006f2..93748feb14 100644 --- a/api/commonjs/loader.js +++ b/api/commonjs/loader.js @@ -10,7 +10,6 @@ import { Headers } from '../ipc.js' import location from '../location.js' import path from '../path.js' import URL from '../url.js' -import os from '../os.js' import fs from '../fs.js' const RUNTIME_SERVICE_WORKER_FETCH_MODE = 'Runtime-ServiceWorker-Fetch-Mode' diff --git a/api/index.d.ts b/api/index.d.ts index ad8a04b0d0..3fb9ce277f 100644 --- a/api/index.d.ts +++ b/api/index.d.ts @@ -127,7 +127,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {boolean} */ - get<T_1>(key: Variable<T_1>): boolean; + get<T>(key: Variable<T>): boolean; /** * Sets an `AsyncContext.Variable` value at `key`. If the `Mapping` is frozen, * then a "forked" (new) instance with the value set on it is returned, @@ -137,7 +137,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - set<T_2>(key: Variable<T_2>, value: T_2): Mapping; + set<T>(key: Variable<T>, value: T): Mapping; /** * Delete an `AsyncContext.Variable` value at `key`. * If the `Mapping` is frozen, then a "forked" (new) instance is returned, @@ -147,7 +147,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Mapping} */ - delete<T_3>(key: Variable<T_3>): Mapping; + delete<T>(key: Variable<T>): Mapping; #private; } /** @@ -177,7 +177,7 @@ declare module "socket:async/context" { * @param {Variable<T>} key * @return {T|undefined} */ - static get<T_1>(key: Variable<T_1>): T_1; + static get<T>(key: Variable<T>): T | undefined; /** * Set updates the `AsyncContext.Variable` with a new value and returns a * revert action that allows the modification to be reversed in the future. @@ -186,7 +186,7 @@ declare module "socket:async/context" { * @param {T} value * @return {Revert<T>|FrozenRevert} */ - static set<T_2>(key: Variable<T_2>, value: T_2): FrozenRevert | Revert<T_2>; + static set<T>(key: Variable<T>, value: T): Revert<T> | FrozenRevert; /** * "Freezes" the current storage `Mapping`, and returns a new `FrozenRevert` * or `Revert` which can restore the storage state to the state at @@ -200,7 +200,7 @@ declare module "socket:async/context" { * @template T * @param {Revert<T>|FrozenRevert} revert */ - static restore<T_3>(revert: FrozenRevert | Revert<T_3>): void; + static restore<T>(revert: Revert<T> | FrozenRevert): void; /** * Switches storage `Mapping` state to the state at the time of a * "snapshot". @@ -254,7 +254,7 @@ declare module "socket:async/context" { * @template T * @return {T|undefined} */ - get<T_2>(): T_2; + get<T_1>(): T_1 | undefined; #private; } /** @@ -282,7 +282,7 @@ declare module "socket:async/context" { * @param {F} fn * @returns {F} */ - static wrap<F_1>(fn: F_1): F_1; + static wrap<F>(fn: F): F; /** * Runs the given function `fn` with arguments `args`, using a `null` * context and the current snapshot. @@ -340,7 +340,7 @@ declare module "socket:events" { }; export const CustomEvent: { new <T>(type: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>; - prototype: CustomEvent<any>; + prototype: CustomEvent; } | { new (type: any, options: any): { "__#7@#detail": any; @@ -349,7 +349,7 @@ declare module "socket:events" { }; export const MessageEvent: { new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; - prototype: MessageEvent<any>; + prototype: MessageEvent; } | { new (type: any, options: any): { "__#8@#detail": any; @@ -1422,7 +1422,7 @@ declare module "socket:errors" { * `ErrnoError` class constructor. * @param {import('./errno').errno|string} code */ - constructor(code: import('./errno').errno | string, message?: any, ...args: any[]); + constructor(code: import("socket:errno").errno | string, message?: any, ...args: any[]); get name(): string; get code(): number; #private; @@ -2189,7 +2189,7 @@ declare module "socket:util" { export function isTypedArray(object: any): boolean; export function isArrayLike(input: any): boolean; export function isError(object: any): boolean; - export function isSymbol(value: any): boolean; + export function isSymbol(value: any): value is symbol; export function isNumber(value: any): boolean; export function isBoolean(value: any): boolean; export function isArrayBufferView(buf: any): boolean; @@ -2865,7 +2865,7 @@ declare module "socket:internal/events" { * @param {object=} [data] * @param {import('../application/menu.js').Menu} menu */ - constructor(type?: string | undefined, data?: object | undefined, menu?: import('../application/menu.js').Menu); + constructor(type?: string | undefined, data?: object | undefined, menu?: import("socket:application/menu").Menu); /** * The `Menu` this event has been dispatched for. * @type {import('../application/menu.js').Menu?} @@ -3106,7 +3106,7 @@ declare module "socket:os" { * @ignore * @return {'android'|'android-emulator'|'iphoneos'|iphone-simulator'|'linux'|'macosx'|unix'|unknown'|win32'} */ - export function host(): 'android' | 'android-emulator' | 'iphoneos' | iphone; + export function host(): "android" | "android-emulator" | "iphoneos" | iphone; /** * Returns the home directory of the current user. * @return {string} @@ -3136,7 +3136,7 @@ declare module "socket:process/signal" { * @param {string|number} name * @return {signal} */ - export function getCode(name: string | number): any; + export function getCode(name: string | number): signal; /** * Gets the name for a given 'signal' code * @return {string} @@ -3280,7 +3280,7 @@ declare module "socket:internal/streams/web" { constructor(e?: {}, t?: {}); get locked(): boolean; cancel(e?: any): any; - getReader(e?: any): ReadableStreamDefaultReader | ReadableStreamBYOBReader; + getReader(e?: any): ReadableStreamBYOBReader | ReadableStreamDefaultReader; pipeThrough(e: any, t?: {}): any; pipeTo(e: any, t?: {}): any; tee(): any; @@ -3661,7 +3661,7 @@ declare module "socket:process" { export class ProcessEnvironment extends EventTarget { get [Symbol.toStringTag](): string; } - export const env: any; + export const env: ProcessEnvironment; export default process; const process: any; } @@ -4553,9 +4553,9 @@ declare module "socket:diagnostics/window" { patched: { open: { (method: string, url: string | URL): void; - (method: string, url: string | URL, async: boolean, username?: string, password?: string): void; + (method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void; }; - send: (body?: Document | XMLHttpRequestBodyInit) => void; + send: (body?: Document | XMLHttpRequestBodyInit | null) => void; }; } export class WorkerMetric extends Metric { @@ -5283,7 +5283,7 @@ declare module "socket:fs/stats" { * @param {fromBigInt=} [fromBigInt = false] * @return {Stats} */ - static from(stat?: object | Stats, fromBigInt?: any): Stats; + static from(stat?: object | Stats, fromBigInt?: any | undefined): Stats; /** * `Stats` class constructor. * @param {object|Stats} stat @@ -6156,7 +6156,7 @@ declare module "socket:fs/watcher" { * The encoding of the `filename` * @type {'utf8'|'buffer'} */ - encoding: 'utf8' | 'buffer'; + encoding: "utf8" | "buffer"; /** * A `AbortController` `AbortSignal` for async aborts. * @type {AbortSignal?} @@ -7000,7 +7000,7 @@ declare module "socket:application/client" { * The frame type of the client. * @type {'top-level'|'nested'|'none'} */ - get frameType(): "none" | "nested" | "top-level"; + get frameType(): "none" | "top-level" | "nested"; /** * The type of the client. * @type {'window'|'worker'} @@ -7032,10 +7032,10 @@ declare module "socket:application/client" { export default _default; export type ClientState = { id?: string | null; - type?: 'window' | 'worker'; + type?: "window" | "worker"; parent?: object | null; top?: object | null; - frameType?: 'top-level' | 'nested' | 'none'; + frameType?: "top-level" | "nested" | "none"; }; } @@ -7109,7 +7109,7 @@ declare module "socket:window/hotkey" { * @ignore * @param {import('../internal/events.js').HotKeyEvent} event */ - onHotKey(event: import('../internal/events.js').HotKeyEvent): boolean; + onHotKey(event: import("socket:internal/events").HotKeyEvent): boolean; /** * The number of `Binding` instances in the mapping. * @type {number} @@ -7322,6 +7322,7 @@ declare module "socket:window/hotkey" { */ export const bindings: Bindings; export default bindings; + import { HotKeyEvent } from "socket:internal/events"; } declare module "socket:window" { @@ -8046,7 +8047,7 @@ declare module "socket:internal/promise" { export const NativePromise: PromiseConstructor; export namespace NativePromisePrototype { export let then: <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => globalThis.Promise<TResult1 | TResult2>; - let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any>; + let _catch: <TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>) => globalThis.Promise<any | TResult>; export { _catch as catch }; let _finally: (onfinally?: () => void) => globalThis.Promise<any>; export { _finally as finally }; @@ -8092,9 +8093,9 @@ declare module "socket:internal/promise" { readonly destroyed: boolean; asyncId(): number; triggerAsyncId(): number; - emitDestroy(): asyncHooks.CoreAsyncResource; - bind(fn: Function, thisArg?: any): Function; - runInAsyncScope(fn: Function, thisArg?: any, ...args?: any[]): any; + emitDestroy(): CoreAsyncResource; + bind(fn: Function, thisArg?: object | undefined): Function; + runInAsyncScope(fn: Function, thisArg?: object | undefined, ...args?: any[]): any; }; } export namespace Promise { @@ -8714,7 +8715,7 @@ declare module "socket:worker_threads" { * @ignore * @param {import('./process.js').ProcessEnvironmentEvent} event */ - onProcessEnvironmentEvent(event: import('./process.js').ProcessEnvironmentEvent): void; + onProcessEnvironmentEvent(event: import("socket:process").ProcessEnvironmentEvent): void; /** * The unique ID for this `Worker` thread instace. * @type {number} @@ -9868,7 +9869,7 @@ declare module "socket:extension" { * @param {string} name * @return {Promise<'shared'|'wasm32'|'unknown'|null>} */ - static type(name: string): Promise<'shared' | 'wasm32' | 'unknown' | null>; + static type(name: string): Promise<"shared" | "wasm32" | "unknown" | null>; /** * Provides current stats about the loaded extensions or one by name. * @param {?string} name @@ -9943,7 +9944,7 @@ declare module "socket:extension" { export type ExtensionLoadOptions = { allow: string[] | string; imports?: object; - type?: 'shared' | 'wasm32'; + type?: "shared" | "wasm32"; path?: string; stats?: object; instance?: WebAssembly.Instance; @@ -10369,7 +10370,7 @@ declare module "socket:internal/database" { export type DatabasePutOptions = { store?: string | undefined; stores?: string[] | undefined; - durability?: 'strict' | 'relaxed' | undefined; + durability?: "strict" | "relaxed" | undefined; }; /** * A typed container for various optional options made to a `delete()` function @@ -10433,7 +10434,7 @@ declare module "socket:service-worker/env" { * @param {'set'|'delete'} type * @param {object=} [entry] */ - constructor(type: 'set' | 'delete', entry?: object | undefined); + constructor(type: "set" | "delete", entry?: object | undefined); entry: any; } /** @@ -10565,7 +10566,7 @@ declare module "socket:service-worker/context" { * `Context` class constructor. * @param {import('./events.js').ExtendableEvent} event */ - constructor(event: import('./events.js').ExtendableEvent); + constructor(event: import("socket:service-worker/events").ExtendableEvent); /** * Context data. This may be a custom protocol handler scheme data * by default, if available. @@ -10606,7 +10607,7 @@ declare module "socket:service-worker/context" { * Gets the client for this event context. * @return {Promise<import('./clients.js').Client>} */ - client(): Promise<import('./clients.js').Client>; + client(): Promise<import("socket:service-worker/clients").Client>; #private; } namespace _default { @@ -10793,7 +10794,7 @@ declare module "socket:http/adapters" { * @param {import('../http.js').Server} server * @param {HTTPModuleInterface} httpInterface */ - constructor(server: import('../http.js').Server, httpInterface: HTTPModuleInterface); + constructor(server: import("socket:http").Server, httpInterface: HTTPModuleInterface); /** * A readonly reference to the underlying HTTP(S) server * for this adapter. @@ -10828,13 +10829,13 @@ declare module "socket:http/adapters" { * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onInstall(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onInstall(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'activate' service worker event. * @ignore * @param {import('../service-worker/events.js').ExtendableEvent} event */ - onActivate(event: import('../service-worker/events.js').ExtendableEvent): Promise<void>; + onActivate(event: import("socket:service-worker/events").ExtendableEvent): Promise<void>; /** * Handles the 'fetch' service worker event. * @ignore @@ -13474,7 +13475,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - notDeepEqual<T_1>(actual: T_1, expected: T_1, msg?: string): void; + notDeepEqual<T>(actual: T, expected: T, msg?: string): void; /** * @template T * @param {T} actual @@ -13482,7 +13483,7 @@ declare module "socket:test/index" { * @param {string} [msg] * @returns {void} */ - equal<T_2>(actual: T_2, expected: T_2, msg?: string): void; + equal<T>(actual: T, expected: T, msg?: string): void; /** * @param {unknown} actual * @param {unknown} expected @@ -13731,7 +13732,7 @@ declare module "socket:test/index" { * }) * ``` */ - waitForText(selector: string | HTMLElement | Element, opts?: string | RegExp | { + waitForText(selector: string | HTMLElement | Element, opts?: { /** * - The text to wait for */ @@ -13742,7 +13743,7 @@ declare module "socket:test/index" { * The regex to wait for */ regex?: RegExp; - }, msg?: string): Promise<HTMLElement | Element | void>; + } | string | RegExp, msg?: string): Promise<HTMLElement | Element | void>; /** * Run a querySelector as an assert and also get the results * @@ -15023,7 +15024,7 @@ declare module "socket:commonjs/package" { * @param {PackageResolveOptions=} [options] * @return {string} */ - resolve(pathname: string | URL, options?: PackageResolveOptions): string; + resolve(pathname: string | URL, options?: PackageResolveOptions | undefined): string; #private; } export default Package; @@ -15034,13 +15035,13 @@ declare module "socket:commonjs/package" { version?: string; license?: string; exports?: object; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; info?: object; origin?: string; dependencies?: Dependencies | object | Map<any, any>; }; export type PackageLoadOptions = import("socket:commonjs/loader").RequestOptions & { - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; prefix?: string; }; export type ParsedPackageName = { @@ -15080,7 +15081,7 @@ declare module "socket:commonjs/module" { * @param {typeof process} process * @param {object} global */ - export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: typeof process, global: object): void; + export function CommonJSModuleScope(exports: object, require: (arg0: string) => any, module: Module, __filename: string, __dirname: string, process: any, global: object): void; /** * Creates a `require` function from a given module URL. * @param {string|URL} url @@ -15440,7 +15441,7 @@ declare module "socket:commonjs/module" { * @throws TypeError * @return {any} */ - require(url: any, options?: RequireOptions): any; + require(url: any, options?: RequireOptions | undefined): any; /** * Loads the module * @param {ModuleLoadOptions=} [options] @@ -15478,9 +15479,9 @@ declare module "socket:commonjs/module" { export type ModuleLoadOptions = { extensions?: object; }; - import process from "socket:process"; import { Package } from "socket:commonjs/package"; import { Loader } from "socket:commonjs/loader"; + import process from "socket:process"; } declare module "socket:commonjs/require" { @@ -15539,7 +15540,7 @@ declare module "socket:commonjs/require" { * `Meta` class constructor. * @param {import('./module.js').Module} module */ - constructor(module: import('./module.js').Module); + constructor(module: import("socket:commonjs/module").Module); /** * The referrer (parent) of this module. * @type {string} @@ -16099,7 +16100,7 @@ declare module "socket:notification" { * @param {boolean=} [options.force = false] * @return {Promise<'granted'|'default'|'denied'>} */ - static requestPermission(options?: object | undefined): Promise<'granted' | 'default' | 'denied'>; + static requestPermission(options?: object | undefined): Promise<"granted" | "default" | "denied">; /** * `Notification` class constructor. * @param {string} title @@ -16285,9 +16286,9 @@ declare module "socket:service-worker/instance" { readonly state: any; readonly scriptURL: any; postMessage(): void; - addEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }; }; export default createServiceWorker; @@ -17003,7 +17004,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker} * @return {Promise<FileSystemFileHandle[]>} */ - export function showOpenFilePicker(options?: ShowOpenFilePickerOptions): Promise<FileSystemFileHandle[]>; + export function showOpenFilePicker(options?: ShowOpenFilePickerOptions | undefined): Promise<FileSystemFileHandle[]>; /** * @typedef {{ * id?: string, @@ -17023,7 +17024,7 @@ declare module "socket:internal/pickers" { * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker} * @return {Promise<FileSystemHandle>} */ - export function showSaveFilePicker(options?: ShowSaveFilePickerOptions): Promise<FileSystemHandle>; + export function showSaveFilePicker(options?: ShowSaveFilePickerOptions | undefined): Promise<FileSystemHandle>; /** * Key-value store for general usage by the file pickers" * @ignore @@ -17045,8 +17046,8 @@ declare module "socket:internal/pickers" { export default _default; export type ShowDirectoryPickerOptions = { id?: string; - mode?: 'read' | 'readwrite'; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + mode?: "read" | "readwrite"; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; }; /** * ]?: string[] @@ -17056,10 +17057,10 @@ declare module "socket:internal/pickers" { export type object = { id?: string; excludeAcceptAllOption?: boolean; - startIn?: FileSystemHandle | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos'; + startIn?: FileSystemHandle | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; types?: Array<{ description?: string; - [keyof]; + [keyof]: any; }>; }; } @@ -17160,7 +17161,7 @@ declare module "socket:npm/module" { */ export function resolve(specifier: string | URL, origin?: (string | URL) | undefined, options?: { prefix?: string; - type?: 'commonjs' | 'module'; + type?: "commonjs" | "module"; }): ModuleResolution | null; namespace _default { export { resolve }; @@ -17169,7 +17170,7 @@ declare module "socket:npm/module" { export type ModuleResolution = { package: Package; origin: string; - type: 'commonjs' | 'module'; + type: "commonjs" | "module"; url: string; }; import { Package } from "socket:commonjs/package"; @@ -17250,8 +17251,8 @@ declare module "socket:service-worker/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array; @@ -17265,7 +17266,7 @@ declare module "socket:service-worker/storage" { * @param {'memoryStorage'|'localStorage'|'sessionStorage'} type * @return {Promise<Storage>} */ - export function createStorageInterface(type: 'memoryStorage' | 'localStorage' | 'sessionStorage'): Promise<Storage>; + export function createStorageInterface(type: "memoryStorage" | "localStorage" | "sessionStorage"): Promise<Storage>; /** * @typedef {{ done: boolean, value: string | undefined }} IndexIteratorResult */ @@ -17715,12 +17716,12 @@ declare module "socket:test/harness" { * @param {new (options: object) => T} harnessClass * @returns {TapeTestFn<T>} */ - export function wrapHarness<T extends exports.Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): exports.TapeTestFn<T>; + export function wrapHarness<T extends Harness>(tapzero: typeof import("socket:test/index"), harnessClass: new (options: object) => T): TapeTestFn<T>; export default exports; /** * @template {Harness} T */ - export class TapeHarness<T extends exports.Harness> { + export class TapeHarness<T extends Harness> { /** * @param {import('./index.js')} tapzero * @param {new (options: object) => T} harnessClass @@ -17773,7 +17774,7 @@ declare module "socket:test/harness" { bootstrap(): Promise<void>; close(): Promise<void>; }; - export type TapeTestFn<T extends exports.Harness> = { + export type TapeTestFn<T extends Harness> = { (name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; (name: string, opts: object, cb: (harness: T, test: Test) => (void | Promise<void>)): void; only(name: string, cb?: (harness: T, test: Test) => (void | Promise<void>)): void; @@ -17790,8 +17791,8 @@ declare module "socket:vm/init" { } declare function isTypedArray(object: any): boolean; declare function isTypedArray(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; -declare function isArrayBuffer(object: any): boolean; +declare function isArrayBuffer(object: any): object is ArrayBuffer; +declare function isArrayBuffer(object: any): object is ArrayBuffer; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare function findMessageTransfers(transfers: any, object: any, options?: any): any; declare const Uint8ArrayPrototype: Uint8Array;