From 9572cbffe1877c8e7b546ad68c20a1e9ff6236e0 Mon Sep 17 00:00:00 2001 From: Ivan Kudinov Date: Wed, 13 May 2026 17:29:27 +0300 Subject: [PATCH] CCS-112452 aurora platform support --- README.md | 2 +- package.json | 2 +- src/index.ts | 3 + src/lib/constants.ts | 1 + src/lib/platformDetector.ts | 6 +- src/lib/platforms/aurora.ts | 234 ++++++++++++++++++++++++++++++++++++ src/types/index.d.ts | 3 + src/version.ts | 2 +- test/aurora.html | 49 ++++++++ 9 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 src/lib/platforms/aurora.ts create mode 100644 test/aurora.html diff --git a/README.md b/README.md index 4164c1f..498d93d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SmartApp bridge library This library provides a universal interface for exchanging events with an express client. -Andriod, iOS and Web clients supported. +Andriod, iOS, Aurora and Web clients supported. All types can be found [here](https://smartapp.ccsteam.xyz/smartapp-bridge/). diff --git a/package.json b/package.json index 74bab34..70d946c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expressms/smartapp-bridge", - "version": "1.4.5", + "version": "1.5.0-alpha.0", "description": "SmartApp bridge library", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/src/index.ts b/src/index.ts index a78c53f..6383456 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { PLATFORM } from './lib/constants' import getPlatform from './lib/platformDetector' import AndroidBridge from './lib/platforms/android' +import AuroraBridge from './lib/platforms/aurora' import IosBridge from './lib/platforms/ios' import WebBridge from './lib/platforms/web' import { Bridge } from './types/bridgeInterface' @@ -15,6 +16,8 @@ const getBridge = (): Bridge | null => { switch (platform) { case PLATFORM.ANDROID: return new AndroidBridge() + case PLATFORM.AURORA: + return new AuroraBridge() case PLATFORM.IOS: return new IosBridge() case PLATFORM.WEB: diff --git a/src/lib/constants.ts b/src/lib/constants.ts index a64acf0..e4c50c8 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -2,6 +2,7 @@ export enum PLATFORM { WEB = 'web', IOS = 'ios', ANDROID = 'android', + AURORA = 'aurora', UNKNOWN = 'unknown', } diff --git a/src/lib/platformDetector.ts b/src/lib/platformDetector.ts index 0cb073b..6d25277 100644 --- a/src/lib/platformDetector.ts +++ b/src/lib/platformDetector.ts @@ -21,6 +21,10 @@ const detectPlatformByUserAgent = (): PLATFORM => { ) return PLATFORM.IOS + if (/aurora/i.test(navigator.userAgent)) { + return PLATFORM.AURORA + } + return PLATFORM.WEB } @@ -30,7 +34,7 @@ const detectPlatformByUserAgent = (): PLATFORM => { * ```typescript * const platform = getPlatform(); * - * // => 'web' | 'ios' | 'android' + * // => 'web' | 'ios' | 'android' | 'aurora' * ``` */ const getPlatform = (): PLATFORM => { diff --git a/src/lib/platforms/aurora.ts b/src/lib/platforms/aurora.ts new file mode 100644 index 0000000..96ddd6b --- /dev/null +++ b/src/lib/platforms/aurora.ts @@ -0,0 +1,234 @@ +import { v4 as uuid } from 'uuid' + +import { + Bridge, + BridgeSendBotEventParams, + BridgeSendClientEventParams, + BridgeSendEventParams, + EventEmitterCallback, +} from '../../types' +import { camelCaseToSnakeCase, snakeCaseToCamelCase } from '../case' +import { EVENT_TYPE, HANDLER, RESPONSE_TIMEOUT, SYNC_RESPONSE_TIMEOUT, WEB_COMMAND_TYPE_RPC } from '../constants' +import ExtendedEventEmitter from '../eventEmitter' +import Logger from '../logger' + +class AuroraBridge extends Logger implements Bridge { + private readonly eventEmitter: ExtendedEventEmitter + isRenameParamsEnabledForBotx: boolean + + constructor() { + super() + + this.eventEmitter = new ExtendedEventEmitter() + this.isRenameParamsEnabledForBotx = true + window.handleAuroraEvent = this.handleAuroraEvent.bind(this) + } + + private handleAuroraEvent({ + ref, + data, + files, + }: { + readonly ref: string + readonly data: { + readonly type: string + } + readonly files: any + }): void { + this.logRecvEvent({ ref, data, files }) + + const { type, ...payload } = data + + const emitterType = ref || EVENT_TYPE.RECEIVE + + const eventFiles = this.isRenameParamsEnabledForBotx ? files?.map((file: any) => snakeCaseToCamelCase(file)) : files + + const event = { + ref, + type, + payload: this.isRenameParamsEnabledForBotx ? snakeCaseToCamelCase(payload) : payload, + files: eventFiles, + } + + this.eventEmitter.emit(emitterType, event) + } + + /** + * Set callback function to handle events without **ref** + * (notifications for example). + * + * ```js + * bridge.onRecieve(({ type, handler, payload }) => { + * // Handle event data + * console.log('event', type, handler, payload) + * }) + * ``` + * @param callback - Callback function. + */ + onReceive(callback: EventEmitterCallback) { + this.eventEmitter.on(EVENT_TYPE.RECEIVE, callback) + } + + private sendEvent({ + handler, + method, + params, + files, + timeout = RESPONSE_TIMEOUT, + guaranteed_delivery_required = false, + sync_request = false, + sync_request_timeout = SYNC_RESPONSE_TIMEOUT, + hide_send_event_data = false, + hide_recv_event_data = false, + }: BridgeSendEventParams) { + const ref = uuid() // UUID to detect express response. + const isRenameParamsEnabled = handler === HANDLER.BOTX ? this.isRenameParamsEnabledForBotx : true + const eventProps = { + ref, + type: WEB_COMMAND_TYPE_RPC, + method, + handler, + payload: isRenameParamsEnabled ? camelCaseToSnakeCase(params) : params, + guaranteed_delivery_required, + sync_request, + sync_request_timeout, + hide_send_event_data, + hide_recv_event_data, + } + + const eventFiles = isRenameParamsEnabled ? files?.map((file: any) => camelCaseToSnakeCase(file)) : files + + const event = files ? { ...eventProps, files: eventFiles } : eventProps + + this.logSendEvent(event) + + const customEvent = new CustomEvent('framescript:action', { detail: event }) + document.dispatchEvent(customEvent) + + return this.eventEmitter.onceWithTimeout(ref, timeout) + } + + /** + * Send event and wait response from express client. + * + * ```js + * bridge + * .sendBotEvent( + * { + * method: 'get_weather', + * params: { + * city: 'Moscow', + * }, + * files: [] + * } + * ) + * .then(data => { + * // Handle response + * console.log('response', data) + * }) + * ``` + */ + sendBotEvent({ + method, + params, + files, + timeout = RESPONSE_TIMEOUT, + guaranteed_delivery_required, + sync_request, + sync_request_timeout, + hide_send_event_data, + hide_recv_event_data, + }: BridgeSendBotEventParams) { + return this.sendEvent({ + handler: HANDLER.BOTX, + method, + params, + files, + timeout, + guaranteed_delivery_required, + sync_request, + sync_request_timeout, + hide_send_event_data, + hide_recv_event_data, + }) + } + + /** + * Send event and wait response from express client. + * + * ```js + * bridge + * .sendClientEvent( + * { + * type: 'get_weather', + * handler: 'express', + * payload: { + * city: 'Moscow', + * }, + * } + * ) + * .then(data => { + * // Handle response + * console.log('response', data) + * }) + * ``` + */ + sendClientEvent({ + method, + params, + timeout = RESPONSE_TIMEOUT, + hide_send_event_data, + hide_recv_event_data, + }: BridgeSendClientEventParams) { + return this.sendEvent({ + handler: HANDLER.EXPRESS, + method, + params, + timeout, + hide_send_event_data, + hide_recv_event_data, + }) + } + + /** + * Enabling renaming event params from camelCase to snake_case and vice versa + * ```js + * bridge + * .enableRenameParams() + * ``` + */ + enableRenameParams() { + this.isRenameParamsEnabledForBotx = true + console.log('Bridge ~ Enabled renaming event params from camelCase to snake_case and vice versa') + } + + /** + * Enabling renaming event params from camelCase to snake_case and vice versa + * ```js + * bridge + * .disableRenameParams() + * ``` + */ + disableRenameParams() { + this.isRenameParamsEnabledForBotx = false + console.log('Bridge ~ Disabled renaming event params from camelCase to snake_case and vice versa') + } + + /** + * Write log to client + * @param data Any data to log + */ + log(data: string | object): void { + let value: typeof data = '' + if (typeof data === 'string') { + value = data + } else if (typeof data === 'object') { + value = JSON.stringify(data, null, 2) + } else return + + const event = new CustomEvent('framescript:action', { detail: { 'SmartApp Log': value } }) + document.dispatchEvent(event) + } +} + +export default AuroraBridge diff --git a/src/types/index.d.ts b/src/types/index.d.ts index e74e905..986820a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -17,5 +17,8 @@ declare global { } } } + + // Aurora interface + handleAuroraEvent: Function } } diff --git a/src/version.ts b/src/version.ts index 4f9b375..2ff06f6 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const LIB_VERSION = "1.4.4"; +export const LIB_VERSION = "1.5.0"; diff --git a/test/aurora.html b/test/aurora.html new file mode 100644 index 0000000..ae242d8 --- /dev/null +++ b/test/aurora.html @@ -0,0 +1,49 @@ + + + + + + + SmartApp bridge testing + + + + +

Test bridge for Aurora

+ + + + + +