@tribecloud/sdk 1.0.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/esm/core.d.ts +52 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +326 -0
- package/dist/esm/types.d.ts +94 -0
- package/dist/iife/sdk.min.js +87 -0
- package/dist/sdk.min.js +87 -0
- package/package.json +32 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TribeConfig, TribeSDK, User, Announcement, FeedbackOptions, ApiKey, ApiKeyResult, ActiveDevice, ForgotPasswordOptions, ResendVerificationOptions, MagicLinkOptions, AuthConfig } from "./types";
|
|
2
|
+
export declare class Tribe implements TribeSDK {
|
|
3
|
+
private siteId;
|
|
4
|
+
private baseUrl;
|
|
5
|
+
private sessionId;
|
|
6
|
+
private pageStart;
|
|
7
|
+
private currentPath;
|
|
8
|
+
private tokenKey;
|
|
9
|
+
private anonIdKey;
|
|
10
|
+
private autoTrackingActive;
|
|
11
|
+
private originalPushState;
|
|
12
|
+
private popstateHandler;
|
|
13
|
+
private visibilityHandler;
|
|
14
|
+
constructor(config: TribeConfig);
|
|
15
|
+
static init(config: TribeConfig): Tribe;
|
|
16
|
+
startAutoTracking(): void;
|
|
17
|
+
stopAutoTracking(): void;
|
|
18
|
+
private sendEvent;
|
|
19
|
+
private gql;
|
|
20
|
+
private getAnonymousId;
|
|
21
|
+
register(email: string, password: string): Promise<{
|
|
22
|
+
user: User;
|
|
23
|
+
}>;
|
|
24
|
+
login(email: string, password: string): Promise<{
|
|
25
|
+
user: User;
|
|
26
|
+
}>;
|
|
27
|
+
logout(): Promise<void>;
|
|
28
|
+
getSession(): Promise<{
|
|
29
|
+
user: User;
|
|
30
|
+
} | null>;
|
|
31
|
+
track(eventName: string, data?: Record<string, unknown>): void;
|
|
32
|
+
feedback(message: string, options?: FeedbackOptions): Promise<void>;
|
|
33
|
+
getFeatureFlag(key: string): Promise<boolean>;
|
|
34
|
+
getAnnouncements(): Promise<Announcement[]>;
|
|
35
|
+
ackAnnouncement(announcementId: string): Promise<void>;
|
|
36
|
+
verifyEmail(token: string): Promise<void>;
|
|
37
|
+
resendVerification(options?: ResendVerificationOptions): Promise<void>;
|
|
38
|
+
forgotPassword(email: string, options?: ForgotPasswordOptions): Promise<void>;
|
|
39
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
40
|
+
requestMagicLink(email: string, options?: MagicLinkOptions): Promise<void>;
|
|
41
|
+
verifyMagicLink(token: string): Promise<{
|
|
42
|
+
user: User;
|
|
43
|
+
}>;
|
|
44
|
+
getAuthConfig(): Promise<AuthConfig>;
|
|
45
|
+
setRole(role: string): Promise<User>;
|
|
46
|
+
createApiKey(name: string, scopes: string[]): Promise<ApiKeyResult>;
|
|
47
|
+
listApiKeys(): Promise<ApiKey[]>;
|
|
48
|
+
deleteApiKey(id: string): Promise<void>;
|
|
49
|
+
invalidateAllSessions(): Promise<void>;
|
|
50
|
+
getActiveDevices(): Promise<ActiveDevice[]>;
|
|
51
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { Tribe } from "./core";
|
|
2
|
+
export { Tribe as default } from "./core";
|
|
3
|
+
export type { TribeConfig, TribeSDK, User, Announcement, FeedbackOptions, ApiKey, ApiKeyResult, ActiveDevice, ForgotPasswordOptions, ResendVerificationOptions, MagicLinkOptions, AuthConfig, } from "./types";
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// src/core.ts
|
|
2
|
+
class Tribe {
|
|
3
|
+
siteId;
|
|
4
|
+
baseUrl;
|
|
5
|
+
sessionId;
|
|
6
|
+
pageStart;
|
|
7
|
+
currentPath;
|
|
8
|
+
tokenKey;
|
|
9
|
+
anonIdKey;
|
|
10
|
+
autoTrackingActive = false;
|
|
11
|
+
originalPushState = null;
|
|
12
|
+
popstateHandler = null;
|
|
13
|
+
visibilityHandler = null;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.siteId = config.siteId;
|
|
16
|
+
this.baseUrl = config.baseUrl;
|
|
17
|
+
this.sessionId = Math.random().toString(36).slice(2);
|
|
18
|
+
this.pageStart = Date.now();
|
|
19
|
+
this.currentPath = location.pathname;
|
|
20
|
+
this.tokenKey = `__tribe_${config.siteId}`;
|
|
21
|
+
this.anonIdKey = `__tribe_anon_${config.siteId}`;
|
|
22
|
+
if (config.autoTrack !== false) {
|
|
23
|
+
this.startAutoTracking();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static init(config) {
|
|
27
|
+
return new Tribe(config);
|
|
28
|
+
}
|
|
29
|
+
startAutoTracking() {
|
|
30
|
+
if (this.autoTrackingActive)
|
|
31
|
+
return;
|
|
32
|
+
this.autoTrackingActive = true;
|
|
33
|
+
this.sendEvent("pageview");
|
|
34
|
+
this.visibilityHandler = () => {
|
|
35
|
+
if (document.visibilityState === "hidden") {
|
|
36
|
+
this.sendEvent("leave", { d: Date.now() - this.pageStart });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
40
|
+
this.originalPushState = history.pushState;
|
|
41
|
+
history.pushState = (...args) => {
|
|
42
|
+
this.sendEvent("leave", { d: Date.now() - this.pageStart });
|
|
43
|
+
this.originalPushState.apply(history, args);
|
|
44
|
+
this.pageStart = Date.now();
|
|
45
|
+
this.currentPath = location.pathname;
|
|
46
|
+
this.sendEvent("pageview");
|
|
47
|
+
};
|
|
48
|
+
this.popstateHandler = () => {
|
|
49
|
+
this.sendEvent("leave", { d: Date.now() - this.pageStart });
|
|
50
|
+
this.pageStart = Date.now();
|
|
51
|
+
this.currentPath = location.pathname;
|
|
52
|
+
this.sendEvent("pageview");
|
|
53
|
+
};
|
|
54
|
+
window.addEventListener("popstate", this.popstateHandler);
|
|
55
|
+
}
|
|
56
|
+
stopAutoTracking() {
|
|
57
|
+
if (!this.autoTrackingActive)
|
|
58
|
+
return;
|
|
59
|
+
this.autoTrackingActive = false;
|
|
60
|
+
if (this.visibilityHandler) {
|
|
61
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
62
|
+
this.visibilityHandler = null;
|
|
63
|
+
}
|
|
64
|
+
if (this.originalPushState) {
|
|
65
|
+
history.pushState = this.originalPushState;
|
|
66
|
+
this.originalPushState = null;
|
|
67
|
+
}
|
|
68
|
+
if (this.popstateHandler) {
|
|
69
|
+
window.removeEventListener("popstate", this.popstateHandler);
|
|
70
|
+
this.popstateHandler = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
sendEvent(type, extra = {}) {
|
|
74
|
+
const data = {
|
|
75
|
+
s: this.siteId,
|
|
76
|
+
t: type,
|
|
77
|
+
p: this.currentPath,
|
|
78
|
+
r: document.referrer || null,
|
|
79
|
+
sid: this.sessionId,
|
|
80
|
+
w: screen.width,
|
|
81
|
+
...extra
|
|
82
|
+
};
|
|
83
|
+
if (navigator.sendBeacon) {
|
|
84
|
+
const blob = new Blob([JSON.stringify(data)], { type: "application/json" });
|
|
85
|
+
navigator.sendBeacon(`${this.baseUrl}/collect`, blob);
|
|
86
|
+
} else {
|
|
87
|
+
fetch(`${this.baseUrl}/collect`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
body: JSON.stringify(data),
|
|
90
|
+
keepalive: true,
|
|
91
|
+
headers: { "Content-Type": "application/json" }
|
|
92
|
+
}).catch(() => {});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async gql(query, variables) {
|
|
96
|
+
const token = localStorage.getItem(this.tokenKey);
|
|
97
|
+
const headers = {
|
|
98
|
+
"Content-Type": "application/json"
|
|
99
|
+
};
|
|
100
|
+
if (token) {
|
|
101
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
102
|
+
} else {
|
|
103
|
+
headers["X-Tribe-Anonymous-Id"] = this.getAnonymousId();
|
|
104
|
+
}
|
|
105
|
+
const response = await fetch(`${this.baseUrl}/graphql`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers,
|
|
108
|
+
body: JSON.stringify({ query, variables })
|
|
109
|
+
});
|
|
110
|
+
const json = await response.json();
|
|
111
|
+
if (json.errors) {
|
|
112
|
+
throw new Error(json.errors[0].message);
|
|
113
|
+
}
|
|
114
|
+
return json.data;
|
|
115
|
+
}
|
|
116
|
+
getAnonymousId() {
|
|
117
|
+
let anonId = localStorage.getItem(this.anonIdKey);
|
|
118
|
+
if (!anonId) {
|
|
119
|
+
anonId = crypto.randomUUID();
|
|
120
|
+
localStorage.setItem(this.anonIdKey, anonId);
|
|
121
|
+
}
|
|
122
|
+
return anonId;
|
|
123
|
+
}
|
|
124
|
+
async register(email, password) {
|
|
125
|
+
const data = await this.gql(`mutation($siteId: ID!, $email: String!, $password: String!) {
|
|
126
|
+
sdkRegister(siteId: $siteId, email: $email, password: $password) {
|
|
127
|
+
token
|
|
128
|
+
user { id email }
|
|
129
|
+
}
|
|
130
|
+
}`, { siteId: this.siteId, email, password });
|
|
131
|
+
localStorage.setItem(this.tokenKey, data.sdkRegister.token);
|
|
132
|
+
return { user: data.sdkRegister.user };
|
|
133
|
+
}
|
|
134
|
+
async login(email, password) {
|
|
135
|
+
const data = await this.gql(`mutation($siteId: ID!, $email: String!, $password: String!) {
|
|
136
|
+
sdkLogin(siteId: $siteId, email: $email, password: $password) {
|
|
137
|
+
token
|
|
138
|
+
user { id email }
|
|
139
|
+
}
|
|
140
|
+
}`, { siteId: this.siteId, email, password });
|
|
141
|
+
localStorage.setItem(this.tokenKey, data.sdkLogin.token);
|
|
142
|
+
return { user: data.sdkLogin.user };
|
|
143
|
+
}
|
|
144
|
+
async logout() {
|
|
145
|
+
try {
|
|
146
|
+
await this.gql(`mutation { sdkLogout }`);
|
|
147
|
+
} catch {}
|
|
148
|
+
localStorage.removeItem(this.tokenKey);
|
|
149
|
+
}
|
|
150
|
+
async getSession() {
|
|
151
|
+
const token = localStorage.getItem(this.tokenKey);
|
|
152
|
+
if (!token)
|
|
153
|
+
return null;
|
|
154
|
+
try {
|
|
155
|
+
const data = await this.gql(`query { sdkSession { user { id email } } }`);
|
|
156
|
+
return data.sdkSession;
|
|
157
|
+
} catch {
|
|
158
|
+
localStorage.removeItem(this.tokenKey);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
track(eventName, data) {
|
|
163
|
+
this.sendEvent("custom", { e: eventName, ed: data });
|
|
164
|
+
}
|
|
165
|
+
async feedback(message, options) {
|
|
166
|
+
const url = this.currentPath;
|
|
167
|
+
await this.gql(`mutation($siteId: ID!, $message: String!, $type: String, $email: String, $url: String) {
|
|
168
|
+
submitFeedback(siteId: $siteId, message: $message, type: $type, email: $email, url: $url)
|
|
169
|
+
}`, {
|
|
170
|
+
siteId: this.siteId,
|
|
171
|
+
message,
|
|
172
|
+
url,
|
|
173
|
+
type: options?.type,
|
|
174
|
+
email: options?.email
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async getFeatureFlag(key) {
|
|
178
|
+
const data = await this.gql(`query($siteId: ID!) {
|
|
179
|
+
sdkFeatureFlags(siteId: $siteId) {
|
|
180
|
+
key
|
|
181
|
+
enabled
|
|
182
|
+
}
|
|
183
|
+
}`, { siteId: this.siteId });
|
|
184
|
+
const flag = data.sdkFeatureFlags.find((f) => f.key === key);
|
|
185
|
+
return flag?.enabled ?? false;
|
|
186
|
+
}
|
|
187
|
+
async getAnnouncements() {
|
|
188
|
+
const data = await this.gql(`query($siteId: ID!) {
|
|
189
|
+
activeAnnouncements(siteId: $siteId) {
|
|
190
|
+
id
|
|
191
|
+
title
|
|
192
|
+
message
|
|
193
|
+
type
|
|
194
|
+
}
|
|
195
|
+
}`, { siteId: this.siteId });
|
|
196
|
+
const dismissedKey = `__tribe_dismissed_${this.siteId}`;
|
|
197
|
+
const dismissed = JSON.parse(localStorage.getItem(dismissedKey) || "[]");
|
|
198
|
+
return data.activeAnnouncements.filter((a) => !dismissed.includes(a.id));
|
|
199
|
+
}
|
|
200
|
+
async ackAnnouncement(announcementId) {
|
|
201
|
+
const dismissedKey = `__tribe_dismissed_${this.siteId}`;
|
|
202
|
+
const dismissed = JSON.parse(localStorage.getItem(dismissedKey) || "[]");
|
|
203
|
+
if (!dismissed.includes(announcementId)) {
|
|
204
|
+
dismissed.push(announcementId);
|
|
205
|
+
localStorage.setItem(dismissedKey, JSON.stringify(dismissed));
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
await this.gql(`mutation($announcementId: ID!) {
|
|
209
|
+
ackAnnouncement(announcementId: $announcementId)
|
|
210
|
+
}`, { announcementId });
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
async verifyEmail(token) {
|
|
214
|
+
await this.gql(`mutation($token: String!) {
|
|
215
|
+
verifyEmail(token: $token)
|
|
216
|
+
}`, { token });
|
|
217
|
+
}
|
|
218
|
+
async resendVerification(options) {
|
|
219
|
+
await this.gql(`mutation($verifyEmailUrl: String) {
|
|
220
|
+
resendVerification(verifyEmailUrl: $verifyEmailUrl)
|
|
221
|
+
}`, { verifyEmailUrl: options?.verifyEmailUrl });
|
|
222
|
+
}
|
|
223
|
+
async forgotPassword(email, options) {
|
|
224
|
+
await this.gql(`mutation($siteId: ID!, $email: String!, $resetPasswordUrl: String) {
|
|
225
|
+
forgotPassword(siteId: $siteId, email: $email, resetPasswordUrl: $resetPasswordUrl)
|
|
226
|
+
}`, { siteId: this.siteId, email, resetPasswordUrl: options?.resetPasswordUrl });
|
|
227
|
+
}
|
|
228
|
+
async resetPassword(token, newPassword) {
|
|
229
|
+
await this.gql(`mutation($token: String!, $newPassword: String!) {
|
|
230
|
+
resetPassword(token: $token, newPassword: $newPassword)
|
|
231
|
+
}`, { token, newPassword });
|
|
232
|
+
}
|
|
233
|
+
async requestMagicLink(email, options) {
|
|
234
|
+
await this.gql(`mutation($siteId: ID!, $email: String!, $magicLinkUrl: String) {
|
|
235
|
+
requestMagicLink(siteId: $siteId, email: $email, magicLinkUrl: $magicLinkUrl)
|
|
236
|
+
}`, { siteId: this.siteId, email, magicLinkUrl: options?.magicLinkUrl });
|
|
237
|
+
}
|
|
238
|
+
async verifyMagicLink(token) {
|
|
239
|
+
const data = await this.gql(`mutation($token: String!) {
|
|
240
|
+
verifyMagicLink(token: $token) {
|
|
241
|
+
token
|
|
242
|
+
user { id email }
|
|
243
|
+
}
|
|
244
|
+
}`, { token });
|
|
245
|
+
localStorage.setItem(this.tokenKey, data.verifyMagicLink.token);
|
|
246
|
+
return { user: data.verifyMagicLink.user };
|
|
247
|
+
}
|
|
248
|
+
async getAuthConfig() {
|
|
249
|
+
const data = await this.gql(`query($siteId: ID!) {
|
|
250
|
+
siteAuthConfig(siteId: $siteId) {
|
|
251
|
+
authEnabled
|
|
252
|
+
magicLinkEnabled
|
|
253
|
+
captchaEnabled
|
|
254
|
+
captchaProvider
|
|
255
|
+
captchaSiteKey
|
|
256
|
+
captchaRequiredForRegistration
|
|
257
|
+
captchaRequiredForLogin
|
|
258
|
+
breachedPasswordPolicy
|
|
259
|
+
}
|
|
260
|
+
}`, { siteId: this.siteId });
|
|
261
|
+
return data.siteAuthConfig;
|
|
262
|
+
}
|
|
263
|
+
async setRole(role) {
|
|
264
|
+
const data = await this.gql(`mutation($role: String!) {
|
|
265
|
+
setRole(role: $role) {
|
|
266
|
+
id
|
|
267
|
+
email
|
|
268
|
+
}
|
|
269
|
+
}`, { role });
|
|
270
|
+
return data.setRole;
|
|
271
|
+
}
|
|
272
|
+
async createApiKey(name, scopes) {
|
|
273
|
+
const data = await this.gql(`mutation($name: String!, $scopes: [String!]!) {
|
|
274
|
+
createApiKey(name: $name, scopes: $scopes) {
|
|
275
|
+
id
|
|
276
|
+
key
|
|
277
|
+
name
|
|
278
|
+
scopes
|
|
279
|
+
}
|
|
280
|
+
}`, { name, scopes });
|
|
281
|
+
return data.createApiKey;
|
|
282
|
+
}
|
|
283
|
+
async listApiKeys() {
|
|
284
|
+
const data = await this.gql(`query {
|
|
285
|
+
myApiKeys {
|
|
286
|
+
id
|
|
287
|
+
name
|
|
288
|
+
keyPrefix
|
|
289
|
+
scopes
|
|
290
|
+
createdAt
|
|
291
|
+
}
|
|
292
|
+
}`);
|
|
293
|
+
return data.myApiKeys;
|
|
294
|
+
}
|
|
295
|
+
async deleteApiKey(id) {
|
|
296
|
+
await this.gql(`mutation($id: ID!) {
|
|
297
|
+
deleteApiKey(id: $id)
|
|
298
|
+
}`, { id });
|
|
299
|
+
}
|
|
300
|
+
async invalidateAllSessions() {
|
|
301
|
+
await this.gql(`mutation { invalidateAllSessions }`);
|
|
302
|
+
}
|
|
303
|
+
async getActiveDevices() {
|
|
304
|
+
const data = await this.gql(`query {
|
|
305
|
+
sdkActiveDevices {
|
|
306
|
+
id
|
|
307
|
+
browser
|
|
308
|
+
os
|
|
309
|
+
deviceType
|
|
310
|
+
lastActiveAt
|
|
311
|
+
createdAt
|
|
312
|
+
isCurrent
|
|
313
|
+
}
|
|
314
|
+
}`);
|
|
315
|
+
return data.sdkActiveDevices;
|
|
316
|
+
}
|
|
317
|
+
async revokeSession(sessionId) {
|
|
318
|
+
await this.gql(`mutation($sessionId: ID!) {
|
|
319
|
+
sdkRevokeSession(sessionId: $sessionId)
|
|
320
|
+
}`, { sessionId });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
Tribe as default,
|
|
325
|
+
Tribe
|
|
326
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Announcement {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
message: string;
|
|
9
|
+
type: string;
|
|
10
|
+
}
|
|
11
|
+
export interface FeedbackOptions {
|
|
12
|
+
type?: string;
|
|
13
|
+
email?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ApiKey {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
keyPrefix: string;
|
|
19
|
+
scopes: string[];
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ApiKeyResult {
|
|
23
|
+
id: string;
|
|
24
|
+
key: string;
|
|
25
|
+
name: string;
|
|
26
|
+
scopes: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface ActiveDevice {
|
|
29
|
+
id: string;
|
|
30
|
+
browser: string;
|
|
31
|
+
os: string;
|
|
32
|
+
deviceType: string;
|
|
33
|
+
lastActiveAt: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
isCurrent: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ForgotPasswordOptions {
|
|
38
|
+
resetPasswordUrl?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ResendVerificationOptions {
|
|
41
|
+
verifyEmailUrl?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface MagicLinkOptions {
|
|
44
|
+
magicLinkUrl?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface AuthConfig {
|
|
47
|
+
authEnabled: boolean;
|
|
48
|
+
magicLinkEnabled: boolean;
|
|
49
|
+
captchaEnabled: boolean;
|
|
50
|
+
captchaProvider: string | null;
|
|
51
|
+
captchaSiteKey: string | null;
|
|
52
|
+
captchaRequiredForRegistration: boolean;
|
|
53
|
+
captchaRequiredForLogin: boolean;
|
|
54
|
+
breachedPasswordPolicy: string;
|
|
55
|
+
}
|
|
56
|
+
export interface TribeSDK {
|
|
57
|
+
register(email: string, password: string): Promise<{
|
|
58
|
+
user: User;
|
|
59
|
+
}>;
|
|
60
|
+
login(email: string, password: string): Promise<{
|
|
61
|
+
user: User;
|
|
62
|
+
}>;
|
|
63
|
+
logout(): Promise<void>;
|
|
64
|
+
getSession(): Promise<{
|
|
65
|
+
user: User;
|
|
66
|
+
} | null>;
|
|
67
|
+
track(eventName: string, data?: Record<string, unknown>): void;
|
|
68
|
+
feedback(message: string, options?: FeedbackOptions): Promise<void>;
|
|
69
|
+
getFeatureFlag(key: string): Promise<boolean>;
|
|
70
|
+
getAnnouncements(): Promise<Announcement[]>;
|
|
71
|
+
ackAnnouncement(announcementId: string): Promise<void>;
|
|
72
|
+
verifyEmail(token: string): Promise<void>;
|
|
73
|
+
resendVerification(options?: ResendVerificationOptions): Promise<void>;
|
|
74
|
+
forgotPassword(email: string, options?: ForgotPasswordOptions): Promise<void>;
|
|
75
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
76
|
+
requestMagicLink(email: string, options?: MagicLinkOptions): Promise<void>;
|
|
77
|
+
verifyMagicLink(token: string): Promise<{
|
|
78
|
+
user: User;
|
|
79
|
+
}>;
|
|
80
|
+
getAuthConfig(): Promise<AuthConfig>;
|
|
81
|
+
getActiveDevices(): Promise<ActiveDevice[]>;
|
|
82
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
83
|
+
setRole(role: string): Promise<User>;
|
|
84
|
+
createApiKey(name: string, scopes: string[]): Promise<ApiKeyResult>;
|
|
85
|
+
listApiKeys(): Promise<ApiKey[]>;
|
|
86
|
+
deleteApiKey(id: string): Promise<void>;
|
|
87
|
+
invalidateAllSessions(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
export interface TribeConfig {
|
|
90
|
+
siteId: string;
|
|
91
|
+
baseUrl: string;
|
|
92
|
+
/** Whether to auto-track pageviews and navigation. Defaults to true. */
|
|
93
|
+
autoTrack?: boolean;
|
|
94
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class A{siteId;baseUrl;sessionId;pageStart;currentPath;tokenKey;anonIdKey;autoTrackingActive=!1;originalPushState=null;popstateHandler=null;visibilityHandler=null;constructor(T){if(this.siteId=T.siteId,this.baseUrl=T.baseUrl,this.sessionId=Math.random().toString(36).slice(2),this.pageStart=Date.now(),this.currentPath=location.pathname,this.tokenKey=`__tribe_${T.siteId}`,this.anonIdKey=`__tribe_anon_${T.siteId}`,T.autoTrack!==!1)this.startAutoTracking()}static init(T){return new A(T)}startAutoTracking(){if(this.autoTrackingActive)return;this.autoTrackingActive=!0,this.sendEvent("pageview"),this.visibilityHandler=()=>{if(document.visibilityState==="hidden")this.sendEvent("leave",{d:Date.now()-this.pageStart})},document.addEventListener("visibilitychange",this.visibilityHandler),this.originalPushState=history.pushState,history.pushState=(...T)=>{this.sendEvent("leave",{d:Date.now()-this.pageStart}),this.originalPushState.apply(history,T),this.pageStart=Date.now(),this.currentPath=location.pathname,this.sendEvent("pageview")},this.popstateHandler=()=>{this.sendEvent("leave",{d:Date.now()-this.pageStart}),this.pageStart=Date.now(),this.currentPath=location.pathname,this.sendEvent("pageview")},window.addEventListener("popstate",this.popstateHandler)}stopAutoTracking(){if(!this.autoTrackingActive)return;if(this.autoTrackingActive=!1,this.visibilityHandler)document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null;if(this.originalPushState)history.pushState=this.originalPushState,this.originalPushState=null;if(this.popstateHandler)window.removeEventListener("popstate",this.popstateHandler),this.popstateHandler=null}sendEvent(T,E={}){let U={s:this.siteId,t:T,p:this.currentPath,r:document.referrer||null,sid:this.sessionId,w:screen.width,...E};if(navigator.sendBeacon){let _=new Blob([JSON.stringify(U)],{type:"application/json"});navigator.sendBeacon(`${this.baseUrl}/collect`,_)}else fetch(`${this.baseUrl}/collect`,{method:"POST",body:JSON.stringify(U),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}async gql(T,E){let U=localStorage.getItem(this.tokenKey),_={"Content-Type":"application/json"};if(U)_.Authorization=`Bearer ${U}`;else _["X-Tribe-Anonymous-Id"]=this.getAnonymousId();let B=await(await fetch(`${this.baseUrl}/graphql`,{method:"POST",headers:_,body:JSON.stringify({query:T,variables:E})})).json();if(B.errors)throw Error(B.errors[0].message);return B.data}getAnonymousId(){let T=localStorage.getItem(this.anonIdKey);if(!T)T=crypto.randomUUID(),localStorage.setItem(this.anonIdKey,T);return T}async register(T,E){let U=await this.gql(`mutation($siteId: ID!, $email: String!, $password: String!) {
|
|
2
|
+
sdkRegister(siteId: $siteId, email: $email, password: $password) {
|
|
3
|
+
token
|
|
4
|
+
user { id email }
|
|
5
|
+
}
|
|
6
|
+
}`,{siteId:this.siteId,email:T,password:E});return localStorage.setItem(this.tokenKey,U.sdkRegister.token),{user:U.sdkRegister.user}}async login(T,E){let U=await this.gql(`mutation($siteId: ID!, $email: String!, $password: String!) {
|
|
7
|
+
sdkLogin(siteId: $siteId, email: $email, password: $password) {
|
|
8
|
+
token
|
|
9
|
+
user { id email }
|
|
10
|
+
}
|
|
11
|
+
}`,{siteId:this.siteId,email:T,password:E});return localStorage.setItem(this.tokenKey,U.sdkLogin.token),{user:U.sdkLogin.user}}async logout(){try{await this.gql("mutation { sdkLogout }")}catch{}localStorage.removeItem(this.tokenKey)}async getSession(){if(!localStorage.getItem(this.tokenKey))return null;try{return(await this.gql("query { sdkSession { user { id email } } }")).sdkSession}catch{return localStorage.removeItem(this.tokenKey),null}}track(T,E){this.sendEvent("custom",{e:T,ed:E})}async feedback(T,E){let U=this.currentPath;await this.gql(`mutation($siteId: ID!, $message: String!, $type: String, $email: String, $url: String) {
|
|
12
|
+
submitFeedback(siteId: $siteId, message: $message, type: $type, email: $email, url: $url)
|
|
13
|
+
}`,{siteId:this.siteId,message:T,url:U,type:E?.type,email:E?.email})}async getFeatureFlag(T){return(await this.gql(`query($siteId: ID!) {
|
|
14
|
+
sdkFeatureFlags(siteId: $siteId) {
|
|
15
|
+
key
|
|
16
|
+
enabled
|
|
17
|
+
}
|
|
18
|
+
}`,{siteId:this.siteId})).sdkFeatureFlags.find((_)=>_.key===T)?.enabled??!1}async getAnnouncements(){let T=await this.gql(`query($siteId: ID!) {
|
|
19
|
+
activeAnnouncements(siteId: $siteId) {
|
|
20
|
+
id
|
|
21
|
+
title
|
|
22
|
+
message
|
|
23
|
+
type
|
|
24
|
+
}
|
|
25
|
+
}`,{siteId:this.siteId}),E=`__tribe_dismissed_${this.siteId}`,U=JSON.parse(localStorage.getItem(E)||"[]");return T.activeAnnouncements.filter((_)=>!U.includes(_.id))}async ackAnnouncement(T){let E=`__tribe_dismissed_${this.siteId}`,U=JSON.parse(localStorage.getItem(E)||"[]");if(!U.includes(T))U.push(T),localStorage.setItem(E,JSON.stringify(U));try{await this.gql(`mutation($announcementId: ID!) {
|
|
26
|
+
ackAnnouncement(announcementId: $announcementId)
|
|
27
|
+
}`,{announcementId:T})}catch{}}async verifyEmail(T){await this.gql(`mutation($token: String!) {
|
|
28
|
+
verifyEmail(token: $token)
|
|
29
|
+
}`,{token:T})}async resendVerification(T){await this.gql(`mutation($verifyEmailUrl: String) {
|
|
30
|
+
resendVerification(verifyEmailUrl: $verifyEmailUrl)
|
|
31
|
+
}`,{verifyEmailUrl:T?.verifyEmailUrl})}async forgotPassword(T,E){await this.gql(`mutation($siteId: ID!, $email: String!, $resetPasswordUrl: String) {
|
|
32
|
+
forgotPassword(siteId: $siteId, email: $email, resetPasswordUrl: $resetPasswordUrl)
|
|
33
|
+
}`,{siteId:this.siteId,email:T,resetPasswordUrl:E?.resetPasswordUrl})}async resetPassword(T,E){await this.gql(`mutation($token: String!, $newPassword: String!) {
|
|
34
|
+
resetPassword(token: $token, newPassword: $newPassword)
|
|
35
|
+
}`,{token:T,newPassword:E})}async requestMagicLink(T,E){await this.gql(`mutation($siteId: ID!, $email: String!, $magicLinkUrl: String) {
|
|
36
|
+
requestMagicLink(siteId: $siteId, email: $email, magicLinkUrl: $magicLinkUrl)
|
|
37
|
+
}`,{siteId:this.siteId,email:T,magicLinkUrl:E?.magicLinkUrl})}async verifyMagicLink(T){let E=await this.gql(`mutation($token: String!) {
|
|
38
|
+
verifyMagicLink(token: $token) {
|
|
39
|
+
token
|
|
40
|
+
user { id email }
|
|
41
|
+
}
|
|
42
|
+
}`,{token:T});return localStorage.setItem(this.tokenKey,E.verifyMagicLink.token),{user:E.verifyMagicLink.user}}async getAuthConfig(){return(await this.gql(`query($siteId: ID!) {
|
|
43
|
+
siteAuthConfig(siteId: $siteId) {
|
|
44
|
+
authEnabled
|
|
45
|
+
magicLinkEnabled
|
|
46
|
+
captchaEnabled
|
|
47
|
+
captchaProvider
|
|
48
|
+
captchaSiteKey
|
|
49
|
+
captchaRequiredForRegistration
|
|
50
|
+
captchaRequiredForLogin
|
|
51
|
+
breachedPasswordPolicy
|
|
52
|
+
}
|
|
53
|
+
}`,{siteId:this.siteId})).siteAuthConfig}async setRole(T){return(await this.gql(`mutation($role: String!) {
|
|
54
|
+
setRole(role: $role) {
|
|
55
|
+
id
|
|
56
|
+
email
|
|
57
|
+
}
|
|
58
|
+
}`,{role:T})).setRole}async createApiKey(T,E){return(await this.gql(`mutation($name: String!, $scopes: [String!]!) {
|
|
59
|
+
createApiKey(name: $name, scopes: $scopes) {
|
|
60
|
+
id
|
|
61
|
+
key
|
|
62
|
+
name
|
|
63
|
+
scopes
|
|
64
|
+
}
|
|
65
|
+
}`,{name:T,scopes:E})).createApiKey}async listApiKeys(){return(await this.gql(`query {
|
|
66
|
+
myApiKeys {
|
|
67
|
+
id
|
|
68
|
+
name
|
|
69
|
+
keyPrefix
|
|
70
|
+
scopes
|
|
71
|
+
createdAt
|
|
72
|
+
}
|
|
73
|
+
}`)).myApiKeys}async deleteApiKey(T){await this.gql(`mutation($id: ID!) {
|
|
74
|
+
deleteApiKey(id: $id)
|
|
75
|
+
}`,{id:T})}async invalidateAllSessions(){await this.gql("mutation { invalidateAllSessions }")}async getActiveDevices(){return(await this.gql(`query {
|
|
76
|
+
sdkActiveDevices {
|
|
77
|
+
id
|
|
78
|
+
browser
|
|
79
|
+
os
|
|
80
|
+
deviceType
|
|
81
|
+
lastActiveAt
|
|
82
|
+
createdAt
|
|
83
|
+
isCurrent
|
|
84
|
+
}
|
|
85
|
+
}`)).sdkActiveDevices}async revokeSession(T){await this.gql(`mutation($sessionId: ID!) {
|
|
86
|
+
sdkRevokeSession(sessionId: $sessionId)
|
|
87
|
+
}`,{sessionId:T})}}var D="SITE_ID",L="BASE_URL",R=new A({siteId:D,baseUrl:L,autoTrack:!0});window.Tribe=R;
|
package/dist/sdk.min.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
(function(X,G,V){let $=Math.random().toString(36).slice(2),M=Date.now(),Q=location.pathname,J=`__tribe_${G}`,Z=`__tribe_anon_${G}`;function R(){let z=localStorage.getItem(Z);if(!z)z=crypto.randomUUID(),localStorage.setItem(Z,z);return z}function L(z,B={}){let F={s:G,t:z,p:Q,r:document.referrer||null,sid:$,w:screen.width,...B};if(navigator.sendBeacon){let H=new Blob([JSON.stringify(F)],{type:"application/json"});navigator.sendBeacon(`${V}/collect`,H)}else fetch(`${V}/collect`,{method:"POST",body:JSON.stringify(F),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}L("pageview"),document.addEventListener("visibilitychange",()=>{if(document.visibilityState==="hidden")L("leave",{d:Date.now()-M})});let f=history.pushState;history.pushState=function(...z){L("leave",{d:Date.now()-M}),f.apply(history,z),M=Date.now(),Q=location.pathname,L("pageview")},X.addEventListener("popstate",()=>{L("leave",{d:Date.now()-M}),M=Date.now(),Q=location.pathname,L("pageview")});async function C(z,B){let F=localStorage.getItem(J),H={"Content-Type":"application/json"};if(F)H.Authorization=`Bearer ${F}`;else H["X-Tribe-Anonymous-Id"]=R();let W=await(await fetch(`${V}/graphql`,{method:"POST",headers:H,body:JSON.stringify({query:z,variables:B})})).json();if(W.errors)throw Error(W.errors[0].message);return W.data}let x={async register(z,B){let F=await C(`mutation($siteId: ID!, $email: String!, $password: String!) {
|
|
2
|
+
sdkRegister(siteId: $siteId, email: $email, password: $password) {
|
|
3
|
+
token
|
|
4
|
+
user { id email }
|
|
5
|
+
}
|
|
6
|
+
}`,{siteId:G,email:z,password:B});return localStorage.setItem(J,F.sdkRegister.token),{user:F.sdkRegister.user}},async login(z,B){let F=await C(`mutation($siteId: ID!, $email: String!, $password: String!) {
|
|
7
|
+
sdkLogin(siteId: $siteId, email: $email, password: $password) {
|
|
8
|
+
token
|
|
9
|
+
user { id email }
|
|
10
|
+
}
|
|
11
|
+
}`,{siteId:G,email:z,password:B});return localStorage.setItem(J,F.sdkLogin.token),{user:F.sdkLogin.user}},async logout(){try{await C("mutation { sdkLogout }")}catch{}localStorage.removeItem(J)},async getSession(){if(!localStorage.getItem(J))return null;try{return(await C("query { sdkSession { user { id email } } }")).sdkSession}catch{return localStorage.removeItem(J),null}},track(z,B){L("custom",{e:z,ed:B})},async feedback(z,B){await C(`mutation($siteId: ID!, $message: String!, $type: String, $email: String, $url: String) {
|
|
12
|
+
submitFeedback(siteId: $siteId, message: $message, type: $type, email: $email, url: $url)
|
|
13
|
+
}`,{siteId:G,message:z,url:Q,type:B?.type,email:B?.email})},async getFeatureFlag(z){return(await C(`query($siteId: ID!) {
|
|
14
|
+
sdkFeatureFlags(siteId: $siteId) {
|
|
15
|
+
key
|
|
16
|
+
enabled
|
|
17
|
+
}
|
|
18
|
+
}`,{siteId:G})).sdkFeatureFlags.find((H)=>H.key===z)?.enabled??!1},async getAnnouncements(){let z=await C(`query($siteId: ID!) {
|
|
19
|
+
activeAnnouncements(siteId: $siteId) {
|
|
20
|
+
id
|
|
21
|
+
title
|
|
22
|
+
message
|
|
23
|
+
type
|
|
24
|
+
}
|
|
25
|
+
}`,{siteId:G}),B=`__tribe_dismissed_${G}`,F=JSON.parse(localStorage.getItem(B)||"[]");return z.activeAnnouncements.filter((H)=>!F.includes(H.id))},async ackAnnouncement(z){let B=`__tribe_dismissed_${G}`,F=JSON.parse(localStorage.getItem(B)||"[]");if(!F.includes(z))F.push(z),localStorage.setItem(B,JSON.stringify(F));try{await C(`mutation($announcementId: ID!) {
|
|
26
|
+
ackAnnouncement(announcementId: $announcementId)
|
|
27
|
+
}`,{announcementId:z})}catch{}},async verifyEmail(z){await C(`mutation($token: String!) {
|
|
28
|
+
verifyEmail(token: $token)
|
|
29
|
+
}`,{token:z})},async resendVerification(z){await C(`mutation($verifyEmailUrl: String) {
|
|
30
|
+
resendVerification(verifyEmailUrl: $verifyEmailUrl)
|
|
31
|
+
}`,{verifyEmailUrl:z?.verifyEmailUrl})},async forgotPassword(z,B){await C(`mutation($siteId: ID!, $email: String!, $resetPasswordUrl: String) {
|
|
32
|
+
forgotPassword(siteId: $siteId, email: $email, resetPasswordUrl: $resetPasswordUrl)
|
|
33
|
+
}`,{siteId:G,email:z,resetPasswordUrl:B?.resetPasswordUrl})},async resetPassword(z,B){await C(`mutation($token: String!, $newPassword: String!) {
|
|
34
|
+
resetPassword(token: $token, newPassword: $newPassword)
|
|
35
|
+
}`,{token:z,newPassword:B})},async requestMagicLink(z,B){await C(`mutation($siteId: ID!, $email: String!, $magicLinkUrl: String) {
|
|
36
|
+
requestMagicLink(siteId: $siteId, email: $email, magicLinkUrl: $magicLinkUrl)
|
|
37
|
+
}`,{siteId:G,email:z,magicLinkUrl:B?.magicLinkUrl})},async verifyMagicLink(z){let B=await C(`mutation($token: String!) {
|
|
38
|
+
verifyMagicLink(token: $token) {
|
|
39
|
+
token
|
|
40
|
+
user { id email }
|
|
41
|
+
}
|
|
42
|
+
}`,{token:z});return localStorage.setItem(J,B.verifyMagicLink.token),{user:B.verifyMagicLink.user}},async getAuthConfig(){return(await C(`query($siteId: ID!) {
|
|
43
|
+
siteAuthConfig(siteId: $siteId) {
|
|
44
|
+
authEnabled
|
|
45
|
+
magicLinkEnabled
|
|
46
|
+
captchaEnabled
|
|
47
|
+
captchaProvider
|
|
48
|
+
captchaSiteKey
|
|
49
|
+
captchaRequiredForRegistration
|
|
50
|
+
captchaRequiredForLogin
|
|
51
|
+
breachedPasswordPolicy
|
|
52
|
+
}
|
|
53
|
+
}`,{siteId:G})).siteAuthConfig},async setRole(z){return(await C(`mutation($role: String!) {
|
|
54
|
+
setRole(role: $role) {
|
|
55
|
+
id
|
|
56
|
+
email
|
|
57
|
+
}
|
|
58
|
+
}`,{role:z})).setRole},async createApiKey(z,B){return(await C(`mutation($name: String!, $scopes: [String!]!) {
|
|
59
|
+
createApiKey(name: $name, scopes: $scopes) {
|
|
60
|
+
id
|
|
61
|
+
key
|
|
62
|
+
name
|
|
63
|
+
scopes
|
|
64
|
+
}
|
|
65
|
+
}`,{name:z,scopes:B})).createApiKey},async listApiKeys(){return(await C(`query {
|
|
66
|
+
myApiKeys {
|
|
67
|
+
id
|
|
68
|
+
name
|
|
69
|
+
keyPrefix
|
|
70
|
+
scopes
|
|
71
|
+
createdAt
|
|
72
|
+
}
|
|
73
|
+
}`)).myApiKeys},async deleteApiKey(z){await C(`mutation($id: ID!) {
|
|
74
|
+
deleteApiKey(id: $id)
|
|
75
|
+
}`,{id:z})},async invalidateAllSessions(){await C("mutation { invalidateAllSessions }")},async getActiveDevices(){return(await C(`query {
|
|
76
|
+
sdkActiveDevices {
|
|
77
|
+
id
|
|
78
|
+
browser
|
|
79
|
+
os
|
|
80
|
+
deviceType
|
|
81
|
+
lastActiveAt
|
|
82
|
+
createdAt
|
|
83
|
+
isCurrent
|
|
84
|
+
}
|
|
85
|
+
}`)).sdkActiveDevices},async revokeSession(z){await C(`mutation($sessionId: ID!) {
|
|
86
|
+
sdkRevokeSession(sessionId: $sessionId)
|
|
87
|
+
}`,{sessionId:z})}};X.Tribe=x})(window,"SITE_ID","BASE_URL");
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tribecloud/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/esm/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/esm/index.d.ts",
|
|
12
|
+
"default": "./dist/esm/index.js"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "bun run build:iife && bun run build:esm && bun run build:types",
|
|
21
|
+
"build:iife": "bun build src/iife.ts --outfile dist/iife/sdk.min.js --minify --target browser",
|
|
22
|
+
"build:esm": "bun build src/index.ts --outfile dist/esm/index.js --target browser --format esm",
|
|
23
|
+
"build:types": "bunx tsc --project tsconfig.build.json",
|
|
24
|
+
"dev": "bun build src/iife.ts --outfile dist/iife/sdk.min.js --target browser --watch",
|
|
25
|
+
"test": "bun test",
|
|
26
|
+
"test:watch": "bun test --watch"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bun": "latest",
|
|
30
|
+
"typescript": "^5"
|
|
31
|
+
}
|
|
32
|
+
}
|