@cshah18/sdk 3.0.5 → 4.1.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.
@@ -4,9 +4,9 @@ import { ApiClient } from "./api-client";
4
4
  */
5
5
  export interface AnalyticsEvent {
6
6
  event: string;
7
- productId: string;
8
7
  timestamp: string;
9
- sessionId: string;
8
+ productId?: string;
9
+ sessionId?: string;
10
10
  context?: {
11
11
  pageUrl?: string;
12
12
  userAgent?: string;
@@ -31,6 +31,10 @@ export declare class AnalyticsClient {
31
31
  * Send event to backend analytics endpoint using unified ApiClient
32
32
  */
33
33
  private sendEvent;
34
+ /**
35
+ * Track page view event
36
+ */
37
+ trackPageView(): Promise<void>;
34
38
  /**
35
39
  * Track custom event (extensible for future use)
36
40
  */
@@ -0,0 +1,74 @@
1
+ import { InternalConfig, GroupFulfilledEvent, Reward } from "./types";
2
+ export interface GroupRealtimeSnapshot {
3
+ groupId: string;
4
+ productId: string;
5
+ participants: number;
6
+ maxParticipants?: number;
7
+ progressPercent?: number;
8
+ expiresAt?: string;
9
+ reward?: Reward | null;
10
+ status?: string;
11
+ timeLeftSeconds?: number;
12
+ }
13
+ export type GroupChannelEvent = {
14
+ type: "update";
15
+ snapshot: GroupRealtimeSnapshot;
16
+ } | {
17
+ type: "fulfilled";
18
+ snapshot: GroupRealtimeSnapshot;
19
+ };
20
+ export type GroupChannelListener = (event: GroupChannelEvent) => void;
21
+ /**
22
+ * Centralized real-time channel manager for all group-aware UI surfaces.
23
+ * Ensures a single socket connection, one room subscription per group, and
24
+ * shared event dispatch to interested listeners.
25
+ */
26
+ export declare class GroupChannelManager {
27
+ private readonly config;
28
+ private readonly sdkVersion;
29
+ private readonly onFulfilledCallback?;
30
+ private socket;
31
+ private readonly logger;
32
+ private readonly listeners;
33
+ private readonly groupRefCount;
34
+ private readonly groupToProduct;
35
+ private readonly latestByGroup;
36
+ private readonly latestByProduct;
37
+ private isConnecting;
38
+ constructor(config: InternalConfig, sdkVersion: string, onFulfilledCallback?: ((event: GroupFulfilledEvent) => void) | undefined);
39
+ /**
40
+ * Start socket connection if not already connected.
41
+ */
42
+ connect(): void;
43
+ /**
44
+ * Subscribe to realtime updates for a group. Multiple callers are reference-counted.
45
+ */
46
+ subscribeToGroup(groupId: string, productId: string): void;
47
+ /**
48
+ * Unsubscribe from a group when no listeners remain.
49
+ */
50
+ unsubscribeFromGroup(groupId: string): void;
51
+ /**
52
+ * Switch from one group room to another, ensuring only one active subscription.
53
+ */
54
+ switchGroup(oldGroupId: string | null, newGroupId: string, productId: string): void;
55
+ /**
56
+ * Register for realtime events. Returns an unsubscribe function.
57
+ */
58
+ addListener(listener: GroupChannelListener): () => void;
59
+ /**
60
+ * Retrieve the latest snapshot for a product if one exists.
61
+ */
62
+ getLatestSnapshotForProduct(productId: string): GroupRealtimeSnapshot | null;
63
+ /**
64
+ * Disconnect and clear state.
65
+ */
66
+ disconnect(): void;
67
+ private handleGroupUpdate;
68
+ private handleGroupFulfilled;
69
+ private cacheSnapshot;
70
+ private broadcast;
71
+ private normalizeSnapshot;
72
+ private pickString;
73
+ private pickNumber;
74
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Offline Redemption Utilities
3
+ * Helpers for constructing QR URLs, copying codes, and downloading QR images
4
+ */
5
+ import { OfflineRedemption } from "./types";
6
+ /**
7
+ * Build full S3 URL for QR code image
8
+ * @param qrCodeData - S3 key from offline_redemption.qr_code_data
9
+ * @returns Full HTTPS URL to the QR code image
10
+ */
11
+ export declare function buildQRUrl(qrCodeData: string): string;
12
+ /**
13
+ * Copy redemption code to clipboard
14
+ * @param code - Redemption code to copy
15
+ * @returns Promise that resolves when copy is complete
16
+ */
17
+ export declare function copyRedemptionCode(code: string): Promise<boolean>;
18
+ /**
19
+ * Download QR code image
20
+ * @param qrUrl - Full URL to the QR code image
21
+ * @param filename - Name for the downloaded file
22
+ */
23
+ export declare function downloadQRCode(qrUrl: string, filename?: string): Promise<boolean>;
24
+ /**
25
+ * Validate offline redemption data
26
+ * @param redemption - Offline redemption object
27
+ * @returns true if data is valid and complete
28
+ */
29
+ export declare function isValidOfflineRedemption(redemption: unknown): redemption is OfflineRedemption;
30
+ /**
31
+ * Format expiry date for display
32
+ * @param isoDate - ISO 8601 date string
33
+ * @returns Formatted date string (e.g., "Feb 20, 2026 at 10:21 AM")
34
+ */
35
+ export declare function formatExpiryDate(isoDate: string): string;
36
+ /**
37
+ * Check if offline redemption is still valid
38
+ * @param expiryDate - ISO 8601 expiry date string
39
+ * @returns true if redemption has not expired
40
+ */
41
+ export declare function isRedemptionValid(expiryDate: string): boolean;
@@ -11,9 +11,10 @@ export interface SocketHandlers {
11
11
  export declare class SocketManager {
12
12
  private readonly config;
13
13
  private readonly sdkVersion;
14
+ private readonly sessionId;
14
15
  private socket;
15
16
  private readonly logger;
16
- constructor(config: InternalConfig, sdkVersion: string);
17
+ constructor(config: InternalConfig, sdkVersion: string, sessionId: string);
17
18
  private dispatchWindowEvent;
18
19
  private formatPayload;
19
20
  /**
@@ -161,14 +161,58 @@ export interface CheckoutValidationData {
161
161
  reward?: CheckoutValidationReward;
162
162
  message?: string;
163
163
  }
164
+ /**
165
+ * Offline redemption information for fulfilled groups
166
+ */
167
+ export interface OfflineRedemption {
168
+ member_id: string;
169
+ qr_code_value: string;
170
+ redemption_code: string;
171
+ qr_code_data: string;
172
+ offline_expires_at: string;
173
+ }
174
+ /**
175
+ * Group information in fulfillment event
176
+ */
177
+ export interface FulfilledGroup {
178
+ id: string;
179
+ group_name: string;
180
+ description?: string;
181
+ campaign_creative_name?: string;
182
+ campaign_id: string;
183
+ campaign_name: string;
184
+ campaign_status: string;
185
+ participants_count: number;
186
+ max_participants: number;
187
+ status: string;
188
+ expiry_at: string;
189
+ timeLeftSeconds: number;
190
+ }
191
+ /**
192
+ * Frozen reward information in fulfillment event
193
+ */
194
+ export interface FrozenReward {
195
+ reward: Reward;
196
+ group_id: string;
197
+ frozen_at: string;
198
+ product_id: string;
199
+ eligibility?: {
200
+ isEligible: boolean;
201
+ };
202
+ }
164
203
  /**
165
204
  * Socket payload emitted when a group reaches fulfillment
205
+ * Includes all details needed for offline redemption
166
206
  */
167
207
  export interface GroupFulfilledEvent {
168
- groupId: string;
169
- productId: string;
170
- participants: number;
171
- reward: Reward;
208
+ group: FulfilledGroup;
209
+ frozen_reward: FrozenReward;
210
+ product_id: string;
211
+ offline_redemption?: OfflineRedemption;
212
+ groupId?: string;
213
+ productId?: string;
214
+ participants?: number;
215
+ reward?: Reward;
172
216
  }
173
217
  /**
174
218
  * Socket payload emitted when a new group is created
@@ -302,6 +346,10 @@ export interface ModalOptions {
302
346
  timeAgo: string;
303
347
  color: "pink" | "purple" | "blue" | "green" | "orange";
304
348
  }>;
349
+ /**
350
+ * Offline redemption data for fulfilled groups
351
+ */
352
+ offlineRedemption?: OfflineRedemption;
305
353
  /**
306
354
  * Optional callback when modal opens
307
355
  */
@@ -393,6 +441,7 @@ export interface ProductPrimaryGroupData {
393
441
  status: string;
394
442
  expiry_at: string;
395
443
  timeLeftSeconds: number;
444
+ offline_redemption?: OfflineRedemption;
396
445
  }
397
446
  /**
398
447
  * Group join response data
@@ -414,6 +463,7 @@ export interface GroupJoinResponseData {
414
463
  };
415
464
  isPrimary: boolean;
416
465
  product_id: string;
466
+ offline_redemption?: OfflineRedemption;
417
467
  }
418
468
  export type ShareChannel = "whatsapp" | "facebook" | "email" | "sms" | "copy_link" | "tiktok" | "x" | "other";
419
469
  export interface GroupInviteResponseData {
@@ -488,7 +538,6 @@ export interface RenderWidgetOptions {
488
538
  */
489
539
  viewAllLink?: {
490
540
  container?: string | HTMLElement;
491
- text?: string;
492
541
  align?: "left" | "center" | "right";
493
542
  className?: string;
494
543
  linkClassName?: string;
@@ -2,6 +2,7 @@ import { ApiClient } from "../../core/api-client";
2
2
  import { ProductPrimaryGroupData, GroupJoinResponseData } from "../../core/types";
3
3
  export interface GroupListItem {
4
4
  groupId: string;
5
+ name?: string;
5
6
  timeLabel: string;
6
7
  timeLeftSeconds?: number;
7
8
  joined: number;
@@ -28,7 +29,9 @@ export declare class GroupListModal {
28
29
  private currentSessionId;
29
30
  private onGroupJoined;
30
31
  private onViewProgress;
32
+ private socketListenerRegistered;
31
33
  private readonly escapeHandler;
34
+ private readonly handleGroupMemberJoinedEvent;
32
35
  constructor(groups?: GroupListItem[], liveCount?: number, debug?: boolean, apiClient?: ApiClient | null);
33
36
  /** Set callback for when a group is joined successfully */
34
37
  setOnGroupJoined(callback: (joinData: GroupJoinResponseData) => void): void;
@@ -38,6 +41,14 @@ export declare class GroupListModal {
38
41
  setCurrentJoinedGroup(groupId: string): void;
39
42
  /** Check if a group ID exists in the current groups list */
40
43
  hasGroup(groupId: string): boolean;
44
+ /** Subscribe to socket events for real-time group updates */
45
+ private subscribeToSocketEvents;
46
+ /** Unsubscribe from socket events */
47
+ private unsubscribeFromSocketEvents;
48
+ /** Handle group member joined socket event and update groups list */
49
+ private onGroupMemberJoined;
50
+ /** Update the rendered group card with new data */
51
+ private updateGroupCard;
41
52
  open(productId?: string, sessionId?: string, joinedGroupId?: string): Promise<void>;
42
53
  private fetchAndRenderGroups;
43
54
  private formatTimeRemaining;
@@ -1,4 +1,5 @@
1
1
  import { ApiClient } from "../../core/api-client";
2
+ import { OfflineRedemption } from "../../core/types";
2
3
  import { AnalyticsClient } from "../../core/analytics";
3
4
  import { SocketManager } from "../../core/socket";
4
5
  import "./styles/styles.css";
@@ -16,6 +17,7 @@ export interface LobbyModalData {
16
17
  shareMessage?: string;
17
18
  activities?: ActivityItem[];
18
19
  isLocked?: boolean;
20
+ offlineRedemption?: OfflineRedemption;
19
21
  }
20
22
  export interface ActivityItem {
21
23
  emoji: string;
@@ -58,6 +60,18 @@ export declare class LobbyModal {
58
60
  * Default activity items
59
61
  */
60
62
  private getDefaultActivities;
63
+ /**
64
+ * Create entrance animation overlay
65
+ */
66
+ private createEntranceAnimation;
67
+ /**
68
+ * Show entrance animation and auto-hide after duration
69
+ */
70
+ private showEntranceAnimation;
71
+ /**
72
+ * Hide entrance animation with exit effect
73
+ */
74
+ private hideEntranceAnimation;
61
75
  /**
62
76
  * Create the modal DOM structure
63
77
  */
@@ -83,9 +97,29 @@ export declare class LobbyModal {
83
97
  */
84
98
  private createTitleSection;
85
99
  /**
86
- * Create link section
100
+ * Create connected section (subtitle + link + share)
101
+ */
102
+ private createConnectedSection;
103
+ /**
104
+ * Create link section (just the link box and share button, no wrapper)
87
105
  */
88
106
  private createLinkSection;
107
+ /**
108
+ * Create offline redemption section
109
+ */
110
+ private createOfflineRedemptionSection;
111
+ /**
112
+ * Handle copying offline redemption code
113
+ */
114
+ private copyOfflineRedemptionCode;
115
+ /**
116
+ * Handle downloading offline QR code
117
+ */
118
+ private downloadOfflineQR;
119
+ /**
120
+ * Inject styles for offline redemption section
121
+ */
122
+ private injectOfflineRedemptionStyles;
89
123
  /**
90
124
  * Create offer section
91
125
  */
@@ -230,6 +264,10 @@ export declare class LobbyModal {
230
264
  * Show/hide link section based on lock state
231
265
  */
232
266
  private updateLinkVisibility;
267
+ /**
268
+ * Update offline redemption visibility when group is fulfilled
269
+ */
270
+ private updateOfflineRedemptionVisibility;
233
271
  /**
234
272
  * Subscribe to socket events
235
273
  */
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Offline Redemption Modal
3
+ * Displays QR code and redemption code for fulfilled groups
4
+ */
5
+ import { OfflineRedemption } from "../../core/types";
6
+ export declare class OfflineRedemptionModal {
7
+ private modalElement;
8
+ private offlineRedemption;
9
+ private onClose?;
10
+ constructor(offlineRedemption: OfflineRedemption, onClose?: () => void);
11
+ /**
12
+ * Open the modal
13
+ */
14
+ open(): void;
15
+ /**
16
+ * Close the modal
17
+ */
18
+ close(): void;
19
+ /**
20
+ * Create the modal DOM structure
21
+ */
22
+ private createModalStructure;
23
+ /**
24
+ * Handle download QR button click
25
+ */
26
+ private handleDownloadQR;
27
+ /**
28
+ * Handle copy code button click
29
+ */
30
+ private handleCopyCode;
31
+ /**
32
+ * Inject modal styles
33
+ */
34
+ private injectStyles;
35
+ }
@@ -29,6 +29,8 @@ export declare class WidgetRoot {
29
29
  private lastGroupDataRefreshTime;
30
30
  private readonly GROUP_REFRESH_DEBOUNCE;
31
31
  private groupExpiryRefreshTriggered;
32
+ private offlineRedemption;
33
+ private offlineRedemptionModal;
32
34
  constructor(config: InternalConfig, apiClient: ApiClient | null, analyticsClient?: AnalyticsClient | null);
33
35
  /** Subscribe once to backend socket events routed through the host page */
34
36
  private subscribeToSocketEvents;
@@ -104,6 +106,10 @@ export declare class WidgetRoot {
104
106
  * Handle CTA button click with analytics and modal opening
105
107
  */
106
108
  private handleCTAClick;
109
+ /**
110
+ * Open offline redemption modal when user clicks "Redeem In-store"
111
+ */
112
+ private openOfflineRedemptionModal;
107
113
  /**
108
114
  * Emit checkout intent without performing navigation
109
115
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cshah18/sdk",
3
- "version": "3.0.5",
3
+ "version": "4.1.0",
4
4
  "description": "CoBuy Embedded SDK for browser JavaScript integration",
5
5
  "type": "module",
6
6
  "main": "dist/cobuy-sdk.umd.js",
@@ -1,126 +0,0 @@
1
- import { InternalConfig } from "./types";
2
- export interface GroupPayload {
3
- id: string;
4
- group_name: string;
5
- participants_count: number;
6
- max_participants: number;
7
- status: string;
8
- expiry_at: Date | string;
9
- timeLeftSeconds: number;
10
- }
11
- export interface MembershipPayload {
12
- id: string;
13
- session_id: string;
14
- created_at: Date | string;
15
- }
16
- export interface GroupMemberJoinedPayload {
17
- group: GroupPayload;
18
- membership: MembershipPayload;
19
- product_id?: string;
20
- isPrimary?: boolean;
21
- }
22
- export interface GroupCreatedPayload {
23
- group: GroupPayload;
24
- membership: MembershipPayload;
25
- product_id?: string;
26
- }
27
- export interface GroupFulfilledPayload {
28
- group: GroupPayload;
29
- frozen_reward: unknown;
30
- product_id?: string | null;
31
- }
32
- export interface SocketClientConfig {
33
- serverUrl: string;
34
- merchantKey?: string;
35
- sdkVersion?: string;
36
- sessionId?: string;
37
- debug?: boolean;
38
- }
39
- export interface SocketEventHandlers {
40
- onGroupMemberJoined?: (payload: GroupMemberJoinedPayload) => void;
41
- onGroupCreated?: (payload: GroupCreatedPayload) => void;
42
- onGroupFulfilled?: (payload: GroupFulfilledPayload) => void;
43
- onConnect?: () => void;
44
- onDisconnect?: (reason: string) => void;
45
- onError?: (error: Error) => void;
46
- }
47
- /**
48
- * Frontend Socket.IO client for real-time group updates
49
- * Manages connection to the backend /sdk namespace and handles group subscriptions
50
- */
51
- export declare class SocketClient {
52
- private socket;
53
- private logger;
54
- private config;
55
- private handlers;
56
- private currentGroupId;
57
- private currentProductId;
58
- private maxReconnectAttempts;
59
- constructor(config: SocketClientConfig);
60
- /**
61
- * Connect to the Socket.IO server
62
- * @param handlers - Event handlers for socket events
63
- */
64
- connect(handlers: SocketEventHandlers): Promise<void>;
65
- /**
66
- * Register event listeners for group updates
67
- */
68
- private registerEventListeners;
69
- /**
70
- * Subscribe to a specific group's updates
71
- * @param groupId - The group ID to subscribe to
72
- */
73
- subscribeToGroup(groupId: string): void;
74
- /**
75
- * Unsubscribe from a specific group's updates
76
- * @param groupId - The group ID to unsubscribe from
77
- */
78
- unsubscribeFromGroup(groupId: string): void;
79
- /**
80
- * Subscribe to product availability updates (groups being created for the product)
81
- * @param productId - The product ID to watch for
82
- */
83
- subscribeToProductAvailable(productId: string): void;
84
- /**
85
- * Unsubscribe from product availability updates
86
- * @param productId - The product ID to stop watching
87
- */
88
- unsubscribeFromProductAvailable(productId: string): void;
89
- /**
90
- * Check if socket is connected
91
- */
92
- isConnected(): boolean;
93
- /**
94
- * Get current socket ID
95
- */
96
- getSocketId(): string | null;
97
- /**
98
- * Get currently subscribed group ID
99
- */
100
- getCurrentGroupId(): string | null;
101
- /**
102
- * Get currently subscribed product ID
103
- */
104
- getCurrentProductId(): string | null;
105
- /**
106
- * Disconnect from the server
107
- */
108
- disconnect(): void;
109
- /**
110
- * Force reconnection to the server
111
- */
112
- reconnect(): void;
113
- /**
114
- * Update authentication credentials
115
- * @param merchantKey - New merchant key
116
- */
117
- updateAuth(merchantKey: string): void;
118
- }
119
- /**
120
- * Create a socket client from SDK config
121
- * @param config - Internal SDK configuration
122
- * @param sdkVersion - SDK version
123
- * @param sessionId - Session ID from CoBuy instance
124
- * @returns Configured SocketClient instance
125
- */
126
- export declare function createSocketClient(config: InternalConfig, sdkVersion: string, sessionId: string): SocketClient;