@followgate/js 0.15.3 → 0.15.5

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/README.md CHANGED
@@ -12,7 +12,7 @@ npm install @followgate/js
12
12
 
13
13
  ## Quick Start (Built-in Modal)
14
14
 
15
- Just 10 lines of code - no custom UI needed:
15
+ Just a few lines of code - no custom UI needed:
16
16
 
17
17
  ```typescript
18
18
  import { FollowGate } from '@followgate/js';
@@ -21,10 +21,8 @@ import { FollowGate } from '@followgate/js';
21
21
  FollowGate.init({
22
22
  appId: 'your-app-id',
23
23
  apiKey: 'fg_live_xxx',
24
- twitter: {
25
- handle: 'your_twitter_handle',
26
- tweetId: '1234567890', // Optional: require repost
27
- },
24
+ userId: user.id, // Recommended for server-side verification
25
+ // Target handle is configured in the Dashboard - no code needed!
28
26
  onComplete: () => {
29
27
  // User completed all actions - redirect to your app
30
28
  router.push('/dashboard');
@@ -39,8 +37,10 @@ The SDK renders a beautiful, fully-functional modal with:
39
37
 
40
38
  - Username input step
41
39
  - Follow action with intent URL
42
- - Repost action (optional)
43
- - Confirmation step
40
+ - Repost action (optional, configured in Dashboard)
41
+ - Confirmation step with custom text support
42
+ - X button to close/cancel (redirects to `cancelUrl` if configured)
43
+ - Dark/light themes
44
44
  - All styling included (no CSS needed)
45
45
 
46
46
  ## When to Show the Modal
@@ -69,10 +69,10 @@ if (!FollowGate.isUnlocked()) {
69
69
  FollowGate.init({
70
70
  appId: 'your-app-id',
71
71
  apiKey: 'fg_live_xxx',
72
- twitter: { handle: 'your_handle' },
72
+ // Handle configured in Dashboard!
73
73
 
74
74
  onComplete: async () => {
75
- // User completed all steps - NOW create the account!
75
+ // User completed all steps - NOW create the account!
76
76
  const user = FollowGate.getUser();
77
77
 
78
78
  await fetch('/api/create-account', {
@@ -119,10 +119,10 @@ If no username is passed, the modal shows a Welcome step where the user types th
119
119
  FollowGate.init({
120
120
  appId: 'your-app-id',
121
121
  apiKey: 'fg_live_xxx',
122
- twitter: { handle: 'your_handle' },
122
+ // Handle configured in Dashboard!
123
123
  });
124
124
 
125
- FollowGate.show(); // Modal starts with username input step
125
+ FollowGate.show(); // Modal starts with username input step
126
126
  ```
127
127
 
128
128
  **Method 2: App passes username via code (skips Welcome step)**
@@ -133,12 +133,11 @@ FollowGate.init({
133
133
  appId: 'your-app-id',
134
134
  apiKey: 'fg_live_xxx',
135
135
  twitter: {
136
- handle: 'your_handle',
137
- username: 'their_x_username', // ← skips Welcome step
136
+ username: 'their_x_username', // skips Welcome step
138
137
  },
139
138
  });
140
139
 
141
- FollowGate.show(); // Modal starts directly with Follow step
140
+ FollowGate.show(); // Modal starts directly with Follow step
142
141
  ```
143
142
 
144
143
  > **Note:** No Twitter API calls are made in either case. The SDK uses intent URLs only.
@@ -151,16 +150,15 @@ FollowGate.init({
151
150
  appId: 'your-app-id', // Your App ID from the dashboard
152
151
  apiKey: 'fg_live_xxx', // Your API key (starts with fg_live_ or fg_test_)
153
152
 
154
- // Twitter/X Configuration
153
+ // Twitter/X Configuration (all optional - configure in Dashboard!)
155
154
  twitter: {
156
- handle: 'your_handle', // Your Twitter username (without @)
157
- tweetId: '1234567890', // Tweet ID to repost (optional)
155
+ overrideHandle: 'other_handle', // Override Dashboard handle (highest priority)
156
+ overridePostUrl: 'https://x.com/user/status/123', // Override Dashboard post URL for repost
157
+ username: 'their_username', // Pre-fill user's X username (skips Welcome step)
158
158
  },
159
159
 
160
160
  // Callback when user completes all actions
161
161
  onComplete: () => {
162
- // Called after the user finishes all steps and clicks "Got it"
163
- // Typically used to redirect to your app
164
162
  router.push('/dashboard');
165
163
  },
166
164
 
@@ -170,9 +168,17 @@ FollowGate.init({
170
168
  accentColor: '#6366f1', // Customize primary button color (hex)
171
169
  theme: 'dark', // 'dark' | 'light' - modal appearance
172
170
  apiUrl: 'https://...', // Custom API URL (for self-hosted)
171
+ forceShow: false, // Always show modal, even if user already unlocked
173
172
  });
174
173
  ```
175
174
 
175
+ **Dashboard Configuration:** Target handle, required actions, custom texts, redirect URLs, and skip options are all configured in the [Dashboard](https://followgate.app) and fetched automatically by the SDK.
176
+
177
+ **Handle Priority:**
178
+
179
+ 1. `twitter.overrideHandle` (explicit code override - highest priority)
180
+ 2. Dashboard `targetHandle` (default)
181
+
176
182
  ### Handle & Target Formats
177
183
 
178
184
  The SDK automatically normalizes usernames - you can use `@username` or `username`, both work:
@@ -180,31 +186,34 @@ The SDK automatically normalizes usernames - you can use `@username` or `usernam
180
186
  | Platform | Action | Target Format | Example |
181
187
  | ------------ | ------ | ----------------------------------- | ------------------------------------ |
182
188
  | **Twitter** | follow | Username (without @) | `lukasvanuden` |
183
- | **Twitter** | repost | Tweet ID (numbers only) | `1234567890123456789` |
184
- | **Twitter** | like | Tweet ID (numbers only) | `1234567890123456789` |
189
+ | **Twitter** | repost | Post URL or Tweet ID | `https://x.com/user/status/123` |
190
+ | **Twitter** | like | Post URL or Tweet ID | `https://x.com/user/status/123` |
185
191
  | **Bluesky** | follow | Handle (with or without @) | `user.bsky.social` |
186
192
  | **Bluesky** | repost | AT URI or post path | `user.bsky.social/post/xyz` |
187
193
  | **LinkedIn** | follow | Company name or prefixed identifier | `company:anthropic` or `in:username` |
188
194
 
189
- **Tip:** For Twitter, you can find the Tweet ID in the URL: `https://x.com/user/status/1234567890123456789`
190
-
191
195
  ## API Reference
192
196
 
193
197
  ### Modal Methods
194
198
 
195
199
  ```typescript
196
- // Show the FollowGate modal
200
+ // Show the FollowGate modal (async - fetches config first)
197
201
  // If user is already unlocked, calls onComplete immediately
198
- FollowGate.show();
202
+ await FollowGate.show();
199
203
 
200
204
  // Hide the modal programmatically
205
+ // Pass true to redirect to cancelUrl (if configured)
201
206
  FollowGate.hide();
207
+ FollowGate.hide(true); // redirect to cancelUrl
202
208
 
203
209
  // Check if user has completed all actions
204
210
  FollowGate.isUnlocked(); // true | false
205
211
 
206
212
  // Reset user's session (for testing)
207
213
  FollowGate.reset();
214
+
215
+ // Clear only unlock status, keep user data (v0.13.0+)
216
+ FollowGate.clearUnlockStatus();
208
217
  ```
209
218
 
210
219
  ### Advanced Usage
@@ -287,16 +296,16 @@ FollowGate.init({
287
296
  appId: 'your-app-id',
288
297
  apiKey: 'fg_live_xxx',
289
298
  userId: clerkUserId,
290
- twitter: { handle: 'lukasvanuden' },
299
+ // Handle configured in Dashboard!
291
300
  });
292
301
 
293
302
  const isVerified = await FollowGate.isVerified();
294
303
 
295
304
  if (isVerified) {
296
- // User completed FollowGate Full access
305
+ // User completed FollowGate - Full access
297
306
  enableAllFeatures();
298
307
  } else {
299
- // User hasn't completed Limited access
308
+ // User hasn't completed - Limited access
300
309
  enableTrialMode();
301
310
  }
302
311
  ```
@@ -347,7 +356,7 @@ export default function WelcomePage() {
347
356
  FollowGate.init({
348
357
  appId: process.env.NEXT_PUBLIC_FOLLOWGATE_APP_ID!,
349
358
  apiKey: process.env.NEXT_PUBLIC_FOLLOWGATE_API_KEY!,
350
- twitter: { handle: 'your_handle' },
359
+ // Handle configured in Dashboard!
351
360
  onComplete: () => router.push('/dashboard'),
352
361
  });
353
362
 
@@ -385,7 +394,7 @@ export default function WelcomePage() {
385
394
  appId: process.env.NEXT_PUBLIC_FOLLOWGATE_APP_ID!,
386
395
  apiKey: process.env.NEXT_PUBLIC_FOLLOWGATE_API_KEY!,
387
396
  userId: user.id, // Per-user unlock status (recommended!)
388
- twitter: { handle: 'lukasvanuden' },
397
+ // Handle configured in Dashboard!
389
398
  onComplete: () => router.push('/dashboard'),
390
399
  });
391
400
 
@@ -421,17 +430,12 @@ import type {
421
430
 
422
431
  ## Pricing
423
432
 
424
- | Tier | Price | Users | Verification |
425
- | -------- | ------ | ------ | ------------------ |
426
- | Free | $0 | 100 | Intent-URL only |
427
- | Starter | $15/mo | 500 | Weekly sampling |
428
- | Pro | $49/mo | 2,000 | OAuth verification |
429
- | Business | $99/mo | 5,000+ | Daily verification |
433
+ **FollowGate is 100% free.** No tiers, no limits, no billing. Use it for as many apps and users as you want.
430
434
 
431
435
  ## Links
432
436
 
433
437
  - [Dashboard](https://followgate.app)
434
- - [Documentation](https://docs.followgate.app)
438
+ - [Documentation](https://followgate.app/docs)
435
439
  - [GitHub](https://github.com/JustFF5/FollowGate)
436
440
  - [Live Demo](https://follow-gate-web-demo.vercel.app)
437
441
 
package/dist/index.d.mts CHANGED
@@ -5,7 +5,7 @@ type Platform = 'twitter' | 'bluesky' | 'linkedin';
5
5
  /**
6
6
  * Supported social actions
7
7
  */
8
- type SocialAction = 'follow' | 'repost' | 'like';
8
+ type SocialAction = 'follow' | 'repost';
9
9
  /**
10
10
  * Twitter/X configuration
11
11
  */
@@ -178,7 +178,6 @@ declare class FollowGateClient {
178
178
  clearUnlockStatus(): void;
179
179
  getFollowUrl(platform: Platform, target: string): string;
180
180
  getRepostUrl(platform: Platform, target: string): string;
181
- getLikeUrl(platform: Platform, target: string): string;
182
181
  openIntent(options: CompleteOptions): Promise<void>;
183
182
  complete(options: CompleteOptions): Promise<void>;
184
183
  unlock(): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ type Platform = 'twitter' | 'bluesky' | 'linkedin';
5
5
  /**
6
6
  * Supported social actions
7
7
  */
8
- type SocialAction = 'follow' | 'repost' | 'like';
8
+ type SocialAction = 'follow' | 'repost';
9
9
  /**
10
10
  * Twitter/X configuration
11
11
  */
@@ -178,7 +178,6 @@ declare class FollowGateClient {
178
178
  clearUnlockStatus(): void;
179
179
  getFollowUrl(platform: Platform, target: string): string;
180
180
  getRepostUrl(platform: Platform, target: string): string;
181
- getLikeUrl(platform: Platform, target: string): string;
182
181
  openIntent(options: CompleteOptions): Promise<void>;
183
182
  complete(options: CompleteOptions): Promise<void>;
184
183
  unlock(): Promise<void>;
package/dist/index.js CHANGED
@@ -614,11 +614,13 @@ var FollowGateClient = class {
614
614
  );
615
615
  }
616
616
  } else {
617
- console.warn(
618
- "[FollowGate] Modal skipped - user already unlocked.",
619
- "Use forceShow: true in init() to always show modal, or call FollowGate.reset() to clear.",
620
- this.config.userId ? `(userId: ${this.config.userId})` : "(no userId set)"
621
- );
617
+ if (this.config.debug) {
618
+ console.warn(
619
+ "[FollowGate] Modal skipped - user already unlocked.",
620
+ "Use forceShow: true in init() to always show modal, or call FollowGate.reset() to clear.",
621
+ this.config.userId ? `(userId: ${this.config.userId})` : "(no userId set)"
622
+ );
623
+ }
622
624
  this.config.onComplete?.();
623
625
  return;
624
626
  }
@@ -768,11 +770,13 @@ var FollowGateClient = class {
768
770
  const postUrl = this.getTargetPostUrl();
769
771
  const actionsIncludeRepost = this.serverConfig?.actions?.includes("repost");
770
772
  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
- );
773
+ if (this.config?.debug) {
774
+ console.warn(
775
+ "[FollowGate] REPOST action configured but no targetPostUrl available.",
776
+ "Set targetPostUrl in Dashboard or use overridePostUrl in config.",
777
+ "Skipping repost step."
778
+ );
779
+ }
776
780
  return false;
777
781
  }
778
782
  if (this.config?.twitter?.tweetId) {
@@ -846,16 +850,16 @@ var FollowGateClient = class {
846
850
  this.trackEvent("step_viewed", { step: "follow" });
847
851
  const handle = this.getTargetHandle();
848
852
  if (!handle) {
849
- console.error(
850
- "[FollowGate] No target handle configured. Set it in Dashboard or use overrideHandle."
851
- );
853
+ if (this.config?.debug) {
854
+ console.error(
855
+ "[FollowGate] No target handle configured. Set it in Dashboard or use overrideHandle."
856
+ );
857
+ }
852
858
  return;
853
859
  }
854
860
  const hasRepost = this.shouldShowRepostStep();
855
- const defaultTitle = hasRepost ? "Step 1: Follow" : "Follow to Continue";
856
- const defaultMessage = `Follow @${handle} on X`;
857
- const title = this.serverConfig?.welcomeTitle || defaultTitle;
858
- const message = this.serverConfig?.welcomeMessage || defaultMessage;
861
+ const title = hasRepost ? "Step 1: Follow" : "Follow to Continue";
862
+ const message = `Follow @${handle} on X`;
859
863
  content.innerHTML = `
860
864
  ${hasRepost ? this.renderStepIndicator(1) : ""}
861
865
  <div class="fg-icon-box">
@@ -1100,14 +1104,14 @@ var FollowGateClient = class {
1100
1104
  </div>
1101
1105
  ` : ""}
1102
1106
  </div>
1103
- <p class="fg-verify-hint">Verification may take some time</p>
1104
- <p style="color: rgba(250, 204, 21, 0.85); font-size: 11px; margin: 8px 0 10px; text-align: center;">Access may be revoked if actions are not completed.</p>
1107
+ <p class="fg-verify-hint" style="margin-bottom: 32px;">Verification may take some time</p>
1108
+ <p style="color: rgba(250, 204, 21, 0.85); font-size: 11px; margin: 0 0 10px; text-align: center;">Access may be revoked if actions are not completed.</p>
1105
1109
  <button class="fg-btn fg-btn-green" id="fg-finish-btn">
1106
1110
  ${ICONS.check}
1107
- Got it - Get access
1111
+ Got it - Get access now!
1108
1112
  </button>
1109
1113
  ${hasFollow || hasRepost ? `
1110
- <p style="color: rgba(255,255,255,0.4); font-size: 11px; margin: 24px 0 6px; text-align: center;">Missed something? Open again:</p>
1114
+ <p style="color: rgba(255,255,255,0.4); font-size: 11px; margin: 24px 0 0; text-align: center;">Missed something? Open again:</p>
1111
1115
  <div class="fg-btn-row">
1112
1116
  ${hasFollow ? `
1113
1117
  <button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
@@ -1242,9 +1246,6 @@ var FollowGateClient = class {
1242
1246
  getRepostUrl(platform, target) {
1243
1247
  return this.buildIntentUrl({ platform, action: "repost", target });
1244
1248
  }
1245
- getLikeUrl(platform, target) {
1246
- return this.buildIntentUrl({ platform, action: "like", target });
1247
- }
1248
1249
  async openIntent(options) {
1249
1250
  if (!this.config) {
1250
1251
  throw new Error("[FollowGate] SDK not initialized. Call init() first.");
@@ -1523,8 +1524,6 @@ var FollowGateClient = class {
1523
1524
  return `https://twitter.com/intent/follow?screen_name=${encodeURIComponent(target)}`;
1524
1525
  case "repost":
1525
1526
  return `https://twitter.com/intent/retweet?tweet_id=${encodeURIComponent(target)}`;
1526
- case "like":
1527
- return `https://twitter.com/intent/like?tweet_id=${encodeURIComponent(target)}`;
1528
1527
  default:
1529
1528
  throw new Error(`[FollowGate] Unsupported Twitter action: ${action}`);
1530
1529
  }
@@ -1535,7 +1534,6 @@ var FollowGateClient = class {
1535
1534
  case "follow":
1536
1535
  return `https://bsky.app/profile/${encodeURIComponent(normalizedTarget)}`;
1537
1536
  case "repost":
1538
- case "like":
1539
1537
  if (target.startsWith("at://") || target.includes("/post/")) {
1540
1538
  const postPath = target.replace("at://", "").replace("app.bsky.feed.post/", "post/");
1541
1539
  return `https://bsky.app/profile/${postPath}`;
package/dist/index.mjs CHANGED
@@ -588,11 +588,13 @@ var FollowGateClient = class {
588
588
  );
589
589
  }
590
590
  } else {
591
- console.warn(
592
- "[FollowGate] Modal skipped - user already unlocked.",
593
- "Use forceShow: true in init() to always show modal, or call FollowGate.reset() to clear.",
594
- this.config.userId ? `(userId: ${this.config.userId})` : "(no userId set)"
595
- );
591
+ if (this.config.debug) {
592
+ console.warn(
593
+ "[FollowGate] Modal skipped - user already unlocked.",
594
+ "Use forceShow: true in init() to always show modal, or call FollowGate.reset() to clear.",
595
+ this.config.userId ? `(userId: ${this.config.userId})` : "(no userId set)"
596
+ );
597
+ }
596
598
  this.config.onComplete?.();
597
599
  return;
598
600
  }
@@ -742,11 +744,13 @@ var FollowGateClient = class {
742
744
  const postUrl = this.getTargetPostUrl();
743
745
  const actionsIncludeRepost = this.serverConfig?.actions?.includes("repost");
744
746
  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
- );
747
+ if (this.config?.debug) {
748
+ console.warn(
749
+ "[FollowGate] REPOST action configured but no targetPostUrl available.",
750
+ "Set targetPostUrl in Dashboard or use overridePostUrl in config.",
751
+ "Skipping repost step."
752
+ );
753
+ }
750
754
  return false;
751
755
  }
752
756
  if (this.config?.twitter?.tweetId) {
@@ -820,16 +824,16 @@ var FollowGateClient = class {
820
824
  this.trackEvent("step_viewed", { step: "follow" });
821
825
  const handle = this.getTargetHandle();
822
826
  if (!handle) {
823
- console.error(
824
- "[FollowGate] No target handle configured. Set it in Dashboard or use overrideHandle."
825
- );
827
+ if (this.config?.debug) {
828
+ console.error(
829
+ "[FollowGate] No target handle configured. Set it in Dashboard or use overrideHandle."
830
+ );
831
+ }
826
832
  return;
827
833
  }
828
834
  const hasRepost = this.shouldShowRepostStep();
829
- const defaultTitle = hasRepost ? "Step 1: Follow" : "Follow to Continue";
830
- const defaultMessage = `Follow @${handle} on X`;
831
- const title = this.serverConfig?.welcomeTitle || defaultTitle;
832
- const message = this.serverConfig?.welcomeMessage || defaultMessage;
835
+ const title = hasRepost ? "Step 1: Follow" : "Follow to Continue";
836
+ const message = `Follow @${handle} on X`;
833
837
  content.innerHTML = `
834
838
  ${hasRepost ? this.renderStepIndicator(1) : ""}
835
839
  <div class="fg-icon-box">
@@ -1074,14 +1078,14 @@ var FollowGateClient = class {
1074
1078
  </div>
1075
1079
  ` : ""}
1076
1080
  </div>
1077
- <p class="fg-verify-hint">Verification may take some time</p>
1078
- <p style="color: rgba(250, 204, 21, 0.85); font-size: 11px; margin: 8px 0 10px; text-align: center;">Access may be revoked if actions are not completed.</p>
1081
+ <p class="fg-verify-hint" style="margin-bottom: 32px;">Verification may take some time</p>
1082
+ <p style="color: rgba(250, 204, 21, 0.85); font-size: 11px; margin: 0 0 10px; text-align: center;">Access may be revoked if actions are not completed.</p>
1079
1083
  <button class="fg-btn fg-btn-green" id="fg-finish-btn">
1080
1084
  ${ICONS.check}
1081
- Got it - Get access
1085
+ Got it - Get access now!
1082
1086
  </button>
1083
1087
  ${hasFollow || hasRepost ? `
1084
- <p style="color: rgba(255,255,255,0.4); font-size: 11px; margin: 24px 0 6px; text-align: center;">Missed something? Open again:</p>
1088
+ <p style="color: rgba(255,255,255,0.4); font-size: 11px; margin: 24px 0 0; text-align: center;">Missed something? Open again:</p>
1085
1089
  <div class="fg-btn-row">
1086
1090
  ${hasFollow ? `
1087
1091
  <button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
@@ -1216,9 +1220,6 @@ var FollowGateClient = class {
1216
1220
  getRepostUrl(platform, target) {
1217
1221
  return this.buildIntentUrl({ platform, action: "repost", target });
1218
1222
  }
1219
- getLikeUrl(platform, target) {
1220
- return this.buildIntentUrl({ platform, action: "like", target });
1221
- }
1222
1223
  async openIntent(options) {
1223
1224
  if (!this.config) {
1224
1225
  throw new Error("[FollowGate] SDK not initialized. Call init() first.");
@@ -1497,8 +1498,6 @@ var FollowGateClient = class {
1497
1498
  return `https://twitter.com/intent/follow?screen_name=${encodeURIComponent(target)}`;
1498
1499
  case "repost":
1499
1500
  return `https://twitter.com/intent/retweet?tweet_id=${encodeURIComponent(target)}`;
1500
- case "like":
1501
- return `https://twitter.com/intent/like?tweet_id=${encodeURIComponent(target)}`;
1502
1501
  default:
1503
1502
  throw new Error(`[FollowGate] Unsupported Twitter action: ${action}`);
1504
1503
  }
@@ -1509,7 +1508,6 @@ var FollowGateClient = class {
1509
1508
  case "follow":
1510
1509
  return `https://bsky.app/profile/${encodeURIComponent(normalizedTarget)}`;
1511
1510
  case "repost":
1512
- case "like":
1513
1511
  if (target.startsWith("at://") || target.includes("/post/")) {
1514
1512
  const postPath = target.replace("at://", "").replace("app.bsky.feed.post/", "post/");
1515
1513
  return `https://bsky.app/profile/${postPath}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@followgate/js",
3
- "version": "0.15.3",
3
+ "version": "0.15.5",
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",