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

Add deeplink creation and connection status methods #37

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,99 @@ yield openContactCard({ userHuid: "123e4567-e89b-12d3-a456-426655440000" })
```

Метод отправляет клиенту запрос на открытие карточки контакта с указанным huid.


__Статус подключения клиента к серверу__

```
const response = yield getConnectionStatus()
```

Метод отправляет клиенту запрос типа:
```
{
"ref": <string>,
"handler": "express",
"type": "smartapp_rpc",
"method": "get_connection_status",
"payload": {},
"files": []
}
```

И получает ответ:
```
{
"ref": <string>,
"status": "success|error",
"data": {
"connectionStatus": "connected" | "disconnected",
}
}
```

__Создание ссылки (deeplink)__

```
const response = yield createDeeplink({
appId: "email-app",
meta: [
{
key: "route",
value: "/send-email",
},
{
key: "email",
value: "[email protected]",
},
]
})
```

Метод отправляет клиенту запрос типа:
```
{
"ref": <string>,
"handler": "express",
"type": "smartapp_rpc",
"method": "create_deeplink",
"payload": {
"app_id": "email-app",
"meta": [
{
"key": "route",
"value": "/send-email",
},
{
"key": "email",
"value": "[email protected]",
},
]
},
"files": []
}
```

И получает ответ:
```
{
"ref": <string>,
"status": "success|error",
"data": {
"deeplink": "https://xlnk.ms/open/smartapp/email-app?route=%2Fsend-email&email=test%40mail.ru",
}
}
```

Также можно подписаться на изменение статуса подключения:
```
yield subscribeClientEvents('connection_status', (event) => {
// TODO: обработать event.data.connectionStatus
})
```

Отписаться от изменения статуса подключения:
```
yield unsubscribeClientEvents('connection_status', functionName)
```

Binary file removed SmartApp SDK.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@expressms/smartapp-bridge": "^1.2.1"
"@expressms/smartapp-bridge": "1.2.3"
},
"files": [
"build/main",
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import Bridge from '@expressms/smartapp-bridge'
import {
createDeeplink,
getChats,
getConnectionStatus,
openClientSettings,
openFile,
openGroupChat,
requestLocation,
searchCorporatePhonebook,
sendBotCommand,
subscribeClientEvents,
unsubscribeClientEvents,
} from './lib/client'
import {
addContact,
Expand Down Expand Up @@ -52,4 +56,8 @@ export {
openContactCard,
requestSelfProfile,
closeSmartApp,
getConnectionStatus,
subscribeClientEvents,
unsubscribeClientEvents,
createDeeplink,
}
81 changes: 81 additions & 0 deletions src/lib/client/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import bridge from '@expressms/smartapp-bridge'
import { ERROR_CODES, METHODS, STATUS, SubscriptionEventType } from '../../types'

const subscriptions: Array<{ eventType: SubscriptionEventType; callback: Function }> = []
let bridgeEventListenerInstalled = false

const isAnySubscriptionsOfType = (eventType: SubscriptionEventType) => {
return subscriptions.some(sub => sub.eventType == eventType)
}

const installBridgeEventListener = () => {
if (bridgeEventListenerInstalled || !bridge) return

bridgeEventListenerInstalled = true

bridge.onReceive(event => {
subscriptions.filter(sub => sub.eventType === event.type).map(sub => sub.callback(event))
})
}

/**
* Subscribe to special client events
* @param eventType Event from SubscriptionEventType enum to be subscribed
* @param callback Function to be handled when event is coming
* @returns Promise that'll be fullfilled on successful subscription, otherwise rejected with reason
*/
const subscribeClientEvents = (eventType: SubscriptionEventType, callback: Function): Promise<{ status: string }> => {
const successResponse = { status: STATUS.SUCCESS }

// No need to subscribe event twice on client
if (isAnySubscriptionsOfType(eventType)) {
subscriptions.push({ eventType, callback })
return Promise.resolve(successResponse)
}

if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)

return bridge
.sendClientEvent({
method: METHODS.SUBSCRIBE_CLIENT_EVENTS,
params: {
event: eventType,
},
})
.then(() => {
installBridgeEventListener()
subscriptions.push({ eventType, callback })
return successResponse
})
}

/**
* Unsubscribe from previously subscribed client events
* @param eventType Event from SubscriptionEventType enum to be unsubscribed
* @param callback Function to be unsibscribed
* @returns Promise that'll be fullfilled on successful unsubscription, otherwise rejected with reason
*/
const unsubscribeClientEvents = (eventType: SubscriptionEventType, callback: Function): Promise<{ status: string }> => {
const successResponse = { status: STATUS.SUCCESS }

const index = subscriptions.findIndex(sub => sub.eventType == eventType && sub.callback == callback)

if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)
if (index == -1) return Promise.reject(ERROR_CODES.SUBSCRIPTION_NOT_FOUND)

subscriptions.splice(index, 1)

// Send unsubscribe to client only at last subscription
if (isAnySubscriptionsOfType(eventType)) return Promise.resolve(successResponse)

return bridge
.sendClientEvent({
method: METHODS.UNSUBSCRIBE_CLIENT_EVENTS,
params: {
event: eventType,
},
})
.then(() => successResponse)
}

export { subscribeClientEvents, unsubscribeClientEvents }
42 changes: 40 additions & 2 deletions src/lib/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import bridge from '@expressms/smartapp-bridge'
import { File, METHODS } from '../../types'
import { CreateDeeplinkResponse, ERROR_CODES, File, GetConnectionStatusResponse, METHODS } from '../../types'
export * from './events'

const openClientSettings = () => {
return bridge?.sendClientEvent({
Expand Down Expand Up @@ -68,12 +69,49 @@ const requestLocation = () => {
})
}

/**
* Get client current connection status. It's based on client's WebSocket connection state.
* @returns Promise that'll be fullfilled with status data on success, otherwise rejected with reason
*/
const getConnectionStatus = async (): Promise<GetConnectionStatusResponse> => {
if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)

const response = await bridge.sendClientEvent({
method: METHODS.GET_CONNECTION_STATUS,
params: {},
})

return response as GetConnectionStatusResponse
}

/**
* Create deeplink URL to open SmartApp
* @param appId ID of SmartApp
* @param meta Array of params to be included in deeplink
* @returns Promise that'll be fullfilled with deeplink data on success, otherwise rejected with reason
*/
const createDeeplink = async (
appId: string,
meta: Array<{ key: string; value: null | boolean | string | number }>
): Promise<CreateDeeplinkResponse> => {
if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)

const response = await bridge.sendClientEvent({
method: METHODS.CREATE_DEEPLINK,
params: { appId, meta },
})

return response as CreateDeeplinkResponse
}

export {
openFile,
openClientSettings,
getChats,
searchCorporatePhonebook,
openGroupChat,
sendBotCommand,
requestLocation
requestLocation,
getConnectionStatus,
createDeeplink,
}
6 changes: 4 additions & 2 deletions src/lib/logging/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import bridge from "@expressms/smartapp-bridge"
import { ReadyEventResponse } from "../../types"
import { ERROR_CODES, ReadyEventResponse } from "../../types"
import { bridgeSendReady } from "../index"

const ready = async (timeout?: number) => {
const response = await bridgeSendReady(timeout) as ReadyEventResponse
const isLogsEnabled = response?.payload?.logsEnabled

if (isLogsEnabled) (bridge as any)?.enableLogs?.()
if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)

if (isLogsEnabled) bridge.enableLogs?.()

return response
}
Expand Down
16 changes: 15 additions & 1 deletion src/types/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,25 @@ export enum METHODS {
REQUEST_SELF_PROFILE = 'request_self_profile',
CLOSE_SMART_APP = 'close_smart_app',
OPEN_FILE = 'open_file',
SUBSCRIBE_CLIENT_EVENTS = 'subscribe_client_events',
UNSUBSCRIBE_CLIENT_EVENTS = 'unsubscribe_client_events',
GET_CONNECTION_STATUS = 'get_connection_status',
CREATE_DEEPLINK = 'create_deeplink',
}

export enum STATUS {
SUCCESS = 'success',
ERROR = 'error',
}

export enum ERROR_CODES {
NO_BRIDGE = 'no_bridge',
SUBSCRIPTION_NOT_FOUND = 'subscription_not_found',
}

export type ReadyEventResponse = ({
ref: string,
status: "success",
status: STATUS.SUCCESS,
payload: {
logsEnabled?: boolean,
isMain?: boolean,
Expand Down
21 changes: 21 additions & 0 deletions src/types/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export enum SubscriptionEventType {
CONNECTION_STATUS = "connection_status",
}

export type GetConnectionStatusResponse = ({
ref: string,
payload: {
connectionStatus: "connected" | "disconnected",
}
})

export type CreateDeeplinkResponse = ({
ref: string,
payload: {
status: 'error' | 'success',
errorCode?: string,
data?: {
deeplink: string,
}
}
})
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from "./bridge"
export * from "./routing"
export * from "./contacts"
export * from "./client"
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"

"@expressms/smartapp-bridge@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@expressms/smartapp-bridge/-/smartapp-bridge-1.2.1.tgz#70feb358172294029f7de42487e1e2d5f8024546"
integrity sha512-5laVBOdPLK98sG7PT7EfSNIHMYQbgUwFhAmM49irgpo1BN/lWyEZOYcqqVNRgmt9B/XocLqTFimAwFdro4TZLQ==
"@expressms/[email protected].3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@expressms/smartapp-bridge/-/smartapp-bridge-1.2.3.tgz#1705dab4a87d2518d357dace2c645830128ed9e6"
integrity sha512-X8VsuZZySb6rlXlO56bvdIjmrQeJjiU2RKcOnHSNkUsNFQ1dnxkwaNeFGGUE0F+dimD+bqShCKwZJ2Djiwreng==
dependencies:
"@types/lodash" "^4.14.168"
eventemitter3 "^4.0.7"
lodash-es "^4.17.21"
uuid "^8.3.2"

"@humanwhocodes/config-array@^0.5.0":
Expand Down Expand Up @@ -148,11 +148,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==

"@types/lodash@^4.14.168":
version "4.14.195"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==

"@types/node@*":
version "20.4.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"
Expand Down Expand Up @@ -1307,6 +1302,11 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"

lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==

lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
Expand Down