@joryio/web-sdk 0.0.0-stage → 1.3.0-beta.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +54 -0
  3. package/README.md +497 -2
  4. package/dist/core/config.d.ts +101 -0
  5. package/dist/core/config.d.ts.map +1 -0
  6. package/dist/core/device.d.ts +14 -0
  7. package/dist/core/device.d.ts.map +1 -0
  8. package/dist/core/ecommerce.d.ts +67 -0
  9. package/dist/core/ecommerce.d.ts.map +1 -0
  10. package/dist/core/identity.d.ts +36 -0
  11. package/dist/core/identity.d.ts.map +1 -0
  12. package/dist/core/inapp.d.ts +169 -0
  13. package/dist/core/inapp.d.ts.map +1 -0
  14. package/dist/core/network.d.ts +158 -0
  15. package/dist/core/network.d.ts.map +1 -0
  16. package/dist/core/outbox.d.ts +79 -0
  17. package/dist/core/outbox.d.ts.map +1 -0
  18. package/dist/core/pending-attributes.d.ts +43 -0
  19. package/dist/core/pending-attributes.d.ts.map +1 -0
  20. package/dist/core/push.d.ts +59 -0
  21. package/dist/core/push.d.ts.map +1 -0
  22. package/dist/core/queue.d.ts +107 -0
  23. package/dist/core/queue.d.ts.map +1 -0
  24. package/dist/core/sdk.d.ts +169 -0
  25. package/dist/core/sdk.d.ts.map +1 -0
  26. package/dist/core/session.d.ts +36 -0
  27. package/dist/core/session.d.ts.map +1 -0
  28. package/dist/core/storage.d.ts +17 -0
  29. package/dist/core/storage.d.ts.map +1 -0
  30. package/dist/core/user.d.ts +28 -0
  31. package/dist/core/user.d.ts.map +1 -0
  32. package/dist/core/utm.d.ts +39 -0
  33. package/dist/core/utm.d.ts.map +1 -0
  34. package/dist/core/values.d.ts +3 -0
  35. package/dist/core/values.d.ts.map +1 -0
  36. package/dist/core/write-retry.d.ts +66 -0
  37. package/dist/core/write-retry.d.ts.map +1 -0
  38. package/dist/index.d.mts +711 -0
  39. package/dist/index.d.ts +21 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/joryio-sw.js +6 -0
  42. package/dist/joryio.cjs.js +7265 -0
  43. package/dist/joryio.min.js +7 -0
  44. package/dist/joryio.min.js.map +1 -0
  45. package/dist/joryio.mjs +7254 -0
  46. package/dist/joryio.umd.js +7271 -0
  47. package/dist/utils/bucket-hash.d.ts +2 -0
  48. package/dist/utils/bucket-hash.d.ts.map +1 -0
  49. package/dist/utils/logger.d.ts +14 -0
  50. package/dist/utils/logger.d.ts.map +1 -0
  51. package/dist/utils/uuid.d.ts +3 -0
  52. package/dist/utils/uuid.d.ts.map +1 -0
  53. package/package.json +90 -4
  54. package/src/core/config.ts +362 -0
  55. package/src/core/device.ts +50 -0
  56. package/src/core/ecommerce.ts +482 -0
  57. package/src/core/identity.ts +251 -0
  58. package/src/core/inapp.ts +2419 -0
  59. package/src/core/network.ts +1261 -0
  60. package/src/core/outbox.ts +707 -0
  61. package/src/core/pending-attributes.ts +350 -0
  62. package/src/core/push.ts +667 -0
  63. package/src/core/queue.ts +894 -0
  64. package/src/core/sdk.ts +3108 -0
  65. package/src/core/session.ts +311 -0
  66. package/src/core/storage.ts +157 -0
  67. package/src/core/user.ts +203 -0
  68. package/src/core/utm.ts +304 -0
  69. package/src/core/values.ts +85 -0
  70. package/src/core/write-retry.ts +136 -0
  71. package/src/index.ts +305 -0
  72. package/src/snippet/loader.js +134 -0
  73. package/src/utils/bucket-hash.ts +44 -0
  74. package/src/utils/logger.ts +58 -0
  75. package/src/utils/uuid.ts +29 -0
  76. 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
+ }