From 44fe5ff6ec6ff21a5d804084230da79a87350f82 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Thu, 9 Jan 2025 19:04:17 -0500 Subject: [PATCH] refactor(api/{mime,service-worker}): improve mime query input support, use 'BroadcastChannel' in serviceworkers --- api/internal/geolocation.js | 29 +++++- api/ipc.js | 72 ++++++++------ api/mime/index.js | 10 +- api/service-worker/container.js | 94 +++++++++--------- api/service-worker/frame.html | 145 ---------------------------- api/service-worker/init.js | 29 +++--- api/service-worker/instance.js | 19 +--- api/service-worker/notification.js | 20 +--- api/service-worker/shared-worker.js | 61 ------------ api/service-worker/state.js | 2 +- 10 files changed, 142 insertions(+), 339 deletions(-) delete mode 100644 api/service-worker/frame.html delete mode 100644 api/service-worker/shared-worker.js diff --git a/api/internal/geolocation.js b/api/internal/geolocation.js index 7c247cba8c..3d1fd94e22 100644 --- a/api/internal/geolocation.js +++ b/api/internal/geolocation.js @@ -64,7 +64,7 @@ function createGeolocationPosition (data) { altitude: { configurable: false, writable: false, - value: data.coords.altitude + value: data.coords.altitude ?? null }, accuracy: { @@ -95,12 +95,34 @@ function createGeolocationPosition (data) { configurable: false, writable: false, value: data.coords.speed <= 0 ? null : data.coords.speed + }, + + toJSON: { + configurable: false, + writable: true, + value: () => ({ + accuracy: data.coords.accuracy, + latitude: data.coords.latitude, + longitude: data.coords.longitude, + altitude: data.coords.altitude ?? null, + altitudeAccuracy: data.coords.altitudeAccuracy ?? null, + heading: data.coords.heading ?? null, + speed: data.coords.speed ?? null + }) } }) return Object.create(GeolocationPosition.prototype, { coords: { configurable: false, writable: false, value: coords }, - timestamp: { configurable: false, writable: false, value: Date.now() } + timestamp: { configurable: false, writable: false, value: Date.now() }, + toJSON: { + configurable: false, + writable: true, + value: () => ({ + coords: coords.toJSON(), + timestamp: Date.now() + }) + } }) } @@ -176,7 +198,8 @@ export async function getCurrentPosition ( timer = setTimeout(() => { didTimeout = true const error = Object.create(GeolocationPositionError.prototype, { - code: { value: GeolocationPositionError.TIMEOUT } + code: { value: GeolocationPositionError.TIMEOUT }, + message: { value: 'Position acquisition timed out' } }) if (typeof onError === 'function') { diff --git a/api/ipc.js b/api/ipc.js index 4d022f1f13..ea96b9abf3 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1772,39 +1772,51 @@ export function findIPCMessageTransfers (transfers, object) { object[i] = findIPCMessageTransfers(transfers, object[i]) } } else if (object && typeof object === 'object') { - if (object instanceof MessagePort) { - if (object instanceof IPCMessagePort) { - const port = IPCMessagePort.create(object) - ports.get(port.id).channel.onmessage = (event) => { - if (port.closed === true) { - port.onmessage = null - event.preventDefault() - event.stopImmediatePropagation() - return false - } + if ( + object instanceof MessagePort || ( + typeof object.postMessage === 'function' && + Object.getPrototypeOf(object).constructor.name === 'MessagePort' + ) + ) { + const port = IPCMessagePort.create(object) + object.addEventListener('message', function onMessage (event) { + if (port.closed === true) { + port.onmessage = null + event.preventDefault() + event.stopImmediatePropagation() + object.removeEventListener('message', onMessage) + return false + } - if (port.started && event.data?.token !== port.token) { - console.log(event.data) - const transfers = new Set() - findIPCMessageTransfers(transfers, event.data) - object.postMessage(event.data, { - transfer: Array.from(transfers) - }) - } + port.dispatchEvent(new MessageEvent('message', event)) + }) + + port.onmessage = (event) => { + if (port.closed === true) { + port.onmessage = null + event.preventDefault() + event.stopImmediatePropagation() + return false } - add(port) - return port - } else { - add(object) - return object - } - } else if (Object.getPrototypeOf(object) === Object.prototype) { - for (const key in object) { - object[key] = findIPCMessageTransfers( - transfers, - object[key] - ) + + const transfers = new Set() + findIPCMessageTransfers(transfers, event.data) + object.postMessage(event.data, { + transfer: Array.from(transfers) + }) } + add(port) + return port + } else { + add(object) + return object + } + } else if (Object.getPrototypeOf(object) === Object.prototype) { + for (const key in object) { + object[key] = findIPCMessageTransfers( + transfers, + object[key] + ) } } diff --git a/api/mime/index.js b/api/mime/index.js index 81dc241d7f..b16afd923f 100644 --- a/api/mime/index.js +++ b/api/mime/index.js @@ -157,8 +157,16 @@ export class Database { query (query) { query = query.toLowerCase() - const queryParts = query.split('+') + if (query.startsWith('.')) { + query = query.slice(1) + } + const results = [] + const queryParts = query + .split('+') + .map((query) => query.trim()) + .map((query) => query.startsWith('.') ? query.slice(1) : query) + .filter(Boolean) for (const [key, value] of this.map.entries()) { const name = key.toLowerCase() diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 061f5eef5e..65bdbbc007 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -1,7 +1,6 @@ /* global EventTarget */ import { ServiceWorkerRegistration } from './registration.js' -import { createServiceWorker, SHARED_WORKER_URL } from './instance.js' -import { SharedWorker } from '../shared-worker/index.js' +import { createServiceWorker } from './instance.js' import { Deferred } from '../async.js' import application from '../application.js' import location from '../location.js' @@ -23,8 +22,7 @@ class ServiceWorkerContainerInternalStateMap extends Map { class ServiceWorkerContainerInternalState { currentWindow = null controller = null - sharedWorker = null - channel = new BroadcastChannel('socket.runtime.ServiceWorkerContainer') + channel = new BroadcastChannel('socket.runtime.serviceWorker') ready = new Deferred() init = new Deferred() @@ -487,52 +485,26 @@ export class ServiceWorkerContainer extends EventTarget { return globalThis.top.navigator.serviceWorker.startMessages() } - 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 - } - } - } else if ( - event.data?.from === 'instance' && - event.data?.registration?.id && - event.data?.client?.id && - event.data?.client?.id !== globalThis.__args.client.id - ) { - if (globalThis.isWorkerScope && globalThis.serviceWorker) { + internal.get(this).channel.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: event.data.client.origin.origin, + origin: new URL(info.scriptURL, location.origin).origin, data: event.data.message }) @@ -540,12 +512,34 @@ export class ServiceWorkerContainer extends EventTarget { configurable: false, enumerable: false, writable: false, - value: await globalThis.clients.get(event.data.client.id) + value: serviceWorker }) this.dispatchEvent(messageEvent) + break } } + } else if ( + event.data?.from === 'instance' && + event.data?.registration?.id && + event.data?.client?.id && + event.data?.client?.id !== globalThis.__args.client.id + ) { + if (globalThis.isWorkerScope && globalThis.serviceWorker) { + const messageEvent = new MessageEvent('message', { + origin: event.data.client.origin.origin, + data: event.data.message + }) + + Object.defineProperty(messageEvent, 'source', { + configurable: false, + enumerable: false, + writable: false, + value: await globalThis.clients.get(event.data.client.id) + }) + + this.dispatchEvent(messageEvent) + } } } } diff --git a/api/service-worker/frame.html b/api/service-worker/frame.html deleted file mode 100644 index 828400c539..0000000000 --- a/api/service-worker/frame.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - -

-  
-
diff --git a/api/service-worker/init.js b/api/service-worker/init.js
index a2f6339db2..5b54f7239d 100644
--- a/api/service-worker/init.js
+++ b/api/service-worker/init.js
@@ -1,6 +1,4 @@
 /* global Worker */
-import { SHARED_WORKER_URL } from './instance.js'
-import { SharedWorker } from '../shared-worker/index.js'
 import { Notification } from '../notification.js'
 import { sleep } from '../timers.js'
 import globals from '../internal/globals.js'
@@ -9,13 +7,12 @@ import hooks from '../hooks.js'
 import ipc from '../ipc.js'
 
 export const workers = new Map()
+export const channel = new BroadcastChannel('socket.runtime.serviceWorker')
 
 globals.register('ServiceWorkerContext.workers', workers)
 globals.register('ServiceWorkerContext.info', new Map())
 
-const sharedWorker = new SharedWorker(SHARED_WORKER_URL)
-sharedWorker.port.start()
-sharedWorker.port.onmessage = (event) => {
+channel.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) {
@@ -24,9 +21,9 @@ sharedWorker.port.onmessage = (event) => {
       }
     }
   } else if (event.data?.showNotification && event.data.registration?.id) {
-    onNotificationShow(event, sharedWorker.port)
+    onNotificationShow(event)
   } else if (event.data?.getNotifications && event.data.registration?.id) {
-    onGetNotifications(event, sharedWorker.port)
+    onGetNotifications(event)
   }
 }
 
@@ -79,7 +76,7 @@ export class ServiceWorkerInstance extends Worker {
       this.postMessage({ install: info })
       info.promise.resolve()
     } else if (event.data?.message && event?.data.client?.id) {
-      sharedWorker.port.postMessage({
+      channel.postMessage({
         ...event.data,
         from: 'realm'
       })
@@ -344,7 +341,7 @@ export function onNotificationShow (event, target) {
   }
 }
 
-export function onNotificationClose (event, target) {
+export function onNotificationClose (event) {
   for (const worker of workers.values()) {
     for (const notification of worker.notifications) {
       if (event.data.notificationclose.id === notification.id) {
@@ -355,10 +352,10 @@ export function onNotificationClose (event, target) {
   }
 }
 
-export function onGetNotifications (event, target) {
+export function onGetNotifications (event) {
   for (const worker of workers.values()) {
     if (worker.info.id === event.data.registration.id) {
-      return target.postMessage({
+      return channel.postMessage({
         nonce: event.data.nonce,
         notifications: worker.notifications
           .filter((notification) =>
@@ -392,10 +389,12 @@ hooks.onReady(async () => {
     __service_worker_frame_init: true
   })
 
-  const result = await ipc.request('serviceWorker.getRegistrations')
-  if (Array.isArray(result.data)) {
-    for (const info of result.data) {
-      await navigator.serviceWorker.register(info.scriptURL, info)
+  if (workers.size === 0) {
+    const result = await ipc.request('serviceWorker.getRegistrations')
+    if (Array.isArray(result.data)) {
+      for (const info of result.data) {
+        await navigator.serviceWorker.register(info.scriptURL, info)
+      }
     }
   }
 })
diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js
index 7a2965d51b..46a6e45c0c 100644
--- a/api/service-worker/instance.js
+++ b/api/service-worker/instance.js
@@ -1,13 +1,10 @@
-import { SharedWorker } from '../shared-worker/index.js'
 import location from '../location.js'
 import state from './state.js'
 import ipc from '../ipc.js'
 
 const serviceWorkers = new Map()
-let sharedWorker = null
-
-export const SHARED_WORKER_URL = `${globalThis.origin}/socket/service-worker/shared-worker.js`
 
+export const channel = new BroadcastChannel('socket.runtime.serviceWorker')
 export const ServiceWorker = globalThis.ServiceWorker ?? class ServiceWorker extends EventTarget {
   get onmessage () { return null }
   set onmessage (_) {}
@@ -32,8 +29,6 @@ export function createServiceWorker (
     }
   }
 
-  const channel = new BroadcastChannel('socket.runtime.serviceWorker.state')
-
   // events
   const eventTarget = new EventTarget()
   let onstatechange = null
@@ -43,21 +38,13 @@ export function createServiceWorker (
   let serviceWorker = null
   let scriptURL = options?.scriptURL ?? null
 
-  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) {
-        if (sharedWorker && globalThis.__args?.client) {
-          sharedWorker.port.postMessage({
+        if (globalThis.__args?.client) {
+          channel.postMessage({
             message,
             from: 'instance',
             registration: { id },
diff --git a/api/service-worker/notification.js b/api/service-worker/notification.js
index ae094bfdf2..1bc1ddeb30 100644
--- a/api/service-worker/notification.js
+++ b/api/service-worker/notification.js
@@ -1,12 +1,9 @@
 import { Notification, NotificationOptions } from '../notification.js'
-import { SHARED_WORKER_URL } from './instance.js'
 import { NotAllowedError } from '../errors.js'
-import { SharedWorker } from '../shared-worker/index.js'
 import permissions from '../internal/permissions.js'
 
-let sharedWorker = null
-
 const observedNotifications = new Set()
+const channel = new BroadcastChannel('socket.runtime.serviceWorker')
 
 if (globalThis.isServiceWorkerScope) {
   globalThis.addEventListener('notificationclose', (event) => {
@@ -19,16 +16,7 @@ if (globalThis.isServiceWorkerScope) {
   })
 }
 
-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 ?? ''
@@ -53,7 +41,7 @@ export async function showNotification (registration, title, options) {
   // 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 target = globalThis.isServiceWorkerScope ? globalThis : channel
   const message = {
     nonce,
     registration: { id: info.id },
@@ -77,8 +65,6 @@ export async function showNotification (registration, title, options) {
 }
 
 export async function getNotifications (registration, options = null) {
-  ensureSharedWorker()
-
   const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')]
 
   if (!info) {
@@ -90,7 +76,7 @@ export async function getNotifications (registration, options = null) {
   }
 
   const nonce = Math.random().toString(16).slice(2)
-  const target = globalThis.isServiceWorkerScope ? globalThis : sharedWorker.port
+  const target = globalThis.isServiceWorkerScope ? globalThis : channel
   const message = {
     nonce,
     registration: { id: info.id },
diff --git a/api/service-worker/shared-worker.js b/api/service-worker/shared-worker.js
deleted file mode 100644
index 9821a8351a..0000000000
--- a/api/service-worker/shared-worker.js
+++ /dev/null
@@ -1,61 +0,0 @@
-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
index 2e35fec2bf..63e1090eff 100644
--- a/api/service-worker/state.js
+++ b/api/service-worker/state.js
@@ -2,7 +2,7 @@ import application from '../application.js'
 import debug from './debug.js'
 import ipc from '../ipc.js'
 
-export const channel = new BroadcastChannel('socket.runtime.serviceWorker.state')
+export const channel = new BroadcastChannel('socket.runtime.serviceWorker')
 
 const descriptors = {
   channel: {