@followgate/js 0.1.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 +159 -0
- package/dist/index.d.mts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +208 -0
- package/dist/index.mjs +182 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @followgate/js
|
|
2
|
+
|
|
3
|
+
**Grow your audience with every download.** Require social actions (follow, repost) before users can access your app.
|
|
4
|
+
|
|
5
|
+
FollowGate is inspired by [Hypeddit](https://hypeddit.com) (for musicians), but built for software developers and app creators.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @followgate/js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { FollowGate } from '@followgate/js';
|
|
17
|
+
|
|
18
|
+
// Initialize with your API credentials (get them at https://app.followgate.io)
|
|
19
|
+
FollowGate.init({
|
|
20
|
+
appId: 'your-app-id',
|
|
21
|
+
apiKey: 'fg_live_xxx',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Require a Twitter follow before granting access
|
|
25
|
+
FollowGate.open({
|
|
26
|
+
platform: 'twitter',
|
|
27
|
+
action: 'follow',
|
|
28
|
+
target: 'yourusername',
|
|
29
|
+
userId: 'user-123', // Optional: your app's user ID for tracking
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Supported Platforms
|
|
34
|
+
|
|
35
|
+
| Platform | Actions |
|
|
36
|
+
|----------|---------|
|
|
37
|
+
| Twitter/X | `follow`, `repost`, `like` |
|
|
38
|
+
| Bluesky | `follow`, `repost`, `like` |
|
|
39
|
+
| LinkedIn | `follow` |
|
|
40
|
+
|
|
41
|
+
## API Reference
|
|
42
|
+
|
|
43
|
+
### `FollowGate.init(config)`
|
|
44
|
+
|
|
45
|
+
Initialize the SDK with your credentials.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
FollowGate.init({
|
|
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
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `FollowGate.open(options)`
|
|
57
|
+
|
|
58
|
+
Open a social action popup/intent.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
await FollowGate.open({
|
|
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
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `FollowGate.verify(options)`
|
|
70
|
+
|
|
71
|
+
Verify if a user completed the action (Pro/Business tiers with OAuth).
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const isVerified = await FollowGate.verify({
|
|
75
|
+
platform: 'twitter',
|
|
76
|
+
action: 'follow',
|
|
77
|
+
target: 'yourusername',
|
|
78
|
+
userId: 'user-123',
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `FollowGate.on(event, callback)`
|
|
83
|
+
|
|
84
|
+
Listen for events.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
FollowGate.on('complete', (data) => {
|
|
88
|
+
console.log('User completed action:', data);
|
|
89
|
+
// Grant access to your app
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
FollowGate.on('error', (error) => {
|
|
93
|
+
console.error('Error:', error);
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Platform-Specific Notes
|
|
98
|
+
|
|
99
|
+
### Twitter/X
|
|
100
|
+
|
|
101
|
+
- `target` for `follow`: Twitter username (without @)
|
|
102
|
+
- `target` for `repost`/`like`: Tweet ID
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Follow
|
|
106
|
+
FollowGate.open({ platform: 'twitter', action: 'follow', target: 'elonmusk' });
|
|
107
|
+
|
|
108
|
+
// Repost a tweet
|
|
109
|
+
FollowGate.open({ platform: 'twitter', action: 'repost', target: '1234567890' });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Bluesky
|
|
113
|
+
|
|
114
|
+
- `target` for `follow`: Bluesky handle (e.g., `alice.bsky.social`)
|
|
115
|
+
- `target` for `repost`/`like`: Post path (e.g., `alice.bsky.social/post/xxx`)
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Follow
|
|
119
|
+
FollowGate.open({ platform: 'bluesky', action: 'follow', target: 'alice.bsky.social' });
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### LinkedIn
|
|
123
|
+
|
|
124
|
+
- `target` for `follow`: Company name or `in:username` for personal profiles
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Follow a company
|
|
128
|
+
FollowGate.open({ platform: 'linkedin', action: 'follow', target: 'microsoft' });
|
|
129
|
+
|
|
130
|
+
// Follow a personal profile
|
|
131
|
+
FollowGate.open({ platform: 'linkedin', action: 'follow', target: 'in:satyanadella' });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Pricing
|
|
135
|
+
|
|
136
|
+
| Tier | Price | Users | Verification |
|
|
137
|
+
|------|-------|-------|--------------|
|
|
138
|
+
| Free | $0 | 100 | Intent-URL only |
|
|
139
|
+
| Starter | $15/mo | 500 | Weekly sampling |
|
|
140
|
+
| Pro | $49/mo | 2,000 | OAuth verification |
|
|
141
|
+
| Business | $99/mo | 5,000+ | Daily verification |
|
|
142
|
+
|
|
143
|
+
## TypeScript
|
|
144
|
+
|
|
145
|
+
Full TypeScript support included. Types are exported:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import type { Platform, SocialAction, FollowGateConfig } from '@followgate/js';
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Links
|
|
152
|
+
|
|
153
|
+
- [Dashboard](https://app.followgate.io)
|
|
154
|
+
- [Documentation](https://followgate.io/docs)
|
|
155
|
+
- [GitHub](https://github.com/JustFF5/FollowGate)
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported social platforms
|
|
3
|
+
*/
|
|
4
|
+
type Platform = 'twitter' | 'bluesky' | 'linkedin';
|
|
5
|
+
/**
|
|
6
|
+
* Supported social actions
|
|
7
|
+
*/
|
|
8
|
+
type SocialAction = 'follow' | 'repost' | 'like';
|
|
9
|
+
/**
|
|
10
|
+
* SDK Configuration
|
|
11
|
+
*/
|
|
12
|
+
interface FollowGateConfig {
|
|
13
|
+
appId: string;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
apiUrl?: string;
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open action options
|
|
20
|
+
*/
|
|
21
|
+
interface OpenOptions {
|
|
22
|
+
platform: Platform;
|
|
23
|
+
action: SocialAction;
|
|
24
|
+
target: string;
|
|
25
|
+
userId?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* LinkedIn target type
|
|
29
|
+
*/
|
|
30
|
+
type LinkedInTargetType = 'company' | 'profile';
|
|
31
|
+
/**
|
|
32
|
+
* Event types
|
|
33
|
+
*/
|
|
34
|
+
type EventType = 'complete' | 'error' | 'cancel';
|
|
35
|
+
/**
|
|
36
|
+
* Event callback
|
|
37
|
+
*/
|
|
38
|
+
type EventCallback = (data: unknown) => void;
|
|
39
|
+
/**
|
|
40
|
+
* FollowGate SDK Client
|
|
41
|
+
*/
|
|
42
|
+
declare class FollowGateClient {
|
|
43
|
+
private config;
|
|
44
|
+
private listeners;
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the SDK
|
|
47
|
+
*/
|
|
48
|
+
init(config: FollowGateConfig): void;
|
|
49
|
+
/**
|
|
50
|
+
* Open social action popup
|
|
51
|
+
*/
|
|
52
|
+
open(options: OpenOptions): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Verify follow status (for Pro/Business tiers with OAuth)
|
|
55
|
+
*/
|
|
56
|
+
verify(options: OpenOptions): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Track analytics event
|
|
59
|
+
*/
|
|
60
|
+
private trackEvent;
|
|
61
|
+
/**
|
|
62
|
+
* Register event listener
|
|
63
|
+
*/
|
|
64
|
+
on(event: EventType, callback: EventCallback): void;
|
|
65
|
+
/**
|
|
66
|
+
* Remove event listener
|
|
67
|
+
*/
|
|
68
|
+
off(event: EventType, callback: EventCallback): void;
|
|
69
|
+
/**
|
|
70
|
+
* Build intent URL for platform
|
|
71
|
+
*/
|
|
72
|
+
private buildIntentUrl;
|
|
73
|
+
private buildTwitterUrl;
|
|
74
|
+
private buildBlueskyUrl;
|
|
75
|
+
private buildLinkedInUrl;
|
|
76
|
+
private emit;
|
|
77
|
+
}
|
|
78
|
+
declare const FollowGate: FollowGateClient;
|
|
79
|
+
|
|
80
|
+
export { type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type Platform, type SocialAction };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported social platforms
|
|
3
|
+
*/
|
|
4
|
+
type Platform = 'twitter' | 'bluesky' | 'linkedin';
|
|
5
|
+
/**
|
|
6
|
+
* Supported social actions
|
|
7
|
+
*/
|
|
8
|
+
type SocialAction = 'follow' | 'repost' | 'like';
|
|
9
|
+
/**
|
|
10
|
+
* SDK Configuration
|
|
11
|
+
*/
|
|
12
|
+
interface FollowGateConfig {
|
|
13
|
+
appId: string;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
apiUrl?: string;
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open action options
|
|
20
|
+
*/
|
|
21
|
+
interface OpenOptions {
|
|
22
|
+
platform: Platform;
|
|
23
|
+
action: SocialAction;
|
|
24
|
+
target: string;
|
|
25
|
+
userId?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* LinkedIn target type
|
|
29
|
+
*/
|
|
30
|
+
type LinkedInTargetType = 'company' | 'profile';
|
|
31
|
+
/**
|
|
32
|
+
* Event types
|
|
33
|
+
*/
|
|
34
|
+
type EventType = 'complete' | 'error' | 'cancel';
|
|
35
|
+
/**
|
|
36
|
+
* Event callback
|
|
37
|
+
*/
|
|
38
|
+
type EventCallback = (data: unknown) => void;
|
|
39
|
+
/**
|
|
40
|
+
* FollowGate SDK Client
|
|
41
|
+
*/
|
|
42
|
+
declare class FollowGateClient {
|
|
43
|
+
private config;
|
|
44
|
+
private listeners;
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the SDK
|
|
47
|
+
*/
|
|
48
|
+
init(config: FollowGateConfig): void;
|
|
49
|
+
/**
|
|
50
|
+
* Open social action popup
|
|
51
|
+
*/
|
|
52
|
+
open(options: OpenOptions): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Verify follow status (for Pro/Business tiers with OAuth)
|
|
55
|
+
*/
|
|
56
|
+
verify(options: OpenOptions): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Track analytics event
|
|
59
|
+
*/
|
|
60
|
+
private trackEvent;
|
|
61
|
+
/**
|
|
62
|
+
* Register event listener
|
|
63
|
+
*/
|
|
64
|
+
on(event: EventType, callback: EventCallback): void;
|
|
65
|
+
/**
|
|
66
|
+
* Remove event listener
|
|
67
|
+
*/
|
|
68
|
+
off(event: EventType, callback: EventCallback): void;
|
|
69
|
+
/**
|
|
70
|
+
* Build intent URL for platform
|
|
71
|
+
*/
|
|
72
|
+
private buildIntentUrl;
|
|
73
|
+
private buildTwitterUrl;
|
|
74
|
+
private buildBlueskyUrl;
|
|
75
|
+
private buildLinkedInUrl;
|
|
76
|
+
private emit;
|
|
77
|
+
}
|
|
78
|
+
declare const FollowGate: FollowGateClient;
|
|
79
|
+
|
|
80
|
+
export { type EventCallback, type EventType, FollowGate, FollowGateClient, type FollowGateConfig, type LinkedInTargetType, type OpenOptions, type Platform, type SocialAction };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
FollowGate: () => FollowGate,
|
|
24
|
+
FollowGateClient: () => FollowGateClient
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var DEFAULT_API_URL = "https://api.followgate.io";
|
|
28
|
+
var FollowGateClient = class {
|
|
29
|
+
config = null;
|
|
30
|
+
listeners = /* @__PURE__ */ new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the SDK
|
|
33
|
+
*/
|
|
34
|
+
init(config) {
|
|
35
|
+
this.config = {
|
|
36
|
+
...config,
|
|
37
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
38
|
+
};
|
|
39
|
+
if (config.debug) {
|
|
40
|
+
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Open social action popup
|
|
45
|
+
*/
|
|
46
|
+
async open(options) {
|
|
47
|
+
if (!this.config) {
|
|
48
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
49
|
+
}
|
|
50
|
+
const url = this.buildIntentUrl(options);
|
|
51
|
+
if (this.config.debug) {
|
|
52
|
+
console.log("[FollowGate] Opening:", url);
|
|
53
|
+
}
|
|
54
|
+
await this.trackEvent("gate_opened", options);
|
|
55
|
+
const popup = window.open(url, "_blank", "width=600,height=700");
|
|
56
|
+
if (!popup) {
|
|
57
|
+
this.emit("error", { message: "Popup blocked" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await this.trackEvent("action_clicked", options);
|
|
61
|
+
this.emit("complete", {
|
|
62
|
+
platform: options.platform,
|
|
63
|
+
action: options.action,
|
|
64
|
+
target: options.target
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Verify follow status (for Pro/Business tiers with OAuth)
|
|
69
|
+
*/
|
|
70
|
+
async verify(options) {
|
|
71
|
+
if (!this.config) {
|
|
72
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(`${this.config.apiUrl}/api/v1/verify`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
"X-API-Key": this.config.apiKey
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
platform: options.platform,
|
|
83
|
+
action: options.action,
|
|
84
|
+
target: options.target,
|
|
85
|
+
externalUserId: options.userId
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
return data.success && data.data?.verified === true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (this.config.debug) {
|
|
92
|
+
console.error("[FollowGate] Verification error:", error);
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Track analytics event
|
|
99
|
+
*/
|
|
100
|
+
async trackEvent(event, options) {
|
|
101
|
+
if (!this.config) return;
|
|
102
|
+
try {
|
|
103
|
+
await fetch(`${this.config.apiUrl}/api/v1/events`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
"X-API-Key": this.config.apiKey
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
event,
|
|
111
|
+
platform: options.platform,
|
|
112
|
+
action: options.action,
|
|
113
|
+
target: options.target,
|
|
114
|
+
externalUserId: options.userId
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (this.config.debug) {
|
|
119
|
+
console.warn("[FollowGate] Failed to track event:", error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Register event listener
|
|
125
|
+
*/
|
|
126
|
+
on(event, callback) {
|
|
127
|
+
if (!this.listeners.has(event)) {
|
|
128
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
129
|
+
}
|
|
130
|
+
this.listeners.get(event).add(callback);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Remove event listener
|
|
134
|
+
*/
|
|
135
|
+
off(event, callback) {
|
|
136
|
+
this.listeners.get(event)?.delete(callback);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build intent URL for platform
|
|
140
|
+
*/
|
|
141
|
+
buildIntentUrl(options) {
|
|
142
|
+
const { platform, action, target } = options;
|
|
143
|
+
switch (platform) {
|
|
144
|
+
case "twitter":
|
|
145
|
+
return this.buildTwitterUrl(action, target);
|
|
146
|
+
case "bluesky":
|
|
147
|
+
return this.buildBlueskyUrl(action, target);
|
|
148
|
+
case "linkedin":
|
|
149
|
+
return this.buildLinkedInUrl(action, target);
|
|
150
|
+
default:
|
|
151
|
+
throw new Error(`[FollowGate] Unsupported platform: ${platform}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
buildTwitterUrl(action, target) {
|
|
155
|
+
switch (action) {
|
|
156
|
+
case "follow":
|
|
157
|
+
return `https://twitter.com/intent/follow?screen_name=${encodeURIComponent(target)}`;
|
|
158
|
+
case "repost":
|
|
159
|
+
return `https://twitter.com/intent/retweet?tweet_id=${encodeURIComponent(target)}`;
|
|
160
|
+
case "like":
|
|
161
|
+
return `https://twitter.com/intent/like?tweet_id=${encodeURIComponent(target)}`;
|
|
162
|
+
default:
|
|
163
|
+
throw new Error(`[FollowGate] Unsupported Twitter action: ${action}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
buildBlueskyUrl(action, target) {
|
|
167
|
+
const normalizedTarget = target.startsWith("@") ? target.slice(1) : target;
|
|
168
|
+
switch (action) {
|
|
169
|
+
case "follow":
|
|
170
|
+
return `https://bsky.app/profile/${encodeURIComponent(normalizedTarget)}`;
|
|
171
|
+
case "repost":
|
|
172
|
+
case "like":
|
|
173
|
+
if (target.startsWith("at://") || target.includes("/post/")) {
|
|
174
|
+
const postPath = target.replace("at://", "").replace("app.bsky.feed.post/", "post/");
|
|
175
|
+
return `https://bsky.app/profile/${postPath}`;
|
|
176
|
+
}
|
|
177
|
+
return `https://bsky.app/profile/${encodeURIComponent(normalizedTarget)}`;
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`[FollowGate] Unsupported Bluesky action: ${action}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
buildLinkedInUrl(action, target) {
|
|
183
|
+
switch (action) {
|
|
184
|
+
case "follow": {
|
|
185
|
+
if (target.startsWith("in:")) {
|
|
186
|
+
const username = target.slice(3);
|
|
187
|
+
return `https://www.linkedin.com/in/${encodeURIComponent(username)}`;
|
|
188
|
+
} else if (target.startsWith("company:")) {
|
|
189
|
+
const company = target.slice(8);
|
|
190
|
+
return `https://www.linkedin.com/company/${encodeURIComponent(company)}`;
|
|
191
|
+
} else {
|
|
192
|
+
return `https://www.linkedin.com/company/${encodeURIComponent(target)}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
default:
|
|
196
|
+
throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
emit(event, data) {
|
|
200
|
+
this.listeners.get(event)?.forEach((callback) => callback(data));
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var FollowGate = new FollowGateClient();
|
|
204
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
205
|
+
0 && (module.exports = {
|
|
206
|
+
FollowGate,
|
|
207
|
+
FollowGateClient
|
|
208
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api.followgate.io";
|
|
3
|
+
var FollowGateClient = class {
|
|
4
|
+
config = null;
|
|
5
|
+
listeners = /* @__PURE__ */ new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Initialize the SDK
|
|
8
|
+
*/
|
|
9
|
+
init(config) {
|
|
10
|
+
this.config = {
|
|
11
|
+
...config,
|
|
12
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
13
|
+
};
|
|
14
|
+
if (config.debug) {
|
|
15
|
+
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open social action popup
|
|
20
|
+
*/
|
|
21
|
+
async open(options) {
|
|
22
|
+
if (!this.config) {
|
|
23
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
24
|
+
}
|
|
25
|
+
const url = this.buildIntentUrl(options);
|
|
26
|
+
if (this.config.debug) {
|
|
27
|
+
console.log("[FollowGate] Opening:", url);
|
|
28
|
+
}
|
|
29
|
+
await this.trackEvent("gate_opened", options);
|
|
30
|
+
const popup = window.open(url, "_blank", "width=600,height=700");
|
|
31
|
+
if (!popup) {
|
|
32
|
+
this.emit("error", { message: "Popup blocked" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await this.trackEvent("action_clicked", options);
|
|
36
|
+
this.emit("complete", {
|
|
37
|
+
platform: options.platform,
|
|
38
|
+
action: options.action,
|
|
39
|
+
target: options.target
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Verify follow status (for Pro/Business tiers with OAuth)
|
|
44
|
+
*/
|
|
45
|
+
async verify(options) {
|
|
46
|
+
if (!this.config) {
|
|
47
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(`${this.config.apiUrl}/api/v1/verify`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
"X-API-Key": this.config.apiKey
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
platform: options.platform,
|
|
58
|
+
action: options.action,
|
|
59
|
+
target: options.target,
|
|
60
|
+
externalUserId: options.userId
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
return data.success && data.data?.verified === true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (this.config.debug) {
|
|
67
|
+
console.error("[FollowGate] Verification error:", error);
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Track analytics event
|
|
74
|
+
*/
|
|
75
|
+
async trackEvent(event, options) {
|
|
76
|
+
if (!this.config) return;
|
|
77
|
+
try {
|
|
78
|
+
await fetch(`${this.config.apiUrl}/api/v1/events`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
"X-API-Key": this.config.apiKey
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
event,
|
|
86
|
+
platform: options.platform,
|
|
87
|
+
action: options.action,
|
|
88
|
+
target: options.target,
|
|
89
|
+
externalUserId: options.userId
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (this.config.debug) {
|
|
94
|
+
console.warn("[FollowGate] Failed to track event:", error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Register event listener
|
|
100
|
+
*/
|
|
101
|
+
on(event, callback) {
|
|
102
|
+
if (!this.listeners.has(event)) {
|
|
103
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
104
|
+
}
|
|
105
|
+
this.listeners.get(event).add(callback);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Remove event listener
|
|
109
|
+
*/
|
|
110
|
+
off(event, callback) {
|
|
111
|
+
this.listeners.get(event)?.delete(callback);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build intent URL for platform
|
|
115
|
+
*/
|
|
116
|
+
buildIntentUrl(options) {
|
|
117
|
+
const { platform, action, target } = options;
|
|
118
|
+
switch (platform) {
|
|
119
|
+
case "twitter":
|
|
120
|
+
return this.buildTwitterUrl(action, target);
|
|
121
|
+
case "bluesky":
|
|
122
|
+
return this.buildBlueskyUrl(action, target);
|
|
123
|
+
case "linkedin":
|
|
124
|
+
return this.buildLinkedInUrl(action, target);
|
|
125
|
+
default:
|
|
126
|
+
throw new Error(`[FollowGate] Unsupported platform: ${platform}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
buildTwitterUrl(action, target) {
|
|
130
|
+
switch (action) {
|
|
131
|
+
case "follow":
|
|
132
|
+
return `https://twitter.com/intent/follow?screen_name=${encodeURIComponent(target)}`;
|
|
133
|
+
case "repost":
|
|
134
|
+
return `https://twitter.com/intent/retweet?tweet_id=${encodeURIComponent(target)}`;
|
|
135
|
+
case "like":
|
|
136
|
+
return `https://twitter.com/intent/like?tweet_id=${encodeURIComponent(target)}`;
|
|
137
|
+
default:
|
|
138
|
+
throw new Error(`[FollowGate] Unsupported Twitter action: ${action}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
buildBlueskyUrl(action, target) {
|
|
142
|
+
const normalizedTarget = target.startsWith("@") ? target.slice(1) : target;
|
|
143
|
+
switch (action) {
|
|
144
|
+
case "follow":
|
|
145
|
+
return `https://bsky.app/profile/${encodeURIComponent(normalizedTarget)}`;
|
|
146
|
+
case "repost":
|
|
147
|
+
case "like":
|
|
148
|
+
if (target.startsWith("at://") || target.includes("/post/")) {
|
|
149
|
+
const postPath = target.replace("at://", "").replace("app.bsky.feed.post/", "post/");
|
|
150
|
+
return `https://bsky.app/profile/${postPath}`;
|
|
151
|
+
}
|
|
152
|
+
return `https://bsky.app/profile/${encodeURIComponent(normalizedTarget)}`;
|
|
153
|
+
default:
|
|
154
|
+
throw new Error(`[FollowGate] Unsupported Bluesky action: ${action}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
buildLinkedInUrl(action, target) {
|
|
158
|
+
switch (action) {
|
|
159
|
+
case "follow": {
|
|
160
|
+
if (target.startsWith("in:")) {
|
|
161
|
+
const username = target.slice(3);
|
|
162
|
+
return `https://www.linkedin.com/in/${encodeURIComponent(username)}`;
|
|
163
|
+
} else if (target.startsWith("company:")) {
|
|
164
|
+
const company = target.slice(8);
|
|
165
|
+
return `https://www.linkedin.com/company/${encodeURIComponent(company)}`;
|
|
166
|
+
} else {
|
|
167
|
+
return `https://www.linkedin.com/company/${encodeURIComponent(target)}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
default:
|
|
171
|
+
throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
emit(event, data) {
|
|
175
|
+
this.listeners.get(event)?.forEach((callback) => callback(data));
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var FollowGate = new FollowGateClient();
|
|
179
|
+
export {
|
|
180
|
+
FollowGate,
|
|
181
|
+
FollowGateClient
|
|
182
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@followgate/js",
|
|
3
|
+
"version": "0.1.0",
|
|
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.io>",
|
|
6
|
+
"homepage": "https://followgate.io",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "tsup src/index.ts --watch",
|
|
23
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.10.0",
|
|
28
|
+
"tsup": "^8.0.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"followgate",
|
|
32
|
+
"social",
|
|
33
|
+
"follow",
|
|
34
|
+
"twitter",
|
|
35
|
+
"x",
|
|
36
|
+
"bluesky",
|
|
37
|
+
"linkedin",
|
|
38
|
+
"growth",
|
|
39
|
+
"audience",
|
|
40
|
+
"sdk",
|
|
41
|
+
"social-gate"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/JustFF5/FollowGate.git",
|
|
47
|
+
"directory": "packages/sdk"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/JustFF5/FollowGate/issues"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=16"
|
|
54
|
+
}
|
|
55
|
+
}
|