UNPKG

@firebolt-js/sdk

Version:
291 lines (256 loc) 8.36 kB
/* * Copyright 2021 Comcast Cable Communications Management, LLC * * 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. * * SPDX-License-Identifier: Apache-2.0 */ import mock from './MockTransport.mjs' import Queue from './queue.mjs' import Settings, { initSettings } from '../Settings/index.mjs' import LegacyTransport from './LegacyTransport.mjs' import WebsocketTransport from './WebsocketTransport.mjs' import Results from '../Results/index.mjs' const LEGACY_TRANSPORT_SERVICE_NAME = 'com.comcast.BridgeObject_1' let moduleInstance = null const isEventSuccess = (x) => x && typeof x.event === 'string' && typeof x.listening === 'boolean' const win = typeof window !== 'undefined' ? window : {} export default class Transport { constructor() { this._promises = [] this._transport = null this._id = 1 this._eventEmitters = [] this._eventIds = [] this._queue = new Queue() this._deprecated = {} this.isMock = false } static addEventEmitter(emitter) { Transport.get()._eventEmitters.push(emitter) } static registerDeprecatedMethod(module, method, alternative) { Transport.get()._deprecated[ module.toLowerCase() + '.' + method.toLowerCase() ] = { alternative: alternative || '', } } _endpoint() { if (win.__firebolt && win.__firebolt.endpoint) { return win.__firebolt.endpoint } return null } constructTransportLayer() { let transport const endpoint = this._endpoint() if ( endpoint && (endpoint.startsWith('ws://') || endpoint.startsWith('wss://')) ) { transport = new WebsocketTransport(endpoint) transport.receive(this.receiveHandler.bind(this)) } else if ( typeof win.ServiceManager !== 'undefined' && win.ServiceManager && win.ServiceManager.version ) { // Wire up the queue transport = this._queue // get the default bridge service, and flush the queue win.ServiceManager.getServiceForJavaScript( LEGACY_TRANSPORT_SERVICE_NAME, (service) => { if (LegacyTransport.isLegacy(service)) { transport = new LegacyTransport(service) } else { transport = service } this.setTransportLayer(transport) }, ) } else { this.isMock = true transport = mock transport.receive(this.receiveHandler.bind(this)) } return transport } setTransportLayer(tl) { this._transport = tl this._queue.flush(tl) } static send(module, method, params, transforms) { /** Transport singleton across all SDKs to keep single id map */ return Transport.get()._send(module, method, params, transforms) } static listen(module, method, params, transforms) { return Transport.get()._sendAndGetId(module, method, params, transforms) } _send(module, method, params, transforms) { if (Array.isArray(module) && !method && !params) { return this._batch(module) } else { return this._sendAndGetId(module, method, params, transforms).promise } } _sendAndGetId(module, method, params, transforms) { const { promise, json, id } = this._processRequest( module, method, params, transforms, ) const msg = JSON.stringify(json) if (Settings.getLogLevel() === 'DEBUG') { console.debug('Sending message to transport: ' + msg) } this._transport.send(msg) return { id, promise } } _batch(requests) { const results = [] const json = [] requests.forEach(({ module, method, params, transforms }) => { const result = this._processRequest(module, method, params, transforms) results.push({ promise: result.promise, id: result.id, }) json.push(result.json) }) const msg = JSON.stringify(json) if (Settings.getLogLevel() === 'DEBUG') { console.debug('Sending message to transport: ' + msg) } this._transport.send(msg) return results } _processRequest(module, method, params, transforms) { const p = this._addPromiseToQueue(module, method, params, transforms) const json = this._createRequestJSON(module, method, params) const result = { promise: p, json: json, id: this._id, } this._id++ return result } _createRequestJSON(module, method, params) { return { jsonrpc: '2.0', method: module.toLowerCase() + '.' + method, params: params, id: this._id, } } _addPromiseToQueue(module, method, params, transforms) { return new Promise((resolve, reject) => { this._promises[this._id] = {} this._promises[this._id].promise = this this._promises[this._id].resolve = resolve this._promises[this._id].reject = reject this._promises[this._id].transforms = transforms const deprecated = this._deprecated[module.toLowerCase() + '.' + method.toLowerCase()] if (deprecated) { console.warn( `WARNING: ${module}.${method}() is deprecated. ` + deprecated.alternative, ) } // store the ID of the first listen for each event // TODO: what about wild cards? if (method.match(/^on[A-Z]/)) { if (params.listen) { this._eventIds.push(this._id) } else { this._eventIds = this._eventIds.filter((id) => id !== this._id) } } }) } /** * If we have a global transport, use that. Otherwise, use the module-scoped transport instance. * @returns {Transport} */ static get() { /** Set up singleton and initialize it */ win.__firebolt = win.__firebolt || {} if (win.__firebolt.transport == null && moduleInstance == null) { const transport = new Transport() transport.init() if (transport.isMock) { /** We should use the mock transport built with the SDK, not a global */ moduleInstance = transport } else { win.__firebolt = win.__firebolt || {} win.__firebolt.transport = transport } win.__firebolt.setTransportLayer = transport.setTransportLayer.bind(transport) } return win.__firebolt.transport ? win.__firebolt.transport : moduleInstance } receiveHandler(message) { if (Settings.getLogLevel() === 'DEBUG') { console.debug('Received message from transport: ' + message) } const json = JSON.parse(message) const p = this._promises[json.id] if (p) { if (json.error) p.reject(json.error) else { // Do any module-specific transforms on the result let result = json.result if (p.transforms) { if (Array.isArray(json.result)) { result = result.map((x) => Results.transform(x, p.transforms)) } else { result = Results.transform(result, p.transforms) } } p.resolve(result) } delete this._promises[json.id] } // event responses need to be emitted, even after the listen call is resolved if (this._eventIds.includes(json.id) && !isEventSuccess(json.result)) { this._eventEmitters.forEach((emit) => { emit(json.id, json.result) }) } } init() { initSettings({}, { log: true }) this._queue.receive(this.receiveHandler.bind(this)) if (win.__firebolt) { if (win.__firebolt.mockTransportLayer === true) { this.isMock = true this.setTransportLayer(mock) } else if (win.__firebolt.getTransportLayer) { this.setTransportLayer(win.__firebolt.getTransportLayer()) } } if (this._transport == null) { this._transport = this.constructTransportLayer() } } } win.__firebolt = win.__firebolt || {} win.__firebolt.setTransportLayer = (transport) => { Transport.get().setTransportLayer(transport) }