diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 31c02371b..f6ec0e777 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -50,6 +50,15 @@ module.exports = { 'includeRoles': ['button'] }, ], + '@typescript-eslint/no-unused-vars': [ + 1, + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }, + ], }, settings: { react: { diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index 1cdd6add8..76a51dc94 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -41,8 +41,6 @@ const Chat = () => { stores.sdkStore.initialized, ]); - - const onBeforeSendMessage = < T extends UserMessageCreateParams | FileMessageCreateParams >( diff --git a/src/const.ts b/src/const.ts index 4bea88654..e7e64b01c 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,6 +1,4 @@ -import SendbirdChat, { SessionHandler } from '@sendbird/chat'; -import { type SendbirdGroupChat } from '@sendbird/chat/groupChannel'; -import { type SendbirdOpenChat } from '@sendbird/chat/openChannel'; +import { SessionHandler } from '@sendbird/chat'; import React from 'react'; import { StringSet } from '@uikit/ui/Label/stringSet'; @@ -67,9 +65,7 @@ export const DEFAULT_CONSTANT = { enableResetHistoryOnConnect: false, } satisfies Partial; -type ConfigureSession = ( - sdk: SendbirdChat | SendbirdGroupChat | SendbirdOpenChat -) => SessionHandler; +type ConfigureSession = () => SessionHandler; type MessageData = { suggested_replies?: string[]; diff --git a/src/context/WidgetSettingContext.tsx b/src/context/WidgetSettingContext.tsx index f4d3ea6e6..103438493 100644 --- a/src/context/WidgetSettingContext.tsx +++ b/src/context/WidgetSettingContext.tsx @@ -19,10 +19,10 @@ interface WidgetSession { strategy: 'auto' | 'manual'; userId: string; expireAt: number; - // channelUrl is optional and is dynamically generated for a manual strategy. - channelUrl?: string; // sessionToken is optional and is dynamically generated by configureSession provided by the user for a manual strategy. sessionToken?: string; + // channelUrl is optional and is dynamically generated for a legacy manual strategy. + channelUrl?: string; } export interface BotStyle { @@ -85,7 +85,9 @@ export const WidgetSettingProvider = ({ botId, }); - const reuseCachedSession = ((cache: typeof cachedSession): cache is NonNullable => { + const reuseCachedSession = (( + cache: typeof cachedSession + ): cache is NonNullable => { if (!cache || cache.strategy !== strategy) return false; if (cache.strategy === 'manual') { // NOTE: There is no need to check the expiration of the session if it is managed manually. @@ -98,27 +100,49 @@ export const WidgetSettingProvider = ({ return false; })(cachedSession); - const response = await getWidgetSetting({ + const getConfigureSessionParams = async () => { + if (strategy !== 'manual') return undefined; + + if (injectedUserId && configureSession && !reuseCachedSession) { + const sessionHandler = configureSession(); + if (sessionHandler.onSessionTokenRequired) { + try { + const token = await new Promise( + sessionHandler.onSessionTokenRequired + ); + if (token) return { userId: injectedUserId, sessionKey: token }; + } catch { + // NO-OP + } + } + } + return undefined; + }; + + const widgetSettingParams = { host: apiHost, appId, botId, - createUserAndChannel: reuseCachedSession ? false : strategy === 'auto', - }); + createUserAndChannel: !reuseCachedSession, + ...(await getConfigureSessionParams()), + }; + const response = await getWidgetSetting(widgetSettingParams); setBotStyle(response.botStyle); + if (reuseCachedSession) { setWidgetSession({ strategy: sessionStrategy, userId: cachedSession.userId, expireAt: cachedSession.expireAt, - channelUrl: cachedSession.channelUrl, sessionToken: cachedSession.sessionToken, + channelUrl: cachedSession.channelUrl, }); } else { if (sessionStrategy === 'auto' && response.user && response.channel) { const session = { strategy: sessionStrategy, - expireAt: response.user.expireAt, userId: response.user.userId, + expireAt: response.user.expireAt, sessionToken: response.user.sessionToken, channelUrl: response.channel.channelUrl, }; @@ -126,25 +150,38 @@ export const WidgetSettingProvider = ({ saveWidgetSessionCache({ appId, botId, data: session }); } - /** - * NOTE: We don't fully initialize the manual strategy session here. - * After the uikit is initialized, we should call the `initManualSession` function. - * */ if (sessionStrategy === 'manual' && injectedUserId) { - const session = { - strategy: sessionStrategy, - userId: injectedUserId, - sessionToken: undefined, - channelUrl: undefined, - expireAt: 0, - }; - setWidgetSession(session); + if (response.channel) { + const session = { + strategy: sessionStrategy, + userId: injectedUserId, + expireAt: getDateNDaysLater(30), + sessionToken: widgetSettingParams.sessionKey, + channelUrl: response.channel.channelUrl, + }; + setWidgetSession(session); + saveWidgetSessionCache({ appId, botId, data: session }); + } else { + /** + * TODO: Remove this after the widget_setting deployed to all region. + * NOTE: We don't fully initialize the legacy manual strategy session here. + * After the uikit is initialized, we should call the `initManualSession` function. + * */ + const session = { + strategy: sessionStrategy, + userId: injectedUserId, + expireAt: 0, + sessionToken: widgetSettingParams.sessionKey, + channelUrl: undefined, + }; + setWidgetSession(session); + } } } - setInitialized(true); } + // TODO: Remove this after the widget_setting deployed to all region. async function initManualSession( sdk: SendbirdChatWith<[GroupChannelModule]> ) { @@ -163,9 +200,9 @@ export const WidgetSettingProvider = ({ const session = { strategy: sessionStrategy, - expireAt: getDateNDaysLater(30), userId: injectedUserId, - sessionToken: undefined, + expireAt: getDateNDaysLater(30), + sessionToken: widgetSession?.sessionToken, channelUrl: channel.url, }; setWidgetSession(session); diff --git a/src/libs/api/widgetSetting.ts b/src/libs/api/widgetSetting.ts index 43a14222f..5d75e5dc1 100644 --- a/src/libs/api/widgetSetting.ts +++ b/src/libs/api/widgetSetting.ts @@ -20,11 +20,19 @@ type APIResponse = { }; }; -type Params = { +interface Params extends ConfigureSessionParams { host: string; botId: string; appId: string; createUserAndChannel: boolean; +} + +/** + * This is required to configure the session manually. + * */ +type ConfigureSessionParams = { + userId?: string; + sessionKey?: string; }; type Response = { @@ -50,17 +58,21 @@ export async function getWidgetSetting({ botId, appId, createUserAndChannel, + userId, + sessionKey, }: Params): Promise { - const params = new URLSearchParams({ + const headers = sessionKey ? { 'Session-Key': sessionKey } : undefined; + const params = asQueryParams({ create_user_and_channel: createUserAndChannel ? 'True' : 'False', - }).toString(); + user_id: createUserAndChannel && userId ? userId : undefined, + }); const path = resolvePath( host, `/v3/bots/${botId}/${appId}/widget_setting?${params}` ); - const response = await fetch(path); + const response = await fetch(path, { headers }); const result = await response.json(); if (!response.ok) { throw new Error(result.message || 'Something went wrong'); @@ -92,3 +104,10 @@ export async function getWidgetSetting({ : undefined, }; } + +function asQueryParams(obj: object) { + return Object.entries(obj) + .filter(([_, value]) => value !== undefined && value !== null) + .map(([key, value]) => `${key}=${value}`) + .join('&'); +}