@cshah18/sdk 1.0.0 → 2.0.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.
@@ -52,14 +52,63 @@ export interface CoBuyThemeOptions {
52
52
  */
53
53
  animationStyle?: AnimationStyle;
54
54
  }
55
+ /**
56
+ * Authentication mode for SDK initialization
57
+ * - "public": Use merchant key (no-backend sites, read-only endpoints)
58
+ * - "token": Use short-lived session token (backend-integrated sites, full access)
59
+ */
60
+ export type AuthMode = "public" | "token";
61
+ /**
62
+ * Authentication callbacks for token mode
63
+ */
64
+ export interface AuthCallbacks {
65
+ /**
66
+ * Called when authentication fails (401/403)
67
+ * Use this to handle auth errors in your application
68
+ */
69
+ onAuthError?: (error: Error) => void;
70
+ /**
71
+ * Called when token expires or needs refresh
72
+ * Use this to fetch a new token from your backend
73
+ */
74
+ onTokenExpired?: () => void | Promise<void>;
75
+ }
55
76
  /**
56
77
  * Options for initializing the CoBuy SDK
57
78
  */
58
79
  export interface CoBuyInitOptions {
80
+ /**
81
+ * Authentication mode (default: "public")
82
+ * - "public": For no-backend sites (uses merchantKey)
83
+ * - "token": For backend-integrated sites (uses sessionToken)
84
+ */
85
+ authMode?: AuthMode;
59
86
  /**
60
87
  * Unique merchant key provided by CoBuy
88
+ * Required for authMode="public"
89
+ * Not used in authMode="token"
61
90
  */
62
- merchantKey: string;
91
+ merchantKey?: string;
92
+ /**
93
+ * Session token for authenticated requests
94
+ * Required for authMode="token"
95
+ * Can be a string or async function that returns a token
96
+ * Token should be short-lived (5-15 minutes) and fetched from your backend
97
+ *
98
+ * Example:
99
+ * ```ts
100
+ * sessionToken: async () => {
101
+ * const response = await fetch('/api/cobuy-token');
102
+ * const { token } = await response.json();
103
+ * return token;
104
+ * }
105
+ * ```
106
+ */
107
+ sessionToken?: string | (() => string | Promise<string>);
108
+ /**
109
+ * Authentication callbacks for handling errors and token refresh
110
+ */
111
+ auth?: AuthCallbacks;
63
112
  /**
64
113
  * Environment to connect to (default: "production")
65
114
  */
@@ -97,6 +146,44 @@ export interface CheckoutData {
97
146
  groupId: string;
98
147
  productId: string;
99
148
  }
149
+ /**
150
+ * Socket payload emitted when a group reaches fulfillment
151
+ */
152
+ export interface GroupFulfilledEvent {
153
+ groupId: string;
154
+ productId: string;
155
+ participants: number;
156
+ reward: Reward;
157
+ }
158
+ /**
159
+ * Socket payload emitted when a new group is created
160
+ */
161
+ export interface GroupCreatedEvent {
162
+ groupId: string;
163
+ productId: string;
164
+ createdAt: string;
165
+ participantCount?: number;
166
+ maxParticipants?: number;
167
+ groupNumber?: string;
168
+ }
169
+ /**
170
+ * Socket payload emitted when a member joins a group
171
+ */
172
+ export interface GroupMemberJoinedEvent {
173
+ groupId: string;
174
+ productId: string;
175
+ memberId: string;
176
+ memberCount: number;
177
+ maxParticipants?: number;
178
+ timestamp?: string;
179
+ }
180
+ /**
181
+ * Payload emitted when shopper opts to continue to checkout
182
+ */
183
+ export interface CheckoutRequestedEvent {
184
+ groupId: string;
185
+ productId: string;
186
+ }
100
187
  /**
101
188
  * Widget event callbacks for lifecycle events
102
189
  */
@@ -125,6 +212,22 @@ export interface WidgetEvents {
125
212
  * Called when modal closes
126
213
  */
127
214
  onModalClose?: (_productId: string) => void;
215
+ /**
216
+ * Called when the backend notifies that a group has been fulfilled
217
+ */
218
+ onGroupFulfilled?: (_event: GroupFulfilledEvent) => void;
219
+ /**
220
+ * Called when the backend notifies that a group was created
221
+ */
222
+ onGroupCreated?: (_event: GroupCreatedEvent) => void;
223
+ /**
224
+ * Called when the backend notifies that a member joined a group
225
+ */
226
+ onGroupMemberJoined?: (_event: GroupMemberJoinedEvent) => void;
227
+ /**
228
+ * Called when shopper clicks continue to checkout from the fulfilled state
229
+ */
230
+ onCheckoutRequested?: (_event: CheckoutRequestedEvent) => void;
128
231
  }
129
232
  /**
130
233
  * Modal configuration options
@@ -134,10 +237,61 @@ export interface ModalOptions {
134
237
  * Product ID to show in the modal
135
238
  */
136
239
  productId: string;
240
+ /**
241
+ * Group ID for the lobby
242
+ */
243
+ groupId?: string;
137
244
  /**
138
245
  * Optional custom title for the modal
139
246
  */
140
247
  title?: string;
248
+ /**
249
+ * Group number (e.g., "1000")
250
+ */
251
+ groupNumber?: string;
252
+ /**
253
+ * Lobby status
254
+ */
255
+ status?: "active" | "complete";
256
+ /**
257
+ * Group progress percentage (0-100)
258
+ */
259
+ progress?: number;
260
+ /**
261
+ * Current number of members in the group
262
+ */
263
+ currentMembers?: number;
264
+ /**
265
+ * Total members needed for the group
266
+ */
267
+ totalMembers?: number;
268
+ /**
269
+ * Time left in seconds
270
+ */
271
+ timeLeft?: number;
272
+ /**
273
+ * Discount text (e.g., "20% OFF")
274
+ */
275
+ discount?: string;
276
+ /**
277
+ * Group link URL
278
+ */
279
+ groupLink?: string;
280
+ /**
281
+ * Whether the offer is locked
282
+ */
283
+ isLocked?: boolean;
284
+ /**
285
+ * Activity items to display
286
+ */
287
+ activities?: Array<{
288
+ emoji: string;
289
+ username: string;
290
+ location: string;
291
+ action: string;
292
+ timeAgo: string;
293
+ color: "pink" | "purple" | "blue" | "green" | "orange";
294
+ }>;
141
295
  /**
142
296
  * Optional callback when modal opens
143
297
  */
@@ -145,12 +299,38 @@ export interface ModalOptions {
145
299
  /**
146
300
  * Optional callback when modal closes
147
301
  */
148
- onClose?: (_productId: string) => void;
302
+ onClose?: () => void;
303
+ /**
304
+ * Optional callback when user copies the group link
305
+ */
306
+ onCopyLink?: (_link: string) => void;
307
+ /**
308
+ * Optional callback when user shares
309
+ */
310
+ onShare?: () => void;
311
+ }
312
+ /**
313
+ * Contact information for user identification
314
+ * Supports email or phone number as contact types
315
+ * Used for enriching anonymous sessions with user contact data
316
+ */
317
+ export interface Contact {
318
+ /**
319
+ * Type of contact information
320
+ * - "email": User's email address
321
+ * - "phone": User's phone number
322
+ */
323
+ type: "email" | "phone";
324
+ /**
325
+ * The contact value (email address or phone number)
326
+ * Must be a valid email or phone format
327
+ */
328
+ value: string;
149
329
  }
150
330
  /**
151
331
  * Reward type enumeration
152
332
  */
153
- export type RewardType = "percentage" | "fixed" | "points" | "cashback";
333
+ export type RewardType = "percentage" | "fixed" | "points" | "cashback" | "flat";
154
334
  /**
155
335
  * Reward status enumeration
156
336
  */
@@ -185,6 +365,60 @@ export interface ProductRewardData {
185
365
  reward: Reward;
186
366
  eligibility: RewardEligibility;
187
367
  }
368
+ /**
369
+ * Primary group information for a product (SCRUM-284)
370
+ */
371
+ export interface ProductPrimaryGroupData {
372
+ group: ProductPrimaryGroupData | PromiseLike<ProductPrimaryGroupData | null> | null;
373
+ id: string;
374
+ group_name: string;
375
+ description: string | null;
376
+ campaign_creative_id: string | null;
377
+ campaign_creative_name: string | null;
378
+ campaign_id: string | null;
379
+ campaign_name: string | null;
380
+ campaign_status: string | null;
381
+ participants_count: number;
382
+ max_participants: number;
383
+ status: string;
384
+ expiry_at: string;
385
+ timeLeftSeconds: number;
386
+ }
387
+ /**
388
+ * Group join response data
389
+ */
390
+ export interface GroupJoinResponseData {
391
+ group: {
392
+ id: string;
393
+ group_name: string;
394
+ participants_count: number;
395
+ max_participants: number;
396
+ status: string;
397
+ expiry_at: string;
398
+ timeLeftSeconds: number;
399
+ };
400
+ membership: {
401
+ id: string;
402
+ session_id: string;
403
+ created_at: string;
404
+ };
405
+ isPrimary: boolean;
406
+ product_id: string;
407
+ }
408
+ export type ShareChannel = "whatsapp" | "facebook" | "email" | "sms" | "copy_link" | "tiktok" | "x" | "other";
409
+ export interface GroupInviteResponseData {
410
+ invite_url?: string;
411
+ invite_token?: string;
412
+ share_message?: string;
413
+ group?: {
414
+ id: string;
415
+ name?: string;
416
+ current_count?: number;
417
+ slots_remaining?: number;
418
+ expires_at?: string;
419
+ };
420
+ [key: string]: unknown;
421
+ }
188
422
  /**
189
423
  * Options for rendering the CoBuy widget
190
424
  */
@@ -218,6 +452,45 @@ export interface RenderWidgetOptions {
218
452
  * Callback triggered when widget closes
219
453
  */
220
454
  onClose?: () => void;
455
+ /**
456
+ * Optional layout order for widget sections.
457
+ * Controls the rendering order of 'group' (participation info),
458
+ * 'reward' (reward text), and 'cta' (action button).
459
+ * Defaults to ["group", "reward", "cta"].
460
+ */
461
+ layout?: Array<"group" | "reward" | "cta">;
462
+ /**
463
+ * Optional separate container for group participation info.
464
+ * If provided, group info will render here instead of inside the main widget.
465
+ * Can be a selector string or HTMLElement.
466
+ */
467
+ groupContainer?: string | HTMLElement;
468
+ /**
469
+ * Optional separate container for reward text.
470
+ * If provided, reward text will render here instead of inside the main widget.
471
+ * Can be a selector string or HTMLElement.
472
+ */
473
+ rewardContainer?: string | HTMLElement;
474
+ /**
475
+ * Optional customization for the "View all Groups" link.
476
+ * - container: render the link in a separate element (like reward/group containers)
477
+ * - text / classes / styles: adjust copy, font, color, underline, alignment, etc.
478
+ */
479
+ viewAllLink?: {
480
+ container?: string | HTMLElement;
481
+ text?: string;
482
+ align?: "left" | "center" | "right";
483
+ className?: string;
484
+ linkClassName?: string;
485
+ underline?: boolean;
486
+ color?: string;
487
+ fontSize?: string;
488
+ fontWeight?: string | number;
489
+ fontFamily?: string;
490
+ textDecoration?: string;
491
+ style?: Partial<Record<string, string | number>>;
492
+ linkStyle?: Partial<Record<string, string | number>>;
493
+ };
221
494
  }
222
495
  /**
223
496
  * Public CoBuy SDK interface
@@ -231,17 +504,30 @@ export interface CoBuySDK {
231
504
  * Render the CoBuy widget into a DOM container
232
505
  */
233
506
  renderWidget(_options: RenderWidgetOptions): void;
507
+ /**
508
+ * Set or update contact information for the current session
509
+ * Allows merchants to enrich an anonymous session with user contact data
510
+ * @param _contact Contact information (email or phone)
511
+ */
512
+ setContact(_contact: Contact): Promise<void>;
234
513
  /**
235
514
  * SDK version
236
515
  */
237
516
  readonly version: string;
517
+ /**
518
+ * Optional cleanup for long-lived pages (closes sockets, etc.)
519
+ */
520
+ destroy?(): void;
238
521
  }
239
522
  /**
240
523
  * Internal configuration object used throughout the SDK
241
524
  * This extends the public CoBuyInitOptions with computed values
242
525
  */
243
526
  export interface InternalConfig {
244
- merchantKey: string;
527
+ authMode: AuthMode;
528
+ merchantKey?: string;
529
+ sessionToken?: string | (() => string | Promise<string>);
530
+ auth?: AuthCallbacks;
245
531
  env: CoBuyEnvironment;
246
532
  apiBaseUrl: string;
247
533
  theme: CoBuyThemeOptions;
@@ -278,7 +564,17 @@ export interface ApiResponse<T = unknown> {
278
564
  */
279
565
  export interface ApiClientConfig {
280
566
  baseUrl: string;
281
- merchantKey: string;
567
+ authStrategy: AuthStrategy;
568
+ sessionId: string;
282
569
  debug?: boolean;
283
570
  timeout?: number;
284
571
  }
572
+ /**
573
+ * Authentication strategy interface (imported from auth-strategy.ts)
574
+ * Re-exported here for convenience
575
+ */
576
+ export interface AuthStrategy {
577
+ getHeaders(): Record<string, string>;
578
+ refreshIfNeeded(): Promise<boolean>;
579
+ onAuthError(error: Error): void;
580
+ }
@@ -2,6 +2,8 @@ import { CoBuy } from "./core/cobuy";
2
2
  import type { CoBuySDK } from "./core/types";
3
3
  export type { CoBuySDK };
4
4
  export * from "./core/types";
5
+ export type { AuthStrategy } from "./core/auth-strategy";
6
+ export { PublicKeyAuth, TokenAuth } from "./core/auth-strategy";
5
7
  declare const instance: CoBuy;
6
8
  declare global {
7
9
  interface Window {
@@ -0,0 +1,61 @@
1
+ import { ApiClient } from "../../core/api-client";
2
+ import { ProductPrimaryGroupData, GroupJoinResponseData } from "../../core/types";
3
+ export interface GroupListItem {
4
+ groupId: string;
5
+ timeLabel: string;
6
+ timeLeftSeconds?: number;
7
+ joined: number;
8
+ total: number;
9
+ isMember: boolean;
10
+ }
11
+ export interface ApiGroupData extends ProductPrimaryGroupData {
12
+ is_member: boolean;
13
+ }
14
+ /**
15
+ * GroupListModal - Renders the "View all Groups" popup with optional API integration.
16
+ */
17
+ export declare class GroupListModal {
18
+ private overlayEl;
19
+ private logger;
20
+ private groups;
21
+ private liveCount;
22
+ private apiClient;
23
+ private isLoading;
24
+ private startButton;
25
+ private countdownIntervals;
26
+ private currentJoinedGroupId;
27
+ private currentProductId;
28
+ private currentSessionId;
29
+ private onGroupJoined;
30
+ private onViewProgress;
31
+ private readonly escapeHandler;
32
+ constructor(groups?: GroupListItem[], liveCount?: number, debug?: boolean, apiClient?: ApiClient | null);
33
+ /** Set callback for when a group is joined successfully */
34
+ setOnGroupJoined(callback: (joinData: GroupJoinResponseData) => void): void;
35
+ /** Set callback for when viewing progress on an already joined group */
36
+ setOnViewProgress(callback: (groupId: string, groupData: GroupListItem) => void): void;
37
+ /** Set the currently joined group ID to show appropriate button states */
38
+ setCurrentJoinedGroup(groupId: string): void;
39
+ /** Check if a group ID exists in the current groups list */
40
+ hasGroup(groupId: string): boolean;
41
+ open(productId?: string, sessionId?: string, joinedGroupId?: string): Promise<void>;
42
+ private fetchAndRenderGroups;
43
+ private formatTimeRemaining;
44
+ close(): void;
45
+ isOpen(): boolean;
46
+ /** Handle join group button click - calls API and executes callback */
47
+ private handleJoinGroup;
48
+ /** Handle create new group button click - creates and joins a group, then triggers callback */
49
+ private handleCreateNewGroup;
50
+ private buildOverlay;
51
+ private createLoadingSkeleton;
52
+ private createHeader;
53
+ private createLiveBox;
54
+ private createCardsGrid;
55
+ private startCountdown;
56
+ private setJoinButtonsDisabled;
57
+ private updateStartButtonState;
58
+ private createGroupCard;
59
+ private createMemberAvatar;
60
+ private injectStyles;
61
+ }
@@ -0,0 +1,257 @@
1
+ import { ApiClient } from "../../core/api-client";
2
+ import { AnalyticsClient } from "../../core/analytics";
3
+ import { SocketManager } from "../../core/socket";
4
+ import "./styles/styles.css";
5
+ export interface LobbyModalData {
6
+ productId: string;
7
+ groupId?: string;
8
+ groupNumber?: string;
9
+ status?: "active" | "complete";
10
+ progress?: number;
11
+ currentMembers?: number;
12
+ totalMembers?: number;
13
+ timeLeft?: number;
14
+ discount?: string;
15
+ groupLink?: string;
16
+ shareMessage?: string;
17
+ activities?: ActivityItem[];
18
+ isLocked?: boolean;
19
+ }
20
+ export interface ActivityItem {
21
+ emoji: string;
22
+ username: string;
23
+ location: string;
24
+ action: string;
25
+ timeAgo: string;
26
+ color: "pink" | "purple" | "blue" | "green" | "orange";
27
+ }
28
+ export interface LobbyModalCallbacks {
29
+ onClose?: () => void;
30
+ onCopyLink?: (link: string) => void;
31
+ onShare?: () => void;
32
+ }
33
+ /**
34
+ * LobbyModal - Renders and manages the group buying lobby modal
35
+ */
36
+ export declare class LobbyModal {
37
+ private logger;
38
+ private analyticsClient;
39
+ private socketManager;
40
+ private apiClient;
41
+ private modalElement;
42
+ private data;
43
+ private callbacks;
44
+ private timerInterval;
45
+ private activityInterval;
46
+ private activityCards;
47
+ private socketListenerRegistered;
48
+ private currentGroupId;
49
+ private shareOverlay;
50
+ constructor(data: LobbyModalData, callbacks: LobbyModalCallbacks, apiClient: ApiClient | null, analyticsClient: AnalyticsClient | null, socketManager?: SocketManager | null, debug?: boolean);
51
+ /**
52
+ * Derive lock state from data so UI reflects completion
53
+ */
54
+ private computeIsLocked;
55
+ private getRewardText;
56
+ private getTitleText;
57
+ /**
58
+ * Default activity items
59
+ */
60
+ private getDefaultActivities;
61
+ /**
62
+ * Create the modal DOM structure
63
+ */
64
+ private createModalStructure;
65
+ /**
66
+ * Create close icon
67
+ */
68
+ private createCloseIcon;
69
+ /**
70
+ * Create top section
71
+ */
72
+ private createTopSection;
73
+ /**
74
+ * Create left section
75
+ */
76
+ private createLeftSection;
77
+ /**
78
+ * Create status section
79
+ */
80
+ private createStatusSection;
81
+ /**
82
+ * Create title section
83
+ */
84
+ private createTitleSection;
85
+ /**
86
+ * Create link section
87
+ */
88
+ private createLinkSection;
89
+ /**
90
+ * Create offer section
91
+ */
92
+ private createOfferSection;
93
+ /**
94
+ * Create right section
95
+ */
96
+ private createRightSection;
97
+ /**
98
+ * Create group info box
99
+ */
100
+ private createGroupInfoBox;
101
+ /**
102
+ * Create progress card
103
+ */
104
+ private createProgressCard;
105
+ /**
106
+ * Create team card
107
+ */
108
+ private createTeamCard;
109
+ /**
110
+ * Create time card
111
+ */
112
+ private createTimeCard;
113
+ /**
114
+ * Create activity section
115
+ */
116
+ private createActivitySection;
117
+ /**
118
+ * Create activity card
119
+ */
120
+ private createActivityCard;
121
+ /**
122
+ * Format time in HH:MM:SS format
123
+ */
124
+ private formatTime;
125
+ /**
126
+ * Update timer
127
+ */
128
+ private updateTimer;
129
+ /**
130
+ * Start timer
131
+ */
132
+ private startTimer;
133
+ /**
134
+ * Stop timer
135
+ */
136
+ private stopTimer;
137
+ /**
138
+ * Layout activity cards
139
+ */
140
+ private layoutActivityCards;
141
+ /**
142
+ * Shuffle activity cards
143
+ */
144
+ private shuffleActivityCards;
145
+ /**
146
+ * Start activity animation
147
+ */
148
+ private startActivityAnimation;
149
+ /**
150
+ * Stop activity animation
151
+ */
152
+ private stopActivityAnimation;
153
+ /**
154
+ * Record invite/share event and refresh share link/message
155
+ */
156
+ private recordInvite;
157
+ /**
158
+ * Copy link to clipboard
159
+ */
160
+ private copyLink;
161
+ /**
162
+ * Share functionality
163
+ */
164
+ private share;
165
+ /**
166
+ * Create share overlay modal
167
+ */
168
+ private createShareOverlay;
169
+ /**
170
+ * Create a share card
171
+ */
172
+ private createShareCard;
173
+ /**
174
+ * Open share modal
175
+ */
176
+ private openShareModal;
177
+ /**
178
+ * Close share modal
179
+ */
180
+ private closeShareModal;
181
+ /**
182
+ * Copy link from modal
183
+ */
184
+ private copyLinkFromModal;
185
+ /**
186
+ * Share to WhatsApp
187
+ */
188
+ private shareToWhatsApp;
189
+ /**
190
+ * Share to Facebook
191
+ */
192
+ private shareToFacebook;
193
+ /**
194
+ * Share to Twitter (X)
195
+ */
196
+ private shareToTwitter;
197
+ /**
198
+ * Share via SMS
199
+ */
200
+ private shareToSMS;
201
+ /**
202
+ * Open/render the modal
203
+ */
204
+ open(groupId?: string): void;
205
+ /**
206
+ * Close the modal
207
+ */
208
+ close(): void;
209
+ /**
210
+ * Check if modal is open
211
+ */
212
+ isOpen(): boolean;
213
+ /**
214
+ * Update modal data
215
+ */
216
+ updateData(data: Partial<LobbyModalData>): void;
217
+ /**
218
+ * Update title and subtitle dynamically from API/state
219
+ */
220
+ private updateTitleUI;
221
+ /**
222
+ * Update status chip text and styling
223
+ */
224
+ private updateStatusUI;
225
+ /**
226
+ * Update lock status and reward text in the UI
227
+ */
228
+ private updateLockUI;
229
+ /**
230
+ * Show/hide link section based on lock state
231
+ */
232
+ private updateLinkVisibility;
233
+ /**
234
+ * Subscribe to socket events
235
+ */
236
+ private subscribeToSocketEvents;
237
+ /**
238
+ * Unsubscribe from socket events
239
+ */
240
+ private unsubscribeFromSocketEvents;
241
+ /**
242
+ * Handle socket group update events
243
+ */
244
+ private handleSocketGroupUpdate;
245
+ /**
246
+ * Create activity item from socket event data
247
+ */
248
+ private createActivityFromEvent;
249
+ /**
250
+ * Update activity list dynamically from live data
251
+ */
252
+ private updateActivityList;
253
+ /**
254
+ * Update team card members dynamically
255
+ */
256
+ private updateTeamCard;
257
+ }