@blinkdotnew/sdk 0.17.4 → 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.mjs CHANGED
@@ -16,9 +16,49 @@ var BlinkError = class extends Error {
16
16
  }
17
17
  };
18
18
  var BlinkAuthError = class extends BlinkError {
19
- constructor(message, details) {
20
- super(message, "AUTH_ERROR", 401, details);
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 = "https://blink.new";
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,7 @@ 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;
994
1052
  if (!redirectUrl && typeof window !== "undefined") {
995
1053
  if (window.location.href.startsWith("http")) {
996
1054
  redirectUrl = window.location.href;
@@ -1120,7 +1178,7 @@ var BlinkAuth = class {
1120
1178
  }
1121
1179
  const timeout = setTimeout(() => {
1122
1180
  unsubscribe();
1123
- reject(new BlinkAuthError("Authentication timeout - no user available"));
1181
+ reject(new BlinkAuthError("AUTH_TIMEOUT" /* AUTH_TIMEOUT */, "Authentication timeout - no user available"));
1124
1182
  }, 5e3);
1125
1183
  const unsubscribe = this.onAuthStateChanged((state) => {
1126
1184
  if (state.user) {
@@ -1130,14 +1188,14 @@ var BlinkAuth = class {
1130
1188
  } else if (!state.isLoading && !state.isAuthenticated) {
1131
1189
  clearTimeout(timeout);
1132
1190
  unsubscribe();
1133
- reject(new BlinkAuthError("Not authenticated"));
1191
+ reject(new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "Not authenticated"));
1134
1192
  }
1135
1193
  });
1136
1194
  });
1137
1195
  }
1138
1196
  let token = this.getToken();
1139
1197
  if (!token) {
1140
- throw new BlinkAuthError("No access token available");
1198
+ throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
1141
1199
  }
1142
1200
  try {
1143
1201
  const response = await fetch(`${this.authUrl}/api/auth/me`, {
@@ -1172,7 +1230,9 @@ var BlinkAuth = class {
1172
1230
  this.redirectToAuth();
1173
1231
  }
1174
1232
  }
1175
- throw new BlinkAuthError(`Failed to fetch user: ${response.statusText}`);
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}`);
1176
1236
  }
1177
1237
  const data = await response.json();
1178
1238
  const user = data.user;
@@ -1185,16 +1245,650 @@ var BlinkAuth = class {
1185
1245
  if (error instanceof BlinkAuthError) {
1186
1246
  throw error;
1187
1247
  }
1188
- 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
+ );
1189
1497
  }
1190
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
+ );
1715
+ }
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
+ }
1191
1885
  /**
1192
1886
  * Update user profile
1193
1887
  */
1194
1888
  async updateMe(updates) {
1195
1889
  const token = this.getToken();
1196
1890
  if (!token) {
1197
- throw new BlinkAuthError("No access token available");
1891
+ throw new BlinkAuthError("TOKEN_EXPIRED" /* TOKEN_EXPIRED */, "No access token available");
1198
1892
  }
1199
1893
  try {
1200
1894
  const response = await fetch(`${this.authUrl}/api/auth/me`, {
@@ -1206,7 +1900,9 @@ var BlinkAuth = class {
1206
1900
  body: JSON.stringify(updates)
1207
1901
  });
1208
1902
  if (!response.ok) {
1209
- throw new BlinkAuthError(`Failed to update user: ${response.statusText}`);
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}`);
1210
1906
  }
1211
1907
  const data = await response.json();
1212
1908
  const user = data.user;
@@ -1219,7 +1915,7 @@ var BlinkAuth = class {
1219
1915
  if (error instanceof BlinkAuthError) {
1220
1916
  throw error;
1221
1917
  }
1222
- 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"}`);
1223
1919
  }
1224
1920
  }
1225
1921
  /**
@@ -1533,6 +2229,93 @@ var BlinkAuth = class {
1533
2229
  }
1534
2230
  });
1535
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
+ }
1536
2319
  };
1537
2320
 
1538
2321
  // src/database.ts
@@ -2565,47 +3348,39 @@ var BlinkAIImpl = class {
2565
3348
  }
2566
3349
  }
2567
3350
  /**
2568
- * Generates images from text descriptions using AI.
2569
- *
2570
- * @param options - Object containing:
2571
- * - `prompt`: Text description of the desired image (required)
2572
- * - `size`: Image dimensions (default: "1024x1024")
2573
- * - `quality`: Image quality ("auto", "low", "medium", or "high", default: "auto")
2574
- * - `n`: Number of images to generate (default: 1)
2575
- * - `background`: Background handling ("auto", "transparent", "opaque", default: "auto")
2576
- * - Plus optional signal parameter
2577
- *
2578
- * @example
2579
- * ```ts
2580
- * // Basic image generation
2581
- * const { data } = await blink.ai.generateImage({
2582
- * prompt: "A serene landscape with mountains and a lake at sunset"
2583
- * });
2584
- * console.log("Image URL:", data[0].url);
2585
- *
2586
- * // High-quality image with specific size
2587
- * const { data } = await blink.ai.generateImage({
2588
- * prompt: "A futuristic city skyline with flying cars",
2589
- * size: "1536x1024",
2590
- * quality: "high",
2591
- * background: "transparent"
2592
- * });
2593
- *
2594
- * // Multiple images
2595
- * const { data } = await blink.ai.generateImage({
2596
- * prompt: "A cute robot mascot for a tech company",
2597
- * n: 3,
2598
- * size: "1024x1024",
2599
- * quality: "high"
2600
- * });
2601
- * data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url));
2602
- * ```
2603
- *
2604
- * @returns Promise<ImageGenerationResponse> - Object containing:
2605
- * - `data`: Array of generated images with URLs
2606
- * - `created`: Timestamp of generation
2607
- * - `usage`: Token usage information
2608
- */
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
+ */
2609
3384
  async generateImage(options) {
2610
3385
  try {
2611
3386
  if (!options.prompt) {
@@ -2614,12 +3389,7 @@ var BlinkAIImpl = class {
2614
3389
  const response = await this.httpClient.aiImage(
2615
3390
  options.prompt,
2616
3391
  {
2617
- model: "gpt-image-1",
2618
- size: options.size,
2619
- quality: options.quality,
2620
3392
  n: options.n,
2621
- background: options.background,
2622
- response_format: "url",
2623
3393
  signal: options.signal
2624
3394
  }
2625
3395
  );
@@ -2656,65 +3426,70 @@ var BlinkAIImpl = class {
2656
3426
  }
2657
3427
  }
2658
3428
  /**
2659
- * Modifies existing images using AI with text prompts for image-to-image editing.
2660
- *
2661
- * @param options - Object containing:
2662
- * - `images`: Array of public image URLs to modify (required, up to 16 images)
2663
- * - `prompt`: Text description of desired modifications (required)
2664
- * - `size`: Output image dimensions (default: "auto")
2665
- * - `quality`: Image quality ("auto", "low", "medium", or "high", default: "auto")
2666
- * - `n`: Number of output images to generate (default: 1)
2667
- * - `background`: Background handling ("auto", "transparent", "opaque", default: "auto")
2668
- * - Plus optional signal parameter
2669
- *
2670
- * @example
2671
- * ```ts
2672
- * // Professional headshots from casual photos
2673
- * const { data } = await blink.ai.modifyImage({
2674
- * images: [
2675
- * "https://storage.example.com/user-photo-1.jpg",
2676
- * "https://storage.example.com/user-photo-2.jpg"
2677
- * ],
2678
- * prompt: "Transform into professional business headshots with studio lighting",
2679
- * quality: "high",
2680
- * n: 4
2681
- * });
2682
- * data.forEach((img, i) => console.log(`Headshot ${i+1}:`, img.url));
2683
- *
2684
- * // Artistic style transformation
2685
- * const { data } = await blink.ai.modifyImage({
2686
- * images: ["https://storage.example.com/portrait.jpg"],
2687
- * prompt: "Transform into oil painting style with dramatic lighting",
2688
- * quality: "high",
2689
- * size: "1024x1024"
2690
- * });
2691
- *
2692
- * // Background replacement
2693
- * const { data } = await blink.ai.modifyImage({
2694
- * images: ["https://storage.example.com/product.jpg"],
2695
- * prompt: "Remove background and place on clean white studio background",
2696
- * background: "transparent",
2697
- * n: 2
2698
- * });
2699
- *
2700
- * // Batch processing multiple photos
2701
- * const userPhotos = [
2702
- * "https://storage.example.com/photo1.jpg",
2703
- * "https://storage.example.com/photo2.jpg",
2704
- * "https://storage.example.com/photo3.jpg"
2705
- * ];
2706
- * const { data } = await blink.ai.modifyImage({
2707
- * images: userPhotos,
2708
- * prompt: "Convert to black and white vintage style photographs",
2709
- * quality: "high"
2710
- * });
2711
- * ```
2712
- *
2713
- * @returns Promise<ImageGenerationResponse> - Object containing:
2714
- * - `data`: Array of modified images with URLs
2715
- * - `created`: Timestamp of generation
2716
- * - `usage`: Token usage information
2717
- */
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
+ */
2718
3493
  async modifyImage(options) {
2719
3494
  try {
2720
3495
  if (!options.prompt) {
@@ -2723,8 +3498,8 @@ var BlinkAIImpl = class {
2723
3498
  if (!options.images || !Array.isArray(options.images) || options.images.length === 0) {
2724
3499
  throw new BlinkAIError("Images array is required and must contain at least one image URL");
2725
3500
  }
2726
- if (options.images.length > 16) {
2727
- throw new BlinkAIError("Maximum 16 images allowed");
3501
+ if (options.images.length > 50) {
3502
+ throw new BlinkAIError("Maximum 50 images allowed");
2728
3503
  }
2729
3504
  for (let i = 0; i < options.images.length; i++) {
2730
3505
  const validation = this.validateImageUrl(options.images[i]);
@@ -2736,13 +3511,8 @@ var BlinkAIImpl = class {
2736
3511
  options.prompt,
2737
3512
  // Non-null assertion since we validated above
2738
3513
  {
2739
- model: "gpt-image-1",
2740
3514
  images: options.images,
2741
- size: options.size,
2742
- quality: options.quality,
2743
3515
  n: options.n,
2744
- background: options.background,
2745
- response_format: "url",
2746
3516
  signal: options.signal
2747
3517
  }
2748
3518
  );