@cshah18/sdk 4.14.0 → 4.16.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.
@@ -1,4 +1,4 @@
1
- import { ApiRequestOptions, ApiResponse, ApiClientConfig, ProductRewardData, ProductContextData, ProductPrimaryGroupData, GroupJoinResponseData, GroupInviteResponseData, InviteResolveResponseData, AuthStrategy, ShareChannel, Contact, CheckoutValidationData, CheckoutConfirmData } from "./types";
1
+ import { ApiRequestOptions, ApiResponse, ApiClientConfig, ProductRewardData, ProductContextData, ProductPrimaryGroupData, GroupJoinResponseData, GroupInviteResponseData, InviteResolveResponseData, AuthStrategy, ShareChannel, Contact, OfflineRedemption, LeaveGroupResponseData, RecoverOrJoinGroupResponseData, CheckoutValidationData, CheckoutConfirmData } from "./types";
2
2
  /**
3
3
  * HTTP client for communicating with CoBuy API
4
4
  *
@@ -446,6 +446,28 @@ export declare class ApiClient {
446
446
  * ```
447
447
  */
448
448
  setContact(contact: Contact): Promise<ApiResponse<void>>;
449
+ /**
450
+ * Leave an active group for the current SDK session.
451
+ */
452
+ leaveGroup(groupId: string, params?: {
453
+ leave_source?: "close_icon" | "browser_close" | "unknown";
454
+ leave_reason?: "voluntary_with_contact" | "voluntary_no_contact" | "unknown";
455
+ }): Promise<ApiResponse<LeaveGroupResponseData>>;
456
+ /**
457
+ * Fetch current session member's offline redemption codes for a group.
458
+ */
459
+ getGroupOfflineRedemption(groupId: string): Promise<ApiResponse<{
460
+ offline_redemption?: OfflineRedemption;
461
+ }>>;
462
+ recoverOrJoinGroup(productId: string, contact?: Contact): Promise<ApiResponse<RecoverOrJoinGroupResponseData>>;
463
+ /**
464
+ * Best-effort leave signal for unload/pagehide scenarios.
465
+ * Uses fetch keepalive so it can run while page is closing.
466
+ */
467
+ leaveGroupBestEffort(groupId: string, params?: {
468
+ leave_source?: "close_icon" | "browser_close" | "unknown";
469
+ leave_reason?: "voluntary_with_contact" | "voluntary_no_contact" | "unknown";
470
+ }): void;
449
471
  /**
450
472
  * Prepare checkout for a group
451
473
  *
@@ -5,10 +5,13 @@ export declare const API_ENDPOINTS: {
5
5
  readonly PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context";
6
6
  readonly PRODUCT_REWARD: "/v1/sdk/products/:productId/reward";
7
7
  readonly PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary";
8
+ readonly PRODUCT_RECOVER_OR_JOIN_GROUP: "/v1/sdk/products/:productId/group/recover-or-join";
8
9
  readonly GROUP_JOIN: "/v1/sdk/groups/:groupId/join";
10
+ readonly GROUP_LEAVE: "/v1/sdk/groups/:groupId/leave";
9
11
  readonly GROUP_CREATE_AND_JOIN: "/v1/sdk/groups/new/join";
10
12
  readonly PRODUCT_ACTIVE_GROUPS: "/v1/sdk/products/:productId/groups/active";
11
13
  readonly GROUP_INVITE: "/v1/sdk/groups/:groupId/invite";
14
+ readonly GROUP_OFFLINE_REDEMPTION: "/v1/sdk/groups/:groupId/offline-redemption";
12
15
  readonly GROUP_CHECKOUT_PREPARE: "/v1/sdk/groups/:groupId/checkout/prepare";
13
16
  readonly GROUP_CHECKOUT_VALIDATE: "/v1/sdk/groups/:groupId/checkout/validate";
14
17
  readonly GROUP_CHECKOUT_CONFIRM: "/v1/sdk/groups/:groupId/checkout/confirm";
@@ -1,7 +1,8 @@
1
- import { InternalConfig, GroupFulfilledEvent, GroupCreatedEvent, GroupMemberJoinedEvent } from "./types";
2
- export type SocketEventName = "group:member:joined" | "group:created" | "group:fulfilled";
1
+ import { InternalConfig, GroupFulfilledEvent, GroupCreatedEvent, GroupMemberJoinedEvent, GroupMemberLeftEvent } from "./types";
2
+ export type SocketEventName = "group:member:joined" | "group:member:left" | "group:created" | "group:fulfilled";
3
3
  export interface SocketHandlers {
4
4
  onGroupMemberJoined?: (payload: GroupMemberJoinedEvent) => void;
5
+ onGroupMemberLeft?: (payload: GroupMemberLeftEvent) => void;
5
6
  onGroupCreated?: (payload: GroupCreatedEvent) => void;
6
7
  onGroupFulfilled?: (payload: GroupFulfilledEvent) => void;
7
8
  }
@@ -305,6 +305,7 @@ export interface FulfilledGroup {
305
305
  campaign_id: string;
306
306
  campaign_name: string;
307
307
  campaign_status: string;
308
+ campaign_redemption_method?: "online" | "offline" | "both";
308
309
  participants_count: number;
309
310
  max_participants: number;
310
311
  status: string;
@@ -486,6 +487,10 @@ export interface ModalOptions {
486
487
  * Offline redemption data for fulfilled groups
487
488
  */
488
489
  offlineRedemption?: OfflineRedemption;
490
+ /**
491
+ * Campaign redemption method - controls which checkout options are shown
492
+ */
493
+ redemptionMethod?: "online" | "offline" | "both";
489
494
  /**
490
495
  * Optional callback when modal opens
491
496
  */
@@ -582,6 +587,7 @@ export interface ProductContextCampaignData {
582
587
  reward_value?: string | number | null;
583
588
  start_date?: string | null;
584
589
  end_date?: string | null;
590
+ redemption_method?: "online" | "offline" | "both" | null;
585
591
  }
586
592
  export interface ProductContextData {
587
593
  product_id: string;
@@ -613,6 +619,7 @@ export interface ProductPrimaryGroupData {
613
619
  campaign_id: string | null;
614
620
  campaign_name: string | null;
615
621
  campaign_status: string | null;
622
+ campaign_redemption_method?: "online" | "offline" | "both";
616
623
  participants_count: number;
617
624
  max_participants: number;
618
625
  status: string;
@@ -632,6 +639,7 @@ export interface GroupJoinResponseData {
632
639
  status: string;
633
640
  expiry_at: string;
634
641
  timeLeftSeconds: number;
642
+ campaign_redemption_method?: "online" | "offline" | "both";
635
643
  };
636
644
  membership: {
637
645
  id: string;
@@ -642,6 +650,37 @@ export interface GroupJoinResponseData {
642
650
  product_id: string;
643
651
  offline_redemption?: OfflineRedemption;
644
652
  }
653
+ export interface LeaveGroupResponseData {
654
+ left: boolean;
655
+ session_id: string;
656
+ group: {
657
+ id: string;
658
+ group_name: string;
659
+ description: string | null;
660
+ campaign_creative_id: string | null;
661
+ campaign_creative_name: string | null;
662
+ campaign_id: string | null;
663
+ campaign_name: string | null;
664
+ campaign_status: string | null;
665
+ campaign_redemption_method?: "online" | "offline" | "both";
666
+ participants_count: number;
667
+ max_participants: number;
668
+ status: string;
669
+ expiry_at: string;
670
+ timeLeftSeconds: number;
671
+ };
672
+ product_id?: string;
673
+ }
674
+ export interface RecoverOrJoinGroupResponseData extends GroupJoinResponseData {
675
+ recovery: {
676
+ recovered: boolean;
677
+ matched_by: "session" | "contact" | "none";
678
+ outcome: "existing_active_membership" | "rejoined_previous_group" | "joined_next_available_group" | "created_new_group";
679
+ previous_group_id?: string;
680
+ joined_group_id: string;
681
+ };
682
+ }
683
+ export type GroupMemberLeftEvent = LeaveGroupResponseData;
645
684
  export type ShareChannel = "whatsapp" | "facebook" | "email" | "sms" | "copy_link" | "tiktok" | "x" | "other";
646
685
  export interface GroupInviteResponseData {
647
686
  invite_url?: string;
@@ -1,4 +1,5 @@
1
1
  import { ApiClient } from "../../core/api-client";
2
+ import { AnalyticsClient } from "../../core/analytics";
2
3
  import { OfflineRedemption } from "../../core/types";
3
4
  import { SocketManager } from "../../core/socket";
4
5
  import "./styles/styles.css";
@@ -17,6 +18,7 @@ export interface LobbyModalData {
17
18
  activities?: ActivityItem[];
18
19
  isLocked?: boolean;
19
20
  offlineRedemption?: OfflineRedemption;
21
+ redemptionMethod?: "online" | "offline" | "both";
20
22
  }
21
23
  export interface ActivityItem {
22
24
  emoji: string;
@@ -31,6 +33,9 @@ export interface LobbyModalCallbacks {
31
33
  onCopyLink?: (link: string) => void;
32
34
  onShare?: () => void;
33
35
  }
36
+ interface CloseOptions {
37
+ skipLeaveFlow?: boolean;
38
+ }
34
39
  /**
35
40
  * LobbyModal - Renders and manages the group buying lobby modal
36
41
  */
@@ -38,6 +43,7 @@ export declare class LobbyModal {
38
43
  private readonly logger;
39
44
  private readonly socketManager;
40
45
  private readonly apiClient;
46
+ private readonly analyticsClient;
41
47
  private modalElement;
42
48
  private data;
43
49
  private readonly callbacks;
@@ -48,7 +54,36 @@ export declare class LobbyModal {
48
54
  private currentGroupId;
49
55
  private shareOverlay;
50
56
  private keyboardHandler;
51
- constructor(data: LobbyModalData, callbacks: LobbyModalCallbacks, apiClient: ApiClient | null, socketManager?: SocketManager | null, debug?: boolean);
57
+ private leaveFlowInProgress;
58
+ private leaveSignalSent;
59
+ private hasContactProtection;
60
+ private initialContactPromptTimer;
61
+ private contactPromptOverlay;
62
+ private leaveLoaderOverlay;
63
+ private beforeUnloadHandler;
64
+ private pageHideHandler;
65
+ private visibilityChangeHandler;
66
+ private readonly CONTACT_PROTECTION_PREFIX;
67
+ private readonly LAST_LEFT_GROUP_PREFIX;
68
+ constructor(data: LobbyModalData, callbacks: LobbyModalCallbacks, apiClient: ApiClient | null, socketManager?: SocketManager | null, analyticsClient?: AnalyticsClient | null, debug?: boolean);
69
+ private getSessionId;
70
+ private getContactProtectionStorageKey;
71
+ private getLastLeftGroupStorageKey;
72
+ private readContactProtectionState;
73
+ private persistContactProtectionState;
74
+ private persistLastLeftGroup;
75
+ private clearInitialContactPromptTimer;
76
+ private clearContactPromptOverlay;
77
+ private showBlockingLoader;
78
+ private hideBlockingLoader;
79
+ private updateContactProtectionUI;
80
+ private showToastMessage;
81
+ private trackAnalyticsEvent;
82
+ private saveContactValue;
83
+ private openContactProtectionPrompt;
84
+ private openUnprotectedLeaveWarning;
85
+ private scheduleInitialContactPrompt;
86
+ private leaveGroupExplicitly;
52
87
  /**
53
88
  * Derive lock state from data so UI reflects completion
54
89
  */
@@ -103,6 +138,10 @@ export declare class LobbyModal {
103
138
  * Create link section (just the link box and share button, no wrapper)
104
139
  */
105
140
  private createLinkSection;
141
+ /**
142
+ * Create a standalone "Checkout Online" button for the link/share section
143
+ */
144
+ private createOnlineCheckoutButton;
106
145
  /**
107
146
  * Create offline redemption section
108
147
  */
@@ -251,10 +290,16 @@ export declare class LobbyModal {
251
290
  * Clean up keyboard event listeners
252
291
  */
253
292
  private removeKeyboardAccessibility;
293
+ private registerLifecycleLeaveHandlers;
294
+ private unregisterLifecycleLeaveHandlers;
295
+ private triggerBestEffortLeave;
296
+ private shouldRunLeaveFlow;
297
+ private inferContactType;
298
+ private runLeaveFlowAndClose;
254
299
  /**
255
300
  * Close the modal
256
301
  */
257
- close(): void;
302
+ close(options?: CloseOptions): void;
258
303
  /**
259
304
  * Check if modal is open
260
305
  */
@@ -291,10 +336,16 @@ export declare class LobbyModal {
291
336
  * Subscribe to socket events
292
337
  */
293
338
  private subscribeToSocketEvents;
339
+ private unsubscribeFromSocketEvents;
294
340
  /**
295
341
  * Handle socket group update events
296
342
  */
297
343
  private readonly handleSocketGroupUpdate;
344
+ /**
345
+ * Fetch current user's offline redemption codes when group becomes fulfilled
346
+ * This ensures we show the current user's codes, not someone else's
347
+ */
348
+ private fetchCurrentUserOfflineCodesIfNeeded;
298
349
  /**
299
350
  * Create activity item from socket event data
300
351
  */
@@ -308,3 +359,4 @@ export declare class LobbyModal {
308
359
  */
309
360
  private updateTeamCard;
310
361
  }
362
+ export {};
@@ -30,6 +30,7 @@ export declare class WidgetRoot {
30
30
  private groupExpiryRefreshTriggered;
31
31
  private offlineRedemption;
32
32
  private offlineRedemptionModal;
33
+ private campaignRedemptionMethod;
33
34
  private isRendering;
34
35
  private renderPromise;
35
36
  private liveRegionAnnouncer;
@@ -38,11 +39,16 @@ export declare class WidgetRoot {
38
39
  private renderDebounceTimer;
39
40
  private pendingRenderOptions;
40
41
  private readonly RENDER_DEBOUNCE_MS;
42
+ private readonly LAST_LEFT_GROUP_PREFIX;
41
43
  /**
42
44
  * Get max retries from config (configurable via performance options)
43
45
  */
44
46
  private get MAX_RETRIES();
45
47
  constructor(config: InternalConfig, apiClient: ApiClient | null, analyticsClient?: AnalyticsClient | null);
48
+ private getRecoveryStorageKey;
49
+ private getStoredRecoveryGroupId;
50
+ private clearStoredRecoveryGroupId;
51
+ private showRecoveryToast;
46
52
  /** Subscribe once to backend socket events routed through the host page */
47
53
  private subscribeToSocketEvents;
48
54
  /** Handle backend fulfillment notifications */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cshah18/sdk",
3
- "version": "4.14.0",
3
+ "version": "4.16.0",
4
4
  "description": "CoBuy Embedded SDK for browser JavaScript integration",
5
5
  "type": "module",
6
6
  "main": "dist/cobuy-sdk.umd.js",