diff --git a/.changeset/add-auth-tokens-support.md b/.changeset/add-auth-tokens-support.md new file mode 100644 index 00000000..0c8af282 --- /dev/null +++ b/.changeset/add-auth-tokens-support.md @@ -0,0 +1,18 @@ +--- +"react-use-intercom": minor +--- + +Add support for auth_tokens in boot and update methods, plus a setAuthTokens method + +Users can now pass authentication tokens to Intercom for secure data operations. The `authTokens` property accepts an object with any string key-value pairs, and the `setAuthTokens` method refreshes them at runtime without a full update. + +Example usage: +```js +boot({ + email: 'john.doe@example.com', + userId: '9876', + authTokens: { + security_token: 'abc...' // JWT + } +}) +``` \ No newline at end of file diff --git a/examples/auth-tokens-example.tsx b/examples/auth-tokens-example.tsx new file mode 100644 index 00000000..0f996f94 --- /dev/null +++ b/examples/auth-tokens-example.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { IntercomProvider, useIntercom } from 'react-use-intercom'; + +function MyApp() { + const { boot } = useIntercom(); + + const handleLogin = () => { + // After successful login, boot Intercom with auth tokens + boot({ + email: 'john.doe@example.com', + createdAt: 1234567890, + name: 'John Doe', + userId: '9876', + authTokens: { + security_token: 'abc...', // Your JWT token + // You can add any other tokens as key-value pairs + api_token: 'xyz...', + custom_token: '123...' + } + }); + }; + + return ( +
+

Intercom Auth Tokens Example

+ +
+ ); +} + +export default function App() { + return ( + + + + ); +} \ No newline at end of file diff --git a/packages/react-use-intercom/README.md b/packages/react-use-intercom/README.md index a7c41f02..e9a56e68 100644 --- a/packages/react-use-intercom/README.md +++ b/packages/react-use-intercom/README.md @@ -157,6 +157,7 @@ Used to retrieve all methods bundled with Intercom. These are based on the offic | showTicket | (ticketId: number) => void | Opens the Messenger with the specified ticket by `ticketId` | showConversation | (conversationId: number) => void | Opens the Messenger with the specified conversation by `conversationId` | hideNotifications | (hidden: boolean) => void | controls the visibility of in-app notifications, pass `true` to hide or `false` to show them +| setAuthTokens | (authTokens: AuthTokens) => void | sets/refreshes the per-user Data Connector auth tokens at runtime without a full `update` #### Example ```ts @@ -194,6 +195,7 @@ const HomePage = () => { showTicket, showConversation, hideNotifications, + setAuthTokens, } = useIntercom(); const bootWithProps = () => boot({ name: 'Russo' }); @@ -216,6 +218,8 @@ const HomePage = () => { const handleShowTicket = () => showTicket(123); const handleShowConversation = () => showConversation(123); const handleHideNotifications = () => hideNotifications(true); + const handleSetAuthTokens = () => + setAuthTokens({ security_token: 'your-jwt' }); return ( <> @@ -247,6 +251,7 @@ const HomePage = () => { + ); }; @@ -270,6 +275,31 @@ All the Intercom default attributes/props are camel cased (`appId` instead of `a }) ``` + #### Authentication tokens + For secure data operations, you can pass authentication tokens to Intercom using the `authTokens` property. This accepts an object with any string key-value pairs. + + ```ts + const { boot } = useIntercom(); + + boot({ + email: 'john.doe@example.com', + userId: '9876', + authTokens: { + security_token: 'abc...', // JWT token + api_token: 'xyz...', + // Any other tokens as key-value pairs + } +}) + ``` + +This is distinct from `intercomUserJwt`, which is for Messenger identity verification. To refresh a token during a session without sending a full `update`, use the `setAuthTokens` method: + +```ts +const { setAuthTokens } = useIntercom(); + +setAuthTokens({ security_token: 'refreshed-jwt' }); +``` + ## Playground Small playground to showcase the functionalities of `react-use-intercom`. diff --git a/packages/react-use-intercom/src/mappers.ts b/packages/react-use-intercom/src/mappers.ts index 8ae4b15c..91f2dcf8 100644 --- a/packages/react-use-intercom/src/mappers.ts +++ b/packages/react-use-intercom/src/mappers.ts @@ -80,6 +80,7 @@ export const mapDataAttributesToRawDataAttributes = ( ), intercom_user_jwt: attributes.intercomUserJwt, page_title: attributes.pageTitle, + auth_tokens: attributes.authTokens, ...attributes.customAttributes, }); diff --git a/packages/react-use-intercom/src/provider.tsx b/packages/react-use-intercom/src/provider.tsx index 9bed329a..5a21fbd5 100644 --- a/packages/react-use-intercom/src/provider.tsx +++ b/packages/react-use-intercom/src/provider.tsx @@ -6,6 +6,7 @@ import initialize from './initialize'; import * as logger from './logger'; import { mapIntercomPropsToRawIntercomProps } from './mappers'; import { + AuthTokens, IntercomContextValues, IntercomProps, IntercomProviderProps, @@ -319,6 +320,15 @@ export const IntercomProvider: React.FC< [ensureIntercom], ); + const setAuthTokens = React.useCallback( + (authTokens: AuthTokens) => { + ensureIntercom('setAuthTokens', () => { + IntercomAPI('setAuthTokens', authTokens); + }); + }, + [ensureIntercom], + ); + const providerValue = React.useMemo(() => { return { boot, @@ -342,6 +352,7 @@ export const IntercomProvider: React.FC< showTicket, showConversation, hideNotifications, + setAuthTokens, }; }, [ boot, @@ -365,6 +376,7 @@ export const IntercomProvider: React.FC< showTicket, showConversation, hideNotifications, + setAuthTokens, ]); return ( diff --git a/packages/react-use-intercom/src/types.ts b/packages/react-use-intercom/src/types.ts index 8e2f5dde..2a3e38b0 100644 --- a/packages/react-use-intercom/src/types.ts +++ b/packages/react-use-intercom/src/types.ts @@ -145,6 +145,8 @@ export type DataAttributesAvatar = { imageUrl?: string; }; +export type AuthTokens = Record; + export type RawDataAttributes = { email?: string; user_id?: string; @@ -166,6 +168,7 @@ export type RawDataAttributes = { intercom_user_jwt?: string; page_title?: string; customAttributes?: Record; + auth_tokens?: AuthTokens; }; export type DataAttributes = { @@ -271,9 +274,26 @@ export type DataAttributes = { * ``` * * @see {@link https://www.intercom.com/help/en/articles/179-send-custom-user-attributes-to-intercom} - * @remarks The key is the attribute name. The value is a placeholder for the data you’ll track + * @remarks The key is the attribute name. The value is a placeholder for the data you'll track */ customAttributes?: Record; + /** + * Authentication tokens for secure data operations + * Can contain any key-value pairs where both key and value are strings + * + * @example + * ``` + * authTokens: { + * security_token: 'abc...' // JWT + * } + * ``` + * + * @see {@link https://www.intercom.com/help/en/articles/6615543-setting-up-data-connectors-authentication#h_5343ec2d2c} + * + * @remarks Distinct from `intercomUserJwt` (Messenger identity verification). To refresh tokens at + * runtime without a full `update`, use the `setAuthTokens` method. + */ + authTokens?: AuthTokens; }; export type IntercomMethod = @@ -299,7 +319,8 @@ export type IntercomMethod = | 'showNews' | 'showTicket' | 'showConversation' - | 'hideNotifications'; + | 'hideNotifications' + | 'setAuthTokens'; export type RawIntercomProps = RawMessengerAttributes & RawDataAttributes; @@ -530,6 +551,17 @@ export type IntercomContextValues = { * @param hidden `true` to hide notifications, `false` to show them */ hideNotifications: (hidden: boolean) => void; + /** + * Sets or refreshes the per-user authentication tokens used for Data Connector requests, + * without sending a full `update`. + * + * @remarks Use this to refresh a short-lived token during a session. For the initial tokens, + * pass `authTokens` to `boot`/`update` instead. + * @see {@link https://www.intercom.com/help/en/articles/6615543-setting-up-data-connectors-authentication} + * + * @param authTokens object of token name → token (JWT) pairs + */ + setAuthTokens: (authTokens: AuthTokens) => void; }; export type IntercomProviderProps = { diff --git a/packages/react-use-intercom/test/authTokens.test.tsx b/packages/react-use-intercom/test/authTokens.test.tsx new file mode 100644 index 00000000..8b5f92bc --- /dev/null +++ b/packages/react-use-intercom/test/authTokens.test.tsx @@ -0,0 +1,101 @@ +import { act, renderHook } from '@testing-library/react'; +import * as React from 'react'; + +import { IntercomProvider, useIntercom } from '../src'; + +describe('auth_tokens', () => { + it('should pass auth_tokens during boot', () => { + const appId = 'app123'; + const mockIntercom = jest.fn(); + window.Intercom = mockIntercom; + window.intercomSettings = undefined; + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + + const { result } = renderHook(() => useIntercom(), { wrapper }); + + act(() => { + result.current.boot({ + email: 'john.doe@example.com', + createdAt: 1234567890, + name: 'John Doe', + userId: '9876', + authTokens: { + security_token: 'abc123', + another_token: 'xyz789', + }, + }); + }); + + expect(mockIntercom).toHaveBeenCalledWith('boot', { + app_id: appId, + email: 'john.doe@example.com', + created_at: 1234567890, + name: 'John Doe', + user_id: '9876', + auth_tokens: { + security_token: 'abc123', + another_token: 'xyz789', + }, + }); + }); + + it('should pass auth_tokens during update', () => { + const appId = 'app123'; + const mockIntercom = jest.fn(); + window.Intercom = mockIntercom; + window.intercomSettings = { app_id: appId }; + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useIntercom(), { wrapper }); + + act(() => { + result.current.update({ + authTokens: { + security_token: 'updated_token', + }, + }); + }); + + const updateCalls = mockIntercom.mock.calls.filter( + (call) => call[0] === 'update', + ); + const lastUpdateCall = updateCalls[updateCalls.length - 1]; + expect(lastUpdateCall).toBeDefined(); + expect(lastUpdateCall[1]).toMatchObject({ + auth_tokens: { + security_token: 'updated_token', + }, + }); + }); + + it('should pass auth_tokens through setAuthTokens', () => { + const appId = 'app123'; + const mockIntercom = jest.fn(); + window.Intercom = mockIntercom; + window.intercomSettings = { app_id: appId }; + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useIntercom(), { wrapper }); + + act(() => { + result.current.setAuthTokens({ security_token: 'refreshed_token' }); + }); + + expect(mockIntercom).toHaveBeenCalledWith('setAuthTokens', { + security_token: 'refreshed_token', + }); + }); +});