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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
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
10 changes: 10 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| 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 | |
| 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 | |

#### Example
```javascript
Expand DownExpand Up@@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

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
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand DownExpand Up@@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All@@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

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 (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All@@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All@@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All@@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand DownExpand Up@@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});