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