@followgate/js 0.13.0 → 0.14.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.
package/dist/index.d.mts CHANGED
@@ -12,6 +12,7 @@ type SocialAction = 'follow' | 'repost' | 'like';
12
12
  interface TwitterConfig {
13
13
  handle?: string;
14
14
  overrideHandle?: string;
15
+ overridePostUrl?: string;
15
16
  tweetId?: string;
16
17
  username?: string;
17
18
  }
@@ -93,6 +94,7 @@ declare class FollowGateClient {
93
94
  private completedActions;
94
95
  private modalElement;
95
96
  private stylesInjected;
97
+ private currentStep;
96
98
  /**
97
99
  * Initialize the SDK
98
100
  */
@@ -121,6 +123,24 @@ declare class FollowGateClient {
121
123
  * 3. config.twitter.handle (legacy fallback, deprecated)
122
124
  */
123
125
  private getTargetHandle;
126
+ /**
127
+ * Get target post URL/ID with priority:
128
+ * 1. config.twitter.overridePostUrl (explicit override)
129
+ * 2. serverConfig.targetPostUrl (from Dashboard)
130
+ * 3. config.twitter.tweetId (legacy fallback, deprecated)
131
+ */
132
+ private getTargetPostUrl;
133
+ /**
134
+ * Extract post ID from URL or return as-is if already an ID
135
+ * Supports: Tweet ID, Twitter/X URLs
136
+ */
137
+ private extractPostId;
138
+ /**
139
+ * Check if repost step should be shown based on:
140
+ * 1. serverConfig.actions includes 'repost'
141
+ * 2. A valid postUrl is available
142
+ */
143
+ private shouldShowRepostStep;
124
144
  private renderUsernameStep;
125
145
  private handleUsernameSubmit;
126
146
  private renderFollowStep;
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ type SocialAction = 'follow' | 'repost' | 'like';
12
12
  interface TwitterConfig {
13
13
  handle?: string;
14
14
  overrideHandle?: string;
15
+ overridePostUrl?: string;
15
16
  tweetId?: string;
16
17
  username?: string;
17
18
  }
@@ -93,6 +94,7 @@ declare class FollowGateClient {
93
94
  private completedActions;
94
95
  private modalElement;
95
96
  private stylesInjected;
97
+ private currentStep;
96
98
  /**
97
99
  * Initialize the SDK
98
100
  */
@@ -121,6 +123,24 @@ declare class FollowGateClient {
121
123
  * 3. config.twitter.handle (legacy fallback, deprecated)
122
124
  */
123
125
  private getTargetHandle;
126
+ /**
127
+ * Get target post URL/ID with priority:
128
+ * 1. config.twitter.overridePostUrl (explicit override)
129
+ * 2. serverConfig.targetPostUrl (from Dashboard)
130
+ * 3. config.twitter.tweetId (legacy fallback, deprecated)
131
+ */
132
+ private getTargetPostUrl;
133
+ /**
134
+ * Extract post ID from URL or return as-is if already an ID
135
+ * Supports: Tweet ID, Twitter/X URLs
136
+ */
137
+ private extractPostId;
138
+ /**
139
+ * Check if repost step should be shown based on:
140
+ * 1. serverConfig.actions includes 'repost'
141
+ * 2. A valid postUrl is available
142
+ */
143
+ private shouldShowRepostStep;
124
144
  private renderUsernameStep;
125
145
  private handleUsernameSubmit;
126
146
  private renderFollowStep;
package/dist/index.js CHANGED
@@ -503,6 +503,7 @@ var FollowGateClient = class {
503
503
  completedActions = [];
504
504
  modalElement = null;
505
505
  stylesInjected = false;
506
+ currentStep = "welcome";
506
507
  /**
507
508
  * Initialize the SDK
508
509
  */
@@ -608,7 +609,9 @@ var FollowGateClient = class {
608
609
  if (this.config.forceShow) {
609
610
  this.clearUnlockStatus();
610
611
  if (this.config.debug) {
611
- console.log("[FollowGate] forceShow enabled - cleared unlock status, showing modal");
612
+ console.log(
613
+ "[FollowGate] forceShow enabled - cleared unlock status, showing modal"
614
+ );
612
615
  }
613
616
  } else {
614
617
  console.warn(
@@ -630,6 +633,7 @@ var FollowGateClient = class {
630
633
  );
631
634
  }
632
635
  }
636
+ this.trackEvent("modal_opened");
633
637
  this.injectStyles();
634
638
  this.createModal();
635
639
  }
@@ -686,6 +690,7 @@ var FollowGateClient = class {
686
690
  document.body.appendChild(backdrop);
687
691
  this.modalElement = backdrop;
688
692
  document.getElementById("fg-close-btn")?.addEventListener("click", () => {
693
+ this.trackEvent("modal_closed", { step: this.currentStep });
689
694
  this.hide(true);
690
695
  });
691
696
  requestAnimationFrame(() => {
@@ -718,11 +723,70 @@ var FollowGateClient = class {
718
723
  }
719
724
  return null;
720
725
  }
726
+ /**
727
+ * Get target post URL/ID with priority:
728
+ * 1. config.twitter.overridePostUrl (explicit override)
729
+ * 2. serverConfig.targetPostUrl (from Dashboard)
730
+ * 3. config.twitter.tweetId (legacy fallback, deprecated)
731
+ */
732
+ getTargetPostUrl() {
733
+ if (this.config?.twitter?.overridePostUrl) {
734
+ return this.extractPostId(this.config.twitter.overridePostUrl);
735
+ }
736
+ if (this.serverConfig?.targetPostUrl) {
737
+ return this.extractPostId(this.serverConfig.targetPostUrl);
738
+ }
739
+ if (this.config?.twitter?.tweetId) {
740
+ return this.config.twitter.tweetId;
741
+ }
742
+ return null;
743
+ }
744
+ /**
745
+ * Extract post ID from URL or return as-is if already an ID
746
+ * Supports: Tweet ID, Twitter/X URLs
747
+ */
748
+ extractPostId(input) {
749
+ if (!input) return null;
750
+ const trimmed = input.trim();
751
+ if (/^\d+$/.test(trimmed)) {
752
+ return trimmed;
753
+ }
754
+ const twitterMatch = trimmed.match(
755
+ /(?:twitter|x)\.com\/\w+\/status\/(\d+)/
756
+ );
757
+ if (twitterMatch) {
758
+ return twitterMatch[1];
759
+ }
760
+ return trimmed;
761
+ }
762
+ /**
763
+ * Check if repost step should be shown based on:
764
+ * 1. serverConfig.actions includes 'repost'
765
+ * 2. A valid postUrl is available
766
+ */
767
+ shouldShowRepostStep() {
768
+ const postUrl = this.getTargetPostUrl();
769
+ const actionsIncludeRepost = this.serverConfig?.actions?.includes("repost");
770
+ if (actionsIncludeRepost && !postUrl) {
771
+ console.warn(
772
+ "[FollowGate] REPOST action configured but no targetPostUrl available.",
773
+ "Set targetPostUrl in Dashboard or use overridePostUrl in config.",
774
+ "Skipping repost step."
775
+ );
776
+ return false;
777
+ }
778
+ if (this.config?.twitter?.tweetId) {
779
+ return true;
780
+ }
781
+ return !!actionsIncludeRepost && !!postUrl;
782
+ }
721
783
  renderUsernameStep() {
722
784
  const content = this.getContentElement();
723
785
  if (!content) return;
786
+ this.currentStep = "welcome";
787
+ this.trackEvent("step_viewed", { step: "welcome" });
724
788
  const handle = this.getTargetHandle();
725
- const hasRepost = !!this.config?.twitter?.tweetId;
789
+ const hasRepost = this.shouldShowRepostStep();
726
790
  const allowSkip = this.serverConfig?.allowSkip ?? false;
727
791
  const welcomeTitle = this.serverConfig?.welcomeTitle || "Unlock Free Access";
728
792
  const welcomeMessage = this.serverConfig?.welcomeMessage || "Enter your X username to get started";
@@ -772,11 +836,14 @@ var FollowGateClient = class {
772
836
  handleUsernameSubmit(username) {
773
837
  const normalized = username.replace(/^@/, "");
774
838
  this.setUsername(normalized);
839
+ this.trackEvent("username_submitted", { username: normalized });
775
840
  this.renderFollowStep();
776
841
  }
777
842
  renderFollowStep() {
778
843
  const content = this.getContentElement();
779
844
  if (!content) return;
845
+ this.currentStep = "follow";
846
+ this.trackEvent("step_viewed", { step: "follow" });
780
847
  const handle = this.getTargetHandle();
781
848
  if (!handle) {
782
849
  console.error(
@@ -784,7 +851,7 @@ var FollowGateClient = class {
784
851
  );
785
852
  return;
786
853
  }
787
- const hasRepost = !!this.config?.twitter?.tweetId;
854
+ const hasRepost = this.shouldShowRepostStep();
788
855
  const defaultTitle = hasRepost ? "Step 1: Follow" : "Follow to Continue";
789
856
  const defaultMessage = `Follow @${handle} on X`;
790
857
  const title = this.serverConfig?.welcomeTitle || defaultTitle;
@@ -819,8 +886,8 @@ var FollowGateClient = class {
819
886
  });
820
887
  }
821
888
  handleSkipFollow() {
822
- if (!this.config?.twitter) return;
823
- if (this.config.twitter.tweetId) {
889
+ this.trackEvent("step_skipped", { step: "follow" });
890
+ if (this.shouldShowRepostStep()) {
824
891
  this.renderRepostStep();
825
892
  } else {
826
893
  this.renderConfirmStep();
@@ -891,7 +958,7 @@ var FollowGateClient = class {
891
958
  action: "follow",
892
959
  target: handle
893
960
  });
894
- if (this.config?.twitter?.tweetId) {
961
+ if (this.shouldShowRepostStep()) {
895
962
  this.renderRepostStep();
896
963
  } else {
897
964
  this.renderConfirmStep();
@@ -899,7 +966,10 @@ var FollowGateClient = class {
899
966
  }
900
967
  renderRepostStep() {
901
968
  const content = this.getContentElement();
902
- if (!content || !this.config?.twitter?.tweetId) return;
969
+ const postId = this.getTargetPostUrl();
970
+ if (!content || !postId) return;
971
+ this.currentStep = "repost";
972
+ this.trackEvent("step_viewed", { step: "repost" });
903
973
  content.innerHTML = `
904
974
  ${this.renderStepIndicator(2)}
905
975
  <div class="fg-icon-box fg-success">
@@ -926,16 +996,17 @@ var FollowGateClient = class {
926
996
  this.handleRepostClick();
927
997
  });
928
998
  document.getElementById("fg-skip-repost")?.addEventListener("click", () => {
999
+ this.trackEvent("step_skipped", { step: "repost" });
929
1000
  this.renderConfirmStep();
930
1001
  });
931
1002
  }
932
1003
  handleRepostClick() {
933
- if (!this.config?.twitter?.tweetId) return;
934
- const tweetId = this.config.twitter.tweetId;
1004
+ const postId = this.getTargetPostUrl();
1005
+ if (!postId) return;
935
1006
  this.openIntent({
936
1007
  platform: "twitter",
937
1008
  action: "repost",
938
- target: tweetId
1009
+ target: postId
939
1010
  });
940
1011
  this.showRepostConfirmation();
941
1012
  }
@@ -985,27 +1056,42 @@ var FollowGateClient = class {
985
1056
  }
986
1057
  }
987
1058
  async handleRepostConfirm() {
988
- if (!this.config?.twitter?.tweetId) return;
1059
+ const postId = this.getTargetPostUrl();
1060
+ if (!postId) return;
989
1061
  await this.complete({
990
1062
  platform: "twitter",
991
1063
  action: "repost",
992
- target: this.config.twitter.tweetId
1064
+ target: postId
993
1065
  });
994
1066
  this.renderConfirmStep();
995
1067
  }
996
1068
  renderConfirmStep() {
997
1069
  const content = this.getContentElement();
998
1070
  if (!content) return;
1071
+ this.currentStep = "confirm";
1072
+ this.trackEvent("step_viewed", { step: "confirm" });
999
1073
  const username = this.currentUser?.username;
1074
+ const targetHandle = this.getTargetHandle();
1075
+ const postId = this.getTargetPostUrl();
1000
1076
  const successTitle = this.serverConfig?.successTitle || "Almost done!";
1001
1077
  const successMessage = this.serverConfig?.successMessage || null;
1078
+ const hasFollow = targetHandle !== null;
1079
+ const hasRepost = postId !== null;
1080
+ let verifyText = "Verifying";
1081
+ if (hasFollow && hasRepost) {
1082
+ verifyText = "Verifying follow & repost";
1083
+ } else if (hasFollow) {
1084
+ verifyText = "Verifying follow";
1085
+ } else if (hasRepost) {
1086
+ verifyText = "Verifying repost";
1087
+ }
1002
1088
  content.innerHTML = `
1003
1089
  <h2 class="fg-title">${this.escapeHtml(successTitle)}</h2>
1004
1090
  ${successMessage ? `<p class="fg-subtitle" style="margin-bottom: 16px;">${this.escapeHtml(successMessage)}</p>` : ""}
1005
1091
  <div class="fg-verify-box">
1006
1092
  <div class="fg-verify-box-left">
1007
1093
  <div class="fg-verify-spinner"></div>
1008
- <span class="fg-verify-text">Verifying follow & repost</span>
1094
+ <span class="fg-verify-text">${verifyText}</span>
1009
1095
  </div>
1010
1096
  ${username ? `
1011
1097
  <div class="fg-user-badge" style="margin: 0; padding: 4px 10px; gap: 6px;">
@@ -1023,13 +1109,15 @@ var FollowGateClient = class {
1023
1109
  ${ICONS.check}
1024
1110
  Got it
1025
1111
  </button>
1026
- ${this.config?.twitter ? `
1112
+ ${hasFollow || hasRepost ? `
1027
1113
  <div class="fg-btn-row">
1028
- <button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
1029
- ${ICONS.x}
1030
- Open follow
1031
- </button>
1032
- ${this.config.twitter.tweetId ? `
1114
+ ${hasFollow ? `
1115
+ <button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
1116
+ ${ICONS.x}
1117
+ Open follow
1118
+ </button>
1119
+ ` : ""}
1120
+ ${hasRepost ? `
1033
1121
  <button class="fg-btn fg-btn-secondary" id="fg-redo-repost">
1034
1122
  ${ICONS.repost}
1035
1123
  Open repost
@@ -1052,11 +1140,12 @@ var FollowGateClient = class {
1052
1140
  }
1053
1141
  });
1054
1142
  document.getElementById("fg-redo-repost")?.addEventListener("click", () => {
1055
- if (this.config?.twitter?.tweetId) {
1143
+ const repostId = this.getTargetPostUrl();
1144
+ if (repostId) {
1056
1145
  this.openIntent({
1057
1146
  platform: "twitter",
1058
1147
  action: "repost",
1059
- target: this.config.twitter.tweetId
1148
+ target: repostId
1060
1149
  });
1061
1150
  }
1062
1151
  });
@@ -1475,8 +1564,37 @@ var FollowGateClient = class {
1475
1564
  throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
1476
1565
  }
1477
1566
  }
1478
- async trackEvent(event, data) {
1567
+ async trackEvent(event, data = {}) {
1479
1568
  if (!this.config) return;
1569
+ const payload = {
1570
+ event
1571
+ };
1572
+ if (data.platform) {
1573
+ payload.platform = data.platform.toUpperCase();
1574
+ }
1575
+ if (data.action) {
1576
+ payload.action = data.action.toUpperCase();
1577
+ }
1578
+ if (data.target) {
1579
+ payload.target = data.target;
1580
+ }
1581
+ if (data.username || this.currentUser?.username) {
1582
+ payload.username = data.username || this.currentUser?.username;
1583
+ }
1584
+ if (data.externalUserId) {
1585
+ payload.externalUserId = data.externalUserId;
1586
+ }
1587
+ const {
1588
+ platform: _p,
1589
+ action: _a,
1590
+ target: _t,
1591
+ username: _u,
1592
+ externalUserId: _e,
1593
+ ...rest
1594
+ } = data;
1595
+ if (Object.keys(rest).length > 0) {
1596
+ payload.metadata = rest;
1597
+ }
1480
1598
  try {
1481
1599
  await fetch(`${this.config.apiUrl}/api/v1/events`, {
1482
1600
  method: "POST",
@@ -1484,11 +1602,7 @@ var FollowGateClient = class {
1484
1602
  "Content-Type": "application/json",
1485
1603
  "X-API-Key": this.config.apiKey
1486
1604
  },
1487
- body: JSON.stringify({
1488
- event,
1489
- appId: this.config.appId,
1490
- ...data
1491
- })
1605
+ body: JSON.stringify(payload)
1492
1606
  });
1493
1607
  } catch (error) {
1494
1608
  if (this.config.debug) {
package/dist/index.mjs CHANGED
@@ -477,6 +477,7 @@ var FollowGateClient = class {
477
477
  completedActions = [];
478
478
  modalElement = null;
479
479
  stylesInjected = false;
480
+ currentStep = "welcome";
480
481
  /**
481
482
  * Initialize the SDK
482
483
  */
@@ -582,7 +583,9 @@ var FollowGateClient = class {
582
583
  if (this.config.forceShow) {
583
584
  this.clearUnlockStatus();
584
585
  if (this.config.debug) {
585
- console.log("[FollowGate] forceShow enabled - cleared unlock status, showing modal");
586
+ console.log(
587
+ "[FollowGate] forceShow enabled - cleared unlock status, showing modal"
588
+ );
586
589
  }
587
590
  } else {
588
591
  console.warn(
@@ -604,6 +607,7 @@ var FollowGateClient = class {
604
607
  );
605
608
  }
606
609
  }
610
+ this.trackEvent("modal_opened");
607
611
  this.injectStyles();
608
612
  this.createModal();
609
613
  }
@@ -660,6 +664,7 @@ var FollowGateClient = class {
660
664
  document.body.appendChild(backdrop);
661
665
  this.modalElement = backdrop;
662
666
  document.getElementById("fg-close-btn")?.addEventListener("click", () => {
667
+ this.trackEvent("modal_closed", { step: this.currentStep });
663
668
  this.hide(true);
664
669
  });
665
670
  requestAnimationFrame(() => {
@@ -692,11 +697,70 @@ var FollowGateClient = class {
692
697
  }
693
698
  return null;
694
699
  }
700
+ /**
701
+ * Get target post URL/ID with priority:
702
+ * 1. config.twitter.overridePostUrl (explicit override)
703
+ * 2. serverConfig.targetPostUrl (from Dashboard)
704
+ * 3. config.twitter.tweetId (legacy fallback, deprecated)
705
+ */
706
+ getTargetPostUrl() {
707
+ if (this.config?.twitter?.overridePostUrl) {
708
+ return this.extractPostId(this.config.twitter.overridePostUrl);
709
+ }
710
+ if (this.serverConfig?.targetPostUrl) {
711
+ return this.extractPostId(this.serverConfig.targetPostUrl);
712
+ }
713
+ if (this.config?.twitter?.tweetId) {
714
+ return this.config.twitter.tweetId;
715
+ }
716
+ return null;
717
+ }
718
+ /**
719
+ * Extract post ID from URL or return as-is if already an ID
720
+ * Supports: Tweet ID, Twitter/X URLs
721
+ */
722
+ extractPostId(input) {
723
+ if (!input) return null;
724
+ const trimmed = input.trim();
725
+ if (/^\d+$/.test(trimmed)) {
726
+ return trimmed;
727
+ }
728
+ const twitterMatch = trimmed.match(
729
+ /(?:twitter|x)\.com\/\w+\/status\/(\d+)/
730
+ );
731
+ if (twitterMatch) {
732
+ return twitterMatch[1];
733
+ }
734
+ return trimmed;
735
+ }
736
+ /**
737
+ * Check if repost step should be shown based on:
738
+ * 1. serverConfig.actions includes 'repost'
739
+ * 2. A valid postUrl is available
740
+ */
741
+ shouldShowRepostStep() {
742
+ const postUrl = this.getTargetPostUrl();
743
+ const actionsIncludeRepost = this.serverConfig?.actions?.includes("repost");
744
+ if (actionsIncludeRepost && !postUrl) {
745
+ console.warn(
746
+ "[FollowGate] REPOST action configured but no targetPostUrl available.",
747
+ "Set targetPostUrl in Dashboard or use overridePostUrl in config.",
748
+ "Skipping repost step."
749
+ );
750
+ return false;
751
+ }
752
+ if (this.config?.twitter?.tweetId) {
753
+ return true;
754
+ }
755
+ return !!actionsIncludeRepost && !!postUrl;
756
+ }
695
757
  renderUsernameStep() {
696
758
  const content = this.getContentElement();
697
759
  if (!content) return;
760
+ this.currentStep = "welcome";
761
+ this.trackEvent("step_viewed", { step: "welcome" });
698
762
  const handle = this.getTargetHandle();
699
- const hasRepost = !!this.config?.twitter?.tweetId;
763
+ const hasRepost = this.shouldShowRepostStep();
700
764
  const allowSkip = this.serverConfig?.allowSkip ?? false;
701
765
  const welcomeTitle = this.serverConfig?.welcomeTitle || "Unlock Free Access";
702
766
  const welcomeMessage = this.serverConfig?.welcomeMessage || "Enter your X username to get started";
@@ -746,11 +810,14 @@ var FollowGateClient = class {
746
810
  handleUsernameSubmit(username) {
747
811
  const normalized = username.replace(/^@/, "");
748
812
  this.setUsername(normalized);
813
+ this.trackEvent("username_submitted", { username: normalized });
749
814
  this.renderFollowStep();
750
815
  }
751
816
  renderFollowStep() {
752
817
  const content = this.getContentElement();
753
818
  if (!content) return;
819
+ this.currentStep = "follow";
820
+ this.trackEvent("step_viewed", { step: "follow" });
754
821
  const handle = this.getTargetHandle();
755
822
  if (!handle) {
756
823
  console.error(
@@ -758,7 +825,7 @@ var FollowGateClient = class {
758
825
  );
759
826
  return;
760
827
  }
761
- const hasRepost = !!this.config?.twitter?.tweetId;
828
+ const hasRepost = this.shouldShowRepostStep();
762
829
  const defaultTitle = hasRepost ? "Step 1: Follow" : "Follow to Continue";
763
830
  const defaultMessage = `Follow @${handle} on X`;
764
831
  const title = this.serverConfig?.welcomeTitle || defaultTitle;
@@ -793,8 +860,8 @@ var FollowGateClient = class {
793
860
  });
794
861
  }
795
862
  handleSkipFollow() {
796
- if (!this.config?.twitter) return;
797
- if (this.config.twitter.tweetId) {
863
+ this.trackEvent("step_skipped", { step: "follow" });
864
+ if (this.shouldShowRepostStep()) {
798
865
  this.renderRepostStep();
799
866
  } else {
800
867
  this.renderConfirmStep();
@@ -865,7 +932,7 @@ var FollowGateClient = class {
865
932
  action: "follow",
866
933
  target: handle
867
934
  });
868
- if (this.config?.twitter?.tweetId) {
935
+ if (this.shouldShowRepostStep()) {
869
936
  this.renderRepostStep();
870
937
  } else {
871
938
  this.renderConfirmStep();
@@ -873,7 +940,10 @@ var FollowGateClient = class {
873
940
  }
874
941
  renderRepostStep() {
875
942
  const content = this.getContentElement();
876
- if (!content || !this.config?.twitter?.tweetId) return;
943
+ const postId = this.getTargetPostUrl();
944
+ if (!content || !postId) return;
945
+ this.currentStep = "repost";
946
+ this.trackEvent("step_viewed", { step: "repost" });
877
947
  content.innerHTML = `
878
948
  ${this.renderStepIndicator(2)}
879
949
  <div class="fg-icon-box fg-success">
@@ -900,16 +970,17 @@ var FollowGateClient = class {
900
970
  this.handleRepostClick();
901
971
  });
902
972
  document.getElementById("fg-skip-repost")?.addEventListener("click", () => {
973
+ this.trackEvent("step_skipped", { step: "repost" });
903
974
  this.renderConfirmStep();
904
975
  });
905
976
  }
906
977
  handleRepostClick() {
907
- if (!this.config?.twitter?.tweetId) return;
908
- const tweetId = this.config.twitter.tweetId;
978
+ const postId = this.getTargetPostUrl();
979
+ if (!postId) return;
909
980
  this.openIntent({
910
981
  platform: "twitter",
911
982
  action: "repost",
912
- target: tweetId
983
+ target: postId
913
984
  });
914
985
  this.showRepostConfirmation();
915
986
  }
@@ -959,27 +1030,42 @@ var FollowGateClient = class {
959
1030
  }
960
1031
  }
961
1032
  async handleRepostConfirm() {
962
- if (!this.config?.twitter?.tweetId) return;
1033
+ const postId = this.getTargetPostUrl();
1034
+ if (!postId) return;
963
1035
  await this.complete({
964
1036
  platform: "twitter",
965
1037
  action: "repost",
966
- target: this.config.twitter.tweetId
1038
+ target: postId
967
1039
  });
968
1040
  this.renderConfirmStep();
969
1041
  }
970
1042
  renderConfirmStep() {
971
1043
  const content = this.getContentElement();
972
1044
  if (!content) return;
1045
+ this.currentStep = "confirm";
1046
+ this.trackEvent("step_viewed", { step: "confirm" });
973
1047
  const username = this.currentUser?.username;
1048
+ const targetHandle = this.getTargetHandle();
1049
+ const postId = this.getTargetPostUrl();
974
1050
  const successTitle = this.serverConfig?.successTitle || "Almost done!";
975
1051
  const successMessage = this.serverConfig?.successMessage || null;
1052
+ const hasFollow = targetHandle !== null;
1053
+ const hasRepost = postId !== null;
1054
+ let verifyText = "Verifying";
1055
+ if (hasFollow && hasRepost) {
1056
+ verifyText = "Verifying follow & repost";
1057
+ } else if (hasFollow) {
1058
+ verifyText = "Verifying follow";
1059
+ } else if (hasRepost) {
1060
+ verifyText = "Verifying repost";
1061
+ }
976
1062
  content.innerHTML = `
977
1063
  <h2 class="fg-title">${this.escapeHtml(successTitle)}</h2>
978
1064
  ${successMessage ? `<p class="fg-subtitle" style="margin-bottom: 16px;">${this.escapeHtml(successMessage)}</p>` : ""}
979
1065
  <div class="fg-verify-box">
980
1066
  <div class="fg-verify-box-left">
981
1067
  <div class="fg-verify-spinner"></div>
982
- <span class="fg-verify-text">Verifying follow & repost</span>
1068
+ <span class="fg-verify-text">${verifyText}</span>
983
1069
  </div>
984
1070
  ${username ? `
985
1071
  <div class="fg-user-badge" style="margin: 0; padding: 4px 10px; gap: 6px;">
@@ -997,13 +1083,15 @@ var FollowGateClient = class {
997
1083
  ${ICONS.check}
998
1084
  Got it
999
1085
  </button>
1000
- ${this.config?.twitter ? `
1086
+ ${hasFollow || hasRepost ? `
1001
1087
  <div class="fg-btn-row">
1002
- <button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
1003
- ${ICONS.x}
1004
- Open follow
1005
- </button>
1006
- ${this.config.twitter.tweetId ? `
1088
+ ${hasFollow ? `
1089
+ <button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
1090
+ ${ICONS.x}
1091
+ Open follow
1092
+ </button>
1093
+ ` : ""}
1094
+ ${hasRepost ? `
1007
1095
  <button class="fg-btn fg-btn-secondary" id="fg-redo-repost">
1008
1096
  ${ICONS.repost}
1009
1097
  Open repost
@@ -1026,11 +1114,12 @@ var FollowGateClient = class {
1026
1114
  }
1027
1115
  });
1028
1116
  document.getElementById("fg-redo-repost")?.addEventListener("click", () => {
1029
- if (this.config?.twitter?.tweetId) {
1117
+ const repostId = this.getTargetPostUrl();
1118
+ if (repostId) {
1030
1119
  this.openIntent({
1031
1120
  platform: "twitter",
1032
1121
  action: "repost",
1033
- target: this.config.twitter.tweetId
1122
+ target: repostId
1034
1123
  });
1035
1124
  }
1036
1125
  });
@@ -1449,8 +1538,37 @@ var FollowGateClient = class {
1449
1538
  throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
1450
1539
  }
1451
1540
  }
1452
- async trackEvent(event, data) {
1541
+ async trackEvent(event, data = {}) {
1453
1542
  if (!this.config) return;
1543
+ const payload = {
1544
+ event
1545
+ };
1546
+ if (data.platform) {
1547
+ payload.platform = data.platform.toUpperCase();
1548
+ }
1549
+ if (data.action) {
1550
+ payload.action = data.action.toUpperCase();
1551
+ }
1552
+ if (data.target) {
1553
+ payload.target = data.target;
1554
+ }
1555
+ if (data.username || this.currentUser?.username) {
1556
+ payload.username = data.username || this.currentUser?.username;
1557
+ }
1558
+ if (data.externalUserId) {
1559
+ payload.externalUserId = data.externalUserId;
1560
+ }
1561
+ const {
1562
+ platform: _p,
1563
+ action: _a,
1564
+ target: _t,
1565
+ username: _u,
1566
+ externalUserId: _e,
1567
+ ...rest
1568
+ } = data;
1569
+ if (Object.keys(rest).length > 0) {
1570
+ payload.metadata = rest;
1571
+ }
1454
1572
  try {
1455
1573
  await fetch(`${this.config.apiUrl}/api/v1/events`, {
1456
1574
  method: "POST",
@@ -1458,11 +1576,7 @@ var FollowGateClient = class {
1458
1576
  "Content-Type": "application/json",
1459
1577
  "X-API-Key": this.config.apiKey
1460
1578
  },
1461
- body: JSON.stringify({
1462
- event,
1463
- appId: this.config.appId,
1464
- ...data
1465
- })
1579
+ body: JSON.stringify(payload)
1466
1580
  });
1467
1581
  } catch (error) {
1468
1582
  if (this.config.debug) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@followgate/js",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "FollowGate SDK - Grow your audience with every download. Require social actions (follow, repost) before users can access your app.",
5
5
  "author": "FollowGate <hello@followgate.app>",
6
6
  "homepage": "https://followgate.app",