@joryio/web-sdk 0.0.0-stage → 1.3.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/CHANGELOG.md +5 -0
- package/LICENSE +54 -0
- package/README.md +497 -2
- package/dist/core/config.d.ts +101 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/device.d.ts +14 -0
- package/dist/core/device.d.ts.map +1 -0
- package/dist/core/ecommerce.d.ts +67 -0
- package/dist/core/ecommerce.d.ts.map +1 -0
- package/dist/core/identity.d.ts +36 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/inapp.d.ts +169 -0
- package/dist/core/inapp.d.ts.map +1 -0
- package/dist/core/network.d.ts +158 -0
- package/dist/core/network.d.ts.map +1 -0
- package/dist/core/outbox.d.ts +79 -0
- package/dist/core/outbox.d.ts.map +1 -0
- package/dist/core/pending-attributes.d.ts +43 -0
- package/dist/core/pending-attributes.d.ts.map +1 -0
- package/dist/core/push.d.ts +59 -0
- package/dist/core/push.d.ts.map +1 -0
- package/dist/core/queue.d.ts +107 -0
- package/dist/core/queue.d.ts.map +1 -0
- package/dist/core/sdk.d.ts +169 -0
- package/dist/core/sdk.d.ts.map +1 -0
- package/dist/core/session.d.ts +36 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/storage.d.ts +17 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/user.d.ts +28 -0
- package/dist/core/user.d.ts.map +1 -0
- package/dist/core/utm.d.ts +39 -0
- package/dist/core/utm.d.ts.map +1 -0
- package/dist/core/values.d.ts +3 -0
- package/dist/core/values.d.ts.map +1 -0
- package/dist/core/write-retry.d.ts +66 -0
- package/dist/core/write-retry.d.ts.map +1 -0
- package/dist/index.d.mts +711 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/joryio-sw.js +6 -0
- package/dist/joryio.cjs.js +7265 -0
- package/dist/joryio.min.js +7 -0
- package/dist/joryio.min.js.map +1 -0
- package/dist/joryio.mjs +7254 -0
- package/dist/joryio.umd.js +7271 -0
- package/dist/utils/bucket-hash.d.ts +2 -0
- package/dist/utils/bucket-hash.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/uuid.d.ts +3 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/package.json +90 -4
- package/src/core/config.ts +362 -0
- package/src/core/device.ts +50 -0
- package/src/core/ecommerce.ts +482 -0
- package/src/core/identity.ts +251 -0
- package/src/core/inapp.ts +2419 -0
- package/src/core/network.ts +1261 -0
- package/src/core/outbox.ts +707 -0
- package/src/core/pending-attributes.ts +350 -0
- package/src/core/push.ts +667 -0
- package/src/core/queue.ts +894 -0
- package/src/core/sdk.ts +3108 -0
- package/src/core/session.ts +311 -0
- package/src/core/storage.ts +157 -0
- package/src/core/user.ts +203 -0
- package/src/core/utm.ts +304 -0
- package/src/core/values.ts +85 -0
- package/src/core/write-retry.ts +136 -0
- package/src/index.ts +305 -0
- package/src/snippet/loader.js +134 -0
- package/src/utils/bucket-hash.ts +44 -0
- package/src/utils/logger.ts +58 -0
- package/src/utils/uuid.ts +29 -0
- package/src/worker/joryio-sw.js +218 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Manager for user identification and attributes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StorageManager } from './storage';
|
|
6
|
+
import { Logger } from '../utils/logger';
|
|
7
|
+
import { generateUUID } from '../utils/uuid';
|
|
8
|
+
import { PendingAttributes } from './pending-attributes';
|
|
9
|
+
|
|
10
|
+
const USER_ID_KEY = 'user_id';
|
|
11
|
+
const ANONYMOUS_ID_KEY = 'anonymous_id';
|
|
12
|
+
const USER_ATTRIBUTES_KEY = 'user_attributes';
|
|
13
|
+
|
|
14
|
+
export interface UserAttributes {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UserIdentity {
|
|
19
|
+
userId: string | null;
|
|
20
|
+
anonymousId: string;
|
|
21
|
+
attributes: UserAttributes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class IdentityManager {
|
|
25
|
+
/**
|
|
26
|
+
* Attribute writes the server has not acknowledged.
|
|
27
|
+
*
|
|
28
|
+
* Lives here because it is identity state, and because both the SDK facade
|
|
29
|
+
* (to flush it) and the in-app sync (to send it) already hold this manager -
|
|
30
|
+
* threading a new dependency through the in-app constructor would have
|
|
31
|
+
* changed a signature with several callers.
|
|
32
|
+
*/
|
|
33
|
+
readonly pending = new PendingAttributes();
|
|
34
|
+
|
|
35
|
+
private storage: StorageManager;
|
|
36
|
+
private logger: Logger;
|
|
37
|
+
private userId: string | null = null;
|
|
38
|
+
private anonymousId: string;
|
|
39
|
+
private attributes: UserAttributes = {};
|
|
40
|
+
|
|
41
|
+
constructor(storage: StorageManager, logger: Logger, userId?: string, anonymousId?: string) {
|
|
42
|
+
this.storage = storage;
|
|
43
|
+
this.logger = logger;
|
|
44
|
+
|
|
45
|
+
// Initialize anonymous ID
|
|
46
|
+
this.anonymousId = anonymousId || this.storage.get<string>(ANONYMOUS_ID_KEY) || generateUUID();
|
|
47
|
+
if (!this.storage.get<string>(ANONYMOUS_ID_KEY)) {
|
|
48
|
+
this.storage.set(ANONYMOUS_ID_KEY, this.anonymousId);
|
|
49
|
+
this.logger.debug(`Generated new anonymous ID: ${this.anonymousId}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Initialize user ID if provided or stored
|
|
53
|
+
this.userId = userId || this.storage.get<string>(USER_ID_KEY) || null;
|
|
54
|
+
if (this.userId) {
|
|
55
|
+
this.logger.debug(`Loaded user ID: ${this.userId}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Attributes are the PII trait bag (email/phone/name/custom). They are
|
|
59
|
+
// NEVER persisted at rest — the server is the source of truth and
|
|
60
|
+
// increment/append/remove are now server-atomic, so no local value is
|
|
61
|
+
// needed for correctness. Start empty each session, kept in memory only
|
|
62
|
+
// for in-session getAttribute read-back.
|
|
63
|
+
this.attributes = {};
|
|
64
|
+
|
|
65
|
+
// One-time purge: delete any attributes bag persisted by an older SDK
|
|
66
|
+
// build so previously-stored PII is removed from existing installs.
|
|
67
|
+
this.storage.remove(USER_ATTRIBUTES_KEY);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Identify a user
|
|
72
|
+
*/
|
|
73
|
+
identify(userId: string, attributes?: UserAttributes): void {
|
|
74
|
+
const previousUserId = this.userId;
|
|
75
|
+
this.userId = userId;
|
|
76
|
+
this.storage.set(USER_ID_KEY, userId);
|
|
77
|
+
|
|
78
|
+
if (attributes) {
|
|
79
|
+
this.setAttributes(attributes);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (previousUserId !== userId) {
|
|
83
|
+
this.logger.info(`User identified: ${userId} (was: ${previousUserId || 'anonymous'})`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reset identity (logout)
|
|
89
|
+
*/
|
|
90
|
+
reset(): void {
|
|
91
|
+
this.logger.debug(`Resetting identity (was: ${this.userId || 'anonymous'})`);
|
|
92
|
+
|
|
93
|
+
this.userId = null;
|
|
94
|
+
this.attributes = {};
|
|
95
|
+
this.anonymousId = generateUUID();
|
|
96
|
+
|
|
97
|
+
this.storage.remove(USER_ID_KEY);
|
|
98
|
+
this.storage.remove(USER_ATTRIBUTES_KEY);
|
|
99
|
+
this.storage.set(ANONYMOUS_ID_KEY, this.anonymousId);
|
|
100
|
+
|
|
101
|
+
this.logger.debug(`New anonymous ID: ${this.anonymousId}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Another tab changed the stored identity (a reset() or identify() there):
|
|
106
|
+
* take it over in memory. Storage already holds it.
|
|
107
|
+
*/
|
|
108
|
+
adopt(anonymousId: string, userId: string | null): void {
|
|
109
|
+
if (anonymousId !== this.anonymousId) this.attributes = {};
|
|
110
|
+
this.anonymousId = anonymousId;
|
|
111
|
+
this.userId = userId;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set user attributes (merge with existing)
|
|
116
|
+
*/
|
|
117
|
+
setAttributes(attributes: UserAttributes): void {
|
|
118
|
+
// Validate that attributes is a plain object
|
|
119
|
+
if (!attributes || typeof attributes !== 'object' || Array.isArray(attributes)) {
|
|
120
|
+
this.logger.error('setAttributes requires a plain object');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// In-memory only — NOT persisted (PII stays off durable storage).
|
|
125
|
+
this.attributes = {
|
|
126
|
+
...this.attributes,
|
|
127
|
+
...attributes,
|
|
128
|
+
};
|
|
129
|
+
this.logger.debug(`Updated user attributes:`, this.attributes);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Set a single attribute
|
|
134
|
+
*/
|
|
135
|
+
setAttribute(key: string, value: any): void {
|
|
136
|
+
// In-memory only — NOT persisted (PII stays off durable storage).
|
|
137
|
+
this.attributes[key] = value;
|
|
138
|
+
this.logger.debug(`Set attribute: ${key} = ${value}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Increment a numeric attribute
|
|
143
|
+
*/
|
|
144
|
+
incrementAttribute(key: string, value: number = 1): void {
|
|
145
|
+
const currentValue = this.attributes[key];
|
|
146
|
+
const numericValue = typeof currentValue === 'number' ? currentValue : 0;
|
|
147
|
+
this.setAttribute(key, numericValue + value);
|
|
148
|
+
this.logger.debug(`Incremented attribute: ${key} by ${value} (now: ${this.attributes[key]})`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Unset (remove) an attribute
|
|
153
|
+
*/
|
|
154
|
+
unsetAttribute(key: string): void {
|
|
155
|
+
// In-memory only — NOT persisted (PII stays off durable storage).
|
|
156
|
+
delete this.attributes[key];
|
|
157
|
+
this.logger.debug(`Unset attribute: ${key}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Add a value to an array attribute
|
|
162
|
+
* If attribute doesn't exist or isn't an array, creates a new array
|
|
163
|
+
*/
|
|
164
|
+
addToArray(key: string, value: any): void {
|
|
165
|
+
const currentValue = this.attributes[key];
|
|
166
|
+
let arrayValue: any[] = [];
|
|
167
|
+
|
|
168
|
+
if (Array.isArray(currentValue)) {
|
|
169
|
+
arrayValue = [...currentValue];
|
|
170
|
+
} else if (currentValue !== undefined && currentValue !== null) {
|
|
171
|
+
this.logger.warn(`Attribute ${key} is not an array, converting to array`);
|
|
172
|
+
arrayValue = [currentValue];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Only add if value doesn't already exist
|
|
176
|
+
if (!arrayValue.includes(value)) {
|
|
177
|
+
arrayValue.push(value);
|
|
178
|
+
this.setAttribute(key, arrayValue);
|
|
179
|
+
this.logger.debug(`Added to array: ${key} = ${value}`);
|
|
180
|
+
} else {
|
|
181
|
+
this.logger.debug(`Value already exists in array: ${key} = ${value}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Remove a value from an array attribute
|
|
187
|
+
* If attribute doesn't exist or isn't an array, does nothing
|
|
188
|
+
*/
|
|
189
|
+
removeFromArray(key: string, value: any): void {
|
|
190
|
+
const currentValue = this.attributes[key];
|
|
191
|
+
|
|
192
|
+
if (!Array.isArray(currentValue)) {
|
|
193
|
+
this.logger.warn(`Attribute ${key} is not an array, cannot remove value`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const newArray = currentValue.filter((item) => item !== value);
|
|
198
|
+
if (newArray.length !== currentValue.length) {
|
|
199
|
+
this.setAttribute(key, newArray);
|
|
200
|
+
this.logger.debug(`Removed from array: ${key} = ${value}`);
|
|
201
|
+
} else {
|
|
202
|
+
this.logger.debug(`Value not found in array: ${key} = ${value}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get current user identity
|
|
208
|
+
*/
|
|
209
|
+
getIdentity(): UserIdentity {
|
|
210
|
+
return {
|
|
211
|
+
userId: this.userId,
|
|
212
|
+
anonymousId: this.anonymousId,
|
|
213
|
+
attributes: { ...this.attributes },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get user ID
|
|
219
|
+
*/
|
|
220
|
+
getUserId(): string | null {
|
|
221
|
+
return this.userId;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get anonymous ID
|
|
226
|
+
*/
|
|
227
|
+
getAnonymousId(): string {
|
|
228
|
+
return this.anonymousId;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get all attributes
|
|
233
|
+
*/
|
|
234
|
+
getAttributes(): UserAttributes {
|
|
235
|
+
return { ...this.attributes };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get a specific attribute
|
|
240
|
+
*/
|
|
241
|
+
getAttribute(key: string): any {
|
|
242
|
+
return this.attributes[key];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if user is identified
|
|
247
|
+
*/
|
|
248
|
+
isIdentified(): boolean {
|
|
249
|
+
return this.userId !== null;
|
|
250
|
+
}
|
|
251
|
+
}
|