diff --git a/assets/jsutils/README.md b/assets/jsutils/README.md deleted file mode 100644 index 5a441965dbd50e24af483de555e5695df44c5472..0000000000000000000000000000000000000000 --- a/assets/jsutils/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This directory stores javascript utility files for xxdk-wasm. These -include wasm worker loaders and golang's wasm_exec.js. diff --git a/assets/jsutils/channelsIndexedDbWorker.js b/assets/jsutils/channelsIndexedDbWorker.js deleted file mode 100644 index 5a4595fe7ae3fe83e45782741d1ec118a8c1b50f..0000000000000000000000000000000000000000 --- a/assets/jsutils/channelsIndexedDbWorker.js +++ /dev/null @@ -1,31 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -// NOTE: wasm_exec.js must always be in the same directory as this script. -importScripts('./wasm_exec.js'); -// NOTE: This relative path must be preserved in distribution. -const binPath = require('../wasm/xxdk-channelsIndexedDbWorker.wasm'); - -const isReady = new Promise((resolve) => { - self.onWasmInitialized = resolve; -}); - -const go = new Go(); -go.argv = [ - '--logLevel=2', - '--threadLogLevel=2', -] -// NOTE: This is wonky, we are in the right path inside the publicPath -// (usually `dist` folder), which is where webpack paths start. They also -// always prefix with a /, so we are going up a directory to come right back -// into e.g., ../dist/assets/wasm/[somefile].wasm -WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => { - go.run(result.instance); - await isReady; -}).catch((err) => { - console.error(err); -}); diff --git a/assets/jsutils/dmIndexedDbWorker.js b/assets/jsutils/dmIndexedDbWorker.js deleted file mode 100644 index 3da9d3704dcbcca2dc59b357f5efc5ed54a5d4d1..0000000000000000000000000000000000000000 --- a/assets/jsutils/dmIndexedDbWorker.js +++ /dev/null @@ -1,32 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -// NOTE: wasm_exec.js must always be in the same directory as this script. -importScripts('./wasm_exec.js'); -// NOTE: This relative path must be preserved in distribution. -const binPath = require('../wasm/xxdk-dmIndexedDbWorker.wasm'); - -const isReady = new Promise((resolve) => { - self.onWasmInitialized = resolve; -}); - -const go = new Go(); -go.argv = [ - '--logLevel=2', - '--threadLogLevel=2', -] -// NOTE: This is wonky, we are in the right path inside the publicPath -// (usually `dist` folder), which is where webpack paths start. They also -// always prefix with a /, so we are going up a directory to come right back -// into e.g., ../dist/assets/wasm/[somefile].wasm -console.log("WORKERWASM: " + binPath); -WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => { - go.run(result.instance); - await isReady; -}).catch((err) => { - console.error(err); -}); diff --git a/assets/jsutils/logFileWorker.js b/assets/jsutils/logFileWorker.js deleted file mode 100644 index ea466ed4dc6498de21f3be08a523db9f54edda0e..0000000000000000000000000000000000000000 --- a/assets/jsutils/logFileWorker.js +++ /dev/null @@ -1,27 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -// NOTE: wasm_exec.js must always be in the same directory as this script. -importScripts('./wasm_exec.js'); -// NOTE: This relative path must be preserved in distribution. -const binPath = require('../wasm/xxdk-logFileWorker.wasm'); - -const isReady = new Promise((resolve) => { - self.onWasmInitialized = resolve; -}); - -const go = new Go(); -// NOTE: This is wonky, we are in the right path inside the publicPath -// (usually `dist` folder), which is where webpack paths start. They also -// always prefix with a /, so we are going up a directory to come right back -// into e.g., ../dist/assets/wasm/[somefile].wasm -WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => { - go.run(result.instance); - await isReady; -}).catch((err) => { - console.error(err); -}); diff --git a/assets/jsutils/stateIndexedDbWorker.js b/assets/jsutils/stateIndexedDbWorker.js deleted file mode 100644 index 5f4502916716e1f305ffb0c136a24bebca387d82..0000000000000000000000000000000000000000 --- a/assets/jsutils/stateIndexedDbWorker.js +++ /dev/null @@ -1,31 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file. // -//////////////////////////////////////////////////////////////////////////////// - -// NOTE: wasm_exec.js must always be in the same directory as this script. -importScripts('./wasm_exec.js'); -// NOTE: This relative path must be preserved in distribution. -const binPath = require('../wasm/xxdk-stateIndexedDbWorker.wasm'); - -const isReady = new Promise((resolve) => { - self.onWasmInitialized = resolve; -}); - -const go = new Go(); -go.argv = [ - '--logLevel=2', - '--threadLogLevel=2', -] -// NOTE: This is wonky, we are in the right path inside the publicPath -// (usually `dist` folder), which is where webpack paths start. They also -// always prefix with a /, so we are going up a directory to come right back -// into e.g., ../dist/assets/wasm/[somefile].wasm -WebAssembly.instantiateStreaming(fetch('..' + binPath), go.importObject).then(async (result) => { - go.run(result.instance); - await isReady; -}).catch((err) => { - console.error(err); -}); diff --git a/assets/jsutils/ndf.js b/ndf.js similarity index 100% rename from assets/jsutils/ndf.js rename to ndf.js diff --git a/src/paths.ts b/src/paths.ts index fba9bf141e305294ec40b85418df278cef8a3877..10c64fca3a6447214567f2bd633c14577e71a7d3 100644 --- a/src/paths.ts +++ b/src/paths.ts @@ -1,11 +1,24 @@ import { BundleVersion } from './version'; +import { startLogFileWorker, startChannelsIndexedDbWorker, + startDmIndexedDbWorker, startStateIndexedDbWorker } from './workers.js'; + +// wasmExec is the path to wasm_exec.js, which is also the root at the +// publicPath in webpack set as an entry point in the config. +// FIXME: I could not derive this via require/import and it is not +// clear from docs how to do so. We don't need to load it because we +// might downloaded it from a CDN. Hardcoded for now since it does not +// change. In an ideal world, we'd be able to get this as a hash name and +// have a special loader that checks the hash. +const wasmExec = '/dist/wasm_exec.js'; + // TODO: should this global decl be in like the general types folder? // we reference it everywhere and it woudl be useful to specify all // the window properties we need in a single place. declare global { interface Window { xxdkBasePath: URL; + xxdkWasmExecBlobURL: URL; } } @@ -33,19 +46,67 @@ export function setXXDKBasePath(newPath: URL) { // TODO: These functions should be kept internal to this package but the current API and legacy // apps need to maintain access -// NOTE: we do not use require here, because these are defined entry points in the webpack config -export function logFileWorkerPath(): URL { - return new URL(window!.xxdkBasePath + '/dist/logFileWorker.js'); +export async function logFileWorkerPath(): Promise<URL> { + const binPath = require('../assets/wasm/xxdk-logFileWorker.wasm'); + const wasm = new URL(window!.xxdkBasePath + binPath.toString()); + console.info("Loading logFileWorker (" + wasm + ")"); + return downloadWorkerToBlobURL(wasm, startLogFileWorker); } -export function channelsIndexedDbWorkerPath(): URL { - return new URL(window!.xxdkBasePath + '/dist/channelsIndexedDbWorker.js'); +export async function channelsIndexedDbWorkerPath(): Promise<URL> { + const binPath = require('../assets/wasm/xxdk-channelsIndexedDbWorker.wasm'); + const wasm = new URL(window!.xxdkBasePath + binPath.toString()); + console.info("Loading channelsIndexedDbWorker (" + wasm + ")"); + return downloadWorkerToBlobURL(wasm, startChannelsIndexedDbWorker); } -export function dmIndexedDbWorkerPath(): URL { - return new URL(window!.xxdkBasePath + '/dist/dmIndexedDbWorker.js'); +export async function dmIndexedDbWorkerPath(): Promise<URL> { + const binPath = require('../assets/wasm/xxdk-dmIndexedDbWorker.wasm'); + const wasm = new URL(window!.xxdkBasePath + binPath.toString()); + console.info("Loading dmIndexedDbWorker (" + wasm + ")"); + return downloadWorkerToBlobURL(wasm, startDmIndexedDbWorker); } -export function stateIndexedDbWorkerPath() { - return new URL(window!.xxdkBasePath + '/dist/stateIndexedDbWorker.js'); +export async function stateIndexedDbWorkerPath(): Promise<URL> { + const binPath = require('../assets/wasm/xxdk-stateIndexedDbWorker.wasm'); + const wasm = new URL(window!.xxdkBasePath + binPath.toString()); + console.info("Loading stateIndexedDbWorker (" + wasm + ")"); + return downloadWorkerToBlobURL(wasm, startStateIndexedDbWorker); +} + +// NOTE: Whereas we can load the workers via function.toString(), they all need +// to reference the wasm_exec.js import script, preferably the same one. To handle this, +// we download that script into it's own blob url, then explicitly call importScrips(blob url). +async function downloadWorkerToBlobURL(wasm: URL, workerFn: (wasm: URL) => any): Promise<URL> { + const wasmExec = await wasmExecBlob(); + const blobElems = [ + "importScripts('" + wasmExec + "'); ", + "(" + workerFn.toString() + ")('" + wasm + "');" + ]; + const urlStr = URL.createObjectURL(new Blob(blobElems, {type: 'text/javascript'})); + console.info("[XXDK] Loaded " + wasm.toString() + " worker at: " + urlStr.toString()); + console.trace("[XXDK] worker contents: " + blobElems.toString()); + return new URL(urlStr); +} + +// wasmExecBlob downloads the wasm_exec.js file from the xxdkBasePath, which could +// be locally hosted or on a CDN, then makes it available via a blob url. +async function wasmExecBlob(): Promise<URL> { + if (window!.xxdkWasmExecBlobURL !== undefined) { + return window!.xxdkWasmExecBlobURL; + } + + const url = new URL(window!.xxdkBasePath + wasmExec.toString()); + console.trace("[XXDK] wasm_exec.js download url: " + url.toString()); + try { + const response = await fetch(url); + const data = await response.text(); + const urlStr = URL.createObjectURL(new Blob([data], {type: 'text/javascript'})); + window!.xxdkWasmExecBlobURL = new URL(urlStr); + } catch (x) { + console.error("[XXDK] Unable to load wasm_exec.js into a blob url: " + x); + throw(x); + } + console.info("[XXDK] wasm_exec.js loaded at: " + window!.xxdkWasmExecBlobURL); + return window!.xxdkWasmExecBlobURL; } diff --git a/src/workers.js b/src/workers.js new file mode 100644 index 0000000000000000000000000000000000000000..31cfde8e16b035c8caec2a4b6b540dd96df69b08 --- /dev/null +++ b/src/workers.js @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2024 xx foundation // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file. // +//////////////////////////////////////////////////////////////////////////////// + +// NOTE: These javascript functions are used to load web workers. They +// can't call other functions and they must use the webworker +// available APIs to function. Thats why they are duplicate code. + +export function startLogFileWorker(wasm) { + console.trace("[XXDK] logFileWorker loading from: " + wasm.toString()); + const isReady = new Promise((resolve) => { + self.onWasmInitialized = resolve; + }); + + const go = new Go(); + WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => { + go.run(result.instance); + await isReady; + console.info("[XXDK] logFileWorker started"); + }).catch((err) => { + console.error(err); + }); +} + +export function startChannelsIndexedDbWorker(wasm) { + console.trace("[XXDK] channelsIndexedDbWorker loading from: " + wasm.toString()); + const isReady = new Promise((resolve) => { + self.onWasmInitialized = resolve; + }); + const go = new Go(); + go.argv = [ + '--logLevel=2', + '--threadLogLevel=2', + ] + WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => { + go.run(result.instance); + await isReady; + console.info("[XXDK] channelsIndexedDbWorker started"); + }).catch((err) => { + console.error(err); + }); +} + +export function startDmIndexedDbWorker(wasm) { + console.trace("[XXDK] dmIndexedDbWorker loading from: " + wasm.toString()); + const isReady = new Promise((resolve) => { + self.onWasmInitialized = resolve; + }); + const go = new Go(); + go.argv = [ + '--logLevel=2', + '--threadLogLevel=2', + ] + WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => { + go.run(result.instance); + await isReady; + console.info("[XXDK] dmIndexedDbWorker started"); + }).catch((err) => { + console.error(err); + }); +} + +export function startStateIndexedDbWorker(wasm) { + console.trace("[XXDK] stateIndexedDbWorker loading from: " + wasm.toString()); + const isReady = new Promise((resolve) => { + self.onWasmInitialized = resolve; + }); + const go = new Go(); + go.argv = [ + '--logLevel=2', + '--threadLogLevel=2', + ] + WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(async (result) => { + go.run(result.instance); + await isReady; + console.info("[XXDK] stateIndexedDbWorker started"); + }).catch((err) => { + console.error(err); + }); +} + diff --git a/src/xxdk.ts b/src/xxdk.ts index 3f899f70eeeba37a0fc92e4da2019270dc964db3..e476d3fe189dfdc626df9cf75228ca2f8b0762b0 100644 --- a/src/xxdk.ts +++ b/src/xxdk.ts @@ -35,11 +35,13 @@ export const InitXXDK = () => new Promise<XXDKUtils>(async (xxdkUtils) => { console.log("Fetching xxdkWASM base: " + window!.xxdkBasePath); console.log("Fetching xxdkWASM path: " + xxdkWasm); + const logWorker = await logFileWorkerPath(); + console.log("Got logworkerURL: " + logWorker); let go = new window!.Go(); go.argv = [ '--logLevel=1', '--fileLogLevel=1', - '--workerScriptURL=' + logFileWorkerPath(), + '--workerScriptURL=' + logWorker, ] let stream = await WebAssembly?.instantiateStreaming(