UNPKG

@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.

207 lines (179 loc) 5.68 kB
import { resolve } from './module.js' import process from '../process.js' import debug from '../service-worker/debug.js' import mime from '../mime.js' import path from '../path.js' import http from '../http.js' import util from '../util.js' const DEBUG_LABEL = ' <span\\sstyle="color:\\s#fb8817;"><b>npm</b></span>' /** * @ignore * @param {Request} * @param {object} env * @param {import('../service-worker/context.js').Context} ctx * @return {Promise<Response|null>} */ export async function onRequest (request, env, ctx) { // eslint-disable-next-line void env, ctx; if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { console.debug(request.url) } const url = new URL(request.url) const origin = url.origin.replace('npm://', 'socket://') const referer = request.headers.get('referer') let specifier = url.pathname.replace('/socket/npm/', '') const importOrigins = url.searchParams.getAll('origin').concat(url.searchParams.getAll('origin[]')) if (typeof specifier === 'string') { try { specifier = (new URL(require.resolve(specifier, { type: 'module' }))).toString() } catch {} } debug(`${DEBUG_LABEL}: fetch: %s`, specifier) let resolved = null let origins = [] if (referer && !referer.startsWith('blob:')) { // @ts-ignore if (URL.canParse(referer, origin)) { const refererURL = new URL(referer, origin) if (refererURL.href.endsWith('/')) { importOrigins.push(refererURL.href) } else { importOrigins.push(new URL('./', refererURL).href) } } } for (const value of importOrigins) { if (value.startsWith('npm:')) { origins.push(value) } else if (value.startsWith('.')) { origins.push(new URL(value, `socket://${url.host}${url.pathname}`).href.replace(/\/$/, '')) } else if (value.startsWith('/')) { origins.push(new URL(value, origin).href) // @ts-ignore } else if (URL.canParse(value)) { origins.push(value) // @ts-ignore } else if (URL.canParse(`socket://${value}`)) { origins.push(`socket://${value}`) } } origins.push(origin) origins = Array.from(new Set(origins)) if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { console.debug('resolving: npm:%s (%o)', specifier, origins) } while (origins.length && resolved === null) { const potentialOrigins = [] const origin = origins.shift() if (origin.startsWith('npm:')) { const potentialSpecifier = new URL(origin).pathname for (const potentialOrigin of origins) { const resolution = await resolve(potentialSpecifier, potentialOrigin) if (resolution) { potentialOrigins.push(resolution.url) break } } } else { potentialOrigins.push(origin) } while (potentialOrigins.length && resolved === null) { const importOrigin = new URL('./', potentialOrigins.shift()).href resolved = await resolve(specifier, importOrigin) } } // not found if (!resolved) { debug(`${DEBUG_LABEL}: not found: %s`, specifier) return } const extname = path.extname(resolved.url) const types = extname ? await mime.lookup(extname.slice(1)) : [] if (types.length || resolved.type === 'file') { let redirect = true for (const type of types) { if (type.mime === 'text/javascript' || type.mime.endsWith('json')) { redirect = false break } } if (redirect) { debug(`${DEBUG_LABEL}: resolve: %s (file): %s`, specifier, resolved.url) return Response.redirect(resolved.url, 301) } } debug(`${DEBUG_LABEL}: resolve: %s (%s): %s`, specifier, resolved.type, resolved.url) if (resolved.type === 'module') { const response = await fetch(resolved.url) const text = await response.text() const proxy = /^\s*(export\s*default)/gm.test(text) ? ` import module from '${resolved.url}' export * from '${resolved.url}' export default module ` : ` import * as module from '${resolved.url}' export * from '${resolved.url}' export default module ` const source = proxy .trim() .split('\n') .map((line) => line.trim()) .join('\n') return new Response(source, { headers: { 'content-type': 'text/javascript' } }) } if (resolved.type === 'commonjs') { const proxy = ` import { createRequire } from 'socket:module' const headers = { 'Runtime-ServiceWorker-Fetch-Mode': 'ignore' } const require = createRequire('${resolved.origin}', { headers }) const exports = require('${resolved.url}') export default exports?.default ?? exports ?? null `.trim() const source = proxy .trim() .split('\n') .map((line) => line.trim()) .join('\n') return new Response(source, { headers: { 'content-type': 'text/javascript' } }) } } /** * Handles incoming 'npm://<module_name>/<pathspec...>' requests. * @param {Request} request * @param {object} env * @param {import('../service-worker/context.js').Context} ctx * @return {Response?} */ export default async function (request, env, ctx) { if (request.method === 'OPTIONS') { return new Response('OK', { status: 204 }) } if (request.method !== 'GET') { return new Response('Invalid HTTP method', { status: http.BAD_REQUEST }) } try { return await onRequest(request, env, ctx) } catch (err) { globalThis.reportError(err) if (process.env.SOCKET_RUNTIME_NPM_DEBUG) { console.debug(err) } return new Response(util.inspect(err), { status: 500 }) } }