@interopio/gateway
Version:
[](https://www.npmjs.com/package/@interopio/gateway)
4 lines • 748 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/common/filters.ts", "../src/logger.ts", "../src/common/utilities.ts", "../src/domains/metrics/filters.ts", "../src/metrics/common.ts", "../src/worker/nodeWorker.ts", "../src/worker/core.ts", "../src/metrics/publisher.ts", "../src/metrics/rest.ts", "../src/metrics/file.ts", "../src/main/index.ts", "../src/main/main.ts", "../src/common/encoders.ts", "../src/logging/core.ts", "../src/gateway/core.ts", "../src/constants.ts", "../src/node.ts", "../src/common/messages.ts", "../src/common/types.ts", "../src/reason.ts", "../src/restrictions/compat.ts", "../src/restrictions/antlr4/RestrictionsParser.ts", "../src/restrictions/antlr4/RestrictionsLexer.ts", "../src/restrictions/antlr4/RestrictionsVisitor.ts", "../src/restrictions/visitor.ts", "../src/restrictions.ts", "../src/common/ids.ts", "../src/state/core.ts", "../src/state/peers.ts", "../src/local-node/core.ts", "../src/domains/global/constants.ts", "../src/domains/global/core.ts", "../src/domains/global/messages.ts", "../src/common/peerIdentity.ts", "../src/common/jwt.ts", "../src/common/tokens.ts", "../src/common/context/state.ts", "../src/common/context/messages.ts", "../src/common/context/constants.ts", "../src/common/context/ops.ts", "../src/address.ts", "../src/domains/global/state.ts", "../src/auth/impl.ts", "../src/auth/core.ts", "../src/common/asyncSerializer.ts", "../src/auth/basic.ts", "../src/auth/oauth2.ts", "../src/auth/custom.ts", "../src/mesh/channel-mesh.ts", "../src/domains/agm/core.ts", "../src/domains/agm/constants.ts", "../src/domains/agm/calls.ts", "../src/domains/agm/messages.ts", "../src/domains/agm/utilities.ts", "../src/domains/agm/mthds.ts", "../src/domains/agm/state.ts", "../src/domains/agm/subscriptions.ts", "../src/domains/activity/constants.ts", "../src/domains/activity/core.ts", "../src/state/types/activity.ts", "../src/domains/activity/state.ts", "../src/domains/activity/activities.ts", "../src/domains/activity/messages.ts", "../src/domains/activity/gatewayRequest.ts", "../src/domains/activity/factories.ts", "../src/domains/metrics/core.ts", "../src/domains/metrics/constants.ts", "../src/state/types/metrics.ts", "../src/metrics/custom.ts", "../src/gateway/metrics.ts", "../src/domains/context/core.ts", "../src/state/types/context.ts", "../src/domains/context/constants.ts", "../src/domains/context/messages.ts", "../src/domains/bus/core.ts", "../src/domains/bus/constants.ts", "../src/domains/bus/messages.ts", "../src/gateway/clients.ts", "../src/gateway/scavenger.ts", "../src/versions.ts", "../src/state/types/common.ts", "../src/mesh-node/core.ts", "../src/common/ws/factory.ts", "../src/mesh/ws/broker/client.ts", "../src/mesh/compat.ts", "../src/mesh/ws-mesh.ts", "../src/mesh/ws-mesh/relay.ts", "../src/mesh/rest-directory.ts", "../src/gateway/visibility.ts", "../src/mesh/static-directory.ts"],
"sourcesContent": ["import {IOGateway} from '../../gateway';\n\nfunction isExactMatch(matcher: IOGateway.Filtering.Matcher): matcher is IOGateway.Filtering.ExactMatch {\n return typeof matcher === 'string';\n}\n\nexport function valueMatches(lhs: IOGateway.Filtering.Matcher, rhs?: unknown): boolean {\n if (isExactMatch(lhs)) {\n return lhs === rhs\n } else {\n if (typeof rhs === 'string') {\n return lhs.test(rhs);\n }\n return false;\n }\n}\n\nexport function valuesMatch(matchers: IOGateway.Filtering.Matcher[], value?: string): boolean {\n for (const matcher of matchers) {\n if (valueMatches(matcher, value)) {\n return true;\n }\n }\n return false;\n}\n//https://cljs.github.io/api/syntax/regex\nconst CLOJURESCRIPT_REGEX = /^#(\\(\\?(?<flags>[im]*)\\))?(?<pattern>.*)$/;\n\nconst JAVASCRIPT_REGEX = /^\\/(?<pattern>(?:[^/\\\\]|\\\\.)+)\\/(?<flags>[gimsuyd]*)$/;\n\nexport function regexify(expression: IOGateway.Filtering.Matcher): IOGateway.Filtering.Matcher {\n if (typeof expression === 'string') {\n let groups = JAVASCRIPT_REGEX.exec(expression)?.groups;\n groups ??= CLOJURESCRIPT_REGEX.exec(expression)?.groups;\n\n if (groups?.pattern) {\n return new RegExp(groups.pattern, groups.flags ?? '');\n }\n\n }\n return expression;\n}\n", "import * as core from '@interopio/gateway/logging/core';\n\nexport function getLogger(name: string) {\n return core.getLogger(name);\n}\n", "import { DomainKeys, type Peer } from '../state/types/state.ts';\nimport type { JoinedPeer } from '../state/peers.ts';\nimport { type Draft, produce } from 'immer';\n\nexport type WithRequired<Type, Key extends keyof Type> = Type & {\n [Property in Key]-?: Type[Property]\n}\n\nexport type Omit<Type, Key extends keyof Type> = Pick<Type, Exclude<keyof Type, Key>>\nexport type PartialBy<Type, Key extends keyof Type> = Pick<Partial<Type>, Key> & Omit<Type, Key>;\n\nexport function removev<T>(el: T, c: T[]): T[] {\n return c.reduce((r: T[], e: T) => {\n if (e === el) {\n return r;\n }\n return r.concat(e);\n }, [] as T[]);\n}\n\nexport function ensureDomain<D extends DomainKeys>(peer: Peer, domain: D): JoinedPeer<D> {\n if (peer[domain]) {\n return peer as JoinedPeer<D>;\n }\n return produce(peer, (p: Draft<Peer>) => {\n /* eslint-disable @typescript-eslint/no-explicit-any */\n p[domain] = {} as unknown as any;\n }) as JoinedPeer<D>;\n}\n\nexport function friendlily(o) {\n // Set => array\n // Map => object\n // function is not tested\n if (o instanceof Set) {\n return Array.from(o as Set<unknown>).map(friendlily);\n } else if (o instanceof Array) {\n return o.map(friendlily);\n } else if (o instanceof Map) {\n return friendlily(Object.fromEntries(o));\n } else if (o instanceof Object) {\n return Object.keys(o).reduce((a, k) => {\n a[k] = friendlily(o[k]);\n return a;\n }, {});\n } else {\n return o;\n }\n}\n\nfunction replacerWithPath(replacer: (this: unknown, field: string | number, value: unknown, path: string) => unknown): (this: unknown, field: string | number, value: unknown) => unknown {\n const paths = new Map<unknown, string>();\n return function (this: unknown, field, value) {\n const path = paths.get(this) + (Array.isArray(this) ? `[${field}]` : `.${field}`);\n if (value === Object(value)) paths.set(value, path);\n if (value instanceof RegExp) value = `#${value.source}`;\n return replacer.call(this, field, value, path.replace(/undefined\\.\\.?/, ''));\n };\n}\n/**\n * Creates a function to be used as JSON.stringify() replacer that will replace values for fields in specified paths.\n *\n * @param paths field paths to be sanitized.\n */\nexport function sanitizerOf(...paths: string[]): (this: unknown, field: string | number, value: unknown) => unknown {\n return replacerWithPath((field: string | number, value: unknown, path: string) => {\n if (paths.indexOf(path) !== -1) {\n return '******';\n }\n return value;\n });\n}\n\nconst KEY_COMPARE_FN: ((a: string, b: string) => number) | undefined = (a: string, b: string) => a.localeCompare(b);\n/**\n * Converts an object to a string representations that is stable enough to be used as property or map key.\n */\nexport function objectAsKey(obj: Record<string, unknown>) {\n return JSON.stringify(obj, Object.keys(obj).sort(KEY_COMPARE_FN));\n}\n\nexport function some<T>(e: T | undefined): e is T {\n return !!e;\n}\n", "import {valueMatches, valuesMatch} from '../../common/filters.js';\nimport {IOGateway} from '../../../gateway';\n\nexport function publisherMatches(publisherFilter: Record<string, IOGateway.Filtering.Matcher>,\n repoId: IOGateway.Metrics.Identity): boolean {\n for (const [k, v] of Object.entries(publisherFilter)) {\n const rv = repoId[k];\n if (!valueMatches(v, rv)) {\n return false;\n }\n }\n return true;\n}\n\nexport function metricMatches(metricFilter: IOGateway.Filtering.Matcher[],\n name: IOGateway.Metrics.Name): boolean {\n return valuesMatch(metricFilter, name);\n}\n\nexport function isAllowed(repoId: IOGateway.Metrics.Identity,\n name: IOGateway.Metrics.Name,\n filters?: IOGateway.MetricFilters): boolean {\n const result = filters?.publishers?.reduce((r: boolean | undefined, filterConfig) => {\n if (publisherMatches(filterConfig.identity, repoId)) {\n const blocked = filterConfig.metrics.block ?? filterConfig.metrics.blacklist ?? [];\n if (blocked.length > 0 && metricMatches(blocked, name)) {\n return false;\n } else {\n const allowed = filterConfig.metrics.allow ?? filterConfig.metrics.whitelist ?? [];\n return r || metricMatches(allowed, name);\n }\n }\n return r;\n }, undefined);\n if (result !== undefined) {\n return result;\n }\n const nonMatched: IOGateway.Filtering.Action = filters?.non_matched ?? 'allow';\n return nonMatched === 'allow' || nonMatched === 'whitelist';\n}\n", "import {DataPoint} from './core.js';\nimport {getLogger} from '../logger.js';\nimport {objectAsKey} from '../common/utilities.js';\nimport {IOGateway} from '../../gateway';\n\nconst log = getLogger('gateway.metrics.common');\n\nexport type MetricsEvent = StatusUpdateEvent | DataPointEvent;\n\nexport type StatusUpdateEvent = {\n identity: IOGateway.Metrics.Identity\n status: IOGateway.Metrics.Status\n metadata?: Record<string, unknown>\n}\n\nexport type DataPointEvent = {\n identity: IOGateway.Metrics.Identity,\n metric: IOGateway.Metrics.Definition,\n datapoint: Omit<DataPoint, 'name'>\n}\n\nfunction extractMetadata(metadata?: Record<string, unknown>) {\n if (metadata && Object.keys(metadata).length > 0) {\n return metadata;\n }\n return undefined;\n}\n\nfunction value(v: string | number | object): IOGateway.Metrics.Value {\n if (typeof v === 'object') {\n const composite = Object.entries(v).reduce((acc, [key, val]) => {\n acc[key] = value(val);\n return acc;\n }, {});\n return {value: composite};\n } else if (typeof v === 'string') {\n return {value: v};\n } else {\n return {value: v};\n }\n}\n\nfunction datapoint(dp: Omit<DataPoint, 'name'>): IOGateway.Metrics.DataPoint {\n return {timestamp: dp.timestamp ?? Date.now(), value: value(dp.value)};\n}\n\n\nfunction groupBy<T>(data: Iterable<T>, keyFn: (t: T) => string): Iterable<[string, T[]]> {\n return Array.from(data).reduce((acc, t) => {\n const key = keyFn(t);\n const values = acc.get(key) ?? [];\n if (values.length === 0) {\n acc.set(key, values);\n }\n values.push(t);\n return acc;\n }, new Map<string, T[]>());\n}\n\n/**\n * Conflates a vector of metric updates based on their repository's identity.\n */\nexport function* conflateRepo(maxCount: number, data: MetricsEvent[]): Generator<IOGateway.Metrics.Update> {\n if (log.enabledFor('trace')) {\n log.debug(`conflating ${JSON.stringify(data)}`);\n }\n const dataByIdentity = groupBy(data, (update) => objectAsKey(update.identity));\n\n let current: IOGateway.Metrics.Update | undefined = undefined;\n for (const [, dps] of dataByIdentity) {\n // need to port partition-all?\n for (const e of dps) {\n if (e['status']) {\n const {identity, status, metadata} = e as StatusUpdateEvent;\n // this is a status event\n // emit the current + status message and clear current\n if (current) {\n yield current;\n }\n yield {identity, status, metrics: undefined, metadata: extractMetadata(metadata)};\n current = undefined;\n } else {\n\n // this is a data point\n const {metric: definition, identity, datapoint: dp} = (e as DataPointEvent);\n const name = definition?.name;\n if (!current) {\n current = {identity};\n }\n current.metrics = current.metrics ?? {};\n current.metrics[name] = current.metrics[name] ?? {definition: {...definition}, datapoints: []};\n delete (current.metrics[name].definition as { name?: string })['name'];\n\n current.metrics[name].datapoints.push(datapoint(dp));\n }\n }\n if (current) {\n yield current;\n }\n }\n}\n", "import { fork, ChildProcess } from 'child_process';\nimport {EventEmitter} from 'events';\nimport {fileURLToPath} from 'url';\n\ntype ProcessMessage = {\n type: 'error' | 'message'\n message: string\n data?: unknown\n}\n\nexport class NodeWorker implements Worker {\n private readonly events = new EventEmitter();\n private readonly ps: ChildProcess;\n constructor(url: string | URL, args: readonly string[], options?) {\n this.events = new EventEmitter();\n const modulePath = fileURLToPath(url);\n this.ps = fork(modulePath, args, options);\n this.ps.on('message', (message: string) => {\n let parsed: ProcessMessage;\n try {\n parsed = JSON.parse(message) as ProcessMessage;\n if (parsed?.type === undefined) {\n this.events.emit('messageerror', new Error(`Invalid message: ${message}`));\n }\n } catch (e) {\n this.events.emit('messageerror', e);\n return;\n }\n if (parsed.type === 'error') {\n this.events.emit('error', new Error(parsed.message));\n } else {\n this.events.emit('message', {data: parsed.data});\n }\n });\n this.ps.stdout?.on('data', (data) => {\n console.log(data);\n });\n this.ps.stderr?.on('data', (data) => {\n console.error(data);\n });\n this.ps.on('error', (error) => {\n this.events.emit('error', error);\n });\n this.events.on('message', (message) => {\n if (this.onmessage) {\n this.onmessage(message);\n }\n });\n this.events.on('error', (error) => {\n if (this.onerror) {\n this.onerror(error);\n }\n });\n this.events.on('messageerror', (error) => {\n if (this.onmessageerror) {\n this.onmessageerror(error);\n }\n });\n\n }\n onerror: ((error) => void) | null = null;\n onmessage: ((message) => void) | null = null;\n onmessageerror: ((error) => void) | null = null;\n\n\n postMessage(message: unknown): void {\n this.ps.send({data: message}, (error) =>{\n if (error) {\n this.events.emit('error', error);\n }\n });\n }\n\n terminate(): void {\n this.ps.kill();\n }\n\n addEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void {\n this.events.addListener(type, listener);\n }\n removeEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void {\n this.events.removeListener(type, listener);\n }\n\n dispatchEvent(event: Event): boolean {\n return this.events.emit(event.type, event);\n }\n}\n", "\nasync function nodeWorker(url: URL, parameters?: Record<string, string>, options?) {\n const {NodeWorker} = await import('./nodeWorker.js');\n const args = Object.entries(parameters ?? {}).map(([k,v]) => `--${k}=${v}`);\n return new NodeWorker(url, args, options);\n}\n\nasync function webWorker(url: URL, parameters?: Record<string, string>, options?: & WorkerOptions) {\n Object.entries(parameters ?? {}).forEach(([k, v]) => url.searchParams.append(k, v));\n return new Worker(url, options);\n}\n\nexport function newWorker({url, parameters, options}: {url: URL, parameters?: Record<string, string>, options?: unknown}): Promise<Worker> {\n return (\n typeof process !== 'undefined'\n ? nodeWorker(url, parameters, options)\n : webWorker(url, parameters, options as WorkerOptions))\n ;\n}\n", "import * as core from './core.ts';\nimport * as common from './common.ts';\nimport {newWorker} from '../worker/core.ts'\nimport type { Logger, LogEvent, LogConfig } from '../../types/logging/core';\nimport { getLogger, logEvent } from '@interopio/gateway/logging/core';\nimport {deserializeError} from 'serialize-error';\nimport {sanitizerOf} from '../common/utilities.ts';\nimport {isAllowed} from '../domains/metrics/filters.ts';\nimport type { PublishCommand } from './publisher/types.ts';\nimport { IOGateway } from '../../gateway';\n\nconst log: Logger = getLogger('gateway.metrics.publisher');\n\nfunction makeStatusUpdate(repoId: IOGateway.Metrics.Identity,\n status: core.Status, metadata?: unknown): common.StatusUpdateEvent {\n const statusUpdate = {identity: repoId, status: {...status, 'updated-at': status.updated, 'expires-at': status.expires}};\n if (metadata) {\n statusUpdate['metadata'] = metadata;\n }\n return statusUpdate;\n}\n\ntype LatestStatus = {\n status: Omit<core.Status, 'timestamp' | 'expires' | 'expires-at' | 'updated-at'>\n timestamp?: number,\n expires?: number,\n initial: boolean,\n stopped: boolean\n}\n\nclass BasicRepository implements core.Repository {\n private running: boolean = true;\n private readonly latestStatus: LatestStatus = {\n initial: true,\n stopped: false,\n status: {state: 0, updated: Date.now(), description: 'Running'}\n };\n private heartbeatCleanup?: NodeJS.Timeout;\n private readonly metrics: Map<string, IOGateway.Metrics.Definition> = new Map();\n\n constructor(private readonly publisher: Publisher,\n private readonly filters: IOGateway.MetricFilters | undefined,\n private readonly repoId: IOGateway.Metrics.Identity,\n private readonly heartbeatInterval: number,\n private readonly metadata?: unknown) {\n }\n\n start() {\n log.info(`starting repository for ${JSON.stringify(this.repoId)} with heartbeat interval ${this.heartbeatInterval}ms`);\n const now = Date.now();\n if (this.latestStatus.stopped) {\n this.latestStatus.status.state = 0;\n this.latestStatus.status.description = 'Running';\n this.latestStatus.status.updated = now;\n this.latestStatus.initial = true;\n this.latestStatus.stopped = false;\n }\n if (this.heartbeatInterval >= 0) {\n const latest = this.latestStatus.status;\n const status: core.Status = {\n ...latest,\n timestamp: now,\n expires: now + (3 * this.heartbeatInterval)\n };\n const msg = makeStatusUpdate(this.repoId, status, this.metadata);\n this.publisher(msg);\n }\n this.running = true;\n if (this.heartbeatInterval > 0) {\n this.heartbeatCleanup = setInterval(() => {\n if (!this.running) {\n return;\n }\n const now = Date.now();\n const latest = this.latestStatus.status;\n const status: core.Status = {\n ...latest,\n timestamp: now,\n expires: now + (3 * this.heartbeatInterval)\n };\n const msg = makeStatusUpdate(this.repoId, status, this.metadata);\n this.publisher(msg);\n }, this.heartbeatInterval);\n }\n }\n\n stop() {\n log.info(`stopping repository for ${JSON.stringify(this.repoId)}`);\n this.running = false;\n if (this.heartbeatCleanup) {\n clearInterval(this.heartbeatCleanup);\n }\n const now = Date.now();\n this.latestStatus.stopped = true;\n this.status({\n state: -1,\n timestamp: now,\n updated: now,\n expires: now,\n description: 'Repository Stopped'\n });\n }\n\n add(definitions: core.Definitions) {\n for (const def of definitions) {\n const key = def.name;\n if (!(this.metrics.has(key) || !(this.filters === undefined || isAllowed(this.repoId, key, this.filters)))) {\n this.metrics.set(key, def);\n }\n }\n }\n\n publish(dataPoints: core.DataPoints) {\n if (this.running && dataPoints.length > 0) {\n for (const dp of dataPoints) {\n const metric = this.metrics.get(dp.name);\n if (metric) {\n const msg: common.DataPointEvent = {\n identity: this.repoId,\n metric: metric,\n datapoint: {...dp}\n };\n delete msg.datapoint['name'];\n this.publisher(msg);\n }\n }\n }\n }\n\n status(status: core.Status) {\n const publisherStatus: LatestStatus = {...this.latestStatus};\n Object.assign(publisherStatus.status, status);\n publisherStatus.timestamp = status.timestamp;\n publisherStatus.expires = status.expires;\n\n const statusChanged = publisherStatus.status.state !== this.latestStatus.status.state;\n if (this.latestStatus.initial || statusChanged) {\n publisherStatus.status.updated = status.timestamp;\n }\n publisherStatus.initial = false;\n\n Object.assign(this.latestStatus, publisherStatus);\n\n if (this.running || publisherStatus.stopped) {\n const msg = makeStatusUpdate(this.repoId, status, this.metadata);\n this.publisher(msg);\n }\n }\n}\n\nexport class BasicRepositoryFactory implements core.RepositoryFactory {\n constructor(\n private readonly config: IOGateway.BasicMetricsPublisherConfig | undefined,\n private readonly publisher: Publisher) {\n }\n\n repository(peerIdentity: IOGateway.Metrics.Identity, opts?: core.Options): core.Repository {\n const peerMetadata = opts?.metadata;\n const heartbeatInterval = this.config?.heartbeats ?? 0;\n return new BasicRepository(this.publisher, this.config?.filters, peerIdentity, heartbeatInterval, peerMetadata);\n }\n\n async shutdown() {\n await this.publisher.close();\n }\n\n on(listener: (event: common.MetricsEvent) => void) {\n this.publisher.on(listener);\n }\n}\n\n\nexport interface Publisher {\n (msg: common.MetricsEvent): void\n on(listener: (event: common.MetricsEvent) => void): void\n close(): Promise<void>\n}\n\nexport type WorkerConfig = {\n preload?: string,\n worker?: { url: URL, parameters: {logLevel?: LogConfig['level']}, options?: unknown }\n publishFn?: ((command: PublishCommand) => Promise<unknown>) | string\n}\n\n\nexport interface WorkerCommandType {\n \"start\": {\n cfg: BasicPublisherConfig | undefined,\n publishFn: ((command: PublishCommand) => Promise<unknown>) | string\n }\n \"update\": IOGateway.Metrics.Update\n \"stop\": unknown\n}\n\nexport type WorkerRequest<C extends keyof WorkerCommandType> = {\n id: number\n cmd: C,\n arg: WorkerCommandType[C]\n}\n\nexport type BasicPublisherConfig = IOGateway.BasicMetricsPublisherConfig & {preload?: string, worker?: {url: URL, options?: unknown}};\n\nexport async function basicPublisher<C extends BasicPublisherConfig>(\n cfg: C | undefined,\n xform: (data: common.MetricsEvent[]) => Iterable<IOGateway.Metrics.Update>,\n publishFn: ((command: PublishCommand) => Promise<unknown>) | string\n): Promise<Publisher> {\n log.info(`creating publisher with configuration\\n${JSON.stringify(cfg, sanitizerOf('authentication.password'))}`);\n\n // add support for in process publishing\n const workerCfg = cfg?.worker;\n if (workerCfg === undefined) {\n throw new Error('in proc publishing is not supported. please provide worker configuration');\n }\n const cfgWithoutWorker = cfg ?\n Object.fromEntries(\n Object.entries(cfg)\n .filter(([key]) => key !== 'worker')\n ) : {};\n\n try {\n return await publishInWorker(publishFn as string, cfgWithoutWorker, xform, workerCfg);\n } catch (e) {\n log.error(`Failed to create basic publisher`, e);\n throw e;\n }\n}\n\nclass WorkerPublisher {\n private readonly maxQueueSize: number;\n private readonly cfg: Omit<BasicPublisherConfig, 'worker'>;\n private readonly worker: Worker\n private lastId: number;\n private readonly processing: Map<number, {resolver: {resolve, reject}}>;\n private readonly promises: Map<number, Promise<void>>;\n private readonly listeners: ((event: common.MetricsEvent) => void)[];\n constructor(worker: Worker, cfg: Omit<BasicPublisherConfig, 'worker'>) {\n const {buffer_size: bufferSize} = cfg;\n this.maxQueueSize = bufferSize ?? 1000;\n this.cfg = cfg;\n this.lastId = 0;\n this.worker = worker;\n this.processing = new Map<number, { resolver: { resolve, reject } }>();\n this.promises = new Map<number, Promise<void>>();\n this.listeners = [];\n }\n\n async start(publishFn: string) {\n await new Promise<void>((resolve) => {\n this.worker.onerror = (event) => {\n log.error(`error from worker: ${event.message}`);\n };\n this.worker.onmessageerror = (event) => {\n log.error(`error receiving message: ${event.data}`);\n }\n this.worker.onmessage = (event) => {\n if (event.data.ready) {\n resolve();\n return;\n }\n if (event.data.id) {\n const {id, result, error} = event.data;\n const p = this.processing.get(id);\n if (p) {\n const {resolver} = p;\n try {\n if (error) {\n resolver.reject(deserializeError(error));\n } else {\n resolver.resolve(result);\n }\n } finally {\n this.processing.delete(id);\n }\n } else {\n log.error(`unknown message id: ${id}`);\n }\n } else if (event.data.log) {\n const {level, time, name, message, data} = event.data.log as LogEvent;\n logEvent({level, time: new Date(time), name, message, data: data.map(d => deserializeError(d))});\n } else if (event.data.event) {\n this.listeners.forEach(l => l(event.data.event));\n }\n };\n });\n try {\n /*const context = */await this.enqueue('start', {cfg: this.cfg, publishFn})[1];\n log.info(`publisher worker [${publishFn}] started`);\n } catch (e) {\n log.error(`error starting publisher worker [${publishFn}]: ${e}`);\n this.worker.terminate();\n throw e;\n }\n }\n\n private enqueue<T, K extends keyof WorkerCommandType>(cmd: K, arg: WorkerCommandType[K]): [number, Promise<T>] {\n if (this.processing.size >= this.maxQueueSize) {\n log.warn(`processing queue is full. dropping cmd: ${JSON.stringify(cmd)}`);\n }\n const id = ++this.lastId;\n return [id, new Promise<T>((resolve, reject) => {\n this.processing.set(id, {resolver: {resolve, reject}});\n const request: WorkerRequest<K> = {id, cmd, arg};\n this.worker.postMessage(request);\n })];\n }\n\n next(u: IOGateway.Metrics.Update) {\n const [id, p] = this.enqueue<void, 'update'>('update', u);\n p.catch((e) => {\n log.warn(`update [${id}] error`, e);\n }).finally(() => {\n this.promises.delete(id);\n });\n }\n\n async stop(rejectProcessing = false, timeout = 1000) {\n log.info(`stopping publisher worker rejectProcessing: ${rejectProcessing}, timeout: ${timeout}, promises: ${this.promises.size}`);\n const rejectProcessingFn = (reason: 'now' | 'timeout') => {\n this.processing.forEach(({resolver}) => {\n resolver.reject(new Error(`reject on stop: ${reason}`));\n });\n };\n try {\n if (rejectProcessing) {\n rejectProcessingFn('now');\n }\n const pending = Promise.allSettled(this.promises);\n const stopPromise = this.enqueue('stop', undefined)[1];\n await new Promise<void>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n rejectProcessingFn('timeout');\n reject(new Error('timeout'));\n }, timeout);\n pending.then((outcomes) => {\n clearTimeout(timeoutId);\n for (const outcome of outcomes) {\n if (outcome.status === 'rejected') {\n log.error(`Pending future failed with ${outcome.reason} on stop`)\n }\n }\n resolve();\n });\n });\n await stopPromise;\n if (this.promises.size > 0) {\n log.error(`uncleared promises: ${this.promises.size}`);\n }\n log.info(`publisher worker stopped`);\n } finally {\n this.worker.terminate();\n }\n }\n\n on(listener: (event: common.MetricsEvent) => void) {\n this.listeners.push(listener);\n }\n\n}\n\nasync function publishInWorker(publishFn: string,\n cfg: Omit<BasicPublisherConfig, 'worker'>,\n xform: (data: common.MetricsEvent[]) => Iterable<IOGateway.Metrics.Update>,\n workerCfg: {url: URL, parameters?: Record<string, string>, options?: unknown}): Promise<Publisher> {\n\n\n const worker = new WorkerPublisher(await newWorker(workerCfg), cfg);\n await worker.start(publishFn);\n\n\n const publisher: Publisher = (msg: common.MetricsEvent): void => {\n for (const u of xform([msg])) {\n worker.next(u);\n }\n };\n publisher.close = async (): Promise<void> => {\n await worker.stop();\n };\n publisher.on = (listener: (event: common.MetricsEvent) => void): void => {\n worker.on(listener);\n };\n\n return publisher;\n}\n\nexport function basicRepositoryFactory(config: IOGateway.BasicMetricsPublisherConfig | undefined, publisher: Publisher): BasicRepositoryFactory {\n return new BasicRepositoryFactory(config, publisher);\n}\n", "import {basicRepositoryFactory, basicPublisher, WorkerConfig} from './publisher.js';\nimport {conflateRepo} from './common.js';\nimport * as core from './core.js';\nimport {IOGateway} from '../../gateway';\n\nexport type RestConfig = IOGateway.RestMetricsPublisherConfig & WorkerConfig\n\nexport async function restPublisher(config?: RestConfig) {\n const conflationMaxCount = config?.conflation?.[\"max-datapoints-repo\"] ?? 50;\n return await basicPublisher<RestConfig>(\n config,\n (data) => conflateRepo(conflationMaxCount, data),\n config?.publishFn ?? '@interopio/gateway/metrics/publisher/rest'\n );\n}\n\nexport async function restRepositoryFactory(config?: RestConfig): Promise<core.RepositoryFactory> {\n return basicRepositoryFactory(config, await restPublisher(config));\n}\n", "import {basicPublisher, basicRepositoryFactory, Publisher, WorkerConfig} from './publisher.js';\nimport {conflateRepo} from './common.js';\nimport * as core from './core.js';\nimport {getLogger} from '../logger.js';\nimport {IOGateway} from '../../gateway';\n\nconst log = getLogger('gateway.metrics.file');\n\nexport type FileConfig = IOGateway.FileMetricsPublisherConfig & WorkerConfig\n\nasync function filePublisher(config: FileConfig): Promise<Publisher> {\n const conflationMaxCount = config.conflation?.[\"max-datapoints-repo\"] ?? 50;\n log.info(`will record metrics to ${config.location}`);\n return await basicPublisher(\n config,\n (data) => conflateRepo(conflationMaxCount ?? 1, data),\n config?.publishFn ?? '@interopio/gateway/metrics/publisher/file');\n}\n\nexport async function fileRepositoryFactory(config: FileConfig) : Promise<core.RepositoryFactory> {\n return basicRepositoryFactory(config, await filePublisher(config));\n}\n", "import * as IOGateway from './main.js';\n\nexport {IOGateway};\n\nexport default IOGateway.Factory;\n", "export * as Encoding from '../common/encoders.ts';\nexport * as Logging from '../logging/core.ts';\nexport * as Filtering from '../common/filters.ts';\nexport * from '../gateway/core.ts';\n", "import t from 'transit-js';\n\nclass BiMap {\n private readonly map: Map<string, string>;\n private readonly reverse = new Map<string, string>();\n constructor(map?: Map<string, string>) {\n if (map) {\n for (const [k, v] of map) {\n this.reverse.set(v, k);\n }\n } else {\n // just to not be undefined\n map = this.reverse;\n }\n this.map = map;\n }\n\n public get(key: string): string | undefined {\n return this.map.get(key);\n }\n public rev(value: string): string | undefined {\n return this.reverse.get(value);\n }\n}\n\nfunction namespacedKeyword(value: string, namespaces: BiMap): Keyword {\n const idx = value.indexOf('/');\n if (idx === -1) {\n return t.keyword(value);\n }\n const prefix = value.substring(0, idx);\n const namespace = namespaces?.get(prefix) ?? prefix;\n return t.keyword(namespace + value.substring(idx));\n}\ninterface Keyword {\n name(): string\n namespace(): string | null\n}\nfunction parseKeyword(keyword: Keyword, namespaces: BiMap): string {\n const name = keyword.name();\n const namespace = keyword.namespace();\n if (namespace === null) {\n return name;\n }\n const prefix = namespaces.rev(namespace) ?? namespace;\n return prefix + '/' + name;\n}\n\nfunction toTransit<T>(m: T, namespaces: BiMap, keywordize?: Map<string, KeywordizeCommand>, root = '') {\n if (m instanceof Array) {\n return m.map((v) => {\n // todo support array paths\n return v;\n });\n } else if (m instanceof Object) {\n const data = t.map();\n for (const k in m) {\n const kc = keywordize?.get(root);\n const key = kc === null ? k : namespacedKeyword(k, namespaces);\n const v = m[k];\n const path = `${root}/${k}`; // allow over\n\n const vc = keywordize?.get(path);\n const keywordizeValue = vc !== undefined && typeof v === 'string' && (vc === '*' || vc?.has(v));\n if (keywordizeValue) {\n data.set(key, namespacedKeyword(v, namespaces));\n } else {\n if (kc === null && !keywordize?.has(path)) {\n keywordize?.set(path, null);\n }\n const value = toTransit(v, namespaces, keywordize, path);\n data.set(key, value);\n }\n }\n return data;\n } else {\n return m;\n }\n}\n/* eslint-disable @typescript-eslint/no-explicit-any */\nfunction toJS<T>(data: any, namespaces: BiMap): T {\n if (t.isKeyword(data)) {\n return parseKeyword(data as Keyword, namespaces) as T;\n } else if (t.isMap(data)) {\n const map = {};\n for (const [k, v] of data) {\n const key = t.isKeyword(k) ? parseKeyword(k as Keyword, namespaces) : k;\n map[key] = toJS(v, namespaces);\n }\n return map as T;\n } else if (t.isList(data) || data instanceof Array) {\n const list = [];\n for (const e of data) {\n list.push(toJS(e, namespaces));\n }\n return list as T;\n }\n return data;\n}\n\n// exports\nexport type Codec<T, R> = {\n encode: (msg: T) => R;\n decode: (data: R) => T;\n};\n\nexport type KeywordizeCommand\n = '*' // keywordize for all string values for ex {:type :cluster}\n | Set<string> // keywordize only specific values\n | null // stop keywordization (even for keys)\n ;\n\nexport function transit<T>(opts?: { keywordize?: Map<string, KeywordizeCommand>, verbose?: boolean, namespaces?: Map<string, string> }): Codec<T, string> {\n const type: t.Encodings = opts?.verbose ? 'json-verbose' : 'json';\n const namespaces = new BiMap(opts?.namespaces);\n return {\n encode: (msg: T): string => {\n const data = toTransit(msg, namespaces, opts?.keywordize);\n return t.writer(type).write(data);\n },\n decode: (data: string): T => {\n let map;\n try {\n map = t.reader(type).read(data);\n } catch (err) {\n throw new Error(`\"${data}\" is not valid TRANSIT`, {cause: err});\n }\n return toJS<T>(map, namespaces);\n }\n }\n}\n\nexport function json<T>(): Codec<T, string> {\n return {\n encode: JSON.stringify,\n decode: JSON.parse\n }\n}\n\nfunction identity<T>(v: T): T {\n return v;\n}\n\nfunction transform<T>(v: T): T {\n // clojurescript compatibility with (js->clj)\n // https://github.com/clojure/clojurescript/blob/067eaef03678a06d83caf3f66ddf20f9ee71db5b/src/main/cljs/cljs/core.cljs#L11193\n\n // deep transform arrays\n if (v instanceof Array || Array.isArray(v)) {\n return v.map(transform) as T;\n }\n // deep transform objects (but not dates for example)\n if (v?.constructor === Object) {\n return Object.keys(v).reduce((a: T, k: string) => {\n a[k] = transform(v[k]);\n return a;\n }, {} as T);\n }\n // nullify undefined values\n if (v === undefined) {\n return null as T;\n }\n return v;\n}\n\nexport function direct<T>(opts?: {cljs?: boolean}): Codec<T, T> {\n return {\n encode: identity,\n decode: (opts?.cljs ?? false) ? transform : identity\n }\n}\n", "import * as API from '../../types/logging/core';\n\nenum LogLevel {\n off = 0,\n error = 200,\n warn = 300,\n info = 400,\n debug = 500,\n trace = 600,\n all = Number.MAX_SAFE_INTEGER\n}\n\nconst loggers: {[key: string]: Logger} = Object.create(null);\nconst logLevels: Record<string, LogLevel> = {};\nexport const ROOT_LOGGER_NAME = 'gateway';\nlogLevels[ROOT_LOGGER_NAME] = LogLevel.trace;\nlet appender : (e: API.LogEvent) => void = (e) => {\n console[e.level](`${e.time.toISOString()} ${e.level.toUpperCase()} [${e.name}] - ${e.message}`, ...e.data);\n};\n\ntype LogLevelKey = Exclude<keyof typeof LogLevel, 'off' | 'all'>;\n\nexport interface Logger extends API.Logger {\n (level: LogLevelKey, message?: string, ...args: unknown[])\n}\n\nexport function logEvent(event: API.LogEvent) {\n const { name, level } = event;\n checkNameAndConfigureLevel(name);\n if (isLevelEnabledFor(level, name)) {\n appender(event);\n }\n}\n\nfunction log(this: Logger, name: string, level: LogLevelKey, message: string, ...data: unknown[]) {\n if (this.enabledFor(level)) {\n const time = new Date();\n const event: API.LogEvent = {time, level, name, message, data};\n appender(event);\n }\n}\n\nfunction isLevelEnabledFor(level: LogLevelKey, name: string) {\n const logLevelValue = LogLevel[level];\n const configuredLevel = logLevels[name];\n return configuredLevel >= logLevelValue;\n}\n\nfunction newLogger(name: string): Logger {\n checkNameAndConfigureLevel(name);\n const logger: Logger = function(level: LogLevelKey, message: string, ...args:unknown[]) {\n log.call(logger, name, level, message, ...args);\n } as Logger;\n function methods(key: string): key is LogLevelKey {\n return isNaN(Number(key));\n }\n for (const method of Object.keys(LogLevel).filter(methods)) {\n logger[method] = function(message: string, ...args: unknown[]) {\n log.call(logger, name, method, message, ...args);\n };\n }\n logger.enabledFor = function(level: LogLevelKey) {\n return isLevelEnabledFor(level, name);\n }\n logger.child = function (suffix: string) {\n return getLogger(`${name}.${suffix}`);\n }\n return logger;\n}\n\nfunction checkNameAndConfigureLevel(name: string) {\n if (!name.startsWith(ROOT_LOGGER_NAME)) throw new Error(`Logger name must start with ${ROOT_LOGGER_NAME}`);\n if (!logLevels[name]) {\n const orderedEntries = Object.entries(logLevels).sort(([n1,], [n2,])=> n2.localeCompare(n1));\n const [, level] = orderedEntries.find(([prefix,]) => name.startsWith(prefix))!;\n logLevels[name] = level;\n }\n}\n\nexport function getLogger(name: string): API.Logger {\n let logger: Logger | undefined = loggers[name];\n if (logger === undefined) {\n logger = newLogger(name);\n loggers[name] = logger;\n }\n return logger;\n}\n\nexport function configure(config: API.LogConfig) {\n function updateLevels(prefix: string, level: API.LogConfigLevel) {\n for (const name of Object.keys(logLevels).filter(k => k.startsWith(prefix))) {\n logLevels[name] = LogLevel[level];\n }\n if (logLevels[prefix] === undefined) {\n logLevels[prefix] = LogLevel[level];\n }\n }\n\n const logLevel = config.level;\n\n if (typeof logLevel === 'string') {\n logLevels[ROOT_LOGGER_NAME] = LogLevel[logLevel];\n updateLevels(ROOT_LOGGER_NAME, logLevel);\n }\n else if (typeof logLevel === 'object') {\n const orderedEntries = Object.entries(logLevel).sort(([n1], [n2]) => n1.localeCompare(n2));\n for (const [prefix, level] of orderedEntries) {\n updateLevels(prefix, level);\n }\n }\n\n appender = config.appender ?? appender;\n}\n", "import {getLogger} from '../logger.js';\nimport {localNode} from '../local-node/core.js';\nimport {Authenticators, globalDomain} from '../domains/global/core.js';\nimport {Authenticator} from '../auth/core.ts';\nimport * as basic from '../auth/basic.js';\nimport * as oauth2 from '../auth/oauth2.js';\nimport * as custom from '../auth/custom.js';\nimport { channelCluster } from '../mesh/channel-mesh.js';\nimport {GatewayNode, MessageType, RequestType} from '../node.js';\nimport {LocalAddress} from '../state/types/common.js';\nimport {agmDomain} from '../domains/agm/core.js';\nimport {activityDomain} from '../domains/activity/core.js';\nimport {metricsDomain, MetricPublishers} from '../domains/metrics/core.js';\nimport {metrics} from './metrics.js';\nimport {contextDomain, ContextDomainOptions} from '../domains/context/core.js';\nimport {busDomain} from '../domains/bus/core.js';\nimport Scavenger from './scavenger.js';\nimport {GATEWAY_VERSION} from '../versions.js';\nimport {sanitizerOf, WithRequired} from '../common/utilities.js';\nimport {meshNode} from '../mesh-node/core.js';\nimport {Domain} from '../domain.js';\nimport {websocketBrokerMesh} from '../mesh/ws/broker/client.js';\nimport {websocketCluster} from '../mesh/ws-mesh.js';\nimport {restDirectory} from '../mesh/rest-directory.js';\nimport {ClientInfo, Clients, addClient, removeClient} from './clients.js';\nimport * as GatewayEncoders from '../common/encoders.js';\nimport {IOGateway} from '../../gateway';\nimport {defaultPeerRestrictions, defaultRestrictions} from './visibility.js';\nimport { Mesh } from '../mesh.js';\nimport {staticDirectory} from '../mesh/static-directory.js';\n\nconst log = getLogger('gateway');\n\nfunction authenticators(config?: IOGateway.AuthenticationConfig,\n globals?: IOGateway.GatewayConfig['globals']): Authenticators {\n const available = (config?.available as Exclude<string, 'default' | 'available'>[] ?? ['basic']).reduce((a, k) => {\n if (k === 'basic') {\n a[k] = basic.authenticator(config?.basic ?? {});\n return a;\n }\n if (k === 'oauth2') {\n a[k] = oauth2.authenticator(config?.oauth2 ?? {}, globals?.fetch ?? globalThis.fetch);\n return a;\n }\n const authConfig = config?.[k] ?? {};\n if (authConfig['authenticator']) {\n const authFn = authConfig['authenticator'];\n const configWithoutAuthenticator = {...authConfig};\n delete configWithoutAuthenticator['authenticator'];\n a[k] = custom.authenticator(configWithoutAuthenticator, authFn);\n return a;\n }\n return a;\n\n }, {} as Record<string, Authenticator>);\n return {'default': config?.default as string ?? 'basic', available};\n}\n\nfunction createWebSocketCluster(endpoint: string, relays?: string, directory?: IOGateway.StaticMeshDirectoryConfig | IOGateway.RestMeshDirectoryConfig, globals?: IOGateway.GatewayConfig['globals']): Mesh {\n let clusterUri = endpoint;\n if (endpoint.startsWith('http')) {\n clusterUri = endpoint.replace('http', 'ws');\n clusterUri += endpoint.endsWith('/') ? '' : '/';\n clusterUri += 'cluster';\n }\n let relaysUri = relays ?? endpoint;\n if (relays === undefined && relaysUri.startsWith('http')) {\n relaysUri = relaysUri.replace('http', 'ws');\n relaysUri += relaysUri.endsWith('/') ? '' : '/';\n relaysUri += 'relays';\n }\n if (directory?.['members'] !== undefined) {\n return websocketCluster(\n {\n cluster: `${clusterUri}`,\n relays: `${relaysUri}`\n },\n staticDirectory((directory as IOGateway.StaticMeshDirectoryConfig).members),\n globals?.websocket);\n } else {\n const announceUri = (directory as IOGateway.RestMeshDirectoryConfig | undefined)?.uri ?? endpoint;\n const announceInterval = (directory as IOGateway.RestMeshDirectoryConfig | undefined)?.interval;\n return websocketCluster(\n {\n cluster: `${clusterUri}`,\n relays: `${relaysUri}`\n },\n restDirectory({uri: announceUri, announceInterval}, globals?.fetch),\n globals?.websocket);\n }\n}\n\nfunction gatewayNode<T extends MessageType = MessageType>(domains: Domain<T>[],\n nodeId?: string,\n signingKey?: string,\n mesh?: IOGateway.MeshConfig,\n globals?: IOGateway.GatewayConfig['globals']): GatewayNode {\n if (mesh) {\n nodeId = mesh?.node ?? nodeId;\n\n const clusterConfig = mesh.cluster;\n if (clusterConfig) {\n const {endpoint, relays, directory} = clusterConfig;\n const cluster = createWebSocketCluster(endpoint, relays, directory, globals);\n return meshNode(cluster, domains, {nodeId, signingKey});\n\n }\n\n const channel = mesh.channel;\n\n if (channel) {\n const cluster = channelCluster(channel);\n return meshNode(cluster, domains, {nodeId, signingKey});\n }\n\n const endpoint = mesh.broker?.endpoint;\n\n if (endpoint) {\n const cluster = websocketBrokerMesh(endpoint, globals?.websocket);\n return meshNode(cluster, domains, {nodeId, signingKey});\n }\n\n }\n return localNode(domains, {nodeId, signingKey});\n}\n\ntype GatewayState = {\n config: IOGateway.GatewayConfig,\n auth: Authenticators,\n factories: MetricPublishers,\n node: GatewayNode\n clients: Clients,\n scavenger: Scavenger,\n environment?: {endpoint?: string},\n};\n\nconst codec = GatewayEncoders.direct<IOGateway.Message>({cljs: true});\n\nconst DEFAULT_CLIENT_BUFFER_SIZE = 100;\nconst INFO_DESCRIPTION = 'io.Gateway';\nconst INFO = {version: GATEWAY_VERSION, description: INFO_DESCRIPTION};\n\nclass Gateway implements IOGateway.Gateway {\n\n #gateway: GatewayState | undefined;\n readonly #config: Readonly<IOGateway.GatewayConfig>;\n\n constructor(config: Readonly<IOGateway.GatewayConfig>) {\n this.#config = config;\n }\n\n protected async doStart(configuration: Readonly<IOGateway.GatewayConfig>,\n environment?: {endpoint?: string }): Promise<GatewayState> {\n const auth = authenticators(configuration.authentication, configuration.globals);\n const configuredContextLifetime = configuration.contexts?.lifetime;\n const contextPeerRestrictions = defaultPeerRestrictions('context', configuration.peers?.visibility) ?? 'cluster';\n const contextOptions: ContextDomainOptions = {\n