diff --git a/languages/javascript/src/shared/Events/index.mjs b/languages/javascript/src/shared/Events/index.mjs index d1030956..0949785a 100644 --- a/languages/javascript/src/shared/Events/index.mjs +++ b/languages/javascript/src/shared/Events/index.mjs @@ -139,10 +139,10 @@ const doListen = function(module, event, callback, context, once, internal=false const subscriber = module + '.on' + event[0].toUpperCase() + event.substring(1) const notifier = module + '.' + event - const promise = Gateway.request(subscriber, args) Gateway.subscribe(notifier, (params) => { - callCallbacks(key, params[Object.keys(params).pop()]) + callCallbacks(key, params) }) + const promise = Gateway.request(subscriber, args) promises.push(promise) } diff --git a/languages/javascript/src/shared/Gateway/Bidirectional.mjs b/languages/javascript/src/shared/Gateway/Bidirectional.mjs new file mode 100644 index 00000000..a0b44538 --- /dev/null +++ b/languages/javascript/src/shared/Gateway/Bidirectional.mjs @@ -0,0 +1,108 @@ +/* + * 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 Server from "./Server.mjs" +import Client from "./Client.mjs" +import Transport from "../Transport/index.mjs" +import Settings from "../Settings/index.mjs" + +Transport.receive(async (message) => { + const json = JSON.parse(message) + if (Array.isArray(json)) { + json.forEach(message => processMessage(message)) + } + else { + processMessage(json) + } +}) + +function processMessage(json) { + if (Settings.getLogLevel() === 'DEBUG') { + console.debug('Receiving message from transport: \n' + JSON.stringify(json, { indent: '\t'})) + } + + if (json.method !== undefined) { + if (json.id !== undefined) { + Server.request(json.id, json.method, json.params) + } + else { + Server.notify(json.method, json.params) + } + } + else if (json.id !== undefined) { + Client.response(json.id, json.result, json.error) + } +} + +export async function batch(requests) { + if (Array.isArray(requests)) { + return await Client.batch(requests) + } + else { + throw "Gateway.batch() requires an array of requests: { method: String, params: Object, id: Boolean }" + } +} + +export async function request(method, params) { + if (Array.isArray(method)) { + throw "Use Gateway.batch() for batch requests." + } + else { + return await Client.request(method, params) + } +} + +export async function notify(method, params) { + if (Array.isArray(method)) { + throw "Use Gateway.batch() for batch requests." + } + else { + return await Client.notify(method, params) + } +} + +export function subscribe(event, callback) { + Server.subscribe(event, callback) +} + +export function unsubscribe(event) { + Server.unsubscribe(event) +} + +export function simulate(event, value) { + Server.simulate(event, value) +} + +export function provide(interfaceName, provider) { + Server.provide(interfaceName, provider) +} + +export function deprecate(method, alternative) { + Client.deprecate(method, alternative) +} + +export default { + request, + notify, + batch, + subscribe, + unsubscribe, + simulate, + provide, + deprecate +} \ No newline at end of file diff --git a/languages/javascript/src/shared/Gateway/Server.mjs b/languages/javascript/src/shared/Gateway/Server.mjs index f6cd6c86..ef832973 100644 --- a/languages/javascript/src/shared/Gateway/Server.mjs +++ b/languages/javascript/src/shared/Gateway/Server.mjs @@ -54,13 +54,17 @@ export async function request(id, method, params, transforms) { Transport.send(response) } +// TODO: How do we know what order the params are in!? +// Need to implement this spec: +// https://github.com/rdkcentral/firebolt-apis/blob/feature/protocol/requirements/specifications/general/context-parameters.md +// Which ensures that we'll only have one (any name) or two (data & context) parameters. export async function notify(method, params) { if (listeners[method]) { - listeners[method](params) + listeners[method](...Object.values(params)) return } throw `Notification not implemented: ${method}` -} +} // Register a provider implementation with an interface name export function provide(interfaceName, provider) { @@ -76,6 +80,10 @@ export function unsubscribe(event) { delete listeners[event] } +export function simulate(event, value) { + listeners[event](value) +} + // TODO: consider renaming export function registerProviderInterface(capability, _interface, method, parameters, response, focusable) { interfaces[_interface] = interfaces[_interface] || { @@ -115,5 +123,6 @@ export default { notify, provide, subscribe, - unsubscribe + unsubscribe, + simulate } diff --git a/languages/javascript/src/shared/Transport/old.mjs b/languages/javascript/src/shared/Gateway/Unidirectional.mjs similarity index 55% rename from languages/javascript/src/shared/Transport/old.mjs rename to languages/javascript/src/shared/Gateway/Unidirectional.mjs index 4b02ddea..c0bdb821 100644 --- a/languages/javascript/src/shared/Transport/old.mjs +++ b/languages/javascript/src/shared/Gateway/Unidirectional.mjs @@ -16,47 +16,55 @@ * SPDX-License-Identifier: Apache-2.0 */ -import mock from './MockTransport.mjs' -import Queue from './queue.mjs' +import mock from '../Transport/MockTransport.mjs' +import Queue from '../Transport/queue.mjs' import Settings, { initSettings } from '../Settings/index.mjs' -import LegacyTransport from './LegacyTransport.mjs' -import WebsocketTransport from './WebsocketTransport.mjs' +import LegacyTransport from '../Transport/LegacyTransport.mjs' +import WebsocketTransport from '../Transport/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 isEventSuccess = (x) => + x && typeof x.event === 'string' && typeof x.listening === 'boolean' const win = typeof window !== 'undefined' ? window : {} export default class Transport { - constructor (endpoint) { + constructor() { this._promises = [] - this._implementation = null + this._transport = null this._id = 1 + this._subscribers = [] + this._eventIds = {} this._queue = new Queue() this._deprecated = {} this.isMock = false } - static registerDeprecatedMethod (module, method, alternative) { - Transport.get()._deprecated[module.toLowerCase() + '.' + method.toLowerCase()] = { - alternative: alternative || '' + static registerDeprecatedMethod(module, method, alternative) { + Transport.get()._deprecated[ + module.toLowerCase() + '.' + method.toLowerCase() + ] = { + alternative: alternative || '', } } - _endpoint () { + _endpoint() { if (win.__firebolt && win.__firebolt.endpoint) { return win.__firebolt.endpoint } return null } - constructTransportLayer () { + constructTransportLayer() { let transport const endpoint = this._endpoint() - if (endpoint && (endpoint.startsWith('ws://') || endpoint.startsWith('wss://'))) { + if ( + endpoint && + (endpoint.startsWith('ws://') || endpoint.startsWith('wss://')) + ) { transport = new WebsocketTransport(endpoint) transport.receive(this.receiveHandler.bind(this)) } else if ( @@ -67,14 +75,17 @@ export default class Transport { // 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) - }) + 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 @@ -83,49 +94,94 @@ export default class Transport { return transport } - setTransportLayer (tl) { - this._implementation = tl + setTransportLayer(tl) { + this._transport = tl this._queue.flush(tl) } - static send (module, method, params, transforms) { + static request(method, params, transforms) { /** Transport singleton across all SDKs to keep single id map */ - return Transport.get()._send(module, method, params, transforms) + console.dir(method) + + const module = method.split('.')[0] + method = method.split('.')[1] + + if (method.match(/^on[A-Z]/) && params.listen !== undefined) { + const { id, promise } = Transport.get()._sendAndGetId(module, method, params, transforms) + const notifier = `${module}.${method.charAt(2).toLowerCase() + method.substring(3)}` + if (params.listen) { + Transport.get()._eventIds[id] = notifier + } + else { + delete Transport.get()._eventIds[id] + delete Transport.get()._subscribers[notifier] + } + return promise + } + else { + return Transport.get()._send(module, method, params, transforms) + } + + } + + static subscribe(method, listener) { + const module = method.split('.')[0] + method = method.split('.')[1] + const notifier = `${module}.${method}` + Transport.get()._subscribers[notifier] = Transport.get()._subscribers[notifier] || [] + Transport.get()._subscribers[notifier].push(listener) + return } - static listen(module, method, params, transforms) { - return Transport.get()._sendAndGetId(module, method, params, transforms) + static unsubscribe(method) { + // TODO: implement } - _send (module, method, params, transforms) { + static simulate(method, value) { + console.log(`simulate(${method})`) + const module = method.split('.')[0] + method = method.split('.')[1] + const notifier = `${module}.${method}` + Transport.get()._subscribers[notifier].forEach(callback => callback(value)) + } + + static deprecate() { + // TODO: implement + } + + _send(module, method, params, transforms) { if (Array.isArray(module) && !method && !params) { return this._batch(module) - } - else { + } else { return this._sendAndGetId(module, method, params, transforms).promise } } - _sendAndGetId (module, method, params, transforms) { - const {promise, json, id } = this._processRequest(module, method, params, transforms) + _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._implementation.send(msg) + this._transport.send(msg) return { id, promise } } - _batch (requests) { + _batch(requests) { const results = [] const json = [] - requests.forEach( ({module, method, params, transforms}) => { + requests.forEach(({ module, method, params, transforms }) => { const result = this._processRequest(module, method, params, transforms) results.push({ promise: result.promise, - id: result.id + id: result.id, }) json.push(result.json) }) @@ -134,20 +190,19 @@ export default class Transport { if (Settings.getLogLevel() === 'DEBUG') { console.debug('Sending message to transport: ' + msg) } - this._implementation.send(msg) + this._transport.send(msg) return results } - _processRequest (module, method, params, transforms) { - + _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 + id: this._id, } this._id++ @@ -155,11 +210,16 @@ export default class Transport { return result } - _createRequestJSON (module, method, params) { - return { jsonrpc: '2.0', method: module.toLowerCase() + '.' + method, params: params, id: this._id } + _createRequestJSON(module, method, params) { + return { + jsonrpc: '2.0', + method: module.toLowerCase() + '.' + method, + params: params, + id: this._id, + } } - _addPromiseToQueue (module, method, params, transforms) { + _addPromiseToQueue(module, method, params, transforms) { return new Promise((resolve, reject) => { this._promises[this._id] = {} this._promises[this._id].promise = this @@ -167,19 +227,13 @@ export default class Transport { this._promises[this._id].reject = reject this._promises[this._id].transforms = transforms - const deprecated = this._deprecated[module.toLowerCase() + '.' + method.toLowerCase()] + 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) - } + console.warn( + `WARNING: ${module}.${method}() is deprecated. ` + + deprecated.alternative, + ) } }) } @@ -188,10 +242,10 @@ export default class Transport { * If we have a global transport, use that. Otherwise, use the module-scoped transport instance. * @returns {Transport} */ - static get () { + static get() { /** Set up singleton and initialize it */ win.__firebolt = win.__firebolt || {} - if ((win.__firebolt.transport == null) && (moduleInstance == null)) { + if (win.__firebolt.transport == null && moduleInstance == null) { const transport = new Transport() transport.init() if (transport.isMock) { @@ -201,33 +255,17 @@ export default class Transport { win.__firebolt = win.__firebolt || {} win.__firebolt.transport = transport } - win.__firebolt.setTransportLayer = transport.setTransportLayer.bind(transport) + win.__firebolt.setTransportLayer = + transport.setTransportLayer.bind(transport) } return win.__firebolt.transport ? win.__firebolt.transport : moduleInstance } - receiveHandler (message) { + receiveHandler(message) { if (Settings.getLogLevel() === 'DEBUG') { console.debug('Received message from transport: ' + message) } const json = JSON.parse(message) - - if (json.method) { - requestHandler(json) - } - else if (json.result || json.error) { - responseHandler(json) - } - else { - throw "Invalid JSON-RPC message received from transport." - } - } - - requestHandler (json) { - this._requestHandler() - } - - responseHandler (json) { const p = this._promises[json.id] if (p) { @@ -238,27 +276,24 @@ export default class Transport { if (p.transforms) { if (Array.isArray(json.result)) { - result = result.map(x => Results.transform(x, p.transforms)) - } - else { + 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) - }) + if (this._eventIds[json.id] && !isEventSuccess(json.result)) { + this._subscribers[this._eventIds[json.id]].forEach(callback => callback(json.result)) } } - init () { + init() { initSettings({}, { log: true }) this._queue.receive(this.receiveHandler.bind(this)) if (win.__firebolt) { @@ -269,12 +304,12 @@ export default class Transport { this.setTransportLayer(win.__firebolt.getTransportLayer()) } } - if (this._implementation == null) { - this._implementation = this.constructTransportLayer() + if (this._transport == null) { + this._transport = this.constructTransportLayer() } } } win.__firebolt = win.__firebolt || {} -win.__firebolt.setTransportLayer = transport => { +win.__firebolt.setTransportLayer = (transport) => { Transport.get().setTransportLayer(transport) } diff --git a/languages/javascript/src/shared/Gateway/index.mjs b/languages/javascript/src/shared/Gateway/index.mjs index 9ba21aa9..256e3017 100644 --- a/languages/javascript/src/shared/Gateway/index.mjs +++ b/languages/javascript/src/shared/Gateway/index.mjs @@ -16,87 +16,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -import Server from "./Server.mjs" -import Client from "./Client.mjs" -import Transport from "../Transport/index.mjs" -import Settings from "../Settings/index.mjs" - -Transport.receive(async (json) => { - if (Array.isArray(json)) { - json.forEach(message => processMessage(message)) - } - else { - processMessage(json) - } -}) - -function processMessage(json) { - if (Settings.getLogLevel() === 'DEBUG') { - console.debug('Receiving message from transport: \n' + JSON.stringify(json, { indent: '\t'})) - } - - if (json.method !== undefined) { - if (json.id !== undefined) { - Server.request(json.id, json.method, json.params) - } - else { - Server.notify(json.method, json.params) - } - } - else if (json.id !== undefined) { - Client.response(json.id, json.result, json.error) - } -} - -export async function batch(requests) { - if (Array.isArray(requests)) { - return await Client.batch(requests) - } - else { - throw "Gateway.batch() requires an array of requests: { method: String, params: Object, id: Boolean }" - } -} - -export async function request(method, params) { - if (Array.isArray(method)) { - throw "Use Gateway.batch() for batch requests." - } - else { - return await Client.request(method, params) - } -} - -export async function notify(method, params) { - if (Array.isArray(method)) { - throw "Use Gateway.batch() for batch requests." - } - else { - return await Client.notify(method, params) - } -} - -export function subscribe(event, callback) { - Server.subscribe(event, callback) -} - -export function unsubscribe(event) { - Server.unsubscribe(event) -} - -export function provide(interfaceName, provider) { - Server.provide(interfaceName, provider) -} - -export function deprecate(method, alternative) { - Client.deprecate(method, alternative) -} - -export default { - request, - notify, - batch, - subscribe, - unsubscribe, - provide, - deprecate -} \ No newline at end of file +export {default} from './Bidirectional.mjs' \ No newline at end of file diff --git a/languages/javascript/src/shared/Prop/MockProps.mjs b/languages/javascript/src/shared/Prop/MockProps.mjs index 99ac2a31..4aaf6e76 100644 --- a/languages/javascript/src/shared/Prop/MockProps.mjs +++ b/languages/javascript/src/shared/Prop/MockProps.mjs @@ -1,4 +1,3 @@ -import Server from "../Gateway/Server.mjs" import router from "./Router.mjs" const mocks = {} @@ -16,8 +15,6 @@ function mock(module, method, params, value, contextParameterCount, def) { } else if (type === "setter") { mocks[key] = value - // notify the app's RPC server directly, w/out a real RPC call - Server.notify(module + `.${method}Changed`, { value }) return null } } diff --git a/languages/javascript/src/shared/ProvideManager/index.mjs b/languages/javascript/src/shared/ProvideManager/index.mjs index b98574b9..8ab79cb4 100644 --- a/languages/javascript/src/shared/ProvideManager/index.mjs +++ b/languages/javascript/src/shared/ProvideManager/index.mjs @@ -19,20 +19,26 @@ import Events from '../Events/index.mjs' import Gateway from '../Gateway/index.mjs' +// NOTE: this class only used by Unidirectional SDKs Gateway/index.mjs provides this capability to Bidirectional SDKs + const providerInterfaces = {} -export const registerProviderInterface = (interfaceName, module, methods) => { - if (providerInterfaces[interfaceName]) { - throw `Interface ${interfaceName} has multiple provider interfaces registered.` +export const registerProviderInterface = (capability, _interface, method, params, response, focusable) => { + if (!providerInterfaces[capability]) { + providerInterfaces[capability] = [] } - methods.forEach(m => m.name = `${module}.${m.name}`) - providerInterfaces[interfaceName] = methods.concat() + providerInterfaces[capability].push({ + name: `${_interface}.${method}`, + parameters: params && params.length, + response, + focusable + }) } -const provide = function(interfaceName, provider) { +const provide = function(capability, provider) { const methods = [] - const iface = providerInterfaces[interfaceName] + const iface = providerInterfaces[capability] if (provider.constructor.name !== 'Object') { methods.push(...Object.getOwnPropertyNames(Object.getPrototypeOf(provider)).filter(item => typeof provider[item] === 'function' && item !== 'constructor')) @@ -49,10 +55,10 @@ const provide = function(interfaceName, provider) { const valid = iface.every(method => methods.find(m => m === method.name.split('.').pop())) if (!valid) { - throw `Provider that does not fully implement ${interfaceName}:\n\t${iface.map(m=>m.name.split('.').pop()).join('\n\t')}` + throw `Provider that does not fully implement ${capability}:\n\t${iface.map(m=>m.name.split('.').pop()).join('\n\t')}` } - Gateway.provide(interfaceName, provider) +// Gateway.provide(iface[0].name.split('.')[0], provider) iface.forEach(imethod => { const parts = imethod.name.split('.') @@ -66,7 +72,9 @@ const provide = function(interfaceName, provider) { Events.listen(module, `request${method.charAt(0).toUpperCase() + method.substr(1)}`, function (request) { const providerCallArgs = [] - + + console.dir(request) + // only pass in parameters object if schema exists if (imethod.parameters) { providerCallArgs.push(request.parameters) diff --git a/languages/javascript/src/shared/Settings/index.mjs b/languages/javascript/src/shared/Settings/index.mjs index 5d7afff8..463e0828 100644 --- a/languages/javascript/src/shared/Settings/index.mjs +++ b/languages/javascript/src/shared/Settings/index.mjs @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -const settings = {} +const settings = { platform: {} } const subscribers = {} export const initSettings = (appSettings, platformSettings) => { diff --git a/languages/javascript/src/shared/Transport/MockTransport.mjs b/languages/javascript/src/shared/Transport/MockTransport.mjs index 2f36d64f..278b7c17 100644 --- a/languages/javascript/src/shared/Transport/MockTransport.mjs +++ b/languages/javascript/src/shared/Transport/MockTransport.mjs @@ -16,6 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import Gateway from "../Gateway/index.mjs" const win = typeof window !== 'undefined' ? window : {} @@ -32,7 +33,8 @@ if (win.__firebolt && win.__firebolt.testHarness) { testHarness = win.__firebolt.testHarness } -function send(json) { +function send(message) { + const json = JSON.parse(message) // handle bulk sends if (Array.isArray(json)) { json.forEach(send) @@ -69,21 +71,21 @@ function handle(json) { result = getResult(json.method, json.params) } catch (error) { - setTimeout(() => callback({ + setTimeout(() => callback(JSON.stringify({ jsonrpc: '2.0', error: { code: -32602, message: "Invalid params (this is a mock error from the mock transport layer)" }, id: json.id - })) + }))) } - setTimeout(() => callback({ + setTimeout(() => callback(JSON.stringify({ jsonrpc: '2.0', result: result, id: json.id - })) + }))) } function receive(_callback) { @@ -91,14 +93,16 @@ function receive(_callback) { if (testHarness && (typeof testHarness.initialize === 'function')) { testHarness.initialize({ - emit: event, + emit: (module, method, value) => { + Gateway.simulate(`${module}.${method}`, value) + }, listen: function(...args) { listener(...args) }, }) } } function event(module, event, value) { - callback({ + callback(JSON.stringify({ jsonrpc: '2.0', method: `${module}.${event}`, params: [ @@ -107,7 +111,7 @@ function event(module, event, value) { value: value } ] - }) + })) } let id = 0 @@ -117,12 +121,12 @@ function request(method, params) { const promise = new Promise( (resolve, reject) => { requests[id] = { resolve, reject } }) - callback({ + callback(JSON.stringify({ jsonrpc: '2.0', id: id, method: `${method}`, params: params - }) + })) return promise } diff --git a/languages/javascript/src/shared/Transport/WebsocketTransport.mjs b/languages/javascript/src/shared/Transport/WebsocketTransport.mjs index b3e575f8..f1e0d7cd 100644 --- a/languages/javascript/src/shared/Transport/WebsocketTransport.mjs +++ b/languages/javascript/src/shared/Transport/WebsocketTransport.mjs @@ -9,14 +9,14 @@ export default class WebsocketTransport { this._callbacks = [] } - send (json) { + send (message) { this._connect() if (this._connected) { - this._ws.send(JSON.stringify(json)) + this._ws.send(message) } else { if (this._queue.length < MAX_QUEUED_MESSAGES) { - this._queue.push(json) + this._queue.push(message) } } } @@ -37,7 +37,7 @@ export default class WebsocketTransport { if (this._ws) return this._ws = new WebSocket(this._endpoint, ['jsonrpc']) this._ws.addEventListener('message', message => { - this._notifyCallbacks(JSON.parse(message.data)) + this._notifyCallbacks(message.data) }) this._ws.addEventListener('error', message => { }) diff --git a/languages/javascript/src/shared/Transport/index.mjs b/languages/javascript/src/shared/Transport/index.mjs index 80c30ad7..d400b4ac 100644 --- a/languages/javascript/src/shared/Transport/index.mjs +++ b/languages/javascript/src/shared/Transport/index.mjs @@ -35,7 +35,7 @@ export function send(json) { console.debug('Sending message to transport: \n' + JSON.stringify(json, { indent: '\t'})) } - implementation.send(json) + implementation.send(JSON.stringify(json)) } export function receive(callback) { diff --git a/languages/javascript/src/shared/Transport/queue.mjs b/languages/javascript/src/shared/Transport/queue.mjs index 513b52e1..bdfe448d 100644 --- a/languages/javascript/src/shared/Transport/queue.mjs +++ b/languages/javascript/src/shared/Transport/queue.mjs @@ -22,8 +22,8 @@ export default class Queue { this._queue = [] } - send (json) { - this._queue.push(json) + send (message) { + this._queue.push(message) } receive (_callback) { diff --git a/languages/javascript/templates/codeblocks/provider.js b/languages/javascript/templates/codeblocks/provider.js index b2895fe0..ed0e64c3 100644 --- a/languages/javascript/templates/codeblocks/provider.js +++ b/languages/javascript/templates/codeblocks/provider.js @@ -1 +1,5 @@ ${interface} + +${if.unidirectional} +function provide(capability: '${capability}', provider: ${provider} | object): Promise +${end.if.unidirectional} \ No newline at end of file diff --git a/languages/javascript/templates/imports/event-based-provider.mjs b/languages/javascript/templates/imports/unidirectional-provider.mjs similarity index 100% rename from languages/javascript/templates/imports/event-based-provider.mjs rename to languages/javascript/templates/imports/unidirectional-provider.mjs diff --git a/languages/javascript/templates/imports/unidirectional-rpc.mjs b/languages/javascript/templates/imports/unidirectional-rpc.mjs new file mode 100644 index 00000000..05456bae --- /dev/null +++ b/languages/javascript/templates/imports/unidirectional-rpc.mjs @@ -0,0 +1 @@ +import Gateway from '../Gateway/Unidirectional.mjs' diff --git a/languages/javascript/templates/interfaces/default.mjs b/languages/javascript/templates/interfaces/default.mjs index 34ffa217..1d8a2365 100644 --- a/languages/javascript/templates/interfaces/default.mjs +++ b/languages/javascript/templates/interfaces/default.mjs @@ -1 +1 @@ - ${method.name}(${method.signature.params}): Promise<${method.result.type}> + ${method.name}(${method.signature.params}${if.unidirectional}, session: ProviderSession${end.if.unidirectional}): Promise<${method.result.type}> diff --git a/languages/javascript/templates/interfaces/focusable.mjs b/languages/javascript/templates/interfaces/focusable.mjs index 34ffa217..60a3b2c2 100644 --- a/languages/javascript/templates/interfaces/focusable.mjs +++ b/languages/javascript/templates/interfaces/focusable.mjs @@ -1 +1 @@ - ${method.name}(${method.signature.params}): Promise<${method.result.type}> + ${method.name}(${method.signature.params}${if.unidirectional}, session: FocusableProviderSession${end.if.unidirectional}): Promise<${method.result.type}> diff --git a/languages/javascript/templates/methods/provide.js b/languages/javascript/templates/methods/provide.js index f2dcf0a7..8cb48885 100644 --- a/languages/javascript/templates/methods/provide.js +++ b/languages/javascript/templates/methods/provide.js @@ -1,3 +1,3 @@ -// function provide(capability, provider) { -// return ProvideManager.provide(capability, provider) -// } +function provide(capability, provider) { + return ProvideManager.provide(capability, provider) +} diff --git a/languages/javascript/templates/sdk/Gateway/index.mjs b/languages/javascript/templates/sdk/Gateway/index.mjs new file mode 100644 index 00000000..2ee1a3ab --- /dev/null +++ b/languages/javascript/templates/sdk/Gateway/index.mjs @@ -0,0 +1,26 @@ +/* + * 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 + */ + + +${if.bidirectional} +export {default} from './Bidirectional.mjs' +${end.if.bidirectional} + +${if.unidirectional} +export {default} from './Unidirectional.mjs' +${end.if.unidirectional} diff --git a/languages/javascript/templates/sections/provider-interfaces.js b/languages/javascript/templates/sections/provider-interfaces.js index d897cb7d..05fe0f7e 100644 --- a/languages/javascript/templates/sections/provider-interfaces.js +++ b/languages/javascript/templates/sections/provider-interfaces.js @@ -1,3 +1,11 @@ -// Provider Interfaces +// Provider Interfaces ${if.unidirectional} +interface ProviderSession { + correlationId(): string // Returns the correlation id of the current provider session +} + +interface FocusableProviderSession extends ProviderSession { + focus(): Promise // Requests that the provider app be moved into focus to prevent a user experience +} +${end.if.unidirectional} ${providers.list} \ No newline at end of file diff --git a/src/cli.mjs b/src/cli.mjs index f5e2f2f8..0abc1ebf 100755 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -20,7 +20,6 @@ const knownOpts = { 'schemas': [path, Array], 'template': [path], 'static-module': [String, Array], - 'copy-schemas': [Boolean], 'language': [path], 'examples': [path, Array], 'as-path': [Boolean], @@ -78,5 +77,6 @@ try { } } catch (error) { + console.dir(error) throw error } \ No newline at end of file diff --git a/src/firebolt-openrpc.json b/src/firebolt-openrpc.json index bce6c20c..c1a12ffd 100644 --- a/src/firebolt-openrpc.json +++ b/src/firebolt-openrpc.json @@ -696,6 +696,7 @@ "enum": [ "name", "x-response", + "x-response-name", "x-alternative", "x-since", "x-pulls-for", diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index 268737ae..d41168c7 100644 --- a/src/macrofier/engine.mjs +++ b/src/macrofier/engine.mjs @@ -29,7 +29,7 @@ import isString from 'crocks/core/isString.js' import predicates from 'crocks/predicates/index.js' const { isObject, isArray, propEq, pathSatisfies, propSatisfies } = predicates -import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, isAllowFocusMethod, hasAllowFocusMethods, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod, getProvidedInterfaces } from '../shared/modules.mjs' +import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, isAllowFocusMethod, hasAllowFocusMethods, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod, getProvidedInterfaces, getUnidirectionalProviderInterfaceName } from '../shared/modules.mjs' import { extension, getNotifier, name as methodName, name, provides } from '../shared/methods.mjs' import isEmpty from 'crocks/core/isEmpty.js' import { getReferencedSchema, getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema, mergeAnyOf, mergeOneOf, getSafeEnumKeyName, getAllValuesForName } from '../shared/json-schema.mjs' @@ -114,7 +114,8 @@ const getTemplateForExample = (method, templates) => { const getTemplateForExampleResult = (method, templates) => { const template = getTemplateTypeForMethod(method, 'examples/results', templates) - return template || JSON.stringify(method.examples[0].result.value, null, '\t') + const value = method.examples[0].result ? method.examples[0].result.value : method.examples[0].params.slice(-1)[0]?.value + return template || JSON.stringify(value) } const getLinkForSchema = (schema, json) => { @@ -333,7 +334,7 @@ const makeProviderMethod = x => x.name["onRequest".length].toLowerCase() + x.nam //import { default as platform } from '../Platform/defaults' -const generateAggregateMacros = (server, additional, templates, library) => { +const generateAggregateMacros = (server, client, additional, templates, library) => { return additional.reduce((acc, module) => { const infoMacros = generateInfoMacros(module) @@ -360,7 +361,8 @@ const generateAggregateMacros = (server, additional, templates, library) => { mockImports: '', mockObjects: '', version: getSemanticVersion(server), - library: library + library: library, + unidirectional: !client }) } @@ -575,7 +577,8 @@ const generateMacros = (server, client, templates, languages, options = {}) => { moduleInclude: moduleInclude, moduleIncludePrivate: moduleIncludePrivate, moduleInit: moduleInit, - public: hasPublicAPIs(server) + public: hasPublicAPIs(server), + unidirectional: !client }) Object.assign(macros, generateInfoMacros(server)) @@ -605,6 +608,8 @@ const insertAggregateMacros = (fContents = '', aggregateMacros = {}) => { fContents = fContents.replace(/[ \t]*\/\* \$\{MOCK_OBJECTS\} \*\/[ \t]*\n/, aggregateMacros.mockObjects) fContents = fContents.replace(/\$\{readable\}/g, aggregateMacros.version ? aggregateMacros.version.readable : '') fContents = fContents.replace(/\$\{package.name\}/g, aggregateMacros.library) + fContents = fContents.replace(/\$\{if\.unidirectional\}(.*?)\$\{end\.if\.unidirectional\}/gms, aggregateMacros.unidirectional ? '$1' : '') + fContents = fContents.replace(/\$\{if\.bidirectional\}(.*?)\$\{end\.if\.bidirectional\}/gms, !aggregateMacros.unidirectional ? '$1' : '') return fContents } @@ -622,6 +627,8 @@ const insertMacros = (fContents = '', macros = {}) => { fContents = fContents.replace(/\$\{if\.enums\}(.*?)\$\{end\.if\.enums\}/gms, macros.enums.types.trim() ? '$1' : '') fContents = fContents.replace(/\$\{if\.declarations\}(.*?)\$\{end\.if\.declarations\}/gms, (macros.methods.declarations && macros.methods.declarations.trim() || macros.enums.types.trim()) || macros.types.types.trim()? '$1' : '') fContents = fContents.replace(/\$\{if\.callsmetrics\}(.*?)\$\{end\.if\.callsmetrics\}/gms, macros.callsMetrics ? '$1' : '') + fContents = fContents.replace(/\$\{if\.unidirectional\}(.*?)\$\{end\.if\.unidirectional\}/gms, macros.unidirectional ? '$1' : '') + fContents = fContents.replace(/\$\{if\.bidirectional\}(.*?)\$\{end\.if\.bidirectional\}/gms, !macros.unidirectional ? '$1' : '') fContents = fContents.replace(/\$\{module\.list\}/g, macros.module) fContents = fContents.replace(/\$\{module\.includes\}/g, macros.moduleInclude) @@ -1041,7 +1048,12 @@ const generateImports = (server, client, templates, options = { destination: '' let imports = '' if (rpcMethodsOrEmptyArray(server).length) { - imports += getTemplate('/imports/rpc', templates) + if (client) { + imports += getTemplate('/imports/rpc', templates) + } + else { + imports += getTemplate('/imports/unidirectional-rpc', templates) + } } if (eventsOrEmptyArray(server).length) { @@ -1057,7 +1069,7 @@ const generateImports = (server, client, templates, options = { destination: '' imports += getTemplate('/imports/provider', templates) } else { - imports += getTemplate('/imports/event-based-provider', templates) + imports += getTemplate('/imports/unidirectional-provider', templates) } } @@ -1106,8 +1118,6 @@ const generateEventInitialization = (server, client, templates) => { } } -const getProviderInterfaceNameFromRPC = name => name.charAt(9).toLowerCase() + name.substr(10) // Drop onRequest prefix - // TODO: this passes a JSON object to the template... might be hard to get working in non JavaScript languages. const generateProviderInitialization = (document, templates) => { let result = '' @@ -1268,12 +1278,6 @@ function generateMethods(server = {}, client = null, examples = {}, templates = return acc }, [], methods) - // TODO: might be useful to pass in local macro for an array with all capability & provider interface names - // TODO: need a way to trigger generation of client-side provide method for uni-directional providers - if (server.methods && server.methods.find(isProviderInterfaceMethod)) { - // results.push(generateMethodResult('provide', templates)) - } - // TODO: might be useful to pass in local macro for an array with all event names if (server.methods && server.methods.find(isPublicEventMethod)) { ['listen', 'once', 'clear'].forEach(type => { @@ -1281,6 +1285,12 @@ function generateMethods(server = {}, client = null, examples = {}, templates = }) } + if (server.methods && server.methods.find(isProviderInterfaceMethod)) { + ['provide'].forEach(type => { + results.push(generateMethodResult(type, templates)) + }) + } + results.sort((a, b) => a.name.localeCompare(b.name)) return results } @@ -1864,17 +1874,6 @@ function generateProviderInterfaces(server, client, templates, codeblock, direct return interfaces.length ? template.replace(/\$\{providers\.list\}/g, providers) : '' } -function getProviderInterfaceName(iface, _interface, document = {}) { - const [ module, method ] = iface[0].name.split('.') - const uglyName = _interface.split(":").slice(-2).map(capitalize).reverse().join('') + "Provider" - let name = iface.length === 1 ? method.charAt(0).toUpperCase() + method.substr(1) + "Provider" : uglyName - - if (document.info['x-interface-names']) { - name = document.info['x-interface-names'][_interface] || name - } - return name -} - function getProviderXValues(method) { let xValues = [] if (method.tags.find(t => t['x-error']) || method.tags.find(t => t['x-response'])) { @@ -1913,17 +1912,22 @@ function insertProviderSubscribeMacros(template, capability, server = {}, client return template } -// TODO: split into /codeblocks/class & /codeblocks/interface (and /classes/* & /interaces/*) // TODO: ideally this method should be configurable with tag-names/template-names function insertProviderInterfaceMacros(template, _interface, server = {}, client = null, codeblock='interface', directory='interfaces', templates, bidirectional) { const document = client || server const iface = getProviderInterface(_interface, document, bidirectional) - let name = _interface //getProviderInterfaceName(iface, _interface, document) + console.dir(iface) + const capability = extension(iface[0], 'x-provides') let xValues let interfaceShape = getTemplate(`/codeblocks/${codeblock}`, templates) - interfaceShape = interfaceShape.replace(/\$\{name\}/g, name) - .replace(/\$\{capability\}/g, _interface) + if (!client) { + _interface = getUnidirectionalProviderInterfaceName(_interface, capability, server) + } + console.log(_interface + ": " + capability) + + interfaceShape = interfaceShape.replace(/\$\{name\}/g, _interface) + .replace(/\$\{capability\}/g, capability) .replace(/[ \t]*\$\{methods\}[ \t]*\n/g, iface.map(method => { const focusable = method.tags.find(t => t['x-allow-focus']) const interfaceTemplate = `/${directory}/` + (focusable ? 'focusable' : 'default') @@ -2018,9 +2022,9 @@ function insertProviderInterfaceMacros(template, _interface, server = {}, client // TODO: JSON-RPC examples need to use ${provider.interface} macros, but we're replacing them globally instead of each block // there's examples of this in methods, i think - template = template.replace(/\$\{provider\}/g, name) + template = template.replace(/\$\{provider\}/g, _interface) template = template.replace(/\$\{interface\}/g, interfaceShape) - template = template.replace(/\$\{capability\}/g, _interface) + template = template.replace(/\$\{capability\}/g, capability) template = insertProviderXValues(template, document, xValues) return template diff --git a/src/macrofier/index.mjs b/src/macrofier/index.mjs index 1664b3ff..8e5904d4 100644 --- a/src/macrofier/index.mjs +++ b/src/macrofier/index.mjs @@ -168,7 +168,7 @@ const macrofy = async ( const aggregatedExternalSchemas = mergeOnTitle ? Object.values(externalSchemas).filter(s => !modules.find(m => m.info.title === s.title)) : Object.values(externalSchemas) let start = Date.now() - const aggregateMacros = engine.generateAggregateMacros(serverRpc, modules.concat(staticModules).concat(copySchemasIntoModules ? [] : Object.values(aggregatedExternalSchemas)), templates, libraryName) + const aggregateMacros = engine.generateAggregateMacros(serverRpc, clientRpc, modules.concat(staticModules).concat(copySchemasIntoModules ? [] : Object.values(aggregatedExternalSchemas)), templates, libraryName) logSuccess(`Generated aggregate macros (${Date.now() - start}ms)`) const outputFiles = Object.fromEntries(Object.entries(await readFiles( staticCodeList, staticContent)) diff --git a/src/sdk/index.mjs b/src/sdk/index.mjs index 58470d31..8c2f1380 100755 --- a/src/sdk/index.mjs +++ b/src/sdk/index.mjs @@ -33,7 +33,6 @@ const run = async ({ output: output, language: language, 'static-module': staticModuleNames, - 'copy-schemas': copySchemas, argv: { remain: moduleWhitelist } @@ -42,49 +41,68 @@ const run = async ({ let mainFilename let declarationsFilename + const config = { + language: null, + project: null + } + try { + const projectDir = process.env.npm_config_local_prefix + const workspaceDir = path.dirname(process.env.npm_package_json) + // Important file/directory locations - const packageJsonFile = path.join(path.dirname(server), '..', 'package.json') + const packageJsonFile = path.join(workspaceDir, 'package.json') const packageJson = await readJson(packageJsonFile) mainFilename = path.basename(packageJson.main) declarationsFilename = path.basename(packageJson.types) + + // Load project firebolt-openrpc.config.json, if it exists + config.project = await readJson(path.join(projectDir, 'firebolt-openrpc.config.json')) } catch (error) { + //console.dir(error) // fail silently } - - const config = await readJson(path.join(language, 'language.config.json')) + config.language = await readJson(path.join(language, 'language.config.json')) + + if (config.project && config.project.languages && config.project.languages[config.language.langcode]) { + console.log(`Applying project overrides to language config:`) + const overrides = config.project.languages[config.language.langcode] + console.log(Object.entries(overrides).map( ([key, value]) => ` - ${key} -> ${JSON.stringify(value)}`).join('\n')) + Object.assign(config.language, overrides) + } + return macrofy(server, client, template, output, { headline: 'SDK code', outputDirectory: 'sdk', sharedTemplates: path.join(language, 'templates'), staticContent: path.join(language, 'src', 'shared'), - templatesPerModule: config.templatesPerModule, - templatesPerSchema: config.templatesPerSchema, - persistPermission: config.persistPermission, - createPolymorphicMethods: config.createPolymorphicMethods, - operators: config.operators, - primitives: config.primitives, - createModuleDirectories: config.createModuleDirectories, - copySchemasIntoModules: copySchemas === undefined ? config.copySchemasIntoModules : copySchemas, - mergeOnTitle: config.mergeOnTitle, - extractSubSchemas: config.extractSubSchemas, - convertTuplesToArraysOrObjects: config.convertTuplesToArraysOrObjects, - unwrapResultObjects: config.unwrapResultObjects, - allocatedPrimitiveProxies: config.allocatedPrimitiveProxies, - additionalSchemaTemplates: config.additionalSchemaTemplates, - additionalMethodTemplates: config.additionalMethodTemplates, - templateExtensionMap: config.templateExtensionMap, - excludeDeclarations: config.excludeDeclarations, - extractProviderSchema: config.extractProviderSchema, + templatesPerModule: config.language.templatesPerModule, + templatesPerSchema: config.language.templatesPerSchema, + persistPermission: config.language.persistPermission, + createPolymorphicMethods: config.language.createPolymorphicMethods, + operators: config.language.operators, + primitives: config.language.primitives, + createModuleDirectories: config.language.createModuleDirectories, + copySchemasIntoModules: config.language.copySchemasIntoModules, + mergeOnTitle: config.language.mergeOnTitle, + extractSubSchemas: config.language.extractSubSchemas, + convertTuplesToArraysOrObjects: config.language.convertTuplesToArraysOrObjects, + unwrapResultObjects: config.language.unwrapResultObjects, + allocatedPrimitiveProxies: config.language.allocatedPrimitiveProxies, + additionalSchemaTemplates: config.language.additionalSchemaTemplates, + additionalMethodTemplates: config.language.additionalMethodTemplates, + templateExtensionMap: config.language.templateExtensionMap, + excludeDeclarations: config.language.excludeDeclarations, + extractProviderSchema: config.language.extractProviderSchema, staticModuleNames: staticModuleNames, hideExcluded: true, moduleWhitelist: moduleWhitelist, - aggregateFiles: config.aggregateFiles, + aggregateFiles: config.language.aggregateFiles, rename: mainFilename ? { '/index.mjs': mainFilename, '/index.d.ts': declarationsFilename } : {}, - treeshakePattern: config.treeshakePattern ? new RegExp(config.treeshakePattern, "g") : undefined, - treeshakeTypes: config.treeshakeTypes, + treeshakePattern: config.language.treeshakePattern ? new RegExp(config.language.treeshakePattern, "g") : undefined, + treeshakeTypes: config.language.treeshakeTypes, treeshakeEntry: mainFilename ? '/' + mainFilename : '/index.mjs' }) } diff --git a/src/shared/json-schema.mjs b/src/shared/json-schema.mjs index b365aeae..f0f75c68 100644 --- a/src/shared/json-schema.mjs +++ b/src/shared/json-schema.mjs @@ -213,7 +213,8 @@ const getPropertySchema = (json, dotPath, document) => { const property = path[i] const remainingPath = path.filter((x, j) => j >= i ).join('.') if (node.$ref) { - node = getPropertySchema(getPath(node.$ref, document), remainingPath, document) + console.log(node.$ref) + node = getPropertySchema(getReferencedSchema(node.$ref, document), remainingPath, document) } else if (property === '') { return node diff --git a/src/shared/methods.mjs b/src/shared/methods.mjs index 808d2812..d99d6c47 100644 --- a/src/shared/methods.mjs +++ b/src/shared/methods.mjs @@ -24,7 +24,7 @@ export const isProvider = method => capabilities(method)['x-provides'] export const isPusher = method => capabilities(method)['x-push'] export const isNotifier = method => method.tags.find(t => t.name === 'notifier') export const isEvent = method => tag(method, 'event') -export const isRegistration = method => !tag(method, 'registration') +export const isRegistration = method => tag(method, 'registration') export const isProviderInterface = method => isProvider(method) && !isRegistration(method) && !isPusher(method) export const name = method => method.name.split('.').pop() diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index aa22c404..dfff7e19 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -31,11 +31,7 @@ import predicates from 'crocks/predicates/index.js' import { getExternalSchemaPaths, isDefinitionReferencedBySchema, isNull, localizeDependencies, isSchema, getLocalSchemaPaths, replaceRef, getPropertySchema, getLinkedSchemaUris, getAllValuesForName, replaceUri } from './json-schema.mjs' import { getReferencedSchema } from './json-schema.mjs' const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates -import { extension, getNotifier, isEvent, isNotifier, isPusher, name as methodName, rename as methodRename, provides } from './methods.mjs' - -// TODO remove these when major/rpc branch is merged -const name = method => method.name.split('.').pop() -const rename = (method, renamer) => method.name.split('.').map((x, i, arr) => i === (arr.length-1) ? renamer(x) : x).join('.') +import { extension, getNotifier, isEvent, isNotifier, isPusher, isRegistration, name as methodName, rename as methodRename, provides } from './methods.mjs' // util for visually debugging crocks ADTs const inspector = obj => { @@ -62,7 +58,7 @@ const getMethods = compose( const isProviderInterfaceMethod = method => { let tag = method.tags.find(t => t.name === 'capabilities') - const isProvider = tag['x-provides'] && !tag['x-allow-focus-for'] && !tag['x-response-for'] && !tag['x-error-for'] + const isProvider = tag['x-provides'] && !tag['x-allow-focus-for'] && !tag['x-response-for'] && !tag['x-error-for'] && !tag['x-push'] && !method.tags.find(t => t.name === 'registration') tag = method.tags.find(t => t.name.startsWith('polymorphic-pull')) const isPuller = !!tag @@ -140,14 +136,31 @@ function getProviderInterface(_interface, module) { return iface } +const capitalize = str => str.charAt(0).toUpperCase() + str.substr(1) + +// This is getting called before downgrading the provider interfaces AND after... it can't work for both cases. +function getUnidirectionalProviderInterfaceName(_interface, capability, document = {}) { + const iface = getProviderInterface(_interface, document) + const [ module, method ] = iface[0].name.split('.') + const uglyName = capability.split(":").slice(-2).map(capitalize).reverse().join('') + "Provider" + let name = iface.length === 1 ? method.charAt(0).toUpperCase() + method.substr(1) + "Provider" : uglyName + + if (document.info['x-interface-names']) { + name = document.info['x-interface-names'][capability] || name + } + return name + } + function updateUnidirectionalProviderInterface(iface, module) { iface.forEach(method => { const payload = getPayloadFromEvent(method) const focusable = method.tags.find(t => t['x-allow-focus']) // remove `onRequest` - method.name = method.name.charAt(9).toLowerCase() + method.name.substr(10) - + method.name = methodRename(method, name => name.charAt(9).toLowerCase() + name.substr(10)) + + console.dir(method) + const schema = getPropertySchema(payload, 'properties.parameters', module) method.params = [ @@ -158,7 +171,8 @@ function updateUnidirectionalProviderInterface(iface, module) { } ] - if (!extractProviderSchema) { + // TODO: we used to say !extractProviderSchema, which CPP sets to true and therefor skips this. not sure why... + if (true) { let exampleResult = null if (method.tags.find(tag => tag['x-response'])) { @@ -372,6 +386,9 @@ const getPayloadFromEvent = (event, client) => { const payload = getNotifier(event, client).params.slice(-1)[0].schema return payload } + else { + return event.result.schema + } } } catch (error) { @@ -1052,13 +1069,16 @@ const generateUnidirectionalProviderMethods = json => { // Transform providers to legacy events providers.forEach(p => { const name = methodRename(p, name => 'onRequest' + name.charAt(0).toUpperCase() + name.substring(1)) - json.methods.filter(m => m.tags && m.tags.find( t=> t.name === 'capabilities')['x-provided-by'] === p.name).forEach(m => { + const prefix = name.split('.').pop().substring(9) + + json.methods.filter(m => m.tags && m.tags.find( t=> t.name === 'capabilities')['x-provided-by'] === p.name && !m.tags.find(t => t.name === 'notifier')).forEach(m => { m.tags.find(t => t.name === 'capabilities')['x-provided-by'] = name }) p.name = name p.tags.push({ name: 'event', - 'x-response': p.result.schema + 'x-response-name': p.result.name, + 'x-response': p.result.schema, // todo: add examples }) @@ -1066,16 +1086,19 @@ const generateUnidirectionalProviderMethods = json => { // This is here because we're generating names that used to be editorial. These don't match exactly, // but they're good enough and "PinChallengeRequest" is way better than "PinChallengeChallengeRequest" let overlap = 0 - const module = p.name.split('.')[0] + const _interface = p.name.split('.')[0] const method = methodName(p).substring(9) + const capability = extension(p, 'x-provides') - for (let i=0; i { } }) + console.dir(parameters) + // remove them from the method p.params = [] @@ -1102,9 +1127,13 @@ const generateUnidirectionalProviderMethods = json => { const request = { title: prefix + 'Request', type: "object", + required: [ + "parameters", + "correlationId" + ], properties: { parameters: { - $ref: `#/components/schemas/${parameters.title}` + $ref: `#/components/schemas/${_interface}.${parameters.title}` }, correlationId: { type: "string" @@ -1112,22 +1141,40 @@ const generateUnidirectionalProviderMethods = json => { }, additionalProperties: false } + + console.dir(request) - json.components.schemas[request.title] = request - json.components.schemas[parameters.title] = parameters + console.dir(_interface) + + json.components.schemas[_interface + '.' + request.title] = request + json.components.schemas[_interface + '.' + parameters.title] = parameters // Put the request into the new event's result p.result = { name: 'result', schema: { - $ref: `#/components/schemas/${request.title}` + $ref: `#/components/schemas/${_interface}.${request.title}` } } - - + const eventTag = p.tags.find(t => t.name === 'event') + eventTag['x-response'].examples = [] p.examples.forEach(example => { // transform examples + eventTag['x-response'].examples.push(example.result.value) + example.result = { + name: 'result', + value: { + correlationId: '1', + parameters: Object.fromEntries(example.params.map(p => [p.name, p.value])) + } + } + example.params = [ + { + name: 'listen', + value: true + } + ] }) }) @@ -1308,15 +1355,22 @@ const generateProviderRegistrars = json => { return json } +const removeProviderRegistrars = (json) => { + json.methods && (json.methods = json.methods.filter(m => !isRegistration(m))) + return json +} + const generateUnidirectionalEventMethods = json => { const events = json.methods.filter( m => m.tags && m.tags.find(t => t.name == 'notifier')) || [] events.forEach(event => { const tag = event.tags.find(t => t.name === 'notifier') - event.name = tag['x-event'] + event.name = tag['x-event'] || methodRename(event, n => 'on' + n.charAt(0).toUpperCase() + n.substr(1)) delete tag['x-event'] + tag['x-subscriber-for'] = tag['x-notifier-for'] + delete tag['x-notifier-for'] + tag.name = 'event' - tag['x-notifier'] = event.name event.result = event.params.pop() event.examples.forEach(example => { example.result = example.params.pop() @@ -1598,6 +1652,7 @@ const fireboltize = (json, bidirectional) => { json = generateProviderMethods(json) json = generateEventListenerParameters(json) json = generateEventListenResponse(json) + json = removeProviderRegistrars(json) } json = generateTemporalSetMethods(json) @@ -1941,6 +1996,7 @@ export { getProviderInterface, getProvidedCapabilities, getProvidedInterfaces, + getUnidirectionalProviderInterfaceName, getSetterFor, getSubscriberFor, getEnums, diff --git a/src/slice/index.mjs b/src/slice/index.mjs index 94a52d94..02689223 100644 --- a/src/slice/index.mjs +++ b/src/slice/index.mjs @@ -61,6 +61,9 @@ const run = ({ const matches = openrpc.methods.filter(matcher) + console.dir(rule) + console.dir(matches) + methods.push(...matches) }) openrpc.methods.length = 0 diff --git a/src/validate/index.mjs b/src/validate/index.mjs index fd02fa9a..6a6f54e9 100644 --- a/src/validate/index.mjs +++ b/src/validate/index.mjs @@ -190,7 +190,10 @@ const run = async ({ if (transformations) { // put module name in front of each method - json.methods.filter(method => method.name.indexOf('.') === -1).forEach(method => method.name = json.info.title + '.' + method.name) + json.methods.filter(method => method.name.indexOf('.') === -1).forEach(method => { + console.log(json.info.title + '.' + method.name) + method.name = json.info.title + '.' + method.name + }) json.components && json.components.schemas && (json.components.schemas = Object.fromEntries(Object.entries(json.components.schemas).map( ([key, schema]) => ([json.info.title + '.' + key, schema]) ))) namespaceRefs('', json.info.title, json) @@ -285,7 +288,7 @@ const run = async ({ const examples = ajv.compile(exampleSpec) try { - const exampleResult = validate(method, { title: json.info.title + '.' + method.name, path: `/methods/${method.name}` }, ajv, examples) + const exampleResult = validate(method, { title: json.info.title, path: `/methods/${method.name}` }, ajv, examples) if (exampleResult.valid) { // printResult(exampleResult, "Firebolt Examples") @@ -336,7 +339,7 @@ const run = async ({ if (invalidResults) { console.error(`\nExiting due to ${invalidResults} invalid document${invalidResults === 1 ? '' : 's'}.\n`) - process.abort() + process.exit(-1) } return Promise.resolve() } diff --git a/src/validate/validator/index.mjs b/src/validate/validator/index.mjs index 2d4ecbae..d9ddc1b0 100644 --- a/src/validate/validator/index.mjs +++ b/src/validate/validator/index.mjs @@ -38,7 +38,7 @@ const addPrettyPath = (error, json, info) => { }) error.instancePath = (info.path ? info.path : '') + error.instancePath - error.prettyPath = '/' + path.join('/') + error.prettyPath = (info.path ? info.path : '') + '/' + path.join('/') error.document = root error.node = pointer return error