Skip to content

Commit 66d7988

Browse files
cursoragentarmenzg
andcommitted
Fix Intercom in-flight boot cross-org race
Co-authored-by: Armen Zambrano G. <armenzg@users.noreply.github.com>
1 parent 27a607c commit 66d7988

File tree

1 file changed

+43
-9
lines changed

1 file changed

+43
-9
lines changed

static/app/utils/intercom.tsx

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,29 @@ interface IntercomJwtResponse {
2424

2525
let hasBooted = false;
2626
let bootPromise: Promise<void> | null = null;
27+
let bootingOrgSlug: string | null = null;
2728
let bootedOrgSlug: string | null = null;
2829

2930
/**
3031
* Initialize Intercom with identity verification.
3132
* Only fetches JWT and boots on first call.
3233
*/
3334
async function initIntercom(orgSlug: string): Promise<void> {
34-
if (hasBooted) {
35+
if (hasBooted && bootedOrgSlug === orgSlug) {
3536
return;
3637
}
3738

38-
// Prevent concurrent initialization
39-
if (bootPromise) {
39+
// Prevent concurrent initialization for the same organization
40+
if (bootPromise && bootingOrgSlug === orgSlug) {
4041
return bootPromise;
4142
}
4243

43-
bootPromise = (async () => {
44+
// If a different org is currently booting, invalidate it first.
45+
if (bootPromise && bootingOrgSlug !== orgSlug) {
46+
await shutdownIntercom();
47+
}
48+
49+
const currentBootPromise = (async () => {
4450
try {
4551
const intercomAppId = ConfigStore.get('intercomAppId');
4652
if (!intercomAppId) {
@@ -55,6 +61,12 @@ async function initIntercom(orgSlug: string): Promise<void> {
5561

5662
// Boot Intercom with user data
5763
const {default: Intercom} = await import('@intercom/messenger-js-sdk');
64+
65+
// Boot may have been invalidated while async work was in-flight.
66+
if (bootPromise !== currentBootPromise || bootingOrgSlug !== orgSlug) {
67+
return;
68+
}
69+
5870
Intercom({
5971
app_id: intercomAppId,
6072
user_id: jwtData.userData.userId,
@@ -71,21 +83,36 @@ async function initIntercom(orgSlug: string): Promise<void> {
7183

7284
hasBooted = true;
7385
bootedOrgSlug = orgSlug;
86+
87+
if (bootPromise === currentBootPromise) {
88+
bootPromise = null;
89+
bootingOrgSlug = null;
90+
}
7491
} catch (error) {
75-
// Reset so user can retry on next click
76-
bootPromise = null;
77-
throw error;
92+
// Reset so user can retry on next click.
93+
if (bootPromise === currentBootPromise) {
94+
bootPromise = null;
95+
bootingOrgSlug = null;
96+
throw error;
97+
}
7898
}
7999
})();
80100

81-
return bootPromise;
101+
bootPromise = currentBootPromise;
102+
bootingOrgSlug = orgSlug;
103+
104+
return currentBootPromise;
82105
}
83106

84107
/**
85108
* Shutdown Intercom and clear session data.
86109
* Call this when user logs out or switches organizations.
87110
*/
88111
export async function shutdownIntercom(): Promise<void> {
112+
// Invalidate any in-flight boot immediately.
113+
bootPromise = null;
114+
bootingOrgSlug = null;
115+
89116
if (!hasBooted) {
90117
return;
91118
}
@@ -105,11 +132,18 @@ export async function shutdownIntercom(): Promise<void> {
105132
*/
106133
export async function showIntercom(orgSlug: string): Promise<void> {
107134
// If booted for a different org, shutdown first to re-initialize with new org context
108-
if (hasBooted && bootedOrgSlug !== orgSlug) {
135+
if (
136+
(hasBooted && bootedOrgSlug !== orgSlug) ||
137+
(bootPromise && bootingOrgSlug !== orgSlug)
138+
) {
109139
await shutdownIntercom();
110140
}
111141

112142
await initIntercom(orgSlug);
143+
if (!hasBooted || bootedOrgSlug !== orgSlug) {
144+
return;
145+
}
146+
113147
const {show} = await import('@intercom/messenger-js-sdk');
114148
show();
115149
}

0 commit comments

Comments
 (0)