Skip to content

Commit

Permalink
$mol_worker_service plugins, refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
zerkalica committed Oct 28, 2024
1 parent cd84bfa commit 958e207
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 222 deletions.
6 changes: 1 addition & 5 deletions offline/install/install.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
namespace $ {
try {
$mol_offline.main.run()
} catch( error ) {
console.error( error )
}
$mol_offline.attach_to($mol_worker_service)
}
25 changes: 15 additions & 10 deletions offline/offline.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
namespace $ {

export type $mol_offline_web_message = {
ignore_cache?: boolean
blocked_urls?: readonly string[]
cached_urls?: readonly string[]
}

export class $mol_offline extends $mol_worker {
static main = new $mol_offline
export class $mol_offline extends $mol_worker_service_plugin {
constructor() {
super()
this.$.$mol_dom_context.addEventListener('message', e => {
if (e.data === 'mol_build_obsolete') this.value('ignore_cache', true)
})
}

blocked(urls?: readonly string[]) { return urls ?? [] }
cached(urls?: readonly string[]) { return urls ?? [] }
override defaults() {
return {
ignore_cache: false,
blocked_urls: [
'//cse.google.com/adsense/search/async-ads.js'
]
}
}
}

}
80 changes: 8 additions & 72 deletions offline/offline.web.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,11 @@
/// <reference lib="webworker" />

namespace $ {
export class $mol_offline_web extends $mol_worker_web {
static main = new $mol_offline_web

override path() { return 'web.js' }

override ready(reg: $mol_worker_reg_active) {
reg.active.postMessage({ ignore_cache: false })
}

override async registration_init() {
window.addEventListener('message', this.window_message.bind(this))
return super.registration_init()
}

protected window_message(e: MessageEvent) {
const data = e.data
if (data === 'mol_build_obsolete') return this.send({ ignore_cache: true })
}

blocked(urls?: readonly string[]) {
urls = urls ?? this.blocked_urls
this.send({ blocked_urls: urls })
return urls
}

cached(urls?: readonly string[]) {
urls = urls ?? this.cached_urls
this.send({ cached_urls: urls })
return urls
}

override message(event: ExtendableMessageEvent) {
const data = event.data as string | null | $mol_offline_web_message
if ( ! data || typeof data !== 'object' ) return

if (data.ignore_cache !== undefined) this.ignore_cache = data.ignore_cache
if (data.blocked_urls) this.blocked_regexp = this.url_regexp(data.blocked_urls)
if (data.cached_urls) this.cached_regexp = this.url_regexp(data.cached_urls)
}


override activate(event: ExtendableEvent) {
super.activate(event)
$$.$mol_log3_done({
place: '$mol_offline',
message: 'Activated',
})
}

protected blocked_urls = [
'//cse\.google\.com/adsense/search/async-ads\.js'
] as readonly string[]

protected cached_urls = [
'.*/index\.html'
] as readonly string[]

protected blocked_regexp = this.url_regexp(this.blocked_urls)
protected cached_regexp = this.url_regexp(this.cached_urls)

url_regexp(list: readonly string[]) {
return new RegExp(`#^https?:(?:(?:${list.join(')|(?:')}))#`)
}

protected ignore_cache = false
export class $mol_offline_web extends $mol_offline {

override fetch_event(event: FetchEvent) {
const request = event.request

if( this.blocked_regexp.test(request.url) ) {
return event.respondWith(
if( this.value('blocked_urls')?.includes(request.url.replace( /^https?:/, '' )) ) {
event.respondWith(
new Response(
null,
{
Expand All @@ -80,23 +14,25 @@ namespace $ {
},
)
)
return true
}

if( request.method !== 'GET' ) return
if( !/^https?:/.test( request.url ) ) return
if( /\?/.test( request.url ) ) return
if( request.cache === 'no-store' ) return

event.respondWith( this.respond(event.request) )
return true
}

async respond(request: Request) {
let fallback_header

const force_cache = this.cached_regexp.test(request.url)
const force_cache = /.+\/[^\/]+\.html/.test(request.url)
const no_cache = request.cache === 'no-cache' && ! force_cache

if (this.ignore_cache || request.cache === 'reload' || no_cache) {
if (this.value('ignore_cache') || request.cache === 'reload' || no_cache) {

if (request.cache !== 'no-cache' && request.cache !== 'reload') {
request = new Request(request, { cache: 'no-cache' })
Expand Down
49 changes: 49 additions & 0 deletions worker/service/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// <reference lib="webworker" />
namespace $ {
export class $mol_worker_service_plugin extends $mol_object {
static attach_to< This extends typeof $mol_worker_service_plugin >(
this : This,
worker = $mol_worker_service,
config?: Partial< InstanceType< This > >,
) {
const plugin = new this
if (config) {
for( let key in config ) ( plugin as any )[ key ] = config[ key ]!
}

worker.attach(plugin)

return plugin as InstanceType< This >
}

defaults() { return {} }

id = this.toString()

@ $mol_mem
data_actual( next?: Partial<ReturnType<typeof this['defaults']>> | null ) {
return next ?? null
}

@ $mol_mem
data( next?: Partial<ReturnType<typeof this['defaults']>> | null ) {
return this.data_actual(next) ?? this.defaults() as ReturnType< this['defaults'] >
}

value< Field extends keyof NonNullable<ReturnType< this['data'] >> >(
field: Field,
value?: NonNullable<ReturnType< this['data'] >>[ Field ] | null,
) {
const next = value === undefined ? undefined : { ...this.data(), [ field ]: value }

return this.data(next)?.[ field as never ] as NonNullable<ReturnType< this['data'] >>[ Field ]
}

init(worker: ServiceWorkerGlobalScope) {}

fetch_event(event: FetchEvent) { return false as void | boolean }
activate(event: ExtendableEvent) { return false as void | boolean }
install(event: ExtendableEvent) { return false as void | boolean }
before_install(event: Event & { prompt?(): void }) { return false as void | boolean }
}
}
26 changes: 26 additions & 0 deletions worker/service/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace $ {

export class $mol_worker_service extends $mol_object {
static in_worker() { return typeof window === 'undefined' }

static path() { return 'web.js' }

static plugins = {} as Record<string, $mol_worker_service_plugin>

@ $mol_mem_key
static data_actual<State extends {} | null>(plugin_name: string, next?: State) {
if (next && ! this.in_worker()) {
this.send({ [plugin_name]: next })
}

return next ?? null
}

static attach(plugin: $mol_worker_service_plugin) {
this.plugins[plugin.id] = plugin
plugin.data_actual = state => this.data_actual(plugin.id, state)
}

static send(data: unknown) {}
}
}
164 changes: 164 additions & 0 deletions worker/service/worker.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
namespace $ {
export type $mol_worker_service_reg_active = ServiceWorkerRegistration & { active: ServiceWorker }
export type $mol_worker_service_reg_installing = ServiceWorkerRegistration & { installing: ServiceWorker }

export class $mol_worker_service_web extends $mol_worker_service {
static is_supported() {
if( location.protocol !== 'https:' && location.hostname !== 'localhost' ) {
console.warn( 'HTTPS or localhost is required for service workers.' )
return false
}

if( ! navigator.serviceWorker ) {
console.warn( 'Service Worker is not supported.' )
return false
}

return true
}

protected static registration = null as null | $mol_worker_service_reg_active

static async registration_init() {
let reg
let ready

try {
const reg_promise = navigator.serviceWorker.register(this.path())
ready = navigator.serviceWorker.ready as Promise<$mol_worker_service_reg_active>
reg = (await reg_promise) as $mol_worker_service_reg_active
if (reg.installing) this.installing(reg as $mol_worker_service_reg_installing)

await ready

this.registration = reg
} catch (error) {
console.error(error)
return null
}

let intial_state = {} as Record<string, unknown>
for (let name in this.plugins) {
const data = this.plugins[name].data()
intial_state[name] = data
}

this.send(intial_state)

return reg
}


static installing(reg: $mol_worker_service_reg_installing) {
reg.addEventListener( 'updatefound', this.update_found.bind(this, reg.installing))
}

static update_found(worker: ServiceWorker) {
worker.addEventListener( 'statechange', this.statechange.bind(this, worker))
}

static statechange(worker: ServiceWorker) {}

static override send(data: unknown) {
if (! this.registration?.active) {
throw new Error('Send called before worker ready')
}

this.registration.active.postMessage(data)
}

static override attach(plugin: $mol_worker_service_plugin) {
super.attach(plugin)

if ( this.in_worker() ) {
this.worker()
return
}

if (this.registration) return
if ( ! this.is_supported() ) return

this.registration_init()
}

protected static _worker = null as null | ServiceWorkerGlobalScope

static worker() {
if (this._worker) return this._worker

const worker = this._worker = self as unknown as ServiceWorkerGlobalScope

worker.addEventListener( 'beforeinstallprompt' , this.before_install.bind(this) )
worker.addEventListener( 'install' , this.install.bind(this))
worker.addEventListener( 'activate' , this.activate.bind(this))
worker.addEventListener( 'message', this.message.bind(this))
worker.addEventListener( 'fetch', this.fetch_event.bind(this))

for (let name in this.plugins) this.plugins[name].init(worker)

return worker
}

static message(event: ExtendableMessageEvent) {
const data = event.data as string | null | {
[k: string]: unknown
}
if ( ! data || typeof data !== 'object' ) return false

for (let name in data) {
this.data_actual(name, data[name])
}

return true
}

static before_install(event: Event & { prompt?(): void }) {
let handled
for (let name in this.plugins) {
if (this.plugins[name]?.before_install(event)) handled = true
}

if (! handled) event.prompt?.()

return true
}

static install(event: ExtendableEvent) {
let handled
for (let name in this.plugins) {
if (this.plugins[name]?.install(event)) handled = true
}
if (! handled) this.worker().skipWaiting()
return true
}

static activate(event: ExtendableEvent) {
let handled
for (let name in this.plugins) {
if (this.plugins[name]?.activate(event)) handled = true
}

if (handled) return true

event.waitUntil( this.worker().clients.claim() )

this.$.$mol_log3_done({
place: `${this}.activate()`,
message: 'Activated',
})

return true
}

static fetch_event(event: FetchEvent) {
let handled
for (let name in this.plugins) {
if (this.plugins[name]?.fetch_event(event)) handled = true
}
return handled
}
}

$.$mol_worker_service = $mol_worker_service_web

}
Loading

0 comments on commit 958e207

Please sign in to comment.