@blinkdotnew/sdk 0.17.3 → 0.18.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 +665 -85
- package/dist/index.d.mts +209 -47
- package/dist/index.d.ts +209 -47
- package/dist/index.js +902 -125
- package/dist/index.mjs +902 -125
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -16,9 +16,49 @@ var BlinkError = class extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
var BlinkAuthError = class extends BlinkError {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
code;
|
|
20
|
+
retryable;
|
|
21
|
+
userMessage;
|
|
22
|
+
constructor(code, message, userMessage, details) {
|
|
23
|
+
super(message, code, 401, details);
|
|
21
24
|
this.name = "BlinkAuthError";
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.retryable = ["NETWORK_ERROR", "RATE_LIMITED"].includes(code);
|
|
27
|
+
this.userMessage = userMessage || this.getDefaultUserMessage(code);
|
|
28
|
+
}
|
|
29
|
+
getDefaultUserMessage(code) {
|
|
30
|
+
switch (code) {
|
|
31
|
+
case "INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */:
|
|
32
|
+
return "Invalid email or password. Please try again.";
|
|
33
|
+
case "EMAIL_NOT_VERIFIED" /* EMAIL_NOT_VERIFIED */:
|
|
34
|
+
return "Please verify your email address before signing in.";
|
|
35
|
+
case "POPUP_CANCELED" /* POPUP_CANCELED */:
|
|
36
|
+
return "Sign-in was canceled. Please try again.";
|
|
37
|
+
case "NETWORK_ERROR" /* NETWORK_ERROR */:
|
|
38
|
+
return "Network error. Please check your connection and try again.";
|
|
39
|
+
case "RATE_LIMITED" /* RATE_LIMITED */:
|
|
40
|
+
return "Too many attempts. Please wait a moment and try again.";
|
|
41
|
+
case "AUTH_TIMEOUT" /* AUTH_TIMEOUT */:
|
|
42
|
+
return "Authentication timed out. Please try again.";
|
|
43
|
+
case "REDIRECT_FAILED" /* REDIRECT_FAILED */:
|
|
44
|
+
return "Redirect failed. Please try again.";
|
|
45
|
+
case "TOKEN_EXPIRED" /* TOKEN_EXPIRED */:
|
|
46
|
+
return "Session expired. Please sign in again.";
|
|
47
|
+
case "USER_NOT_FOUND" /* USER_NOT_FOUND */:
|
|
48
|
+
return "User not found. Please check your email and try again.";
|
|
49
|
+
case "EMAIL_ALREADY_EXISTS" /* EMAIL_ALREADY_EXISTS */:
|
|
50
|
+
return "An account with this email already exists.";
|
|
51
|
+
case "WEAK_PASSWORD" /* WEAK_PASSWORD */:
|
|
52
|
+
return "Password is too weak. Please choose a stronger password.";
|
|
53
|
+
case "INVALID_EMAIL" /* INVALID_EMAIL */:
|
|
54
|
+
return "Please enter a valid email address.";
|
|
55
|
+
case "MAGIC_LINK_EXPIRED" /* MAGIC_LINK_EXPIRED */:
|
|
56
|
+
return "Magic link has expired. Please request a new one.";
|
|
57
|
+
case "VERIFICATION_FAILED" /* VERIFICATION_FAILED */:
|
|
58
|
+
return "Verification failed. Please try again.";
|
|
59
|
+
default:
|
|
60
|
+
return "Authentication error. Please try again.";
|
|
61
|
+
}
|
|
22
62
|
}
|
|
23
63
|
};
|
|
24
64
|
var BlinkNetworkError = class extends BlinkError {
|
|
@@ -863,15 +903,32 @@ var HttpClient = class {
|
|
|
863
903
|
// src/auth.ts
|
|
864
904
|
var BlinkAuth = class {
|
|
865
905
|
config;
|
|
906
|
+
authConfig;
|
|
866
907
|
authState;
|
|
867
908
|
listeners = /* @__PURE__ */ new Set();
|
|
868
|
-
authUrl
|
|
909
|
+
authUrl;
|
|
910
|
+
coreUrl;
|
|
869
911
|
parentWindowTokens = null;
|
|
870
912
|
isIframe = false;
|
|
871
913
|
initializationPromise = null;
|
|
872
914
|
isInitialized = false;
|
|
873
915
|
constructor(config) {
|
|
874
916
|
this.config = config;
|
|
917
|
+
this.authConfig = {
|
|
918
|
+
mode: "managed",
|
|
919
|
+
// Default mode
|
|
920
|
+
authUrl: "https://blink.new",
|
|
921
|
+
coreUrl: "https://core.blink.new",
|
|
922
|
+
...config.auth
|
|
923
|
+
};
|
|
924
|
+
this.authUrl = this.authConfig.authUrl || "https://blink.new";
|
|
925
|
+
this.coreUrl = this.authConfig.coreUrl || "https://core.blink.new";
|
|
926
|
+
if (typeof window !== "undefined" && this.coreUrl === "https://core.blink.new" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) {
|
|
927
|
+
console.warn("\u26A0\uFE0F Using default coreUrl in development. Set auth.coreUrl to your app origin for headless auth endpoints to work.");
|
|
928
|
+
}
|
|
929
|
+
if (config.authRequired !== void 0 && !config.auth?.mode) {
|
|
930
|
+
this.authConfig.mode = config.authRequired ? "managed" : "headless";
|
|
931
|
+
}
|
|
875
932
|
this.authState = {
|
|
876
933
|
user: null,
|
|
877
934
|
tokens: null,
|
|
@@ -881,6 +938,7 @@ var BlinkAuth = class {
|
|
|
881
938
|
if (typeof window !== "undefined") {
|
|
882
939
|
this.isIframe = window.self !== window.top;
|
|
883
940
|
this.setupParentWindowListener();
|
|
941
|
+
this.setupCrossTabSync();
|
|
884
942
|
this.initializationPromise = this.initialize();
|
|
885
943
|
} else {
|
|
886
944
|
this.isInitialized = true;
|
|
@@ -990,7 +1048,14 @@ var BlinkAuth = class {
|
|
|
990
1048
|
* Redirect to Blink auth page
|
|
991
1049
|
*/
|
|
992
1050
|
login(nextUrl) {
|
|
993
|
-
let redirectUrl = nextUrl ||
|
|
1051
|
+
let redirectUrl = nextUrl || this.authConfig.redirectUrl;
|
|
1052
|
+
if (!redirectUrl && typeof window !== "undefined") {
|
|
1053
|
+
if (window.location.href.startsWith("http")) {
|
|
1054
|
+
redirectUrl = window.location.href;
|
|
1055
|
+
} else {
|
|
1056
|
+
redirectUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
994
1059
|
if (redirectUrl && typeof window !== "undefined") {
|
|
995
1060
|
try {
|
|
996
1061
|
const url = new URL(redirectUrl);
|
|
@@ -1002,7 +1067,7 @@ var BlinkAuth = class {
|
|
|
1002
1067
|
}
|
|
1003
1068
|
}
|
|
1004
1069
|
const authUrl = new URL("/auth", this.authUrl);
|
|
1005
|
-
authUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1070
|
+
authUrl.searchParams.set("redirect_url", redirectUrl || "");
|
|
1006
1071
|
if (this.config.projectId) {
|
|
1007
1072
|
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1008
1073
|
}
|
|
@@ -1113,7 +1178,7 @@ var BlinkAuth = class {
|
|
|
1113
1178
|
}
|
|
1114
1179
|
const timeout = setTimeout(() => {
|
|
1115
1180
|
unsubscribe();
|
|
1116
|
-
reject(new BlinkAuthError("Authentication timeout - no user available"));
|
|
1181
|
+
reject(new BlinkAuthError("AUTH_TIMEOUT" /* AUTH_TIMEOUT */, "Authentication timeout - no user available"));
|
|
1117
1182
|
}, 5e3);
|
|
1118
1183
|
const unsubscribe = this.onAuthStateChanged((state) => {
|
|
1119
1184
|
if (state.user) {
|
|
@@ -1123,14 +1188,14 @@ var BlinkAuth = class {
|
|
|
1123
1188
|
} else if (!state.isLoading && !state.isAuthenticated) {
|
|
1124
1189
|
clearTimeout(timeout);
|
|
1125
1190
|
unsubscribe();
|
|
1126
|
-
reject(new BlinkAuthError("Not authenticated"));
|
|
1191
|
+
reject(new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "Not authenticated"));
|
|
1127
1192
|
}
|
|
1128
1193
|
});
|
|
1129
1194
|
});
|
|
1130
1195
|
}
|
|
1131
1196
|
let token = this.getToken();
|
|
1132
1197
|
if (!token) {
|
|
1133
|
-
throw new BlinkAuthError("No access token available");
|
|
1198
|
+
throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
|
|
1134
1199
|
}
|
|
1135
1200
|
try {
|
|
1136
1201
|
const response = await fetch(`${this.authUrl}/api/auth/me`, {
|
|
@@ -1165,7 +1230,9 @@ var BlinkAuth = class {
|
|
|
1165
1230
|
this.redirectToAuth();
|
|
1166
1231
|
}
|
|
1167
1232
|
}
|
|
1168
|
-
|
|
1233
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1234
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1235
|
+
throw new BlinkAuthError(errorCode, errorData.error || `Failed to fetch user: ${response.statusText}`);
|
|
1169
1236
|
}
|
|
1170
1237
|
const data = await response.json();
|
|
1171
1238
|
const user = data.user;
|
|
@@ -1178,16 +1245,650 @@ var BlinkAuth = class {
|
|
|
1178
1245
|
if (error instanceof BlinkAuthError) {
|
|
1179
1246
|
throw error;
|
|
1180
1247
|
}
|
|
1181
|
-
throw new BlinkAuthError(`Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1248
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Sign up with email and password (headless mode)
|
|
1253
|
+
*/
|
|
1254
|
+
async signUp(data) {
|
|
1255
|
+
if (this.authConfig.mode !== "headless") {
|
|
1256
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signUp is only available in headless mode");
|
|
1257
|
+
}
|
|
1258
|
+
try {
|
|
1259
|
+
const response = await fetch(`${this.coreUrl}/api/auth/signup`, {
|
|
1260
|
+
method: "POST",
|
|
1261
|
+
headers: {
|
|
1262
|
+
"Content-Type": "application/json"
|
|
1263
|
+
},
|
|
1264
|
+
body: JSON.stringify({
|
|
1265
|
+
...data,
|
|
1266
|
+
projectId: this.config.projectId
|
|
1267
|
+
})
|
|
1268
|
+
});
|
|
1269
|
+
if (!response.ok) {
|
|
1270
|
+
const errorData = await response.json();
|
|
1271
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1272
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Sign up failed");
|
|
1273
|
+
}
|
|
1274
|
+
const result = await response.json();
|
|
1275
|
+
await this.setTokens({
|
|
1276
|
+
access_token: result.access_token,
|
|
1277
|
+
refresh_token: result.refresh_token,
|
|
1278
|
+
token_type: result.token_type,
|
|
1279
|
+
expires_in: result.expires_in,
|
|
1280
|
+
refresh_expires_in: result.refresh_expires_in
|
|
1281
|
+
}, true);
|
|
1282
|
+
return result.user;
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
if (error instanceof BlinkAuthError) {
|
|
1285
|
+
throw error;
|
|
1286
|
+
}
|
|
1287
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Sign in with email and password (headless mode)
|
|
1292
|
+
*/
|
|
1293
|
+
async signInWithEmail(email, password) {
|
|
1294
|
+
if (this.authConfig.mode !== "headless") {
|
|
1295
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithEmail is only available in headless mode");
|
|
1296
|
+
}
|
|
1297
|
+
try {
|
|
1298
|
+
const response = await fetch(`${this.coreUrl}/api/auth/signin/email`, {
|
|
1299
|
+
method: "POST",
|
|
1300
|
+
headers: {
|
|
1301
|
+
"Content-Type": "application/json"
|
|
1302
|
+
},
|
|
1303
|
+
body: JSON.stringify({
|
|
1304
|
+
email,
|
|
1305
|
+
password,
|
|
1306
|
+
projectId: this.config.projectId
|
|
1307
|
+
})
|
|
1308
|
+
});
|
|
1309
|
+
if (!response.ok) {
|
|
1310
|
+
const errorData = await response.json();
|
|
1311
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1312
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Sign in failed");
|
|
1313
|
+
}
|
|
1314
|
+
const result = await response.json();
|
|
1315
|
+
await this.setTokens({
|
|
1316
|
+
access_token: result.access_token,
|
|
1317
|
+
refresh_token: result.refresh_token,
|
|
1318
|
+
token_type: result.token_type,
|
|
1319
|
+
expires_in: result.expires_in,
|
|
1320
|
+
refresh_expires_in: result.refresh_expires_in
|
|
1321
|
+
}, true);
|
|
1322
|
+
return result.user;
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
if (error instanceof BlinkAuthError) {
|
|
1325
|
+
throw error;
|
|
1326
|
+
}
|
|
1327
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Sign in with Google (headless mode)
|
|
1332
|
+
*/
|
|
1333
|
+
async signInWithGoogle(options) {
|
|
1334
|
+
if (this.authConfig.mode !== "headless") {
|
|
1335
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithGoogle is only available in headless mode");
|
|
1336
|
+
}
|
|
1337
|
+
return this.signInWithProvider("google", options);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Sign in with GitHub (headless mode)
|
|
1341
|
+
*/
|
|
1342
|
+
async signInWithGitHub(options) {
|
|
1343
|
+
if (this.authConfig.mode !== "headless") {
|
|
1344
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithGitHub is only available in headless mode");
|
|
1345
|
+
}
|
|
1346
|
+
return this.signInWithProvider("github", options);
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Sign in with Apple (headless mode)
|
|
1350
|
+
*/
|
|
1351
|
+
async signInWithApple(options) {
|
|
1352
|
+
if (this.authConfig.mode !== "headless") {
|
|
1353
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithApple is only available in headless mode");
|
|
1354
|
+
}
|
|
1355
|
+
return this.signInWithProvider("apple", options);
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Sign in with Microsoft (headless mode)
|
|
1359
|
+
*/
|
|
1360
|
+
async signInWithMicrosoft(options) {
|
|
1361
|
+
if (this.authConfig.mode !== "headless") {
|
|
1362
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithMicrosoft is only available in headless mode");
|
|
1363
|
+
}
|
|
1364
|
+
return this.signInWithProvider("microsoft", options);
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Generic provider sign-in method (headless mode)
|
|
1368
|
+
*/
|
|
1369
|
+
async signInWithProvider(provider, options) {
|
|
1370
|
+
if (this.authConfig.mode !== "headless") {
|
|
1371
|
+
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
|
|
1372
|
+
}
|
|
1373
|
+
return new Promise((resolve, reject) => {
|
|
1374
|
+
const state = this.generateState();
|
|
1375
|
+
try {
|
|
1376
|
+
if (typeof window !== "undefined") {
|
|
1377
|
+
sessionStorage.setItem("blink_oauth_state", state);
|
|
1378
|
+
}
|
|
1379
|
+
} catch {
|
|
1380
|
+
}
|
|
1381
|
+
const redirectUrl = options?.redirectUrl || window.location.origin;
|
|
1382
|
+
const popupUrl = new URL("/auth", this.authUrl);
|
|
1383
|
+
popupUrl.searchParams.set("provider", provider);
|
|
1384
|
+
popupUrl.searchParams.set("project_id", this.config.projectId);
|
|
1385
|
+
popupUrl.searchParams.set("state", state);
|
|
1386
|
+
popupUrl.searchParams.set("mode", "popup");
|
|
1387
|
+
popupUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1388
|
+
const popup = window.open(
|
|
1389
|
+
popupUrl.toString(),
|
|
1390
|
+
"blink-auth",
|
|
1391
|
+
"width=500,height=600,scrollbars=yes,resizable=yes"
|
|
1392
|
+
);
|
|
1393
|
+
if (!popup) {
|
|
1394
|
+
reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
let timeoutId;
|
|
1398
|
+
const messageListener = (event) => {
|
|
1399
|
+
let allowed = false;
|
|
1400
|
+
try {
|
|
1401
|
+
const authOrigin = new URL(this.authUrl).origin;
|
|
1402
|
+
if (event.origin === authOrigin) allowed = true;
|
|
1403
|
+
} catch {
|
|
1404
|
+
}
|
|
1405
|
+
if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
|
|
1406
|
+
if (!allowed) return;
|
|
1407
|
+
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
1408
|
+
const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
|
|
1409
|
+
try {
|
|
1410
|
+
const expected = sessionStorage.getItem("blink_oauth_state");
|
|
1411
|
+
if (returnedState && expected && returnedState !== expected) {
|
|
1412
|
+
reject(new BlinkAuthError("VERIFICATION_FAILED" /* VERIFICATION_FAILED */, "State mismatch"));
|
|
1413
|
+
clearTimeout(timeoutId);
|
|
1414
|
+
window.removeEventListener("message", messageListener);
|
|
1415
|
+
popup.close();
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
} catch {
|
|
1419
|
+
}
|
|
1420
|
+
if (projectId !== this.config.projectId) {
|
|
1421
|
+
reject(new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "Project ID mismatch"));
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
this.setTokens({
|
|
1425
|
+
access_token,
|
|
1426
|
+
refresh_token,
|
|
1427
|
+
token_type,
|
|
1428
|
+
expires_in,
|
|
1429
|
+
refresh_expires_in
|
|
1430
|
+
}, true).then(() => {
|
|
1431
|
+
resolve(this.authState.user);
|
|
1432
|
+
}).catch(reject);
|
|
1433
|
+
clearTimeout(timeoutId);
|
|
1434
|
+
window.removeEventListener("message", messageListener);
|
|
1435
|
+
popup.close();
|
|
1436
|
+
} else if (event.data?.type === "BLINK_AUTH_ERROR") {
|
|
1437
|
+
const errorCode = this.mapErrorCodeFromResponse(event.data.code);
|
|
1438
|
+
reject(new BlinkAuthError(errorCode, event.data.message || "Authentication failed"));
|
|
1439
|
+
clearTimeout(timeoutId);
|
|
1440
|
+
window.removeEventListener("message", messageListener);
|
|
1441
|
+
popup.close();
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
timeoutId = setTimeout(() => {
|
|
1445
|
+
window.removeEventListener("message", messageListener);
|
|
1446
|
+
if (!popup.closed) {
|
|
1447
|
+
popup.close();
|
|
1448
|
+
}
|
|
1449
|
+
reject(new BlinkAuthError("AUTH_TIMEOUT" /* AUTH_TIMEOUT */, "Authentication timed out"));
|
|
1450
|
+
}, 3e4);
|
|
1451
|
+
const checkClosed = setInterval(() => {
|
|
1452
|
+
if (popup.closed) {
|
|
1453
|
+
clearInterval(checkClosed);
|
|
1454
|
+
clearTimeout(timeoutId);
|
|
1455
|
+
window.removeEventListener("message", messageListener);
|
|
1456
|
+
reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Authentication was canceled"));
|
|
1457
|
+
}
|
|
1458
|
+
}, 1e3);
|
|
1459
|
+
window.addEventListener("message", messageListener);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Generate password reset token (for custom email delivery)
|
|
1464
|
+
*/
|
|
1465
|
+
async generatePasswordResetToken(email) {
|
|
1466
|
+
try {
|
|
1467
|
+
const response = await fetch(`${this.coreUrl}/api/auth/password/reset/generate`, {
|
|
1468
|
+
method: "POST",
|
|
1469
|
+
headers: {
|
|
1470
|
+
"Content-Type": "application/json"
|
|
1471
|
+
},
|
|
1472
|
+
body: JSON.stringify({
|
|
1473
|
+
email,
|
|
1474
|
+
projectId: this.config.projectId
|
|
1475
|
+
})
|
|
1476
|
+
});
|
|
1477
|
+
if (!response.ok) {
|
|
1478
|
+
const errorData = await response.json();
|
|
1479
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1480
|
+
throw new BlinkAuthError(
|
|
1481
|
+
errorCode,
|
|
1482
|
+
errorData.error || "Failed to generate password reset token",
|
|
1483
|
+
errorData.error
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
const data = await response.json();
|
|
1487
|
+
return data;
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
if (error instanceof BlinkAuthError) {
|
|
1490
|
+
throw error;
|
|
1491
|
+
}
|
|
1492
|
+
throw new BlinkAuthError(
|
|
1493
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1494
|
+
"Failed to generate password reset token",
|
|
1495
|
+
"Network error occurred"
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Send password reset email (using Blink default email service)
|
|
1501
|
+
*/
|
|
1502
|
+
async sendPasswordResetEmail(email) {
|
|
1503
|
+
try {
|
|
1504
|
+
const response = await fetch(`${this.coreUrl}/api/auth/password/reset`, {
|
|
1505
|
+
method: "POST",
|
|
1506
|
+
headers: {
|
|
1507
|
+
"Content-Type": "application/json"
|
|
1508
|
+
},
|
|
1509
|
+
body: JSON.stringify({
|
|
1510
|
+
email,
|
|
1511
|
+
projectId: this.config.projectId
|
|
1512
|
+
})
|
|
1513
|
+
});
|
|
1514
|
+
if (!response.ok) {
|
|
1515
|
+
const errorData = await response.json();
|
|
1516
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1517
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Failed to send password reset email");
|
|
1518
|
+
}
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
if (error instanceof BlinkAuthError) {
|
|
1521
|
+
throw error;
|
|
1522
|
+
}
|
|
1523
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Confirm password reset with token
|
|
1528
|
+
*/
|
|
1529
|
+
async confirmPasswordReset(token, newPassword) {
|
|
1530
|
+
try {
|
|
1531
|
+
const response = await fetch(`${this.coreUrl}/api/auth/password/reset/confirm`, {
|
|
1532
|
+
method: "POST",
|
|
1533
|
+
headers: {
|
|
1534
|
+
"Content-Type": "application/json"
|
|
1535
|
+
},
|
|
1536
|
+
body: JSON.stringify({
|
|
1537
|
+
token,
|
|
1538
|
+
password: newPassword,
|
|
1539
|
+
projectId: this.config.projectId
|
|
1540
|
+
})
|
|
1541
|
+
});
|
|
1542
|
+
if (!response.ok) {
|
|
1543
|
+
const errorData = await response.json();
|
|
1544
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1545
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Failed to reset password");
|
|
1546
|
+
}
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
if (error instanceof BlinkAuthError) {
|
|
1549
|
+
throw error;
|
|
1550
|
+
}
|
|
1551
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Change password (requires current authentication)
|
|
1556
|
+
*/
|
|
1557
|
+
async changePassword(oldPassword, newPassword) {
|
|
1558
|
+
const token = await this.getValidToken();
|
|
1559
|
+
if (!token) {
|
|
1560
|
+
throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
|
|
1561
|
+
}
|
|
1562
|
+
try {
|
|
1563
|
+
const response = await fetch(`${this.coreUrl}/api/auth/password/change`, {
|
|
1564
|
+
method: "POST",
|
|
1565
|
+
headers: {
|
|
1566
|
+
"Authorization": `Bearer ${token}`,
|
|
1567
|
+
"Content-Type": "application/json"
|
|
1568
|
+
},
|
|
1569
|
+
body: JSON.stringify({
|
|
1570
|
+
oldPassword,
|
|
1571
|
+
newPassword
|
|
1572
|
+
})
|
|
1573
|
+
});
|
|
1574
|
+
if (!response.ok) {
|
|
1575
|
+
const errorData = await response.json();
|
|
1576
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1577
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Failed to change password");
|
|
1578
|
+
}
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
if (error instanceof BlinkAuthError) {
|
|
1581
|
+
throw error;
|
|
1582
|
+
}
|
|
1583
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Generate email verification token (for custom email delivery)
|
|
1588
|
+
*/
|
|
1589
|
+
async generateEmailVerificationToken() {
|
|
1590
|
+
const token = await this.getValidToken();
|
|
1591
|
+
if (!token) {
|
|
1592
|
+
throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
|
|
1593
|
+
}
|
|
1594
|
+
try {
|
|
1595
|
+
const response = await fetch(`${this.coreUrl}/api/auth/email/verify/generate`, {
|
|
1596
|
+
method: "POST",
|
|
1597
|
+
headers: {
|
|
1598
|
+
"Authorization": `Bearer ${token}`,
|
|
1599
|
+
"Content-Type": "application/json"
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
if (!response.ok) {
|
|
1603
|
+
const errorData = await response.json();
|
|
1604
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1605
|
+
throw new BlinkAuthError(
|
|
1606
|
+
errorCode,
|
|
1607
|
+
errorData.error || "Failed to generate email verification token",
|
|
1608
|
+
errorData.error
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
const data = await response.json();
|
|
1612
|
+
return data;
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
if (error instanceof BlinkAuthError) {
|
|
1615
|
+
throw error;
|
|
1616
|
+
}
|
|
1617
|
+
throw new BlinkAuthError(
|
|
1618
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1619
|
+
"Failed to generate email verification token",
|
|
1620
|
+
"Network error occurred"
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Send email verification (using Blink default email service)
|
|
1626
|
+
*/
|
|
1627
|
+
async sendEmailVerification() {
|
|
1628
|
+
const token = await this.getValidToken();
|
|
1629
|
+
if (!token) {
|
|
1630
|
+
throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
const response = await fetch(`${this.coreUrl}/api/auth/email/verify/send`, {
|
|
1634
|
+
method: "POST",
|
|
1635
|
+
headers: {
|
|
1636
|
+
"Authorization": `Bearer ${token}`,
|
|
1637
|
+
"Content-Type": "application/json"
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
if (!response.ok) {
|
|
1641
|
+
const errorData = await response.json();
|
|
1642
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1643
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Failed to send verification email");
|
|
1644
|
+
}
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
if (error instanceof BlinkAuthError) {
|
|
1647
|
+
throw error;
|
|
1648
|
+
}
|
|
1649
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Verify email with token
|
|
1654
|
+
*/
|
|
1655
|
+
async verifyEmail(token) {
|
|
1656
|
+
try {
|
|
1657
|
+
const response = await fetch(`${this.coreUrl}/api/auth/email/verify`, {
|
|
1658
|
+
method: "POST",
|
|
1659
|
+
headers: {
|
|
1660
|
+
"Content-Type": "application/json"
|
|
1661
|
+
},
|
|
1662
|
+
body: JSON.stringify({
|
|
1663
|
+
token,
|
|
1664
|
+
projectId: this.config.projectId
|
|
1665
|
+
})
|
|
1666
|
+
});
|
|
1667
|
+
if (!response.ok) {
|
|
1668
|
+
const errorData = await response.json();
|
|
1669
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1670
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Failed to verify email");
|
|
1671
|
+
}
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
if (error instanceof BlinkAuthError) {
|
|
1674
|
+
throw error;
|
|
1675
|
+
}
|
|
1676
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Generate magic link token (for custom email delivery)
|
|
1681
|
+
*/
|
|
1682
|
+
async generateMagicLinkToken(email, options) {
|
|
1683
|
+
try {
|
|
1684
|
+
const response = await fetch(`${this.coreUrl}/api/auth/signin/magic/generate`, {
|
|
1685
|
+
method: "POST",
|
|
1686
|
+
headers: {
|
|
1687
|
+
"Content-Type": "application/json"
|
|
1688
|
+
},
|
|
1689
|
+
body: JSON.stringify({
|
|
1690
|
+
email,
|
|
1691
|
+
redirectUrl: options?.redirectUrl,
|
|
1692
|
+
projectId: this.config.projectId
|
|
1693
|
+
})
|
|
1694
|
+
});
|
|
1695
|
+
if (!response.ok) {
|
|
1696
|
+
const errorData = await response.json();
|
|
1697
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1698
|
+
throw new BlinkAuthError(
|
|
1699
|
+
errorCode,
|
|
1700
|
+
errorData.error || "Failed to generate magic link token",
|
|
1701
|
+
errorData.error
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
const data = await response.json();
|
|
1705
|
+
return data;
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
if (error instanceof BlinkAuthError) {
|
|
1708
|
+
throw error;
|
|
1709
|
+
}
|
|
1710
|
+
throw new BlinkAuthError(
|
|
1711
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1712
|
+
"Failed to generate magic link token",
|
|
1713
|
+
"Network error occurred"
|
|
1714
|
+
);
|
|
1182
1715
|
}
|
|
1183
1716
|
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Send magic link (using Blink default email service)
|
|
1719
|
+
*/
|
|
1720
|
+
async sendMagicLink(email, options) {
|
|
1721
|
+
try {
|
|
1722
|
+
const response = await fetch(`${this.coreUrl}/api/auth/signin/magic`, {
|
|
1723
|
+
method: "POST",
|
|
1724
|
+
headers: {
|
|
1725
|
+
"Content-Type": "application/json"
|
|
1726
|
+
},
|
|
1727
|
+
body: JSON.stringify({
|
|
1728
|
+
email,
|
|
1729
|
+
redirectUrl: options?.redirectUrl,
|
|
1730
|
+
projectId: this.config.projectId
|
|
1731
|
+
})
|
|
1732
|
+
});
|
|
1733
|
+
if (!response.ok) {
|
|
1734
|
+
const errorData = await response.json();
|
|
1735
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1736
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Failed to send magic link");
|
|
1737
|
+
}
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
if (error instanceof BlinkAuthError) {
|
|
1740
|
+
throw error;
|
|
1741
|
+
}
|
|
1742
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Verify magic link (automatic on redirect)
|
|
1747
|
+
*/
|
|
1748
|
+
async verifyMagicLink(token) {
|
|
1749
|
+
const magicToken = token || this.extractMagicTokenFromUrl();
|
|
1750
|
+
if (!magicToken) {
|
|
1751
|
+
throw new BlinkAuthError("VERIFICATION_FAILED" /* VERIFICATION_FAILED */, "No magic link token found");
|
|
1752
|
+
}
|
|
1753
|
+
try {
|
|
1754
|
+
const response = await fetch(`${this.coreUrl}/api/auth/signin/magic/verify`, {
|
|
1755
|
+
method: "POST",
|
|
1756
|
+
headers: {
|
|
1757
|
+
"Content-Type": "application/json"
|
|
1758
|
+
},
|
|
1759
|
+
body: JSON.stringify({
|
|
1760
|
+
token: magicToken,
|
|
1761
|
+
projectId: this.config.projectId
|
|
1762
|
+
})
|
|
1763
|
+
});
|
|
1764
|
+
if (!response.ok) {
|
|
1765
|
+
const errorData = await response.json();
|
|
1766
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1767
|
+
throw new BlinkAuthError(errorCode, errorData.error || "Magic link verification failed");
|
|
1768
|
+
}
|
|
1769
|
+
const result = await response.json();
|
|
1770
|
+
await this.setTokens({
|
|
1771
|
+
access_token: result.access_token,
|
|
1772
|
+
refresh_token: result.refresh_token,
|
|
1773
|
+
token_type: result.token_type,
|
|
1774
|
+
expires_in: result.expires_in,
|
|
1775
|
+
refresh_expires_in: result.refresh_expires_in
|
|
1776
|
+
}, true);
|
|
1777
|
+
return result.user;
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
if (error instanceof BlinkAuthError) {
|
|
1780
|
+
throw error;
|
|
1781
|
+
}
|
|
1782
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Get available providers for the current project
|
|
1787
|
+
*/
|
|
1788
|
+
async getAvailableProviders() {
|
|
1789
|
+
try {
|
|
1790
|
+
const response = await fetch(`${this.coreUrl}/api/auth/providers?projectId=${encodeURIComponent(this.config.projectId)}`);
|
|
1791
|
+
if (!response.ok) {
|
|
1792
|
+
return ["email", "google"];
|
|
1793
|
+
}
|
|
1794
|
+
const data = await response.json();
|
|
1795
|
+
return data.providers || ["email", "google"];
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
return ["email", "google"];
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Check if user has a specific role
|
|
1802
|
+
*/
|
|
1803
|
+
hasRole(role) {
|
|
1804
|
+
const user = this.authState.user;
|
|
1805
|
+
if (!user || !user.role) {
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
if (Array.isArray(role)) {
|
|
1809
|
+
return role.includes(user.role);
|
|
1810
|
+
}
|
|
1811
|
+
return user.role === role;
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Check if user can perform a specific action
|
|
1815
|
+
*/
|
|
1816
|
+
can(permission, resource) {
|
|
1817
|
+
const user = this.authState.user;
|
|
1818
|
+
if (!user || !user.role) {
|
|
1819
|
+
return false;
|
|
1820
|
+
}
|
|
1821
|
+
const roles = this.authConfig.roles;
|
|
1822
|
+
if (!roles) {
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
const roleConfig = roles[user.role];
|
|
1826
|
+
if (!roleConfig) {
|
|
1827
|
+
return false;
|
|
1828
|
+
}
|
|
1829
|
+
if (roleConfig.permissions.includes("*")) {
|
|
1830
|
+
return true;
|
|
1831
|
+
}
|
|
1832
|
+
const fullPermission = resource ? `${permission}.${resource}` : permission;
|
|
1833
|
+
if (roleConfig.permissions.includes(fullPermission)) {
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
if (roleConfig.permissions.includes(permission)) {
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1840
|
+
const hasPermissionInRole = (roleName) => {
|
|
1841
|
+
if (visited.has(roleName)) return false;
|
|
1842
|
+
visited.add(roleName);
|
|
1843
|
+
const rc = roles[roleName];
|
|
1844
|
+
if (!rc) return false;
|
|
1845
|
+
if (rc.permissions.includes("*")) return true;
|
|
1846
|
+
const fullPermission2 = resource ? `${permission}.${resource}` : permission;
|
|
1847
|
+
if (rc.permissions.includes(fullPermission2) || rc.permissions.includes(permission)) return true;
|
|
1848
|
+
if (rc.inherit) {
|
|
1849
|
+
for (const parent of rc.inherit) {
|
|
1850
|
+
if (hasPermissionInRole(parent)) return true;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return false;
|
|
1854
|
+
};
|
|
1855
|
+
if (hasPermissionInRole(user.role)) return true;
|
|
1856
|
+
return false;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Sign out (clear local tokens)
|
|
1860
|
+
* Note: With stateless tokens, this only clears local storage
|
|
1861
|
+
*/
|
|
1862
|
+
async signOut() {
|
|
1863
|
+
this.clearTokens();
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* @deprecated Use signOut() instead. Kept for backward compatibility.
|
|
1867
|
+
*/
|
|
1868
|
+
async revokeAllSessions() {
|
|
1869
|
+
return this.signOut();
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Recover auth state (clear corrupted tokens and re-initialize)
|
|
1873
|
+
*/
|
|
1874
|
+
async recoverAuthState() {
|
|
1875
|
+
console.log("\u{1F504} Recovering auth state...");
|
|
1876
|
+
this.clearTokens();
|
|
1877
|
+
this.isInitialized = false;
|
|
1878
|
+
this.initializationPromise = null;
|
|
1879
|
+
if (typeof window !== "undefined") {
|
|
1880
|
+
this.initializationPromise = this.initialize();
|
|
1881
|
+
await this.initializationPromise;
|
|
1882
|
+
}
|
|
1883
|
+
console.log("\u2705 Auth state recovery complete");
|
|
1884
|
+
}
|
|
1184
1885
|
/**
|
|
1185
1886
|
* Update user profile
|
|
1186
1887
|
*/
|
|
1187
1888
|
async updateMe(updates) {
|
|
1188
1889
|
const token = this.getToken();
|
|
1189
1890
|
if (!token) {
|
|
1190
|
-
throw new BlinkAuthError("No access token available");
|
|
1891
|
+
throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
|
|
1191
1892
|
}
|
|
1192
1893
|
try {
|
|
1193
1894
|
const response = await fetch(`${this.authUrl}/api/auth/me`, {
|
|
@@ -1199,7 +1900,9 @@ var BlinkAuth = class {
|
|
|
1199
1900
|
body: JSON.stringify(updates)
|
|
1200
1901
|
});
|
|
1201
1902
|
if (!response.ok) {
|
|
1202
|
-
|
|
1903
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1904
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1905
|
+
throw new BlinkAuthError(errorCode, errorData.error || `Failed to update user: ${response.statusText}`);
|
|
1203
1906
|
}
|
|
1204
1907
|
const data = await response.json();
|
|
1205
1908
|
const user = data.user;
|
|
@@ -1212,7 +1915,7 @@ var BlinkAuth = class {
|
|
|
1212
1915
|
if (error instanceof BlinkAuthError) {
|
|
1213
1916
|
throw error;
|
|
1214
1917
|
}
|
|
1215
|
-
throw new BlinkAuthError(`Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1918
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, `Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1216
1919
|
}
|
|
1217
1920
|
}
|
|
1218
1921
|
/**
|
|
@@ -1526,6 +2229,93 @@ var BlinkAuth = class {
|
|
|
1526
2229
|
}
|
|
1527
2230
|
});
|
|
1528
2231
|
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Generate secure random state for OAuth flows
|
|
2234
|
+
*/
|
|
2235
|
+
generateState() {
|
|
2236
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2237
|
+
const array = new Uint8Array(16);
|
|
2238
|
+
crypto.getRandomValues(array);
|
|
2239
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2240
|
+
} else {
|
|
2241
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Extract magic link token from URL
|
|
2246
|
+
*/
|
|
2247
|
+
extractMagicTokenFromUrl() {
|
|
2248
|
+
if (typeof window === "undefined") return null;
|
|
2249
|
+
const params = new URLSearchParams(window.location.search);
|
|
2250
|
+
return params.get("magic_token") || params.get("token");
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Map server error codes to BlinkAuthErrorCode
|
|
2254
|
+
*/
|
|
2255
|
+
mapErrorCodeFromResponse(serverCode) {
|
|
2256
|
+
switch (serverCode) {
|
|
2257
|
+
case "INVALID_CREDENTIALS":
|
|
2258
|
+
case "auth/invalid-credential":
|
|
2259
|
+
case "auth/wrong-password":
|
|
2260
|
+
case "auth/user-not-found":
|
|
2261
|
+
return "INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */;
|
|
2262
|
+
case "EMAIL_NOT_VERIFIED":
|
|
2263
|
+
case "auth/email-not-verified":
|
|
2264
|
+
return "EMAIL_NOT_VERIFIED" /* EMAIL_NOT_VERIFIED */;
|
|
2265
|
+
case "EMAIL_ALREADY_VERIFIED":
|
|
2266
|
+
return "VERIFICATION_FAILED" /* VERIFICATION_FAILED */;
|
|
2267
|
+
case "POPUP_CANCELED":
|
|
2268
|
+
case "auth/popup-closed-by-user":
|
|
2269
|
+
return "POPUP_CANCELED" /* POPUP_CANCELED */;
|
|
2270
|
+
case "NETWORK_ERROR":
|
|
2271
|
+
return "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
2272
|
+
case "RATE_LIMITED":
|
|
2273
|
+
case "auth/too-many-requests":
|
|
2274
|
+
return "RATE_LIMITED" /* RATE_LIMITED */;
|
|
2275
|
+
case "AUTH_TIMEOUT":
|
|
2276
|
+
return "AUTH_TIMEOUT" /* AUTH_TIMEOUT */;
|
|
2277
|
+
case "REDIRECT_FAILED":
|
|
2278
|
+
return "REDIRECT_FAILED" /* REDIRECT_FAILED */;
|
|
2279
|
+
case "TOKEN_EXPIRED":
|
|
2280
|
+
case "auth/id-token-expired":
|
|
2281
|
+
return "TOKEN_EXPIRED" /* TOKEN_EXPIRED */;
|
|
2282
|
+
case "USER_NOT_FOUND":
|
|
2283
|
+
return "USER_NOT_FOUND" /* USER_NOT_FOUND */;
|
|
2284
|
+
case "EMAIL_ALREADY_EXISTS":
|
|
2285
|
+
case "auth/email-already-in-use":
|
|
2286
|
+
return "EMAIL_ALREADY_EXISTS" /* EMAIL_ALREADY_EXISTS */;
|
|
2287
|
+
case "WEAK_PASSWORD":
|
|
2288
|
+
case "auth/weak-password":
|
|
2289
|
+
return "WEAK_PASSWORD" /* WEAK_PASSWORD */;
|
|
2290
|
+
case "INVALID_EMAIL":
|
|
2291
|
+
case "auth/invalid-email":
|
|
2292
|
+
return "INVALID_EMAIL" /* INVALID_EMAIL */;
|
|
2293
|
+
case "MAGIC_LINK_EXPIRED":
|
|
2294
|
+
return "MAGIC_LINK_EXPIRED" /* MAGIC_LINK_EXPIRED */;
|
|
2295
|
+
case "VERIFICATION_FAILED":
|
|
2296
|
+
return "VERIFICATION_FAILED" /* VERIFICATION_FAILED */;
|
|
2297
|
+
default:
|
|
2298
|
+
return "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Setup cross-tab authentication synchronization
|
|
2303
|
+
*/
|
|
2304
|
+
setupCrossTabSync() {
|
|
2305
|
+
if (typeof window === "undefined") return;
|
|
2306
|
+
window.addEventListener("storage", (e) => {
|
|
2307
|
+
if (e.key === "blink_tokens") {
|
|
2308
|
+
const newTokens = e.newValue ? JSON.parse(e.newValue) : null;
|
|
2309
|
+
if (newTokens && newTokens !== this.authState.tokens) {
|
|
2310
|
+
this.setTokens(newTokens, false).catch((error) => {
|
|
2311
|
+
console.error("Failed to sync tokens from other tab:", error);
|
|
2312
|
+
});
|
|
2313
|
+
} else if (!newTokens && this.authState.tokens) {
|
|
2314
|
+
this.clearTokens();
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
1529
2319
|
};
|
|
1530
2320
|
|
|
1531
2321
|
// src/database.ts
|
|
@@ -2558,47 +3348,39 @@ var BlinkAIImpl = class {
|
|
|
2558
3348
|
}
|
|
2559
3349
|
}
|
|
2560
3350
|
/**
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
* data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url));
|
|
2595
|
-
* ```
|
|
2596
|
-
*
|
|
2597
|
-
* @returns Promise<ImageGenerationResponse> - Object containing:
|
|
2598
|
-
* - `data`: Array of generated images with URLs
|
|
2599
|
-
* - `created`: Timestamp of generation
|
|
2600
|
-
* - `usage`: Token usage information
|
|
2601
|
-
*/
|
|
3351
|
+
* Generates images from text descriptions using Gemini 2.5 Flash Image.
|
|
3352
|
+
*
|
|
3353
|
+
* @param options - Object containing:
|
|
3354
|
+
* - `prompt`: Text description of the desired image (required, up to 100k characters)
|
|
3355
|
+
* - `n`: Number of images to generate (default: 1)
|
|
3356
|
+
* - Plus optional signal parameter
|
|
3357
|
+
*
|
|
3358
|
+
* @example
|
|
3359
|
+
* ```ts
|
|
3360
|
+
* // Basic image generation
|
|
3361
|
+
* const { data } = await blink.ai.generateImage({
|
|
3362
|
+
* prompt: "A serene landscape with mountains and a lake at sunset"
|
|
3363
|
+
* });
|
|
3364
|
+
* console.log("Image URL:", data[0].url);
|
|
3365
|
+
*
|
|
3366
|
+
* // Multiple images
|
|
3367
|
+
* const { data } = await blink.ai.generateImage({
|
|
3368
|
+
* prompt: "A futuristic city skyline with flying cars",
|
|
3369
|
+
* n: 3
|
|
3370
|
+
* });
|
|
3371
|
+
* data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url));
|
|
3372
|
+
*
|
|
3373
|
+
* // Detailed prompt for better results
|
|
3374
|
+
* const { data } = await blink.ai.generateImage({
|
|
3375
|
+
* prompt: "A cute robot mascot for a tech company, digital art style, vibrant colors, modern design, friendly expression"
|
|
3376
|
+
* });
|
|
3377
|
+
* ```
|
|
3378
|
+
*
|
|
3379
|
+
* @returns Promise<ImageGenerationResponse> - Object containing:
|
|
3380
|
+
* - `data`: Array of generated images with URLs
|
|
3381
|
+
* - `created`: Timestamp of generation
|
|
3382
|
+
* - `model`: Always "gemini-2.5-flash-image-preview"
|
|
3383
|
+
*/
|
|
2602
3384
|
async generateImage(options) {
|
|
2603
3385
|
try {
|
|
2604
3386
|
if (!options.prompt) {
|
|
@@ -2607,12 +3389,7 @@ var BlinkAIImpl = class {
|
|
|
2607
3389
|
const response = await this.httpClient.aiImage(
|
|
2608
3390
|
options.prompt,
|
|
2609
3391
|
{
|
|
2610
|
-
model: "gpt-image-1",
|
|
2611
|
-
size: options.size,
|
|
2612
|
-
quality: options.quality,
|
|
2613
3392
|
n: options.n,
|
|
2614
|
-
background: options.background,
|
|
2615
|
-
response_format: "url",
|
|
2616
3393
|
signal: options.signal
|
|
2617
3394
|
}
|
|
2618
3395
|
);
|
|
@@ -2649,65 +3426,70 @@ var BlinkAIImpl = class {
|
|
|
2649
3426
|
}
|
|
2650
3427
|
}
|
|
2651
3428
|
/**
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
3429
|
+
* Modifies existing images using Gemini 2.5 Flash Image with text prompts for image-to-image editing.
|
|
3430
|
+
*
|
|
3431
|
+
* @param options - Object containing:
|
|
3432
|
+
* - `images`: Array of public image URLs to modify (required, up to 50 images)
|
|
3433
|
+
* - `prompt`: Text description of desired modifications (required, up to 100k characters)
|
|
3434
|
+
* - `n`: Number of output images to generate (default: 1)
|
|
3435
|
+
* - Plus optional signal parameter
|
|
3436
|
+
*
|
|
3437
|
+
* @example
|
|
3438
|
+
* ```ts
|
|
3439
|
+
* // Professional headshots from casual photos
|
|
3440
|
+
* const { data } = await blink.ai.modifyImage({
|
|
3441
|
+
* images: [
|
|
3442
|
+
* "https://storage.example.com/user-photo-1.jpg",
|
|
3443
|
+
* "https://storage.example.com/user-photo-2.jpg"
|
|
3444
|
+
* ],
|
|
3445
|
+
* prompt: "Transform into professional business headshots with studio lighting",
|
|
3446
|
+
* n: 4
|
|
3447
|
+
* });
|
|
3448
|
+
* data.forEach((img, i) => console.log(`Headshot ${i+1}:`, img.url));
|
|
3449
|
+
*
|
|
3450
|
+
* // Artistic style transformation
|
|
3451
|
+
* const { data } = await blink.ai.modifyImage({
|
|
3452
|
+
* images: ["https://storage.example.com/portrait.jpg"],
|
|
3453
|
+
* prompt: "Transform into oil painting style with dramatic lighting"
|
|
3454
|
+
* });
|
|
3455
|
+
*
|
|
3456
|
+
* // Background replacement
|
|
3457
|
+
* const { data } = await blink.ai.modifyImage({
|
|
3458
|
+
* images: ["https://storage.example.com/product.jpg"],
|
|
3459
|
+
* prompt: "Remove background and place on clean white studio background",
|
|
3460
|
+
* n: 2
|
|
3461
|
+
* });
|
|
3462
|
+
*
|
|
3463
|
+
* // Batch processing multiple photos
|
|
3464
|
+
* const userPhotos = [
|
|
3465
|
+
* "https://storage.example.com/photo1.jpg",
|
|
3466
|
+
* "https://storage.example.com/photo2.jpg",
|
|
3467
|
+
* "https://storage.example.com/photo3.jpg"
|
|
3468
|
+
* ];
|
|
3469
|
+
* const { data } = await blink.ai.modifyImage({
|
|
3470
|
+
* images: userPhotos,
|
|
3471
|
+
* prompt: "Convert to black and white vintage style photographs"
|
|
3472
|
+
* });
|
|
3473
|
+
*
|
|
3474
|
+
* // 🎨 Style Transfer - IMPORTANT: Provide all images in array
|
|
3475
|
+
* // ❌ WRONG - Don't reference other images in prompt
|
|
3476
|
+
* const wrong = await blink.ai.modifyImage({
|
|
3477
|
+
* images: [userPhotoUrl],
|
|
3478
|
+
* prompt: `Apply hairstyle from ${referenceUrl}`
|
|
3479
|
+
* });
|
|
3480
|
+
*
|
|
3481
|
+
* // ✅ CORRECT - Provide all images in array
|
|
3482
|
+
* const { data } = await blink.ai.modifyImage({
|
|
3483
|
+
* images: [userPhotoUrl, hairstyleReferenceUrl],
|
|
3484
|
+
* prompt: "Apply the hairstyle from the second image to the person in the first image"
|
|
3485
|
+
* });
|
|
3486
|
+
* ```
|
|
3487
|
+
*
|
|
3488
|
+
* @returns Promise<ImageGenerationResponse> - Object containing:
|
|
3489
|
+
* - `data`: Array of modified images with URLs
|
|
3490
|
+
* - `created`: Timestamp of generation
|
|
3491
|
+
* - `model`: Always "gemini-2.5-flash-image-preview"
|
|
3492
|
+
*/
|
|
2711
3493
|
async modifyImage(options) {
|
|
2712
3494
|
try {
|
|
2713
3495
|
if (!options.prompt) {
|
|
@@ -2716,8 +3498,8 @@ var BlinkAIImpl = class {
|
|
|
2716
3498
|
if (!options.images || !Array.isArray(options.images) || options.images.length === 0) {
|
|
2717
3499
|
throw new BlinkAIError("Images array is required and must contain at least one image URL");
|
|
2718
3500
|
}
|
|
2719
|
-
if (options.images.length >
|
|
2720
|
-
throw new BlinkAIError("Maximum
|
|
3501
|
+
if (options.images.length > 50) {
|
|
3502
|
+
throw new BlinkAIError("Maximum 50 images allowed");
|
|
2721
3503
|
}
|
|
2722
3504
|
for (let i = 0; i < options.images.length; i++) {
|
|
2723
3505
|
const validation = this.validateImageUrl(options.images[i]);
|
|
@@ -2729,13 +3511,8 @@ var BlinkAIImpl = class {
|
|
|
2729
3511
|
options.prompt,
|
|
2730
3512
|
// Non-null assertion since we validated above
|
|
2731
3513
|
{
|
|
2732
|
-
model: "gpt-image-1",
|
|
2733
3514
|
images: options.images,
|
|
2734
|
-
size: options.size,
|
|
2735
|
-
quality: options.quality,
|
|
2736
3515
|
n: options.n,
|
|
2737
|
-
background: options.background,
|
|
2738
|
-
response_format: "url",
|
|
2739
3516
|
signal: options.signal
|
|
2740
3517
|
}
|
|
2741
3518
|
);
|