Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JavaScript namespaces, JSON-Schema Bundles, Bi-directional JSON-RPC changes #179

Open
wants to merge 8 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion languages/cpp/templates/codeblocks/provider.h

This file was deleted.

1 change: 0 additions & 1 deletion languages/cpp/templates/imports/calls-metrics.cpp

This file was deleted.

1 change: 0 additions & 1 deletion languages/cpp/templates/imports/calls-metrics.impl

This file was deleted.

19 changes: 17 additions & 2 deletions languages/cpp/templates/modules/include/module.h
Expand Up @@ -19,7 +19,10 @@
#pragma once

#include "error.h"
/* ${IMPORTS} */
/* ${IMPORTS:h} */
${if.callsmetrics}#include "metrics.h"
${end.if.callsmetrics}


${if.declarations}namespace Firebolt {
namespace ${info.Title} {
Expand All @@ -30,7 +33,19 @@ namespace ${info.Title} {
${if.types}
// Types
/* ${TYPES} */${end.if.types}
${if.providers}/* ${PROVIDERS} */${end.if.providers}${if.xuses}/* ${XUSES} */${end.if.xuses}
${if.providers}// Provider Interfaces
struct IProviderSession {
virtual ~IProviderSession() = default;

virtual std::string correlationId() const = 0;
};

struct IFocussableProviderSession : virtual public IProviderSession {
virtual ~IFocussableProviderSession() override = default;

virtual void focus( Firebolt::Error *err = nullptr ) = 0;
};
/* ${PROVIDER_INTERFACES} */${end.if.providers}${if.xuses}/* ${XUSES} */${end.if.xuses}
${if.methods}struct I${info.Title} {

virtual ~I${info.Title}() = default;
Expand Down
4 changes: 2 additions & 2 deletions languages/cpp/templates/modules/src/module_impl.cpp
Expand Up @@ -22,7 +22,7 @@
namespace Firebolt {
namespace ${info.Title} {
${if.providers}
/* ${PROVIDERS} */${end.if.providers}
/* ${PROVIDER_CLASES} */${end.if.providers}
// Methods
/* ${METHODS} */

Expand All @@ -35,5 +35,5 @@ namespace ${info.Title} {

namespace WPEFramework {

/* ${ENUMS} */
/* ${ENUM_IMPLEMENTATIONS} */
}${end.if.enums}
4 changes: 3 additions & 1 deletion languages/cpp/templates/modules/src/module_impl.h
Expand Up @@ -20,7 +20,9 @@

#include "FireboltSDK.h"
#include "IModule.h"
/* ${IMPORTS} */
/* ${IMPORTS:impl} */
${if.callsmetrics}#include "metrics_impl.h"
${end.if.callsmetrics}
#include "${info.title.lowercase}.h"

${if.implementations}
Expand Down
2 changes: 1 addition & 1 deletion languages/cpp/templates/schemas/include/common/module.h
Expand Up @@ -19,7 +19,7 @@
#pragma once

#include "error.h"
/* ${IMPORTS} */
/* ${IMPORTS:h} */

${if.declarations}namespace Firebolt {
namespace ${info.Title} {
Expand Down
2 changes: 1 addition & 1 deletion languages/cpp/templates/schemas/src/jsondata_module.h
Expand Up @@ -18,7 +18,7 @@

#pragma once

/* ${IMPORTS} */
/* ${IMPORTS:jsondata} */
#include "common/${info.title.lowercase}.h"

${if.schemas}namespace Firebolt {
Expand Down
4 changes: 2 additions & 2 deletions languages/cpp/templates/schemas/src/module_common.cpp
Expand Up @@ -17,11 +17,11 @@
*/

#include "FireboltSDK.h"
/* ${IMPORTS} */
/* ${IMPORTS:cpp} */
#include "jsondata_${info.title.lowercase}.h"
${if.enums}

namespace WPEFramework {

/* ${ENUMS} */
/* ${ENUM_IMPLEMENTATIONS} */
}${end.if.enums}
2 changes: 1 addition & 1 deletion languages/cpp/templates/sdk/include/firebolt.h
Expand Up @@ -127,7 +127,7 @@ struct IFireboltAccessor {
// Module Instance methods goes here.
// Instances are owned by the FireboltAcccessor and linked with its lifecycle.

${module.init}
${module.init:h}
};

}
2 changes: 1 addition & 1 deletion languages/cpp/templates/sdk/src/firebolt.cpp
Expand Up @@ -94,7 +94,7 @@ namespace Firebolt {
{
}

${module.init}
${module.init:cpp}
private:
FireboltSDK::Accessor* _accessor;
static FireboltAccessorImpl* _singleton;
Expand Down
14 changes: 0 additions & 14 deletions languages/cpp/templates/sections/provider-interfaces.h

This file was deleted.

4 changes: 4 additions & 0 deletions languages/javascript/language.config.json
Expand Up @@ -5,8 +5,12 @@
"/index.mjs",
"/defaults.mjs"
],
"templatesPerSchema": [
"/index.mjs"
],
"createModuleDirectories": true,
"copySchemasIntoModules": true,
"mergeOnTitle": true,
"aggregateFiles": [
"/index.d.ts"
],
Expand Down
66 changes: 27 additions & 39 deletions languages/javascript/src/shared/Events/index.mjs
Expand Up @@ -16,8 +16,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import Transport from '../Transport/index.mjs'
import { setMockListener } from '../Transport/MockTransport.mjs'
import Gateway from '../Gateway/index.mjs'

let listenerId = 0

Expand Down Expand Up @@ -82,41 +81,30 @@ const listeners = {
}
}

// holds a map of RPC Ids => Context Key, e.g. the RPC id of an onEvent call mapped to the corresponding context parameters key for that RPC call
const keys = {}

// holds a map of ${module}.${event} => Transport.send calls (only called once per event)
// note that the keys here MUST NOT contain wild cards
const oncers = []
const validEvents = {}
const validContext = {}

let transportInitialized = false

export const emit = (id, value) => {
callCallbacks(listeners.internal[keys[id]], [value])
callCallbacks(listeners.external[keys[id]], [value])
}

export const registerEvents = (module, events) => {
validEvents[module.toLowerCase()] = events.concat()
validEvents[module] = events.concat()
}

export const registerEventContext = (module, event, context) => {
validContext[module.toLowerCase()] = validContext[module.toLowerCase()] || {}
validContext[module.toLowerCase()][event] = context.concat()
validContext[module] = validContext[module] || {}
validContext[module][event] = context.concat()
}

const callCallbacks = (cbs, args) => {
cbs &&
Object.keys(cbs).forEach(listenerId => {
let callback = cbs[listenerId]
if (oncers.indexOf(parseInt(listenerId)) >= 0) {
oncers.splice(oncers.indexOf(parseInt(listenerId)), 1)
delete cbs[listenerId]
}
callback.apply(null, args)
})
const callCallbacks = (key, args) => {
const callbacks = Object.entries(listeners.internal[key] || {}).concat(Object.entries(listeners.external[key] || {}))
callbacks.forEach( ([listenerId, callback]) => {
if (oncers.indexOf(parseInt(listenerId)) >= 0) {
oncers.splice(oncers.indexOf(parseInt(listenerId)), 1)
delete listeners.external[key][listenerId]
}
callback.apply(null, [args])
})
}

const doListen = function(module, event, callback, context, once, internal=false) {
Expand Down Expand Up @@ -146,8 +134,15 @@ const doListen = function(module, event, callback, context, once, internal=false

if (Object.values(listeners.get(key)).length === 0) {
const args = Object.assign({ listen: true }, context)
const { id, promise } = Transport.listen(module, 'on' + event[0].toUpperCase() + event.substring(1), args)
keys[id] = key

// TODO: Is subscriber -> notifer required to be a simple transform (drop 'on'?)
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()])
})
promises.push(promise)
}

Expand Down Expand Up @@ -197,7 +192,7 @@ const getListenArgs = function(...args) {
}

const getClearArgs = function(...args) {
const module = (args.shift() || '*').toLowerCase()
const module = (args.shift() || '*')
const event = args.shift() || '*'
const context = {}

Expand Down Expand Up @@ -240,7 +235,8 @@ export const prioritize = function(...args) {
const unsubscribe = (key, context) => {
const [module, event] = key.split('.').slice(0, 2)
const args = Object.assign({ listen: false }, context)
Transport.send(module, 'on' + event[0].toUpperCase() + event.substr(1), args)
Gateway.request(module + '.on' + event[0].toUpperCase() + event.substr(1), args)
Gateway.unsubscribe(`${module}.${event}`)
}


Expand Down Expand Up @@ -270,7 +266,7 @@ const doClear = function (moduleOrId = false, event = false, context) {
})
} else if (!event) {
listeners.keys().forEach(key => {
if (key.indexOf(moduleOrId.toLowerCase()) === 0) {
if (key.indexOf(moduleOrId) === 0) {
listeners.removeKey(key)
unsubscribe(key)
}
Expand All @@ -287,18 +283,10 @@ const doClear = function (moduleOrId = false, event = false, context) {
}

const init = () => {
if (!transportInitialized) {
Transport.addEventEmitter(emit)
setMockListener(listen)
transportInitialized = true
}
}

export default {
listen: listen,
once: once,
clear: clear,
broadcast(event, value) {
emit(Object.entries(keys).find( ([k, v]) => v === 'app.'+event)[0], value)
},
clear: clear
}
104 changes: 104 additions & 0 deletions languages/javascript/src/shared/Gateway/Client.mjs
@@ -0,0 +1,104 @@
/*
* 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 Transport from "../Transport/index.mjs"

const win = typeof window !== 'undefined' ? window : {}
win.__firebolt = win.__firebolt || {}

// JSON RPC id generator, to be shared across all SDKs
class JsonRpcIdIterator {
constructor() {
this._id = 1
}
getJsonRpcId() {
return this._id++
}
}

let idGenerator = win.__firebolt.idGenerator || new JsonRpcIdIterator()
win.__firebolt.idGenerator = idGenerator

const promises = {}
const deprecated = {}

export async function bulk(requests) {
if (Array.isArray(requests)) {
const body = requests.map(req => processRequest(req.method, req.params))
Transport.send(body)
return await Promise.all(requests.map((req, i) => addPromiseToQueue(req.id, requests[i].transforms)))
}
throw `Bulk requests must be in an array`
}

// Request that the server provide fulfillment of an method
export async function request(method, params, transforms) {
const json = processRequest(method, params)
const promise = addPromiseToQueue(json.id, transforms)
Transport.send(json)
return promise
}

export async function notify(method, params) {
Transport.send(processRequest(method, params, true))
}

export function response(id, result, error) {
if (result !== undefined) {
promises[id].resolve(result)
}
else if (error !== undefined) {
promises[id].reject(error)
}
}

export function deprecate(method, alternative) {
deprecated[method] = alternative
}

function addPromiseToQueue (id, transforms) {
return new Promise((resolve, reject) => {
promises[id] = {}
promises[id].promise = this
promises[id].resolve = resolve
promises[id].reject = reject
promises[id].transforms = transforms
})
}

function processRequest(method, params, notification=false) {
if (deprecated[method]) {
console.warn(`WARNING: ${method}() is deprecated. ` + deprecated[method])
}

const id = !notification && idGenerator.getJsonRpcId()
const jsonrpc = '2.0'
const json = { jsonrpc, method, params }

!notification && (json.id = id)

return json
}


export default {
request,
bulk,
response,
deprecate
}