pulse-updates 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,8 @@ private class PulseReactHostDelegate(
32
32
  override val jsRuntimeFactory: JSRuntimeFactory = HermesInstance(),
33
33
  override val bindingsInstaller: BindingsInstaller? = null,
34
34
  override val turboModuleManagerDelegateBuilder: ReactPackageTurboModuleManagerDelegate.Builder = DefaultTurboModuleManagerDelegate.Builder(),
35
- private val exceptionHandler: (Exception) -> Unit = { throw it }
35
+ private val exceptionHandler: (Exception) -> Unit = { throw it },
36
+ private val useDevSupport: Boolean = false
36
37
  ) : ReactHostDelegate {
37
38
 
38
39
  /**
@@ -44,6 +45,16 @@ private class PulseReactHostDelegate(
44
45
  val context = weakContext.get()
45
46
  ?: throw IllegalStateException("Context is no longer available")
46
47
 
48
+ // A dev build takes its JS from Metro, so there is no Pulse artefact to
49
+ // restore and nothing to verify: the embedded asset is a plain Metro
50
+ // bundle, prepare() rightly returns null for it, and validating anyway
51
+ // threw at the first frame — an unrecoverable crash-loop that surviving
52
+ // app data made look permanent. Stand aside instead, exactly as an app
53
+ // without Pulse would, and let React Native load the asset.
54
+ if (useDevSupport) {
55
+ return JSBundleLoader.createAssetLoader(context, "assets://index.android.bundle", false)
56
+ }
57
+
47
58
  val bundleFile = PulseUpdatesModule.getBundleFile(context)
48
59
 
49
60
  return if (bundleFile != null) {
@@ -95,7 +106,8 @@ object PulseReactHostFactory {
95
106
  val delegate = PulseReactHostDelegate(
96
107
  weakContext = weakContext,
97
108
  jsMainModulePath = jsMainModuleName,
98
- reactPackages = packages
109
+ reactPackages = packages,
110
+ useDevSupport = useDevSupport
99
111
  )
100
112
 
101
113
  val componentFactory = ComponentFactory()
@@ -199,33 +199,43 @@ class PulseUpdatesModule(private val reactContext: ReactApplicationContext) :
199
199
  return@runOnUiThread
200
200
  }
201
201
 
202
- // Try new architecture first (ReactHost) - like expo-updates does
202
+ // New architecture (ReactHost) first.
203
+ //
204
+ // Reached through the TYPED interface, not `javaClass.getMethod("getReactHost")`:
205
+ // reflection by name looks the method up on the app's own class, and in a
206
+ // minified release R8 has renamed it. The lookup then throws, the code falls
207
+ // through to the bridge path below, and that path is gone on bridgeless — the
208
+ // user taps "Reload" and gets `ReactInstanceManager.createReactContext is
209
+ // unsupported` with the update sitting there, applied only at the next cold
210
+ // start. `ReactApplication.getReactHost()` is part of the RN interface, so it
211
+ // survives minification; older RN without it throws a LINKAGE error, which is
212
+ // caught separately so those runtimes still reach the fallback.
203
213
  try {
204
- val reactHost = reactApplication.javaClass.getMethod("getReactHost").invoke(reactApplication)
214
+ val reactHost = reactApplication.reactHost
205
215
  if (reactHost != null) {
206
216
  pulseLog(TAG, "reloadAsync: using ReactHost.reload()")
207
217
 
208
- // Check lifecycle state and resume if needed (like expo does)
218
+ // Resume first when the host is not RESUMED: reload on a paused host
219
+ // is a no-op (same reason expo-updates does this).
209
220
  val currentActivity = reactContext.currentActivity
210
221
  try {
211
- val lifecycleState = reactHost.javaClass.getMethod("getLifecycleState").invoke(reactHost)
212
- val resumedState = Class.forName("com.facebook.react.common.LifecycleState")
213
- .getField("RESUMED").get(null)
214
- if (lifecycleState != resumedState && currentActivity != null) {
215
- reactHost.javaClass.getMethod("onHostResume", android.app.Activity::class.java)
216
- .invoke(reactHost, currentActivity)
222
+ if (reactHost.lifecycleState != com.facebook.react.common.LifecycleState.RESUMED && currentActivity != null) {
223
+ reactHost.onHostResume(currentActivity)
217
224
  }
218
225
  } catch (e: Exception) {
219
226
  pulseLogWarn(TAG, "reloadAsync: could not check/resume lifecycle: ${e.message}")
220
227
  }
221
228
 
222
- reactHost.javaClass.getMethod("reload", String::class.java)
223
- .invoke(reactHost, "PulseUpdates reload")
229
+ reactHost.reload("PulseUpdates reload")
224
230
  promise.resolve(null)
225
231
  return@runOnUiThread
226
232
  }
227
- } catch (e: Exception) {
233
+ pulseLog(TAG, "reloadAsync: ReactHost is null — falling back to the bridge path")
234
+ } catch (e: LinkageError) {
235
+ // RN too old to expose ReactHost at all.
228
236
  pulseLog(TAG, "reloadAsync: ReactHost not available: ${e.message}")
237
+ } catch (e: Exception) {
238
+ pulseLogWarn(TAG, "reloadAsync: ReactHost.reload failed: ${e.message}")
229
239
  }
230
240
 
231
241
  // Fall back to old architecture (ReactNativeHost)
@@ -206,6 +206,14 @@ public class PulseUpdates: RCTEventEmitter {
206
206
 
207
207
  @objc
208
208
  public static func getBundleURL() -> URL? {
209
+ #if DEBUG
210
+ // A dev build serves its JS from Metro. Handing back a Pulse asset here
211
+ // means the app runs neither what the developer just wrote nor anything
212
+ // Pulse can vouch for, so return nil and let the host fall back to its
213
+ // normal debug source — the Android side stands aside the same way.
214
+ pulseLog("getBundleURL skipped: debug build, JS comes from the dev server")
215
+ return nil
216
+ #else
209
217
  // If controller hasn't started yet, start it now
210
218
  if !PulseController.shared.isStarted {
211
219
  PulseController.shared.initializeWithoutStarting()
@@ -214,6 +222,7 @@ public class PulseUpdates: RCTEventEmitter {
214
222
  let url = PulseController.shared.launchAssetURL()
215
223
  pulseLog("getBundleURL isStarted=\(PulseController.shared.isStarted) url=\(url?.lastPathComponent ?? "nil")")
216
224
  return url
225
+ #endif
217
226
  }
218
227
 
219
228
  // MARK: - Helpers
@@ -8,6 +8,7 @@ exports.configureConfig = configureConfig;
8
8
  exports.fetchConfig = fetchConfig;
9
9
  exports.getAllConfig = getAllConfig;
10
10
  exports.getConfigBoolean = getConfigBoolean;
11
+ exports.getConfigExperiments = getConfigExperiments;
11
12
  exports.getConfigInfo = getConfigInfo;
12
13
  exports.getConfigJson = getConfigJson;
13
14
  exports.getConfigKeys = getConfigKeys;
@@ -15,6 +16,7 @@ exports.getConfigNumber = getConfigNumber;
15
16
  exports.getConfigSource = getConfigSource;
16
17
  exports.getConfigString = getConfigString;
17
18
  exports.getConfigValue = getConfigValue;
19
+ exports.getConfigVariant = getConfigVariant;
18
20
  exports.hasPendingConfig = hasPendingConfig;
19
21
  exports.onConfigChange = onConfigChange;
20
22
  exports.resetConfigForTests = resetConfigForTests;
@@ -42,22 +44,55 @@ exports.startConfigAutoRefresh = startConfigAutoRefresh;
42
44
  * config costs a 304 with no body.
43
45
  */
44
46
 
47
+ /**
48
+ * An A/B arm this install was assigned, as the server resolved it.
49
+ *
50
+ * Carried separately from the values even though the arm has already been folded
51
+ * into them: a control arm and an install outside the experiment are served exactly
52
+ * the same config, so the values cannot say which is which. Without the name here,
53
+ * every analytics event would be missing the one dimension the experiment is for.
54
+ */
55
+
45
56
  /**
46
57
  * Everything the server may ever want to target on. Sent on every request even when
47
58
  * no rule uses it yet: the client can only start sending a new field with a store
48
59
  * build, so a contract that is incomplete today cannot be completed for months.
49
60
  */
50
61
 
62
+ /**
63
+ * The values and the arms move as one unit, always.
64
+ *
65
+ * They are two halves of a single answer: the arm is what produced those values.
66
+ * Applying one without the other — on activate, on a cache read, on a partial
67
+ * payload — would tag events with an arm whose values are not in force, and the
68
+ * resulting experiment would report a difference between two groups that were both
69
+ * served the same thing.
70
+ */
71
+
51
72
  const STORAGE_KEY = 'pulse.config.v1';
52
73
  const DEFAULT_POLL_MS = 5 * 60 * 1000;
53
74
  const DEFAULT_TIMEOUT_MS = 10_000;
54
75
  let options = null;
55
76
  let defaults = {};
56
77
  let values = {};
78
+ let experiments = [];
57
79
  let etag = null;
58
80
  let fetchedAt = 0;
81
+ /**
82
+ * When the server last confirmed these values are current — a 200 that applied a
83
+ * payload, or a 304 that said the one we hold is still right.
84
+ *
85
+ * Distinct from fetchedAt, which is when the values were last *changed*. A caller
86
+ * comparing this config against another source needs to know the difference: values
87
+ * restored from disk and never revalidated may be days behind what the server would
88
+ * answer now, and reporting them as "what Pulse says" turns a stale cache into a
89
+ * disagreement that nobody can act on.
90
+ */
91
+ let verifiedAt = 0;
59
92
  let source = 'defaults';
60
93
  let pending = null;
94
+ /** The arm set already reported this session, so a poll does not re-report it. */
95
+ let reportedExposure = null;
61
96
  let inFlight = null;
62
97
  let pollTimer = null;
63
98
  const listeners = new Set();
@@ -75,6 +110,7 @@ function configureConfig(opts) {
75
110
  const cached = readCache(opts.storage);
76
111
  if (cached) {
77
112
  values = cached.values;
113
+ experiments = cached.experiments ?? [];
78
114
  etag = cached.etag;
79
115
  fetchedAt = cached.fetchedAt;
80
116
  source = 'cache';
@@ -114,7 +150,13 @@ async function fetchConfig() {
114
150
  headers: buildHeaders(opts, etag),
115
151
  signal: controller?.signal
116
152
  });
117
- if (response.status === 304) return false;
153
+
154
+ // Nothing changed — which is a positive answer about freshness, not a
155
+ // non-event: the values we hold are the ones the server would send.
156
+ if (response.status === 304) {
157
+ verifiedAt = Date.now();
158
+ return false;
159
+ }
118
160
  if (!response.ok) throw new Error(`Pulse config HTTP ${response.status}`);
119
161
  const payload = await response.json();
120
162
  const nextValues = payload?.values;
@@ -125,11 +167,16 @@ async function fetchConfig() {
125
167
  }
126
168
  etag = response.headers?.get?.('etag') ?? null;
127
169
  fetchedAt = Date.now();
170
+ verifiedAt = fetchedAt;
171
+ const snapshot = {
172
+ values: nextValues,
173
+ experiments: parseExperiments(payload?.experiments)
174
+ };
128
175
  if (opts.activateOnFetch === false) {
129
- pending = nextValues;
176
+ pending = snapshot;
130
177
  return false;
131
178
  }
132
- return applyValues(nextValues, opts);
179
+ return applySnapshot(snapshot, opts);
133
180
  } catch (error) {
134
181
  // A failed refresh must never clear what we have: the whole point of the cache
135
182
  // is that an unreachable server degrades to the last good config.
@@ -151,7 +198,7 @@ function activateConfig() {
151
198
  if (!pending || !options) return false;
152
199
  const next = pending;
153
200
  pending = null;
154
- return applyValues(next, options);
201
+ return applySnapshot(next, options);
155
202
  }
156
203
 
157
204
  /** Whether a fetched-but-not-yet-applied payload is waiting. */
@@ -178,22 +225,92 @@ function startConfigAutoRefresh(appState) {
178
225
  stopPolling();
179
226
  };
180
227
  }
181
- function applyValues(next, opts) {
182
- const changed = !shallowEqual(values, next);
183
- values = next;
228
+ function applySnapshot(next, opts) {
229
+ const changed = !shallowEqual(values, next.values) || !sameExperiments(experiments, next.experiments);
230
+ values = next.values;
231
+ experiments = next.experiments;
184
232
  source = 'remote';
185
233
  writeCache(opts.storage, {
186
234
  values,
235
+ experiments,
187
236
  etag,
188
237
  fetchedAt
189
238
  });
190
239
 
240
+ // Reported on apply, never on fetch: with activateOnFetch false a payload can sit
241
+ // unapplied for the rest of a session, and an arm the app is not actually serving
242
+ // is not an exposure.
243
+ void reportExposure(opts);
244
+
191
245
  // Listeners drive re-renders and gate re-evaluation: firing them for an identical
192
246
  // payload is pure churn, and the 200-with-same-content case is common enough
193
247
  // (a proxy that drops the ETag) to be worth checking.
194
248
  if (changed) notify();
195
249
  return changed;
196
250
  }
251
+
252
+ /**
253
+ * Tell the server which arms this install ended up in.
254
+ *
255
+ * The server already knows how it would assign a given device, so this is not how
256
+ * the experiment is measured — it is how a lopsided split gets noticed. A hash over
257
+ * a real population is only asymptotically fair, installs without a device id sit
258
+ * outside every experiment, and an audience filters unevenly across platforms; all
259
+ * three show up here as arms that are not the size they should be.
260
+ *
261
+ * Sent once per distinct set of arms per session rather than on every poll. The
262
+ * server counts one row per install per session, so a five-minute poll would
263
+ * multiply every arm by the length of the session and make a long user look like a
264
+ * crowd.
265
+ *
266
+ * Failure is swallowed on purpose. This is a diagnostic, and no diagnostic is worth
267
+ * a rejected promise on the path that delivers config to the app.
268
+ */
269
+ async function reportExposure(opts) {
270
+ if (opts.reportExposure !== true) return;
271
+ if (experiments.length === 0) return;
272
+ const signature = experiments.map(e => `${e.key}=${e.variant}`).join(',');
273
+ if (signature === reportedExposure) return;
274
+ reportedExposure = signature;
275
+ const context = opts.getContext?.() ?? {};
276
+ const assignments = {};
277
+ for (const entry of experiments) assignments[entry.key] = entry.variant;
278
+ try {
279
+ await fetch(`${opts.url.replace(/\/+$/, '')}/exposure`, {
280
+ method: 'POST',
281
+ headers: {
282
+ 'Content-Type': 'application/json'
283
+ },
284
+ body: JSON.stringify({
285
+ platform: context.platform,
286
+ appVersion: context.appVersion,
287
+ assignments
288
+ })
289
+ });
290
+ } catch {
291
+ // A device that cannot report its arm still has the right arm.
292
+ reportedExposure = signature;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * The arms as the server sent them, keeping only entries that name both halves.
298
+ *
299
+ * A half-formed assignment is dropped rather than carried: an experiment key with
300
+ * no variant would tag every event with an empty cohort, which reads downstream as
301
+ * a third group that does not exist.
302
+ */
303
+ function parseExperiments(raw) {
304
+ if (!Array.isArray(raw)) return [];
305
+ return raw.filter(entry => !!entry && typeof entry === 'object' && typeof entry.key === 'string' && typeof entry.variant === 'string' && entry.key !== '' && entry.variant !== '').map(entry => ({
306
+ key: entry.key,
307
+ variant: entry.variant
308
+ }));
309
+ }
310
+ function sameExperiments(a, b) {
311
+ if (a.length !== b.length) return false;
312
+ return a.every((entry, i) => entry.key === b[i].key && entry.variant === b[i].variant);
313
+ }
197
314
  function shallowEqual(a, b) {
198
315
  const aKeys = Object.keys(a);
199
316
  const bKeys = Object.keys(b);
@@ -277,6 +394,24 @@ function getConfigSource(key) {
277
394
  if (Object.prototype.hasOwnProperty.call(defaults, key)) return 'default';
278
395
  return 'missing';
279
396
  }
397
+
398
+ /**
399
+ * The A/B arms this install is in, for tagging analytics events.
400
+ *
401
+ * Empty when nothing is running — which is also what an install with no device id
402
+ * gets, since an install that draws a fresh arm on every request would take both
403
+ * sides in turn and pollute both.
404
+ */
405
+ function getConfigExperiments() {
406
+ return experiments.map(entry => ({
407
+ ...entry
408
+ }));
409
+ }
410
+
411
+ /** The arm for one experiment, or null when this install is not in it. */
412
+ function getConfigVariant(experimentKey) {
413
+ return experiments.find(entry => entry.key === experimentKey)?.variant ?? null;
414
+ }
280
415
  function getConfigKeys() {
281
416
  return Object.keys(getAllConfig()).sort();
282
417
  }
@@ -290,6 +425,7 @@ function getConfigInfo() {
290
425
  return {
291
426
  source,
292
427
  fetchedAt,
428
+ verifiedAt,
293
429
  etag,
294
430
  keyCount: Object.keys(getAllConfig()).length
295
431
  };
@@ -299,12 +435,15 @@ function getConfigInfo() {
299
435
  function resetConfigForTests() {
300
436
  options = null;
301
437
  defaults = {};
438
+ verifiedAt = 0;
302
439
  values = {};
440
+ experiments = [];
303
441
  etag = null;
304
442
  fetchedAt = 0;
305
443
  source = 'defaults';
306
444
  inFlight = null;
307
445
  pending = null;
446
+ reportedExposure = null;
308
447
  listeners.clear();
309
448
  stopPolling();
310
449
  }
@@ -321,6 +460,7 @@ function buildHeaders(opts, currentEtag) {
321
460
  if (ctx.osVersion) headers['X-Pulse-Os-Version'] = ctx.osVersion;
322
461
  if (ctx.language) headers['X-Pulse-Language'] = ctx.language;
323
462
  if (ctx.deviceId) headers['Pulse-Device-Id'] = ctx.deviceId;
463
+ if (ctx.userId) headers['Pulse-User-Id'] = ctx.userId;
324
464
  if (ctx.userAttributes && Object.keys(ctx.userAttributes).length > 0) {
325
465
  headers['X-Pulse-User-Attributes'] = JSON.stringify(ctx.userAttributes);
326
466
  }
@@ -1 +1 @@
1
- {"version":3,"names":["STORAGE_KEY","DEFAULT_POLL_MS","DEFAULT_TIMEOUT_MS","options","defaults","values","etag","fetchedAt","source","pending","inFlight","pollTimer","listeners","Set","configureConfig","opts","cached","readCache","storage","setConfigDefaults","more","fetchConfig","minInterval","minimumFetchIntervalMs","Date","now","controller","AbortController","timer","setTimeout","abort","timeoutMs","response","fetch","url","method","headers","buildHeaders","signal","status","ok","Error","payload","json","nextValues","Array","isArray","get","activateOnFetch","applyValues","error","onError","clearTimeout","activateConfig","next","hasPendingConfig","startConfigAutoRefresh","appState","subscription","addEventListener","state","interval","pollIntervalMs","stopPolling","setInterval","remove","changed","shallowEqual","writeCache","notify","a","b","aKeys","Object","keys","bKeys","length","every","k","av","bv","JSON","stringify","clearInterval","getConfigValue","key","prototype","hasOwnProperty","call","fallback","undefined","getConfigBoolean","value","toLowerCase","getConfigNumber","parsed","Number","isNaN","getConfigString","String","getAllConfig","getConfigJson","parse","getConfigSource","getConfigKeys","sort","onConfigChange","listener","add","delete","getConfigInfo","keyCount","resetConfigForTests","clear","currentEtag","ctx","getContext","Accept","platform","appVersion","osVersion","language","deviceId","userAttributes","raw","getString","set","snapshot","forEach"],"sourceRoot":"../../src","sources":["config.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["STORAGE_KEY","DEFAULT_POLL_MS","DEFAULT_TIMEOUT_MS","options","defaults","values","experiments","etag","fetchedAt","verifiedAt","source","pending","reportedExposure","inFlight","pollTimer","listeners","Set","configureConfig","opts","cached","readCache","storage","setConfigDefaults","more","fetchConfig","minInterval","minimumFetchIntervalMs","Date","now","controller","AbortController","timer","setTimeout","abort","timeoutMs","response","fetch","url","method","headers","buildHeaders","signal","status","ok","Error","payload","json","nextValues","Array","isArray","get","snapshot","parseExperiments","activateOnFetch","applySnapshot","error","onError","clearTimeout","activateConfig","next","hasPendingConfig","startConfigAutoRefresh","appState","subscription","addEventListener","state","interval","pollIntervalMs","stopPolling","setInterval","remove","changed","shallowEqual","sameExperiments","writeCache","reportExposure","notify","length","signature","map","e","key","variant","join","context","getContext","assignments","entry","replace","body","JSON","stringify","platform","appVersion","raw","filter","a","b","every","i","aKeys","Object","keys","bKeys","k","av","bv","clearInterval","getConfigValue","prototype","hasOwnProperty","call","fallback","undefined","getConfigBoolean","value","toLowerCase","getConfigNumber","parsed","Number","isNaN","getConfigString","String","getAllConfig","getConfigJson","parse","getConfigSource","getConfigExperiments","getConfigVariant","experimentKey","find","getConfigKeys","sort","onConfigChange","listener","add","delete","getConfigInfo","keyCount","resetConfigForTests","clear","currentEtag","ctx","Accept","osVersion","language","deviceId","userId","userAttributes","getString","set","forEach"],"sourceRoot":"../../src","sources":["config.ts"],"mappings":"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","ignoreList":[]}
@@ -20,22 +20,55 @@
20
20
  * config costs a 304 with no body.
21
21
  */
22
22
 
23
+ /**
24
+ * An A/B arm this install was assigned, as the server resolved it.
25
+ *
26
+ * Carried separately from the values even though the arm has already been folded
27
+ * into them: a control arm and an install outside the experiment are served exactly
28
+ * the same config, so the values cannot say which is which. Without the name here,
29
+ * every analytics event would be missing the one dimension the experiment is for.
30
+ */
31
+
23
32
  /**
24
33
  * Everything the server may ever want to target on. Sent on every request even when
25
34
  * no rule uses it yet: the client can only start sending a new field with a store
26
35
  * build, so a contract that is incomplete today cannot be completed for months.
27
36
  */
28
37
 
38
+ /**
39
+ * The values and the arms move as one unit, always.
40
+ *
41
+ * They are two halves of a single answer: the arm is what produced those values.
42
+ * Applying one without the other — on activate, on a cache read, on a partial
43
+ * payload — would tag events with an arm whose values are not in force, and the
44
+ * resulting experiment would report a difference between two groups that were both
45
+ * served the same thing.
46
+ */
47
+
29
48
  const STORAGE_KEY = 'pulse.config.v1';
30
49
  const DEFAULT_POLL_MS = 5 * 60 * 1000;
31
50
  const DEFAULT_TIMEOUT_MS = 10_000;
32
51
  let options = null;
33
52
  let defaults = {};
34
53
  let values = {};
54
+ let experiments = [];
35
55
  let etag = null;
36
56
  let fetchedAt = 0;
57
+ /**
58
+ * When the server last confirmed these values are current — a 200 that applied a
59
+ * payload, or a 304 that said the one we hold is still right.
60
+ *
61
+ * Distinct from fetchedAt, which is when the values were last *changed*. A caller
62
+ * comparing this config against another source needs to know the difference: values
63
+ * restored from disk and never revalidated may be days behind what the server would
64
+ * answer now, and reporting them as "what Pulse says" turns a stale cache into a
65
+ * disagreement that nobody can act on.
66
+ */
67
+ let verifiedAt = 0;
37
68
  let source = 'defaults';
38
69
  let pending = null;
70
+ /** The arm set already reported this session, so a poll does not re-report it. */
71
+ let reportedExposure = null;
39
72
  let inFlight = null;
40
73
  let pollTimer = null;
41
74
  const listeners = new Set();
@@ -53,6 +86,7 @@ export function configureConfig(opts) {
53
86
  const cached = readCache(opts.storage);
54
87
  if (cached) {
55
88
  values = cached.values;
89
+ experiments = cached.experiments ?? [];
56
90
  etag = cached.etag;
57
91
  fetchedAt = cached.fetchedAt;
58
92
  source = 'cache';
@@ -92,7 +126,13 @@ export async function fetchConfig() {
92
126
  headers: buildHeaders(opts, etag),
93
127
  signal: controller?.signal
94
128
  });
95
- if (response.status === 304) return false;
129
+
130
+ // Nothing changed — which is a positive answer about freshness, not a
131
+ // non-event: the values we hold are the ones the server would send.
132
+ if (response.status === 304) {
133
+ verifiedAt = Date.now();
134
+ return false;
135
+ }
96
136
  if (!response.ok) throw new Error(`Pulse config HTTP ${response.status}`);
97
137
  const payload = await response.json();
98
138
  const nextValues = payload?.values;
@@ -103,11 +143,16 @@ export async function fetchConfig() {
103
143
  }
104
144
  etag = response.headers?.get?.('etag') ?? null;
105
145
  fetchedAt = Date.now();
146
+ verifiedAt = fetchedAt;
147
+ const snapshot = {
148
+ values: nextValues,
149
+ experiments: parseExperiments(payload?.experiments)
150
+ };
106
151
  if (opts.activateOnFetch === false) {
107
- pending = nextValues;
152
+ pending = snapshot;
108
153
  return false;
109
154
  }
110
- return applyValues(nextValues, opts);
155
+ return applySnapshot(snapshot, opts);
111
156
  } catch (error) {
112
157
  // A failed refresh must never clear what we have: the whole point of the cache
113
158
  // is that an unreachable server degrades to the last good config.
@@ -129,7 +174,7 @@ export function activateConfig() {
129
174
  if (!pending || !options) return false;
130
175
  const next = pending;
131
176
  pending = null;
132
- return applyValues(next, options);
177
+ return applySnapshot(next, options);
133
178
  }
134
179
 
135
180
  /** Whether a fetched-but-not-yet-applied payload is waiting. */
@@ -156,22 +201,92 @@ export function startConfigAutoRefresh(appState) {
156
201
  stopPolling();
157
202
  };
158
203
  }
159
- function applyValues(next, opts) {
160
- const changed = !shallowEqual(values, next);
161
- values = next;
204
+ function applySnapshot(next, opts) {
205
+ const changed = !shallowEqual(values, next.values) || !sameExperiments(experiments, next.experiments);
206
+ values = next.values;
207
+ experiments = next.experiments;
162
208
  source = 'remote';
163
209
  writeCache(opts.storage, {
164
210
  values,
211
+ experiments,
165
212
  etag,
166
213
  fetchedAt
167
214
  });
168
215
 
216
+ // Reported on apply, never on fetch: with activateOnFetch false a payload can sit
217
+ // unapplied for the rest of a session, and an arm the app is not actually serving
218
+ // is not an exposure.
219
+ void reportExposure(opts);
220
+
169
221
  // Listeners drive re-renders and gate re-evaluation: firing them for an identical
170
222
  // payload is pure churn, and the 200-with-same-content case is common enough
171
223
  // (a proxy that drops the ETag) to be worth checking.
172
224
  if (changed) notify();
173
225
  return changed;
174
226
  }
227
+
228
+ /**
229
+ * Tell the server which arms this install ended up in.
230
+ *
231
+ * The server already knows how it would assign a given device, so this is not how
232
+ * the experiment is measured — it is how a lopsided split gets noticed. A hash over
233
+ * a real population is only asymptotically fair, installs without a device id sit
234
+ * outside every experiment, and an audience filters unevenly across platforms; all
235
+ * three show up here as arms that are not the size they should be.
236
+ *
237
+ * Sent once per distinct set of arms per session rather than on every poll. The
238
+ * server counts one row per install per session, so a five-minute poll would
239
+ * multiply every arm by the length of the session and make a long user look like a
240
+ * crowd.
241
+ *
242
+ * Failure is swallowed on purpose. This is a diagnostic, and no diagnostic is worth
243
+ * a rejected promise on the path that delivers config to the app.
244
+ */
245
+ async function reportExposure(opts) {
246
+ if (opts.reportExposure !== true) return;
247
+ if (experiments.length === 0) return;
248
+ const signature = experiments.map(e => `${e.key}=${e.variant}`).join(',');
249
+ if (signature === reportedExposure) return;
250
+ reportedExposure = signature;
251
+ const context = opts.getContext?.() ?? {};
252
+ const assignments = {};
253
+ for (const entry of experiments) assignments[entry.key] = entry.variant;
254
+ try {
255
+ await fetch(`${opts.url.replace(/\/+$/, '')}/exposure`, {
256
+ method: 'POST',
257
+ headers: {
258
+ 'Content-Type': 'application/json'
259
+ },
260
+ body: JSON.stringify({
261
+ platform: context.platform,
262
+ appVersion: context.appVersion,
263
+ assignments
264
+ })
265
+ });
266
+ } catch {
267
+ // A device that cannot report its arm still has the right arm.
268
+ reportedExposure = signature;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * The arms as the server sent them, keeping only entries that name both halves.
274
+ *
275
+ * A half-formed assignment is dropped rather than carried: an experiment key with
276
+ * no variant would tag every event with an empty cohort, which reads downstream as
277
+ * a third group that does not exist.
278
+ */
279
+ function parseExperiments(raw) {
280
+ if (!Array.isArray(raw)) return [];
281
+ return raw.filter(entry => !!entry && typeof entry === 'object' && typeof entry.key === 'string' && typeof entry.variant === 'string' && entry.key !== '' && entry.variant !== '').map(entry => ({
282
+ key: entry.key,
283
+ variant: entry.variant
284
+ }));
285
+ }
286
+ function sameExperiments(a, b) {
287
+ if (a.length !== b.length) return false;
288
+ return a.every((entry, i) => entry.key === b[i].key && entry.variant === b[i].variant);
289
+ }
175
290
  function shallowEqual(a, b) {
176
291
  const aKeys = Object.keys(a);
177
292
  const bKeys = Object.keys(b);
@@ -255,6 +370,24 @@ export function getConfigSource(key) {
255
370
  if (Object.prototype.hasOwnProperty.call(defaults, key)) return 'default';
256
371
  return 'missing';
257
372
  }
373
+
374
+ /**
375
+ * The A/B arms this install is in, for tagging analytics events.
376
+ *
377
+ * Empty when nothing is running — which is also what an install with no device id
378
+ * gets, since an install that draws a fresh arm on every request would take both
379
+ * sides in turn and pollute both.
380
+ */
381
+ export function getConfigExperiments() {
382
+ return experiments.map(entry => ({
383
+ ...entry
384
+ }));
385
+ }
386
+
387
+ /** The arm for one experiment, or null when this install is not in it. */
388
+ export function getConfigVariant(experimentKey) {
389
+ return experiments.find(entry => entry.key === experimentKey)?.variant ?? null;
390
+ }
258
391
  export function getConfigKeys() {
259
392
  return Object.keys(getAllConfig()).sort();
260
393
  }
@@ -268,6 +401,7 @@ export function getConfigInfo() {
268
401
  return {
269
402
  source,
270
403
  fetchedAt,
404
+ verifiedAt,
271
405
  etag,
272
406
  keyCount: Object.keys(getAllConfig()).length
273
407
  };
@@ -277,12 +411,15 @@ export function getConfigInfo() {
277
411
  export function resetConfigForTests() {
278
412
  options = null;
279
413
  defaults = {};
414
+ verifiedAt = 0;
280
415
  values = {};
416
+ experiments = [];
281
417
  etag = null;
282
418
  fetchedAt = 0;
283
419
  source = 'defaults';
284
420
  inFlight = null;
285
421
  pending = null;
422
+ reportedExposure = null;
286
423
  listeners.clear();
287
424
  stopPolling();
288
425
  }
@@ -299,6 +436,7 @@ function buildHeaders(opts, currentEtag) {
299
436
  if (ctx.osVersion) headers['X-Pulse-Os-Version'] = ctx.osVersion;
300
437
  if (ctx.language) headers['X-Pulse-Language'] = ctx.language;
301
438
  if (ctx.deviceId) headers['Pulse-Device-Id'] = ctx.deviceId;
439
+ if (ctx.userId) headers['Pulse-User-Id'] = ctx.userId;
302
440
  if (ctx.userAttributes && Object.keys(ctx.userAttributes).length > 0) {
303
441
  headers['X-Pulse-User-Attributes'] = JSON.stringify(ctx.userAttributes);
304
442
  }
@@ -1 +1 @@
1
- {"version":3,"names":["STORAGE_KEY","DEFAULT_POLL_MS","DEFAULT_TIMEOUT_MS","options","defaults","values","etag","fetchedAt","source","pending","inFlight","pollTimer","listeners","Set","configureConfig","opts","cached","readCache","storage","setConfigDefaults","more","fetchConfig","minInterval","minimumFetchIntervalMs","Date","now","controller","AbortController","timer","setTimeout","abort","timeoutMs","response","fetch","url","method","headers","buildHeaders","signal","status","ok","Error","payload","json","nextValues","Array","isArray","get","activateOnFetch","applyValues","error","onError","clearTimeout","activateConfig","next","hasPendingConfig","startConfigAutoRefresh","appState","subscription","addEventListener","state","interval","pollIntervalMs","stopPolling","setInterval","remove","changed","shallowEqual","writeCache","notify","a","b","aKeys","Object","keys","bKeys","length","every","k","av","bv","JSON","stringify","clearInterval","getConfigValue","key","prototype","hasOwnProperty","call","fallback","undefined","getConfigBoolean","value","toLowerCase","getConfigNumber","parsed","Number","isNaN","getConfigString","String","getAllConfig","getConfigJson","parse","getConfigSource","getConfigKeys","sort","onConfigChange","listener","add","delete","getConfigInfo","keyCount","resetConfigForTests","clear","currentEtag","ctx","getContext","Accept","platform","appVersion","osVersion","language","deviceId","userAttributes","raw","getString","set","snapshot","forEach"],"sourceRoot":"../../src","sources":["config.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["STORAGE_KEY","DEFAULT_POLL_MS","DEFAULT_TIMEOUT_MS","options","defaults","values","experiments","etag","fetchedAt","verifiedAt","source","pending","reportedExposure","inFlight","pollTimer","listeners","Set","configureConfig","opts","cached","readCache","storage","setConfigDefaults","more","fetchConfig","minInterval","minimumFetchIntervalMs","Date","now","controller","AbortController","timer","setTimeout","abort","timeoutMs","response","fetch","url","method","headers","buildHeaders","signal","status","ok","Error","payload","json","nextValues","Array","isArray","get","snapshot","parseExperiments","activateOnFetch","applySnapshot","error","onError","clearTimeout","activateConfig","next","hasPendingConfig","startConfigAutoRefresh","appState","subscription","addEventListener","state","interval","pollIntervalMs","stopPolling","setInterval","remove","changed","shallowEqual","sameExperiments","writeCache","reportExposure","notify","length","signature","map","e","key","variant","join","context","getContext","assignments","entry","replace","body","JSON","stringify","platform","appVersion","raw","filter","a","b","every","i","aKeys","Object","keys","bKeys","k","av","bv","clearInterval","getConfigValue","prototype","hasOwnProperty","call","fallback","undefined","getConfigBoolean","value","toLowerCase","getConfigNumber","parsed","Number","isNaN","getConfigString","String","getAllConfig","getConfigJson","parse","getConfigSource","getConfigExperiments","getConfigVariant","experimentKey","find","getConfigKeys","sort","onConfigChange","listener","add","delete","getConfigInfo","keyCount","resetConfigForTests","clear","currentEtag","ctx","Accept","osVersion","language","deviceId","userId","userAttributes","getString","set","forEach"],"sourceRoot":"../../src","sources":["config.ts"],"mappings":"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","ignoreList":[]}
@@ -20,6 +20,18 @@
20
20
  * config costs a 304 with no body.
21
21
  */
22
22
  export type ConfigValue = boolean | number | string | null | object;
23
+ /**
24
+ * An A/B arm this install was assigned, as the server resolved it.
25
+ *
26
+ * Carried separately from the values even though the arm has already been folded
27
+ * into them: a control arm and an install outside the experiment are served exactly
28
+ * the same config, so the values cannot say which is which. Without the name here,
29
+ * every analytics event would be missing the one dimension the experiment is for.
30
+ */
31
+ export interface ConfigExperiment {
32
+ key: string;
33
+ variant: string;
34
+ }
23
35
  export interface ConfigStorage {
24
36
  getString(key: string): string | null | undefined;
25
37
  set(key: string, value: string): void;
@@ -35,6 +47,15 @@ export interface ConfigContext {
35
47
  osVersion?: string;
36
48
  language?: string;
37
49
  deviceId?: string;
50
+ /**
51
+ * The account this install is signed into, when it is signed into one.
52
+ *
53
+ * Only an experiment configured to assign on the user reads it, and then only to
54
+ * decide the arm. Sending it keeps one account in one arm across its devices,
55
+ * which is what a metric owned per account — revenue above all — has to be
56
+ * joined on. Leave it unset and every experiment stays device-level.
57
+ */
58
+ userId?: string;
38
59
  /** Free-form user attributes a rule can compare (registration date, plan, counters). */
39
60
  userAttributes?: Record<string, string>;
40
61
  }
@@ -63,6 +84,16 @@ export interface ConfigOptions {
63
84
  * minutes stale. Default true, matching fetchAndActivate.
64
85
  */
65
86
  activateOnFetch?: boolean;
87
+ /**
88
+ * Report the arm this install landed in. Opt-in: default false.
89
+ *
90
+ * Off by default because the two failure modes are not equally bad. An app that
91
+ * already posts its own exposure — Lyra does — would double every count if this
92
+ * defaulted on, and a doubled count is wrong in the way that matters: it looks
93
+ * like data. A missing count looks like a missing count. So the switch defaults to
94
+ * the mistake that announces itself.
95
+ */
96
+ reportExposure?: boolean;
66
97
  /** Network timeout per request. */
67
98
  timeoutMs?: number;
68
99
  onError?: (error: unknown) => void;
@@ -110,12 +141,24 @@ export declare function getAllConfig(): Record<string, ConfigValue>;
110
141
  export declare function getConfigJson<T = unknown>(key: string): T | null;
111
142
  /** Where this key's value came from — the answer to "why is this flag off?". */
112
143
  export declare function getConfigSource(key: string): 'remote' | 'default' | 'missing';
144
+ /**
145
+ * The A/B arms this install is in, for tagging analytics events.
146
+ *
147
+ * Empty when nothing is running — which is also what an install with no device id
148
+ * gets, since an install that draws a fresh arm on every request would take both
149
+ * sides in turn and pollute both.
150
+ */
151
+ export declare function getConfigExperiments(): ConfigExperiment[];
152
+ /** The arm for one experiment, or null when this install is not in it. */
153
+ export declare function getConfigVariant(experimentKey: string): string | null;
113
154
  export declare function getConfigKeys(): string[];
114
155
  export declare function onConfigChange(listener: (values: Record<string, ConfigValue>) => void): () => void;
115
156
  /** For diagnostics: where the current values came from and how old they are. */
116
157
  export declare function getConfigInfo(): {
117
158
  source: 'defaults' | 'cache' | 'remote';
118
159
  fetchedAt: number;
160
+ /** 0 when the server has not answered since this process started. */
161
+ verifiedAt: number;
119
162
  etag: string | null;
120
163
  keyCount: number;
121
164
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,aAAa,CAAC;IACjC,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAqDD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAYzD;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI,CAEzE;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAmEpD;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAKxC;AAED,gEAAgE;AAChE,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE;IAChD,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK;QAAE,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;CAChG,GAAG,MAAM,IAAI,CAiBb;AAmHD,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQnD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAE1D;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAYhE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAI7E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,gBAAgB,EAAE,CAEzD;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAErE;AAED,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAExC;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAGlG;AAED,gFAAgF;AAChF,wBAAgB,aAAa,IAAI;IAC/B,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAEA;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,IAAI,IAAI,CAc1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "OTA updates for React Native - lightweight alternative to expo-updates",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -60,7 +60,12 @@
60
60
  "targets": [
61
61
  "commonjs",
62
62
  "module",
63
- ["typescript", { "project": "tsconfig.build.json" }]
63
+ [
64
+ "typescript",
65
+ {
66
+ "project": "tsconfig.build.json"
67
+ }
68
+ ]
64
69
  ]
65
70
  }
66
- }
71
+ }
package/src/config.ts CHANGED
@@ -22,6 +22,19 @@
22
22
 
23
23
  export type ConfigValue = boolean | number | string | null | object;
24
24
 
25
+ /**
26
+ * An A/B arm this install was assigned, as the server resolved it.
27
+ *
28
+ * Carried separately from the values even though the arm has already been folded
29
+ * into them: a control arm and an install outside the experiment are served exactly
30
+ * the same config, so the values cannot say which is which. Without the name here,
31
+ * every analytics event would be missing the one dimension the experiment is for.
32
+ */
33
+ export interface ConfigExperiment {
34
+ key: string;
35
+ variant: string;
36
+ }
37
+
25
38
  export interface ConfigStorage {
26
39
  getString(key: string): string | null | undefined;
27
40
  set(key: string, value: string): void;
@@ -38,6 +51,15 @@ export interface ConfigContext {
38
51
  osVersion?: string;
39
52
  language?: string;
40
53
  deviceId?: string;
54
+ /**
55
+ * The account this install is signed into, when it is signed into one.
56
+ *
57
+ * Only an experiment configured to assign on the user reads it, and then only to
58
+ * decide the arm. Sending it keeps one account in one arm across its devices,
59
+ * which is what a metric owned per account — revenue above all — has to be
60
+ * joined on. Leave it unset and every experiment stays device-level.
61
+ */
62
+ userId?: string;
41
63
  /** Free-form user attributes a rule can compare (registration date, plan, counters). */
42
64
  userAttributes?: Record<string, string>;
43
65
  }
@@ -67,6 +89,16 @@ export interface ConfigOptions {
67
89
  * minutes stale. Default true, matching fetchAndActivate.
68
90
  */
69
91
  activateOnFetch?: boolean;
92
+ /**
93
+ * Report the arm this install landed in. Opt-in: default false.
94
+ *
95
+ * Off by default because the two failure modes are not equally bad. An app that
96
+ * already posts its own exposure — Lyra does — would double every count if this
97
+ * defaulted on, and a doubled count is wrong in the way that matters: it looks
98
+ * like data. A missing count looks like a missing count. So the switch defaults to
99
+ * the mistake that announces itself.
100
+ */
101
+ reportExposure?: boolean;
70
102
  /** Network timeout per request. */
71
103
  timeoutMs?: number;
72
104
  onError?: (error: unknown) => void;
@@ -74,10 +106,26 @@ export interface ConfigOptions {
74
106
 
75
107
  interface CachedPayload {
76
108
  values: Record<string, ConfigValue>;
109
+ /** Absent in caches written before experiments existed; read as "none". */
110
+ experiments?: ConfigExperiment[];
77
111
  etag: string | null;
78
112
  fetchedAt: number;
79
113
  }
80
114
 
115
+ /**
116
+ * The values and the arms move as one unit, always.
117
+ *
118
+ * They are two halves of a single answer: the arm is what produced those values.
119
+ * Applying one without the other — on activate, on a cache read, on a partial
120
+ * payload — would tag events with an arm whose values are not in force, and the
121
+ * resulting experiment would report a difference between two groups that were both
122
+ * served the same thing.
123
+ */
124
+ interface ConfigSnapshot {
125
+ values: Record<string, ConfigValue>;
126
+ experiments: ConfigExperiment[];
127
+ }
128
+
81
129
  const STORAGE_KEY = 'pulse.config.v1';
82
130
  const DEFAULT_POLL_MS = 5 * 60 * 1000;
83
131
  const DEFAULT_TIMEOUT_MS = 10_000;
@@ -85,10 +133,24 @@ const DEFAULT_TIMEOUT_MS = 10_000;
85
133
  let options: ConfigOptions | null = null;
86
134
  let defaults: Record<string, ConfigValue> = {};
87
135
  let values: Record<string, ConfigValue> = {};
136
+ let experiments: ConfigExperiment[] = [];
88
137
  let etag: string | null = null;
89
138
  let fetchedAt = 0;
139
+ /**
140
+ * When the server last confirmed these values are current — a 200 that applied a
141
+ * payload, or a 304 that said the one we hold is still right.
142
+ *
143
+ * Distinct from fetchedAt, which is when the values were last *changed*. A caller
144
+ * comparing this config against another source needs to know the difference: values
145
+ * restored from disk and never revalidated may be days behind what the server would
146
+ * answer now, and reporting them as "what Pulse says" turns a stale cache into a
147
+ * disagreement that nobody can act on.
148
+ */
149
+ let verifiedAt = 0;
90
150
  let source: 'defaults' | 'cache' | 'remote' = 'defaults';
91
- let pending: Record<string, ConfigValue> | null = null;
151
+ let pending: ConfigSnapshot | null = null;
152
+ /** The arm set already reported this session, so a poll does not re-report it. */
153
+ let reportedExposure: string | null = null;
92
154
  let inFlight: Promise<boolean> | null = null;
93
155
  let pollTimer: ReturnType<typeof setInterval> | null = null;
94
156
  const listeners = new Set<(values: Record<string, ConfigValue>) => void>();
@@ -105,6 +167,7 @@ export function configureConfig(opts: ConfigOptions): void {
105
167
  const cached = readCache(opts.storage);
106
168
  if (cached) {
107
169
  values = cached.values;
170
+ experiments = cached.experiments ?? [];
108
171
  etag = cached.etag;
109
172
  fetchedAt = cached.fetchedAt;
110
173
  source = 'cache';
@@ -147,7 +210,12 @@ export async function fetchConfig(): Promise<boolean> {
147
210
  signal: controller?.signal,
148
211
  });
149
212
 
150
- if (response.status === 304) return false;
213
+ // Nothing changed which is a positive answer about freshness, not a
214
+ // non-event: the values we hold are the ones the server would send.
215
+ if (response.status === 304) {
216
+ verifiedAt = Date.now();
217
+ return false;
218
+ }
151
219
  if (!response.ok) throw new Error(`Pulse config HTTP ${response.status}`);
152
220
 
153
221
  const payload = await response.json();
@@ -160,13 +228,19 @@ export async function fetchConfig(): Promise<boolean> {
160
228
 
161
229
  etag = response.headers?.get?.('etag') ?? null;
162
230
  fetchedAt = Date.now();
231
+ verifiedAt = fetchedAt;
232
+
233
+ const snapshot: ConfigSnapshot = {
234
+ values: nextValues as Record<string, ConfigValue>,
235
+ experiments: parseExperiments(payload?.experiments),
236
+ };
163
237
 
164
238
  if (opts.activateOnFetch === false) {
165
- pending = nextValues as Record<string, ConfigValue>;
239
+ pending = snapshot;
166
240
  return false;
167
241
  }
168
242
 
169
- return applyValues(nextValues as Record<string, ConfigValue>, opts);
243
+ return applySnapshot(snapshot, opts);
170
244
  } catch (error) {
171
245
  // A failed refresh must never clear what we have: the whole point of the cache
172
246
  // is that an unreachable server degrades to the last good config.
@@ -189,7 +263,7 @@ export function activateConfig(): boolean {
189
263
  if (!pending || !options) return false;
190
264
  const next = pending;
191
265
  pending = null;
192
- return applyValues(next, options);
266
+ return applySnapshot(next, options);
193
267
  }
194
268
 
195
269
  /** Whether a fetched-but-not-yet-applied payload is waiting. */
@@ -222,12 +296,18 @@ export function startConfigAutoRefresh(appState?: {
222
296
  };
223
297
  }
224
298
 
225
- function applyValues(next: Record<string, ConfigValue>, opts: ConfigOptions): boolean {
226
- const changed = !shallowEqual(values, next);
299
+ function applySnapshot(next: ConfigSnapshot, opts: ConfigOptions): boolean {
300
+ const changed = !shallowEqual(values, next.values) || !sameExperiments(experiments, next.experiments);
227
301
 
228
- values = next;
302
+ values = next.values;
303
+ experiments = next.experiments;
229
304
  source = 'remote';
230
- writeCache(opts.storage, { values, etag, fetchedAt });
305
+ writeCache(opts.storage, { values, experiments, etag, fetchedAt });
306
+
307
+ // Reported on apply, never on fetch: with activateOnFetch false a payload can sit
308
+ // unapplied for the rest of a session, and an arm the app is not actually serving
309
+ // is not an exposure.
310
+ void reportExposure(opts);
231
311
 
232
312
  // Listeners drive re-renders and gate re-evaluation: firing them for an identical
233
313
  // payload is pure churn, and the 200-with-same-content case is common enough
@@ -236,6 +316,76 @@ function applyValues(next: Record<string, ConfigValue>, opts: ConfigOptions): bo
236
316
  return changed;
237
317
  }
238
318
 
319
+ /**
320
+ * Tell the server which arms this install ended up in.
321
+ *
322
+ * The server already knows how it would assign a given device, so this is not how
323
+ * the experiment is measured — it is how a lopsided split gets noticed. A hash over
324
+ * a real population is only asymptotically fair, installs without a device id sit
325
+ * outside every experiment, and an audience filters unevenly across platforms; all
326
+ * three show up here as arms that are not the size they should be.
327
+ *
328
+ * Sent once per distinct set of arms per session rather than on every poll. The
329
+ * server counts one row per install per session, so a five-minute poll would
330
+ * multiply every arm by the length of the session and make a long user look like a
331
+ * crowd.
332
+ *
333
+ * Failure is swallowed on purpose. This is a diagnostic, and no diagnostic is worth
334
+ * a rejected promise on the path that delivers config to the app.
335
+ */
336
+ async function reportExposure(opts: ConfigOptions): Promise<void> {
337
+ if (opts.reportExposure !== true) return;
338
+ if (experiments.length === 0) return;
339
+
340
+ const signature = experiments.map((e) => `${e.key}=${e.variant}`).join(',');
341
+ if (signature === reportedExposure) return;
342
+ reportedExposure = signature;
343
+
344
+ const context = opts.getContext?.() ?? {};
345
+ const assignments: Record<string, string> = {};
346
+ for (const entry of experiments) assignments[entry.key] = entry.variant;
347
+
348
+ try {
349
+ await fetch(`${opts.url.replace(/\/+$/, '')}/exposure`, {
350
+ method: 'POST',
351
+ headers: { 'Content-Type': 'application/json' },
352
+ body: JSON.stringify({
353
+ platform: context.platform,
354
+ appVersion: context.appVersion,
355
+ assignments,
356
+ }),
357
+ });
358
+ } catch {
359
+ // A device that cannot report its arm still has the right arm.
360
+ reportedExposure = signature;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * The arms as the server sent them, keeping only entries that name both halves.
366
+ *
367
+ * A half-formed assignment is dropped rather than carried: an experiment key with
368
+ * no variant would tag every event with an empty cohort, which reads downstream as
369
+ * a third group that does not exist.
370
+ */
371
+ function parseExperiments(raw: unknown): ConfigExperiment[] {
372
+ if (!Array.isArray(raw)) return [];
373
+ return raw
374
+ .filter((entry): entry is ConfigExperiment =>
375
+ !!entry &&
376
+ typeof entry === 'object' &&
377
+ typeof (entry as ConfigExperiment).key === 'string' &&
378
+ typeof (entry as ConfigExperiment).variant === 'string' &&
379
+ (entry as ConfigExperiment).key !== '' &&
380
+ (entry as ConfigExperiment).variant !== '')
381
+ .map((entry) => ({ key: entry.key, variant: entry.variant }));
382
+ }
383
+
384
+ function sameExperiments(a: ConfigExperiment[], b: ConfigExperiment[]): boolean {
385
+ if (a.length !== b.length) return false;
386
+ return a.every((entry, i) => entry.key === b[i]!.key && entry.variant === b[i]!.variant);
387
+ }
388
+
239
389
  function shallowEqual(a: Record<string, ConfigValue>, b: Record<string, ConfigValue>): boolean {
240
390
  const aKeys = Object.keys(a);
241
391
  const bKeys = Object.keys(b);
@@ -321,6 +471,22 @@ export function getConfigSource(key: string): 'remote' | 'default' | 'missing' {
321
471
  return 'missing';
322
472
  }
323
473
 
474
+ /**
475
+ * The A/B arms this install is in, for tagging analytics events.
476
+ *
477
+ * Empty when nothing is running — which is also what an install with no device id
478
+ * gets, since an install that draws a fresh arm on every request would take both
479
+ * sides in turn and pollute both.
480
+ */
481
+ export function getConfigExperiments(): ConfigExperiment[] {
482
+ return experiments.map((entry) => ({ ...entry }));
483
+ }
484
+
485
+ /** The arm for one experiment, or null when this install is not in it. */
486
+ export function getConfigVariant(experimentKey: string): string | null {
487
+ return experiments.find((entry) => entry.key === experimentKey)?.variant ?? null;
488
+ }
489
+
324
490
  export function getConfigKeys(): string[] {
325
491
  return Object.keys(getAllConfig()).sort();
326
492
  }
@@ -334,22 +500,27 @@ export function onConfigChange(listener: (values: Record<string, ConfigValue>) =
334
500
  export function getConfigInfo(): {
335
501
  source: 'defaults' | 'cache' | 'remote';
336
502
  fetchedAt: number;
503
+ /** 0 when the server has not answered since this process started. */
504
+ verifiedAt: number;
337
505
  etag: string | null;
338
506
  keyCount: number;
339
507
  } {
340
- return { source, fetchedAt, etag, keyCount: Object.keys(getAllConfig()).length };
508
+ return { source, fetchedAt, verifiedAt, etag, keyCount: Object.keys(getAllConfig()).length };
341
509
  }
342
510
 
343
511
  /** Test seam: drops every piece of module state. */
344
512
  export function resetConfigForTests(): void {
345
513
  options = null;
346
514
  defaults = {};
515
+ verifiedAt = 0;
347
516
  values = {};
517
+ experiments = [];
348
518
  etag = null;
349
519
  fetchedAt = 0;
350
520
  source = 'defaults';
351
521
  inFlight = null;
352
522
  pending = null;
523
+ reportedExposure = null;
353
524
  listeners.clear();
354
525
  stopPolling();
355
526
  }
@@ -365,6 +536,7 @@ function buildHeaders(opts: ConfigOptions, currentEtag: string | null): Record<s
365
536
  if (ctx.osVersion) headers['X-Pulse-Os-Version'] = ctx.osVersion;
366
537
  if (ctx.language) headers['X-Pulse-Language'] = ctx.language;
367
538
  if (ctx.deviceId) headers['Pulse-Device-Id'] = ctx.deviceId;
539
+ if (ctx.userId) headers['Pulse-User-Id'] = ctx.userId;
368
540
  if (ctx.userAttributes && Object.keys(ctx.userAttributes).length > 0) {
369
541
  headers['X-Pulse-User-Attributes'] = JSON.stringify(ctx.userAttributes);
370
542
  }