UNPKG

iopa

Version:

API-first, Internet of Things (IoT) stack for Typescript, official implementation of the Internet Open Protocols Alliance (IOPA) reference pattern

160 lines (140 loc) 4.93 kB
/* * Internet Open Protocol Abstraction (IOPA) * Copyright (c) 2016-2022 Internet Open Protocols Alliance * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { IContextIopaLegacy, IContextIopa, IFactory } from '@iopa/types' import { mergeContext } from '../util/shallow' import FreeList from '../util/freelist' import { ContextCore } from './context-core' /** Represents IopaContext Factory of up to 100 items */ export default class Factory< C, T extends IContextIopa<C> & { init: Function; dispose?: Function } > implements IFactory<C, T> { protected _factory: FreeList<T> public constructor( name: string = 'IopaContext', size: number = 100, factory: () => T = () => new ContextCore() as any ) { this._factory = new FreeList(name, size, factory) } /** Creates a new IOPA Context */ public createContext( urlOrRequest?: string | Request, options?: Partial<IContextIopaLegacy<C>> ): T { const optionsValid = _validOptions(options) const context: T & { init: Function dispose?: Function } & IContextIopa<C> = this._factory.alloc().init() context.dispose = this._dispose.bind(this, context) context.set('iopa.Id', `#${_nextSequence()}`) context.set('server.Timestamp', Date.now()) if (urlOrRequest) { if (typeof urlOrRequest === 'string') { const url = urlOrRequest context.set('iopa.OriginalUrl', url) context.set('iopa.Url', new URL(url)) } else { const url = urlOrRequest.url context.set('iopa.OriginalUrl', url) context.set('iopa.Url', new URL(url)) context.set('iopa.RawRequest', urlOrRequest) context.set('iopa.RawRequestClone', urlOrRequest.clone()) } } context.createChild = this.createChildContext.bind(this, context) mergeContext(context, optionsValid) return context as T } /** Creates a new IOPA Context that is a child request/response of a parent Context */ protected createChildContext( parentContext: T, urlPath?: string, defaults?: any ): T { const defaultsValid = _validOptions(defaults) const context = this.createContext() as unknown as T context.set('server.Events', parentContext.get('server.Events')) context.set('server.Capabilities', parentContext.get('server.Capabilities')) context.app = parentContext.app const parentUrl = parentContext.get('iopa.Url') || new URL('https://schema.iopa.io') const childUrl = `${parentUrl.protocol}//${parentUrl.host}/${ urlPath ? urlPath.replace(/^\//, '') : parentUrl.pathname.replace(/^\//, '') }` context.set('iopa.OriginalUrl', childUrl) context.set('iopa.Url', new URL(childUrl)) if (parentContext.get('iopa.RawRequest')) { context.set( 'iopa.RawRequest', new Request(childUrl, { headers: defaultsValid['iopa.Headers'] || parentContext.get('iopa.Headers'), method: defaultsValid['iopa.Method'] || parentContext.get('iopa.Method'), body: null }) ) context.set( 'iopa.RawRequestClone', new Request(childUrl, { headers: defaultsValid['iopa.Headers'] || parentContext.get('iopa.Headers'), method: defaultsValid['iopa.Method'] || parentContext.get('iopa.Method'), body: null }) ) } mergeContext(context, defaultsValid) return context } /** Release the memory used by a given IOPA Context */ protected _dispose( context: T & IContextIopa<C> & { init: Function; dispose?: Function } ): void { if (context === null || context.get('server.AbortController') === null) { return } Object.keys(context).forEach((prop) => { context[prop] = null }) this._factory.free(context) } } /** Clean Options; allows overide for future validation */ function _validOptions(options?: any): any { if (typeof options === 'string' || options instanceof String) { const result = {} result['iopa.Method'] = options return result } return options || {} } const maxSequence: number = 2 ** 16 let _lastSequence: number = Math.floor(Math.random() * (maxSequence - 1)) export function _nextSequence(): string { _lastSequence += 1 if (_lastSequence === maxSequence) { _lastSequence = 1 } return _lastSequence.toString() }