@selwise/react-native 0.1.1
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/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/selwise.d.ts +59 -0
- package/dist/selwise.js +647 -0
- package/dist/storage.d.ts +3 -0
- package/dist/storage.js +41 -0
- package/dist/transport.d.ts +22 -0
- package/dist/transport.js +47 -0
- package/dist/types.d.ts +113 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +54 -0
- package/package.json +37 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { Selwise } from './selwise';
|
|
2
|
+
export type { AsyncStorageLike, ConsentCategories, ConsentState, IdentifyResult, JourneyState, RecommendationProductsContext, RecommendationWidgetQuery, ScreenContext, SearchOptions, SelwiseDataLayer, SelwiseReactNativeConfig, TrackArgs, TrackOrderInput, TrackOrderItem, } from './types';
|
|
3
|
+
export { Selwise as default } from './selwise';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = exports.Selwise = void 0;
|
|
4
|
+
var selwise_1 = require("./selwise");
|
|
5
|
+
Object.defineProperty(exports, "Selwise", { enumerable: true, get: function () { return selwise_1.Selwise; } });
|
|
6
|
+
var selwise_2 = require("./selwise");
|
|
7
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return selwise_2.Selwise; } });
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ConsentCategories, ConsentState, IdentifyResult, JourneyState, RecommendationProductsContext, RecommendationWidgetQuery, ScreenContext, SearchOptions, SelwiseDataLayer, SelwiseReactNativeConfig, TrackArgs, TrackOrderInput } from './types';
|
|
2
|
+
export declare class Selwise {
|
|
3
|
+
private runtimeConfig;
|
|
4
|
+
private storage;
|
|
5
|
+
private transport;
|
|
6
|
+
private identityState;
|
|
7
|
+
private queue;
|
|
8
|
+
private flushTimer;
|
|
9
|
+
private appStateSubscription;
|
|
10
|
+
private readonly activeExperiments;
|
|
11
|
+
private queueStorageKey;
|
|
12
|
+
private stateStorageKey;
|
|
13
|
+
private initialized;
|
|
14
|
+
private isFlushing;
|
|
15
|
+
init(config: SelwiseReactNativeConfig): Promise<void>;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
track(name: string, args?: TrackArgs): void;
|
|
18
|
+
trackOrder(order: TrackOrderInput): Promise<void>;
|
|
19
|
+
identify(userId: string, metadata?: Record<string, unknown>): Promise<IdentifyResult>;
|
|
20
|
+
setTraits(traits: Record<string, unknown>): Promise<boolean>;
|
|
21
|
+
getVisitorId(): string;
|
|
22
|
+
getSessionId(): string;
|
|
23
|
+
getJourneyId(): string | null;
|
|
24
|
+
getJourneyState(): JourneyState;
|
|
25
|
+
endJourney(): void;
|
|
26
|
+
getVariant(experimentId: string): unknown;
|
|
27
|
+
getExperimentVariant(experimentId: string): string | null;
|
|
28
|
+
getActiveExperiments(): unknown[];
|
|
29
|
+
pushDataLayer(data: SelwiseDataLayer): void;
|
|
30
|
+
getDataLayer(): SelwiseDataLayer[];
|
|
31
|
+
getConsentState(): ConsentState | null;
|
|
32
|
+
grantConsent(categories?: Partial<ConsentState>): void;
|
|
33
|
+
revokeConsent(categories?: (keyof ConsentCategories)[]): void;
|
|
34
|
+
flush(): Promise<void>;
|
|
35
|
+
setScreenContext(context: ScreenContext): void;
|
|
36
|
+
getSiteConfig(): Promise<Record<string, unknown>>;
|
|
37
|
+
getSearchConfig(): Promise<Record<string, unknown>>;
|
|
38
|
+
search(query: string, options?: SearchOptions): Promise<Record<string, unknown>>;
|
|
39
|
+
getSearchSuggestions(): Promise<Record<string, unknown>>;
|
|
40
|
+
getRecommendationWidgets(query?: RecommendationWidgetQuery): Promise<Record<string, unknown>>;
|
|
41
|
+
getRecommendationProducts(widgetId: string, context?: RecommendationProductsContext): Promise<Record<string, unknown>>;
|
|
42
|
+
private normalizeConfig;
|
|
43
|
+
private ensureIdentityState;
|
|
44
|
+
private enqueueEvent;
|
|
45
|
+
private ensureJourney;
|
|
46
|
+
private startFlushTimer;
|
|
47
|
+
private stopFlushTimer;
|
|
48
|
+
private attachAppStateFlush;
|
|
49
|
+
private syncConsent;
|
|
50
|
+
private mergeConsentCategories;
|
|
51
|
+
private defaultConsentState;
|
|
52
|
+
private getSdkMetadata;
|
|
53
|
+
private captureExperimentAssignments;
|
|
54
|
+
private requestPublicGet;
|
|
55
|
+
private loadIdentityState;
|
|
56
|
+
private loadQueue;
|
|
57
|
+
private persistIdentityState;
|
|
58
|
+
private persistQueue;
|
|
59
|
+
}
|
package/dist/selwise.js
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Selwise = void 0;
|
|
4
|
+
const storage_1 = require("./storage");
|
|
5
|
+
const transport_1 = require("./transport");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
const DEFAULT_API_URL = 'https://api.selwise.com/api/v1';
|
|
8
|
+
const DEFAULT_PLATFORM = 'react-native';
|
|
9
|
+
const DEFAULT_VERSION = '0.1.0';
|
|
10
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 15000;
|
|
11
|
+
const DEFAULT_BATCH_SIZE = 25;
|
|
12
|
+
const DEFAULT_MAX_RETRIES = 5;
|
|
13
|
+
const DEFAULT_MAX_QUEUE_SIZE = 500;
|
|
14
|
+
const DEFAULT_JOURNEY_TTL_MS = 30 * 60 * 1000;
|
|
15
|
+
const DEFAULT_STORAGE_PREFIX = 'selwise_rn';
|
|
16
|
+
const MAX_DATALAYER_ITEMS = 500;
|
|
17
|
+
class Selwise {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.runtimeConfig = null;
|
|
20
|
+
this.storage = null;
|
|
21
|
+
this.transport = null;
|
|
22
|
+
this.identityState = null;
|
|
23
|
+
this.queue = [];
|
|
24
|
+
this.flushTimer = null;
|
|
25
|
+
this.appStateSubscription = null;
|
|
26
|
+
this.activeExperiments = new Map();
|
|
27
|
+
this.queueStorageKey = null;
|
|
28
|
+
this.stateStorageKey = null;
|
|
29
|
+
this.initialized = false;
|
|
30
|
+
this.isFlushing = false;
|
|
31
|
+
}
|
|
32
|
+
async init(config) {
|
|
33
|
+
const normalized = this.normalizeConfig(config);
|
|
34
|
+
this.runtimeConfig = normalized;
|
|
35
|
+
this.stateStorageKey = `${normalized.storagePrefix}:${normalized.siteKey}:state`;
|
|
36
|
+
this.queueStorageKey = `${normalized.storagePrefix}:${normalized.siteKey}:queue`;
|
|
37
|
+
this.storage = (0, storage_1.resolveAsyncStorage)(config.storage);
|
|
38
|
+
this.transport = new transport_1.SelwiseTransport({
|
|
39
|
+
baseUrl: normalized.apiUrl,
|
|
40
|
+
siteKey: normalized.siteKey,
|
|
41
|
+
apiKey: normalized.apiKey,
|
|
42
|
+
sdkPlatform: normalized.sdkPlatform,
|
|
43
|
+
sdkVersion: normalized.sdkVersion,
|
|
44
|
+
});
|
|
45
|
+
this.identityState = await this.loadIdentityState();
|
|
46
|
+
this.identityState = this.ensureIdentityState(this.identityState);
|
|
47
|
+
this.queue = await this.loadQueue();
|
|
48
|
+
await Promise.all([this.persistIdentityState(), this.persistQueue()]);
|
|
49
|
+
this.attachAppStateFlush();
|
|
50
|
+
this.startFlushTimer();
|
|
51
|
+
this.initialized = true;
|
|
52
|
+
}
|
|
53
|
+
destroy() {
|
|
54
|
+
void this.flush();
|
|
55
|
+
this.stopFlushTimer();
|
|
56
|
+
if (this.appStateSubscription) {
|
|
57
|
+
this.appStateSubscription.remove();
|
|
58
|
+
this.appStateSubscription = null;
|
|
59
|
+
}
|
|
60
|
+
this.initialized = false;
|
|
61
|
+
}
|
|
62
|
+
track(name, args) {
|
|
63
|
+
if (!this.initialized || !this.runtimeConfig || !this.identityState) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const event = {
|
|
68
|
+
eventId: (0, utils_1.createEventId)(),
|
|
69
|
+
eventSchemaVersion: 1,
|
|
70
|
+
eventTs: now,
|
|
71
|
+
timestamp: now,
|
|
72
|
+
name,
|
|
73
|
+
entityType: args?.entityType || 'custom',
|
|
74
|
+
entityId: args?.entityId,
|
|
75
|
+
metadata: {
|
|
76
|
+
...((0, utils_1.sanitizeMetadata)(args?.metadata) || {}),
|
|
77
|
+
...(this.identityState.screenContext
|
|
78
|
+
? {
|
|
79
|
+
screenName: this.identityState.screenContext.name,
|
|
80
|
+
screenPath: this.identityState.screenContext.path,
|
|
81
|
+
}
|
|
82
|
+
: {}),
|
|
83
|
+
},
|
|
84
|
+
retryCount: 0,
|
|
85
|
+
nextAttemptAt: now,
|
|
86
|
+
};
|
|
87
|
+
this.enqueueEvent(event);
|
|
88
|
+
}
|
|
89
|
+
async trackOrder(order) {
|
|
90
|
+
if (!this.initialized || !this.transport || !this.identityState || !this.runtimeConfig) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const orderId = (order.orderId || '').trim();
|
|
94
|
+
if (!orderId) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const sdkMetadata = this.getSdkMetadata();
|
|
98
|
+
await this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}/orders`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body: {
|
|
101
|
+
...order,
|
|
102
|
+
orderId,
|
|
103
|
+
sessionId: this.identityState.sessionId,
|
|
104
|
+
visitorId: order.visitorId || this.identityState.visitorId,
|
|
105
|
+
siteUserId: order.siteUserId || this.identityState.siteUserId,
|
|
106
|
+
pageUrl: this.identityState.screenContext?.path,
|
|
107
|
+
referrer: this.identityState.screenContext?.referrer,
|
|
108
|
+
metadata: {
|
|
109
|
+
...(order.metadata || {}),
|
|
110
|
+
...sdkMetadata,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async identify(userId, metadata) {
|
|
116
|
+
if (!this.initialized || !this.transport || !this.identityState || !this.runtimeConfig) {
|
|
117
|
+
return { success: false, merged: false };
|
|
118
|
+
}
|
|
119
|
+
const externalId = (userId || '').trim();
|
|
120
|
+
if (!externalId) {
|
|
121
|
+
return { success: false, merged: false };
|
|
122
|
+
}
|
|
123
|
+
this.identityState.siteUserId = externalId;
|
|
124
|
+
await this.persistIdentityState();
|
|
125
|
+
try {
|
|
126
|
+
const result = await this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}/users/identify`, {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
body: {
|
|
129
|
+
externalId,
|
|
130
|
+
visitorId: this.identityState.visitorId,
|
|
131
|
+
sessionId: this.identityState.sessionId,
|
|
132
|
+
traits: metadata,
|
|
133
|
+
pageUrl: this.identityState.screenContext?.path,
|
|
134
|
+
referrer: this.identityState.screenContext?.referrer,
|
|
135
|
+
userAgent: 'react-native',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return { success: false, merged: false };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async setTraits(traits) {
|
|
145
|
+
if (!this.initialized || !this.transport || !this.identityState || !this.runtimeConfig) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const result = await this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}/users/traits`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: {
|
|
152
|
+
externalId: this.identityState.siteUserId,
|
|
153
|
+
visitorId: this.identityState.siteUserId ? undefined : this.identityState.visitorId,
|
|
154
|
+
traits,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return result.success === true;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
getVisitorId() {
|
|
164
|
+
if (!this.initialized) {
|
|
165
|
+
return '';
|
|
166
|
+
}
|
|
167
|
+
return this.identityState?.visitorId || '';
|
|
168
|
+
}
|
|
169
|
+
getSessionId() {
|
|
170
|
+
if (!this.initialized) {
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
return this.identityState?.sessionId || '';
|
|
174
|
+
}
|
|
175
|
+
getJourneyId() {
|
|
176
|
+
if (!this.initialized || !this.identityState || !this.runtimeConfig) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const age = Date.now() - this.identityState.journeyCreatedAt;
|
|
180
|
+
if (age >= this.runtimeConfig.journeyTtlMs) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return this.identityState.journeyId;
|
|
184
|
+
}
|
|
185
|
+
getJourneyState() {
|
|
186
|
+
if (!this.initialized || !this.identityState || !this.runtimeConfig) {
|
|
187
|
+
return {
|
|
188
|
+
journeyId: null,
|
|
189
|
+
age: null,
|
|
190
|
+
isValid: false,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const age = Date.now() - this.identityState.journeyCreatedAt;
|
|
194
|
+
return {
|
|
195
|
+
journeyId: this.identityState.journeyId,
|
|
196
|
+
age,
|
|
197
|
+
isValid: age < this.runtimeConfig.journeyTtlMs,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
endJourney() {
|
|
201
|
+
if (!this.initialized || !this.identityState) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.identityState.journeyId = (0, utils_1.createJourneyId)(this.identityState.visitorId);
|
|
205
|
+
this.identityState.journeyCreatedAt = Date.now();
|
|
206
|
+
void this.persistIdentityState();
|
|
207
|
+
}
|
|
208
|
+
getVariant(experimentId) {
|
|
209
|
+
return this.activeExperiments.get(experimentId);
|
|
210
|
+
}
|
|
211
|
+
getExperimentVariant(experimentId) {
|
|
212
|
+
const assignment = this.activeExperiments.get(experimentId);
|
|
213
|
+
if (!assignment) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (typeof assignment === 'string') {
|
|
217
|
+
return assignment;
|
|
218
|
+
}
|
|
219
|
+
return assignment.variantId || assignment.id || null;
|
|
220
|
+
}
|
|
221
|
+
getActiveExperiments() {
|
|
222
|
+
return Array.from(this.activeExperiments.values());
|
|
223
|
+
}
|
|
224
|
+
pushDataLayer(data) {
|
|
225
|
+
if (!this.initialized || !this.identityState) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.identityState.dataLayer.push(data);
|
|
229
|
+
if (this.identityState.dataLayer.length > MAX_DATALAYER_ITEMS) {
|
|
230
|
+
this.identityState.dataLayer = this.identityState.dataLayer.slice(this.identityState.dataLayer.length - MAX_DATALAYER_ITEMS);
|
|
231
|
+
}
|
|
232
|
+
void this.persistIdentityState();
|
|
233
|
+
}
|
|
234
|
+
getDataLayer() {
|
|
235
|
+
if (!this.initialized) {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
return this.identityState?.dataLayer || [];
|
|
239
|
+
}
|
|
240
|
+
getConsentState() {
|
|
241
|
+
if (!this.initialized) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
return this.identityState?.consentState || null;
|
|
245
|
+
}
|
|
246
|
+
grantConsent(categories) {
|
|
247
|
+
if (!this.initialized || !this.identityState) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const base = this.identityState.consentState || this.defaultConsentState();
|
|
251
|
+
const nextCategories = this.mergeConsentCategories(base.categories, categories);
|
|
252
|
+
this.identityState.consentState = {
|
|
253
|
+
granted: nextCategories.analytics || nextCategories.marketing || nextCategories.preferences,
|
|
254
|
+
timestamp: Date.now(),
|
|
255
|
+
categories: nextCategories,
|
|
256
|
+
};
|
|
257
|
+
void this.persistIdentityState();
|
|
258
|
+
void this.syncConsent('granted');
|
|
259
|
+
}
|
|
260
|
+
revokeConsent(categories) {
|
|
261
|
+
if (!this.initialized || !this.identityState) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const base = this.identityState.consentState || this.defaultConsentState();
|
|
265
|
+
const nextCategories = {
|
|
266
|
+
...base.categories,
|
|
267
|
+
necessary: true,
|
|
268
|
+
analytics: categories ? base.categories.analytics : false,
|
|
269
|
+
marketing: categories ? base.categories.marketing : false,
|
|
270
|
+
preferences: categories ? base.categories.preferences : false,
|
|
271
|
+
};
|
|
272
|
+
if (categories && categories.length > 0) {
|
|
273
|
+
for (const category of categories) {
|
|
274
|
+
if (category !== 'necessary') {
|
|
275
|
+
nextCategories[category] = false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.identityState.consentState = {
|
|
280
|
+
granted: nextCategories.analytics || nextCategories.marketing || nextCategories.preferences,
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
categories: nextCategories,
|
|
283
|
+
};
|
|
284
|
+
void this.persistIdentityState();
|
|
285
|
+
void this.syncConsent('revoked');
|
|
286
|
+
}
|
|
287
|
+
async flush() {
|
|
288
|
+
const runtimeConfig = this.runtimeConfig;
|
|
289
|
+
if (!this.initialized || !this.transport || !this.identityState || !runtimeConfig || this.isFlushing) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.isFlushing = true;
|
|
293
|
+
try {
|
|
294
|
+
while (true) {
|
|
295
|
+
const now = Date.now();
|
|
296
|
+
const readyEvents = this.queue
|
|
297
|
+
.filter((event) => event.nextAttemptAt <= now)
|
|
298
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
299
|
+
.slice(0, runtimeConfig.batchSize);
|
|
300
|
+
if (readyEvents.length === 0) {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const journeyId = this.ensureJourney();
|
|
305
|
+
await this.transport.request(`/public/sites/${runtimeConfig.siteKey}/events/batch`, {
|
|
306
|
+
method: 'POST',
|
|
307
|
+
body: {
|
|
308
|
+
sessionId: this.identityState.sessionId,
|
|
309
|
+
visitorId: this.identityState.visitorId,
|
|
310
|
+
siteUserId: this.identityState.siteUserId,
|
|
311
|
+
journeyId,
|
|
312
|
+
pageUrl: this.identityState.screenContext?.path,
|
|
313
|
+
referrer: this.identityState.screenContext?.referrer,
|
|
314
|
+
events: readyEvents.map((event) => ({
|
|
315
|
+
eventId: event.eventId,
|
|
316
|
+
eventSchemaVersion: event.eventSchemaVersion,
|
|
317
|
+
eventTs: event.eventTs,
|
|
318
|
+
name: event.name,
|
|
319
|
+
entityType: event.entityType,
|
|
320
|
+
entityId: event.entityId,
|
|
321
|
+
metadata: event.metadata,
|
|
322
|
+
})),
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
const flushedIds = new Set(readyEvents.map((event) => event.eventId));
|
|
326
|
+
this.queue = this.queue.filter((event) => !flushedIds.has(event.eventId));
|
|
327
|
+
await Promise.all([this.persistQueue(), this.persistIdentityState()]);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
const failedIds = new Set(readyEvents.map((event) => event.eventId));
|
|
331
|
+
this.queue = this.queue.map((event) => {
|
|
332
|
+
if (!failedIds.has(event.eventId)) {
|
|
333
|
+
return event;
|
|
334
|
+
}
|
|
335
|
+
const nextRetryCount = event.retryCount + 1;
|
|
336
|
+
if (nextRetryCount > runtimeConfig.maxRetries) {
|
|
337
|
+
return {
|
|
338
|
+
...event,
|
|
339
|
+
retryCount: nextRetryCount,
|
|
340
|
+
nextAttemptAt: Number.POSITIVE_INFINITY,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const backoffMs = Math.min(1000 * 2 ** nextRetryCount, 60000);
|
|
344
|
+
return {
|
|
345
|
+
...event,
|
|
346
|
+
retryCount: nextRetryCount,
|
|
347
|
+
nextAttemptAt: Date.now() + backoffMs,
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
this.queue = this.queue.filter((event) => event.retryCount <= runtimeConfig.maxRetries);
|
|
351
|
+
await this.persistQueue();
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
this.isFlushing = false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
setScreenContext(context) {
|
|
361
|
+
if (!this.initialized || !this.identityState) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
this.identityState.screenContext = {
|
|
365
|
+
name: context.name,
|
|
366
|
+
path: context.path,
|
|
367
|
+
referrer: context.referrer,
|
|
368
|
+
};
|
|
369
|
+
void this.persistIdentityState();
|
|
370
|
+
}
|
|
371
|
+
async getSiteConfig() {
|
|
372
|
+
const data = await this.requestPublicGet('/config');
|
|
373
|
+
this.captureExperimentAssignments(data);
|
|
374
|
+
return data;
|
|
375
|
+
}
|
|
376
|
+
async getSearchConfig() {
|
|
377
|
+
return this.requestPublicGet('/search-config');
|
|
378
|
+
}
|
|
379
|
+
async search(query, options) {
|
|
380
|
+
return this.requestPublicGet('/search', {
|
|
381
|
+
q: query,
|
|
382
|
+
...options,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
async getSearchSuggestions() {
|
|
386
|
+
return this.requestPublicGet('/search/suggestions');
|
|
387
|
+
}
|
|
388
|
+
async getRecommendationWidgets(query) {
|
|
389
|
+
return this.requestPublicGet('/recommendations', {
|
|
390
|
+
pageUrl: query?.pageUrl || this.identityState?.screenContext?.path,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async getRecommendationProducts(widgetId, context) {
|
|
394
|
+
if (!this.initialized || !this.transport || !this.runtimeConfig || !this.identityState) {
|
|
395
|
+
throw new Error('Selwise is not initialized');
|
|
396
|
+
}
|
|
397
|
+
return this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}/recommendations/${widgetId}/products`, {
|
|
398
|
+
method: 'POST',
|
|
399
|
+
body: {
|
|
400
|
+
currentProductId: context?.currentProductId,
|
|
401
|
+
sessionId: context?.sessionId || this.identityState.sessionId,
|
|
402
|
+
visitorId: context?.visitorId || this.identityState.visitorId,
|
|
403
|
+
userId: context?.userId || this.identityState.siteUserId,
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
normalizeConfig(config) {
|
|
408
|
+
if (!config.siteKey || config.siteKey.trim().length === 0) {
|
|
409
|
+
throw new Error('siteKey is required');
|
|
410
|
+
}
|
|
411
|
+
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
412
|
+
throw new Error('apiKey is required');
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
siteKey: config.siteKey,
|
|
416
|
+
apiKey: config.apiKey,
|
|
417
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
418
|
+
sdkPlatform: config.sdkPlatform || DEFAULT_PLATFORM,
|
|
419
|
+
sdkVersion: config.sdkVersion || DEFAULT_VERSION,
|
|
420
|
+
flushIntervalMs: config.flushIntervalMs || DEFAULT_FLUSH_INTERVAL_MS,
|
|
421
|
+
batchSize: config.batchSize || DEFAULT_BATCH_SIZE,
|
|
422
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
423
|
+
maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
424
|
+
journeyTtlMs: config.journeyTtlMs ?? DEFAULT_JOURNEY_TTL_MS,
|
|
425
|
+
storagePrefix: config.storagePrefix || DEFAULT_STORAGE_PREFIX,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
ensureIdentityState(state) {
|
|
429
|
+
const visitorId = state?.visitorId || (0, utils_1.randomId)('vid');
|
|
430
|
+
const sessionId = state?.sessionId || (0, utils_1.randomId)('sid');
|
|
431
|
+
const nextState = {
|
|
432
|
+
visitorId,
|
|
433
|
+
sessionId,
|
|
434
|
+
journeyId: state?.journeyId || (0, utils_1.createJourneyId)(visitorId),
|
|
435
|
+
journeyCreatedAt: state?.journeyCreatedAt || Date.now(),
|
|
436
|
+
siteUserId: state?.siteUserId,
|
|
437
|
+
dataLayer: Array.isArray(state?.dataLayer) ? state.dataLayer : [],
|
|
438
|
+
consentState: state?.consentState || null,
|
|
439
|
+
screenContext: state?.screenContext || null,
|
|
440
|
+
};
|
|
441
|
+
if (Date.now() - nextState.journeyCreatedAt >=
|
|
442
|
+
(this.runtimeConfig?.journeyTtlMs || DEFAULT_JOURNEY_TTL_MS)) {
|
|
443
|
+
nextState.journeyId = (0, utils_1.createJourneyId)(nextState.visitorId);
|
|
444
|
+
nextState.journeyCreatedAt = Date.now();
|
|
445
|
+
}
|
|
446
|
+
return nextState;
|
|
447
|
+
}
|
|
448
|
+
enqueueEvent(event) {
|
|
449
|
+
if (!this.runtimeConfig) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.queue.push(event);
|
|
453
|
+
if (this.queue.length > this.runtimeConfig.maxQueueSize) {
|
|
454
|
+
this.queue = this.queue.slice(this.queue.length - this.runtimeConfig.maxQueueSize);
|
|
455
|
+
}
|
|
456
|
+
void this.persistQueue();
|
|
457
|
+
if (this.queue.length >= this.runtimeConfig.batchSize) {
|
|
458
|
+
void this.flush();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
ensureJourney() {
|
|
462
|
+
if (!this.identityState || !this.runtimeConfig) {
|
|
463
|
+
return '';
|
|
464
|
+
}
|
|
465
|
+
const age = Date.now() - this.identityState.journeyCreatedAt;
|
|
466
|
+
if (age >= this.runtimeConfig.journeyTtlMs) {
|
|
467
|
+
this.identityState.journeyId = (0, utils_1.createJourneyId)(this.identityState.visitorId);
|
|
468
|
+
this.identityState.journeyCreatedAt = Date.now();
|
|
469
|
+
}
|
|
470
|
+
return this.identityState.journeyId;
|
|
471
|
+
}
|
|
472
|
+
startFlushTimer() {
|
|
473
|
+
if (!this.runtimeConfig) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
this.stopFlushTimer();
|
|
477
|
+
this.flushTimer = setInterval(() => {
|
|
478
|
+
void this.flush();
|
|
479
|
+
}, this.runtimeConfig.flushIntervalMs);
|
|
480
|
+
}
|
|
481
|
+
stopFlushTimer() {
|
|
482
|
+
if (!this.flushTimer) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
clearInterval(this.flushTimer);
|
|
486
|
+
this.flushTimer = null;
|
|
487
|
+
}
|
|
488
|
+
attachAppStateFlush() {
|
|
489
|
+
if (this.appStateSubscription) {
|
|
490
|
+
this.appStateSubscription.remove();
|
|
491
|
+
this.appStateSubscription = null;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
495
|
+
const rn = require('react-native');
|
|
496
|
+
const appState = rn?.AppState;
|
|
497
|
+
if (appState?.addEventListener) {
|
|
498
|
+
this.appStateSubscription = appState.addEventListener('change', (nextState) => {
|
|
499
|
+
if (nextState !== 'active') {
|
|
500
|
+
void this.flush();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// AppState is optional in non-RN test environments.
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async syncConsent(action) {
|
|
510
|
+
if (!this.transport ||
|
|
511
|
+
!this.identityState ||
|
|
512
|
+
!this.runtimeConfig ||
|
|
513
|
+
!this.identityState.consentState) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const body = {
|
|
517
|
+
categories: this.identityState.consentState.categories,
|
|
518
|
+
visitorId: this.identityState.visitorId,
|
|
519
|
+
sessionId: this.identityState.sessionId,
|
|
520
|
+
siteUserId: this.identityState.siteUserId,
|
|
521
|
+
action,
|
|
522
|
+
};
|
|
523
|
+
try {
|
|
524
|
+
if (action === 'granted') {
|
|
525
|
+
await this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}/consent`, {
|
|
526
|
+
method: 'POST',
|
|
527
|
+
body,
|
|
528
|
+
});
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
await this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}/consent`, {
|
|
532
|
+
method: 'DELETE',
|
|
533
|
+
body,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// Best effort; consent state is still persisted locally.
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
mergeConsentCategories(base, override) {
|
|
541
|
+
const raw = override?.categories;
|
|
542
|
+
const flat = (!raw ? override : raw) || {};
|
|
543
|
+
return {
|
|
544
|
+
necessary: true,
|
|
545
|
+
analytics: flat.analytics ?? base.analytics,
|
|
546
|
+
marketing: flat.marketing ?? base.marketing,
|
|
547
|
+
preferences: flat.preferences ?? base.preferences,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
defaultConsentState() {
|
|
551
|
+
return {
|
|
552
|
+
granted: false,
|
|
553
|
+
timestamp: Date.now(),
|
|
554
|
+
categories: {
|
|
555
|
+
necessary: true,
|
|
556
|
+
analytics: false,
|
|
557
|
+
marketing: false,
|
|
558
|
+
preferences: false,
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
getSdkMetadata() {
|
|
563
|
+
if (!this.runtimeConfig) {
|
|
564
|
+
return {};
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
sdkPlatform: this.runtimeConfig.sdkPlatform,
|
|
568
|
+
sdkVersion: this.runtimeConfig.sdkVersion,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
captureExperimentAssignments(data) {
|
|
572
|
+
const assignments = data.experiments;
|
|
573
|
+
if (!Array.isArray(assignments)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
this.activeExperiments.clear();
|
|
577
|
+
for (const assignment of assignments) {
|
|
578
|
+
if (!assignment || typeof assignment !== 'object') {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const record = assignment;
|
|
582
|
+
const experimentId = typeof record.experimentId === 'string'
|
|
583
|
+
? record.experimentId
|
|
584
|
+
: typeof record.id === 'string'
|
|
585
|
+
? record.id
|
|
586
|
+
: undefined;
|
|
587
|
+
if (!experimentId) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
this.activeExperiments.set(experimentId, assignment);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async requestPublicGet(suffix, query) {
|
|
594
|
+
if (!this.initialized || !this.transport || !this.runtimeConfig) {
|
|
595
|
+
throw new Error('Selwise is not initialized');
|
|
596
|
+
}
|
|
597
|
+
return this.transport.request(`/public/sites/${this.runtimeConfig.siteKey}${suffix}`, {
|
|
598
|
+
method: 'GET',
|
|
599
|
+
query,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
async loadIdentityState() {
|
|
603
|
+
if (!this.storage || !this.stateStorageKey) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
const value = await this.storage.getItem(this.stateStorageKey);
|
|
608
|
+
if (!value) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
const parsed = JSON.parse(value);
|
|
612
|
+
return parsed;
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async loadQueue() {
|
|
619
|
+
if (!this.storage || !this.queueStorageKey) {
|
|
620
|
+
return [];
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const value = await this.storage.getItem(this.queueStorageKey);
|
|
624
|
+
if (!value) {
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
627
|
+
const parsed = JSON.parse(value);
|
|
628
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
async persistIdentityState() {
|
|
635
|
+
if (!this.storage || !this.stateStorageKey || !this.identityState) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
await this.storage.setItem(this.stateStorageKey, JSON.stringify(this.identityState));
|
|
639
|
+
}
|
|
640
|
+
async persistQueue() {
|
|
641
|
+
if (!this.storage || !this.queueStorageKey) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
await this.storage.setItem(this.queueStorageKey, JSON.stringify(this.queue));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
exports.Selwise = Selwise;
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createInMemoryStorage = createInMemoryStorage;
|
|
4
|
+
exports.resolveAsyncStorage = resolveAsyncStorage;
|
|
5
|
+
class InMemoryStorage {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.store = new Map();
|
|
8
|
+
}
|
|
9
|
+
async getItem(key) {
|
|
10
|
+
return this.store.has(key) ? this.store.get(key) : null;
|
|
11
|
+
}
|
|
12
|
+
async setItem(key, value) {
|
|
13
|
+
this.store.set(key, value);
|
|
14
|
+
}
|
|
15
|
+
async removeItem(key) {
|
|
16
|
+
this.store.delete(key);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function createInMemoryStorage() {
|
|
20
|
+
return new InMemoryStorage();
|
|
21
|
+
}
|
|
22
|
+
function resolveAsyncStorage(providedStorage) {
|
|
23
|
+
if (providedStorage) {
|
|
24
|
+
return providedStorage;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
28
|
+
const moduleValue = require('@react-native-async-storage/async-storage');
|
|
29
|
+
const candidate = moduleValue?.default || moduleValue;
|
|
30
|
+
if (candidate &&
|
|
31
|
+
typeof candidate.getItem === 'function' &&
|
|
32
|
+
typeof candidate.setItem === 'function' &&
|
|
33
|
+
typeof candidate.removeItem === 'function') {
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Ignore and fallback to in-memory storage.
|
|
39
|
+
}
|
|
40
|
+
return createInMemoryStorage();
|
|
41
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface TransportRequestOptions {
|
|
2
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
3
|
+
query?: Record<string, unknown>;
|
|
4
|
+
body?: unknown;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare class SelwiseTransport {
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
private readonly siteKey;
|
|
10
|
+
private readonly apiKey;
|
|
11
|
+
private readonly sdkPlatform;
|
|
12
|
+
private readonly sdkVersion;
|
|
13
|
+
constructor(args: {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
siteKey: string;
|
|
16
|
+
apiKey: string;
|
|
17
|
+
sdkPlatform: string;
|
|
18
|
+
sdkVersion: string;
|
|
19
|
+
});
|
|
20
|
+
getSiteKey(): string;
|
|
21
|
+
request<T = unknown>(path: string, options?: TransportRequestOptions): Promise<T>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SelwiseTransport = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
class SelwiseTransport {
|
|
6
|
+
constructor(args) {
|
|
7
|
+
this.baseUrl = (0, utils_1.stripTrailingSlash)(args.baseUrl);
|
|
8
|
+
this.siteKey = args.siteKey;
|
|
9
|
+
this.apiKey = args.apiKey;
|
|
10
|
+
this.sdkPlatform = args.sdkPlatform;
|
|
11
|
+
this.sdkVersion = args.sdkVersion;
|
|
12
|
+
}
|
|
13
|
+
getSiteKey() {
|
|
14
|
+
return this.siteKey;
|
|
15
|
+
}
|
|
16
|
+
async request(path, options = {}) {
|
|
17
|
+
if (typeof fetch !== 'function') {
|
|
18
|
+
throw new Error('Global fetch is required for @selwise/react-native');
|
|
19
|
+
}
|
|
20
|
+
const url = `${this.baseUrl}${path}${(0, utils_1.toQueryString)(options.query || {})}`;
|
|
21
|
+
const method = options.method || 'GET';
|
|
22
|
+
const response = await fetch(url, {
|
|
23
|
+
method,
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'x-selwise-api-key': this.apiKey,
|
|
27
|
+
'x-selwise-client-platform': this.sdkPlatform,
|
|
28
|
+
'x-selwise-client-version': this.sdkVersion,
|
|
29
|
+
...(options.headers || {}),
|
|
30
|
+
},
|
|
31
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
32
|
+
});
|
|
33
|
+
if (response.status === 204) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const text = await response.text();
|
|
37
|
+
const payload = text ? JSON.parse(text) : {};
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const error = new Error(`Request failed: ${response.status}`);
|
|
40
|
+
error.status = response.status;
|
|
41
|
+
error.payload = payload;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
return payload;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.SelwiseTransport = SelwiseTransport;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export interface AsyncStorageLike {
|
|
2
|
+
getItem(key: string): Promise<string | null>;
|
|
3
|
+
setItem(key: string, value: string): Promise<void>;
|
|
4
|
+
removeItem(key: string): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface SelwiseDataLayer {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface ConsentCategories {
|
|
10
|
+
necessary: boolean;
|
|
11
|
+
analytics: boolean;
|
|
12
|
+
marketing: boolean;
|
|
13
|
+
preferences: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface ConsentState {
|
|
16
|
+
granted: boolean;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
categories: ConsentCategories;
|
|
19
|
+
}
|
|
20
|
+
export interface ScreenContext {
|
|
21
|
+
name: string;
|
|
22
|
+
path: string;
|
|
23
|
+
referrer?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface TrackArgs {
|
|
26
|
+
entityType?: string;
|
|
27
|
+
entityId?: string;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface TrackOrderItem {
|
|
31
|
+
productItemCode?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
quantity?: number;
|
|
34
|
+
unitPrice?: number;
|
|
35
|
+
totalPrice?: number;
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export interface TrackOrderInput {
|
|
39
|
+
orderId: string;
|
|
40
|
+
currency?: string;
|
|
41
|
+
total?: number;
|
|
42
|
+
subtotal?: number;
|
|
43
|
+
discountTotal?: number;
|
|
44
|
+
shippingTotal?: number;
|
|
45
|
+
taxTotal?: number;
|
|
46
|
+
status?: string;
|
|
47
|
+
placedAt?: string;
|
|
48
|
+
visitorId?: string;
|
|
49
|
+
siteUserId?: string;
|
|
50
|
+
metadata?: Record<string, unknown>;
|
|
51
|
+
items?: TrackOrderItem[];
|
|
52
|
+
}
|
|
53
|
+
export interface SearchOptions {
|
|
54
|
+
limit?: number;
|
|
55
|
+
minPrice?: number;
|
|
56
|
+
maxPrice?: number;
|
|
57
|
+
inStock?: boolean;
|
|
58
|
+
category?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface RecommendationWidgetQuery {
|
|
61
|
+
pageUrl?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface RecommendationProductsContext {
|
|
64
|
+
currentProductId?: string;
|
|
65
|
+
sessionId?: string;
|
|
66
|
+
visitorId?: string;
|
|
67
|
+
userId?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface SelwiseReactNativeConfig {
|
|
70
|
+
siteKey: string;
|
|
71
|
+
apiKey: string;
|
|
72
|
+
apiUrl?: string;
|
|
73
|
+
sdkPlatform?: string;
|
|
74
|
+
sdkVersion?: string;
|
|
75
|
+
flushIntervalMs?: number;
|
|
76
|
+
batchSize?: number;
|
|
77
|
+
maxRetries?: number;
|
|
78
|
+
maxQueueSize?: number;
|
|
79
|
+
journeyTtlMs?: number;
|
|
80
|
+
storagePrefix?: string;
|
|
81
|
+
storage?: AsyncStorageLike;
|
|
82
|
+
}
|
|
83
|
+
export interface IdentityState {
|
|
84
|
+
visitorId: string;
|
|
85
|
+
sessionId: string;
|
|
86
|
+
journeyId: string;
|
|
87
|
+
journeyCreatedAt: number;
|
|
88
|
+
siteUserId?: string;
|
|
89
|
+
dataLayer: SelwiseDataLayer[];
|
|
90
|
+
consentState: ConsentState | null;
|
|
91
|
+
screenContext: ScreenContext | null;
|
|
92
|
+
}
|
|
93
|
+
export interface QueuedEvent {
|
|
94
|
+
eventId: string;
|
|
95
|
+
eventSchemaVersion: number;
|
|
96
|
+
eventTs: number;
|
|
97
|
+
timestamp: number;
|
|
98
|
+
name: string;
|
|
99
|
+
entityType: string;
|
|
100
|
+
entityId?: string;
|
|
101
|
+
metadata?: Record<string, unknown>;
|
|
102
|
+
retryCount: number;
|
|
103
|
+
nextAttemptAt: number;
|
|
104
|
+
}
|
|
105
|
+
export interface IdentifyResult {
|
|
106
|
+
success: boolean;
|
|
107
|
+
merged?: boolean;
|
|
108
|
+
}
|
|
109
|
+
export interface JourneyState {
|
|
110
|
+
journeyId: string | null;
|
|
111
|
+
age: number | null;
|
|
112
|
+
isValid: boolean;
|
|
113
|
+
}
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function sanitizeMetadata(metadata?: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
2
|
+
export declare function stripTrailingSlash(url: string): string;
|
|
3
|
+
export declare function randomId(prefix: string): string;
|
|
4
|
+
export declare function createJourneyId(visitorId: string): string;
|
|
5
|
+
export declare function createEventId(): string;
|
|
6
|
+
export declare function toQueryString(params: Record<string, unknown>): string;
|
|
7
|
+
export declare function delay(ms: number): Promise<void>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeMetadata = sanitizeMetadata;
|
|
4
|
+
exports.stripTrailingSlash = stripTrailingSlash;
|
|
5
|
+
exports.randomId = randomId;
|
|
6
|
+
exports.createJourneyId = createJourneyId;
|
|
7
|
+
exports.createEventId = createEventId;
|
|
8
|
+
exports.toQueryString = toQueryString;
|
|
9
|
+
exports.delay = delay;
|
|
10
|
+
const DEPRECATED_METADATA_FIELDS = new Set(['productId', 'productSku', 'sku']);
|
|
11
|
+
function sanitizeMetadata(metadata) {
|
|
12
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
13
|
+
return metadata;
|
|
14
|
+
}
|
|
15
|
+
const sanitized = {};
|
|
16
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
17
|
+
if (DEPRECATED_METADATA_FIELDS.has(key)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
sanitized[key] = value;
|
|
21
|
+
}
|
|
22
|
+
return sanitized;
|
|
23
|
+
}
|
|
24
|
+
function stripTrailingSlash(url) {
|
|
25
|
+
return url.replace(/\/+$/, '');
|
|
26
|
+
}
|
|
27
|
+
function randomId(prefix) {
|
|
28
|
+
const cryptoApi = globalThis.crypto;
|
|
29
|
+
if (cryptoApi && typeof cryptoApi.randomUUID === 'function') {
|
|
30
|
+
return `${prefix}_${cryptoApi.randomUUID()}`;
|
|
31
|
+
}
|
|
32
|
+
const randomChunk = Math.random().toString(36).slice(2);
|
|
33
|
+
return `${prefix}_${Date.now().toString(36)}${randomChunk}`;
|
|
34
|
+
}
|
|
35
|
+
function createJourneyId(visitorId) {
|
|
36
|
+
return `journey_${visitorId}_${Date.now()}`;
|
|
37
|
+
}
|
|
38
|
+
function createEventId() {
|
|
39
|
+
return randomId('evt');
|
|
40
|
+
}
|
|
41
|
+
function toQueryString(params) {
|
|
42
|
+
const query = new URLSearchParams();
|
|
43
|
+
for (const [key, value] of Object.entries(params)) {
|
|
44
|
+
if (value === undefined || value === null) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
query.append(key, String(value));
|
|
48
|
+
}
|
|
49
|
+
const serialized = query.toString();
|
|
50
|
+
return serialized.length > 0 ? `?${serialized}` : '';
|
|
51
|
+
}
|
|
52
|
+
async function delay(ms) {
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
54
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@selwise/react-native",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Selwise headless React Native SDK",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"clean": "rm -rf dist",
|
|
15
|
+
"build": "pnpm run clean && tsc -p tsconfig.json",
|
|
16
|
+
"test": "jest"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@react-native-async-storage/async-storage": ">=1.0.0",
|
|
20
|
+
"react-native": ">=0.70.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependenciesMeta": {
|
|
23
|
+
"@react-native-async-storage/async-storage": {
|
|
24
|
+
"optional": true
|
|
25
|
+
},
|
|
26
|
+
"react-native": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/jest": "^29.5.11",
|
|
32
|
+
"@types/node": "^20.10.0",
|
|
33
|
+
"jest": "^29.7.0",
|
|
34
|
+
"ts-jest": "^29.4.6",
|
|
35
|
+
"typescript": "^5.3.3"
|
|
36
|
+
}
|
|
37
|
+
}
|