@dotbots-boutique/auth-sdk 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/LICENSE +21 -0
- package/README.md +299 -0
- package/SECURITY.md +51 -0
- package/dist/cjs/index.js +391 -0
- package/dist/cjs/react/index.js +66 -0
- package/dist/esm/index.js +388 -0
- package/dist/esm/react/index.js +63 -0
- package/dist/types/DotBotsAuth.d.ts +32 -0
- package/dist/types/DotBotsAuthError.d.ts +6 -0
- package/dist/types/PostMessageHandler.d.ts +18 -0
- package/dist/types/ProxyConfigManager.d.ts +16 -0
- package/dist/types/TokenManager.d.ts +21 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +388 -0
- package/dist/types/react/DotBotsAuthProvider.d.ts +23 -0
- package/dist/types/react/index.d.ts +3 -0
- package/dist/types/react/index.js +63 -0
- package/dist/types/react/useDotBotsAuth.d.ts +2 -0
- package/dist/types/types.d.ts +46 -0
- package/package.json +70 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
class DotBotsAuthError extends Error {
|
|
2
|
+
constructor(code, message, originalError) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.code = code;
|
|
5
|
+
this.originalError = originalError;
|
|
6
|
+
this.name = 'DotBotsAuthError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class TokenManager {
|
|
11
|
+
constructor(config, environment, onRefreshed, onSessionExpired) {
|
|
12
|
+
this.accessToken = null;
|
|
13
|
+
this.refreshToken = null;
|
|
14
|
+
this.expiresAt = 0;
|
|
15
|
+
this.refreshTimer = null;
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.environment = environment;
|
|
18
|
+
this.onRefreshed = onRefreshed;
|
|
19
|
+
this.onSessionExpired = onSessionExpired;
|
|
20
|
+
}
|
|
21
|
+
setTokens(tokens) {
|
|
22
|
+
this.accessToken = tokens.accessToken;
|
|
23
|
+
this.refreshToken = tokens.refreshToken;
|
|
24
|
+
this.expiresAt = Date.now() + tokens.expiresIn * 1000;
|
|
25
|
+
this.scheduleRefresh(tokens.expiresIn * 1000);
|
|
26
|
+
}
|
|
27
|
+
getAccessToken() {
|
|
28
|
+
return this.accessToken;
|
|
29
|
+
}
|
|
30
|
+
isAuthenticated() {
|
|
31
|
+
return this.accessToken !== null && Date.now() < this.expiresAt;
|
|
32
|
+
}
|
|
33
|
+
async exchangeCode(code) {
|
|
34
|
+
const response = await this.apiRequest('/api/auth/token', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({ code, appId: this.config.appId }),
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
if (response.status === 401 || response.status === 400) {
|
|
41
|
+
throw new DotBotsAuthError('CODE_EXPIRED', 'Auth code is expired or invalid');
|
|
42
|
+
}
|
|
43
|
+
throw new DotBotsAuthError('NETWORK_ERROR', `Token exchange failed: ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
const tokens = await response.json();
|
|
46
|
+
this.setTokens(tokens);
|
|
47
|
+
}
|
|
48
|
+
async refresh() {
|
|
49
|
+
if (!this.refreshToken) {
|
|
50
|
+
throw new DotBotsAuthError('REFRESH_FAILED', 'No refresh token available');
|
|
51
|
+
}
|
|
52
|
+
let response;
|
|
53
|
+
try {
|
|
54
|
+
response = await this.apiRequest('/api/auth/refresh', {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
refreshToken: this.refreshToken,
|
|
59
|
+
appId: this.config.appId,
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
throw new DotBotsAuthError('REFRESH_FAILED', 'Token refresh request failed', err instanceof Error ? err : undefined);
|
|
65
|
+
}
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new DotBotsAuthError('REFRESH_FAILED', `Token refresh failed: ${response.status}`);
|
|
68
|
+
}
|
|
69
|
+
const tokens = await response.json();
|
|
70
|
+
this.setTokens(tokens);
|
|
71
|
+
}
|
|
72
|
+
async revoke() {
|
|
73
|
+
if (this.accessToken && this.refreshToken) {
|
|
74
|
+
try {
|
|
75
|
+
await this.apiRequest('/api/auth/revoke', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({ refreshToken: this.refreshToken }),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Best effort — continue with local cleanup regardless
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.clear();
|
|
89
|
+
}
|
|
90
|
+
clear() {
|
|
91
|
+
this.accessToken = null;
|
|
92
|
+
this.refreshToken = null;
|
|
93
|
+
this.expiresAt = 0;
|
|
94
|
+
if (this.refreshTimer !== null) {
|
|
95
|
+
clearTimeout(this.refreshTimer);
|
|
96
|
+
this.refreshTimer = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
scheduleRefresh(expiresInMs) {
|
|
100
|
+
if (this.refreshTimer !== null) {
|
|
101
|
+
clearTimeout(this.refreshTimer);
|
|
102
|
+
}
|
|
103
|
+
const buffer = this.config.tokenRefreshBuffer ?? 60000;
|
|
104
|
+
const delay = Math.max(expiresInMs - buffer, 0);
|
|
105
|
+
this.refreshTimer = setTimeout(async () => {
|
|
106
|
+
try {
|
|
107
|
+
await this.refresh();
|
|
108
|
+
this.onRefreshed();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
this.onSessionExpired();
|
|
112
|
+
this.config.onTokenRefreshFailed?.();
|
|
113
|
+
}
|
|
114
|
+
}, delay);
|
|
115
|
+
}
|
|
116
|
+
apiRequest(path, init) {
|
|
117
|
+
const headers = new Headers(init.headers);
|
|
118
|
+
headers.set('X-App-Id', this.config.appId);
|
|
119
|
+
headers.set('X-Environment', this.environment);
|
|
120
|
+
return fetch(`${this.config.apiUrl}${path}`, { ...init, headers });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class PostMessageHandler {
|
|
125
|
+
constructor(marketplaceOrigin) {
|
|
126
|
+
this.marketplaceOrigin = marketplaceOrigin;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Detect if the current window is running inside an iframe.
|
|
130
|
+
* Also performs a security check: if window.frameElement is accessible
|
|
131
|
+
* in a cross-origin context, something is wrong.
|
|
132
|
+
*/
|
|
133
|
+
isInIframe() {
|
|
134
|
+
try {
|
|
135
|
+
if (window.self === window.top)
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Cross-origin restriction — we are in an iframe
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
// Security check: frameElement should NOT be accessible cross-origin
|
|
143
|
+
try {
|
|
144
|
+
if (window.frameElement) {
|
|
145
|
+
throw new DotBotsAuthError('UNAUTHORIZED', 'Security violation: window.frameElement is accessible in cross-origin iframe');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (err instanceof DotBotsAuthError)
|
|
150
|
+
throw err;
|
|
151
|
+
// SecurityError is expected — this is the normal case
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Send a ready signal to the parent window and wait for the auth code.
|
|
157
|
+
*/
|
|
158
|
+
requestAuthCode(appId, timeout) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const timer = setTimeout(() => {
|
|
161
|
+
window.removeEventListener('message', handler);
|
|
162
|
+
reject(new DotBotsAuthError('IFRAME_TIMEOUT', `Parent did not respond within ${timeout}ms`));
|
|
163
|
+
}, timeout);
|
|
164
|
+
const handler = (event) => {
|
|
165
|
+
if (event.origin !== this.marketplaceOrigin)
|
|
166
|
+
return;
|
|
167
|
+
if (event.data?.type !== 'DOTBOTS_AUTH_CODE')
|
|
168
|
+
return;
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
window.removeEventListener('message', handler);
|
|
171
|
+
resolve(event.data.code);
|
|
172
|
+
};
|
|
173
|
+
window.addEventListener('message', handler);
|
|
174
|
+
window.parent.postMessage({ type: 'DOTBOTS_READY', appId }, this.marketplaceOrigin);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Notify the parent that the user has logged out.
|
|
179
|
+
*/
|
|
180
|
+
sendLogout() {
|
|
181
|
+
if (this.isInIframe()) {
|
|
182
|
+
window.parent.postMessage({ type: 'DOTBOTS_LOGOUT' }, this.marketplaceOrigin);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class ProxyConfigManager {
|
|
188
|
+
constructor(apiUrl, appId, environment) {
|
|
189
|
+
this.config = null;
|
|
190
|
+
this.apiUrl = apiUrl;
|
|
191
|
+
this.appId = appId;
|
|
192
|
+
this.environment = environment;
|
|
193
|
+
}
|
|
194
|
+
async fetchConfig() {
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch(`${this.apiUrl}/api/proxy/config`, {
|
|
197
|
+
headers: {
|
|
198
|
+
'X-App-Id': this.appId,
|
|
199
|
+
'X-Environment': this.environment,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(`Proxy config request failed: ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
this.config = await response.json();
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
// Non-fatal: log warning, SDK falls back to direct apiUrl
|
|
209
|
+
console.warn('[DotBotsAuth] Could not fetch proxy config, falling back to direct API:', err instanceof Error ? err.message : err);
|
|
210
|
+
this.config = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
getProxyUrl() {
|
|
214
|
+
return this.config?.proxyUrl ?? null;
|
|
215
|
+
}
|
|
216
|
+
getConfig() {
|
|
217
|
+
return this.config;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Returns the base URL to use for API calls.
|
|
221
|
+
* Uses the proxy if available, otherwise falls back to apiUrl.
|
|
222
|
+
*/
|
|
223
|
+
getBaseUrl() {
|
|
224
|
+
return this.config?.proxyUrl ?? this.apiUrl;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
class DotBotsAuth {
|
|
229
|
+
constructor(config) {
|
|
230
|
+
this.listeners = new Map();
|
|
231
|
+
this.cachedUser = null;
|
|
232
|
+
this.initialized = false;
|
|
233
|
+
this.config = config;
|
|
234
|
+
this.environment = this.detectEnvironment();
|
|
235
|
+
const marketplaceOrigin = config.marketplaceOrigin ?? 'https://dotbots.boutique';
|
|
236
|
+
this.tokenManager = new TokenManager(config, this.environment, () => this.emit('tokenRefreshed'), () => this.emit('sessionExpired'));
|
|
237
|
+
this.postMessageHandler = new PostMessageHandler(marketplaceOrigin);
|
|
238
|
+
this.proxyConfigManager = new ProxyConfigManager(config.apiUrl, config.appId, this.environment);
|
|
239
|
+
}
|
|
240
|
+
async initialize() {
|
|
241
|
+
// Step 1 — Fetch proxy config
|
|
242
|
+
await this.proxyConfigManager.fetchConfig();
|
|
243
|
+
// Step 2 — Authenticate
|
|
244
|
+
if (this.postMessageHandler.isInIframe()) {
|
|
245
|
+
await this.initializeIframe();
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
await this.initializeStandalone();
|
|
249
|
+
}
|
|
250
|
+
this.initialized = true;
|
|
251
|
+
}
|
|
252
|
+
async getUser() {
|
|
253
|
+
this.assertInitialized();
|
|
254
|
+
if (this.cachedUser)
|
|
255
|
+
return this.cachedUser;
|
|
256
|
+
const response = await this.buildRequest(`${this.config.apiUrl}/api/auth/me`);
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
if (response.status === 401) {
|
|
259
|
+
throw new DotBotsAuthError('UNAUTHORIZED', 'Not authorized to access this app');
|
|
260
|
+
}
|
|
261
|
+
throw new DotBotsAuthError('NETWORK_ERROR', `Failed to fetch user: ${response.status}`);
|
|
262
|
+
}
|
|
263
|
+
this.cachedUser = await response.json();
|
|
264
|
+
this.emit('userLoaded');
|
|
265
|
+
return this.cachedUser;
|
|
266
|
+
}
|
|
267
|
+
can(permission) {
|
|
268
|
+
if (!this.cachedUser) {
|
|
269
|
+
throw new DotBotsAuthError('NOT_INITIALIZED', 'User data not loaded. Call getUser() first.');
|
|
270
|
+
}
|
|
271
|
+
return this.cachedUser.permissions.includes(permission);
|
|
272
|
+
}
|
|
273
|
+
canAll(permissions) {
|
|
274
|
+
return permissions.every((p) => this.can(p));
|
|
275
|
+
}
|
|
276
|
+
canAny(permissions) {
|
|
277
|
+
return permissions.some((p) => this.can(p));
|
|
278
|
+
}
|
|
279
|
+
hasRole(role) {
|
|
280
|
+
if (!this.cachedUser) {
|
|
281
|
+
throw new DotBotsAuthError('NOT_INITIALIZED', 'User data not loaded. Call getUser() first.');
|
|
282
|
+
}
|
|
283
|
+
return this.cachedUser.roles.includes(role);
|
|
284
|
+
}
|
|
285
|
+
async fetch(url, options) {
|
|
286
|
+
this.assertInitialized();
|
|
287
|
+
const baseUrl = this.proxyConfigManager.getBaseUrl();
|
|
288
|
+
const fullUrl = `${baseUrl}${url.startsWith('/') ? url : `/${url}`}`;
|
|
289
|
+
let response = await this.buildRequest(fullUrl, options);
|
|
290
|
+
// On 401, try one refresh then retry
|
|
291
|
+
if (response.status === 401) {
|
|
292
|
+
try {
|
|
293
|
+
await this.tokenManager.refresh();
|
|
294
|
+
this.emit('tokenRefreshed');
|
|
295
|
+
response = await this.buildRequest(fullUrl, options);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
throw new DotBotsAuthError('UNAUTHORIZED', 'Request unauthorized and token refresh failed');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return response;
|
|
302
|
+
}
|
|
303
|
+
async logout() {
|
|
304
|
+
this.assertInitialized();
|
|
305
|
+
await this.tokenManager.revoke();
|
|
306
|
+
this.cachedUser = null;
|
|
307
|
+
this.emit('loggedOut');
|
|
308
|
+
if (this.postMessageHandler.isInIframe()) {
|
|
309
|
+
this.postMessageHandler.sendLogout();
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const redirectUri = encodeURIComponent(window.location.origin);
|
|
313
|
+
window.location.href = `${this.config.apiUrl}/api/auth/logout?redirectUri=${redirectUri}`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
on(event, handler) {
|
|
317
|
+
if (!this.listeners.has(event)) {
|
|
318
|
+
this.listeners.set(event, new Set());
|
|
319
|
+
}
|
|
320
|
+
this.listeners.get(event).add(handler);
|
|
321
|
+
}
|
|
322
|
+
off(event, handler) {
|
|
323
|
+
this.listeners.get(event)?.delete(handler);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Returns the proxy config if available. Useful for advanced use cases.
|
|
327
|
+
*/
|
|
328
|
+
getProxyConfig() {
|
|
329
|
+
return this.proxyConfigManager.getConfig();
|
|
330
|
+
}
|
|
331
|
+
// --- Private ---
|
|
332
|
+
detectEnvironment() {
|
|
333
|
+
const hostname = window.location.hostname;
|
|
334
|
+
if (hostname.includes('test-apps'))
|
|
335
|
+
return 'test';
|
|
336
|
+
return 'prod';
|
|
337
|
+
}
|
|
338
|
+
async initializeIframe() {
|
|
339
|
+
const timeout = this.config.iframeTimeout ?? 5000;
|
|
340
|
+
const code = await this.postMessageHandler.requestAuthCode(this.config.appId, timeout);
|
|
341
|
+
await this.tokenManager.exchangeCode(code);
|
|
342
|
+
}
|
|
343
|
+
async initializeStandalone() {
|
|
344
|
+
const url = new URL(window.location.href);
|
|
345
|
+
const code = url.searchParams.get('code');
|
|
346
|
+
if (code) {
|
|
347
|
+
// Remove code from URL immediately
|
|
348
|
+
url.searchParams.delete('code');
|
|
349
|
+
window.history.replaceState({}, '', url.toString());
|
|
350
|
+
await this.tokenManager.exchangeCode(code);
|
|
351
|
+
}
|
|
352
|
+
else if (!this.tokenManager.isAuthenticated()) {
|
|
353
|
+
// Redirect to auth
|
|
354
|
+
const redirectUri = encodeURIComponent(window.location.href);
|
|
355
|
+
window.location.href = `${this.config.apiUrl}/api/auth/authorize?appId=${this.config.appId}&redirectUri=${redirectUri}`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async buildRequest(url, options) {
|
|
359
|
+
const headers = new Headers(options?.headers);
|
|
360
|
+
const accessToken = this.tokenManager.getAccessToken();
|
|
361
|
+
if (accessToken) {
|
|
362
|
+
headers.set('Authorization', `Bearer ${accessToken}`);
|
|
363
|
+
}
|
|
364
|
+
headers.set('X-App-Id', this.config.appId);
|
|
365
|
+
headers.set('X-Environment', this.environment);
|
|
366
|
+
return globalThis.fetch(url, { ...options, headers });
|
|
367
|
+
}
|
|
368
|
+
assertInitialized() {
|
|
369
|
+
if (!this.initialized) {
|
|
370
|
+
throw new DotBotsAuthError('NOT_INITIALIZED', 'Call initialize() before using the SDK');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
emit(event) {
|
|
374
|
+
const handlers = this.listeners.get(event);
|
|
375
|
+
if (handlers) {
|
|
376
|
+
for (const handler of handlers) {
|
|
377
|
+
try {
|
|
378
|
+
handler();
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// Don't let listener errors break the SDK
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export { DotBotsAuth, DotBotsAuthError };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, useState, useEffect, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
const DotBotsAuthContext = createContext(null);
|
|
5
|
+
function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent, }) {
|
|
6
|
+
const [user, setUser] = useState(null);
|
|
7
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let cancelled = false;
|
|
11
|
+
async function init() {
|
|
12
|
+
try {
|
|
13
|
+
await auth.initialize();
|
|
14
|
+
const userData = await auth.getUser();
|
|
15
|
+
if (!cancelled) {
|
|
16
|
+
setUser(userData);
|
|
17
|
+
setIsLoading(false);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (!cancelled) {
|
|
22
|
+
setError(err);
|
|
23
|
+
setIsLoading(false);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
init();
|
|
28
|
+
return () => {
|
|
29
|
+
cancelled = true;
|
|
30
|
+
};
|
|
31
|
+
}, [auth]);
|
|
32
|
+
if (isLoading) {
|
|
33
|
+
return jsx(Fragment, { children: loadingComponent ?? jsx("div", { children: "Loading..." }) });
|
|
34
|
+
}
|
|
35
|
+
if (error) {
|
|
36
|
+
if (errorComponent) {
|
|
37
|
+
return jsx(Fragment, { children: errorComponent(error) });
|
|
38
|
+
}
|
|
39
|
+
return jsxs("div", { children: ["Authentication error: ", error.message] });
|
|
40
|
+
}
|
|
41
|
+
const value = {
|
|
42
|
+
user,
|
|
43
|
+
isLoading,
|
|
44
|
+
error,
|
|
45
|
+
can: (permission) => auth.can(permission),
|
|
46
|
+
canAll: (permissions) => auth.canAll(permissions),
|
|
47
|
+
canAny: (permissions) => auth.canAny(permissions),
|
|
48
|
+
hasRole: (role) => auth.hasRole(role),
|
|
49
|
+
fetch: (url, options) => auth.fetch(url, options),
|
|
50
|
+
logout: () => auth.logout(),
|
|
51
|
+
};
|
|
52
|
+
return (jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function useDotBotsAuth() {
|
|
56
|
+
const context = useContext(DotBotsAuthContext);
|
|
57
|
+
if (!context) {
|
|
58
|
+
throw new Error('useDotBotsAuth must be used within a DotBotsAuthProvider');
|
|
59
|
+
}
|
|
60
|
+
return context;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { DotBotsAuthProvider, useDotBotsAuth };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DotBotsConfig, DotBotsUser, DotBotsAuthEvent, DotBotsProxyConfig } from './types.js';
|
|
2
|
+
export declare class DotBotsAuth {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly environment;
|
|
5
|
+
private readonly tokenManager;
|
|
6
|
+
private readonly postMessageHandler;
|
|
7
|
+
private readonly proxyConfigManager;
|
|
8
|
+
private readonly listeners;
|
|
9
|
+
private cachedUser;
|
|
10
|
+
private initialized;
|
|
11
|
+
constructor(config: DotBotsConfig);
|
|
12
|
+
initialize(): Promise<void>;
|
|
13
|
+
getUser(): Promise<DotBotsUser>;
|
|
14
|
+
can(permission: string): boolean;
|
|
15
|
+
canAll(permissions: string[]): boolean;
|
|
16
|
+
canAny(permissions: string[]): boolean;
|
|
17
|
+
hasRole(role: string): boolean;
|
|
18
|
+
fetch(url: string, options?: RequestInit): Promise<Response>;
|
|
19
|
+
logout(): Promise<void>;
|
|
20
|
+
on(event: DotBotsAuthEvent, handler: Function): void;
|
|
21
|
+
off(event: DotBotsAuthEvent, handler: Function): void;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the proxy config if available. Useful for advanced use cases.
|
|
24
|
+
*/
|
|
25
|
+
getProxyConfig(): DotBotsProxyConfig | null;
|
|
26
|
+
private detectEnvironment;
|
|
27
|
+
private initializeIframe;
|
|
28
|
+
private initializeStandalone;
|
|
29
|
+
private buildRequest;
|
|
30
|
+
private assertInitialized;
|
|
31
|
+
private emit;
|
|
32
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DotBotsAuthErrorCode } from './types.js';
|
|
2
|
+
export declare class DotBotsAuthError extends Error {
|
|
3
|
+
code: DotBotsAuthErrorCode;
|
|
4
|
+
originalError?: Error | undefined;
|
|
5
|
+
constructor(code: DotBotsAuthErrorCode, message: string, originalError?: Error | undefined);
|
|
6
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class PostMessageHandler {
|
|
2
|
+
private readonly marketplaceOrigin;
|
|
3
|
+
constructor(marketplaceOrigin: string);
|
|
4
|
+
/**
|
|
5
|
+
* Detect if the current window is running inside an iframe.
|
|
6
|
+
* Also performs a security check: if window.frameElement is accessible
|
|
7
|
+
* in a cross-origin context, something is wrong.
|
|
8
|
+
*/
|
|
9
|
+
isInIframe(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Send a ready signal to the parent window and wait for the auth code.
|
|
12
|
+
*/
|
|
13
|
+
requestAuthCode(appId: string, timeout: number): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Notify the parent that the user has logged out.
|
|
16
|
+
*/
|
|
17
|
+
sendLogout(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DotBotsProxyConfig, DotBotsEnvironment } from './types.js';
|
|
2
|
+
export declare class ProxyConfigManager {
|
|
3
|
+
private config;
|
|
4
|
+
private readonly apiUrl;
|
|
5
|
+
private readonly appId;
|
|
6
|
+
private readonly environment;
|
|
7
|
+
constructor(apiUrl: string, appId: string, environment: DotBotsEnvironment);
|
|
8
|
+
fetchConfig(): Promise<void>;
|
|
9
|
+
getProxyUrl(): string | null;
|
|
10
|
+
getConfig(): DotBotsProxyConfig | null;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the base URL to use for API calls.
|
|
13
|
+
* Uses the proxy if available, otherwise falls back to apiUrl.
|
|
14
|
+
*/
|
|
15
|
+
getBaseUrl(): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DotBotsConfig, TokenPair, DotBotsEnvironment } from './types.js';
|
|
2
|
+
export declare class TokenManager {
|
|
3
|
+
private accessToken;
|
|
4
|
+
private refreshToken;
|
|
5
|
+
private expiresAt;
|
|
6
|
+
private refreshTimer;
|
|
7
|
+
private readonly config;
|
|
8
|
+
private readonly environment;
|
|
9
|
+
private readonly onRefreshed;
|
|
10
|
+
private readonly onSessionExpired;
|
|
11
|
+
constructor(config: DotBotsConfig, environment: DotBotsEnvironment, onRefreshed: () => void, onSessionExpired: () => void);
|
|
12
|
+
setTokens(tokens: TokenPair): void;
|
|
13
|
+
getAccessToken(): string | null;
|
|
14
|
+
isAuthenticated(): boolean;
|
|
15
|
+
exchangeCode(code: string): Promise<void>;
|
|
16
|
+
refresh(): Promise<void>;
|
|
17
|
+
revoke(): Promise<void>;
|
|
18
|
+
clear(): void;
|
|
19
|
+
private scheduleRefresh;
|
|
20
|
+
private apiRequest;
|
|
21
|
+
}
|