@blinkdotnew/sdk 0.19.1 → 0.19.4

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.d.mts CHANGED
@@ -118,6 +118,11 @@ type AuthProvider = 'email' | 'google' | 'github' | 'apple' | 'microsoft' | 'twi
118
118
  interface AuthOptions {
119
119
  redirectUrl?: string;
120
120
  metadata?: Record<string, any>;
121
+ /**
122
+ * Force redirect flow instead of popup (useful for Safari or when popups are blocked)
123
+ * When true, always uses redirect flow regardless of browser detection
124
+ */
125
+ forceRedirect?: boolean;
121
126
  }
122
127
  interface SignUpData {
123
128
  email: string;
@@ -1008,8 +1013,14 @@ declare class BlinkAuth {
1008
1013
  * Sign in with Microsoft (headless mode)
1009
1014
  */
1010
1015
  signInWithMicrosoft(options?: AuthOptions): Promise<BlinkUser>;
1016
+ /**
1017
+ * Check if current browser is Safari (desktop, iPhone, iPad)
1018
+ * Safari has strict popup blocking, so we must use redirect flow
1019
+ */
1020
+ private isSafari;
1011
1021
  /**
1012
1022
  * Generic provider sign-in method (headless mode)
1023
+ * Uses redirect flow for Safari, popup flow for other browsers
1013
1024
  */
1014
1025
  signInWithProvider(provider: AuthProvider, options?: AuthOptions): Promise<BlinkUser>;
1015
1026
  /**
@@ -1114,6 +1125,11 @@ declare class BlinkAuth {
1114
1125
  private setTokens;
1115
1126
  private clearTokens;
1116
1127
  private getStoredTokens;
1128
+ /**
1129
+ * Extract URL parameters from both search params and hash fragments
1130
+ * Safari OAuth redirects often use hash fragments instead of query params
1131
+ */
1132
+ private extractUrlParams;
1117
1133
  private extractTokensFromUrl;
1118
1134
  private clearUrlTokens;
1119
1135
  private redirectToAuth;
package/dist/index.d.ts CHANGED
@@ -118,6 +118,11 @@ type AuthProvider = 'email' | 'google' | 'github' | 'apple' | 'microsoft' | 'twi
118
118
  interface AuthOptions {
119
119
  redirectUrl?: string;
120
120
  metadata?: Record<string, any>;
121
+ /**
122
+ * Force redirect flow instead of popup (useful for Safari or when popups are blocked)
123
+ * When true, always uses redirect flow regardless of browser detection
124
+ */
125
+ forceRedirect?: boolean;
121
126
  }
122
127
  interface SignUpData {
123
128
  email: string;
@@ -1008,8 +1013,14 @@ declare class BlinkAuth {
1008
1013
  * Sign in with Microsoft (headless mode)
1009
1014
  */
1010
1015
  signInWithMicrosoft(options?: AuthOptions): Promise<BlinkUser>;
1016
+ /**
1017
+ * Check if current browser is Safari (desktop, iPhone, iPad)
1018
+ * Safari has strict popup blocking, so we must use redirect flow
1019
+ */
1020
+ private isSafari;
1011
1021
  /**
1012
1022
  * Generic provider sign-in method (headless mode)
1023
+ * Uses redirect flow for Safari, popup flow for other browsers
1013
1024
  */
1014
1025
  signInWithProvider(provider: AuthProvider, options?: AuthOptions): Promise<BlinkUser>;
1015
1026
  /**
@@ -1114,6 +1125,11 @@ declare class BlinkAuth {
1114
1125
  private setTokens;
1115
1126
  private clearTokens;
1116
1127
  private getStoredTokens;
1128
+ /**
1129
+ * Extract URL parameters from both search params and hash fragments
1130
+ * Safari OAuth redirects often use hash fragments instead of query params
1131
+ */
1132
+ private extractUrlParams;
1117
1133
  private extractTokensFromUrl;
1118
1134
  private clearUrlTokens;
1119
1135
  private redirectToAuth;
package/dist/index.js CHANGED
@@ -1144,6 +1144,36 @@ var BlinkAuth = class {
1144
1144
  return;
1145
1145
  }
1146
1146
  }
1147
+ try {
1148
+ if (typeof window !== "undefined") {
1149
+ console.log("\u{1F50D} Checking URL for errors/tokens:", {
1150
+ href: window.location.href,
1151
+ search: window.location.search,
1152
+ hash: window.location.hash
1153
+ });
1154
+ const urlParams = this.extractUrlParams();
1155
+ const errorParam = urlParams.get("error");
1156
+ if (errorParam) {
1157
+ const errorCode = this.mapErrorCodeFromResponse(errorParam);
1158
+ const errorMessage = urlParams.get("error_description") || "Authentication failed";
1159
+ const error = new BlinkAuthError(errorCode, errorMessage);
1160
+ console.error("\u274C Auth error in URL:", {
1161
+ error: errorParam,
1162
+ errorMessage,
1163
+ allParams: Object.fromEntries(urlParams.entries())
1164
+ });
1165
+ if (typeof window !== "undefined") {
1166
+ window.dispatchEvent(new CustomEvent("blink:auth:error", {
1167
+ detail: { error, errorMessage }
1168
+ }));
1169
+ }
1170
+ this.clearUrlTokens();
1171
+ return;
1172
+ }
1173
+ }
1174
+ } catch (error) {
1175
+ console.error("Error handling failed redirect:", error);
1176
+ }
1147
1177
  const tokensFromUrl = this.extractTokensFromUrl();
1148
1178
  if (tokensFromUrl) {
1149
1179
  console.log("\u{1F4E5} Found tokens in URL, setting them...");
@@ -1151,6 +1181,31 @@ var BlinkAuth = class {
1151
1181
  this.clearUrlTokens();
1152
1182
  console.log("\u2705 Auth initialization complete (from URL)");
1153
1183
  return;
1184
+ } else {
1185
+ console.log("\u26A0\uFE0F No tokens found in URL after redirect - checking if this was a redirect callback");
1186
+ if (typeof window !== "undefined") {
1187
+ const urlParams = this.extractUrlParams();
1188
+ const state = urlParams.get("state");
1189
+ if (state) {
1190
+ console.log("\u26A0\uFE0F State found in URL but no tokens - redirect may have failed silently");
1191
+ try {
1192
+ const expectedState = sessionStorage.getItem("blink_oauth_state");
1193
+ if (expectedState === state) {
1194
+ console.error("\u274C Redirect callback received but no tokens - authentication may have failed");
1195
+ const errorMessage = "Authentication failed. Please try again.";
1196
+ if (typeof window !== "undefined") {
1197
+ window.dispatchEvent(new CustomEvent("blink:auth:error", {
1198
+ detail: { error: new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, errorMessage), errorMessage }
1199
+ }));
1200
+ }
1201
+ this.clearUrlTokens();
1202
+ return;
1203
+ }
1204
+ } catch (e) {
1205
+ console.error("Error checking sessionStorage:", e);
1206
+ }
1207
+ }
1208
+ }
1154
1209
  }
1155
1210
  const storedTokens = await this.getStoredTokens();
1156
1211
  if (storedTokens) {
@@ -1504,37 +1559,145 @@ var BlinkAuth = class {
1504
1559
  }
1505
1560
  return this.signInWithProvider("microsoft", options);
1506
1561
  }
1562
+ /**
1563
+ * Check if current browser is Safari (desktop, iPhone, iPad)
1564
+ * Safari has strict popup blocking, so we must use redirect flow
1565
+ */
1566
+ isSafari() {
1567
+ if (typeof window === "undefined") return false;
1568
+ const ua = navigator.userAgent.toLowerCase();
1569
+ const hasSafariUA = /safari/i.test(ua) && !/chrome|chromium|android|edg|firefox|opera/i.test(ua);
1570
+ const isSafariVendor = typeof navigator !== "undefined" && !!navigator.vendor && /apple/i.test(navigator.vendor);
1571
+ const isIOSSafari = /safari/i.test(ua) && /iphone|ipad|ipod/i.test(ua);
1572
+ const hasSafariProperties = typeof window !== "undefined" && ("safari" in window || window.safari !== void 0) && !("chrome" in window);
1573
+ const isMacSafari = /macintosh/i.test(ua) && isSafariVendor && !/chrome|chromium|firefox|edg/i.test(ua);
1574
+ const isSafari = !!(hasSafariUA || isSafariVendor || isIOSSafari || hasSafariProperties || isMacSafari);
1575
+ if (isSafari) {
1576
+ console.log("\u{1F34E} Safari browser detected - will use redirect flow", {
1577
+ hasSafariUA,
1578
+ isSafariVendor,
1579
+ isIOSSafari,
1580
+ hasSafariProperties,
1581
+ isMacSafari,
1582
+ userAgent: ua.substring(0, 100),
1583
+ vendor: navigator.vendor
1584
+ });
1585
+ } else {
1586
+ console.log("\u{1F50D} Not detected as Safari:", {
1587
+ hasSafariUA,
1588
+ isSafariVendor,
1589
+ isIOSSafari,
1590
+ hasSafariProperties,
1591
+ isMacSafari,
1592
+ userAgent: ua.substring(0, 100),
1593
+ vendor: navigator.vendor
1594
+ });
1595
+ }
1596
+ return isSafari;
1597
+ }
1507
1598
  /**
1508
1599
  * Generic provider sign-in method (headless mode)
1600
+ * Uses redirect flow for Safari, popup flow for other browsers
1509
1601
  */
1510
1602
  async signInWithProvider(provider, options) {
1511
1603
  if (this.authConfig.mode !== "headless") {
1512
1604
  throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
1513
1605
  }
1514
- return new Promise((resolve, reject) => {
1515
- const state = this.generateState();
1516
- try {
1517
- if (typeof window !== "undefined") {
1518
- sessionStorage.setItem("blink_oauth_state", state);
1519
- }
1520
- } catch {
1606
+ const state = this.generateState();
1607
+ let redirectUrl = options?.redirectUrl || "";
1608
+ if (!redirectUrl && typeof window !== "undefined") {
1609
+ if (window.location.href.startsWith("http")) {
1610
+ redirectUrl = window.location.href;
1611
+ } else {
1612
+ redirectUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}${window.location.search}${window.location.hash}`;
1521
1613
  }
1522
- const redirectUrl = options?.redirectUrl || window.location.origin;
1614
+ }
1615
+ try {
1616
+ const redirectUrlObj = new URL(redirectUrl);
1617
+ redirectUrlObj.searchParams.delete("access_token");
1618
+ redirectUrlObj.searchParams.delete("refresh_token");
1619
+ redirectUrlObj.searchParams.delete("state");
1620
+ redirectUrlObj.searchParams.delete("error");
1621
+ redirectUrlObj.searchParams.delete("error_description");
1622
+ redirectUrlObj.hash = "";
1623
+ redirectUrl = redirectUrlObj.toString();
1624
+ } catch (e) {
1625
+ console.warn("Failed to clean redirect URL:", e);
1626
+ }
1627
+ try {
1628
+ if (typeof window !== "undefined") {
1629
+ sessionStorage.setItem("blink_oauth_state", state);
1630
+ sessionStorage.setItem("blink_oauth_redirect_url", redirectUrl);
1631
+ console.log("\u{1F4BE} Stored OAuth state:", { state, redirectUrl });
1632
+ }
1633
+ } catch (e) {
1634
+ console.warn("Failed to store OAuth state in sessionStorage:", e);
1635
+ }
1636
+ const isSafari = this.isSafari();
1637
+ const forceRedirect = options?.forceRedirect === true;
1638
+ if ((isSafari || forceRedirect) && typeof window !== "undefined") {
1639
+ console.log("\u{1F310} Using redirect flow (no popup)", {
1640
+ provider,
1641
+ projectId: this.config.projectId,
1642
+ state,
1643
+ redirectUrl,
1644
+ authUrl: this.authUrl,
1645
+ reason: isSafari ? "Safari detected" : "forceRedirect option set"
1646
+ });
1647
+ const authRedirectUrl = new URL("/auth", this.authUrl);
1648
+ authRedirectUrl.searchParams.set("provider", provider);
1649
+ authRedirectUrl.searchParams.set("project_id", this.config.projectId);
1650
+ authRedirectUrl.searchParams.set("state", state);
1651
+ authRedirectUrl.searchParams.set("mode", "redirect");
1652
+ authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
1653
+ console.log("\u{1F504} Redirecting to:", authRedirectUrl.toString());
1654
+ window.location.href = authRedirectUrl.toString();
1655
+ return new Promise(() => {
1656
+ });
1657
+ }
1658
+ return new Promise((resolve, reject) => {
1523
1659
  const popupUrl = new URL("/auth", this.authUrl);
1524
1660
  popupUrl.searchParams.set("provider", provider);
1525
1661
  popupUrl.searchParams.set("project_id", this.config.projectId);
1526
1662
  popupUrl.searchParams.set("state", state);
1527
1663
  popupUrl.searchParams.set("mode", "popup");
1528
1664
  popupUrl.searchParams.set("redirect_url", redirectUrl);
1665
+ if (typeof window !== "undefined") {
1666
+ popupUrl.searchParams.set("opener_origin", window.location.origin);
1667
+ }
1529
1668
  const popup = window.open(
1530
1669
  popupUrl.toString(),
1531
1670
  "blink-auth",
1532
1671
  "width=500,height=600,scrollbars=yes,resizable=yes"
1533
1672
  );
1534
1673
  if (!popup) {
1535
- reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
1674
+ console.warn("\u26A0\uFE0F Popup was blocked, falling back to redirect flow");
1675
+ const authRedirectUrl = new URL("/auth", this.authUrl);
1676
+ authRedirectUrl.searchParams.set("provider", provider);
1677
+ authRedirectUrl.searchParams.set("project_id", this.config.projectId);
1678
+ authRedirectUrl.searchParams.set("state", state);
1679
+ authRedirectUrl.searchParams.set("mode", "redirect");
1680
+ authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
1681
+ console.log("\u{1F504} Falling back to redirect:", authRedirectUrl.toString());
1682
+ window.location.href = authRedirectUrl.toString();
1536
1683
  return;
1537
1684
  }
1685
+ try {
1686
+ if (popup.closed || !popup.window) {
1687
+ console.warn("\u26A0\uFE0F Popup appears to be blocked (closed immediately), falling back to redirect flow");
1688
+ const authRedirectUrl = new URL("/auth", this.authUrl);
1689
+ authRedirectUrl.searchParams.set("provider", provider);
1690
+ authRedirectUrl.searchParams.set("project_id", this.config.projectId);
1691
+ authRedirectUrl.searchParams.set("state", state);
1692
+ authRedirectUrl.searchParams.set("mode", "redirect");
1693
+ authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
1694
+ console.log("\u{1F504} Falling back to redirect:", authRedirectUrl.toString());
1695
+ window.location.href = authRedirectUrl.toString();
1696
+ return;
1697
+ }
1698
+ } catch (e) {
1699
+ console.log("\u26A0\uFE0F Could not verify popup state, assuming it opened successfully");
1700
+ }
1538
1701
  let timeoutId;
1539
1702
  const messageListener = (event) => {
1540
1703
  let allowed = false;
@@ -1544,7 +1707,12 @@ var BlinkAuth = class {
1544
1707
  } catch {
1545
1708
  }
1546
1709
  if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
1547
- if (!allowed) return;
1710
+ if (typeof window !== "undefined" && event.origin === window.location.origin) allowed = true;
1711
+ if (!allowed) {
1712
+ console.log("\u{1F6AB} Blocked postMessage from untrusted origin:", event.origin);
1713
+ return;
1714
+ }
1715
+ console.log("\u2705 Accepted postMessage from origin:", event.origin);
1548
1716
  if (event.data?.type === "BLINK_AUTH_TOKENS") {
1549
1717
  const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
1550
1718
  try {
@@ -2304,17 +2472,50 @@ var BlinkAuth = class {
2304
2472
  return null;
2305
2473
  }
2306
2474
  }
2475
+ /**
2476
+ * Extract URL parameters from both search params and hash fragments
2477
+ * Safari OAuth redirects often use hash fragments instead of query params
2478
+ */
2479
+ extractUrlParams() {
2480
+ const params = new URLSearchParams();
2481
+ if (typeof window !== "undefined" && window.location.search) {
2482
+ const searchParams = new URLSearchParams(window.location.search);
2483
+ for (const [key, value] of searchParams.entries()) {
2484
+ params.set(key, value);
2485
+ }
2486
+ }
2487
+ if (typeof window !== "undefined" && window.location.hash) {
2488
+ const hash = window.location.hash.substring(1);
2489
+ const hashParams = new URLSearchParams(hash);
2490
+ for (const [key, value] of hashParams.entries()) {
2491
+ if (!params.has(key)) {
2492
+ params.set(key, value);
2493
+ }
2494
+ }
2495
+ }
2496
+ return params;
2497
+ }
2307
2498
  extractTokensFromUrl() {
2308
2499
  if (typeof window === "undefined") return null;
2309
- const params = new URLSearchParams(window.location.search);
2500
+ const params = this.extractUrlParams();
2310
2501
  const accessToken = params.get("access_token");
2311
2502
  const refreshToken = params.get("refresh_token");
2503
+ const state = params.get("state");
2504
+ const error = params.get("error");
2312
2505
  console.log("\u{1F50D} Extracting tokens from URL:", {
2313
2506
  url: window.location.href,
2507
+ search: window.location.search,
2508
+ hash: window.location.hash,
2314
2509
  accessToken: accessToken ? `${accessToken.substring(0, 20)}...` : null,
2315
2510
  refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null,
2511
+ state: state ? `${state.substring(0, 10)}...` : null,
2512
+ error: error || null,
2316
2513
  allParams: Object.fromEntries(params.entries())
2317
2514
  });
2515
+ if (error) {
2516
+ console.log("\u274C Error parameter found in URL, not extracting tokens");
2517
+ return null;
2518
+ }
2318
2519
  if (accessToken) {
2319
2520
  const tokens = {
2320
2521
  access_token: accessToken,
@@ -2329,7 +2530,8 @@ var BlinkAuth = class {
2329
2530
  };
2330
2531
  console.log("\u2705 Tokens extracted successfully:", {
2331
2532
  hasAccessToken: !!tokens.access_token,
2332
- hasRefreshToken: !!tokens.refresh_token
2533
+ hasRefreshToken: !!tokens.refresh_token,
2534
+ state: state || "no state"
2333
2535
  });
2334
2536
  return tokens;
2335
2537
  }
@@ -2349,8 +2551,9 @@ var BlinkAuth = class {
2349
2551
  url.searchParams.delete("code");
2350
2552
  url.searchParams.delete("error");
2351
2553
  url.searchParams.delete("error_description");
2554
+ url.hash = "";
2352
2555
  window.history.replaceState({}, "", url.toString());
2353
- console.log("\u{1F9F9} URL cleaned up, removed auth parameters");
2556
+ console.log("\u{1F9F9} URL cleaned up, removed auth parameters from both search and hash");
2354
2557
  }
2355
2558
  redirectToAuth() {
2356
2559
  if (typeof window !== "undefined") {
package/dist/index.mjs CHANGED
@@ -1142,6 +1142,36 @@ var BlinkAuth = class {
1142
1142
  return;
1143
1143
  }
1144
1144
  }
1145
+ try {
1146
+ if (typeof window !== "undefined") {
1147
+ console.log("\u{1F50D} Checking URL for errors/tokens:", {
1148
+ href: window.location.href,
1149
+ search: window.location.search,
1150
+ hash: window.location.hash
1151
+ });
1152
+ const urlParams = this.extractUrlParams();
1153
+ const errorParam = urlParams.get("error");
1154
+ if (errorParam) {
1155
+ const errorCode = this.mapErrorCodeFromResponse(errorParam);
1156
+ const errorMessage = urlParams.get("error_description") || "Authentication failed";
1157
+ const error = new BlinkAuthError(errorCode, errorMessage);
1158
+ console.error("\u274C Auth error in URL:", {
1159
+ error: errorParam,
1160
+ errorMessage,
1161
+ allParams: Object.fromEntries(urlParams.entries())
1162
+ });
1163
+ if (typeof window !== "undefined") {
1164
+ window.dispatchEvent(new CustomEvent("blink:auth:error", {
1165
+ detail: { error, errorMessage }
1166
+ }));
1167
+ }
1168
+ this.clearUrlTokens();
1169
+ return;
1170
+ }
1171
+ }
1172
+ } catch (error) {
1173
+ console.error("Error handling failed redirect:", error);
1174
+ }
1145
1175
  const tokensFromUrl = this.extractTokensFromUrl();
1146
1176
  if (tokensFromUrl) {
1147
1177
  console.log("\u{1F4E5} Found tokens in URL, setting them...");
@@ -1149,6 +1179,31 @@ var BlinkAuth = class {
1149
1179
  this.clearUrlTokens();
1150
1180
  console.log("\u2705 Auth initialization complete (from URL)");
1151
1181
  return;
1182
+ } else {
1183
+ console.log("\u26A0\uFE0F No tokens found in URL after redirect - checking if this was a redirect callback");
1184
+ if (typeof window !== "undefined") {
1185
+ const urlParams = this.extractUrlParams();
1186
+ const state = urlParams.get("state");
1187
+ if (state) {
1188
+ console.log("\u26A0\uFE0F State found in URL but no tokens - redirect may have failed silently");
1189
+ try {
1190
+ const expectedState = sessionStorage.getItem("blink_oauth_state");
1191
+ if (expectedState === state) {
1192
+ console.error("\u274C Redirect callback received but no tokens - authentication may have failed");
1193
+ const errorMessage = "Authentication failed. Please try again.";
1194
+ if (typeof window !== "undefined") {
1195
+ window.dispatchEvent(new CustomEvent("blink:auth:error", {
1196
+ detail: { error: new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, errorMessage), errorMessage }
1197
+ }));
1198
+ }
1199
+ this.clearUrlTokens();
1200
+ return;
1201
+ }
1202
+ } catch (e) {
1203
+ console.error("Error checking sessionStorage:", e);
1204
+ }
1205
+ }
1206
+ }
1152
1207
  }
1153
1208
  const storedTokens = await this.getStoredTokens();
1154
1209
  if (storedTokens) {
@@ -1502,37 +1557,145 @@ var BlinkAuth = class {
1502
1557
  }
1503
1558
  return this.signInWithProvider("microsoft", options);
1504
1559
  }
1560
+ /**
1561
+ * Check if current browser is Safari (desktop, iPhone, iPad)
1562
+ * Safari has strict popup blocking, so we must use redirect flow
1563
+ */
1564
+ isSafari() {
1565
+ if (typeof window === "undefined") return false;
1566
+ const ua = navigator.userAgent.toLowerCase();
1567
+ const hasSafariUA = /safari/i.test(ua) && !/chrome|chromium|android|edg|firefox|opera/i.test(ua);
1568
+ const isSafariVendor = typeof navigator !== "undefined" && !!navigator.vendor && /apple/i.test(navigator.vendor);
1569
+ const isIOSSafari = /safari/i.test(ua) && /iphone|ipad|ipod/i.test(ua);
1570
+ const hasSafariProperties = typeof window !== "undefined" && ("safari" in window || window.safari !== void 0) && !("chrome" in window);
1571
+ const isMacSafari = /macintosh/i.test(ua) && isSafariVendor && !/chrome|chromium|firefox|edg/i.test(ua);
1572
+ const isSafari = !!(hasSafariUA || isSafariVendor || isIOSSafari || hasSafariProperties || isMacSafari);
1573
+ if (isSafari) {
1574
+ console.log("\u{1F34E} Safari browser detected - will use redirect flow", {
1575
+ hasSafariUA,
1576
+ isSafariVendor,
1577
+ isIOSSafari,
1578
+ hasSafariProperties,
1579
+ isMacSafari,
1580
+ userAgent: ua.substring(0, 100),
1581
+ vendor: navigator.vendor
1582
+ });
1583
+ } else {
1584
+ console.log("\u{1F50D} Not detected as Safari:", {
1585
+ hasSafariUA,
1586
+ isSafariVendor,
1587
+ isIOSSafari,
1588
+ hasSafariProperties,
1589
+ isMacSafari,
1590
+ userAgent: ua.substring(0, 100),
1591
+ vendor: navigator.vendor
1592
+ });
1593
+ }
1594
+ return isSafari;
1595
+ }
1505
1596
  /**
1506
1597
  * Generic provider sign-in method (headless mode)
1598
+ * Uses redirect flow for Safari, popup flow for other browsers
1507
1599
  */
1508
1600
  async signInWithProvider(provider, options) {
1509
1601
  if (this.authConfig.mode !== "headless") {
1510
1602
  throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
1511
1603
  }
1512
- return new Promise((resolve, reject) => {
1513
- const state = this.generateState();
1514
- try {
1515
- if (typeof window !== "undefined") {
1516
- sessionStorage.setItem("blink_oauth_state", state);
1517
- }
1518
- } catch {
1604
+ const state = this.generateState();
1605
+ let redirectUrl = options?.redirectUrl || "";
1606
+ if (!redirectUrl && typeof window !== "undefined") {
1607
+ if (window.location.href.startsWith("http")) {
1608
+ redirectUrl = window.location.href;
1609
+ } else {
1610
+ redirectUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}${window.location.search}${window.location.hash}`;
1519
1611
  }
1520
- const redirectUrl = options?.redirectUrl || window.location.origin;
1612
+ }
1613
+ try {
1614
+ const redirectUrlObj = new URL(redirectUrl);
1615
+ redirectUrlObj.searchParams.delete("access_token");
1616
+ redirectUrlObj.searchParams.delete("refresh_token");
1617
+ redirectUrlObj.searchParams.delete("state");
1618
+ redirectUrlObj.searchParams.delete("error");
1619
+ redirectUrlObj.searchParams.delete("error_description");
1620
+ redirectUrlObj.hash = "";
1621
+ redirectUrl = redirectUrlObj.toString();
1622
+ } catch (e) {
1623
+ console.warn("Failed to clean redirect URL:", e);
1624
+ }
1625
+ try {
1626
+ if (typeof window !== "undefined") {
1627
+ sessionStorage.setItem("blink_oauth_state", state);
1628
+ sessionStorage.setItem("blink_oauth_redirect_url", redirectUrl);
1629
+ console.log("\u{1F4BE} Stored OAuth state:", { state, redirectUrl });
1630
+ }
1631
+ } catch (e) {
1632
+ console.warn("Failed to store OAuth state in sessionStorage:", e);
1633
+ }
1634
+ const isSafari = this.isSafari();
1635
+ const forceRedirect = options?.forceRedirect === true;
1636
+ if ((isSafari || forceRedirect) && typeof window !== "undefined") {
1637
+ console.log("\u{1F310} Using redirect flow (no popup)", {
1638
+ provider,
1639
+ projectId: this.config.projectId,
1640
+ state,
1641
+ redirectUrl,
1642
+ authUrl: this.authUrl,
1643
+ reason: isSafari ? "Safari detected" : "forceRedirect option set"
1644
+ });
1645
+ const authRedirectUrl = new URL("/auth", this.authUrl);
1646
+ authRedirectUrl.searchParams.set("provider", provider);
1647
+ authRedirectUrl.searchParams.set("project_id", this.config.projectId);
1648
+ authRedirectUrl.searchParams.set("state", state);
1649
+ authRedirectUrl.searchParams.set("mode", "redirect");
1650
+ authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
1651
+ console.log("\u{1F504} Redirecting to:", authRedirectUrl.toString());
1652
+ window.location.href = authRedirectUrl.toString();
1653
+ return new Promise(() => {
1654
+ });
1655
+ }
1656
+ return new Promise((resolve, reject) => {
1521
1657
  const popupUrl = new URL("/auth", this.authUrl);
1522
1658
  popupUrl.searchParams.set("provider", provider);
1523
1659
  popupUrl.searchParams.set("project_id", this.config.projectId);
1524
1660
  popupUrl.searchParams.set("state", state);
1525
1661
  popupUrl.searchParams.set("mode", "popup");
1526
1662
  popupUrl.searchParams.set("redirect_url", redirectUrl);
1663
+ if (typeof window !== "undefined") {
1664
+ popupUrl.searchParams.set("opener_origin", window.location.origin);
1665
+ }
1527
1666
  const popup = window.open(
1528
1667
  popupUrl.toString(),
1529
1668
  "blink-auth",
1530
1669
  "width=500,height=600,scrollbars=yes,resizable=yes"
1531
1670
  );
1532
1671
  if (!popup) {
1533
- reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
1672
+ console.warn("\u26A0\uFE0F Popup was blocked, falling back to redirect flow");
1673
+ const authRedirectUrl = new URL("/auth", this.authUrl);
1674
+ authRedirectUrl.searchParams.set("provider", provider);
1675
+ authRedirectUrl.searchParams.set("project_id", this.config.projectId);
1676
+ authRedirectUrl.searchParams.set("state", state);
1677
+ authRedirectUrl.searchParams.set("mode", "redirect");
1678
+ authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
1679
+ console.log("\u{1F504} Falling back to redirect:", authRedirectUrl.toString());
1680
+ window.location.href = authRedirectUrl.toString();
1534
1681
  return;
1535
1682
  }
1683
+ try {
1684
+ if (popup.closed || !popup.window) {
1685
+ console.warn("\u26A0\uFE0F Popup appears to be blocked (closed immediately), falling back to redirect flow");
1686
+ const authRedirectUrl = new URL("/auth", this.authUrl);
1687
+ authRedirectUrl.searchParams.set("provider", provider);
1688
+ authRedirectUrl.searchParams.set("project_id", this.config.projectId);
1689
+ authRedirectUrl.searchParams.set("state", state);
1690
+ authRedirectUrl.searchParams.set("mode", "redirect");
1691
+ authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
1692
+ console.log("\u{1F504} Falling back to redirect:", authRedirectUrl.toString());
1693
+ window.location.href = authRedirectUrl.toString();
1694
+ return;
1695
+ }
1696
+ } catch (e) {
1697
+ console.log("\u26A0\uFE0F Could not verify popup state, assuming it opened successfully");
1698
+ }
1536
1699
  let timeoutId;
1537
1700
  const messageListener = (event) => {
1538
1701
  let allowed = false;
@@ -1542,7 +1705,12 @@ var BlinkAuth = class {
1542
1705
  } catch {
1543
1706
  }
1544
1707
  if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
1545
- if (!allowed) return;
1708
+ if (typeof window !== "undefined" && event.origin === window.location.origin) allowed = true;
1709
+ if (!allowed) {
1710
+ console.log("\u{1F6AB} Blocked postMessage from untrusted origin:", event.origin);
1711
+ return;
1712
+ }
1713
+ console.log("\u2705 Accepted postMessage from origin:", event.origin);
1546
1714
  if (event.data?.type === "BLINK_AUTH_TOKENS") {
1547
1715
  const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
1548
1716
  try {
@@ -2302,17 +2470,50 @@ var BlinkAuth = class {
2302
2470
  return null;
2303
2471
  }
2304
2472
  }
2473
+ /**
2474
+ * Extract URL parameters from both search params and hash fragments
2475
+ * Safari OAuth redirects often use hash fragments instead of query params
2476
+ */
2477
+ extractUrlParams() {
2478
+ const params = new URLSearchParams();
2479
+ if (typeof window !== "undefined" && window.location.search) {
2480
+ const searchParams = new URLSearchParams(window.location.search);
2481
+ for (const [key, value] of searchParams.entries()) {
2482
+ params.set(key, value);
2483
+ }
2484
+ }
2485
+ if (typeof window !== "undefined" && window.location.hash) {
2486
+ const hash = window.location.hash.substring(1);
2487
+ const hashParams = new URLSearchParams(hash);
2488
+ for (const [key, value] of hashParams.entries()) {
2489
+ if (!params.has(key)) {
2490
+ params.set(key, value);
2491
+ }
2492
+ }
2493
+ }
2494
+ return params;
2495
+ }
2305
2496
  extractTokensFromUrl() {
2306
2497
  if (typeof window === "undefined") return null;
2307
- const params = new URLSearchParams(window.location.search);
2498
+ const params = this.extractUrlParams();
2308
2499
  const accessToken = params.get("access_token");
2309
2500
  const refreshToken = params.get("refresh_token");
2501
+ const state = params.get("state");
2502
+ const error = params.get("error");
2310
2503
  console.log("\u{1F50D} Extracting tokens from URL:", {
2311
2504
  url: window.location.href,
2505
+ search: window.location.search,
2506
+ hash: window.location.hash,
2312
2507
  accessToken: accessToken ? `${accessToken.substring(0, 20)}...` : null,
2313
2508
  refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null,
2509
+ state: state ? `${state.substring(0, 10)}...` : null,
2510
+ error: error || null,
2314
2511
  allParams: Object.fromEntries(params.entries())
2315
2512
  });
2513
+ if (error) {
2514
+ console.log("\u274C Error parameter found in URL, not extracting tokens");
2515
+ return null;
2516
+ }
2316
2517
  if (accessToken) {
2317
2518
  const tokens = {
2318
2519
  access_token: accessToken,
@@ -2327,7 +2528,8 @@ var BlinkAuth = class {
2327
2528
  };
2328
2529
  console.log("\u2705 Tokens extracted successfully:", {
2329
2530
  hasAccessToken: !!tokens.access_token,
2330
- hasRefreshToken: !!tokens.refresh_token
2531
+ hasRefreshToken: !!tokens.refresh_token,
2532
+ state: state || "no state"
2331
2533
  });
2332
2534
  return tokens;
2333
2535
  }
@@ -2347,8 +2549,9 @@ var BlinkAuth = class {
2347
2549
  url.searchParams.delete("code");
2348
2550
  url.searchParams.delete("error");
2349
2551
  url.searchParams.delete("error_description");
2552
+ url.hash = "";
2350
2553
  window.history.replaceState({}, "", url.toString());
2351
- console.log("\u{1F9F9} URL cleaned up, removed auth parameters");
2554
+ console.log("\u{1F9F9} URL cleaned up, removed auth parameters from both search and hash");
2352
2555
  }
2353
2556
  redirectToAuth() {
2354
2557
  if (typeof window !== "undefined") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.19.1",
3
+ "version": "0.19.4",
4
4
  "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps",
5
5
  "keywords": [
6
6
  "blink",