Skip to content

Commit efb2805

Browse files
authored
fix: fixes missing context in first launch event after install (#451)
* fix: fixes missing context in first launch event after install * fix: fixing detox test, refactoring a single callback for onStoreReady
1 parent 0a51559 commit efb2805

File tree

3 files changed

+74
-45
lines changed

3 files changed

+74
-45
lines changed

example/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ PODS:
286286
- React
287287
- RNGestureHandler (2.2.0):
288288
- React-Core
289-
- segment-analytics-react-native (2.1.2-beta):
289+
- segment-analytics-react-native (2.1.4-beta):
290290
- React-Core
291291
- segment-analytics-react-native-plugin-idfa (0.2.0-beta):
292292
- React-Core
293-
- sovran-react-native (0.2.3):
293+
- sovran-react-native (0.2.4):
294294
- React-Core
295295
- Yoga (1.14.0)
296296

@@ -458,9 +458,9 @@ SPEC CHECKSUMS:
458458
RNCAsyncStorage: b49b4e38a1548d03b74b30e558a1d18465b94be7
459459
RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489
460460
RNGestureHandler: bf572f552ea324acd5b5464b8d30755b2d8c1de6
461-
segment-analytics-react-native: 76fa77e887ea38d063e01ec8877eaaf39745790d
461+
segment-analytics-react-native: cafec7a2e5f20b4fb6e872f8055574e982c7bb0f
462462
segment-analytics-react-native-plugin-idfa: 2dc6e38506a5b034db4a4cf16db48643b2f356a2
463-
sovran-react-native: 814ebda5c04a60a4f9eea1b203b95f2f64bca291
463+
sovran-react-native: 1b68d70aaa2d96489e0338eaf3a4cbf92688c793
464464
Yoga: 3f5bfc54ce164fcd5b5d7f9f4232182d6298dd56
465465

466466
PODFILE CHECKSUM: 0c7eb82d495ca56953c50916b7b49e7512632eb6

packages/core/src/analytics.ts

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ export class SegmentClient {
5454
// internal time to know when to flush, ticks every second
5555
private flushInterval: ReturnType<typeof setInterval> | null = null;
5656

57-
// Watcher for isReady updates to the storage
58-
private readinessWatcher?: Unsubscribe = undefined;
59-
6057
// unsubscribe watchers for the store
6158
private watchers: Unsubscribe[] = [];
6259

@@ -70,8 +67,8 @@ export class SegmentClient {
7067

7168
private timeline: Timeline;
7269

73-
// mechanism to prevent adding plugins before we are fully initalised
74-
private isStorageReady = false;
70+
private pendingEvents: SegmentEvent[] = [];
71+
7572
private pluginsToAdd: Plugin[] = [];
7673

7774
private isInitialized = false;
@@ -167,9 +164,6 @@ export class SegmentClient {
167164
this.add({ plugin: segmentDestination });
168165
}
169166

170-
// Setup platform specific plugins
171-
this.platformPlugins.forEach((plugin) => this.add({ plugin: plugin }));
172-
173167
// Initialize the watchables
174168
this.context = {
175169
get: this.store.context.get,
@@ -199,6 +193,13 @@ export class SegmentClient {
199193
get: this.store.events.get,
200194
onChange: this.store.events.onChange,
201195
};
196+
197+
// Watch for isReady so that we can handle any pending events
198+
// Delays events processing in the timeline until the store is ready to prevent missing data injected from the plugins
199+
this.store.isReady.onChange((value) => this.onStorageReady(value));
200+
201+
// Setup platform specific plugins
202+
this.platformPlugins.forEach((plugin) => this.add({ plugin: plugin }));
202203
}
203204

204205
/**
@@ -211,12 +212,6 @@ export class SegmentClient {
211212
return;
212213
}
213214

214-
// Plugin interval check
215-
if (this.store.isReady.get()) {
216-
this.onStorageReady(true);
217-
} else {
218-
this.store.isReady.onChange((value) => this.onStorageReady(value));
219-
}
220215
await this.fetchSettings();
221216

222217
// flush any stored events
@@ -290,7 +285,6 @@ export class SegmentClient {
290285
clearInterval(this.flushInterval);
291286
}
292287

293-
this.unsubscribeReadinessWatcher();
294288
this.unsubscribeStorageWatchers();
295289

296290
this.appStateSubscription?.remove();
@@ -358,7 +352,7 @@ export class SegmentClient {
358352
this.store.settings.add((plugin as DestinationPlugin).key, settings);
359353
}
360354

361-
if (!this.isStorageReady) {
355+
if (!this.store.isReady.get()) {
362356
this.pluginsToAdd.push(plugin);
363357
} else {
364358
this.addPlugin(plugin);
@@ -381,7 +375,11 @@ export class SegmentClient {
381375

382376
process(incomingEvent: SegmentEvent) {
383377
const event = applyRawEventData(incomingEvent, this.store.userInfo.get());
384-
this.timeline.process(event);
378+
if (this.store.isReady.get() === true) {
379+
this.timeline.process(event);
380+
} else {
381+
this.pendingEvents.push(event);
382+
}
385383
}
386384

387385
private async trackDeepLinks() {
@@ -399,29 +397,34 @@ export class SegmentClient {
399397
}
400398
}
401399

402-
private unsubscribeReadinessWatcher() {
403-
this.readinessWatcher?.();
404-
}
405-
400+
/**
401+
* Executes when the state store is initialized.
402+
* @param isReady
403+
*/
406404
private onStorageReady(isReady: boolean) {
407-
if (isReady && this.pluginsToAdd.length > 0 && !this.isAddingPlugins) {
408-
this.isAddingPlugins = true;
409-
try {
410-
// start by adding the plugins
411-
this.pluginsToAdd.forEach((plugin) => {
412-
this.addPlugin(plugin);
413-
});
414-
415-
// now that they're all added, clear the cache
416-
// this prevents this block running for every update
417-
this.pluginsToAdd = [];
405+
if (isReady) {
406+
// Add all plugins awaiting store
407+
if (this.pluginsToAdd.length > 0 && !this.isAddingPlugins) {
408+
this.isAddingPlugins = true;
409+
try {
410+
// start by adding the plugins
411+
this.pluginsToAdd.forEach((plugin) => {
412+
this.addPlugin(plugin);
413+
});
414+
415+
// now that they're all added, clear the cache
416+
// this prevents this block running for every update
417+
this.pluginsToAdd = [];
418+
} finally {
419+
this.isAddingPlugins = false;
420+
}
421+
}
418422

419-
// finally set the flag which means plugins will be added + registered immediately in future
420-
this.isStorageReady = true;
421-
this.unsubscribeReadinessWatcher();
422-
} finally {
423-
this.isAddingPlugins = false;
423+
// Send all events in the queue
424+
for (const e of this.pendingEvents) {
425+
this.timeline.process(e);
424426
}
427+
this.pendingEvents = [];
425428
}
426429
}
427430

packages/core/src/storage/sovranStorage.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,23 @@ const INITIAL_VALUES: Data = {
3131
},
3232
};
3333

34+
interface ReadinessStore {
35+
hasLoadedContext: boolean;
36+
}
37+
3438
export class SovranStorage implements Storage {
3539
private storeId: string;
40+
private readinessStore: Store<ReadinessStore>;
3641
private contextStore: Store<{ context: DeepPartial<Context> }>;
3742
private settingsStore: Store<{ settings: SegmentAPIIntegrations }>;
3843
private eventsStore: Store<{ events: SegmentEvent[] }>;
3944
private userInfoStore: Store<{ userInfo: UserInfoState }>;
4045

4146
constructor(storeId: string) {
4247
this.storeId = storeId;
48+
this.readinessStore = createStore<ReadinessStore>({
49+
hasLoadedContext: false,
50+
});
4351
this.contextStore = createStore(
4452
{ context: INITIAL_VALUES.context },
4553
{
@@ -68,6 +76,17 @@ export class SovranStorage implements Storage {
6876
);
6977

7078
this.fixAnonymousId();
79+
80+
// Wait for context to be loaded
81+
const unsubscribeContext = this.contextStore.subscribe((store) => {
82+
if (store.context !== INITIAL_VALUES.context) {
83+
this.readinessStore.dispatch((state) => ({
84+
...state,
85+
hasLoadedContext: true,
86+
}));
87+
unsubscribeContext();
88+
}
89+
});
7190
}
7291

7392
/**
@@ -86,11 +105,18 @@ export class SovranStorage implements Storage {
86105
});
87106
};
88107

108+
// Check for all things that need to be ready before sending events through the timeline
89109
readonly isReady = {
90-
get: () => true,
91-
onChange: (_callback: (value: boolean) => void) => {
92-
// No need to do anything since storage is always ready
93-
return () => {};
110+
get: () => {
111+
const ready = this.readinessStore.getState();
112+
return ready.hasLoadedContext;
113+
},
114+
onChange: (callback: (value: boolean) => void) => {
115+
return this.readinessStore.subscribe((store) => {
116+
if (store.hasLoadedContext) {
117+
callback(true);
118+
}
119+
});
94120
},
95121
};
96122

0 commit comments

Comments
 (0)