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 unread counter subscription #53

Merged
merged 1 commit into from
Mar 28, 2024
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@expressms/smartapp-sdk",
"version": "1.6.0",
"version": "1.7.0-alpha.1",
"description": "Smartapp SDK",
"main": "build/main/index.js",
"typings": "build/main/index.d.ts",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
createDeeplink,
getChats,
getConnectionStatus,
getUnreadCounter,
handleDeeplink,
openChatMessage,
openClientSettings,
Expand Down Expand Up @@ -76,4 +77,5 @@ export {
openPersonalChat,
handleDeeplink,
searchLocalPhonebook,
getUnreadCounter,
}
93 changes: 70 additions & 23 deletions src/lib/client/events.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import bridge from '@expressms/smartapp-bridge'
import { ERROR_CODES, METHODS, STATUS, SubscriptionEventType } from '../../types'
import { ERROR_CODES, METHODS, STATUS, StatusResponse, SubscriptionEventType, SubscriptionPayload } from '../../types'

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

const isAnySubscriptionsOfType = (eventType: SubscriptionEventType) => {
return subscriptions.some(sub => sub.eventType == eventType)
const composeResponse = (status: STATUS, errorCode?: string): StatusResponse => {
return {
payload: {
status,
errorCode
},
} as StatusResponse
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getSubscriptionId = (eventType: SubscriptionEventType, payload?: any) => {
const sourceId = payload?.id ? '-' + payload?.id : ''
const sourceType = payload?.type ? '-' + payload?.type : ''

return `${eventType}${sourceType}${sourceId}`
}

const isAnySubscriptions = (eventType: SubscriptionEventType, payload?: SubscriptionPayload) => {
const id = getSubscriptionId(eventType, payload)

return subscriptions.some(sub => sub.id == id)
}

const installBridgeEventListener = () => {
Expand All @@ -14,68 +33,96 @@ const installBridgeEventListener = () => {
bridgeEventListenerInstalled = true

bridge.onReceive(event => {
subscriptions.filter(sub => sub.eventType === event.type).map(sub => sub.callback?.(event))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const id = getSubscriptionId(event.type as SubscriptionEventType, (event.payload as any).source)

subscriptions.filter(sub => sub.id === id).map(sub => sub.callback?.(event))
})
}

/**
* Subscribe to special client events
* @param eventType Event from SubscriptionEventType enum to be subscribed
* @param payload Additional params, for example `{ id: 'email-app', type: 'smartapp' }`
* @param callback Optional 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 }

const subscribeClientEvents = ({
eventType,
payload,
callback,
}: {
eventType: SubscriptionEventType
payload?: SubscriptionPayload
callback?: Function
}): Promise<StatusResponse> => {
// No need to subscribe event twice on client
if (isAnySubscriptionsOfType(eventType)) {
subscriptions.push({ eventType, callback })
return Promise.resolve(successResponse)
if (isAnySubscriptions(eventType, payload)) {
const id = getSubscriptionId(eventType, payload)

subscriptions.push({ id, callback })
return Promise.resolve(composeResponse(STATUS.SUCCESS))
}

if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)
if (!bridge) return Promise.reject(composeResponse(STATUS.ERROR, ERROR_CODES.NO_BRIDGE))

return bridge
.sendClientEvent({
method: METHODS.SUBSCRIBE_CLIENT_EVENTS,
params: {
event: eventType,
...payload,
},
})
.then(() => {
.then((event) => {
const response = event as StatusResponse
const id = getSubscriptionId(eventType, payload)

if (response.payload.status !== STATUS.SUCCESS) return response

installBridgeEventListener()
subscriptions.push({ eventType, callback })
return successResponse
subscriptions.push({ id, callback })

return response
})
}

/**
* Unsubscribe from previously subscribed client events
* @param eventType Event from SubscriptionEventType enum to be unsubscribed
* @param payload Additional params, for example `{ id: 'email-app', type: 'smartapp' }`
* @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)
const unsubscribeClientEvents = ({
eventType,
payload,
callback,
}: {
eventType: SubscriptionEventType
payload?: SubscriptionPayload
callback?: Function
}): Promise<StatusResponse> => {
const id = getSubscriptionId(eventType, payload)
const index = subscriptions.findIndex(sub => sub.id == id && sub.callback == callback)

if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)
if (index == -1) return Promise.reject(ERROR_CODES.SUBSCRIPTION_NOT_FOUND)
if (index == -1) return Promise.resolve(composeResponse(STATUS.ERROR, ERROR_CODES.SUBSCRIPTION_NOT_FOUND))

subscriptions.splice(index, 1)

// Send unsubscribe to client only at last subscription
if (isAnySubscriptionsOfType(eventType)) return Promise.resolve(successResponse)
if (isAnySubscriptions(eventType, payload)) return Promise.resolve(composeResponse(STATUS.SUCCESS))

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

export { subscribeClientEvents, unsubscribeClientEvents }
20 changes: 19 additions & 1 deletion src/lib/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
ERROR_CODES,
File,
GetConnectionStatusResponse,
GetUnreadCounterResponse,
METHODS,
StatusResponse,
SearchLocalPhonebookResponse,
StatusResponse,
SubscriptionPayload,
} from '../../types'
export * from './events'
export * from './storage'
Expand Down Expand Up @@ -165,6 +167,21 @@ const searchLocalPhonebook = ({ filter = null }: { filter: string | null }): Pro
.then(event => event as SearchLocalPhonebookResponse)
}

/**
* Get unread counter for chat/user/bot/smartapp.
* @returns Promise that'll be fullfilled with status data on success, otherwise rejected with reason
*/
const getUnreadCounter = async ({ type, id }: SubscriptionPayload): Promise<GetUnreadCounterResponse> => {
if (!bridge) return Promise.reject(ERROR_CODES.NO_BRIDGE)

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

return response as GetUnreadCounterResponse
}

export {
openFile,
openClientSettings,
Expand All @@ -178,4 +195,5 @@ export {
openChatMessage,
handleDeeplink,
searchLocalPhonebook,
getUnreadCounter,
}
1 change: 1 addition & 0 deletions src/types/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum METHODS {
CLIENT_STORAGE_CLEAR = 'client_storage_clear',
HANDLE_DEEPLINK = 'handle_deeplink',
SEARCH_LOCAL_PHONEBOOK = 'search_local_phonebook',
GET_UNREAD_COUNTER = 'get_unread_counter',
}

export enum STATUS {
Expand Down
57 changes: 36 additions & 21 deletions src/types/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,55 @@ import { EmitterEventPayload } from '@expressms/smartapp-bridge/build/main/types
import { STATUS } from './bridge'

export enum SubscriptionEventType {
CONNECTION_STATUS = "connection_status",
CONNECTION_STATUS = 'connection_status',
UNREAD_COUNTER_CHANGE = 'unread_counter_change',
}

export type GetConnectionStatusResponse = ({
ref: string,
export type SubscriptionPayload = {
type: 'huid' | 'chat' | 'smartapp'
id: string
}

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

export type CreateDeeplinkResponse = ({
ref: string,
export type CreateDeeplinkResponse = {
ref: string
payload: {
status: 'error' | 'success',
errorCode?: string,
status: 'error' | 'success'
errorCode?: string
data?: {
deeplink: string,
deeplink: string
}
}
})
}

type LocalPhonebookEntry = ({
avatar: string | null,
name: string | null,
type LocalPhonebookEntry = {
avatar: string | null
name: string | null
contacts: {
contactType: string,
contact: string,
}[],
})
contactType: string
contact: string
}[]
}

export interface SearchLocalPhonebookResponse extends EmitterEventPayload {
payload: {
status: STATUS,
errorCode?: string | null,
localPhonebookEntries: Array<LocalPhonebookEntry>,
status: STATUS
errorCode?: string | null
localPhonebookEntries: Array<LocalPhonebookEntry>
}
}

export type GetUnreadCounterResponse = {
ref: string
payload: {
status: STATUS
errorCode?: string | null
unreadCounter: number
}
}
Loading