react-native-nitro-amplitude 0.2.0 → 0.5.0

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.
Files changed (155) hide show
  1. package/README.md +387 -8
  2. package/cpp/bindings/HybridAmplitudeWorker.cpp +5 -1
  3. package/lib/commonjs/analytics/config.js +31 -10
  4. package/lib/commonjs/analytics/config.js.map +1 -1
  5. package/lib/commonjs/analytics/index.js +8 -2
  6. package/lib/commonjs/analytics/index.js.map +1 -1
  7. package/lib/commonjs/analytics/network-guarded-fetch-transport.js +16 -0
  8. package/lib/commonjs/analytics/network-guarded-fetch-transport.js.map +1 -0
  9. package/lib/commonjs/analytics/nitro-transport.js +2 -0
  10. package/lib/commonjs/analytics/nitro-transport.js.map +1 -1
  11. package/lib/commonjs/analytics/plugins/context.js +7 -1
  12. package/lib/commonjs/analytics/plugins/context.js.map +1 -1
  13. package/lib/commonjs/analytics/react-native-client.js +142 -1
  14. package/lib/commonjs/analytics/react-native-client.js.map +1 -1
  15. package/lib/commonjs/diagnostics.js +92 -0
  16. package/lib/commonjs/diagnostics.js.map +1 -0
  17. package/lib/commonjs/errors.js +48 -0
  18. package/lib/commonjs/errors.js.map +1 -0
  19. package/lib/commonjs/experiment/experimentClient.js +84 -2
  20. package/lib/commonjs/experiment/experimentClient.js.map +1 -1
  21. package/lib/commonjs/experiment/index.js +12 -0
  22. package/lib/commonjs/experiment/index.js.map +1 -1
  23. package/lib/commonjs/experiment/stubClient.js +25 -0
  24. package/lib/commonjs/experiment/stubClient.js.map +1 -1
  25. package/lib/commonjs/experiment/typed-variants.js +52 -0
  26. package/lib/commonjs/experiment/typed-variants.js.map +1 -0
  27. package/lib/commonjs/experiment/types/config.js +32 -4
  28. package/lib/commonjs/experiment/types/config.js.map +1 -1
  29. package/lib/commonjs/index.js +105 -3
  30. package/lib/commonjs/index.js.map +1 -1
  31. package/lib/commonjs/index.web.js +110 -12
  32. package/lib/commonjs/index.web.js.map +1 -1
  33. package/lib/commonjs/native/http.js +23 -8
  34. package/lib/commonjs/native/http.js.map +1 -1
  35. package/lib/commonjs/native/http.web.js +2 -0
  36. package/lib/commonjs/native/http.web.js.map +1 -1
  37. package/lib/commonjs/native/hybrid.web.js +23 -0
  38. package/lib/commonjs/native/hybrid.web.js.map +1 -0
  39. package/lib/commonjs/native/storage.js +27 -15
  40. package/lib/commonjs/native/storage.js.map +1 -1
  41. package/lib/commonjs/network.js +154 -0
  42. package/lib/commonjs/network.js.map +1 -0
  43. package/lib/commonjs/presets.js +118 -0
  44. package/lib/commonjs/presets.js.map +1 -0
  45. package/lib/commonjs/testing.js +166 -0
  46. package/lib/commonjs/testing.js.map +1 -0
  47. package/lib/module/analytics/config.js +32 -11
  48. package/lib/module/analytics/config.js.map +1 -1
  49. package/lib/module/analytics/index.js +4 -1
  50. package/lib/module/analytics/index.js.map +1 -1
  51. package/lib/module/analytics/network-guarded-fetch-transport.js +11 -0
  52. package/lib/module/analytics/network-guarded-fetch-transport.js.map +1 -0
  53. package/lib/module/analytics/nitro-transport.js +2 -0
  54. package/lib/module/analytics/nitro-transport.js.map +1 -1
  55. package/lib/module/analytics/plugins/context.js +7 -1
  56. package/lib/module/analytics/plugins/context.js.map +1 -1
  57. package/lib/module/analytics/react-native-client.js +141 -1
  58. package/lib/module/analytics/react-native-client.js.map +1 -1
  59. package/lib/module/diagnostics.js +85 -0
  60. package/lib/module/diagnostics.js.map +1 -0
  61. package/lib/module/errors.js +41 -0
  62. package/lib/module/errors.js.map +1 -0
  63. package/lib/module/experiment/experimentClient.js +84 -2
  64. package/lib/module/experiment/experimentClient.js.map +1 -1
  65. package/lib/module/experiment/index.js +1 -0
  66. package/lib/module/experiment/index.js.map +1 -1
  67. package/lib/module/experiment/stubClient.js +25 -0
  68. package/lib/module/experiment/stubClient.js.map +1 -1
  69. package/lib/module/experiment/typed-variants.js +43 -0
  70. package/lib/module/experiment/typed-variants.js.map +1 -0
  71. package/lib/module/experiment/types/config.js +31 -4
  72. package/lib/module/experiment/types/config.js.map +1 -1
  73. package/lib/module/index.js +15 -3
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/index.web.js +19 -6
  76. package/lib/module/index.web.js.map +1 -1
  77. package/lib/module/native/http.js +23 -8
  78. package/lib/module/native/http.js.map +1 -1
  79. package/lib/module/native/http.web.js +2 -0
  80. package/lib/module/native/http.web.js.map +1 -1
  81. package/lib/module/native/hybrid.web.js +16 -0
  82. package/lib/module/native/hybrid.web.js.map +1 -0
  83. package/lib/module/native/storage.js +27 -15
  84. package/lib/module/native/storage.js.map +1 -1
  85. package/lib/module/network.js +138 -0
  86. package/lib/module/network.js.map +1 -0
  87. package/lib/module/presets.js +110 -0
  88. package/lib/module/presets.js.map +1 -0
  89. package/lib/module/testing.js +160 -0
  90. package/lib/module/testing.js.map +1 -0
  91. package/lib/typescript/analytics/config.d.ts +19 -6
  92. package/lib/typescript/analytics/config.d.ts.map +1 -1
  93. package/lib/typescript/analytics/index.d.ts +1 -1
  94. package/lib/typescript/analytics/index.d.ts.map +1 -1
  95. package/lib/typescript/analytics/network-guarded-fetch-transport.d.ts +6 -0
  96. package/lib/typescript/analytics/network-guarded-fetch-transport.d.ts.map +1 -0
  97. package/lib/typescript/analytics/nitro-transport.d.ts.map +1 -1
  98. package/lib/typescript/analytics/plugins/context.d.ts +2 -0
  99. package/lib/typescript/analytics/plugins/context.d.ts.map +1 -1
  100. package/lib/typescript/analytics/react-native-client.d.ts +43 -0
  101. package/lib/typescript/analytics/react-native-client.d.ts.map +1 -1
  102. package/lib/typescript/diagnostics.d.ts +21 -0
  103. package/lib/typescript/diagnostics.d.ts.map +1 -0
  104. package/lib/typescript/errors.d.ts +9 -0
  105. package/lib/typescript/errors.d.ts.map +1 -0
  106. package/lib/typescript/experiment/experimentClient.d.ts +9 -1
  107. package/lib/typescript/experiment/experimentClient.d.ts.map +1 -1
  108. package/lib/typescript/experiment/index.d.ts +1 -0
  109. package/lib/typescript/experiment/index.d.ts.map +1 -1
  110. package/lib/typescript/experiment/stubClient.d.ts +6 -1
  111. package/lib/typescript/experiment/stubClient.d.ts.map +1 -1
  112. package/lib/typescript/experiment/typed-variants.d.ts +9 -0
  113. package/lib/typescript/experiment/typed-variants.d.ts.map +1 -0
  114. package/lib/typescript/experiment/types/client.d.ts +21 -0
  115. package/lib/typescript/experiment/types/client.d.ts.map +1 -1
  116. package/lib/typescript/experiment/types/config.d.ts.map +1 -1
  117. package/lib/typescript/index.d.ts +12 -3
  118. package/lib/typescript/index.d.ts.map +1 -1
  119. package/lib/typescript/index.web.d.ts +16 -6
  120. package/lib/typescript/index.web.d.ts.map +1 -1
  121. package/lib/typescript/native/http.d.ts.map +1 -1
  122. package/lib/typescript/native/http.web.d.ts.map +1 -1
  123. package/lib/typescript/native/hybrid.web.d.ts +8 -0
  124. package/lib/typescript/native/hybrid.web.d.ts.map +1 -0
  125. package/lib/typescript/native/storage.d.ts.map +1 -1
  126. package/lib/typescript/network.d.ts +50 -0
  127. package/lib/typescript/network.d.ts.map +1 -0
  128. package/lib/typescript/presets.d.ts +50 -0
  129. package/lib/typescript/presets.d.ts.map +1 -0
  130. package/lib/typescript/testing.d.ts +11 -0
  131. package/lib/typescript/testing.d.ts.map +1 -0
  132. package/package.json +1 -1
  133. package/src/analytics/config.ts +33 -8
  134. package/src/analytics/index.ts +3 -0
  135. package/src/analytics/network-guarded-fetch-transport.ts +10 -0
  136. package/src/analytics/nitro-transport.ts +2 -0
  137. package/src/analytics/plugins/context.ts +10 -1
  138. package/src/analytics/react-native-client.ts +217 -0
  139. package/src/diagnostics.ts +119 -0
  140. package/src/errors.ts +60 -0
  141. package/src/experiment/experimentClient.ts +116 -3
  142. package/src/experiment/index.ts +1 -0
  143. package/src/experiment/stubClient.ts +42 -1
  144. package/src/experiment/typed-variants.ts +68 -0
  145. package/src/experiment/types/client.ts +29 -0
  146. package/src/experiment/types/config.ts +29 -5
  147. package/src/index.ts +28 -2
  148. package/src/index.web.ts +33 -5
  149. package/src/native/http.ts +38 -8
  150. package/src/native/http.web.ts +2 -0
  151. package/src/native/hybrid.web.ts +21 -0
  152. package/src/native/storage.ts +27 -25
  153. package/src/network.ts +258 -0
  154. package/src/presets.ts +208 -0
  155. package/src/testing.ts +177 -0
@@ -30,7 +30,12 @@ import {
30
30
  } from "./storage/cache";
31
31
  import { MemoryStorage } from "./storage/local-storage";
32
32
  import { FetchHttpClient, WrapperClient } from "./transport/http";
33
- import { Client, FetchOptions } from "./types/client";
33
+ import {
34
+ Client,
35
+ ExperimentFetchResult,
36
+ ExperimentVariantResult,
37
+ FetchOptions,
38
+ } from "./types/client";
34
39
  import { ExperimentConfig, Defaults } from "./types/config";
35
40
  import { Exposure } from "./types/exposure";
36
41
  import { LogLevel } from "./types/logger";
@@ -119,6 +124,9 @@ export class ExperimentClient implements Client {
119
124
  private storedFetchSequenceNumber = 0;
120
125
  private readonly fetchVariantsOptions: SingleValueStoreCache<GetVariantsOptions>;
121
126
  private readonly stopCallbacks = new Set<() => void>();
127
+ private readonly inFlightFetches = new Map<string, Promise<Variants>>();
128
+ private lastFetchTime: number | undefined;
129
+ private lastFetchFailure: string | undefined;
122
130
 
123
131
  /**
124
132
  * Creates a new ExperimentClient instance.
@@ -341,6 +349,37 @@ export class ExperimentClient implements Client {
341
349
  return this;
342
350
  }
343
351
 
352
+ public async fetchWithMetadata(
353
+ user: ExperimentUser = this.user,
354
+ options?: FetchOptions,
355
+ ): Promise<ExperimentFetchResult> {
356
+ const startedAt = Date.now();
357
+ const fetchUser = user ?? this.user;
358
+ this.setUser(fetchUser);
359
+ try {
360
+ const variants = await this.fetchWithRetries(fetchUser, options);
361
+ const flagKeys = Object.keys(variants);
362
+ return {
363
+ fetched: true,
364
+ flagKeys,
365
+ cacheHit: false,
366
+ durationMillis: Date.now() - startedAt,
367
+ source: "network",
368
+ };
369
+ } catch (error) {
370
+ const reason = error instanceof Error ? error.message : String(error);
371
+ this.lastFetchFailure = reason;
372
+ return {
373
+ fetched: false,
374
+ flagKeys: options?.flagKeys ?? Object.keys(this.variants.getAll()),
375
+ cacheHit: Object.keys(this.variants.getAll()).length > 0,
376
+ durationMillis: Date.now() - startedAt,
377
+ source: "cache",
378
+ failureReason: reason,
379
+ };
380
+ }
381
+ }
382
+
344
383
  public async fetchOrThrow(
345
384
  user: ExperimentUser = this.user,
346
385
  options?: FetchOptions,
@@ -380,6 +419,45 @@ export class ExperimentClient implements Client {
380
419
  return sourceVariant.variant || {};
381
420
  }
382
421
 
422
+ public variantWithMetadata(
423
+ key: string,
424
+ fallback?: string | Variant,
425
+ ): ExperimentVariantResult {
426
+ if (!this.apiKey) {
427
+ return {
428
+ variant: { value: undefined },
429
+ fallback: true,
430
+ stale: false,
431
+ reason: "missing_flag",
432
+ };
433
+ }
434
+ const sourceVariant = this.variantAndSource(key, fallback);
435
+ if (this.config.automaticExposureTracking) {
436
+ this.exposureInternal(key, sourceVariant);
437
+ }
438
+ this.logger.debug(
439
+ `[Experiment] variant for ${key} is ${sourceVariant.variant?.value}`,
440
+ );
441
+ const variant = sourceVariant.variant || {};
442
+ const fallbackVariant = isFallback(sourceVariant.source);
443
+ const missingVariant = variant.value === undefined;
444
+ return {
445
+ variant,
446
+ source: sourceVariant.source,
447
+ fallback: fallbackVariant,
448
+ stale: false,
449
+ reason: missingVariant
450
+ ? this.lastFetchFailure
451
+ ? "fetch_failure"
452
+ : fallbackVariant
453
+ ? "fallback"
454
+ : "no_assignment"
455
+ : fallbackVariant
456
+ ? "fallback"
457
+ : undefined,
458
+ };
459
+ }
460
+
383
461
  /**
384
462
  * Track an exposure event for the variant associated with the flag/experiment
385
463
  * {@link key}.
@@ -431,6 +509,18 @@ export class ExperimentClient implements Client {
431
509
  void this.variants.store().catch((e) => this.logger.warn(e));
432
510
  }
433
511
 
512
+ public clearVariants(): void {
513
+ this.clear();
514
+ }
515
+
516
+ public hasCachedVariant(key: string): boolean {
517
+ return this.variants.get(key) !== undefined;
518
+ }
519
+
520
+ public getLastFetchTime(): number | undefined {
521
+ return this.lastFetchTime;
522
+ }
523
+
434
524
  /**
435
525
  * Get a copy of the internal {@link ExperimentUser} object if it is set.
436
526
  *
@@ -788,12 +878,35 @@ export class ExperimentClient implements Client {
788
878
  user: ExperimentUser,
789
879
  options?: FetchOptions,
790
880
  ): Promise<Variants> {
791
- return await this.fetchInternal(
881
+ const key = JSON.stringify({
882
+ user,
883
+ flagKeys: options?.flagKeys ?? null,
884
+ });
885
+ const inFlightFetch = this.inFlightFetches.get(key);
886
+ if (inFlightFetch) {
887
+ return await inFlightFetch;
888
+ }
889
+ const fetch = this.fetchInternal(
792
890
  user,
793
891
  this.config.fetchTimeoutMillis,
794
892
  this.config.retryFetchOnFailure,
795
893
  options,
796
- );
894
+ )
895
+ .then((variants) => {
896
+ this.lastFetchTime = Date.now();
897
+ this.lastFetchFailure = undefined;
898
+ return variants;
899
+ })
900
+ .catch((error: unknown) => {
901
+ this.lastFetchFailure =
902
+ error instanceof Error ? error.message : String(error);
903
+ throw error;
904
+ })
905
+ .finally(() => {
906
+ this.inFlightFetches.delete(key);
907
+ });
908
+ this.inFlightFetches.set(key, fetch);
909
+ return await fetch;
797
910
  }
798
911
 
799
912
  private async doFetch(
@@ -12,3 +12,4 @@ export { LogLevel } from "./types/logger";
12
12
  export type { Logger } from "./types/logger";
13
13
  export { ConsoleLogger } from "./logger/consoleLogger";
14
14
  export { LocalStorage, MemoryStorage } from "./storage/local-storage";
15
+ export * from "./typed-variants";
@@ -1,4 +1,9 @@
1
- import { Client, FetchOptions } from "./types/client";
1
+ import {
2
+ Client,
3
+ ExperimentFetchResult,
4
+ ExperimentVariantResult,
5
+ FetchOptions,
6
+ } from "./types/client";
2
7
  import { Defaults } from "./types/config";
3
8
  import { ExperimentUser, ExperimentUserProvider } from "./types/user";
4
9
  import { Variant, Variants } from "./types/variant";
@@ -33,6 +38,20 @@ export class StubExperimentClient implements Client {
33
38
  return this;
34
39
  }
35
40
 
41
+ public async fetchWithMetadata(
42
+ _user?: ExperimentUser,
43
+ _options?: FetchOptions,
44
+ ): Promise<ExperimentFetchResult> {
45
+ return {
46
+ fetched: false,
47
+ flagKeys: [],
48
+ cacheHit: false,
49
+ durationMillis: 0,
50
+ source: "cache",
51
+ failureReason: "stub_client",
52
+ };
53
+ }
54
+
36
55
  public getUserProvider(): ExperimentUserProvider {
37
56
  return {
38
57
  async getUser(): Promise<ExperimentUser> {
@@ -51,11 +70,33 @@ export class StubExperimentClient implements Client {
51
70
  return Defaults.fallbackVariant ?? {};
52
71
  }
53
72
 
73
+ public variantWithMetadata(
74
+ _key: string,
75
+ _fallback?: string | Variant,
76
+ ): ExperimentVariantResult {
77
+ return {
78
+ variant: Defaults.fallbackVariant ?? {},
79
+ fallback: true,
80
+ stale: false,
81
+ reason: "fallback",
82
+ };
83
+ }
84
+
54
85
  public all(): Variants {
55
86
  return {};
56
87
  }
57
88
 
58
89
  public clear(): void {}
59
90
 
91
+ public clearVariants(): void {}
92
+
93
+ public hasCachedVariant(_key: string): boolean {
94
+ return false;
95
+ }
96
+
97
+ public getLastFetchTime(): number | undefined {
98
+ return undefined;
99
+ }
100
+
60
101
  public exposure(_key: string): void {}
61
102
  }
@@ -0,0 +1,68 @@
1
+ import type { Client } from "./types/client";
2
+ import type { Variant } from "./types/variant";
3
+
4
+ export function variantString(
5
+ client: Pick<Client, "variant">,
6
+ key: string,
7
+ fallback: string,
8
+ ): string {
9
+ const value = client.variant(key, fallback).value;
10
+ return value ?? fallback;
11
+ }
12
+
13
+ export function variantBoolean(
14
+ client: Pick<Client, "variant">,
15
+ key: string,
16
+ fallback: boolean,
17
+ ): boolean {
18
+ const value = client.variant(key, String(fallback)).value;
19
+ if (value === "true") {
20
+ return true;
21
+ }
22
+ if (value === "false") {
23
+ return false;
24
+ }
25
+ return fallback;
26
+ }
27
+
28
+ export function variantNumber(
29
+ client: Pick<Client, "variant">,
30
+ key: string,
31
+ fallback: number,
32
+ ): number {
33
+ const value = client.variant(key, String(fallback)).value;
34
+ if (value === undefined) {
35
+ return fallback;
36
+ }
37
+ const parsed = Number(value);
38
+ return Number.isFinite(parsed) ? parsed : fallback;
39
+ }
40
+
41
+ export function variantPayload<T>(
42
+ client: Pick<Client, "variant">,
43
+ key: string,
44
+ fallback: T,
45
+ ): T {
46
+ const payload = client.variant(key).payload;
47
+ return payload === undefined ? fallback : (payload as T);
48
+ }
49
+
50
+ export function variantJson<T>(
51
+ client: Pick<Client, "variant">,
52
+ key: string,
53
+ fallback: T,
54
+ ): T {
55
+ const variant = client.variant(key);
56
+ return parseVariantJson(variant, fallback);
57
+ }
58
+
59
+ export function parseVariantJson<T>(variant: Variant, fallback: T): T {
60
+ if (typeof variant.value !== "string") {
61
+ return fallback;
62
+ }
63
+ try {
64
+ return JSON.parse(variant.value) as T;
65
+ } catch {
66
+ return fallback;
67
+ }
68
+ }
@@ -1,5 +1,6 @@
1
1
  import { ExperimentUser, ExperimentUserProvider } from "./user";
2
2
  import { Variant, Variants } from "./variant";
3
+ import { VariantSource } from "./source";
3
4
 
4
5
  /**
5
6
  * Interface for the main client.
@@ -9,10 +10,21 @@ export interface Client {
9
10
  start(user?: ExperimentUser): Promise<void>;
10
11
  stop(): void;
11
12
  fetch(user?: ExperimentUser, options?: FetchOptions): Promise<Client>;
13
+ fetchWithMetadata(
14
+ user?: ExperimentUser,
15
+ options?: FetchOptions,
16
+ ): Promise<ExperimentFetchResult>;
12
17
  fetchOrThrow(user?: ExperimentUser, options?: FetchOptions): Promise<Client>;
13
18
  variant(key: string, fallback?: string | Variant): Variant;
19
+ variantWithMetadata(
20
+ key: string,
21
+ fallback?: string | Variant,
22
+ ): ExperimentVariantResult;
14
23
  all(): Variants;
15
24
  clear(): void;
25
+ clearVariants(): void;
26
+ hasCachedVariant(key: string): boolean;
27
+ getLastFetchTime(): number | undefined;
16
28
  exposure(key: string): void;
17
29
  getUser(): ExperimentUser;
18
30
  setUser(user: ExperimentUser): void;
@@ -36,3 +48,20 @@ export type FetchOptions = {
36
48
  */
37
49
  flagKeys?: string[];
38
50
  };
51
+
52
+ export type ExperimentFetchResult = {
53
+ fetched: boolean;
54
+ flagKeys: string[];
55
+ cacheHit: boolean;
56
+ durationMillis: number;
57
+ source: "network" | "cache";
58
+ failureReason?: string;
59
+ };
60
+
61
+ export type ExperimentVariantResult = {
62
+ variant: Variant;
63
+ source?: VariantSource;
64
+ fallback: boolean;
65
+ stale: boolean;
66
+ reason?: "missing_flag" | "fetch_failure" | "no_assignment" | "fallback";
67
+ };
@@ -1,6 +1,4 @@
1
- import { nitroHttpClient } from "../../native/http";
2
- import { NitroMemoryStorage } from "../../native/storage";
3
-
1
+ import { isNative } from "../util/platform";
4
2
  import { ExposureTrackingProvider } from "./exposure";
5
3
  import { Logger, LogLevel } from "./logger";
6
4
  import { Source } from "./source";
@@ -9,6 +7,28 @@ import { HttpClient } from "./transport";
9
7
  import { ExperimentUserProvider } from "./user";
10
8
  import { Variant, Variants } from "./variant";
11
9
 
10
+ function createDefaultHttpClient(): HttpClient {
11
+ if (isNative()) {
12
+ const { nitroHttpClient } =
13
+ require("../../native/http") as typeof import("../../native/http");
14
+ return nitroHttpClient;
15
+ }
16
+ const { nitroHttpClient } =
17
+ require("../../native/http.web") as typeof import("../../native/http");
18
+ return nitroHttpClient;
19
+ }
20
+
21
+ function createDefaultStorage(): Storage {
22
+ if (isNative()) {
23
+ const { NitroMemoryStorage } =
24
+ require("../../native/storage") as typeof import("../../native/storage");
25
+ return new NitroMemoryStorage("experiment");
26
+ }
27
+ const { NitroMemoryStorage } =
28
+ require("../../native/storage.web") as typeof import("../../native/storage");
29
+ return new NitroMemoryStorage("experiment");
30
+ }
31
+
12
32
  /**
13
33
  * @category Configuration
14
34
  */
@@ -205,6 +225,10 @@ export const Defaults: ExperimentConfig = {
205
225
  automaticFetchOnAmplitudeIdentityChange: false,
206
226
  userProvider: null,
207
227
  exposureTrackingProvider: null,
208
- httpClient: nitroHttpClient,
209
- storage: new NitroMemoryStorage("experiment"),
228
+ get httpClient() {
229
+ return createDefaultHttpClient();
230
+ },
231
+ get storage() {
232
+ return createDefaultStorage();
233
+ },
210
234
  };
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import analyticsClient, {
2
2
  createInstance,
3
3
  } from "./analytics/react-native-client";
4
+ import type { AmplitudeDiagnostics } from "./diagnostics";
5
+ import { getAmplitudeDiagnostics } from "./diagnostics";
4
6
  import { prefetchNativeContext } from "./native/context";
5
7
 
6
8
  import * as AnalyticsTypes from "./analytics/types";
@@ -27,19 +29,42 @@ export const {
27
29
  shutdown,
28
30
  track,
29
31
  extendSession,
32
+ flushWithResult,
33
+ healthCheck,
30
34
  } = analyticsClient;
31
35
 
36
+ export function getDiagnostics(): AmplitudeDiagnostics {
37
+ return getAmplitudeDiagnostics(analyticsClient);
38
+ }
39
+
32
40
  export { Revenue, Identify } from "@amplitude/analytics-core";
33
41
  export {
34
42
  InMemoryStorage,
35
43
  LocalStorage,
36
44
  MemoryStorage,
37
45
  } from "./analytics/storage/local-storage";
38
- export { NitroAnalyticsStorage, NitroMemoryStorage } from "./native/storage";
46
+ export {
47
+ NitroAnalyticsStorage,
48
+ NitroExperimentStorage,
49
+ NitroMemoryStorage,
50
+ } from "./native/storage";
39
51
  export { nitroHttpClient } from "./native/http";
40
52
  export { nitroTransport } from "./analytics/nitro-transport";
41
53
  export { prefetchNativeContext };
42
54
  export { AnalyticsTypes as Types };
55
+ export {
56
+ getAmplitudeDiagnostics,
57
+ getLastNativeError,
58
+ getNativeStartupDiagnostics,
59
+ } from "./diagnostics";
60
+ export type {
61
+ AmplitudeDiagnostics,
62
+ NativeStartupDiagnostics,
63
+ } from "./diagnostics";
64
+ export * from "./errors";
65
+ export * from "./network";
66
+ export * from "./presets";
67
+ export * from "./testing";
43
68
 
44
69
  export * from "./experiment/types/config";
45
70
  export { Experiment } from "./experiment/factory";
@@ -58,9 +83,10 @@ export {
58
83
  LocalStorage as ExperimentLocalStorage,
59
84
  MemoryStorage as ExperimentMemoryStorage,
60
85
  } from "./experiment/storage/local-storage";
86
+ export * from "./experiment/typed-variants";
61
87
 
62
88
  export type { AmplitudeContext } from "./AmplitudeContext.nitro";
63
89
  export type { AmplitudeStorage } from "./AmplitudeStorage.nitro";
64
90
  export type { AmplitudeWorker } from "./AmplitudeWorker.nitro";
65
91
 
66
- export const VERSION = "0.2.0";
92
+ export const VERSION = "0.5.0";
package/src/index.web.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import analyticsClient, {
2
2
  createInstance,
3
3
  } from "./analytics/react-native-client";
4
- import { prefetchNativeContext } from "./native/context";
4
+ import type { Transport } from "@amplitude/analytics-core";
5
+ import { NetworkGuardedFetchTransport } from "./analytics/network-guarded-fetch-transport";
6
+ import type { AmplitudeDiagnostics } from "./diagnostics";
7
+ import { getAmplitudeDiagnostics } from "./diagnostics";
8
+ import { prefetchNativeContext } from "./native/context.web";
5
9
 
6
10
  import * as AnalyticsTypes from "./analytics/types";
7
11
 
@@ -27,19 +31,42 @@ export const {
27
31
  shutdown,
28
32
  track,
29
33
  extendSession,
34
+ flushWithResult,
35
+ healthCheck,
30
36
  } = analyticsClient;
31
37
 
38
+ export function getDiagnostics(): AmplitudeDiagnostics {
39
+ return getAmplitudeDiagnostics(analyticsClient);
40
+ }
41
+
32
42
  export { Revenue, Identify } from "@amplitude/analytics-core";
33
43
  export {
34
44
  InMemoryStorage,
35
45
  LocalStorage,
36
46
  MemoryStorage,
37
47
  } from "./analytics/storage/local-storage";
38
- export { NitroAnalyticsStorage, NitroMemoryStorage } from "./native/storage";
39
- export { nitroHttpClient } from "./native/http";
40
- export { nitroTransport } from "./analytics/nitro-transport";
48
+ export {
49
+ NitroAnalyticsStorage,
50
+ NitroExperimentStorage,
51
+ NitroMemoryStorage,
52
+ } from "./native/storage.web";
53
+ export { nitroHttpClient } from "./native/http.web";
54
+ export const nitroTransport: Transport = new NetworkGuardedFetchTransport();
41
55
  export { prefetchNativeContext };
42
56
  export { AnalyticsTypes as Types };
57
+ export {
58
+ getAmplitudeDiagnostics,
59
+ getLastNativeError,
60
+ getNativeStartupDiagnostics,
61
+ } from "./diagnostics";
62
+ export type {
63
+ AmplitudeDiagnostics,
64
+ NativeStartupDiagnostics,
65
+ } from "./diagnostics";
66
+ export * from "./errors";
67
+ export * from "./network";
68
+ export * from "./presets";
69
+ export * from "./testing";
43
70
 
44
71
  export * from "./experiment/types/config";
45
72
  export { Experiment } from "./experiment/factory";
@@ -58,9 +85,10 @@ export {
58
85
  LocalStorage as ExperimentLocalStorage,
59
86
  MemoryStorage as ExperimentMemoryStorage,
60
87
  } from "./experiment/storage/local-storage";
88
+ export * from "./experiment/typed-variants";
61
89
 
62
90
  export type { AmplitudeContext } from "./AmplitudeContext.nitro";
63
91
  export type { AmplitudeStorage } from "./AmplitudeStorage.nitro";
64
92
  export type { AmplitudeWorker } from "./AmplitudeWorker.nitro";
65
93
 
66
- export const VERSION = "0.2.0";
94
+ export const VERSION = "0.5.0";
@@ -1,5 +1,8 @@
1
1
  import type { HttpClient, SimpleResponse } from "../experiment/types/transport";
2
+ import { createAmplitudeError, getAmplitudeErrorCode } from "../errors";
3
+ import { assertNetworkEnabled } from "../network";
2
4
  import { getAmplitudeWorker } from "./hybrid";
5
+ import type { AmplitudeWorker } from "../AmplitudeWorker.nitro";
3
6
 
4
7
  type PendingRequest = {
5
8
  resolve: (response: SimpleResponse) => void;
@@ -9,13 +12,18 @@ type PendingRequest = {
9
12
 
10
13
  const pendingRequests = new Map<string, PendingRequest>();
11
14
  let listenerInstalled = false;
15
+ let listenerWorker: AmplitudeWorker | undefined;
16
+ const DEFAULT_TIMEOUT_MILLIS = 10000;
17
+ const MAX_TIMEOUT_MILLIS = 300000;
12
18
 
13
19
  function ensureListener(): void {
14
- if (listenerInstalled) {
20
+ const worker = getAmplitudeWorker();
21
+ if (listenerInstalled && listenerWorker === worker) {
15
22
  return;
16
23
  }
17
24
  listenerInstalled = true;
18
- getAmplitudeWorker().addOnComplete((requestId, statusCode, body, error) => {
25
+ listenerWorker = worker;
26
+ worker.addOnComplete((requestId, statusCode, body, error) => {
19
27
  const pending = pendingRequests.get(requestId);
20
28
  if (!pending) {
21
29
  return;
@@ -23,7 +31,9 @@ function ensureListener(): void {
23
31
  pendingRequests.delete(requestId);
24
32
  clearTimeout(pending.timeoutId);
25
33
  if (error) {
26
- pending.reject(new Error(error));
34
+ pending.reject(
35
+ createAmplitudeError(getAmplitudeErrorCode(new Error(error)), error),
36
+ );
27
37
  return;
28
38
  }
29
39
  pending.resolve({
@@ -37,22 +47,36 @@ function createRequestId(): string {
37
47
  return `req_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
38
48
  }
39
49
 
50
+ function normalizeTimeoutMillis(timeoutMillis: number): number {
51
+ if (!Number.isFinite(timeoutMillis) || timeoutMillis <= 0) {
52
+ return DEFAULT_TIMEOUT_MILLIS;
53
+ }
54
+ return Math.min(Math.ceil(timeoutMillis), MAX_TIMEOUT_MILLIS);
55
+ }
56
+
40
57
  export class NitroHttpClient implements HttpClient {
41
58
  async request(
42
59
  requestUrl: string,
43
60
  method: string,
44
61
  headers: Record<string, string>,
45
62
  data: string | null,
46
- timeoutMillis = 10000,
63
+ timeoutMillis = DEFAULT_TIMEOUT_MILLIS,
47
64
  ): Promise<SimpleResponse> {
65
+ assertNetworkEnabled();
48
66
  ensureListener();
49
67
  const requestId = createRequestId();
68
+ const normalizedTimeoutMillis = normalizeTimeoutMillis(timeoutMillis);
50
69
  return new Promise<SimpleResponse>((resolve, reject) => {
51
70
  const timeoutId = setTimeout(() => {
52
71
  pendingRequests.delete(requestId);
53
72
  getAmplitudeWorker().cancel(requestId);
54
- reject(new Error(`Request timed out after ${timeoutMillis}ms`));
55
- }, timeoutMillis + 250);
73
+ reject(
74
+ createAmplitudeError(
75
+ "timeout",
76
+ `Request timed out after ${normalizedTimeoutMillis}ms`,
77
+ ),
78
+ );
79
+ }, normalizedTimeoutMillis + 250);
56
80
 
57
81
  pendingRequests.set(requestId, { resolve, reject, timeoutId });
58
82
 
@@ -63,12 +87,18 @@ export class NitroHttpClient implements HttpClient {
63
87
  method,
64
88
  JSON.stringify(headers),
65
89
  data ?? "",
66
- timeoutMillis,
90
+ normalizedTimeoutMillis,
67
91
  );
68
92
  } catch (error) {
69
93
  pendingRequests.delete(requestId);
70
94
  clearTimeout(timeoutId);
71
- reject(error instanceof Error ? error : new Error(String(error)));
95
+ reject(
96
+ createAmplitudeError(
97
+ getAmplitudeErrorCode(error),
98
+ error instanceof Error ? error.message : String(error),
99
+ error,
100
+ ),
101
+ );
72
102
  }
73
103
  });
74
104
  }
@@ -1,5 +1,6 @@
1
1
  import { FetchHttpClient } from "../experiment/transport/http";
2
2
  import type { HttpClient, SimpleResponse } from "../experiment/types/transport";
3
+ import { assertNetworkEnabled } from "../network";
3
4
 
4
5
  export class NitroHttpClient implements HttpClient {
5
6
  request(
@@ -9,6 +10,7 @@ export class NitroHttpClient implements HttpClient {
9
10
  data: string | null,
10
11
  timeoutMillis = 10000,
11
12
  ): Promise<SimpleResponse> {
13
+ assertNetworkEnabled();
12
14
  return FetchHttpClient.request(
13
15
  requestUrl,
14
16
  method,
@@ -0,0 +1,21 @@
1
+ import type { AmplitudeContext } from "../AmplitudeContext.nitro";
2
+ import type { AmplitudeStorage } from "../AmplitudeStorage.nitro";
3
+ import type { AmplitudeWorker } from "../AmplitudeWorker.nitro";
4
+
5
+ function unavailable(): never {
6
+ throw new Error("Nitro Amplitude native bindings are not available on web");
7
+ }
8
+
9
+ export function getAmplitudeContext(): AmplitudeContext {
10
+ return unavailable();
11
+ }
12
+
13
+ export function getAmplitudeStorage(): AmplitudeStorage {
14
+ return unavailable();
15
+ }
16
+
17
+ export function getAmplitudeWorker(): AmplitudeWorker {
18
+ return unavailable();
19
+ }
20
+
21
+ export function resetHybridInstancesForTests(): void {}