Skip to content

Commit 4fb766b

Browse files
authored
Merge pull request #835 from devrnt/adopt-crossorigin-load-callbacks
Add crossOrigin, onLoad and onLoadFailed options to IntercomProvider
2 parents 11d6a73 + fbcf07b commit 4fb766b

11 files changed

Lines changed: 226 additions & 2 deletions

File tree

‎.changeset/tidy-otters-jam.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-use-intercom': minor
3+
---
4+
5+
Add `crossOrigin`, `onLoad` and `onLoadFailed` options to `IntercomProvider` to set the messenger `<script>` crossorigin attribute and detect when the messenger script loads or fails to load
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference types="cypress" />
2+
3+
describe('crossOrigin', () => {
4+
it('should set the crossorigin attribute on the Messenger script tag', () => {
5+
cy.visit('/useIntercomWithCrossOrigin');
6+
7+
cy.get('head script[src*="widget.intercom.io"]').should(
8+
'have.attr',
9+
'crossorigin',
10+
'anonymous',
11+
);
12+
});
13+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference types="cypress" />
2+
3+
describe('onLoad / onLoadFailed', () => {
4+
it('should call onLoad when the Messenger script loads successfully', () => {
5+
// Bypass the cache so the script is actually (re)requested on each run
6+
cy.intercept('https://widget.intercom.io/widget/jcabc7e3', (req) => {
7+
req.continue((res) => {
8+
res.headers['cache-control'] = 'no-cache';
9+
});
10+
});
11+
12+
cy.visit('/useIntercomWithLoadCallbacks');
13+
14+
cy.get('[data-cy=call]').should('have.text', 'onLoad was called!');
15+
});
16+
17+
it('should call onLoadFailed when the Messenger script fails to load', () => {
18+
cy.intercept('https://widget.intercom.io/widget/jcabc7e3', {
19+
forceNetworkError: true,
20+
});
21+
22+
cy.visit('/useIntercomWithLoadCallbacks');
23+
24+
cy.get('[data-cy=call]').should('have.text', 'onLoadFailed was called!');
25+
});
26+
});

‎apps/playground/src/app.tsx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
ProviderPage,
1010
UseIntercomPage,
1111
UseIntercomTourPage,
12+
UseIntercomWithCrossOrigin,
1213
UseIntercomWithDelay,
14+
UseIntercomWithLoadCallbacks,
1315
} from './modules';
1416
import { Page, Style } from './modules/common';
1517

@@ -52,10 +54,18 @@ const App = () => {
5254
/>
5355
<Route path="/useIntercom" component={UseIntercomPage} />
5456
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
57+
<Route
58+
path="/useIntercomWithCrossOrigin"
59+
component={UseIntercomWithCrossOrigin}
60+
/>
5561
<Route
5662
path="/useIntercomWithTimeout"
5763
component={UseIntercomWithDelay}
5864
/>
65+
<Route
66+
path="/useIntercomWithLoadCallbacks"
67+
component={UseIntercomWithLoadCallbacks}
68+
/>
5969
<Route path="/" exact>
6070
<Navigation>
6171
<Link to="/provider">
@@ -70,9 +80,15 @@ const App = () => {
7080
<Link to="/useIntercomTour">
7181
<code>useIntercom with tour</code>
7282
</Link>
83+
<Link to="/useIntercomWithCrossOrigin">
84+
<code>useIntercom with crossOrigin</code>
85+
</Link>
7386
<Link to="/useIntercomWithTimeout">
7487
<code>useIntercom with delayed boot</code>
7588
</Link>
89+
<Link to="/useIntercomWithLoadCallbacks">
90+
<code>useIntercom with load callbacks</code>
91+
</Link>
7692
</Navigation>
7793
</Route>
7894
</Router>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export { default as UseIntercomPage } from './useIntercom';
22
export { default as UseIntercomTourPage } from './useIntercomTour';
3+
export { default as UseIntercomWithCrossOrigin } from './useIntercomWithCrossOrigin';
34
export { default as UseIntercomWithDelay } from './useIntercomWithDelay';
5+
export { default as UseIntercomWithLoadCallbacks } from './useIntercomWithLoadCallbacks';
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as React from 'react';
2+
import { IntercomProvider } from 'react-use-intercom';
3+
import styled from 'styled-components';
4+
5+
const Grid = styled.div`
6+
display: grid;
7+
grid-template-columns: repeat(1, 1fr);
8+
width: 100%;
9+
`;
10+
11+
const Item = styled.div`
12+
display: grid;
13+
grid-template-rows: min-content;
14+
15+
&::after {
16+
content: '';
17+
margin: 2rem 0 1.5rem;
18+
border-bottom: 2px solid var(--grey);
19+
width: 100%;
20+
}
21+
`;
22+
23+
const RawUseIntercomPage = () => {
24+
return (
25+
<Grid>
26+
<Item>
27+
<p>
28+
Intercom will be initialized with{' '}
29+
<code>crossOrigin: "anonymous"</code> (and autobooted)
30+
</p>
31+
</Item>
32+
</Grid>
33+
);
34+
};
35+
36+
const UseIntercomWithCrossOriginPage = () => {
37+
return (
38+
<IntercomProvider appId="jcabc7e3" autoBoot crossOrigin="anonymous">
39+
<RawUseIntercomPage />
40+
</IntercomProvider>
41+
);
42+
};
43+
44+
export default UseIntercomWithCrossOriginPage;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as React from 'react';
2+
import { IntercomProvider } from 'react-use-intercom';
3+
import styled from 'styled-components';
4+
5+
const Grid = styled.div`
6+
display: grid;
7+
grid-template-columns: repeat(1, 1fr);
8+
width: 100%;
9+
`;
10+
11+
const Item = styled.div`
12+
display: grid;
13+
grid-template-rows: min-content;
14+
15+
&::after {
16+
content: '';
17+
margin: 2rem 0 1.5rem;
18+
border-bottom: 2px solid var(--grey);
19+
width: 100%;
20+
}
21+
`;
22+
23+
const RawUseIntercomPage = ({ call }: { call: string | undefined }) => {
24+
return (
25+
<Grid>
26+
<Item>
27+
<p>
28+
The Intercom Messenger script will be loaded and{' '}
29+
<code>onLoad/onLoadFailed</code> be called
30+
</p>
31+
<p data-cy="call">{call ?? 'Waiting…'}</p>
32+
</Item>
33+
</Grid>
34+
);
35+
};
36+
37+
const UseIntercomWithLoadCallbacks = () => {
38+
const [call, setCall] = React.useState<string>();
39+
40+
return (
41+
<IntercomProvider
42+
appId="jcabc7e3"
43+
autoBoot
44+
onLoad={() => setCall('onLoad was called!')}
45+
onLoadFailed={() => setCall('onLoadFailed was called!')}
46+
>
47+
<RawUseIntercomPage call={call} />
48+
</IntercomProvider>
49+
);
50+
};
51+
52+
export default UseIntercomWithLoadCallbacks;

‎packages/react-use-intercom/README.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ Place the `IntercomProvider` as high as possible in your application. This will
9191
| apiBase | string | If you need to route your Messenger requests through a different endpoint than the default. Generally speaking, this is not needed.<br/> Format: `https://${INTERCOM_APP_ID}.intercom-messenger.com` (See: [https://github.com/devrnt/react-use-intercom/pull/96](https://github.com/devrnt/react-use-intercom/pull/96)) | false | |
9292
| initializeDelay | number | Indicates if the intercom initialization should be delayed, delay is in ms, defaults to 0. See https://github.com/devrnt/react-use-intercom/pull/236 | false | |
9393
| autoBootProps | IntercomProps | Pass properties to `boot` method when `autoBoot` is `true` | false | |
94+
| crossOrigin | 'anonymous' \| 'use-credentials' \| '' | Sets the [`crossOrigin`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#crossorigin) attribute on the Messenger `<script>` (standard DOM attribute, not an Intercom API). Use `'anonymous'` for full error details in error logging; works because Intercom's CDN allows CORS | false | |
95+
| onLoad | () => void | triggered when the Messenger script has loaded successfully | false | |
96+
| onLoadFailed | () => void | triggered when the Messenger script has failed to load (network issues, Intercom downtime, firewall, blocking browser extensions, ...) | false | |
9497

9598
#### Example
9699
```ts
@@ -306,6 +309,24 @@ Since [v1.2.0](https://github.com/devrnt/react-use-intercom/releases/tag/v1.2.0)
306309

307310
For reference see https://github.com/devrnt/react-use-intercom/pull/236 and https://forum.intercom.com/s/question/0D52G00004WxWLs/can-i-delay-loading-intercom-on-my-site-to-reduce-the-js-load
308311

312+
### Detect a broken Messenger
313+
314+
The Messenger script can fail to load for various reasons, e.g. network issues, Intercom downtime, firewall rules, or browser extensions like tracking blockers.
315+
316+
Pass `onLoadFailed` to `<IntercomProvider />` to detect when that happens and offer the user an alternative support channel. Use `onLoad` to know when the script loaded successfully (e.g. to hide a loading state).
317+
318+
```tsx
319+
<IntercomProvider
320+
appId={INTERCOM_APP_ID}
321+
onLoad={() => console.log('Messenger loaded')}
322+
onLoadFailed={() => console.log('Messenger failed to load')}
323+
>
324+
...
325+
</IntercomProvider>
326+
```
327+
328+
Pass `crossOrigin="anonymous"` to unlock full error details for errors thrown by the Messenger loader script — a standard [`<script>` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#crossorigin) rather than an Intercom feature. It works because Intercom's CDN serves the script with permissive CORS headers.
329+
309330
## Contributing
310331

311332
Contributions are welcome! Please read the [Contributing Guidelines](CONTRIBUTING.md) for details on how to contribute to the project.

‎packages/react-use-intercom/src/initialize.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@
88
* @param appId - Intercom app id
99
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
1010
* @param [cspNonce=undefined] - Content-Security-Policy nonce to use for the Intercom <script> tag during initializing
11+
* @param [crossOrigin=undefined] - `crossOrigin` attribute to set on the Intercom <script> tag
12+
* @param [onLoad=undefined] - Called when the Intercom <script> tag has loaded successfully
13+
* @param [onLoadFailed=undefined] - Called when the Intercom <script> tag has failed to load
1114
*
1215
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
1316
*/
14-
const initialize = (appId: string, timeout: number = 0, cspNonce?: string) => {
17+
const initialize = (
18+
appId: string,
19+
timeout: number = 0,
20+
cspNonce?: string,
21+
crossOrigin?: string,
22+
onLoad?: () => void,
23+
onLoadFailed?: () => void,
24+
) => {
1525
var w = window;
1626
var ic = w.Intercom;
1727
if (typeof ic === 'function') {
@@ -33,7 +43,10 @@ const initialize = (appId: string, timeout: number = 0, cspNonce?: string) => {
3343
s.type = 'text/javascript';
3444
s.async = true;
3545
if (cspNonce) s.setAttribute('nonce', cspNonce);
46+
if (crossOrigin) s.crossOrigin = crossOrigin;
3647
s.src = 'https://widget.intercom.io/widget/' + appId;
48+
if (onLoad) s.addEventListener('load', onLoad);
49+
if (onLoadFailed) s.addEventListener('error', onLoadFailed);
3750
var x = d.getElementsByTagName('script')[0];
3851
x.parentNode.insertBefore(s, x);
3952
}, timeout);

‎packages/react-use-intercom/src/provider.tsx‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ export const IntercomProvider: React.FC<
2121
autoBoot = false,
2222
autoBootProps,
2323
children,
24+
crossOrigin,
2425
onHide,
26+
onLoad,
27+
onLoadFailed,
2528
onShow,
2629
onUnreadCountChange,
2730
onUserEmailSupplied,
@@ -103,7 +106,14 @@ export const IntercomProvider: React.FC<
103106
);
104107

105108
if (!isSSR && shouldInitialize && !isInitialized.current) {
106-
initialize(appId, initializeDelay, cspNonce);
109+
initialize(
110+
appId,
111+
initializeDelay,
112+
cspNonce,
113+
crossOrigin,
114+
onLoad,
115+
onLoadFailed,
116+
);
107117

108118
if (autoBoot) {
109119
boot(autoBootProps);

0 commit comments

Comments
 (0)