@followgate/js 0.2.0 → 0.4.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.
package/dist/index.d.mts CHANGED
@@ -15,6 +15,14 @@ interface FollowGateConfig {
15
15
  apiUrl?: string;
16
16
  debug?: boolean;
17
17
  }
18
+ /**
19
+ * SDK Error class with helpful messages
20
+ */
21
+ declare class FollowGateError extends Error {
22
+ code: string;
23
+ hint?: string | undefined;
24
+ constructor(message: string, code: string, hint?: string | undefined);
25
+ }
18
26
  /**
19
27
  * Open action options
20
28
  */
@@ -44,6 +52,13 @@ interface AuthenticatedUser {
44
52
  username: string;
45
53
  platform: Platform;
46
54
  }
55
+ /**
56
+ * Auth state when username input is needed
57
+ */
58
+ interface PendingUsernameState {
59
+ needsUsername: true;
60
+ token: string;
61
+ }
47
62
  /**
48
63
  * Authentication options
49
64
  */
@@ -61,8 +76,10 @@ declare class FollowGateClient {
61
76
  private listeners;
62
77
  private currentUser;
63
78
  private authToken;
79
+ private pendingUsername;
64
80
  /**
65
81
  * Initialize the SDK
82
+ * @throws {FollowGateError} If configuration is invalid
66
83
  */
67
84
  init(config: FollowGateConfig): void;
68
85
  /**
@@ -82,6 +99,14 @@ declare class FollowGateClient {
82
99
  * Logout - clear stored session
83
100
  */
84
101
  logout(): void;
102
+ /**
103
+ * Check if username input is needed (Twitter Free Tier limitation)
104
+ */
105
+ needsUsernameInput(): boolean;
106
+ /**
107
+ * Set username manually (when needsUsernameInput() returns true)
108
+ */
109
+ setUsername(username: string): void;
85
110
  /**
86
111
  * Handle auth callback from URL params
87
112
  */
@@ -121,4 +146,4 @@ declare class FollowGateClient {
121
146
  }
122
147
  declare const FollowGate: FollowGateClient;
123
148
 
124
- export { type AuthOptions, type AuthenticatedUser, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type Platform, type SocialAction };
149
+ export { type AuthOptions, type AuthenticatedUser, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, FollowGateError, type LinkedInTargetType, type OpenOptions, type PendingUsernameState, type Platform, type SocialAction };
package/dist/index.d.ts CHANGED
@@ -15,6 +15,14 @@ interface FollowGateConfig {
15
15
  apiUrl?: string;
16
16
  debug?: boolean;
17
17
  }
18
+ /**
19
+ * SDK Error class with helpful messages
20
+ */
21
+ declare class FollowGateError extends Error {
22
+ code: string;
23
+ hint?: string | undefined;
24
+ constructor(message: string, code: string, hint?: string | undefined);
25
+ }
18
26
  /**
19
27
  * Open action options
20
28
  */
@@ -44,6 +52,13 @@ interface AuthenticatedUser {
44
52
  username: string;
45
53
  platform: Platform;
46
54
  }
55
+ /**
56
+ * Auth state when username input is needed
57
+ */
58
+ interface PendingUsernameState {
59
+ needsUsername: true;
60
+ token: string;
61
+ }
47
62
  /**
48
63
  * Authentication options
49
64
  */
@@ -61,8 +76,10 @@ declare class FollowGateClient {
61
76
  private listeners;
62
77
  private currentUser;
63
78
  private authToken;
79
+ private pendingUsername;
64
80
  /**
65
81
  * Initialize the SDK
82
+ * @throws {FollowGateError} If configuration is invalid
66
83
  */
67
84
  init(config: FollowGateConfig): void;
68
85
  /**
@@ -82,6 +99,14 @@ declare class FollowGateClient {
82
99
  * Logout - clear stored session
83
100
  */
84
101
  logout(): void;
102
+ /**
103
+ * Check if username input is needed (Twitter Free Tier limitation)
104
+ */
105
+ needsUsernameInput(): boolean;
106
+ /**
107
+ * Set username manually (when needsUsernameInput() returns true)
108
+ */
109
+ setUsername(username: string): void;
85
110
  /**
86
111
  * Handle auth callback from URL params
87
112
  */
@@ -121,4 +146,4 @@ declare class FollowGateClient {
121
146
  }
122
147
  declare const FollowGate: FollowGateClient;
123
148
 
124
- export { type AuthOptions, type AuthenticatedUser, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type Platform, type SocialAction };
149
+ export { type AuthOptions, type AuthenticatedUser, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, FollowGateError, type LinkedInTargetType, type OpenOptions, type PendingUsernameState, type Platform, type SocialAction };
package/dist/index.js CHANGED
@@ -21,19 +21,68 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  FollowGate: () => FollowGate,
24
- FollowGateClient: () => FollowGateClient
24
+ FollowGateClient: () => FollowGateClient,
25
+ FollowGateError: () => FollowGateError
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
27
28
  var DEFAULT_API_URL = "https://api.followgate.app";
29
+ var FollowGateError = class extends Error {
30
+ constructor(message, code, hint) {
31
+ super(message);
32
+ this.code = code;
33
+ this.hint = hint;
34
+ this.name = "FollowGateError";
35
+ }
36
+ };
37
+ function isValidApiKeyFormat(apiKey) {
38
+ return /^fg_(live|test)_[a-zA-Z0-9_-]+$/.test(apiKey);
39
+ }
28
40
  var FollowGateClient = class {
29
41
  config = null;
30
42
  listeners = /* @__PURE__ */ new Map();
31
43
  currentUser = null;
32
44
  authToken = null;
45
+ pendingUsername = null;
33
46
  /**
34
47
  * Initialize the SDK
48
+ * @throws {FollowGateError} If configuration is invalid
35
49
  */
36
50
  init(config) {
51
+ if (!config.appId || typeof config.appId !== "string") {
52
+ throw new FollowGateError(
53
+ "[FollowGate] Missing or invalid appId",
54
+ "INVALID_APP_ID",
55
+ "Get your App ID from https://followgate.app/dashboard. Make sure NEXT_PUBLIC_FOLLOWGATE_APP_ID is set in your environment."
56
+ );
57
+ }
58
+ if (config.appId.trim() === "" || config.appId === "undefined") {
59
+ throw new FollowGateError(
60
+ "[FollowGate] appId is empty or undefined",
61
+ "EMPTY_APP_ID",
62
+ "Your appId appears to be empty. This often happens when environment variables are not properly configured. Check that NEXT_PUBLIC_FOLLOWGATE_APP_ID is set and rebuild your application."
63
+ );
64
+ }
65
+ if (!config.apiKey || typeof config.apiKey !== "string") {
66
+ throw new FollowGateError(
67
+ "[FollowGate] Missing or invalid apiKey",
68
+ "INVALID_API_KEY",
69
+ "Get your API Key from https://followgate.app/dashboard. Make sure NEXT_PUBLIC_FOLLOWGATE_API_KEY is set in your environment."
70
+ );
71
+ }
72
+ if (config.apiKey.trim() === "" || config.apiKey === "undefined") {
73
+ throw new FollowGateError(
74
+ "[FollowGate] apiKey is empty or undefined",
75
+ "EMPTY_API_KEY",
76
+ "Your apiKey appears to be empty. This often happens when environment variables are not properly configured at BUILD TIME (not runtime). Set NEXT_PUBLIC_FOLLOWGATE_API_KEY and REBUILD your application."
77
+ );
78
+ }
79
+ if (!isValidApiKeyFormat(config.apiKey)) {
80
+ throw new FollowGateError(
81
+ `[FollowGate] Invalid API key format: "${config.apiKey.substring(0, 10)}..."`,
82
+ "INVALID_API_KEY_FORMAT",
83
+ 'API keys should start with "fg_live_" (production) or "fg_test_" (development). Get a valid key from https://followgate.app/dashboard'
84
+ );
85
+ }
37
86
  this.config = {
38
87
  ...config,
39
88
  apiUrl: config.apiUrl || DEFAULT_API_URL
@@ -42,6 +91,10 @@ var FollowGateClient = class {
42
91
  this.restoreSession();
43
92
  if (config.debug) {
44
93
  console.log("[FollowGate] Initialized with appId:", config.appId);
94
+ console.log(
95
+ "[FollowGate] API Key:",
96
+ config.apiKey.substring(0, 12) + "..."
97
+ );
45
98
  if (this.currentUser) {
46
99
  console.log("[FollowGate] Restored user:", this.currentUser.username);
47
100
  }
@@ -91,14 +144,49 @@ var FollowGateClient = class {
91
144
  logout() {
92
145
  this.currentUser = null;
93
146
  this.authToken = null;
147
+ this.pendingUsername = null;
94
148
  if (typeof localStorage !== "undefined") {
95
149
  localStorage.removeItem("followgate_token");
96
150
  localStorage.removeItem("followgate_user");
151
+ localStorage.removeItem("followgate_pending_username");
97
152
  }
98
153
  if (this.config?.debug) {
99
154
  console.log("[FollowGate] User logged out");
100
155
  }
101
156
  }
157
+ /**
158
+ * Check if username input is needed (Twitter Free Tier limitation)
159
+ */
160
+ needsUsernameInput() {
161
+ return this.pendingUsername !== null;
162
+ }
163
+ /**
164
+ * Set username manually (when needsUsernameInput() returns true)
165
+ */
166
+ setUsername(username) {
167
+ if (!this.pendingUsername) {
168
+ throw new Error(
169
+ "[FollowGate] No pending username state. User is either not authenticated or username is already set."
170
+ );
171
+ }
172
+ const normalizedUsername = username.startsWith("@") ? username.slice(1) : username;
173
+ this.currentUser = {
174
+ userId: "user_input",
175
+ username: normalizedUsername,
176
+ platform: "twitter"
177
+ };
178
+ this.authToken = this.pendingUsername.token;
179
+ if (typeof localStorage !== "undefined") {
180
+ localStorage.setItem("followgate_token", this.authToken);
181
+ localStorage.setItem("followgate_user", JSON.stringify(this.currentUser));
182
+ localStorage.removeItem("followgate_pending_username");
183
+ }
184
+ this.pendingUsername = null;
185
+ this.emit("authenticated", this.currentUser);
186
+ if (this.config?.debug) {
187
+ console.log("[FollowGate] Username set manually:", normalizedUsername);
188
+ }
189
+ }
102
190
  /**
103
191
  * Handle auth callback from URL params
104
192
  */
@@ -107,7 +195,26 @@ var FollowGateClient = class {
107
195
  const params = new URLSearchParams(window.location.search);
108
196
  const token = params.get("followgate_token");
109
197
  const username = params.get("followgate_user");
110
- if (token && username) {
198
+ const needsUsername = params.get("followgate_needs_username") === "true";
199
+ if (token && needsUsername) {
200
+ this.pendingUsername = {
201
+ needsUsername: true,
202
+ token
203
+ };
204
+ if (typeof localStorage !== "undefined") {
205
+ localStorage.setItem(
206
+ "followgate_pending_username",
207
+ JSON.stringify(this.pendingUsername)
208
+ );
209
+ }
210
+ const url = new URL(window.location.href);
211
+ url.searchParams.delete("followgate_token");
212
+ url.searchParams.delete("followgate_needs_username");
213
+ window.history.replaceState({}, "", url.toString());
214
+ if (this.config?.debug) {
215
+ console.log("[FollowGate] OAuth successful, username input needed");
216
+ }
217
+ } else if (token && username) {
111
218
  this.authToken = token;
112
219
  this.currentUser = {
113
220
  userId: "",
@@ -137,6 +244,18 @@ var FollowGateClient = class {
137
244
  */
138
245
  restoreSession() {
139
246
  if (typeof localStorage === "undefined") return;
247
+ const pendingJson = localStorage.getItem("followgate_pending_username");
248
+ if (pendingJson) {
249
+ try {
250
+ this.pendingUsername = JSON.parse(pendingJson);
251
+ if (this.config?.debug) {
252
+ console.log("[FollowGate] Restored pending username state");
253
+ }
254
+ return;
255
+ } catch {
256
+ localStorage.removeItem("followgate_pending_username");
257
+ }
258
+ }
140
259
  const token = localStorage.getItem("followgate_token");
141
260
  const userJson = localStorage.getItem("followgate_user");
142
261
  if (token && userJson) {
@@ -313,5 +432,6 @@ var FollowGate = new FollowGateClient();
313
432
  // Annotate the CommonJS export names for ESM import in node:
314
433
  0 && (module.exports = {
315
434
  FollowGate,
316
- FollowGateClient
435
+ FollowGateClient,
436
+ FollowGateError
317
437
  });
package/dist/index.mjs CHANGED
@@ -1,14 +1,62 @@
1
1
  // src/index.ts
2
2
  var DEFAULT_API_URL = "https://api.followgate.app";
3
+ var FollowGateError = class extends Error {
4
+ constructor(message, code, hint) {
5
+ super(message);
6
+ this.code = code;
7
+ this.hint = hint;
8
+ this.name = "FollowGateError";
9
+ }
10
+ };
11
+ function isValidApiKeyFormat(apiKey) {
12
+ return /^fg_(live|test)_[a-zA-Z0-9_-]+$/.test(apiKey);
13
+ }
3
14
  var FollowGateClient = class {
4
15
  config = null;
5
16
  listeners = /* @__PURE__ */ new Map();
6
17
  currentUser = null;
7
18
  authToken = null;
19
+ pendingUsername = null;
8
20
  /**
9
21
  * Initialize the SDK
22
+ * @throws {FollowGateError} If configuration is invalid
10
23
  */
11
24
  init(config) {
25
+ if (!config.appId || typeof config.appId !== "string") {
26
+ throw new FollowGateError(
27
+ "[FollowGate] Missing or invalid appId",
28
+ "INVALID_APP_ID",
29
+ "Get your App ID from https://followgate.app/dashboard. Make sure NEXT_PUBLIC_FOLLOWGATE_APP_ID is set in your environment."
30
+ );
31
+ }
32
+ if (config.appId.trim() === "" || config.appId === "undefined") {
33
+ throw new FollowGateError(
34
+ "[FollowGate] appId is empty or undefined",
35
+ "EMPTY_APP_ID",
36
+ "Your appId appears to be empty. This often happens when environment variables are not properly configured. Check that NEXT_PUBLIC_FOLLOWGATE_APP_ID is set and rebuild your application."
37
+ );
38
+ }
39
+ if (!config.apiKey || typeof config.apiKey !== "string") {
40
+ throw new FollowGateError(
41
+ "[FollowGate] Missing or invalid apiKey",
42
+ "INVALID_API_KEY",
43
+ "Get your API Key from https://followgate.app/dashboard. Make sure NEXT_PUBLIC_FOLLOWGATE_API_KEY is set in your environment."
44
+ );
45
+ }
46
+ if (config.apiKey.trim() === "" || config.apiKey === "undefined") {
47
+ throw new FollowGateError(
48
+ "[FollowGate] apiKey is empty or undefined",
49
+ "EMPTY_API_KEY",
50
+ "Your apiKey appears to be empty. This often happens when environment variables are not properly configured at BUILD TIME (not runtime). Set NEXT_PUBLIC_FOLLOWGATE_API_KEY and REBUILD your application."
51
+ );
52
+ }
53
+ if (!isValidApiKeyFormat(config.apiKey)) {
54
+ throw new FollowGateError(
55
+ `[FollowGate] Invalid API key format: "${config.apiKey.substring(0, 10)}..."`,
56
+ "INVALID_API_KEY_FORMAT",
57
+ 'API keys should start with "fg_live_" (production) or "fg_test_" (development). Get a valid key from https://followgate.app/dashboard'
58
+ );
59
+ }
12
60
  this.config = {
13
61
  ...config,
14
62
  apiUrl: config.apiUrl || DEFAULT_API_URL
@@ -17,6 +65,10 @@ var FollowGateClient = class {
17
65
  this.restoreSession();
18
66
  if (config.debug) {
19
67
  console.log("[FollowGate] Initialized with appId:", config.appId);
68
+ console.log(
69
+ "[FollowGate] API Key:",
70
+ config.apiKey.substring(0, 12) + "..."
71
+ );
20
72
  if (this.currentUser) {
21
73
  console.log("[FollowGate] Restored user:", this.currentUser.username);
22
74
  }
@@ -66,14 +118,49 @@ var FollowGateClient = class {
66
118
  logout() {
67
119
  this.currentUser = null;
68
120
  this.authToken = null;
121
+ this.pendingUsername = null;
69
122
  if (typeof localStorage !== "undefined") {
70
123
  localStorage.removeItem("followgate_token");
71
124
  localStorage.removeItem("followgate_user");
125
+ localStorage.removeItem("followgate_pending_username");
72
126
  }
73
127
  if (this.config?.debug) {
74
128
  console.log("[FollowGate] User logged out");
75
129
  }
76
130
  }
131
+ /**
132
+ * Check if username input is needed (Twitter Free Tier limitation)
133
+ */
134
+ needsUsernameInput() {
135
+ return this.pendingUsername !== null;
136
+ }
137
+ /**
138
+ * Set username manually (when needsUsernameInput() returns true)
139
+ */
140
+ setUsername(username) {
141
+ if (!this.pendingUsername) {
142
+ throw new Error(
143
+ "[FollowGate] No pending username state. User is either not authenticated or username is already set."
144
+ );
145
+ }
146
+ const normalizedUsername = username.startsWith("@") ? username.slice(1) : username;
147
+ this.currentUser = {
148
+ userId: "user_input",
149
+ username: normalizedUsername,
150
+ platform: "twitter"
151
+ };
152
+ this.authToken = this.pendingUsername.token;
153
+ if (typeof localStorage !== "undefined") {
154
+ localStorage.setItem("followgate_token", this.authToken);
155
+ localStorage.setItem("followgate_user", JSON.stringify(this.currentUser));
156
+ localStorage.removeItem("followgate_pending_username");
157
+ }
158
+ this.pendingUsername = null;
159
+ this.emit("authenticated", this.currentUser);
160
+ if (this.config?.debug) {
161
+ console.log("[FollowGate] Username set manually:", normalizedUsername);
162
+ }
163
+ }
77
164
  /**
78
165
  * Handle auth callback from URL params
79
166
  */
@@ -82,7 +169,26 @@ var FollowGateClient = class {
82
169
  const params = new URLSearchParams(window.location.search);
83
170
  const token = params.get("followgate_token");
84
171
  const username = params.get("followgate_user");
85
- if (token && username) {
172
+ const needsUsername = params.get("followgate_needs_username") === "true";
173
+ if (token && needsUsername) {
174
+ this.pendingUsername = {
175
+ needsUsername: true,
176
+ token
177
+ };
178
+ if (typeof localStorage !== "undefined") {
179
+ localStorage.setItem(
180
+ "followgate_pending_username",
181
+ JSON.stringify(this.pendingUsername)
182
+ );
183
+ }
184
+ const url = new URL(window.location.href);
185
+ url.searchParams.delete("followgate_token");
186
+ url.searchParams.delete("followgate_needs_username");
187
+ window.history.replaceState({}, "", url.toString());
188
+ if (this.config?.debug) {
189
+ console.log("[FollowGate] OAuth successful, username input needed");
190
+ }
191
+ } else if (token && username) {
86
192
  this.authToken = token;
87
193
  this.currentUser = {
88
194
  userId: "",
@@ -112,6 +218,18 @@ var FollowGateClient = class {
112
218
  */
113
219
  restoreSession() {
114
220
  if (typeof localStorage === "undefined") return;
221
+ const pendingJson = localStorage.getItem("followgate_pending_username");
222
+ if (pendingJson) {
223
+ try {
224
+ this.pendingUsername = JSON.parse(pendingJson);
225
+ if (this.config?.debug) {
226
+ console.log("[FollowGate] Restored pending username state");
227
+ }
228
+ return;
229
+ } catch {
230
+ localStorage.removeItem("followgate_pending_username");
231
+ }
232
+ }
115
233
  const token = localStorage.getItem("followgate_token");
116
234
  const userJson = localStorage.getItem("followgate_user");
117
235
  if (token && userJson) {
@@ -287,5 +405,6 @@ var FollowGateClient = class {
287
405
  var FollowGate = new FollowGateClient();
288
406
  export {
289
407
  FollowGate,
290
- FollowGateClient
408
+ FollowGateClient,
409
+ FollowGateError
291
410
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@followgate/js",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
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",