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.
- package/README.md +387 -8
- package/cpp/bindings/HybridAmplitudeWorker.cpp +5 -1
- package/lib/commonjs/analytics/config.js +31 -10
- package/lib/commonjs/analytics/config.js.map +1 -1
- package/lib/commonjs/analytics/index.js +8 -2
- package/lib/commonjs/analytics/index.js.map +1 -1
- package/lib/commonjs/analytics/network-guarded-fetch-transport.js +16 -0
- package/lib/commonjs/analytics/network-guarded-fetch-transport.js.map +1 -0
- package/lib/commonjs/analytics/nitro-transport.js +2 -0
- package/lib/commonjs/analytics/nitro-transport.js.map +1 -1
- package/lib/commonjs/analytics/plugins/context.js +7 -1
- package/lib/commonjs/analytics/plugins/context.js.map +1 -1
- package/lib/commonjs/analytics/react-native-client.js +142 -1
- package/lib/commonjs/analytics/react-native-client.js.map +1 -1
- package/lib/commonjs/diagnostics.js +92 -0
- package/lib/commonjs/diagnostics.js.map +1 -0
- package/lib/commonjs/errors.js +48 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/experiment/experimentClient.js +84 -2
- package/lib/commonjs/experiment/experimentClient.js.map +1 -1
- package/lib/commonjs/experiment/index.js +12 -0
- package/lib/commonjs/experiment/index.js.map +1 -1
- package/lib/commonjs/experiment/stubClient.js +25 -0
- package/lib/commonjs/experiment/stubClient.js.map +1 -1
- package/lib/commonjs/experiment/typed-variants.js +52 -0
- package/lib/commonjs/experiment/typed-variants.js.map +1 -0
- package/lib/commonjs/experiment/types/config.js +32 -4
- package/lib/commonjs/experiment/types/config.js.map +1 -1
- package/lib/commonjs/index.js +105 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +110 -12
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/native/http.js +23 -8
- package/lib/commonjs/native/http.js.map +1 -1
- package/lib/commonjs/native/http.web.js +2 -0
- package/lib/commonjs/native/http.web.js.map +1 -1
- package/lib/commonjs/native/hybrid.web.js +23 -0
- package/lib/commonjs/native/hybrid.web.js.map +1 -0
- package/lib/commonjs/native/storage.js +27 -15
- package/lib/commonjs/native/storage.js.map +1 -1
- package/lib/commonjs/network.js +154 -0
- package/lib/commonjs/network.js.map +1 -0
- package/lib/commonjs/presets.js +118 -0
- package/lib/commonjs/presets.js.map +1 -0
- package/lib/commonjs/testing.js +166 -0
- package/lib/commonjs/testing.js.map +1 -0
- package/lib/module/analytics/config.js +32 -11
- package/lib/module/analytics/config.js.map +1 -1
- package/lib/module/analytics/index.js +4 -1
- package/lib/module/analytics/index.js.map +1 -1
- package/lib/module/analytics/network-guarded-fetch-transport.js +11 -0
- package/lib/module/analytics/network-guarded-fetch-transport.js.map +1 -0
- package/lib/module/analytics/nitro-transport.js +2 -0
- package/lib/module/analytics/nitro-transport.js.map +1 -1
- package/lib/module/analytics/plugins/context.js +7 -1
- package/lib/module/analytics/plugins/context.js.map +1 -1
- package/lib/module/analytics/react-native-client.js +141 -1
- package/lib/module/analytics/react-native-client.js.map +1 -1
- package/lib/module/diagnostics.js +85 -0
- package/lib/module/diagnostics.js.map +1 -0
- package/lib/module/errors.js +41 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/experiment/experimentClient.js +84 -2
- package/lib/module/experiment/experimentClient.js.map +1 -1
- package/lib/module/experiment/index.js +1 -0
- package/lib/module/experiment/index.js.map +1 -1
- package/lib/module/experiment/stubClient.js +25 -0
- package/lib/module/experiment/stubClient.js.map +1 -1
- package/lib/module/experiment/typed-variants.js +43 -0
- package/lib/module/experiment/typed-variants.js.map +1 -0
- package/lib/module/experiment/types/config.js +31 -4
- package/lib/module/experiment/types/config.js.map +1 -1
- package/lib/module/index.js +15 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +19 -6
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/native/http.js +23 -8
- package/lib/module/native/http.js.map +1 -1
- package/lib/module/native/http.web.js +2 -0
- package/lib/module/native/http.web.js.map +1 -1
- package/lib/module/native/hybrid.web.js +16 -0
- package/lib/module/native/hybrid.web.js.map +1 -0
- package/lib/module/native/storage.js +27 -15
- package/lib/module/native/storage.js.map +1 -1
- package/lib/module/network.js +138 -0
- package/lib/module/network.js.map +1 -0
- package/lib/module/presets.js +110 -0
- package/lib/module/presets.js.map +1 -0
- package/lib/module/testing.js +160 -0
- package/lib/module/testing.js.map +1 -0
- package/lib/typescript/analytics/config.d.ts +19 -6
- package/lib/typescript/analytics/config.d.ts.map +1 -1
- package/lib/typescript/analytics/index.d.ts +1 -1
- package/lib/typescript/analytics/index.d.ts.map +1 -1
- package/lib/typescript/analytics/network-guarded-fetch-transport.d.ts +6 -0
- package/lib/typescript/analytics/network-guarded-fetch-transport.d.ts.map +1 -0
- package/lib/typescript/analytics/nitro-transport.d.ts.map +1 -1
- package/lib/typescript/analytics/plugins/context.d.ts +2 -0
- package/lib/typescript/analytics/plugins/context.d.ts.map +1 -1
- package/lib/typescript/analytics/react-native-client.d.ts +43 -0
- package/lib/typescript/analytics/react-native-client.d.ts.map +1 -1
- package/lib/typescript/diagnostics.d.ts +21 -0
- package/lib/typescript/diagnostics.d.ts.map +1 -0
- package/lib/typescript/errors.d.ts +9 -0
- package/lib/typescript/errors.d.ts.map +1 -0
- package/lib/typescript/experiment/experimentClient.d.ts +9 -1
- package/lib/typescript/experiment/experimentClient.d.ts.map +1 -1
- package/lib/typescript/experiment/index.d.ts +1 -0
- package/lib/typescript/experiment/index.d.ts.map +1 -1
- package/lib/typescript/experiment/stubClient.d.ts +6 -1
- package/lib/typescript/experiment/stubClient.d.ts.map +1 -1
- package/lib/typescript/experiment/typed-variants.d.ts +9 -0
- package/lib/typescript/experiment/typed-variants.d.ts.map +1 -0
- package/lib/typescript/experiment/types/client.d.ts +21 -0
- package/lib/typescript/experiment/types/client.d.ts.map +1 -1
- package/lib/typescript/experiment/types/config.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +12 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +16 -6
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/native/http.d.ts.map +1 -1
- package/lib/typescript/native/http.web.d.ts.map +1 -1
- package/lib/typescript/native/hybrid.web.d.ts +8 -0
- package/lib/typescript/native/hybrid.web.d.ts.map +1 -0
- package/lib/typescript/native/storage.d.ts.map +1 -1
- package/lib/typescript/network.d.ts +50 -0
- package/lib/typescript/network.d.ts.map +1 -0
- package/lib/typescript/presets.d.ts +50 -0
- package/lib/typescript/presets.d.ts.map +1 -0
- package/lib/typescript/testing.d.ts +11 -0
- package/lib/typescript/testing.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/analytics/config.ts +33 -8
- package/src/analytics/index.ts +3 -0
- package/src/analytics/network-guarded-fetch-transport.ts +10 -0
- package/src/analytics/nitro-transport.ts +2 -0
- package/src/analytics/plugins/context.ts +10 -1
- package/src/analytics/react-native-client.ts +217 -0
- package/src/diagnostics.ts +119 -0
- package/src/errors.ts +60 -0
- package/src/experiment/experimentClient.ts +116 -3
- package/src/experiment/index.ts +1 -0
- package/src/experiment/stubClient.ts +42 -1
- package/src/experiment/typed-variants.ts +68 -0
- package/src/experiment/types/client.ts +29 -0
- package/src/experiment/types/config.ts +29 -5
- package/src/index.ts +28 -2
- package/src/index.web.ts +33 -5
- package/src/native/http.ts +38 -8
- package/src/native/http.web.ts +2 -0
- package/src/native/hybrid.web.ts +21 -0
- package/src/native/storage.ts +27 -25
- package/src/network.ts +258 -0
- package/src/presets.ts +208 -0
- 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 {
|
|
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
|
-
|
|
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(
|
package/src/experiment/index.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
209
|
-
|
|
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 {
|
|
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.
|
|
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 {
|
|
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 {
|
|
39
|
-
|
|
40
|
-
|
|
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.
|
|
94
|
+
export const VERSION = "0.5.0";
|
package/src/native/http.ts
CHANGED
|
@@ -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
|
-
|
|
20
|
+
const worker = getAmplitudeWorker();
|
|
21
|
+
if (listenerInstalled && listenerWorker === worker) {
|
|
15
22
|
return;
|
|
16
23
|
}
|
|
17
24
|
listenerInstalled = true;
|
|
18
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
55
|
-
|
|
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
|
-
|
|
90
|
+
normalizedTimeoutMillis,
|
|
67
91
|
);
|
|
68
92
|
} catch (error) {
|
|
69
93
|
pendingRequests.delete(requestId);
|
|
70
94
|
clearTimeout(timeoutId);
|
|
71
|
-
reject(
|
|
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
|
}
|
package/src/native/http.web.ts
CHANGED
|
@@ -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 {}
|