@followgate/js 0.4.0 → 0.5.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 +65 -50
- package/dist/index.d.ts +65 -50
- package/dist/index.js +168 -192
- package/dist/index.mjs +168 -192
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -24,109 +24,112 @@ declare class FollowGateError extends Error {
|
|
|
24
24
|
constructor(message: string, code: string, hint?: string | undefined);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Complete action options
|
|
28
28
|
*/
|
|
29
|
-
interface
|
|
29
|
+
interface CompleteOptions {
|
|
30
30
|
platform: Platform;
|
|
31
31
|
action: SocialAction;
|
|
32
32
|
target: string;
|
|
33
|
-
userId?: string;
|
|
34
33
|
}
|
|
35
|
-
/**
|
|
36
|
-
* LinkedIn target type
|
|
37
|
-
*/
|
|
38
|
-
type LinkedInTargetType = 'company' | 'profile';
|
|
39
34
|
/**
|
|
40
35
|
* Event types
|
|
41
36
|
*/
|
|
42
|
-
type EventType = 'complete' | 'error' | '
|
|
37
|
+
type EventType = 'complete' | 'error' | 'unlocked';
|
|
43
38
|
/**
|
|
44
39
|
* Event callback
|
|
45
40
|
*/
|
|
46
41
|
type EventCallback = (data: unknown) => void;
|
|
47
42
|
/**
|
|
48
|
-
*
|
|
43
|
+
* User info (stored locally)
|
|
49
44
|
*/
|
|
50
|
-
interface
|
|
51
|
-
userId: string;
|
|
45
|
+
interface UserInfo {
|
|
52
46
|
username: string;
|
|
53
47
|
platform: Platform;
|
|
54
48
|
}
|
|
55
49
|
/**
|
|
56
|
-
*
|
|
57
|
-
*/
|
|
58
|
-
interface PendingUsernameState {
|
|
59
|
-
needsUsername: true;
|
|
60
|
-
token: string;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Authentication options
|
|
50
|
+
* Unlock status
|
|
64
51
|
*/
|
|
65
|
-
interface
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
popup?: boolean;
|
|
52
|
+
interface UnlockStatus {
|
|
53
|
+
unlocked: boolean;
|
|
54
|
+
username?: string;
|
|
55
|
+
completedActions?: CompleteOptions[];
|
|
70
56
|
}
|
|
71
57
|
/**
|
|
72
58
|
* FollowGate SDK Client
|
|
59
|
+
*
|
|
60
|
+
* Simple username-based flow:
|
|
61
|
+
* 1. User enters username
|
|
62
|
+
* 2. User clicks intent URLs to follow/repost
|
|
63
|
+
* 3. User confirms they did it
|
|
64
|
+
* 4. App is unlocked
|
|
65
|
+
*
|
|
66
|
+
* No OAuth required!
|
|
73
67
|
*/
|
|
74
68
|
declare class FollowGateClient {
|
|
75
69
|
private config;
|
|
76
70
|
private listeners;
|
|
77
71
|
private currentUser;
|
|
78
|
-
private
|
|
79
|
-
private pendingUsername;
|
|
72
|
+
private completedActions;
|
|
80
73
|
/**
|
|
81
74
|
* Initialize the SDK
|
|
82
75
|
* @throws {FollowGateError} If configuration is invalid
|
|
83
76
|
*/
|
|
84
77
|
init(config: FollowGateConfig): void;
|
|
85
78
|
/**
|
|
86
|
-
*
|
|
87
|
-
* This
|
|
79
|
+
* Set the user's social username
|
|
80
|
+
* This is the main entry point - no OAuth needed!
|
|
88
81
|
*/
|
|
89
|
-
|
|
82
|
+
setUsername(username: string, platform?: Platform): void;
|
|
90
83
|
/**
|
|
91
|
-
* Get current
|
|
84
|
+
* Get current user
|
|
92
85
|
*/
|
|
93
|
-
getUser():
|
|
86
|
+
getUser(): UserInfo | null;
|
|
94
87
|
/**
|
|
95
|
-
* Check if
|
|
88
|
+
* Check if username is set
|
|
96
89
|
*/
|
|
97
|
-
|
|
90
|
+
hasUsername(): boolean;
|
|
98
91
|
/**
|
|
99
|
-
*
|
|
92
|
+
* Clear stored session
|
|
100
93
|
*/
|
|
101
|
-
|
|
94
|
+
reset(): void;
|
|
102
95
|
/**
|
|
103
|
-
*
|
|
96
|
+
* Get follow intent URL for a platform
|
|
104
97
|
*/
|
|
105
|
-
|
|
98
|
+
getFollowUrl(platform: Platform, target: string): string;
|
|
106
99
|
/**
|
|
107
|
-
*
|
|
100
|
+
* Get repost/retweet intent URL for a platform
|
|
108
101
|
*/
|
|
109
|
-
|
|
102
|
+
getRepostUrl(platform: Platform, target: string): string;
|
|
110
103
|
/**
|
|
111
|
-
*
|
|
104
|
+
* Get like intent URL for a platform
|
|
112
105
|
*/
|
|
113
|
-
|
|
106
|
+
getLikeUrl(platform: Platform, target: string): string;
|
|
114
107
|
/**
|
|
115
|
-
*
|
|
108
|
+
* Open intent URL in new window
|
|
116
109
|
*/
|
|
117
|
-
|
|
110
|
+
openIntent(options: CompleteOptions): Promise<void>;
|
|
118
111
|
/**
|
|
119
|
-
*
|
|
112
|
+
* Mark an action as completed (trust-first)
|
|
113
|
+
* Call this when user confirms they did the action
|
|
120
114
|
*/
|
|
121
|
-
|
|
115
|
+
complete(options: CompleteOptions): Promise<void>;
|
|
122
116
|
/**
|
|
123
|
-
*
|
|
117
|
+
* Mark the gate as unlocked
|
|
118
|
+
* Call this when all required actions are done
|
|
124
119
|
*/
|
|
125
|
-
|
|
120
|
+
unlock(): Promise<void>;
|
|
126
121
|
/**
|
|
127
|
-
*
|
|
122
|
+
* Check if gate is unlocked
|
|
128
123
|
*/
|
|
129
|
-
|
|
124
|
+
isUnlocked(): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Get unlock status with details
|
|
127
|
+
*/
|
|
128
|
+
getUnlockStatus(): UnlockStatus;
|
|
129
|
+
/**
|
|
130
|
+
* Get completed actions
|
|
131
|
+
*/
|
|
132
|
+
getCompletedActions(): CompleteOptions[];
|
|
130
133
|
/**
|
|
131
134
|
* Register event listener
|
|
132
135
|
*/
|
|
@@ -135,6 +138,14 @@ declare class FollowGateClient {
|
|
|
135
138
|
* Remove event listener
|
|
136
139
|
*/
|
|
137
140
|
off(event: EventType, callback: EventCallback): void;
|
|
141
|
+
/**
|
|
142
|
+
* Restore session from localStorage
|
|
143
|
+
*/
|
|
144
|
+
private restoreSession;
|
|
145
|
+
/**
|
|
146
|
+
* Save completed actions to localStorage
|
|
147
|
+
*/
|
|
148
|
+
private saveCompletedActions;
|
|
138
149
|
/**
|
|
139
150
|
* Build intent URL for platform
|
|
140
151
|
*/
|
|
@@ -142,8 +153,12 @@ declare class FollowGateClient {
|
|
|
142
153
|
private buildTwitterUrl;
|
|
143
154
|
private buildBlueskyUrl;
|
|
144
155
|
private buildLinkedInUrl;
|
|
156
|
+
/**
|
|
157
|
+
* Track analytics event
|
|
158
|
+
*/
|
|
159
|
+
private trackEvent;
|
|
145
160
|
private emit;
|
|
146
161
|
}
|
|
147
162
|
declare const FollowGate: FollowGateClient;
|
|
148
163
|
|
|
149
|
-
export { type
|
|
164
|
+
export { type CompleteOptions, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, FollowGateError, type Platform, type SocialAction, type UnlockStatus, type UserInfo };
|
package/dist/index.d.ts
CHANGED
|
@@ -24,109 +24,112 @@ declare class FollowGateError extends Error {
|
|
|
24
24
|
constructor(message: string, code: string, hint?: string | undefined);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Complete action options
|
|
28
28
|
*/
|
|
29
|
-
interface
|
|
29
|
+
interface CompleteOptions {
|
|
30
30
|
platform: Platform;
|
|
31
31
|
action: SocialAction;
|
|
32
32
|
target: string;
|
|
33
|
-
userId?: string;
|
|
34
33
|
}
|
|
35
|
-
/**
|
|
36
|
-
* LinkedIn target type
|
|
37
|
-
*/
|
|
38
|
-
type LinkedInTargetType = 'company' | 'profile';
|
|
39
34
|
/**
|
|
40
35
|
* Event types
|
|
41
36
|
*/
|
|
42
|
-
type EventType = 'complete' | 'error' | '
|
|
37
|
+
type EventType = 'complete' | 'error' | 'unlocked';
|
|
43
38
|
/**
|
|
44
39
|
* Event callback
|
|
45
40
|
*/
|
|
46
41
|
type EventCallback = (data: unknown) => void;
|
|
47
42
|
/**
|
|
48
|
-
*
|
|
43
|
+
* User info (stored locally)
|
|
49
44
|
*/
|
|
50
|
-
interface
|
|
51
|
-
userId: string;
|
|
45
|
+
interface UserInfo {
|
|
52
46
|
username: string;
|
|
53
47
|
platform: Platform;
|
|
54
48
|
}
|
|
55
49
|
/**
|
|
56
|
-
*
|
|
57
|
-
*/
|
|
58
|
-
interface PendingUsernameState {
|
|
59
|
-
needsUsername: true;
|
|
60
|
-
token: string;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Authentication options
|
|
50
|
+
* Unlock status
|
|
64
51
|
*/
|
|
65
|
-
interface
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
popup?: boolean;
|
|
52
|
+
interface UnlockStatus {
|
|
53
|
+
unlocked: boolean;
|
|
54
|
+
username?: string;
|
|
55
|
+
completedActions?: CompleteOptions[];
|
|
70
56
|
}
|
|
71
57
|
/**
|
|
72
58
|
* FollowGate SDK Client
|
|
59
|
+
*
|
|
60
|
+
* Simple username-based flow:
|
|
61
|
+
* 1. User enters username
|
|
62
|
+
* 2. User clicks intent URLs to follow/repost
|
|
63
|
+
* 3. User confirms they did it
|
|
64
|
+
* 4. App is unlocked
|
|
65
|
+
*
|
|
66
|
+
* No OAuth required!
|
|
73
67
|
*/
|
|
74
68
|
declare class FollowGateClient {
|
|
75
69
|
private config;
|
|
76
70
|
private listeners;
|
|
77
71
|
private currentUser;
|
|
78
|
-
private
|
|
79
|
-
private pendingUsername;
|
|
72
|
+
private completedActions;
|
|
80
73
|
/**
|
|
81
74
|
* Initialize the SDK
|
|
82
75
|
* @throws {FollowGateError} If configuration is invalid
|
|
83
76
|
*/
|
|
84
77
|
init(config: FollowGateConfig): void;
|
|
85
78
|
/**
|
|
86
|
-
*
|
|
87
|
-
* This
|
|
79
|
+
* Set the user's social username
|
|
80
|
+
* This is the main entry point - no OAuth needed!
|
|
88
81
|
*/
|
|
89
|
-
|
|
82
|
+
setUsername(username: string, platform?: Platform): void;
|
|
90
83
|
/**
|
|
91
|
-
* Get current
|
|
84
|
+
* Get current user
|
|
92
85
|
*/
|
|
93
|
-
getUser():
|
|
86
|
+
getUser(): UserInfo | null;
|
|
94
87
|
/**
|
|
95
|
-
* Check if
|
|
88
|
+
* Check if username is set
|
|
96
89
|
*/
|
|
97
|
-
|
|
90
|
+
hasUsername(): boolean;
|
|
98
91
|
/**
|
|
99
|
-
*
|
|
92
|
+
* Clear stored session
|
|
100
93
|
*/
|
|
101
|
-
|
|
94
|
+
reset(): void;
|
|
102
95
|
/**
|
|
103
|
-
*
|
|
96
|
+
* Get follow intent URL for a platform
|
|
104
97
|
*/
|
|
105
|
-
|
|
98
|
+
getFollowUrl(platform: Platform, target: string): string;
|
|
106
99
|
/**
|
|
107
|
-
*
|
|
100
|
+
* Get repost/retweet intent URL for a platform
|
|
108
101
|
*/
|
|
109
|
-
|
|
102
|
+
getRepostUrl(platform: Platform, target: string): string;
|
|
110
103
|
/**
|
|
111
|
-
*
|
|
104
|
+
* Get like intent URL for a platform
|
|
112
105
|
*/
|
|
113
|
-
|
|
106
|
+
getLikeUrl(platform: Platform, target: string): string;
|
|
114
107
|
/**
|
|
115
|
-
*
|
|
108
|
+
* Open intent URL in new window
|
|
116
109
|
*/
|
|
117
|
-
|
|
110
|
+
openIntent(options: CompleteOptions): Promise<void>;
|
|
118
111
|
/**
|
|
119
|
-
*
|
|
112
|
+
* Mark an action as completed (trust-first)
|
|
113
|
+
* Call this when user confirms they did the action
|
|
120
114
|
*/
|
|
121
|
-
|
|
115
|
+
complete(options: CompleteOptions): Promise<void>;
|
|
122
116
|
/**
|
|
123
|
-
*
|
|
117
|
+
* Mark the gate as unlocked
|
|
118
|
+
* Call this when all required actions are done
|
|
124
119
|
*/
|
|
125
|
-
|
|
120
|
+
unlock(): Promise<void>;
|
|
126
121
|
/**
|
|
127
|
-
*
|
|
122
|
+
* Check if gate is unlocked
|
|
128
123
|
*/
|
|
129
|
-
|
|
124
|
+
isUnlocked(): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Get unlock status with details
|
|
127
|
+
*/
|
|
128
|
+
getUnlockStatus(): UnlockStatus;
|
|
129
|
+
/**
|
|
130
|
+
* Get completed actions
|
|
131
|
+
*/
|
|
132
|
+
getCompletedActions(): CompleteOptions[];
|
|
130
133
|
/**
|
|
131
134
|
* Register event listener
|
|
132
135
|
*/
|
|
@@ -135,6 +138,14 @@ declare class FollowGateClient {
|
|
|
135
138
|
* Remove event listener
|
|
136
139
|
*/
|
|
137
140
|
off(event: EventType, callback: EventCallback): void;
|
|
141
|
+
/**
|
|
142
|
+
* Restore session from localStorage
|
|
143
|
+
*/
|
|
144
|
+
private restoreSession;
|
|
145
|
+
/**
|
|
146
|
+
* Save completed actions to localStorage
|
|
147
|
+
*/
|
|
148
|
+
private saveCompletedActions;
|
|
138
149
|
/**
|
|
139
150
|
* Build intent URL for platform
|
|
140
151
|
*/
|
|
@@ -142,8 +153,12 @@ declare class FollowGateClient {
|
|
|
142
153
|
private buildTwitterUrl;
|
|
143
154
|
private buildBlueskyUrl;
|
|
144
155
|
private buildLinkedInUrl;
|
|
156
|
+
/**
|
|
157
|
+
* Track analytics event
|
|
158
|
+
*/
|
|
159
|
+
private trackEvent;
|
|
145
160
|
private emit;
|
|
146
161
|
}
|
|
147
162
|
declare const FollowGate: FollowGateClient;
|
|
148
163
|
|
|
149
|
-
export { type
|
|
164
|
+
export { type CompleteOptions, type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, FollowGateError, type Platform, type SocialAction, type UnlockStatus, type UserInfo };
|
package/dist/index.js
CHANGED
|
@@ -41,8 +41,7 @@ var FollowGateClient = class {
|
|
|
41
41
|
config = null;
|
|
42
42
|
listeners = /* @__PURE__ */ new Map();
|
|
43
43
|
currentUser = null;
|
|
44
|
-
|
|
45
|
-
pendingUsername = null;
|
|
44
|
+
completedActions = [];
|
|
46
45
|
/**
|
|
47
46
|
* Initialize the SDK
|
|
48
47
|
* @throws {FollowGateError} If configuration is invalid
|
|
@@ -87,7 +86,6 @@ var FollowGateClient = class {
|
|
|
87
86
|
...config,
|
|
88
87
|
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
89
88
|
};
|
|
90
|
-
this.handleAuthCallback();
|
|
91
89
|
this.restoreSession();
|
|
92
90
|
if (config.debug) {
|
|
93
91
|
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
@@ -101,253 +99,171 @@ var FollowGateClient = class {
|
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
/**
|
|
104
|
-
*
|
|
105
|
-
* This
|
|
102
|
+
* Set the user's social username
|
|
103
|
+
* This is the main entry point - no OAuth needed!
|
|
106
104
|
*/
|
|
107
|
-
|
|
105
|
+
setUsername(username, platform = "twitter") {
|
|
108
106
|
if (!this.config) {
|
|
109
107
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
110
108
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
const normalizedUsername = username.startsWith("@") ? username.slice(1) : username;
|
|
110
|
+
this.currentUser = {
|
|
111
|
+
username: normalizedUsername,
|
|
112
|
+
platform
|
|
113
|
+
};
|
|
114
|
+
if (typeof localStorage !== "undefined") {
|
|
115
|
+
localStorage.setItem("followgate_user", JSON.stringify(this.currentUser));
|
|
115
116
|
}
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
authUrl,
|
|
119
|
-
"followgate_auth",
|
|
120
|
-
"width=600,height=700"
|
|
121
|
-
);
|
|
122
|
-
if (!popup) {
|
|
123
|
-
this.emit("error", { message: "Popup blocked" });
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
window.location.href = authUrl;
|
|
117
|
+
if (this.config.debug) {
|
|
118
|
+
console.log("[FollowGate] Username set:", normalizedUsername);
|
|
127
119
|
}
|
|
128
120
|
}
|
|
129
121
|
/**
|
|
130
|
-
* Get current
|
|
122
|
+
* Get current user
|
|
131
123
|
*/
|
|
132
124
|
getUser() {
|
|
133
125
|
return this.currentUser;
|
|
134
126
|
}
|
|
135
127
|
/**
|
|
136
|
-
* Check if
|
|
128
|
+
* Check if username is set
|
|
137
129
|
*/
|
|
138
|
-
|
|
139
|
-
return this.currentUser !== null
|
|
130
|
+
hasUsername() {
|
|
131
|
+
return this.currentUser !== null;
|
|
140
132
|
}
|
|
141
133
|
/**
|
|
142
|
-
*
|
|
134
|
+
* Clear stored session
|
|
143
135
|
*/
|
|
144
|
-
|
|
136
|
+
reset() {
|
|
145
137
|
this.currentUser = null;
|
|
146
|
-
this.
|
|
147
|
-
this.pendingUsername = null;
|
|
138
|
+
this.completedActions = [];
|
|
148
139
|
if (typeof localStorage !== "undefined") {
|
|
149
|
-
localStorage.removeItem("followgate_token");
|
|
150
140
|
localStorage.removeItem("followgate_user");
|
|
151
|
-
localStorage.removeItem("
|
|
141
|
+
localStorage.removeItem("followgate_actions");
|
|
142
|
+
localStorage.removeItem("followgate_unlocked");
|
|
152
143
|
}
|
|
153
144
|
if (this.config?.debug) {
|
|
154
|
-
console.log("[FollowGate]
|
|
145
|
+
console.log("[FollowGate] Session reset");
|
|
155
146
|
}
|
|
156
147
|
}
|
|
148
|
+
// ============================================
|
|
149
|
+
// Intent URL Methods
|
|
150
|
+
// ============================================
|
|
157
151
|
/**
|
|
158
|
-
*
|
|
152
|
+
* Get follow intent URL for a platform
|
|
159
153
|
*/
|
|
160
|
-
|
|
161
|
-
return this.
|
|
154
|
+
getFollowUrl(platform, target) {
|
|
155
|
+
return this.buildIntentUrl({ platform, action: "follow", target });
|
|
162
156
|
}
|
|
163
157
|
/**
|
|
164
|
-
*
|
|
158
|
+
* Get repost/retweet intent URL for a platform
|
|
165
159
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
}
|
|
160
|
+
getRepostUrl(platform, target) {
|
|
161
|
+
return this.buildIntentUrl({ platform, action: "repost", target });
|
|
189
162
|
}
|
|
190
163
|
/**
|
|
191
|
-
*
|
|
164
|
+
* Get like intent URL for a platform
|
|
192
165
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const params = new URLSearchParams(window.location.search);
|
|
196
|
-
const token = params.get("followgate_token");
|
|
197
|
-
const username = params.get("followgate_user");
|
|
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) {
|
|
218
|
-
this.authToken = token;
|
|
219
|
-
this.currentUser = {
|
|
220
|
-
userId: "",
|
|
221
|
-
// Will be set from token verification
|
|
222
|
-
username,
|
|
223
|
-
platform: "twitter"
|
|
224
|
-
};
|
|
225
|
-
if (typeof localStorage !== "undefined") {
|
|
226
|
-
localStorage.setItem("followgate_token", token);
|
|
227
|
-
localStorage.setItem(
|
|
228
|
-
"followgate_user",
|
|
229
|
-
JSON.stringify(this.currentUser)
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
const url = new URL(window.location.href);
|
|
233
|
-
url.searchParams.delete("followgate_token");
|
|
234
|
-
url.searchParams.delete("followgate_user");
|
|
235
|
-
window.history.replaceState({}, "", url.toString());
|
|
236
|
-
this.emit("authenticated", this.currentUser);
|
|
237
|
-
if (this.config?.debug) {
|
|
238
|
-
console.log("[FollowGate] User authenticated:", username);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
166
|
+
getLikeUrl(platform, target) {
|
|
167
|
+
return this.buildIntentUrl({ platform, action: "like", target });
|
|
241
168
|
}
|
|
242
169
|
/**
|
|
243
|
-
*
|
|
170
|
+
* Open intent URL in new window
|
|
244
171
|
*/
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
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
|
-
}
|
|
172
|
+
async openIntent(options) {
|
|
173
|
+
if (!this.config) {
|
|
174
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
258
175
|
}
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
this.authToken = token;
|
|
264
|
-
this.currentUser = JSON.parse(userJson);
|
|
265
|
-
} catch {
|
|
266
|
-
localStorage.removeItem("followgate_token");
|
|
267
|
-
localStorage.removeItem("followgate_user");
|
|
268
|
-
}
|
|
176
|
+
const url = this.buildIntentUrl(options);
|
|
177
|
+
if (this.config.debug) {
|
|
178
|
+
console.log("[FollowGate] Opening intent:", url);
|
|
269
179
|
}
|
|
180
|
+
await this.trackEvent("intent_opened", { ...options });
|
|
181
|
+
window.open(url, "_blank", "width=600,height=700");
|
|
270
182
|
}
|
|
183
|
+
// ============================================
|
|
184
|
+
// Completion Methods
|
|
185
|
+
// ============================================
|
|
271
186
|
/**
|
|
272
|
-
*
|
|
187
|
+
* Mark an action as completed (trust-first)
|
|
188
|
+
* Call this when user confirms they did the action
|
|
273
189
|
*/
|
|
274
|
-
async
|
|
190
|
+
async complete(options) {
|
|
275
191
|
if (!this.config) {
|
|
276
192
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
277
193
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
194
|
+
if (!this.currentUser) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
"[FollowGate] No username set. Call setUsername() first."
|
|
197
|
+
);
|
|
281
198
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
199
|
+
const alreadyCompleted = this.completedActions.some(
|
|
200
|
+
(a) => a.platform === options.platform && a.action === options.action && a.target === options.target
|
|
201
|
+
);
|
|
202
|
+
if (!alreadyCompleted) {
|
|
203
|
+
this.completedActions.push(options);
|
|
204
|
+
this.saveCompletedActions();
|
|
287
205
|
}
|
|
288
|
-
await this.trackEvent("
|
|
206
|
+
await this.trackEvent("action_completed", {
|
|
207
|
+
...options,
|
|
208
|
+
username: this.currentUser.username
|
|
209
|
+
});
|
|
289
210
|
this.emit("complete", {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
target: options.target
|
|
211
|
+
...options,
|
|
212
|
+
username: this.currentUser.username
|
|
293
213
|
});
|
|
214
|
+
if (this.config.debug) {
|
|
215
|
+
console.log("[FollowGate] Action completed:", options);
|
|
216
|
+
}
|
|
294
217
|
}
|
|
295
218
|
/**
|
|
296
|
-
*
|
|
219
|
+
* Mark the gate as unlocked
|
|
220
|
+
* Call this when all required actions are done
|
|
297
221
|
*/
|
|
298
|
-
async
|
|
222
|
+
async unlock() {
|
|
299
223
|
if (!this.config) {
|
|
300
224
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
301
225
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
});
|
|
316
|
-
const data = await response.json();
|
|
317
|
-
return data.success && data.data?.verified === true;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
if (this.config.debug) {
|
|
320
|
-
console.error("[FollowGate] Verification error:", error);
|
|
321
|
-
}
|
|
322
|
-
return false;
|
|
226
|
+
if (typeof localStorage !== "undefined") {
|
|
227
|
+
localStorage.setItem("followgate_unlocked", "true");
|
|
228
|
+
}
|
|
229
|
+
await this.trackEvent("gate_unlocked", {
|
|
230
|
+
username: this.currentUser?.username,
|
|
231
|
+
actions: this.completedActions
|
|
232
|
+
});
|
|
233
|
+
this.emit("unlocked", {
|
|
234
|
+
username: this.currentUser?.username,
|
|
235
|
+
actions: this.completedActions
|
|
236
|
+
});
|
|
237
|
+
if (this.config.debug) {
|
|
238
|
+
console.log("[FollowGate] Gate unlocked!");
|
|
323
239
|
}
|
|
324
240
|
}
|
|
325
241
|
/**
|
|
326
|
-
*
|
|
242
|
+
* Check if gate is unlocked
|
|
327
243
|
*/
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
await fetch(`${this.config.apiUrl}/api/v1/events`, {
|
|
332
|
-
method: "POST",
|
|
333
|
-
headers: {
|
|
334
|
-
"Content-Type": "application/json",
|
|
335
|
-
"X-API-Key": this.config.apiKey
|
|
336
|
-
},
|
|
337
|
-
body: JSON.stringify({
|
|
338
|
-
event,
|
|
339
|
-
platform: options.platform,
|
|
340
|
-
action: options.action,
|
|
341
|
-
target: options.target,
|
|
342
|
-
externalUserId: options.userId
|
|
343
|
-
})
|
|
344
|
-
});
|
|
345
|
-
} catch (error) {
|
|
346
|
-
if (this.config.debug) {
|
|
347
|
-
console.warn("[FollowGate] Failed to track event:", error);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
244
|
+
isUnlocked() {
|
|
245
|
+
if (typeof localStorage === "undefined") return false;
|
|
246
|
+
return localStorage.getItem("followgate_unlocked") === "true";
|
|
350
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Get unlock status with details
|
|
250
|
+
*/
|
|
251
|
+
getUnlockStatus() {
|
|
252
|
+
return {
|
|
253
|
+
unlocked: this.isUnlocked(),
|
|
254
|
+
username: this.currentUser?.username,
|
|
255
|
+
completedActions: [...this.completedActions]
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get completed actions
|
|
260
|
+
*/
|
|
261
|
+
getCompletedActions() {
|
|
262
|
+
return [...this.completedActions];
|
|
263
|
+
}
|
|
264
|
+
// ============================================
|
|
265
|
+
// Event System
|
|
266
|
+
// ============================================
|
|
351
267
|
/**
|
|
352
268
|
* Register event listener
|
|
353
269
|
*/
|
|
@@ -363,6 +279,42 @@ var FollowGateClient = class {
|
|
|
363
279
|
off(event, callback) {
|
|
364
280
|
this.listeners.get(event)?.delete(callback);
|
|
365
281
|
}
|
|
282
|
+
// ============================================
|
|
283
|
+
// Private Methods
|
|
284
|
+
// ============================================
|
|
285
|
+
/**
|
|
286
|
+
* Restore session from localStorage
|
|
287
|
+
*/
|
|
288
|
+
restoreSession() {
|
|
289
|
+
if (typeof localStorage === "undefined") return;
|
|
290
|
+
const userJson = localStorage.getItem("followgate_user");
|
|
291
|
+
if (userJson) {
|
|
292
|
+
try {
|
|
293
|
+
this.currentUser = JSON.parse(userJson);
|
|
294
|
+
} catch {
|
|
295
|
+
localStorage.removeItem("followgate_user");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const actionsJson = localStorage.getItem("followgate_actions");
|
|
299
|
+
if (actionsJson) {
|
|
300
|
+
try {
|
|
301
|
+
this.completedActions = JSON.parse(actionsJson);
|
|
302
|
+
} catch {
|
|
303
|
+
localStorage.removeItem("followgate_actions");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Save completed actions to localStorage
|
|
309
|
+
*/
|
|
310
|
+
saveCompletedActions() {
|
|
311
|
+
if (typeof localStorage !== "undefined") {
|
|
312
|
+
localStorage.setItem(
|
|
313
|
+
"followgate_actions",
|
|
314
|
+
JSON.stringify(this.completedActions)
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
366
318
|
/**
|
|
367
319
|
* Build intent URL for platform
|
|
368
320
|
*/
|
|
@@ -424,6 +376,30 @@ var FollowGateClient = class {
|
|
|
424
376
|
throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
|
|
425
377
|
}
|
|
426
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Track analytics event
|
|
381
|
+
*/
|
|
382
|
+
async trackEvent(event, data) {
|
|
383
|
+
if (!this.config) return;
|
|
384
|
+
try {
|
|
385
|
+
await fetch(`${this.config.apiUrl}/api/v1/events`, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": "application/json",
|
|
389
|
+
"X-API-Key": this.config.apiKey
|
|
390
|
+
},
|
|
391
|
+
body: JSON.stringify({
|
|
392
|
+
event,
|
|
393
|
+
appId: this.config.appId,
|
|
394
|
+
...data
|
|
395
|
+
})
|
|
396
|
+
});
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (this.config.debug) {
|
|
399
|
+
console.warn("[FollowGate] Failed to track event:", error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
427
403
|
emit(event, data) {
|
|
428
404
|
this.listeners.get(event)?.forEach((callback) => callback(data));
|
|
429
405
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -15,8 +15,7 @@ var FollowGateClient = class {
|
|
|
15
15
|
config = null;
|
|
16
16
|
listeners = /* @__PURE__ */ new Map();
|
|
17
17
|
currentUser = null;
|
|
18
|
-
|
|
19
|
-
pendingUsername = null;
|
|
18
|
+
completedActions = [];
|
|
20
19
|
/**
|
|
21
20
|
* Initialize the SDK
|
|
22
21
|
* @throws {FollowGateError} If configuration is invalid
|
|
@@ -61,7 +60,6 @@ var FollowGateClient = class {
|
|
|
61
60
|
...config,
|
|
62
61
|
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
63
62
|
};
|
|
64
|
-
this.handleAuthCallback();
|
|
65
63
|
this.restoreSession();
|
|
66
64
|
if (config.debug) {
|
|
67
65
|
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
@@ -75,253 +73,171 @@ var FollowGateClient = class {
|
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
/**
|
|
78
|
-
*
|
|
79
|
-
* This
|
|
76
|
+
* Set the user's social username
|
|
77
|
+
* This is the main entry point - no OAuth needed!
|
|
80
78
|
*/
|
|
81
|
-
|
|
79
|
+
setUsername(username, platform = "twitter") {
|
|
82
80
|
if (!this.config) {
|
|
83
81
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
84
82
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const normalizedUsername = username.startsWith("@") ? username.slice(1) : username;
|
|
84
|
+
this.currentUser = {
|
|
85
|
+
username: normalizedUsername,
|
|
86
|
+
platform
|
|
87
|
+
};
|
|
88
|
+
if (typeof localStorage !== "undefined") {
|
|
89
|
+
localStorage.setItem("followgate_user", JSON.stringify(this.currentUser));
|
|
89
90
|
}
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
authUrl,
|
|
93
|
-
"followgate_auth",
|
|
94
|
-
"width=600,height=700"
|
|
95
|
-
);
|
|
96
|
-
if (!popup) {
|
|
97
|
-
this.emit("error", { message: "Popup blocked" });
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
window.location.href = authUrl;
|
|
91
|
+
if (this.config.debug) {
|
|
92
|
+
console.log("[FollowGate] Username set:", normalizedUsername);
|
|
101
93
|
}
|
|
102
94
|
}
|
|
103
95
|
/**
|
|
104
|
-
* Get current
|
|
96
|
+
* Get current user
|
|
105
97
|
*/
|
|
106
98
|
getUser() {
|
|
107
99
|
return this.currentUser;
|
|
108
100
|
}
|
|
109
101
|
/**
|
|
110
|
-
* Check if
|
|
102
|
+
* Check if username is set
|
|
111
103
|
*/
|
|
112
|
-
|
|
113
|
-
return this.currentUser !== null
|
|
104
|
+
hasUsername() {
|
|
105
|
+
return this.currentUser !== null;
|
|
114
106
|
}
|
|
115
107
|
/**
|
|
116
|
-
*
|
|
108
|
+
* Clear stored session
|
|
117
109
|
*/
|
|
118
|
-
|
|
110
|
+
reset() {
|
|
119
111
|
this.currentUser = null;
|
|
120
|
-
this.
|
|
121
|
-
this.pendingUsername = null;
|
|
112
|
+
this.completedActions = [];
|
|
122
113
|
if (typeof localStorage !== "undefined") {
|
|
123
|
-
localStorage.removeItem("followgate_token");
|
|
124
114
|
localStorage.removeItem("followgate_user");
|
|
125
|
-
localStorage.removeItem("
|
|
115
|
+
localStorage.removeItem("followgate_actions");
|
|
116
|
+
localStorage.removeItem("followgate_unlocked");
|
|
126
117
|
}
|
|
127
118
|
if (this.config?.debug) {
|
|
128
|
-
console.log("[FollowGate]
|
|
119
|
+
console.log("[FollowGate] Session reset");
|
|
129
120
|
}
|
|
130
121
|
}
|
|
122
|
+
// ============================================
|
|
123
|
+
// Intent URL Methods
|
|
124
|
+
// ============================================
|
|
131
125
|
/**
|
|
132
|
-
*
|
|
126
|
+
* Get follow intent URL for a platform
|
|
133
127
|
*/
|
|
134
|
-
|
|
135
|
-
return this.
|
|
128
|
+
getFollowUrl(platform, target) {
|
|
129
|
+
return this.buildIntentUrl({ platform, action: "follow", target });
|
|
136
130
|
}
|
|
137
131
|
/**
|
|
138
|
-
*
|
|
132
|
+
* Get repost/retweet intent URL for a platform
|
|
139
133
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
}
|
|
134
|
+
getRepostUrl(platform, target) {
|
|
135
|
+
return this.buildIntentUrl({ platform, action: "repost", target });
|
|
163
136
|
}
|
|
164
137
|
/**
|
|
165
|
-
*
|
|
138
|
+
* Get like intent URL for a platform
|
|
166
139
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const params = new URLSearchParams(window.location.search);
|
|
170
|
-
const token = params.get("followgate_token");
|
|
171
|
-
const username = params.get("followgate_user");
|
|
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) {
|
|
192
|
-
this.authToken = token;
|
|
193
|
-
this.currentUser = {
|
|
194
|
-
userId: "",
|
|
195
|
-
// Will be set from token verification
|
|
196
|
-
username,
|
|
197
|
-
platform: "twitter"
|
|
198
|
-
};
|
|
199
|
-
if (typeof localStorage !== "undefined") {
|
|
200
|
-
localStorage.setItem("followgate_token", token);
|
|
201
|
-
localStorage.setItem(
|
|
202
|
-
"followgate_user",
|
|
203
|
-
JSON.stringify(this.currentUser)
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
const url = new URL(window.location.href);
|
|
207
|
-
url.searchParams.delete("followgate_token");
|
|
208
|
-
url.searchParams.delete("followgate_user");
|
|
209
|
-
window.history.replaceState({}, "", url.toString());
|
|
210
|
-
this.emit("authenticated", this.currentUser);
|
|
211
|
-
if (this.config?.debug) {
|
|
212
|
-
console.log("[FollowGate] User authenticated:", username);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
140
|
+
getLikeUrl(platform, target) {
|
|
141
|
+
return this.buildIntentUrl({ platform, action: "like", target });
|
|
215
142
|
}
|
|
216
143
|
/**
|
|
217
|
-
*
|
|
144
|
+
* Open intent URL in new window
|
|
218
145
|
*/
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
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
|
-
}
|
|
146
|
+
async openIntent(options) {
|
|
147
|
+
if (!this.config) {
|
|
148
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
232
149
|
}
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
this.authToken = token;
|
|
238
|
-
this.currentUser = JSON.parse(userJson);
|
|
239
|
-
} catch {
|
|
240
|
-
localStorage.removeItem("followgate_token");
|
|
241
|
-
localStorage.removeItem("followgate_user");
|
|
242
|
-
}
|
|
150
|
+
const url = this.buildIntentUrl(options);
|
|
151
|
+
if (this.config.debug) {
|
|
152
|
+
console.log("[FollowGate] Opening intent:", url);
|
|
243
153
|
}
|
|
154
|
+
await this.trackEvent("intent_opened", { ...options });
|
|
155
|
+
window.open(url, "_blank", "width=600,height=700");
|
|
244
156
|
}
|
|
157
|
+
// ============================================
|
|
158
|
+
// Completion Methods
|
|
159
|
+
// ============================================
|
|
245
160
|
/**
|
|
246
|
-
*
|
|
161
|
+
* Mark an action as completed (trust-first)
|
|
162
|
+
* Call this when user confirms they did the action
|
|
247
163
|
*/
|
|
248
|
-
async
|
|
164
|
+
async complete(options) {
|
|
249
165
|
if (!this.config) {
|
|
250
166
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
251
167
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
168
|
+
if (!this.currentUser) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"[FollowGate] No username set. Call setUsername() first."
|
|
171
|
+
);
|
|
255
172
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
173
|
+
const alreadyCompleted = this.completedActions.some(
|
|
174
|
+
(a) => a.platform === options.platform && a.action === options.action && a.target === options.target
|
|
175
|
+
);
|
|
176
|
+
if (!alreadyCompleted) {
|
|
177
|
+
this.completedActions.push(options);
|
|
178
|
+
this.saveCompletedActions();
|
|
261
179
|
}
|
|
262
|
-
await this.trackEvent("
|
|
180
|
+
await this.trackEvent("action_completed", {
|
|
181
|
+
...options,
|
|
182
|
+
username: this.currentUser.username
|
|
183
|
+
});
|
|
263
184
|
this.emit("complete", {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
target: options.target
|
|
185
|
+
...options,
|
|
186
|
+
username: this.currentUser.username
|
|
267
187
|
});
|
|
188
|
+
if (this.config.debug) {
|
|
189
|
+
console.log("[FollowGate] Action completed:", options);
|
|
190
|
+
}
|
|
268
191
|
}
|
|
269
192
|
/**
|
|
270
|
-
*
|
|
193
|
+
* Mark the gate as unlocked
|
|
194
|
+
* Call this when all required actions are done
|
|
271
195
|
*/
|
|
272
|
-
async
|
|
196
|
+
async unlock() {
|
|
273
197
|
if (!this.config) {
|
|
274
198
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
275
199
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
const data = await response.json();
|
|
291
|
-
return data.success && data.data?.verified === true;
|
|
292
|
-
} catch (error) {
|
|
293
|
-
if (this.config.debug) {
|
|
294
|
-
console.error("[FollowGate] Verification error:", error);
|
|
295
|
-
}
|
|
296
|
-
return false;
|
|
200
|
+
if (typeof localStorage !== "undefined") {
|
|
201
|
+
localStorage.setItem("followgate_unlocked", "true");
|
|
202
|
+
}
|
|
203
|
+
await this.trackEvent("gate_unlocked", {
|
|
204
|
+
username: this.currentUser?.username,
|
|
205
|
+
actions: this.completedActions
|
|
206
|
+
});
|
|
207
|
+
this.emit("unlocked", {
|
|
208
|
+
username: this.currentUser?.username,
|
|
209
|
+
actions: this.completedActions
|
|
210
|
+
});
|
|
211
|
+
if (this.config.debug) {
|
|
212
|
+
console.log("[FollowGate] Gate unlocked!");
|
|
297
213
|
}
|
|
298
214
|
}
|
|
299
215
|
/**
|
|
300
|
-
*
|
|
216
|
+
* Check if gate is unlocked
|
|
301
217
|
*/
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
await fetch(`${this.config.apiUrl}/api/v1/events`, {
|
|
306
|
-
method: "POST",
|
|
307
|
-
headers: {
|
|
308
|
-
"Content-Type": "application/json",
|
|
309
|
-
"X-API-Key": this.config.apiKey
|
|
310
|
-
},
|
|
311
|
-
body: JSON.stringify({
|
|
312
|
-
event,
|
|
313
|
-
platform: options.platform,
|
|
314
|
-
action: options.action,
|
|
315
|
-
target: options.target,
|
|
316
|
-
externalUserId: options.userId
|
|
317
|
-
})
|
|
318
|
-
});
|
|
319
|
-
} catch (error) {
|
|
320
|
-
if (this.config.debug) {
|
|
321
|
-
console.warn("[FollowGate] Failed to track event:", error);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
218
|
+
isUnlocked() {
|
|
219
|
+
if (typeof localStorage === "undefined") return false;
|
|
220
|
+
return localStorage.getItem("followgate_unlocked") === "true";
|
|
324
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Get unlock status with details
|
|
224
|
+
*/
|
|
225
|
+
getUnlockStatus() {
|
|
226
|
+
return {
|
|
227
|
+
unlocked: this.isUnlocked(),
|
|
228
|
+
username: this.currentUser?.username,
|
|
229
|
+
completedActions: [...this.completedActions]
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get completed actions
|
|
234
|
+
*/
|
|
235
|
+
getCompletedActions() {
|
|
236
|
+
return [...this.completedActions];
|
|
237
|
+
}
|
|
238
|
+
// ============================================
|
|
239
|
+
// Event System
|
|
240
|
+
// ============================================
|
|
325
241
|
/**
|
|
326
242
|
* Register event listener
|
|
327
243
|
*/
|
|
@@ -337,6 +253,42 @@ var FollowGateClient = class {
|
|
|
337
253
|
off(event, callback) {
|
|
338
254
|
this.listeners.get(event)?.delete(callback);
|
|
339
255
|
}
|
|
256
|
+
// ============================================
|
|
257
|
+
// Private Methods
|
|
258
|
+
// ============================================
|
|
259
|
+
/**
|
|
260
|
+
* Restore session from localStorage
|
|
261
|
+
*/
|
|
262
|
+
restoreSession() {
|
|
263
|
+
if (typeof localStorage === "undefined") return;
|
|
264
|
+
const userJson = localStorage.getItem("followgate_user");
|
|
265
|
+
if (userJson) {
|
|
266
|
+
try {
|
|
267
|
+
this.currentUser = JSON.parse(userJson);
|
|
268
|
+
} catch {
|
|
269
|
+
localStorage.removeItem("followgate_user");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const actionsJson = localStorage.getItem("followgate_actions");
|
|
273
|
+
if (actionsJson) {
|
|
274
|
+
try {
|
|
275
|
+
this.completedActions = JSON.parse(actionsJson);
|
|
276
|
+
} catch {
|
|
277
|
+
localStorage.removeItem("followgate_actions");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Save completed actions to localStorage
|
|
283
|
+
*/
|
|
284
|
+
saveCompletedActions() {
|
|
285
|
+
if (typeof localStorage !== "undefined") {
|
|
286
|
+
localStorage.setItem(
|
|
287
|
+
"followgate_actions",
|
|
288
|
+
JSON.stringify(this.completedActions)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
340
292
|
/**
|
|
341
293
|
* Build intent URL for platform
|
|
342
294
|
*/
|
|
@@ -398,6 +350,30 @@ var FollowGateClient = class {
|
|
|
398
350
|
throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
|
|
399
351
|
}
|
|
400
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Track analytics event
|
|
355
|
+
*/
|
|
356
|
+
async trackEvent(event, data) {
|
|
357
|
+
if (!this.config) return;
|
|
358
|
+
try {
|
|
359
|
+
await fetch(`${this.config.apiUrl}/api/v1/events`, {
|
|
360
|
+
method: "POST",
|
|
361
|
+
headers: {
|
|
362
|
+
"Content-Type": "application/json",
|
|
363
|
+
"X-API-Key": this.config.apiKey
|
|
364
|
+
},
|
|
365
|
+
body: JSON.stringify({
|
|
366
|
+
event,
|
|
367
|
+
appId: this.config.appId,
|
|
368
|
+
...data
|
|
369
|
+
})
|
|
370
|
+
});
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (this.config.debug) {
|
|
373
|
+
console.warn("[FollowGate] Failed to track event:", error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
401
377
|
emit(event, data) {
|
|
402
378
|
this.listeners.get(event)?.forEach((callback) => callback(data));
|
|
403
379
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@followgate/js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|