@socketsupply/socket
Version:
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.
193 lines (163 loc) • 5.03 kB
JavaScript
/* 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'
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 = '/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,
client: client.toJSON(),
name: options.name,
port: sharedWorker.port,
id: sharedWorker.id
}
})
workers.set(sharedWorker.id, new WeakRef(sharedWorker))
}
export class SharedWorkerMessagePort extends ipc.IPCMessagePort {
[] () {
return {
...(super[Symbol.for('socket.runtime.serialize')]()),
__type__: 'SharedWorkerMessagePort'
}
}
}
export class SharedWorker extends EventTarget {
/**
* `SharedWorker` class constructor.
* @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)
super(url.toString(), nameOrOptions)
this.
this.
this.
scriptURL: url.toString(),
name: typeof nameOrOptions === 'string'
? nameOrOptions
: typeof nameOrOptions?.name === 'string'
? nameOrOptions.name
: null
})
}
get onerror () {
return this.
}
set onerror (onerror) {
if (typeof this.
this.removeEventListener('error', this.
}
this.
if (typeof onerror === 'function') {
this.
this.addEventListener('error', onerror)
}
}
get ready () {
return this.
}
get port () {
return this.
}
get id () {
return this.
}
}
/**
* 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,
// @ts-ignore
debug: Boolean(process.env.SOCKET_RUNTIME_SHARED_WORKER_DEBUG),
index: SHARED_WORKER_WINDOW_INDEX,
title: SHARED_WORKER_WINDOW_TITLE,
path: SHARED_WORKER_WINDOW_PATH,
config: {
webview_watch_reload: false
}
}).catch(() => application.getWindow(SHARED_WORKER_WINDOW_INDEX, {
max: false
}))
)
const promises = [
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
await contextWindow.hide()
return contextWindow
}
export default SharedWorker