From 3e2ef8b1ea0f0b05a08026fb4202ba185f37c28f Mon Sep 17 00:00:00 2001 From: nitro-neal <5314059+nitro-neal@users.noreply.github.com> Date: Tue, 21 May 2024 09:21:10 -0700 Subject: [PATCH] Web Features (@cswildcat) (#567) * Add new web features module with networking feature installation * Update configs * Make TS happy * Make TS happy, part 2 * TS and linting joy * put web5-spec on main * update --------- Co-authored-by: Daniel Buchner --- packages/api/src/index.ts | 1 + packages/api/src/service-worker.ts | 55 ++++++++++++++++++++++++++++++ packages/api/src/web-features.ts | 25 ++++++++++++++ packages/api/tsconfig.json | 4 +++ 4 files changed, 85 insertions(+) create mode 100644 packages/api/src/service-worker.ts create mode 100644 packages/api/src/web-features.ts diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 9c420aba6..30e46b9c1 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -28,6 +28,7 @@ export * from './record.js'; export * from './vc-api.js'; export * from './web5.js'; export * from './tech-preview.js'; +export * from './web-features.js'; import * as utils from './utils.js'; export { utils }; \ No newline at end of file diff --git a/packages/api/src/service-worker.ts b/packages/api/src/service-worker.ts new file mode 100644 index 000000000..7ca3eff6b --- /dev/null +++ b/packages/api/src/service-worker.ts @@ -0,0 +1,55 @@ +import { UniversalResolver, DidDht, DidWeb } from '@web5/dids'; + +const workerSelf = self as any; +const DidResolver = new UniversalResolver({ didResolvers: [DidDht, DidWeb] }); +const didUrlRegex = /^https?:\/\/dweb\/(([^/]+)\/.*)?$/; +const httpToHttpsRegex = /^http:/; +const trailingSlashRegex = /\/$/; + +workerSelf.addEventListener('fetch', event => { + const match = event.request.url.match(didUrlRegex); + if (match) { + event.respondWith((async () => { + const normalizedUrl = event.request.url.replace(httpToHttpsRegex, 'https:').replace(trailingSlashRegex, ''); + const cachedResponse = await caches.open('drl').then(cache => cache.match(normalizedUrl)); + return cachedResponse || handleEvent(event, match[2], match[1]); + })()); + } +}); + +async function handleEvent(event, did, route){ + try { + const result = await DidResolver.resolve(did); + return await fetchResource(event, result.didDocument, route); + } + catch(error){ + if (error instanceof Response) { + return error; + } + console.log(`Error in DID URL fetch: ${error}`); + return new Response('DID URL fetch error', { status: 500 }); + } +} + +async function fetchResource(event, ddo, route) { + let endpoints = ddo?.service?.find(service => service.type === 'DecentralizedWebNode')?.serviceEndpoint; + endpoints = (Array.isArray(endpoints) ? endpoints : [endpoints]).filter(url => url.startsWith('http')); + if (!endpoints?.length) { + throw new Response('DWeb Node resolution failed: no valid endpoints found.', { status: 530 }); + } + + for (const endpoint of endpoints) { + try { + const response = await fetch(`${endpoint.replace(trailingSlashRegex, '')}/${route}`, { headers: event.request.headers }); + if (response.ok) { + return response; + } + console.log(`DWN endpoint error: ${response.status}`); + return new Response('DWeb Node request failed', { status: response.status }); + } + catch (error) { + console.log(`DWN endpoint error: ${error}`); + return new Response('DWeb Node request failed: ' + error, { status: 500 }); + } + } +} \ No newline at end of file diff --git a/packages/api/src/web-features.ts b/packages/api/src/web-features.ts new file mode 100644 index 000000000..e264b0b08 --- /dev/null +++ b/packages/api/src/web-features.ts @@ -0,0 +1,25 @@ + +declare const ServiceWorkerGlobalScope: any; + +export function installNetworkingFeatures(path: string): void { + const workerSelf = self as any; + + try { + if (typeof ServiceWorkerGlobalScope !== 'undefined' && workerSelf instanceof ServiceWorkerGlobalScope) { + // Dynamically import service worker code only if we're in a Service Worker context + import('./service-worker.js').catch(error => { + console.error('Error loading service worker module:', error); + }); + } + else if (globalThis?.navigator?.serviceWorker) { + if (path) navigator.serviceWorker.register(path).catch(error => { + console.error('DWeb networking feature installation failed: ', error); + }); + } + else { + throw new Error('DWeb networking features are not available for install in this environment'); + } + } catch (error) { + console.error('Error in installing networking features:', error); + } +} \ No newline at end of file diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 0701f4d02..8b5a11c1c 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,6 +1,10 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "lib": [ + "DOM", + "ES2022" + ], "strict": false, "declarationDir": "dist/types", "outDir": "dist/esm"