@followgate/js 0.1.0 → 0.3.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/README.md +37 -21
- package/dist/index.d.mts +62 -2
- package/dist/index.d.ts +62 -2
- package/dist/index.js +177 -1
- package/dist/index.mjs +177 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -32,11 +32,11 @@ FollowGate.open({
|
|
|
32
32
|
|
|
33
33
|
## Supported Platforms
|
|
34
34
|
|
|
35
|
-
| Platform
|
|
36
|
-
|
|
35
|
+
| Platform | Actions |
|
|
36
|
+
| --------- | -------------------------- |
|
|
37
37
|
| Twitter/X | `follow`, `repost`, `like` |
|
|
38
|
-
| Bluesky
|
|
39
|
-
| LinkedIn
|
|
38
|
+
| Bluesky | `follow`, `repost`, `like` |
|
|
39
|
+
| LinkedIn | `follow` |
|
|
40
40
|
|
|
41
41
|
## API Reference
|
|
42
42
|
|
|
@@ -46,10 +46,10 @@ Initialize the SDK with your credentials.
|
|
|
46
46
|
|
|
47
47
|
```typescript
|
|
48
48
|
FollowGate.init({
|
|
49
|
-
appId: 'your-app-id',
|
|
50
|
-
apiKey: 'fg_live_xxx',
|
|
51
|
-
apiUrl: 'https://...',
|
|
52
|
-
debug: false,
|
|
49
|
+
appId: 'your-app-id', // Required: Your app ID from dashboard
|
|
50
|
+
apiKey: 'fg_live_xxx', // Required: Your API key
|
|
51
|
+
apiUrl: 'https://...', // Optional: Custom API URL
|
|
52
|
+
debug: false, // Optional: Enable debug logging
|
|
53
53
|
});
|
|
54
54
|
```
|
|
55
55
|
|
|
@@ -59,10 +59,10 @@ Open a social action popup/intent.
|
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
61
|
await FollowGate.open({
|
|
62
|
-
platform: 'twitter',
|
|
63
|
-
action: 'follow',
|
|
64
|
-
target: 'username',
|
|
65
|
-
userId: 'user-123',
|
|
62
|
+
platform: 'twitter', // 'twitter' | 'bluesky' | 'linkedin'
|
|
63
|
+
action: 'follow', // 'follow' | 'repost' | 'like'
|
|
64
|
+
target: 'username', // Username or post ID
|
|
65
|
+
userId: 'user-123', // Optional: Your app's user ID
|
|
66
66
|
});
|
|
67
67
|
```
|
|
68
68
|
|
|
@@ -106,7 +106,11 @@ FollowGate.on('error', (error) => {
|
|
|
106
106
|
FollowGate.open({ platform: 'twitter', action: 'follow', target: 'elonmusk' });
|
|
107
107
|
|
|
108
108
|
// Repost a tweet
|
|
109
|
-
FollowGate.open({
|
|
109
|
+
FollowGate.open({
|
|
110
|
+
platform: 'twitter',
|
|
111
|
+
action: 'repost',
|
|
112
|
+
target: '1234567890',
|
|
113
|
+
});
|
|
110
114
|
```
|
|
111
115
|
|
|
112
116
|
### Bluesky
|
|
@@ -116,7 +120,11 @@ FollowGate.open({ platform: 'twitter', action: 'repost', target: '1234567890' })
|
|
|
116
120
|
|
|
117
121
|
```typescript
|
|
118
122
|
// Follow
|
|
119
|
-
FollowGate.open({
|
|
123
|
+
FollowGate.open({
|
|
124
|
+
platform: 'bluesky',
|
|
125
|
+
action: 'follow',
|
|
126
|
+
target: 'alice.bsky.social',
|
|
127
|
+
});
|
|
120
128
|
```
|
|
121
129
|
|
|
122
130
|
### LinkedIn
|
|
@@ -125,19 +133,27 @@ FollowGate.open({ platform: 'bluesky', action: 'follow', target: 'alice.bsky.soc
|
|
|
125
133
|
|
|
126
134
|
```typescript
|
|
127
135
|
// Follow a company
|
|
128
|
-
FollowGate.open({
|
|
136
|
+
FollowGate.open({
|
|
137
|
+
platform: 'linkedin',
|
|
138
|
+
action: 'follow',
|
|
139
|
+
target: 'microsoft',
|
|
140
|
+
});
|
|
129
141
|
|
|
130
142
|
// Follow a personal profile
|
|
131
|
-
FollowGate.open({
|
|
143
|
+
FollowGate.open({
|
|
144
|
+
platform: 'linkedin',
|
|
145
|
+
action: 'follow',
|
|
146
|
+
target: 'in:satyanadella',
|
|
147
|
+
});
|
|
132
148
|
```
|
|
133
149
|
|
|
134
150
|
## Pricing
|
|
135
151
|
|
|
136
|
-
| Tier
|
|
137
|
-
|
|
138
|
-
| Free
|
|
139
|
-
| Starter
|
|
140
|
-
| Pro
|
|
152
|
+
| Tier | Price | Users | Verification |
|
|
153
|
+
| -------- | ------ | ------ | ------------------ |
|
|
154
|
+
| Free | $0 | 100 | Intent-URL only |
|
|
155
|
+
| Starter | $15/mo | 500 | Weekly sampling |
|
|
156
|
+
| Pro | $49/mo | 2,000 | OAuth verification |
|
|
141
157
|
| Business | $99/mo | 5,000+ | Daily verification |
|
|
142
158
|
|
|
143
159
|
## TypeScript
|
package/dist/index.d.mts
CHANGED
|
@@ -31,21 +31,81 @@ type LinkedInTargetType = 'company' | 'profile';
|
|
|
31
31
|
/**
|
|
32
32
|
* Event types
|
|
33
33
|
*/
|
|
34
|
-
type EventType = 'complete' | 'error' | 'cancel';
|
|
34
|
+
type EventType = 'complete' | 'error' | 'cancel' | 'authenticated';
|
|
35
35
|
/**
|
|
36
36
|
* Event callback
|
|
37
37
|
*/
|
|
38
38
|
type EventCallback = (data: unknown) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Authenticated user info
|
|
41
|
+
*/
|
|
42
|
+
interface AuthenticatedUser {
|
|
43
|
+
userId: string;
|
|
44
|
+
username: string;
|
|
45
|
+
platform: Platform;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Auth state when username input is needed
|
|
49
|
+
*/
|
|
50
|
+
interface PendingUsernameState {
|
|
51
|
+
needsUsername: true;
|
|
52
|
+
token: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Authentication options
|
|
56
|
+
*/
|
|
57
|
+
interface AuthOptions {
|
|
58
|
+
/** Where to redirect after auth (defaults to current page) */
|
|
59
|
+
redirectUri?: string;
|
|
60
|
+
/** Open in popup instead of redirect */
|
|
61
|
+
popup?: boolean;
|
|
62
|
+
}
|
|
39
63
|
/**
|
|
40
64
|
* FollowGate SDK Client
|
|
41
65
|
*/
|
|
42
66
|
declare class FollowGateClient {
|
|
43
67
|
private config;
|
|
44
68
|
private listeners;
|
|
69
|
+
private currentUser;
|
|
70
|
+
private authToken;
|
|
71
|
+
private pendingUsername;
|
|
45
72
|
/**
|
|
46
73
|
* Initialize the SDK
|
|
47
74
|
*/
|
|
48
75
|
init(config: FollowGateConfig): void;
|
|
76
|
+
/**
|
|
77
|
+
* Authenticate user via Twitter OAuth (handled by FollowGate)
|
|
78
|
+
* This identifies WHO is completing the social actions.
|
|
79
|
+
*/
|
|
80
|
+
authenticate(options?: AuthOptions): void;
|
|
81
|
+
/**
|
|
82
|
+
* Get current authenticated user
|
|
83
|
+
*/
|
|
84
|
+
getUser(): AuthenticatedUser | null;
|
|
85
|
+
/**
|
|
86
|
+
* Check if user is authenticated
|
|
87
|
+
*/
|
|
88
|
+
isAuthenticated(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Logout - clear stored session
|
|
91
|
+
*/
|
|
92
|
+
logout(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Check if username input is needed (Twitter Free Tier limitation)
|
|
95
|
+
*/
|
|
96
|
+
needsUsernameInput(): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Set username manually (when needsUsernameInput() returns true)
|
|
99
|
+
*/
|
|
100
|
+
setUsername(username: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Handle auth callback from URL params
|
|
103
|
+
*/
|
|
104
|
+
private handleAuthCallback;
|
|
105
|
+
/**
|
|
106
|
+
* Restore session from localStorage
|
|
107
|
+
*/
|
|
108
|
+
private restoreSession;
|
|
49
109
|
/**
|
|
50
110
|
* Open social action popup
|
|
51
111
|
*/
|
|
@@ -77,4 +137,4 @@ declare class FollowGateClient {
|
|
|
77
137
|
}
|
|
78
138
|
declare const FollowGate: FollowGateClient;
|
|
79
139
|
|
|
80
|
-
export { type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type Platform, type SocialAction };
|
|
140
|
+
export { type AuthOptions, type AuthenticatedUser, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type PendingUsernameState, type Platform, type SocialAction };
|
package/dist/index.d.ts
CHANGED
|
@@ -31,21 +31,81 @@ type LinkedInTargetType = 'company' | 'profile';
|
|
|
31
31
|
/**
|
|
32
32
|
* Event types
|
|
33
33
|
*/
|
|
34
|
-
type EventType = 'complete' | 'error' | 'cancel';
|
|
34
|
+
type EventType = 'complete' | 'error' | 'cancel' | 'authenticated';
|
|
35
35
|
/**
|
|
36
36
|
* Event callback
|
|
37
37
|
*/
|
|
38
38
|
type EventCallback = (data: unknown) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Authenticated user info
|
|
41
|
+
*/
|
|
42
|
+
interface AuthenticatedUser {
|
|
43
|
+
userId: string;
|
|
44
|
+
username: string;
|
|
45
|
+
platform: Platform;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Auth state when username input is needed
|
|
49
|
+
*/
|
|
50
|
+
interface PendingUsernameState {
|
|
51
|
+
needsUsername: true;
|
|
52
|
+
token: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Authentication options
|
|
56
|
+
*/
|
|
57
|
+
interface AuthOptions {
|
|
58
|
+
/** Where to redirect after auth (defaults to current page) */
|
|
59
|
+
redirectUri?: string;
|
|
60
|
+
/** Open in popup instead of redirect */
|
|
61
|
+
popup?: boolean;
|
|
62
|
+
}
|
|
39
63
|
/**
|
|
40
64
|
* FollowGate SDK Client
|
|
41
65
|
*/
|
|
42
66
|
declare class FollowGateClient {
|
|
43
67
|
private config;
|
|
44
68
|
private listeners;
|
|
69
|
+
private currentUser;
|
|
70
|
+
private authToken;
|
|
71
|
+
private pendingUsername;
|
|
45
72
|
/**
|
|
46
73
|
* Initialize the SDK
|
|
47
74
|
*/
|
|
48
75
|
init(config: FollowGateConfig): void;
|
|
76
|
+
/**
|
|
77
|
+
* Authenticate user via Twitter OAuth (handled by FollowGate)
|
|
78
|
+
* This identifies WHO is completing the social actions.
|
|
79
|
+
*/
|
|
80
|
+
authenticate(options?: AuthOptions): void;
|
|
81
|
+
/**
|
|
82
|
+
* Get current authenticated user
|
|
83
|
+
*/
|
|
84
|
+
getUser(): AuthenticatedUser | null;
|
|
85
|
+
/**
|
|
86
|
+
* Check if user is authenticated
|
|
87
|
+
*/
|
|
88
|
+
isAuthenticated(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Logout - clear stored session
|
|
91
|
+
*/
|
|
92
|
+
logout(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Check if username input is needed (Twitter Free Tier limitation)
|
|
95
|
+
*/
|
|
96
|
+
needsUsernameInput(): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Set username manually (when needsUsernameInput() returns true)
|
|
99
|
+
*/
|
|
100
|
+
setUsername(username: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Handle auth callback from URL params
|
|
103
|
+
*/
|
|
104
|
+
private handleAuthCallback;
|
|
105
|
+
/**
|
|
106
|
+
* Restore session from localStorage
|
|
107
|
+
*/
|
|
108
|
+
private restoreSession;
|
|
49
109
|
/**
|
|
50
110
|
* Open social action popup
|
|
51
111
|
*/
|
|
@@ -77,4 +137,4 @@ declare class FollowGateClient {
|
|
|
77
137
|
}
|
|
78
138
|
declare const FollowGate: FollowGateClient;
|
|
79
139
|
|
|
80
|
-
export { type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type Platform, type SocialAction };
|
|
140
|
+
export { type AuthOptions, type AuthenticatedUser, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type PendingUsernameState, type Platform, type SocialAction };
|
package/dist/index.js
CHANGED
|
@@ -24,10 +24,13 @@ __export(index_exports, {
|
|
|
24
24
|
FollowGateClient: () => FollowGateClient
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
|
-
var DEFAULT_API_URL = "https://api.followgate.
|
|
27
|
+
var DEFAULT_API_URL = "https://api.followgate.app";
|
|
28
28
|
var FollowGateClient = class {
|
|
29
29
|
config = null;
|
|
30
30
|
listeners = /* @__PURE__ */ new Map();
|
|
31
|
+
currentUser = null;
|
|
32
|
+
authToken = null;
|
|
33
|
+
pendingUsername = null;
|
|
31
34
|
/**
|
|
32
35
|
* Initialize the SDK
|
|
33
36
|
*/
|
|
@@ -36,8 +39,181 @@ var FollowGateClient = class {
|
|
|
36
39
|
...config,
|
|
37
40
|
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
38
41
|
};
|
|
42
|
+
this.handleAuthCallback();
|
|
43
|
+
this.restoreSession();
|
|
39
44
|
if (config.debug) {
|
|
40
45
|
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
46
|
+
if (this.currentUser) {
|
|
47
|
+
console.log("[FollowGate] Restored user:", this.currentUser.username);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Authenticate user via Twitter OAuth (handled by FollowGate)
|
|
53
|
+
* This identifies WHO is completing the social actions.
|
|
54
|
+
*/
|
|
55
|
+
authenticate(options = {}) {
|
|
56
|
+
if (!this.config) {
|
|
57
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
58
|
+
}
|
|
59
|
+
const redirectUri = options.redirectUri || window.location.href.split("?")[0];
|
|
60
|
+
const authUrl = `${this.config.apiUrl}/api/v1/auth/twitter?app_id=${encodeURIComponent(this.config.appId)}&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
61
|
+
if (this.config.debug) {
|
|
62
|
+
console.log("[FollowGate] Starting auth flow:", authUrl);
|
|
63
|
+
}
|
|
64
|
+
if (options.popup) {
|
|
65
|
+
const popup = window.open(
|
|
66
|
+
authUrl,
|
|
67
|
+
"followgate_auth",
|
|
68
|
+
"width=600,height=700"
|
|
69
|
+
);
|
|
70
|
+
if (!popup) {
|
|
71
|
+
this.emit("error", { message: "Popup blocked" });
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
window.location.href = authUrl;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get current authenticated user
|
|
79
|
+
*/
|
|
80
|
+
getUser() {
|
|
81
|
+
return this.currentUser;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if user is authenticated
|
|
85
|
+
*/
|
|
86
|
+
isAuthenticated() {
|
|
87
|
+
return this.currentUser !== null && this.authToken !== null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Logout - clear stored session
|
|
91
|
+
*/
|
|
92
|
+
logout() {
|
|
93
|
+
this.currentUser = null;
|
|
94
|
+
this.authToken = null;
|
|
95
|
+
this.pendingUsername = null;
|
|
96
|
+
if (typeof localStorage !== "undefined") {
|
|
97
|
+
localStorage.removeItem("followgate_token");
|
|
98
|
+
localStorage.removeItem("followgate_user");
|
|
99
|
+
localStorage.removeItem("followgate_pending_username");
|
|
100
|
+
}
|
|
101
|
+
if (this.config?.debug) {
|
|
102
|
+
console.log("[FollowGate] User logged out");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if username input is needed (Twitter Free Tier limitation)
|
|
107
|
+
*/
|
|
108
|
+
needsUsernameInput() {
|
|
109
|
+
return this.pendingUsername !== null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Set username manually (when needsUsernameInput() returns true)
|
|
113
|
+
*/
|
|
114
|
+
setUsername(username) {
|
|
115
|
+
if (!this.pendingUsername) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"[FollowGate] No pending username state. User is either not authenticated or username is already set."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const normalizedUsername = username.startsWith("@") ? username.slice(1) : username;
|
|
121
|
+
this.currentUser = {
|
|
122
|
+
userId: "user_input",
|
|
123
|
+
username: normalizedUsername,
|
|
124
|
+
platform: "twitter"
|
|
125
|
+
};
|
|
126
|
+
this.authToken = this.pendingUsername.token;
|
|
127
|
+
if (typeof localStorage !== "undefined") {
|
|
128
|
+
localStorage.setItem("followgate_token", this.authToken);
|
|
129
|
+
localStorage.setItem("followgate_user", JSON.stringify(this.currentUser));
|
|
130
|
+
localStorage.removeItem("followgate_pending_username");
|
|
131
|
+
}
|
|
132
|
+
this.pendingUsername = null;
|
|
133
|
+
this.emit("authenticated", this.currentUser);
|
|
134
|
+
if (this.config?.debug) {
|
|
135
|
+
console.log("[FollowGate] Username set manually:", normalizedUsername);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle auth callback from URL params
|
|
140
|
+
*/
|
|
141
|
+
handleAuthCallback() {
|
|
142
|
+
if (typeof window === "undefined") return;
|
|
143
|
+
const params = new URLSearchParams(window.location.search);
|
|
144
|
+
const token = params.get("followgate_token");
|
|
145
|
+
const username = params.get("followgate_user");
|
|
146
|
+
const needsUsername = params.get("followgate_needs_username") === "true";
|
|
147
|
+
if (token && needsUsername) {
|
|
148
|
+
this.pendingUsername = {
|
|
149
|
+
needsUsername: true,
|
|
150
|
+
token
|
|
151
|
+
};
|
|
152
|
+
if (typeof localStorage !== "undefined") {
|
|
153
|
+
localStorage.setItem(
|
|
154
|
+
"followgate_pending_username",
|
|
155
|
+
JSON.stringify(this.pendingUsername)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const url = new URL(window.location.href);
|
|
159
|
+
url.searchParams.delete("followgate_token");
|
|
160
|
+
url.searchParams.delete("followgate_needs_username");
|
|
161
|
+
window.history.replaceState({}, "", url.toString());
|
|
162
|
+
if (this.config?.debug) {
|
|
163
|
+
console.log("[FollowGate] OAuth successful, username input needed");
|
|
164
|
+
}
|
|
165
|
+
} else if (token && username) {
|
|
166
|
+
this.authToken = token;
|
|
167
|
+
this.currentUser = {
|
|
168
|
+
userId: "",
|
|
169
|
+
// Will be set from token verification
|
|
170
|
+
username,
|
|
171
|
+
platform: "twitter"
|
|
172
|
+
};
|
|
173
|
+
if (typeof localStorage !== "undefined") {
|
|
174
|
+
localStorage.setItem("followgate_token", token);
|
|
175
|
+
localStorage.setItem(
|
|
176
|
+
"followgate_user",
|
|
177
|
+
JSON.stringify(this.currentUser)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const url = new URL(window.location.href);
|
|
181
|
+
url.searchParams.delete("followgate_token");
|
|
182
|
+
url.searchParams.delete("followgate_user");
|
|
183
|
+
window.history.replaceState({}, "", url.toString());
|
|
184
|
+
this.emit("authenticated", this.currentUser);
|
|
185
|
+
if (this.config?.debug) {
|
|
186
|
+
console.log("[FollowGate] User authenticated:", username);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Restore session from localStorage
|
|
192
|
+
*/
|
|
193
|
+
restoreSession() {
|
|
194
|
+
if (typeof localStorage === "undefined") return;
|
|
195
|
+
const pendingJson = localStorage.getItem("followgate_pending_username");
|
|
196
|
+
if (pendingJson) {
|
|
197
|
+
try {
|
|
198
|
+
this.pendingUsername = JSON.parse(pendingJson);
|
|
199
|
+
if (this.config?.debug) {
|
|
200
|
+
console.log("[FollowGate] Restored pending username state");
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
} catch {
|
|
204
|
+
localStorage.removeItem("followgate_pending_username");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const token = localStorage.getItem("followgate_token");
|
|
208
|
+
const userJson = localStorage.getItem("followgate_user");
|
|
209
|
+
if (token && userJson) {
|
|
210
|
+
try {
|
|
211
|
+
this.authToken = token;
|
|
212
|
+
this.currentUser = JSON.parse(userJson);
|
|
213
|
+
} catch {
|
|
214
|
+
localStorage.removeItem("followgate_token");
|
|
215
|
+
localStorage.removeItem("followgate_user");
|
|
216
|
+
}
|
|
41
217
|
}
|
|
42
218
|
}
|
|
43
219
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
var DEFAULT_API_URL = "https://api.followgate.
|
|
2
|
+
var DEFAULT_API_URL = "https://api.followgate.app";
|
|
3
3
|
var FollowGateClient = class {
|
|
4
4
|
config = null;
|
|
5
5
|
listeners = /* @__PURE__ */ new Map();
|
|
6
|
+
currentUser = null;
|
|
7
|
+
authToken = null;
|
|
8
|
+
pendingUsername = null;
|
|
6
9
|
/**
|
|
7
10
|
* Initialize the SDK
|
|
8
11
|
*/
|
|
@@ -11,8 +14,181 @@ var FollowGateClient = class {
|
|
|
11
14
|
...config,
|
|
12
15
|
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
13
16
|
};
|
|
17
|
+
this.handleAuthCallback();
|
|
18
|
+
this.restoreSession();
|
|
14
19
|
if (config.debug) {
|
|
15
20
|
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
21
|
+
if (this.currentUser) {
|
|
22
|
+
console.log("[FollowGate] Restored user:", this.currentUser.username);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Authenticate user via Twitter OAuth (handled by FollowGate)
|
|
28
|
+
* This identifies WHO is completing the social actions.
|
|
29
|
+
*/
|
|
30
|
+
authenticate(options = {}) {
|
|
31
|
+
if (!this.config) {
|
|
32
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
33
|
+
}
|
|
34
|
+
const redirectUri = options.redirectUri || window.location.href.split("?")[0];
|
|
35
|
+
const authUrl = `${this.config.apiUrl}/api/v1/auth/twitter?app_id=${encodeURIComponent(this.config.appId)}&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
36
|
+
if (this.config.debug) {
|
|
37
|
+
console.log("[FollowGate] Starting auth flow:", authUrl);
|
|
38
|
+
}
|
|
39
|
+
if (options.popup) {
|
|
40
|
+
const popup = window.open(
|
|
41
|
+
authUrl,
|
|
42
|
+
"followgate_auth",
|
|
43
|
+
"width=600,height=700"
|
|
44
|
+
);
|
|
45
|
+
if (!popup) {
|
|
46
|
+
this.emit("error", { message: "Popup blocked" });
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
window.location.href = authUrl;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get current authenticated user
|
|
54
|
+
*/
|
|
55
|
+
getUser() {
|
|
56
|
+
return this.currentUser;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if user is authenticated
|
|
60
|
+
*/
|
|
61
|
+
isAuthenticated() {
|
|
62
|
+
return this.currentUser !== null && this.authToken !== null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Logout - clear stored session
|
|
66
|
+
*/
|
|
67
|
+
logout() {
|
|
68
|
+
this.currentUser = null;
|
|
69
|
+
this.authToken = null;
|
|
70
|
+
this.pendingUsername = null;
|
|
71
|
+
if (typeof localStorage !== "undefined") {
|
|
72
|
+
localStorage.removeItem("followgate_token");
|
|
73
|
+
localStorage.removeItem("followgate_user");
|
|
74
|
+
localStorage.removeItem("followgate_pending_username");
|
|
75
|
+
}
|
|
76
|
+
if (this.config?.debug) {
|
|
77
|
+
console.log("[FollowGate] User logged out");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if username input is needed (Twitter Free Tier limitation)
|
|
82
|
+
*/
|
|
83
|
+
needsUsernameInput() {
|
|
84
|
+
return this.pendingUsername !== null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Set username manually (when needsUsernameInput() returns true)
|
|
88
|
+
*/
|
|
89
|
+
setUsername(username) {
|
|
90
|
+
if (!this.pendingUsername) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"[FollowGate] No pending username state. User is either not authenticated or username is already set."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const normalizedUsername = username.startsWith("@") ? username.slice(1) : username;
|
|
96
|
+
this.currentUser = {
|
|
97
|
+
userId: "user_input",
|
|
98
|
+
username: normalizedUsername,
|
|
99
|
+
platform: "twitter"
|
|
100
|
+
};
|
|
101
|
+
this.authToken = this.pendingUsername.token;
|
|
102
|
+
if (typeof localStorage !== "undefined") {
|
|
103
|
+
localStorage.setItem("followgate_token", this.authToken);
|
|
104
|
+
localStorage.setItem("followgate_user", JSON.stringify(this.currentUser));
|
|
105
|
+
localStorage.removeItem("followgate_pending_username");
|
|
106
|
+
}
|
|
107
|
+
this.pendingUsername = null;
|
|
108
|
+
this.emit("authenticated", this.currentUser);
|
|
109
|
+
if (this.config?.debug) {
|
|
110
|
+
console.log("[FollowGate] Username set manually:", normalizedUsername);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Handle auth callback from URL params
|
|
115
|
+
*/
|
|
116
|
+
handleAuthCallback() {
|
|
117
|
+
if (typeof window === "undefined") return;
|
|
118
|
+
const params = new URLSearchParams(window.location.search);
|
|
119
|
+
const token = params.get("followgate_token");
|
|
120
|
+
const username = params.get("followgate_user");
|
|
121
|
+
const needsUsername = params.get("followgate_needs_username") === "true";
|
|
122
|
+
if (token && needsUsername) {
|
|
123
|
+
this.pendingUsername = {
|
|
124
|
+
needsUsername: true,
|
|
125
|
+
token
|
|
126
|
+
};
|
|
127
|
+
if (typeof localStorage !== "undefined") {
|
|
128
|
+
localStorage.setItem(
|
|
129
|
+
"followgate_pending_username",
|
|
130
|
+
JSON.stringify(this.pendingUsername)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const url = new URL(window.location.href);
|
|
134
|
+
url.searchParams.delete("followgate_token");
|
|
135
|
+
url.searchParams.delete("followgate_needs_username");
|
|
136
|
+
window.history.replaceState({}, "", url.toString());
|
|
137
|
+
if (this.config?.debug) {
|
|
138
|
+
console.log("[FollowGate] OAuth successful, username input needed");
|
|
139
|
+
}
|
|
140
|
+
} else if (token && username) {
|
|
141
|
+
this.authToken = token;
|
|
142
|
+
this.currentUser = {
|
|
143
|
+
userId: "",
|
|
144
|
+
// Will be set from token verification
|
|
145
|
+
username,
|
|
146
|
+
platform: "twitter"
|
|
147
|
+
};
|
|
148
|
+
if (typeof localStorage !== "undefined") {
|
|
149
|
+
localStorage.setItem("followgate_token", token);
|
|
150
|
+
localStorage.setItem(
|
|
151
|
+
"followgate_user",
|
|
152
|
+
JSON.stringify(this.currentUser)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const url = new URL(window.location.href);
|
|
156
|
+
url.searchParams.delete("followgate_token");
|
|
157
|
+
url.searchParams.delete("followgate_user");
|
|
158
|
+
window.history.replaceState({}, "", url.toString());
|
|
159
|
+
this.emit("authenticated", this.currentUser);
|
|
160
|
+
if (this.config?.debug) {
|
|
161
|
+
console.log("[FollowGate] User authenticated:", username);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Restore session from localStorage
|
|
167
|
+
*/
|
|
168
|
+
restoreSession() {
|
|
169
|
+
if (typeof localStorage === "undefined") return;
|
|
170
|
+
const pendingJson = localStorage.getItem("followgate_pending_username");
|
|
171
|
+
if (pendingJson) {
|
|
172
|
+
try {
|
|
173
|
+
this.pendingUsername = JSON.parse(pendingJson);
|
|
174
|
+
if (this.config?.debug) {
|
|
175
|
+
console.log("[FollowGate] Restored pending username state");
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
} catch {
|
|
179
|
+
localStorage.removeItem("followgate_pending_username");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const token = localStorage.getItem("followgate_token");
|
|
183
|
+
const userJson = localStorage.getItem("followgate_user");
|
|
184
|
+
if (token && userJson) {
|
|
185
|
+
try {
|
|
186
|
+
this.authToken = token;
|
|
187
|
+
this.currentUser = JSON.parse(userJson);
|
|
188
|
+
} catch {
|
|
189
|
+
localStorage.removeItem("followgate_token");
|
|
190
|
+
localStorage.removeItem("followgate_user");
|
|
191
|
+
}
|
|
16
192
|
}
|
|
17
193
|
}
|
|
18
194
|
/**
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@followgate/js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "FollowGate SDK - Grow your audience with every download. Require social actions (follow, repost) before users can access your app.",
|
|
5
|
-
"author": "FollowGate <hello@followgate.
|
|
6
|
-
"homepage": "https://followgate.
|
|
5
|
+
"author": "FollowGate <hello@followgate.app>",
|
|
6
|
+
"homepage": "https://followgate.app",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.mjs",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
46
|
-
"url": "https://github.com/JustFF5/FollowGate.git",
|
|
46
|
+
"url": "git+https://github.com/JustFF5/FollowGate.git",
|
|
47
47
|
"directory": "packages/sdk"
|
|
48
48
|
},
|
|
49
49
|
"bugs": {
|