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.

1,015 lines (838 loc) 24.5 kB
import { IllegalConstructorError } from './errors.js' import { Buffer } from './buffer.js' import { URL } from './url.js' import types from './util/types.js' import mime from './mime.js' import * as exports from './util.js' export { types } const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype) const ObjectPrototype = Object.prototype const kIgnoreInspect = inspect.ignore = Symbol.for('socket.runtime.util.inspect.ignore') function maybeURL (...args) { try { return new URL(...args) } catch (_) { return null } } export const TextDecoder = globalThis.TextDecoder export const TextEncoder = globalThis.TextEncoder export const isArray = Array.isArray.bind(Array) export const inspectSymbols = [ Symbol.for('socket.runtime.util.inspect.custom'), Symbol.for('nodejs.util.inspect.custom') ] inspect.custom = inspectSymbols[0] export function debug (section) { let enabled = false const env = globalThis.__args?.env ?? {} const sections = [].concat( (env.SOCKET_DEBUG ?? '').split(','), (env.NODE_DEBUG ?? '').split(',') ).map((section) => section.trim()) if (section && sections.includes(section)) { enabled = true } function logger (...args) { if (enabled) { return console.debug(...args) } } Object.defineProperty(logger, 'enabled', { configurable: false, enumerable: false, get: () => enabled, set: (value) => { if (value === true) { enabled = true } else if (value === false) { enabled = false } } }) return logger } export function hasOwnProperty (object, property) { return ObjectPrototype.hasOwnProperty.call(object, String(property)) } export function isDate (object) { return types.isDate(object) } export function isTypedArray (object) { return types.isTypedArray(object) } export function isArrayLike (input) { return ( (Array.isArray(input) || isTypedArray(input)) && input !== TypedArrayPrototype && input !== Buffer.prototype ) } export function isError (object) { return types.isNativeError(object) || object instanceof globalThis.Error } export function isSymbol (value) { return typeof value === 'symbol' } export function isNumber (value) { return !isUndefined(value) && !isNull(value) && ( typeof value === 'number' || value instanceof Number ) } export function isBoolean (value) { return !isUndefined(value) && !isNull(value) && ( value === true || value === false || value instanceof Boolean ) } export function isArrayBufferView (buf) { return !Buffer.isBuffer(buf) && ArrayBuffer.isView(buf) } export function isAsyncFunction (object) { return types.isAsyncFunction(object) } export function isArgumentsObject (object) { return types.isArgumentsObject(object) } export function isEmptyObject (object) { return ( object !== null && typeof object === 'object' && Object.keys(object).length === 0 ) } export function isObject (object) { return ( object !== null && typeof object === 'object' ) } export function isUndefined (value) { return value === undefined } export function isNull (value) { return value === null } export function isNullOrUndefined (value) { return isNull(value) || isUndefined(value) } export function isPrimitive (value) { return ( isNullOrUndefined(value) || typeof value === 'number' || typeof value === 'string' || typeof value === 'symbol' || typeof value === 'boolean' ) } export function isRegExp (value) { return value && value instanceof RegExp } export function isPlainObject (object) { return types.isPlainObject(object) } export function isArrayBuffer (object) { return object !== null && object instanceof ArrayBuffer } export function isBufferLike (object) { return isArrayBuffer(object) || isTypedArray(object) || Buffer.isBuffer(object) } export function isFunction (value) { return ( typeof value === 'function' && typeof value.toString === 'function' && !/^class/.test(value.toString()) ) } export function isErrorLike (error) { if (error instanceof Error) return true return isObject(error) && 'name' in error && 'message' in error } export function isClass (value) { return ( typeof value === 'function' && value.prototype.constructor !== Function ) } export function isBuffer (value) { return Buffer.isBuffer(value) } export function isPromiseLike (object) { return isFunction(object?.then) } export function toString (object) { return Object.prototype.toString.call(object) } export function toBuffer (object, encoding = undefined) { if (Buffer.isBuffer(object)) { return object } else if (isTypedArray(object)) { return Buffer.from(object.buffer) } else if (typeof object?.toBuffer === 'function') { return toBuffer(object.toBuffer(), encoding) } return Buffer.from(object, encoding) } export function toProperCase (string) { if (!string) return '' return string[0].toUpperCase() + string.slice(1) } export function splitBuffer (buffer, highWaterMark) { const buffers = [] buffer = Buffer.from(buffer) do { buffers.push(buffer.slice(0, highWaterMark)) buffer = buffer.slice(highWaterMark) } while (buffer.length > highWaterMark) if (buffer.length) { buffers.push(buffer) } return buffers } export function clamp (value, min, max) { if (!Number.isFinite(value)) { value = min } return Math.min(max, Math.max(min, value)) } Object.defineProperties(promisify, { custom: { configurable: false, enumerable: false, value: Symbol.for('nodejs.util.promisify.custom') }, args: { configurable: false, enumerable: false, value: Symbol.for('nodejs.util.promisify.args') } }) export function promisify (original) { if (original && typeof original === 'object') { let object = Object.create(null) if ( // @ts-ignore original[promisify.custom] && // @ts-ignore typeof original[promisify.custom] === 'object' ) { // @ts-ignore object = original[promisify.custom] } else if (original.promises && typeof original.promises === 'object') { object = original.promises } for (const key in original) { const value = original[key] if (typeof value === 'function' || (value && typeof value === 'object')) { object[key] = promisify(original[key].bind(original)) } else { object[key] = original[key] } } // @ts-ignore Object.defineProperty(object, promisify.custom, { configurable: true, enumerable: false, writable: false, // @ts-ignore __proto__: null, value: object }) return object } if (typeof original !== 'function') { throw new TypeError('Expecting original to be a function or object.') } // @ts-ignore if (original[promisify.custom]) { // @ts-ignore const fn = original[promisify.custom] // @ts-ignore Object.defineProperty(fn, promisify.custom, { configurable: true, enumerable: false, writable: false, // @ts-ignore __proto__: null, value: fn }) return fn } // @ts-ignore const argumentNames = Array.isArray(original[promisify.args]) // @ts-ignore ? original[promisify.args] : [] async function fn (...args) { return await new Promise((resolve, reject) => { return Reflect.apply(original, this, args.concat(callback)) function callback (err, ...values) { let [result] = values if (err) { return reject(err) } if (argumentNames.length) { result = {} for (let i = 0; i < argumentNames.length; ++i) { result[argumentNames[i]] = values[i] } } return resolve(result) } }) } Object.setPrototypeOf(fn, Object.getPrototypeOf(original)) return fn } export function inspect (value, options) { const ctx = { seen: options?.seen || [], depth: typeof options?.depth !== 'undefined' ? options.depth : 2, showHidden: options?.showHidden || false, customInspect: ( options?.customInspect === undefined ? true : options.customInspect ), ...options, options: { stylize (label, style) { return label }, ...options } } return formatValue(ctx, value, ctx.depth) function formatValue (ctx, value, depth) { if (value instanceof Symbol || typeof value === 'symbol') { return String(value) } // nodejs `value.inspect()` parity if ( ctx.customInspect && !(value?.constructor && value?.constructor?.prototype === value) ) { if ( isFunction(value?.inspect) && value?.inspect !== inspect && value !== globalThis && value !== globalThis?.system && value !== globalThis?.__args && value?.inspect[kIgnoreInspect] !== true ) { const formatted = value.inspect(depth, ctx) if (typeof formatted !== 'string') { return formatValue(ctx, formatted, depth) } return formatted } else if (value) { for (const inspectSymbol of inspectSymbols) { if (isFunction(value[inspectSymbol]) && value[inspectSymbol] !== inspect) { const formatted = value[inspectSymbol]( depth, ctx.options, inspect ) if (typeof formatted !== 'string') { return formatValue(ctx, formatted, depth) } return formatted } } } } if (value === undefined) { return 'undefined' } if (value === null) { return 'null' } if (typeof value === 'string') { const formatted = JSON.stringify(value) .replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') return `'${formatted}'` } if (typeof value === 'number' || typeof value === 'boolean') { return String(value) } if (typeof value === 'bigint') { return String(value) + 'n' } if (value instanceof WeakSet) { return 'WeakSet { <items unknown> }' } if (value instanceof WeakMap) { return 'WeakMap { <items unknown> }' } let typename = '' const braces = ['{', '}'] const isArrayLikeValue = isArrayLike(value) try { if (value instanceof MIMEParams) { braces[0] = `MIMEParams(${value.size}) ${braces[0]}` } else if (value instanceof Map) { braces[0] = `Map(${value.size}) ${braces[0]}` } else if (value instanceof Set) { braces[0] = `Set(${value.size}) ${braces[0]}` } } catch { braces.splice(0, braces.length) } let keys = [] try { keys = value instanceof Map ? Array.from(value.keys()) : new Set(Object.keys(value)) } catch {} const enumerableKeys = value instanceof Set ? Array(value.size).fill(0).map((_, i) => i) : Object.fromEntries(Array.from(keys).map((k) => [k, true])) if (ctx.showHidden) { try { const hidden = Object.getOwnPropertyNames(value) for (const key of hidden) { if (value instanceof Error && !/stack|message|name/.test(key)) { keys.add(key) } } } catch (err) {} } if (isArrayLikeValue) { braces[0] = '[' braces[1] = ']' } if (isAsyncFunction(value)) { const name = value.name ? `: ${value.name}` : '' typename = `[AsyncFunction${name}]` } else if (isFunction(value)) { const name = value.name ? `: ${value.name}` : '' typename = `[Function${name}]` } if (value instanceof RegExp) { typename = `${RegExp.prototype.toString.call(value)}` } if (value instanceof Date) { typename = `${Date.prototype.toString.call(value)}` braces[0] = '[' braces[1] = ']' } if (value instanceof Error) { typename = `${Error.prototype.toString.call(value)}` braces[0] = '' braces[1] = '' if (value.cause) { keys.add('cause') } if (value.code) { enumerableKeys.code = true keys.add('code') } } if (isArgumentsObject(value)) { typename = 'Arguments' braces[0] = '{' braces[1] = '}' } else if (types.isSetIterator(value)) { typename = 'Set Iterator' } else if (types.isMapIterator(value)) { typename = 'Map Iterator' } else if (types.isIterator(value)) { typename = 'Iterator' } else if (types.isAsyncIterator(value)) { typename = 'AsyncIterator' } else if (types.isGeneratorFunction(value)) { typename = 'GeneratorFunction' } else if (types.isGeneratorObject(value)) { typename = 'Generator' } else if (types.isAsyncGeneratorFunction(value)) { typename = 'AsyncGeneratorFunction' } if (!(value instanceof Map || value instanceof Set)) { if ( typeof value === 'object' && typeof value?.constructor === 'function' && (value.constructor !== Object && value.constructor !== Array) ) { let tag = value?.[Symbol.toStringTag] || value.constructor.name || value?.toString if (typeof tag === 'function') { tag = tag.call(value) } if (tag === '[object Object]') { tag = '' } braces[0] = `${tag} ${braces[0]}` } if (keys.size === 0 && !(value instanceof Error)) { if (isFunction(value)) { return typename } else if (!isArrayLikeValue || value.length === 0) { return `${braces[0]}${typename}${braces[1]}` } else if (!isArrayLikeValue) { return typename } } } if (depth < 0) { if (value instanceof RegExp) { return RegExp.prototype.toString.call(value) } return '[Object]' } ctx.seen.push(value) const output = [] if (!isArgumentsObject(value) && (isArrayLikeValue || value instanceof Set)) { // const items = isArrayLikeValue ? value : Array.from(value.values()) const size = isArrayLikeValue ? value.length : value.size for (let i = 0; i < size; ++i) { const key = String(i) if (value instanceof Set || hasOwnProperty(value, key)) { if (key === 'length' && Array.isArray(value)) { continue } output.push(formatProperty( ctx, value, depth, enumerableKeys, key, true )) } } for (const key of keys) { if (!/^\d+$/.test(key) && key !== 'length') { output.push(formatProperty( ctx, value, depth, enumerableKeys, key, true )) } } } else if (typeof value === 'function') { for (const key of keys) { if ( !/^\d+$/.test(key) && key !== 'name' && key !== 'length' && key !== 'prototype' && key !== 'constructor' ) { output.push(formatProperty( ctx, value, depth, enumerableKeys, key, false )) } } } else { output.push(...Array.from(keys).map((key) => formatProperty( ctx, value, depth, enumerableKeys, key, false ))) } ctx.seen.pop() if (value instanceof Error) { let out = '' if (value?.message && !value?.stack?.startsWith?.(`${value?.name}: ${value?.message}`)) { out += `${value.name}: ${value.message}\n` } const formatWebkitErrorStackLine = (line) => { const [symbol = '', location = ''] = line.endsWith('@') ? [line.slice(0, -1)] : line.startsWith('@') ? ['', line.slice(1)] : line.split('@') let output = [] const root = new URL('../', import.meta.url || globalThis.location.href).pathname let [context, lineno, colno] = ( maybeURL(location)?.pathname?.split(/:/) || location?.split(/:/) || [] ) if (symbol) { output.push(symbol) } if (context) { context = context.replace(root, '') if (/socket\//.test(context)) { context = context.replace('socket/', 'socket:').replace(/.js$/, '') } } if (context && lineno && colno) { output.push(`(${context}:${lineno}:${colno})`) } else if (context && lineno) { output.push(`(${context}:${lineno})`) } else if (context) { output.push(`${context}`) } else if (!symbol) { output.push('<anonymous>') } output = output.map((entry) => entry.trim()).filter(Boolean) if (output.length) { output.unshift(' at') } return output.filter(Boolean).join(' ') } out += (typeof value?.stack === 'string' ? value.stack : '') .split('\n') .map((line) => line.includes(`${value.name}: ${value.message}`) || /^\s*at\s/.test(line) ? line : formatWebkitErrorStackLine(line) ) .filter(Boolean) .join('\n') if (keys.size) { out += ' {\n' } out += ` ${output.join(',\n ')}` if (keys.size) { out += '\n}' } return out.trim() } const length = output.reduce((p, c) => (p + c.length + 1), 0) if (Object.getPrototypeOf(value) === null) { let tag = value?.[Symbol.toStringTag] || value?.toString if (typeof tag === 'function') { tag = tag.call(value) } braces[0] = `${tag || '[Object: null prototype]'} ${braces[0]}` } if (length > 80) { return `${braces[0]}\n${!typename ? '' : ` ${typename}\n`} ${output.join(',\n ')}\n${braces[1]}` } return `${braces[0]}${typename}${output.length ? ` ${output.join(', ')} ` : ''}${braces[1]}` } function formatProperty ( ctx, value, depth, enumerableKeys, key, isArrayLikeValue ) { const descriptor = { value: undefined } const output = ['', ''] try { descriptor.value = value[key] } catch (err) {} try { Object.assign(descriptor, Object.getOwnPropertyDescriptor(value, key)) } catch (err) {} if (descriptor.get && descriptor.set) { output[1] = '[Getter/Setter]' } else if (descriptor.get) { output[1] = '[Getter]' } else if (descriptor.set) { output[1] = '[Setter]' } if (!hasOwnProperty(enumerableKeys, key)) { output[0] = `[${key}]` } if (!output[1]) { if (ctx.seen.includes(descriptor.value)) { output[1] = '[Circular]' } else { const tmp = value instanceof Set ? Array.from(value.values())[key] : value instanceof Map ? value.get(key) : descriptor.value if (depth === null) { output[1] = formatValue(ctx, tmp, null) } else { output[1] = formatValue(ctx, tmp, depth - 1) } if (output[1].includes('\n')) { if (isArrayLikeValue) { output[1] = output[1] .split('\n') .map((line) => ` ${line}`) .join('\n') .slice(2) } else { output[1] = '\n' + output[1] .split('\n') .map((line) => ` ${line}`) .join('\n') } } } } if (!output[0]) { if (isArrayLikeValue && /^\d+$/.test(key)) { return output[1] } output[0] = JSON.stringify(String(key)) .replace(/^"/, '') .replace(/"$/, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'") } if (value instanceof Map) { return output.join(' => ') } else { return output.join(': ') } } } export function format (format, ...args) { let options = args.pop() if (!options || typeof options !== 'object' || !options?.seen || !options?.depth) { if (options !== undefined) { args.push(options) } options = undefined } if (typeof format !== 'string') { return [format] .concat(args) .map((arg) => inspect(arg, { ...options })) .join(' ') } const regex = /%[dfijloOsuxz%]/ig let i = 0 let str = format.replace(regex, (x) => { if (x === '%%') { return '%' } if (i >= args.length) { return x } if (args[i] === globalThis) { i++ } if (args[i] === globalThis?.system) { i++ return '[System]' } switch (x) { case '%d': return Number(args[i++]) case '%u': return Number(args[i++]) case '%l': return Number(args[i++]) case '%lu': return Number(args[i++]) case '%llu': return Number(args[i++]) case '%zu': return Number(args[i++]) case '%f': return parseFloat(args[i++]) case '%i': return parseInt(args[i++]) case '%o': return inspect(args[i++], { showHidden: true }) case '%O': return inspect(args[i++], {}) case '%j': try { return JSON.stringify(args[i++]) } catch (_) { return '[Circular]' } case '%J': try { return JSON.stringify(args[i++], null, ' ') } catch (_) { return '[Circular]' } case '%s': return String(args[i++]) case '%ls': return String(args[i++]) case '%S': return String(args[i++]) case '%LS': return String(args[i++]) } return x }) for (const arg of args.slice(i)) { if (arg === null || typeof arg !== 'object') { str += ' ' + arg } else { str += ' ' + inspect(arg, { ...options }) } } return str } export function parseJSON (string) { if (string !== null) { string = String(string) try { const encoded = encodeURIComponent(string) // detect back slashes without regex hell as they may not be escaped // ie: '\Users' instead of '\\Users' which results in invalid JSON if (encoded.includes('%5C')) { // use `RegExp` because a literal regex may throw a syntax error // in environments where negative lookbehinds are not supported // see https://stackoverflow.com/a/50434875 for platform support // eslint-disable-next-line prefer-regex-literals const regex = new RegExp('(?<!%5C)%5C', 'g') return JSON.parse(decodeURIComponent(encoded.replace(regex, '%5C%5C'))) } } catch (err) {} try { return JSON.parse(string) } catch (err) {} } return null } export function parseHeaders (headers) { if (Array.isArray(headers)) { headers = headers.map((h) => h.trim()).join('\n') } if (typeof headers !== 'string') { return [] } return headers .split(/\r?\n/) .map((l) => l.trim().split(':')) .filter((e) => e.length >= 2) .map((e) => [e[0].trim().toLowerCase(), e.slice(1).join(':').trim().toLowerCase()]) .filter((e) => e[0].length && e[1].length) } export function noop () {} export class IllegalConstructor { constructor () { throw new IllegalConstructorError() } } const percentageRegex = /^(100(\.0+)?|[1-9]?\d(\.\d+)?)%$/ export function isValidPercentageValue (input) { return percentageRegex.test(input) } export function compareBuffers (a, b) { return toBuffer(a).compare(toBuffer(b)) } export function inherits (Constructor, Super) { Object.defineProperty(Constructor, 'super_', { configurable: true, writable: true, value: Super, __proto__: null }) Object.setPrototypeOf(Constructor.prototype, Super.prototype) } export const ESM_TEST_REGEX = /\b(import\s*[\w{},*\s]*\s*from\s*['"][^'"]+['"]|export\s+(?:\*\s*from\s*['"][^'"]+['"]|default\s*from\s*['"][^'"]+['"]|[\w{}*\s,]+))\s*(?:;|\b)/ /** * @ignore * @param {string} source * @return {boolean} */ export function isESMSource (source) { if (ESM_TEST_REGEX.test(source)) { return true } return false } export function deprecate (...args) { // noop } export const MIMEType = mime.MIMEType export const MIMEParams = mime.MIMEParams export default exports