@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 +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +141 -27
- package/dist/index.mjs +141 -27
- package/package.json +1 -1
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
823
|
-
if (this.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
934
|
-
|
|
1004
|
+
const postId = this.getTargetPostUrl();
|
|
1005
|
+
if (!postId) return;
|
|
935
1006
|
this.openIntent({
|
|
936
1007
|
platform: "twitter",
|
|
937
1008
|
action: "repost",
|
|
938
|
-
target:
|
|
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
|
-
|
|
1059
|
+
const postId = this.getTargetPostUrl();
|
|
1060
|
+
if (!postId) return;
|
|
989
1061
|
await this.complete({
|
|
990
1062
|
platform: "twitter",
|
|
991
1063
|
action: "repost",
|
|
992
|
-
target:
|
|
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"
|
|
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
|
-
${
|
|
1112
|
+
${hasFollow || hasRepost ? `
|
|
1027
1113
|
<div class="fg-btn-row">
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1143
|
+
const repostId = this.getTargetPostUrl();
|
|
1144
|
+
if (repostId) {
|
|
1056
1145
|
this.openIntent({
|
|
1057
1146
|
platform: "twitter",
|
|
1058
1147
|
action: "repost",
|
|
1059
|
-
target:
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
797
|
-
if (this.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
908
|
-
|
|
978
|
+
const postId = this.getTargetPostUrl();
|
|
979
|
+
if (!postId) return;
|
|
909
980
|
this.openIntent({
|
|
910
981
|
platform: "twitter",
|
|
911
982
|
action: "repost",
|
|
912
|
-
target:
|
|
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
|
-
|
|
1033
|
+
const postId = this.getTargetPostUrl();
|
|
1034
|
+
if (!postId) return;
|
|
963
1035
|
await this.complete({
|
|
964
1036
|
platform: "twitter",
|
|
965
1037
|
action: "repost",
|
|
966
|
-
target:
|
|
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"
|
|
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
|
-
${
|
|
1086
|
+
${hasFollow || hasRepost ? `
|
|
1001
1087
|
<div class="fg-btn-row">
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
1117
|
+
const repostId = this.getTargetPostUrl();
|
|
1118
|
+
if (repostId) {
|
|
1030
1119
|
this.openIntent({
|
|
1031
1120
|
platform: "twitter",
|
|
1032
1121
|
action: "repost",
|
|
1033
|
-
target:
|
|
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.
|
|
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",
|