react-native-nitro-amplitude 0.1.0 → 0.2.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 (48) hide show
  1. package/README.md +166 -53
  2. package/cpp/bindings/HybridAmplitudeWorker.cpp +22 -6
  3. package/lib/commonjs/analytics/react-native-client.js +13 -8
  4. package/lib/commonjs/analytics/react-native-client.js.map +1 -1
  5. package/lib/commonjs/experiment/transport/http.js +8 -2
  6. package/lib/commonjs/experiment/transport/http.js.map +1 -1
  7. package/lib/commonjs/index.js +1 -1
  8. package/lib/commonjs/index.web.js +289 -13
  9. package/lib/commonjs/index.web.js.map +1 -1
  10. package/lib/commonjs/native/context.web.js +26 -0
  11. package/lib/commonjs/native/context.web.js.map +1 -0
  12. package/lib/commonjs/native/http.web.js +15 -0
  13. package/lib/commonjs/native/http.web.js.map +1 -0
  14. package/lib/commonjs/native/storage.web.js +135 -0
  15. package/lib/commonjs/native/storage.web.js.map +1 -0
  16. package/lib/module/analytics/react-native-client.js +13 -8
  17. package/lib/module/analytics/react-native-client.js.map +1 -1
  18. package/lib/module/experiment/transport/http.js +8 -2
  19. package/lib/module/experiment/transport/http.js.map +1 -1
  20. package/lib/module/index.js +1 -1
  21. package/lib/module/index.web.js +47 -11
  22. package/lib/module/index.web.js.map +1 -1
  23. package/lib/module/native/context.web.js +18 -0
  24. package/lib/module/native/context.web.js.map +1 -0
  25. package/lib/module/native/http.web.js +10 -0
  26. package/lib/module/native/http.web.js.map +1 -0
  27. package/lib/module/native/storage.web.js +128 -0
  28. package/lib/module/native/storage.web.js.map +1 -0
  29. package/lib/typescript/analytics/react-native-client.d.ts +3 -6
  30. package/lib/typescript/analytics/react-native-client.d.ts.map +1 -1
  31. package/lib/typescript/experiment/transport/http.d.ts.map +1 -1
  32. package/lib/typescript/index.d.ts +1 -1
  33. package/lib/typescript/index.web.d.ts +27 -8
  34. package/lib/typescript/index.web.d.ts.map +1 -1
  35. package/lib/typescript/native/context.web.d.ts +8 -0
  36. package/lib/typescript/native/context.web.d.ts.map +1 -0
  37. package/lib/typescript/native/http.web.d.ts +6 -0
  38. package/lib/typescript/native/http.web.d.ts.map +1 -0
  39. package/lib/typescript/native/storage.web.d.ts +30 -0
  40. package/lib/typescript/native/storage.web.d.ts.map +1 -0
  41. package/package.json +4 -2
  42. package/src/analytics/react-native-client.ts +21 -9
  43. package/src/experiment/transport/http.ts +10 -2
  44. package/src/index.ts +1 -1
  45. package/src/index.web.ts +61 -14
  46. package/src/native/context.web.ts +38 -0
  47. package/src/native/http.web.ts +22 -0
  48. package/src/native/storage.web.ts +152 -0
@@ -48,7 +48,7 @@ export type AmplitudeReactNativeClient = ReactNativeClient & {
48
48
  };
49
49
 
50
50
  let nextConnectorOwnerId = 0;
51
- let activeConnectorOwnerId: number | undefined;
51
+ const activeConnectorOwnerIds = new Map<string, number>();
52
52
 
53
53
  export class AmplitudeReactNative
54
54
  extends AmplitudeCore
@@ -62,7 +62,7 @@ export class AmplitudeReactNative
62
62
 
63
63
  // @ts-ignore
64
64
  config: ReactNativeConfig;
65
- userProperties: { [key: string]: any } | undefined;
65
+ userProperties: Record<string, unknown> | undefined;
66
66
 
67
67
  init(apiKey = "", userId?: string, options?: ReactNativeOptions) {
68
68
  this.initPromise =
@@ -99,7 +99,8 @@ export class AmplitudeReactNative
99
99
  // Set up the analytics connector to integrate with the experiment SDK.
100
100
  // Send events from the experiment SDK and forward identifies to the
101
101
  // identity store.
102
- const connector = getAnalyticsConnector();
102
+ const connectorInstanceName = this.getConnectorInstanceName();
103
+ const connector = getAnalyticsConnector(connectorInstanceName);
103
104
  connector.identityStore.setIdentity({
104
105
  userId: this.config.userId,
105
106
  deviceId: this.config.deviceId,
@@ -140,7 +141,7 @@ export class AmplitudeReactNative
140
141
  this.track(event.eventType, event.eventProperties).promise,
141
142
  );
142
143
  });
143
- activeConnectorOwnerId = this.connectorOwnerId;
144
+ activeConnectorOwnerIds.set(connectorInstanceName, this.connectorOwnerId);
144
145
  } catch (error) {
145
146
  if (appStateHandlerInstalled) {
146
147
  this.appStateChangeHandler?.remove();
@@ -162,11 +163,18 @@ export class AmplitudeReactNative
162
163
  this.dispatchQ = [];
163
164
  this.isReady = false;
164
165
 
165
- if (activeConnectorOwnerId === this.connectorOwnerId) {
166
- const connector = getAnalyticsConnector();
166
+ const connectorInstanceName = this.config
167
+ ? this.getConnectorInstanceName()
168
+ : undefined;
169
+ if (
170
+ connectorInstanceName &&
171
+ activeConnectorOwnerIds.get(connectorInstanceName) ===
172
+ this.connectorOwnerId
173
+ ) {
174
+ const connector = getAnalyticsConnector(connectorInstanceName);
167
175
  connector.eventBridge.setEventReceiver(() => undefined);
168
176
  connector.identityStore.setIdentity({});
169
- activeConnectorOwnerId = undefined;
177
+ activeConnectorOwnerIds.delete(connectorInstanceName);
170
178
  }
171
179
  }
172
180
 
@@ -178,6 +186,10 @@ export class AmplitudeReactNative
178
186
  });
179
187
  }
180
188
 
189
+ private getConnectorInstanceName() {
190
+ return this.config.instanceName ?? "$default_instance";
191
+ }
192
+
181
193
  private cancelDestinationFlushes() {
182
194
  this.timeline.plugins.forEach((plugin) => {
183
195
  if (plugin.type !== "destination") {
@@ -232,7 +244,7 @@ export class AmplitudeReactNative
232
244
  return;
233
245
  }
234
246
  this.config.userId = userId;
235
- setConnectorUserId(userId);
247
+ setConnectorUserId(userId, this.getConnectorInstanceName());
236
248
  }
237
249
 
238
250
  getDeviceId() {
@@ -245,7 +257,7 @@ export class AmplitudeReactNative
245
257
  return;
246
258
  }
247
259
  this.config.deviceId = deviceId;
248
- setConnectorDeviceId(deviceId);
260
+ setConnectorDeviceId(deviceId, this.getConnectorInstanceName());
249
261
  }
250
262
 
251
263
  identify(identify: IIdentify, eventOptions?: EventOptions) {
@@ -66,11 +66,19 @@ const _request = (
66
66
  ): Promise<SimpleResponse> => {
67
67
  const abortController = getAbortController();
68
68
  const call = async () => {
69
- const response = await getFetch()(requestUrl, {
69
+ const upperMethod = method.toUpperCase();
70
+ const init: RequestInit = {
70
71
  method: method,
71
72
  headers: headers,
72
- body: data,
73
73
  signal: abortController?.signal,
74
+ };
75
+
76
+ if (upperMethod !== "GET" && upperMethod !== "HEAD") {
77
+ init.body = data;
78
+ }
79
+
80
+ const response = await getFetch()(requestUrl, {
81
+ ...init,
74
82
  });
75
83
  const simpleResponse: SimpleResponse = {
76
84
  status: response.status,
package/src/index.ts CHANGED
@@ -63,4 +63,4 @@ export type { AmplitudeContext } from "./AmplitudeContext.nitro";
63
63
  export type { AmplitudeStorage } from "./AmplitudeStorage.nitro";
64
64
  export type { AmplitudeWorker } from "./AmplitudeWorker.nitro";
65
65
 
66
- export const VERSION = "0.1.0";
66
+ export const VERSION = "0.2.0";
package/src/index.web.ts CHANGED
@@ -1,19 +1,66 @@
1
- const unsupported = (name: string): never => {
2
- throw new Error(
3
- `react-native-nitro-amplitude: ${name} is not supported on web. Use native iOS/Android builds.`,
4
- );
5
- };
1
+ import analyticsClient, {
2
+ createInstance,
3
+ } from "./analytics/react-native-client";
4
+ import { prefetchNativeContext } from "./native/context";
6
5
 
7
- export const init = () => unsupported("init");
8
- export const track = () => unsupported("track");
9
- export const identify = () => unsupported("identify");
10
- export const createInstance = () => unsupported("createInstance");
11
- export const Experiment = {
12
- initialize: () => unsupported("Experiment.initialize"),
13
- initializeWithAmplitudeAnalytics: () =>
14
- unsupported("Experiment.initializeWithAmplitudeAnalytics"),
15
- };
6
+ import * as AnalyticsTypes from "./analytics/types";
7
+
8
+ export { createInstance };
9
+ export const {
10
+ add,
11
+ flush,
12
+ getDeviceId,
13
+ getSessionId,
14
+ getUserId,
15
+ groupIdentify,
16
+ identify,
17
+ init,
18
+ logEvent,
19
+ remove,
20
+ reset,
21
+ revenue,
22
+ setDeviceId,
23
+ setGroup,
24
+ setOptOut,
25
+ setSessionId,
26
+ setUserId,
27
+ shutdown,
28
+ track,
29
+ extendSession,
30
+ } = analyticsClient;
31
+
32
+ export { Revenue, Identify } from "@amplitude/analytics-core";
33
+ export {
34
+ InMemoryStorage,
35
+ LocalStorage,
36
+ MemoryStorage,
37
+ } 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";
41
+ export { prefetchNativeContext };
42
+ export { AnalyticsTypes as Types };
43
+
44
+ export * from "./experiment/types/config";
45
+ export { Experiment } from "./experiment/factory";
46
+ export { StubExperimentClient } from "./experiment/stubClient";
47
+ export { ExperimentClient } from "./experiment/experimentClient";
48
+ export * from "./experiment/types/client";
49
+ export { Source } from "./experiment/types/source";
50
+ export * from "./experiment/types/user";
51
+ export * from "./experiment/types/variant";
52
+ export * from "./experiment/types/exposure";
53
+ export * from "./experiment/types/storage";
54
+ export { LogLevel } from "./experiment/types/logger";
55
+ export type { Logger } from "./experiment/types/logger";
56
+ export { ConsoleLogger } from "./experiment/logger/consoleLogger";
57
+ export {
58
+ LocalStorage as ExperimentLocalStorage,
59
+ MemoryStorage as ExperimentMemoryStorage,
60
+ } from "./experiment/storage/local-storage";
16
61
 
17
62
  export type { AmplitudeContext } from "./AmplitudeContext.nitro";
18
63
  export type { AmplitudeStorage } from "./AmplitudeStorage.nitro";
19
64
  export type { AmplitudeWorker } from "./AmplitudeWorker.nitro";
65
+
66
+ export const VERSION = "0.2.0";
@@ -0,0 +1,38 @@
1
+ import type { ReactNativeTrackingOptions } from "@amplitude/analytics-core";
2
+ import type { LegacySessionData, NativeApplicationContext } from "./context";
3
+
4
+ type NavigatorWithLanguage = Navigator & {
5
+ userLanguage?: string;
6
+ };
7
+
8
+ export function prefetchNativeContext(): void {}
9
+
10
+ export function getNativeApplicationContext(
11
+ _options: ReactNativeTrackingOptions,
12
+ ): NativeApplicationContext {
13
+ const browserNavigator =
14
+ typeof navigator === "undefined"
15
+ ? undefined
16
+ : (navigator as NavigatorWithLanguage);
17
+ return {
18
+ platform: "Web",
19
+ language: browserNavigator?.language ?? browserNavigator?.userLanguage,
20
+ };
21
+ }
22
+
23
+ export function getLegacySessionData(_instanceName: string): LegacySessionData {
24
+ return {};
25
+ }
26
+
27
+ export function getLegacyEvents(
28
+ _instanceName: string,
29
+ _eventKind: string,
30
+ ): string[] {
31
+ return [];
32
+ }
33
+
34
+ export function removeLegacyEvent(
35
+ _instanceName: string,
36
+ _eventKind: string,
37
+ _eventId: number,
38
+ ): void {}
@@ -0,0 +1,22 @@
1
+ import { FetchHttpClient } from "../experiment/transport/http";
2
+ import type { HttpClient, SimpleResponse } from "../experiment/types/transport";
3
+
4
+ export class NitroHttpClient implements HttpClient {
5
+ request(
6
+ requestUrl: string,
7
+ method: string,
8
+ headers: Record<string, string>,
9
+ data: string | null,
10
+ timeoutMillis = 10000,
11
+ ): Promise<SimpleResponse> {
12
+ return FetchHttpClient.request(
13
+ requestUrl,
14
+ method,
15
+ headers,
16
+ data ?? "",
17
+ timeoutMillis,
18
+ );
19
+ }
20
+ }
21
+
22
+ export const nitroHttpClient = new NitroHttpClient();
@@ -0,0 +1,152 @@
1
+ import type { Storage as AnalyticsStorage } from "@amplitude/analytics-core";
2
+ import type { Storage as ExperimentStorage } from "../experiment/types/storage";
3
+
4
+ function namespaceKey(namespace: string, key: string): string {
5
+ return `${namespace}::${key}`;
6
+ }
7
+
8
+ class WebStringStorage {
9
+ private static readonly memory = new Map<string, string>();
10
+
11
+ constructor(private readonly namespace: string) {}
12
+
13
+ get(key: string): string | undefined {
14
+ const storageKey = namespaceKey(this.namespace, key);
15
+ try {
16
+ if (typeof localStorage !== "undefined") {
17
+ return localStorage.getItem(storageKey) ?? undefined;
18
+ }
19
+ } catch {}
20
+ return WebStringStorage.memory.get(storageKey);
21
+ }
22
+
23
+ set(key: string, value: string): void {
24
+ const storageKey = namespaceKey(this.namespace, key);
25
+ WebStringStorage.memory.set(storageKey, value);
26
+ try {
27
+ if (typeof localStorage !== "undefined") {
28
+ localStorage.setItem(storageKey, value);
29
+ }
30
+ } catch {}
31
+ }
32
+
33
+ remove(key: string): void {
34
+ const storageKey = namespaceKey(this.namespace, key);
35
+ WebStringStorage.memory.delete(storageKey);
36
+ try {
37
+ if (typeof localStorage !== "undefined") {
38
+ localStorage.removeItem(storageKey);
39
+ }
40
+ } catch {}
41
+ }
42
+
43
+ reset(): void {
44
+ const prefix = `${this.namespace}::`;
45
+ for (const key of WebStringStorage.memory.keys()) {
46
+ if (key.startsWith(prefix)) {
47
+ WebStringStorage.memory.delete(key);
48
+ }
49
+ }
50
+ try {
51
+ if (typeof localStorage !== "undefined") {
52
+ for (let index = localStorage.length - 1; index >= 0; index--) {
53
+ const key = localStorage.key(index);
54
+ if (key?.startsWith(prefix)) {
55
+ localStorage.removeItem(key);
56
+ }
57
+ }
58
+ }
59
+ } catch {}
60
+ }
61
+ }
62
+
63
+ export class NitroAnalyticsStorage<T> implements AnalyticsStorage<T> {
64
+ private readonly storage: WebStringStorage;
65
+
66
+ constructor(namespace: string) {
67
+ this.storage = new WebStringStorage(namespace);
68
+ }
69
+
70
+ async isEnabled(): Promise<boolean> {
71
+ return true;
72
+ }
73
+
74
+ async get(key: string): Promise<T | undefined> {
75
+ const raw = await this.getRaw(key);
76
+ if (!raw) {
77
+ return undefined;
78
+ }
79
+ try {
80
+ return JSON.parse(raw) as T;
81
+ } catch {
82
+ return undefined;
83
+ }
84
+ }
85
+
86
+ async getRaw(key: string): Promise<string | undefined> {
87
+ return this.storage.get(key);
88
+ }
89
+
90
+ async set(key: string, value: T): Promise<void> {
91
+ this.storage.set(key, JSON.stringify(value));
92
+ }
93
+
94
+ async remove(key: string): Promise<void> {
95
+ this.storage.remove(key);
96
+ }
97
+
98
+ async reset(): Promise<void> {
99
+ this.storage.reset();
100
+ }
101
+ }
102
+
103
+ export class NitroExperimentStorage implements ExperimentStorage {
104
+ private readonly storage: WebStringStorage;
105
+
106
+ constructor(namespace: string) {
107
+ this.storage = new WebStringStorage(namespace);
108
+ }
109
+
110
+ async get(key: string): Promise<string | null> {
111
+ return this.storage.get(key) ?? null;
112
+ }
113
+
114
+ async put(key: string, value: string): Promise<void> {
115
+ this.storage.set(key, value);
116
+ }
117
+
118
+ async delete(key: string): Promise<void> {
119
+ this.storage.remove(key);
120
+ }
121
+
122
+ async reset(): Promise<void> {
123
+ this.storage.reset();
124
+ }
125
+ }
126
+
127
+ export class NitroMemoryStorage implements ExperimentStorage {
128
+ private readonly values = new Map<string, string>();
129
+
130
+ constructor(private readonly namespace: string) {}
131
+
132
+ async get(key: string): Promise<string | null> {
133
+ return this.values.get(namespaceKey(this.namespace, key)) ?? null;
134
+ }
135
+
136
+ async put(key: string, value: string): Promise<void> {
137
+ this.values.set(namespaceKey(this.namespace, key), value);
138
+ }
139
+
140
+ async delete(key: string): Promise<void> {
141
+ this.values.delete(namespaceKey(this.namespace, key));
142
+ }
143
+
144
+ async reset(): Promise<void> {
145
+ const prefix = `${this.namespace}::`;
146
+ for (const key of this.values.keys()) {
147
+ if (key.startsWith(prefix)) {
148
+ this.values.delete(key);
149
+ }
150
+ }
151
+ }
152
+ }