@firebolt-js/core-client 1.0.0-next.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +0 -0
  2. package/CONTRIBUTING.md +4 -0
  3. package/LICENSE +202 -0
  4. package/NOTICE +24 -0
  5. package/README.md +26 -0
  6. package/dist/docs/Accessibility/index.md +384 -0
  7. package/dist/docs/Accessibility/schemas/index.md +52 -0
  8. package/dist/docs/Advertising/index.md +120 -0
  9. package/dist/docs/Device/index.md +297 -0
  10. package/dist/docs/Discovery/index.md +128 -0
  11. package/dist/docs/Display/index.md +87 -0
  12. package/dist/docs/Internal/index.md +68 -0
  13. package/dist/docs/Lifecycle2/index.md +160 -0
  14. package/dist/docs/Localization/index.md +314 -0
  15. package/dist/docs/Localization/schemas/index.md +41 -0
  16. package/dist/docs/Metrics/index.md +987 -0
  17. package/dist/docs/Network/index.md +128 -0
  18. package/dist/docs/Policies/schemas/index.md +25 -0
  19. package/dist/docs/Presentation/index.md +53 -0
  20. package/dist/docs/Stats/index.md +63 -0
  21. package/dist/docs/TextToSpeech/index.md +524 -0
  22. package/dist/docs/Types/schemas/index.md +37 -0
  23. package/dist/firebolt-core-app-open-rpc.json +1082 -0
  24. package/dist/firebolt-core-open-rpc.json +3773 -0
  25. package/dist/lib/Accessibility/defaults.mjs +61 -0
  26. package/dist/lib/Accessibility/index.mjs +148 -0
  27. package/dist/lib/Advertising/defaults.mjs +26 -0
  28. package/dist/lib/Advertising/index.mjs +31 -0
  29. package/dist/lib/Device/defaults.mjs +34 -0
  30. package/dist/lib/Device/index.mjs +84 -0
  31. package/dist/lib/Discovery/defaults.mjs +22 -0
  32. package/dist/lib/Discovery/index.mjs +34 -0
  33. package/dist/lib/Events/index.mjs +345 -0
  34. package/dist/lib/Gateway/AppApi.mjs +125 -0
  35. package/dist/lib/Gateway/Bidirectional.mjs +113 -0
  36. package/dist/lib/Gateway/PlatformApi.mjs +130 -0
  37. package/dist/lib/Gateway/index.mjs +48 -0
  38. package/dist/lib/Localization/defaults.mjs +44 -0
  39. package/dist/lib/Localization/index.mjs +123 -0
  40. package/dist/lib/Log/index.mjs +57 -0
  41. package/dist/lib/Metrics/defaults.mjs +40 -0
  42. package/dist/lib/Metrics/index.mjs +206 -0
  43. package/dist/lib/Network/defaults.mjs +24 -0
  44. package/dist/lib/Network/index.mjs +70 -0
  45. package/dist/lib/Platform/defaults.mjs +27 -0
  46. package/dist/lib/Platform/index.mjs +28 -0
  47. package/dist/lib/Prop/MockProps.mjs +28 -0
  48. package/dist/lib/Prop/Router.mjs +25 -0
  49. package/dist/lib/Prop/index.mjs +60 -0
  50. package/dist/lib/Settings/index.mjs +86 -0
  51. package/dist/lib/Transport/MockTransport.mjs +191 -0
  52. package/dist/lib/Transport/WebsocketTransport.mjs +55 -0
  53. package/dist/lib/Transport/index.mjs +81 -0
  54. package/dist/lib/firebolt.d.ts +795 -0
  55. package/dist/lib/firebolt.mjs +51 -0
  56. package/firebolt-js-core-client-1.0.0-next.5.tgz +0 -0
  57. package/package.json +54 -0
@@ -0,0 +1,345 @@
1
+ /*
2
+ * Copyright 2021 Comcast Cable Communications Management, LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * SPDX-License-Identifier: Apache-2.0
17
+ */
18
+
19
+ import Gateway from '../Gateway/index.mjs'
20
+
21
+ let listenerId = 0
22
+
23
+ // holds two maps of ${module}.${event} => listenerId, e.g. callback method id
24
+ // note that one callback can listen to multiple events, e.g. 'discovery.*'
25
+ // internal is only available via a private export that we use to ensure our modules know about
26
+ // events before the apps using the SDK (otherwise state errors can happen)
27
+ const listeners = {
28
+ internal: {},
29
+ external: {},
30
+
31
+ // Several convenience functions below for checking both internal & external lists w/ one operation
32
+
33
+ // gets a merge list of ids for a single event key
34
+ get: (key) => {
35
+ return Object.assign(
36
+ Object.assign({}, listeners.internal[key]),
37
+ listeners.external[key],
38
+ )
39
+ },
40
+ // adds a callback/id to a key on the external list only
41
+ set: (key, id, value) => {
42
+ listeners.external[key] = listeners.external[key] || {}
43
+ listeners.external[key][id] = value
44
+ },
45
+ // adds a callback/id to a key on the internal list only
46
+ setInternal: (key, id, value) => {
47
+ listeners.internal[key] = listeners.internal[key] || {}
48
+ listeners.internal[key][id] = value
49
+ },
50
+ // finds the key for an id in either list (it can only be in one)
51
+ find: (id) => {
52
+ let key
53
+ ;[listeners.internal, listeners.external].find((group) => {
54
+ key = Object.keys(group).find((key) => group[key][id])
55
+ if (key) return true
56
+ })
57
+ return key
58
+ },
59
+ // removes an id from either list
60
+ remove: (id) => {
61
+ ;[listeners.internal, listeners.external].forEach((group) => {
62
+ Object.keys(group).forEach((key) => {
63
+ if (group[key] && group[key][id]) {
64
+ delete group[key][id]
65
+ if (Object.values(group[key]).length === 0) {
66
+ delete group[key]
67
+ }
68
+ }
69
+ })
70
+ })
71
+ },
72
+ // removes a key from both lists if _internal is true, otherwise only the external list
73
+ removeKey: (key, _internal = false) => {
74
+ _internal && listeners.internal[key] && delete listeners.internal[key]
75
+ listeners.external[key] && delete listeners.external[key]
76
+ },
77
+ // gives a list of all keys
78
+ keys: () => {
79
+ return Array.from(
80
+ new Set(
81
+ Object.keys(listeners.internal).concat(Object.keys(listeners.external)),
82
+ ),
83
+ )
84
+ },
85
+ // counts how many listeners are in a key across both lists
86
+ count: (key) => {
87
+ return Object.values(listeners.get(key)).length
88
+ },
89
+ }
90
+
91
+ // holds a map of ${module}.${event} => Transport.send calls (only called once per event)
92
+ // note that the keys here MUST NOT contain wild cards
93
+ const oncers = []
94
+ const validEvents = {}
95
+ const validContext = {}
96
+
97
+ export const registerEvents = (module, events) => {
98
+ validEvents[module] = events.concat()
99
+ }
100
+
101
+ export const registerEventContext = (module, event, context) => {
102
+ validContext[module] = validContext[module] || {}
103
+ validContext[module][event] = context.concat()
104
+ }
105
+
106
+ const callCallbacks = (key, params) => {
107
+ const args = Object.values(params)
108
+ const callbacks = Object.entries(listeners.internal[key] || {}).concat(
109
+ Object.entries(listeners.external[key] || {}),
110
+ )
111
+ callbacks.forEach(([listenerId, callback]) => {
112
+ if (oncers.indexOf(parseInt(listenerId)) >= 0) {
113
+ oncers.splice(oncers.indexOf(parseInt(listenerId)), 1)
114
+ delete listeners.external[key][listenerId]
115
+ }
116
+ if (args.length <= callback.length) {
117
+ callback.apply(null, args)
118
+ } else if (
119
+ args.length > 0 &&
120
+ callback.length > 0 &&
121
+ callback.length < args.length
122
+ ) {
123
+ // read the params the callback takes and if they are less then the size of args remove the extra params from the front
124
+ const callbackArgs = args.slice(-callback.length, args.length)
125
+ callback.apply(null, callbackArgs)
126
+ } else {
127
+ callback.call(null)
128
+ }
129
+ })
130
+ }
131
+
132
+ const doListen = function (
133
+ module,
134
+ event,
135
+ callback,
136
+ context,
137
+ once,
138
+ internal = false,
139
+ ) {
140
+ init()
141
+
142
+ if (typeof callback !== 'function') {
143
+ return Promise.reject('No valid callback function provided.')
144
+ } else {
145
+ if (module === '*') {
146
+ return Promise.reject('No valid module name provided')
147
+ }
148
+
149
+ //TODO: This is a patch and should be removed! Adds "on" if the event does not start with "on".
150
+ // Added temporarily for backward compatibility with older event namings
151
+ if (event != '*' && !event.startsWith('on')) {
152
+ event = 'on' + event[0].toUpperCase() + event.substring(1)
153
+ console.warn(
154
+ `Event names should begin with 'on'. Using '${module + '.' + event}' instead.`,
155
+ )
156
+ }
157
+
158
+ const wildcard = event === '*'
159
+ const events = wildcard ? validEvents[module] : [event] // explodes wildcards into an array
160
+ const promises = []
161
+ const hasContext = Object.values(context).length > 0
162
+ const contextKey = Object.keys(context)
163
+ .sort()
164
+ .map((key) => key + '=' + JSON.stringify(context[key]))
165
+ .join('&')
166
+
167
+ listenerId++
168
+
169
+ if (once) {
170
+ oncers.push(listenerId)
171
+ }
172
+
173
+ events.forEach((event) => {
174
+ const key = module + '.' + event + (hasContext ? `.${contextKey}` : '')
175
+
176
+ if (Object.values(listeners.get(key)).length === 0) {
177
+ const args = Object.assign({ listen: true }, context)
178
+
179
+ const subscriber = module + '.' + event
180
+ const notifier = module + '.' + event
181
+
182
+ Gateway.subscribe(notifier, (params) => {
183
+ callCallbacks(key, params)
184
+ })
185
+ const promise = Gateway.request(subscriber, args)
186
+ promises.push(promise)
187
+ }
188
+
189
+ const setter = internal ? listeners.setInternal : listeners.set
190
+
191
+ if (wildcard) {
192
+ setter(key, '' + listenerId, (value) => callback(event, value))
193
+ } else {
194
+ setter(key, '' + listenerId, callback)
195
+ }
196
+ })
197
+
198
+ let resolve, reject
199
+ let p = new Promise((res, rej) => {
200
+ resolve = res
201
+ reject = rej
202
+ })
203
+
204
+ // Iterate and resolve/reject through the list of promises sequentially
205
+ const templistenerId = listenerId
206
+ if (promises.length) {
207
+ promises.reduce((prevPromise, currentPromise) => {
208
+ return prevPromise
209
+ .then(() => currentPromise)
210
+ .then((responses) => {
211
+ resolve(templistenerId)
212
+ })
213
+ .catch((error) => {
214
+ if (event === '*') {
215
+ resolve(templistenerId)
216
+ } else {
217
+ // Remove the failed listener
218
+ doClear(templistenerId, event, context)
219
+ reject(error)
220
+ }
221
+ })
222
+ }, Promise.resolve())
223
+ } else {
224
+ resolve(listenerId)
225
+ }
226
+
227
+ return p
228
+ }
229
+ }
230
+
231
+ const getListenArgs = function (...args) {
232
+ const callback = args.pop()
233
+ const [module, event, context] = getClearArgs(...args)
234
+
235
+ return [module, event, callback, context]
236
+ }
237
+
238
+ const getClearArgs = function (...args) {
239
+ const module = args.shift() || '*'
240
+ const event = args.shift() || '*'
241
+ const context = {}
242
+
243
+ // populate context based on registered context for this module/event
244
+ // if an argument is not registered in validContext, that is ok meaning it is not a context argument
245
+ // Needed after introducing x-contextual-params
246
+ for (let i = 0; args.length; i++) {
247
+ let currentArg = args.shift()
248
+ if (
249
+ validContext[module] &&
250
+ validContext[module][event] &&
251
+ validContext[module][event][i]
252
+ ) {
253
+ context[validContext[module][event][i]] = currentArg
254
+ }
255
+ }
256
+ return [module, event, context]
257
+ }
258
+
259
+ const once = function (...args) {
260
+ const [module, event, callback, context] = getListenArgs(...args)
261
+ return doListen(module, event, callback, context, true)
262
+ }
263
+
264
+ const listen = function (...args) {
265
+ const [module, event, callback, context] = getListenArgs(...args)
266
+ return doListen(module, event, callback, context, false)
267
+ }
268
+
269
+ const clear = function (...args) {
270
+ if (args && args.length && typeof args[0] === 'number') {
271
+ return doClear(args[0])
272
+ } else if (args && args.length && typeof args[1] === 'number') {
273
+ return doClear(args[1])
274
+ } else {
275
+ const [moduleOrId, event, context] = getClearArgs(...args)
276
+ return doClear(moduleOrId, event, context)
277
+ }
278
+ }
279
+
280
+ // calls doListen with a priority flag for internal listeners to get priority
281
+ export const prioritize = function (...args) {
282
+ const [module, event, callback, context] = getListenArgs(...args)
283
+ return doListen(module, event, callback, context, false, true)
284
+ }
285
+
286
+ const unsubscribe = (key, context) => {
287
+ const [module, event] = key.split('.').slice(0, 2)
288
+ const args = Object.assign({ listen: false }, context)
289
+ Gateway.request(module + '.' + event, args)
290
+ Gateway.unsubscribe(`${module}.${event}`)
291
+ }
292
+
293
+ // TODO: clear needs to go through Transport Layer
294
+ const doClear = function (moduleOrId = false, event = false, context) {
295
+ if (event === '*') {
296
+ event = false
297
+ }
298
+
299
+ if (typeof moduleOrId === 'number') {
300
+ const searchId = moduleOrId.toString()
301
+ const key = listeners.find(searchId)
302
+
303
+ if (key) {
304
+ listeners.remove(searchId)
305
+ if (listeners.count(key) === 0) {
306
+ unsubscribe(key)
307
+ }
308
+ return true
309
+ }
310
+ return false
311
+ } else {
312
+ if (!moduleOrId && !event) {
313
+ listeners.keys().forEach((key) => {
314
+ listeners.removeKey(key)
315
+ unsubscribe(key)
316
+ })
317
+ } else if (!event) {
318
+ listeners.keys().forEach((key) => {
319
+ if (key.indexOf(moduleOrId) === 0) {
320
+ listeners.removeKey(key)
321
+ unsubscribe(key)
322
+ }
323
+ })
324
+ } else {
325
+ const hasContext = Object.values(context).length > 0
326
+ const contextKey = Object.keys(context)
327
+ .sort()
328
+ .map((key) => key + '=' + JSON.stringify(context[key]))
329
+ .join('&')
330
+ const key =
331
+ moduleOrId + '.' + event + (hasContext ? `.${contextKey}` : '')
332
+
333
+ listeners.removeKey(key)
334
+ unsubscribe(key, context)
335
+ }
336
+ }
337
+ }
338
+
339
+ const init = () => {}
340
+
341
+ export default {
342
+ listen: listen,
343
+ once: once,
344
+ clear: clear,
345
+ }
@@ -0,0 +1,125 @@
1
+ /*
2
+ * Copyright 2021 Comcast Cable Communications Management, LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * SPDX-License-Identifier: Apache-2.0
17
+ */
18
+
19
+ import Transport from '../Transport/index.mjs'
20
+
21
+ const win = typeof window !== 'undefined' ? window : {}
22
+ win.__firebolt = win.__firebolt || {}
23
+
24
+ // JSON RPC id generator, to be shared across all SDKs
25
+ class JsonRpcIdIterator {
26
+ constructor() {
27
+ this._id = 1
28
+ }
29
+ getJsonRpcId() {
30
+ return this._id++
31
+ }
32
+ }
33
+
34
+ let idGenerator = win.__firebolt.idGenerator || new JsonRpcIdIterator()
35
+ win.__firebolt.idGenerator = idGenerator
36
+
37
+ const promises = {}
38
+ const deprecated = {}
39
+
40
+ // request = { method: string, params: object, id: boolean }[]
41
+ // request with no `id` property are assumed to NOT be notifications, i.e. id must be set to false explicitly
42
+ export async function batch(requests) {
43
+ if (Array.isArray(requests)) {
44
+ const processed = requests.map((req) =>
45
+ processRequest(req.method, req.params, req.id, req.id === false),
46
+ )
47
+
48
+ // filter requests exclude notifications, as they don't need promises
49
+ const promises = processed
50
+ .filter((req) => req.id)
51
+ .map((request) => addPromiseToQueue(request.id))
52
+
53
+ Transport.send(processed)
54
+
55
+ // Using Promise.all get's us batch blocking for free
56
+ return Promise.all(promises)
57
+ }
58
+ throw `Bulk requests must be in an array`
59
+ }
60
+
61
+ // Request that the server provide fulfillment of an method
62
+ export async function request(method, params) {
63
+ const json = processRequest(method, params)
64
+ const promise = addPromiseToQueue(json.id)
65
+ Transport.send(json)
66
+ return promise
67
+ }
68
+
69
+ export function notify(method, params) {
70
+ Transport.send(processRequest(method, params, true))
71
+ }
72
+
73
+ export function response(id, result, error) {
74
+ const promise = promises[id]
75
+
76
+ if (promise) {
77
+ if (error !== undefined) {
78
+ promises[id].reject(error)
79
+ } else {
80
+ promises[id].resolve(result)
81
+ }
82
+
83
+ // TODO make sure this works
84
+ delete promises[id]
85
+ } else {
86
+ // All SDKs use a global id generator and a global transport where they register their callbacks for the responses.
87
+ // When the global transport receives a message, it calls all registered callbacks with that same message.
88
+ // That's why we can't generate an error here, as the response could be for a request made by another SDK.
89
+ // console.log(`Received a response for an unidentified request ${id}, result: ${result}, error: ${error}`);
90
+ }
91
+ }
92
+
93
+ export function deprecate(method, alternative) {
94
+ deprecated[method] = alternative
95
+ }
96
+
97
+ function addPromiseToQueue(id) {
98
+ return new Promise((resolve, reject) => {
99
+ promises[id] = {}
100
+ promises[id].promise = this
101
+ promises[id].resolve = resolve
102
+ promises[id].reject = reject
103
+ })
104
+ }
105
+
106
+ function processRequest(method, params, notification = false) {
107
+ if (deprecated[method]) {
108
+ console.warn(`WARNING: ${method}() is deprecated. ` + deprecated[method])
109
+ }
110
+
111
+ const id = !notification && idGenerator.getJsonRpcId()
112
+ const jsonrpc = '2.0'
113
+ const json = { jsonrpc, method, params }
114
+
115
+ !notification && (json.id = id)
116
+
117
+ return json
118
+ }
119
+
120
+ export default {
121
+ request,
122
+ batch,
123
+ response,
124
+ deprecate,
125
+ }
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright 2021 Comcast Cable Communications Management, LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * SPDX-License-Identifier: Apache-2.0
17
+ */
18
+
19
+ import PlatformApi from './PlatformApi.mjs'
20
+ import AppApi from './AppApi.mjs'
21
+ import Transport from '../Transport/index.mjs'
22
+ import Settings from '../Settings/index.mjs'
23
+
24
+ Transport.receive(async (message) => {
25
+ const json = JSON.parse(message)
26
+ if (Array.isArray(json)) {
27
+ json.forEach((message) => processMessage(message))
28
+ } else {
29
+ processMessage(json)
30
+ }
31
+ })
32
+
33
+ function processMessage(json) {
34
+ if (Settings.getLogLevel() === 'DEBUG') {
35
+ console.debug(
36
+ 'Receiving message from transport: \n' +
37
+ JSON.stringify(json, { indent: '\t' }),
38
+ )
39
+ }
40
+
41
+ if (json.method !== undefined) {
42
+ if (json.id !== undefined) {
43
+ PlatformApi.request(json.id, json.method, json.params)
44
+ } else {
45
+ let params = json.params
46
+ // TODO: Check if json.params is an array and if so, convert to a key-value object
47
+ // This is necessary because for Ripple, if params is an array this means that
48
+ // the callback function is expected to have one argument - the array itself.
49
+ // This is not compliant with the JSON-RPC specification, i.e. it should be removed.
50
+ if (Array.isArray(params)) {
51
+ params = { value: json.params }
52
+ }
53
+ PlatformApi.notify(json.method, params)
54
+ }
55
+ } else if (json.id !== undefined) {
56
+ AppApi.response(json.id, json.result, json.error)
57
+ }
58
+ }
59
+
60
+ export async function batch(requests) {
61
+ if (Array.isArray(requests)) {
62
+ return await AppApi.batch(requests)
63
+ } else {
64
+ throw 'Gateway.batch() requires an array of requests: { method: String, params: Object, id: Boolean }'
65
+ }
66
+ }
67
+
68
+ export async function request(method, params) {
69
+ if (Array.isArray(method)) {
70
+ throw 'Use Gateway.batch() for batch requests.'
71
+ } else {
72
+ return await AppApi.request(method, params)
73
+ }
74
+ }
75
+
76
+ export async function notify(method, params) {
77
+ if (Array.isArray(method)) {
78
+ throw 'Use Gateway.batch() for batch requests.'
79
+ } else {
80
+ return await AppApi.notify(method, params)
81
+ }
82
+ }
83
+
84
+ export function subscribe(event, callback) {
85
+ PlatformApi.subscribe(event, callback)
86
+ }
87
+
88
+ export function unsubscribe(event) {
89
+ PlatformApi.unsubscribe(event)
90
+ }
91
+
92
+ export function simulate(event, value) {
93
+ PlatformApi.simulate(event, value)
94
+ }
95
+
96
+ export function provide(interfaceName, provider) {
97
+ PlatformApi.provide(interfaceName, provider)
98
+ }
99
+
100
+ export function deprecate(method, alternative) {
101
+ AppApi.deprecate(method, alternative)
102
+ }
103
+
104
+ export default {
105
+ request,
106
+ notify,
107
+ batch,
108
+ subscribe,
109
+ unsubscribe,
110
+ simulate,
111
+ provide,
112
+ deprecate,
113
+ }
@@ -0,0 +1,130 @@
1
+ /*
2
+ * Copyright 2021 Comcast Cable Communications Management, LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * SPDX-License-Identifier: Apache-2.0
17
+ */
18
+
19
+ import Transport from '../Transport/index.mjs'
20
+
21
+ const providers = {}
22
+ const interfaces = {}
23
+ const listeners = {}
24
+
25
+ // Request that the app provide fulfillment of an method
26
+ export async function request(id, method, params, transforms) {
27
+ let result, error
28
+ try {
29
+ result = await getProviderResult(method, params)
30
+ } catch (e) {
31
+ error = e
32
+ }
33
+
34
+ const response = {
35
+ jsonrpc: '2.0',
36
+ id: id,
37
+ }
38
+
39
+ if (error) {
40
+ // todo: make sure this conforms to JSON-RPC schema for errors
41
+ response.error = error
42
+ } else {
43
+ response.result = result
44
+ if (result === undefined) {
45
+ response.result = null
46
+ }
47
+ }
48
+
49
+ Transport.send(response)
50
+ }
51
+
52
+ export async function notify(method, params) {
53
+ if (listeners[method]) {
54
+ listeners[method](params)
55
+ return
56
+ }
57
+ // All SDKs use a global transport where they register their callbacks for the responses.
58
+ // When the global transport receives a message, it calls all registered callbacks with that same message.
59
+ // That's why we can't generate an error here, as the response could be for an event registered by another SDK.
60
+ //console.log( `Notification not implemented: ${method}`);
61
+ }
62
+
63
+ // Register a provider implementation with an interface name
64
+ export function provide(interfaceName, provider) {
65
+ providers[interfaceName] = provider
66
+ }
67
+
68
+ // Register a notification listener with an event name
69
+ export function subscribe(event, callback) {
70
+ listeners[event] = callback
71
+ }
72
+
73
+ export function unsubscribe(event) {
74
+ delete listeners[event]
75
+ }
76
+
77
+ export function simulate(event, value) {
78
+ listeners[event](value)
79
+ }
80
+
81
+ // TODO: consider renaming
82
+ export function registerProviderInterface(
83
+ capability,
84
+ _interface,
85
+ method,
86
+ parameters,
87
+ response,
88
+ focusable,
89
+ ) {
90
+ interfaces[_interface] = interfaces[_interface] || {
91
+ capability,
92
+ name: _interface,
93
+ methods: [],
94
+ }
95
+
96
+ interfaces[_interface].methods.push({
97
+ name: method,
98
+ parameters,
99
+ response,
100
+ focusable,
101
+ })
102
+ }
103
+
104
+ async function getProviderResult(method, params = {}) {
105
+ const split = method.split('.')
106
+ method = split.pop()
107
+ const interfaceName = split.join('.')
108
+
109
+ if (providers[interfaceName]) {
110
+ if (providers[interfaceName][method]) {
111
+ // sort the params into an array based on the interface parameter order
112
+ const parameters = interfaces[interfaceName].methods
113
+ .find((m) => m.name === method)
114
+ .parameters.map((p) => params[p])
115
+ .filter((p) => p !== undefined)
116
+ return await providers[interfaceName][method](...parameters)
117
+ }
118
+ throw `Method not implemented: ${method}`
119
+ }
120
+ throw `Interface not provided: ${interfaceName}`
121
+ }
122
+
123
+ export default {
124
+ request,
125
+ notify,
126
+ provide,
127
+ subscribe,
128
+ unsubscribe,
129
+ simulate,
130
+ }