Skip to content

Added crossOrigin, onLoad, and onLoadFailed config options #675

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .changeset/nice-dryers-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-use-intercom': minor
---

Added crossOrigin, onLoad, and onLoadFailed config options
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ Place the `IntercomProvider` as high as possible in your application. This will
| 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 | |
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also copy and paste this in the other README?

| 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 | |
| autoBootProps | IntercomProps | Pass properties to `boot` method when `autoBoot` is `true` | false | |
| crossOrigin | string | `crossOrigin` attribute value to pass to `<script>` tag that loads the messenger | false | |
| onLoad | () => void | triggered when the Messenger script has been loaded successfully | false | |
| onLoadFailed | () => void | triggered when the Messenger script has failed to load | false | |

#### Example
```ts
Expand Down Expand Up @@ -295,3 +298,9 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
Since [v1.2.0](https://github.com/devrnt/react-use-intercom/releases/tag/v1.2.0) it's possible to delay this initialisation by passing `initializeDelay` in `<IntercomProvider />` (it's in milliseconds). However most of the users won't need to mess with this.

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

### Detect a broken Messenger

There can be various reasons why the Messenger script has failed to load, e.g. network issues, Intercom downtime, firewall issues, or browser extensions like tracking blockers.

By using the `onLoadFailed` callback it's possible to detect when that happens and provide the user with alternative means of customer support.
12 changes: 12 additions & 0 deletions apps/playground/cypress/e2e/crossOrigin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference types="cypress" />

describe('use crossOrigin', () => {
it('should add attribute to script tag', () => {
cy.visit('/useIntercomWithCrossOrigin');

cy.document()
.get('head script')
.should('have.attr', 'crossOrigin')
.should('eq', 'anonymous');
});
});
29 changes: 29 additions & 0 deletions apps/playground/cypress/e2e/loadCallbacks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference types="cypress" />

describe('onLoad/onLoadFailed', () => {
it('should call onLoad when successful', () => {
cy.intercept('https://widget.intercom.io/widget/jcabc7e3', (request) => {
request.continue((response) => {
response.headers['cache-control'] = 'no-cache';
});
});

cy.visit('/useIntercomWithLoadCallbacks');

cy.get('[data-cy=call]').should(($p) =>
expect($p).to.have.text('onLoad was called!'),
);
});

it('should call onLoadFailed when not successful', () => {
cy.intercept('https://widget.intercom.io/widget/jcabc7e3', {
forceNetworkError: true,
});

cy.visit('/useIntercomWithLoadCallbacks');

cy.get('[data-cy=call]').should(($p) =>
expect($p).to.have.text('onLoadFailed was called!'),
);
});
});
16 changes: 16 additions & 0 deletions apps/playground/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
ProviderPage,
UseIntercomPage,
UseIntercomTourPage,
UseIntercomWithCrossOrigin,
UseIntercomWithDelay,
UseIntercomWithLoadCallbacks,
} from './modules';
import { Page, Style } from './modules/common';

Expand Down Expand Up @@ -52,6 +54,14 @@ const App = () => {
/>
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route
path="/useIntercomWithCrossOrigin"
component={UseIntercomWithCrossOrigin}
/>
<Route
path="/useIntercomWithLoadCallbacks"
component={UseIntercomWithLoadCallbacks}
/>
<Route
path="/useIntercomWithTimeout"
component={UseIntercomWithDelay}
Expand All @@ -70,9 +80,15 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithCrossOrigin">
<code>useIntercom with crossOrigin</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
<Link to="/useIntercomWithLoadCallbacks">
<code>useIntercom with load callbacks</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
2 changes: 2 additions & 0 deletions apps/playground/src/modules/useIntercom/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithCrossOrigin } from './useIntercomWithCrossOrigin';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay';
export { default as UseIntercomWithLoadCallbacks } from './useIntercomWithLoadCallbacks';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import { IntercomProvider } from 'react-use-intercom';
import styled from 'styled-components';

const Grid = styled.div`
display: grid;
grid-template-columns: repeat(1, 1fr);
width: 100%;
`;

const Item = styled.div`
display: grid;
grid-template-rows: min-content;

&::after {
content: '';
margin: 2rem 0 1.5rem;
border-bottom: 2px solid var(--grey);
width: 100%;
}
`;

const RawUseIntercomPage = () => {
return (
<Grid>
<Item>
<p>
Intercom will be initialized with{' '}
<code>crossOrigin: "anonymous"</code> (and autobooted)
</p>
</Item>
</Grid>
);
};

const UseIntercomWithCrossOriginPage = () => {
return (
<IntercomProvider appId="jcabc7e3" autoBoot crossOrigin="anonymous">
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithCrossOriginPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useState } from 'react';
import * as React from 'react';
import { IntercomProvider } from 'react-use-intercom';
import styled from 'styled-components';

const Grid = styled.div`
display: grid;
grid-template-columns: repeat(1, 1fr);
width: 100%;
`;

const Item = styled.div`
display: grid;
grid-template-rows: min-content;

&::after {
content: '';
margin: 2rem 0 1.5rem;
border-bottom: 2px solid var(--grey);
width: 100%;
}
`;

const RawUseIntercomPage = ({ call }: { call: string | undefined }) => {
return (
<Grid>
<Item>
<p>
The Intercom Messenger script will be loaded and{' '}
<code>onLoad/onLoadFailed</code> be called
</p>
<p data-cy="call">{call ?? 'Waiting…'}</p>
</Item>
</Grid>
);
};

const UseIntercomWithLoadCallbacks = () => {
const [call, setCall] = useState<string>();

return (
<IntercomProvider
appId="jcabc7e3"
autoBoot
onLoad={() => setCall('onLoad was called!')}
onLoadFailed={() => setCall('onLoadFailed was called!')}
>
<RawUseIntercomPage call={call} />
</IntercomProvider>
);
};

export default UseIntercomWithLoadCallbacks;
23 changes: 22 additions & 1 deletion packages/react-use-intercom/src/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
* @param [crossOrigin=undefined] - `crossOrigin` attribute value to use for the `<script>` tag, defaults to `undefined`
* @param [onLoad=undefined] - Called when the Messenger script has been loaded successfully, defaults to `undefined`.
* @param [onLoadFailed=undefined] - Called when the Messenger script has failed to load, defaults to `undefined`.
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string, timeout = 0) => {
const initialize = (
appId: string,
timeout = 0,
crossOrigin: string | undefined = undefined,
onLoad: () => void = undefined,
onLoadFailed: () => void = undefined,
) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All @@ -26,9 +35,21 @@ const initialize = (appId: string, timeout = 0) => {
var l = function () {
setTimeout(function () {
var s = d.createElement('script');
s.crossOrigin = crossOrigin;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
s.crossOrigin = crossOrigin;
s.crossorigin = crossOrigin;

s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
if (onLoad) {
s.addEventListener('load', () => {
onLoad();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be undefined right?

});
}
if (onLoadFailed) {
s.addEventListener('error', () => {
// No need to pass any information from the ErrorEvent because it will contain no information about the error.
onLoadFailed();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be also be undefined right?

});
}
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
Expand Down
5 changes: 4 additions & 1 deletion packages/react-use-intercom/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export const IntercomProvider: React.FC<
autoBoot = false,
autoBootProps,
children,
crossOrigin,
onHide,
onLoad,
onLoadFailed,
onShow,
onUnreadCountChange,
onUserEmailSupplied,
Expand Down Expand Up @@ -84,7 +87,7 @@ export const IntercomProvider: React.FC<
}, [onShow, setIsOpen]);

if (!isSSR && shouldInitialize && !isInitialized.current) {
initialize(appId, initializeDelay);
initialize(appId, initializeDelay, crossOrigin, onLoad, onLoadFailed);

// attach listeners
IntercomAPI('onHide', onHideWrapper);
Expand Down
19 changes: 19 additions & 0 deletions packages/react-use-intercom/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,29 @@ export type IntercomProviderProps = {
* @remarks if `true`, 'boot' does not need to be called manually
* */
autoBoot?: boolean;
/**
* The optional `crossOrigin` attribute value to use for the `<script>` tag that loads the messenger.
*
* Use `crossOrigin: "anonymous"` to have errors thrown by the Messenger contain all information (file name, line, message, etc.).
* This is useful for error logging and following up with Intercom's own tech support.
*
* Note that this doesn't work for errors thrown because the Messenger script file has failed to load.
*/
crossOrigin?: 'anonymous' | 'use-credentials' | '' | undefined;
/**
* When we hide the messenger, you can hook into the event. This requires a function argument.
*/
onHide?: () => void;
/**
* Called when the Messenger script file has been loaded successfully.
*/
onLoad?: () => void;
/**
* Called when the Messenger script file has failed to load.
*
* You can use this to let the customer know the support chat is unavailable and provide an alternative communication method.
*/
onLoadFailed?: () => void;
/**
* When we show the messenger, you can hook into the event. This requires a function argument.
*/
Expand Down
Loading