@blinkdotnew/sdk 0.19.6 → 2.0.1

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
@@ -1054,18 +1054,37 @@ declare class BlinkAuth {
1054
1054
  signInWithEmail(email: string, password: string): Promise<BlinkUser>;
1055
1055
  /**
1056
1056
  * Sign in with Google (headless mode)
1057
+ *
1058
+ * **Universal OAuth** - Works on both Web and React Native!
1059
+ *
1060
+ * On React Native, requires `webBrowser` to be configured in client:
1061
+ * ```typescript
1062
+ * const blink = createClient({
1063
+ * auth: { mode: 'headless', webBrowser: WebBrowser }
1064
+ * })
1065
+ * await blink.auth.signInWithGoogle() // Works on both platforms!
1066
+ * ```
1057
1067
  */
1058
1068
  signInWithGoogle(options?: AuthOptions): Promise<BlinkUser>;
1059
1069
  /**
1060
1070
  * Sign in with GitHub (headless mode)
1071
+ *
1072
+ * **Universal OAuth** - Works on both Web and React Native!
1073
+ * See signInWithGoogle() for setup instructions.
1061
1074
  */
1062
1075
  signInWithGitHub(options?: AuthOptions): Promise<BlinkUser>;
1063
1076
  /**
1064
1077
  * Sign in with Apple (headless mode)
1078
+ *
1079
+ * **Universal OAuth** - Works on both Web and React Native!
1080
+ * See signInWithGoogle() for setup instructions.
1065
1081
  */
1066
1082
  signInWithApple(options?: AuthOptions): Promise<BlinkUser>;
1067
1083
  /**
1068
1084
  * Sign in with Microsoft (headless mode)
1085
+ *
1086
+ * **Universal OAuth** - Works on both Web and React Native!
1087
+ * See signInWithGoogle() for setup instructions.
1069
1088
  */
1070
1089
  signInWithMicrosoft(options?: AuthOptions): Promise<BlinkUser>;
1071
1090
  /**
@@ -1151,56 +1170,36 @@ declare class BlinkAuth {
1151
1170
  authenticate: () => Promise<BlinkUser>;
1152
1171
  }>;
1153
1172
  /**
1154
- * Sign in with Google using expo-web-browser (React Native convenience)
1155
- */
1156
- signInWithGoogleMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1157
- authUrl: string;
1158
- authenticate: () => Promise<BlinkUser>;
1159
- }>;
1160
- /**
1161
- * Sign in with GitHub using expo-web-browser (React Native convenience)
1173
+ * Universal OAuth flow using session-based authentication (internal)
1174
+ * Works on ALL platforms: Web, iOS, Android
1175
+ * Uses expo-web-browser to open auth URL and polls for completion
1162
1176
  */
1163
- signInWithGitHubMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1164
- authUrl: string;
1165
- authenticate: () => Promise<BlinkUser>;
1166
- }>;
1167
- /**
1168
- * Sign in with Apple using expo-web-browser (React Native convenience)
1169
- */
1170
- signInWithAppleMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1171
- authUrl: string;
1172
- authenticate: () => Promise<BlinkUser>;
1173
- }>;
1174
- /**
1175
- * React Native OAuth flow using expo-web-browser (internal)
1176
- * Automatically handles opening browser and extracting tokens from redirect
1177
- */
1178
- private signInWithProviderReactNative;
1177
+ private signInWithProviderUniversal;
1179
1178
  /**
1180
1179
  * Generic provider sign-in method (headless mode)
1181
1180
  *
1182
- * Supports both web (popup) and React Native (deep link) OAuth flows.
1181
+ * **Universal OAuth** - Works seamlessly on both Web and React Native!
1183
1182
  *
1184
- * **React Native Setup:**
1185
- * 1. Configure deep linking in your app (app.json):
1186
- * ```json
1187
- * { "expo": { "scheme": "com.yourapp" } }
1188
- * ```
1183
+ * When `webBrowser` is configured in the client, this method automatically
1184
+ * uses the session-based OAuth flow that works on ALL platforms.
1189
1185
  *
1190
- * 2. Add redirect URL in Blink Dashboard:
1191
- * `com.yourapp://**`
1186
+ * **Universal Setup (configure once, works everywhere):**
1187
+ * ```typescript
1188
+ * import * as WebBrowser from 'expo-web-browser'
1189
+ * import AsyncStorage from '@react-native-async-storage/async-storage'
1192
1190
  *
1193
- * 3. Handle deep link callbacks:
1194
- * ```typescript
1195
- * import * as Linking from 'expo-linking'
1191
+ * const blink = createClient({
1192
+ * projectId: 'your-project',
1193
+ * auth: {
1194
+ * mode: 'headless',
1195
+ * webBrowser: WebBrowser // Pass the module here
1196
+ * },
1197
+ * storage: new AsyncStorageAdapter(AsyncStorage)
1198
+ * })
1196
1199
  *
1197
- * Linking.addEventListener('url', async ({ url }) => {
1198
- * const { queryParams } = Linking.parse(url)
1199
- * if (queryParams.access_token) {
1200
- * await blink.auth.setSession(queryParams)
1201
- * }
1202
- * })
1203
- * ```
1200
+ * // Now this works on ALL platforms - no platform checks needed!
1201
+ * const user = await blink.auth.signInWithGoogle()
1202
+ * ```
1204
1203
  *
1205
1204
  * @param provider - OAuth provider (google, github, apple, etc.)
1206
1205
  * @param options - Optional redirect URL and metadata
package/dist/index.d.ts CHANGED
@@ -1054,18 +1054,37 @@ declare class BlinkAuth {
1054
1054
  signInWithEmail(email: string, password: string): Promise<BlinkUser>;
1055
1055
  /**
1056
1056
  * Sign in with Google (headless mode)
1057
+ *
1058
+ * **Universal OAuth** - Works on both Web and React Native!
1059
+ *
1060
+ * On React Native, requires `webBrowser` to be configured in client:
1061
+ * ```typescript
1062
+ * const blink = createClient({
1063
+ * auth: { mode: 'headless', webBrowser: WebBrowser }
1064
+ * })
1065
+ * await blink.auth.signInWithGoogle() // Works on both platforms!
1066
+ * ```
1057
1067
  */
1058
1068
  signInWithGoogle(options?: AuthOptions): Promise<BlinkUser>;
1059
1069
  /**
1060
1070
  * Sign in with GitHub (headless mode)
1071
+ *
1072
+ * **Universal OAuth** - Works on both Web and React Native!
1073
+ * See signInWithGoogle() for setup instructions.
1061
1074
  */
1062
1075
  signInWithGitHub(options?: AuthOptions): Promise<BlinkUser>;
1063
1076
  /**
1064
1077
  * Sign in with Apple (headless mode)
1078
+ *
1079
+ * **Universal OAuth** - Works on both Web and React Native!
1080
+ * See signInWithGoogle() for setup instructions.
1065
1081
  */
1066
1082
  signInWithApple(options?: AuthOptions): Promise<BlinkUser>;
1067
1083
  /**
1068
1084
  * Sign in with Microsoft (headless mode)
1085
+ *
1086
+ * **Universal OAuth** - Works on both Web and React Native!
1087
+ * See signInWithGoogle() for setup instructions.
1069
1088
  */
1070
1089
  signInWithMicrosoft(options?: AuthOptions): Promise<BlinkUser>;
1071
1090
  /**
@@ -1151,56 +1170,36 @@ declare class BlinkAuth {
1151
1170
  authenticate: () => Promise<BlinkUser>;
1152
1171
  }>;
1153
1172
  /**
1154
- * Sign in with Google using expo-web-browser (React Native convenience)
1155
- */
1156
- signInWithGoogleMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1157
- authUrl: string;
1158
- authenticate: () => Promise<BlinkUser>;
1159
- }>;
1160
- /**
1161
- * Sign in with GitHub using expo-web-browser (React Native convenience)
1173
+ * Universal OAuth flow using session-based authentication (internal)
1174
+ * Works on ALL platforms: Web, iOS, Android
1175
+ * Uses expo-web-browser to open auth URL and polls for completion
1162
1176
  */
1163
- signInWithGitHubMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1164
- authUrl: string;
1165
- authenticate: () => Promise<BlinkUser>;
1166
- }>;
1167
- /**
1168
- * Sign in with Apple using expo-web-browser (React Native convenience)
1169
- */
1170
- signInWithAppleMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1171
- authUrl: string;
1172
- authenticate: () => Promise<BlinkUser>;
1173
- }>;
1174
- /**
1175
- * React Native OAuth flow using expo-web-browser (internal)
1176
- * Automatically handles opening browser and extracting tokens from redirect
1177
- */
1178
- private signInWithProviderReactNative;
1177
+ private signInWithProviderUniversal;
1179
1178
  /**
1180
1179
  * Generic provider sign-in method (headless mode)
1181
1180
  *
1182
- * Supports both web (popup) and React Native (deep link) OAuth flows.
1181
+ * **Universal OAuth** - Works seamlessly on both Web and React Native!
1183
1182
  *
1184
- * **React Native Setup:**
1185
- * 1. Configure deep linking in your app (app.json):
1186
- * ```json
1187
- * { "expo": { "scheme": "com.yourapp" } }
1188
- * ```
1183
+ * When `webBrowser` is configured in the client, this method automatically
1184
+ * uses the session-based OAuth flow that works on ALL platforms.
1189
1185
  *
1190
- * 2. Add redirect URL in Blink Dashboard:
1191
- * `com.yourapp://**`
1186
+ * **Universal Setup (configure once, works everywhere):**
1187
+ * ```typescript
1188
+ * import * as WebBrowser from 'expo-web-browser'
1189
+ * import AsyncStorage from '@react-native-async-storage/async-storage'
1192
1190
  *
1193
- * 3. Handle deep link callbacks:
1194
- * ```typescript
1195
- * import * as Linking from 'expo-linking'
1191
+ * const blink = createClient({
1192
+ * projectId: 'your-project',
1193
+ * auth: {
1194
+ * mode: 'headless',
1195
+ * webBrowser: WebBrowser // Pass the module here
1196
+ * },
1197
+ * storage: new AsyncStorageAdapter(AsyncStorage)
1198
+ * })
1196
1199
  *
1197
- * Linking.addEventListener('url', async ({ url }) => {
1198
- * const { queryParams } = Linking.parse(url)
1199
- * if (queryParams.access_token) {
1200
- * await blink.auth.setSession(queryParams)
1201
- * }
1202
- * })
1203
- * ```
1200
+ * // Now this works on ALL platforms - no platform checks needed!
1201
+ * const user = await blink.auth.signInWithGoogle()
1202
+ * ```
1204
1203
  *
1205
1204
  * @param provider - OAuth provider (google, github, apple, etc.)
1206
1205
  * @param options - Optional redirect URL and metadata
package/dist/index.js CHANGED
@@ -1238,10 +1238,7 @@ var BlinkAuth = class {
1238
1238
  setupParentWindowListener() {
1239
1239
  if (!isWeb || !this.isIframe || !hasWindow()) return;
1240
1240
  window.addEventListener("message", (event) => {
1241
- const origin = event.origin;
1242
- const isTrustedOrigin = origin === "https://blink.new" || origin === "http://localhost:3000" || origin === "http://localhost:3001" || origin.endsWith(".sites.blink.new") || // Trust all preview URLs
1243
- origin.endsWith(".preview-blink.com");
1244
- if (!isTrustedOrigin) {
1241
+ if (event.origin !== "https://blink.new" && event.origin !== "http://localhost:3000" && event.origin !== "http://localhost:3001") {
1245
1242
  return;
1246
1243
  }
1247
1244
  if (event.data?.type === "BLINK_AUTH_TOKENS") {
@@ -1617,6 +1614,16 @@ var BlinkAuth = class {
1617
1614
  }
1618
1615
  /**
1619
1616
  * Sign in with Google (headless mode)
1617
+ *
1618
+ * **Universal OAuth** - Works on both Web and React Native!
1619
+ *
1620
+ * On React Native, requires `webBrowser` to be configured in client:
1621
+ * ```typescript
1622
+ * const blink = createClient({
1623
+ * auth: { mode: 'headless', webBrowser: WebBrowser }
1624
+ * })
1625
+ * await blink.auth.signInWithGoogle() // Works on both platforms!
1626
+ * ```
1620
1627
  */
1621
1628
  async signInWithGoogle(options) {
1622
1629
  if (this.authConfig.mode !== "headless") {
@@ -1626,6 +1633,9 @@ var BlinkAuth = class {
1626
1633
  }
1627
1634
  /**
1628
1635
  * Sign in with GitHub (headless mode)
1636
+ *
1637
+ * **Universal OAuth** - Works on both Web and React Native!
1638
+ * See signInWithGoogle() for setup instructions.
1629
1639
  */
1630
1640
  async signInWithGitHub(options) {
1631
1641
  if (this.authConfig.mode !== "headless") {
@@ -1635,6 +1645,9 @@ var BlinkAuth = class {
1635
1645
  }
1636
1646
  /**
1637
1647
  * Sign in with Apple (headless mode)
1648
+ *
1649
+ * **Universal OAuth** - Works on both Web and React Native!
1650
+ * See signInWithGoogle() for setup instructions.
1638
1651
  */
1639
1652
  async signInWithApple(options) {
1640
1653
  if (this.authConfig.mode !== "headless") {
@@ -1644,6 +1657,9 @@ var BlinkAuth = class {
1644
1657
  }
1645
1658
  /**
1646
1659
  * Sign in with Microsoft (headless mode)
1660
+ *
1661
+ * **Universal OAuth** - Works on both Web and React Native!
1662
+ * See signInWithGoogle() for setup instructions.
1647
1663
  */
1648
1664
  async signInWithMicrosoft(options) {
1649
1665
  if (this.authConfig.mode !== "headless") {
@@ -1805,58 +1821,65 @@ var BlinkAuth = class {
1805
1821
  };
1806
1822
  }
1807
1823
  /**
1808
- * Sign in with Google using expo-web-browser (React Native convenience)
1809
- */
1810
- async signInWithGoogleMobile(options) {
1811
- return this.signInWithProviderMobile("google", options);
1812
- }
1813
- /**
1814
- * Sign in with GitHub using expo-web-browser (React Native convenience)
1815
- */
1816
- async signInWithGitHubMobile(options) {
1817
- return this.signInWithProviderMobile("github", options);
1818
- }
1819
- /**
1820
- * Sign in with Apple using expo-web-browser (React Native convenience)
1821
- */
1822
- async signInWithAppleMobile(options) {
1823
- return this.signInWithProviderMobile("apple", options);
1824
- }
1825
- /**
1826
- * React Native OAuth flow using expo-web-browser (internal)
1827
- * Automatically handles opening browser and extracting tokens from redirect
1824
+ * Universal OAuth flow using session-based authentication (internal)
1825
+ * Works on ALL platforms: Web, iOS, Android
1826
+ * Uses expo-web-browser to open auth URL and polls for completion
1828
1827
  */
1829
- async signInWithProviderReactNative(provider, options) {
1830
- throw new BlinkAuthError(
1831
- "NETWORK_ERROR" /* NETWORK_ERROR */,
1832
- 'React Native OAuth detected!\n\nPlease use the mobile-specific methods:\n\nimport * as WebBrowser from "expo-web-browser";\n\nconst { authUrl, authenticate } = await blink.auth.signInWithGoogleMobile();\nawait WebBrowser.openAuthSessionAsync(authUrl);\nconst user = await authenticate();\n\nOr update your useAuth hook to handle this automatically.'
1833
- );
1828
+ async signInWithProviderUniversal(provider, options) {
1829
+ const webBrowser = this.authConfig.webBrowser;
1830
+ if (!webBrowser) {
1831
+ throw new BlinkAuthError(
1832
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
1833
+ "webBrowser module is required for universal OAuth flow"
1834
+ );
1835
+ }
1836
+ const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
1837
+ console.log("\u{1F510} Opening OAuth browser for", provider);
1838
+ const result = await webBrowser.openAuthSessionAsync(authUrl);
1839
+ console.log("\u{1F510} Browser closed with result:", result.type);
1840
+ try {
1841
+ const user = await this.pollMobileOAuthSession(sessionId, {
1842
+ maxAttempts: 60,
1843
+ // 30 seconds (500ms intervals)
1844
+ intervalMs: 500
1845
+ });
1846
+ console.log("\u2705 OAuth completed successfully");
1847
+ return user;
1848
+ } catch (pollError) {
1849
+ if (result.type === "cancel" || result.type === "dismiss") {
1850
+ throw new BlinkAuthError(
1851
+ "POPUP_CANCELED" /* POPUP_CANCELED */,
1852
+ "Authentication was canceled"
1853
+ );
1854
+ }
1855
+ throw pollError;
1856
+ }
1834
1857
  }
1835
1858
  /**
1836
1859
  * Generic provider sign-in method (headless mode)
1837
1860
  *
1838
- * Supports both web (popup) and React Native (deep link) OAuth flows.
1861
+ * **Universal OAuth** - Works seamlessly on both Web and React Native!
1862
+ *
1863
+ * When `webBrowser` is configured in the client, this method automatically
1864
+ * uses the session-based OAuth flow that works on ALL platforms.
1839
1865
  *
1840
- * **React Native Setup:**
1841
- * 1. Configure deep linking in your app (app.json):
1842
- * ```json
1843
- * { "expo": { "scheme": "com.yourapp" } }
1844
- * ```
1866
+ * **Universal Setup (configure once, works everywhere):**
1867
+ * ```typescript
1868
+ * import * as WebBrowser from 'expo-web-browser'
1869
+ * import AsyncStorage from '@react-native-async-storage/async-storage'
1845
1870
  *
1846
- * 2. Add redirect URL in Blink Dashboard:
1847
- * `com.yourapp://**`
1871
+ * const blink = createClient({
1872
+ * projectId: 'your-project',
1873
+ * auth: {
1874
+ * mode: 'headless',
1875
+ * webBrowser: WebBrowser // Pass the module here
1876
+ * },
1877
+ * storage: new AsyncStorageAdapter(AsyncStorage)
1878
+ * })
1848
1879
  *
1849
- * 3. Handle deep link callbacks:
1850
- * ```typescript
1851
- * import * as Linking from 'expo-linking'
1852
- *
1853
- * Linking.addEventListener('url', async ({ url }) => {
1854
- * const { queryParams } = Linking.parse(url)
1855
- * if (queryParams.access_token) {
1856
- * await blink.auth.setSession(queryParams)
1857
- * }
1858
- * })
1859
- * ```
1880
+ * // Now this works on ALL platforms - no platform checks needed!
1881
+ * const user = await blink.auth.signInWithGoogle()
1882
+ * ```
1860
1883
  *
1861
1884
  * @param provider - OAuth provider (google, github, apple, etc.)
1862
1885
  * @param options - Optional redirect URL and metadata
@@ -1866,28 +1889,45 @@ var BlinkAuth = class {
1866
1889
  if (this.authConfig.mode !== "headless") {
1867
1890
  throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
1868
1891
  }
1892
+ if (this.authConfig.webBrowser) {
1893
+ return this.signInWithProviderUniversal(provider, options);
1894
+ }
1895
+ if (isReactNative2()) {
1896
+ throw new BlinkAuthError(
1897
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
1898
+ 'React Native OAuth requires webBrowser in config!\n\nimport * as WebBrowser from "expo-web-browser";\n\nconst blink = createClient({\n projectId: "your-project",\n auth: {\n mode: "headless",\n webBrowser: WebBrowser\n }\n})\n\nawait blink.auth.signInWithGoogle() // Works on all platforms!'
1899
+ );
1900
+ }
1869
1901
  if (!hasWindow()) {
1870
1902
  throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, "signInWithProvider requires a browser environment");
1871
1903
  }
1872
- if (isReactNative2()) {
1873
- return this.signInWithProviderReactNative(provider, options);
1904
+ const shouldPreferRedirect = isWeb && this.isIframe || typeof window !== "undefined" && window.crossOriginIsolated === true;
1905
+ const state = this.generateState();
1906
+ try {
1907
+ const sessionStorage = getSessionStorage();
1908
+ if (sessionStorage) {
1909
+ sessionStorage.setItem("blink_oauth_state", state);
1910
+ }
1911
+ } catch {
1912
+ }
1913
+ const redirectUrl = options?.redirectUrl || getLocationOrigin() || "";
1914
+ const buildAuthUrl = (mode) => {
1915
+ const url = new URL("/auth", this.authUrl);
1916
+ url.searchParams.set("provider", provider);
1917
+ url.searchParams.set("project_id", this.config.projectId);
1918
+ url.searchParams.set("state", state);
1919
+ url.searchParams.set("mode", mode);
1920
+ url.searchParams.set("redirect_url", redirectUrl);
1921
+ url.searchParams.set("opener_origin", getLocationOrigin() || "");
1922
+ return url;
1923
+ };
1924
+ if (shouldPreferRedirect) {
1925
+ window.location.href = buildAuthUrl("redirect").toString();
1926
+ return new Promise(() => {
1927
+ });
1874
1928
  }
1875
1929
  return new Promise((resolve, reject) => {
1876
- const state = this.generateState();
1877
- try {
1878
- const sessionStorage = getSessionStorage();
1879
- if (sessionStorage) {
1880
- sessionStorage.setItem("blink_oauth_state", state);
1881
- }
1882
- } catch {
1883
- }
1884
- const redirectUrl = options?.redirectUrl || getLocationOrigin() || "";
1885
- const popupUrl = new URL("/auth", this.authUrl);
1886
- popupUrl.searchParams.set("provider", provider);
1887
- popupUrl.searchParams.set("project_id", this.config.projectId);
1888
- popupUrl.searchParams.set("state", state);
1889
- popupUrl.searchParams.set("mode", "popup");
1890
- popupUrl.searchParams.set("redirect_url", redirectUrl);
1930
+ const popupUrl = buildAuthUrl("popup");
1891
1931
  const popup = window.open(
1892
1932
  popupUrl.toString(),
1893
1933
  "blink-auth",
@@ -1898,6 +1938,15 @@ var BlinkAuth = class {
1898
1938
  return;
1899
1939
  }
1900
1940
  let timeoutId;
1941
+ let closedIntervalId;
1942
+ let cleanedUp = false;
1943
+ const cleanup = () => {
1944
+ if (cleanedUp) return;
1945
+ cleanedUp = true;
1946
+ clearTimeout(timeoutId);
1947
+ if (closedIntervalId) clearInterval(closedIntervalId);
1948
+ window.removeEventListener("message", messageListener);
1949
+ };
1901
1950
  const messageListener = (event) => {
1902
1951
  let allowed = false;
1903
1952
  try {
@@ -1906,7 +1955,6 @@ var BlinkAuth = class {
1906
1955
  } catch {
1907
1956
  }
1908
1957
  if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
1909
- if (event.origin.endsWith(".sites.blink.new") || event.origin.endsWith(".preview-blink.com")) allowed = true;
1910
1958
  if (!allowed) return;
1911
1959
  if (event.data?.type === "BLINK_AUTH_TOKENS") {
1912
1960
  const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
@@ -1935,29 +1983,34 @@ var BlinkAuth = class {
1935
1983
  }, true).then(() => {
1936
1984
  resolve(this.authState.user);
1937
1985
  }).catch(reject);
1938
- clearTimeout(timeoutId);
1939
- window.removeEventListener("message", messageListener);
1986
+ cleanup();
1940
1987
  popup.close();
1941
1988
  } else if (event.data?.type === "BLINK_AUTH_ERROR") {
1942
1989
  const errorCode = this.mapErrorCodeFromResponse(event.data.code);
1943
1990
  reject(new BlinkAuthError(errorCode, event.data.message || "Authentication failed"));
1944
- clearTimeout(timeoutId);
1945
- window.removeEventListener("message", messageListener);
1991
+ cleanup();
1946
1992
  popup.close();
1947
1993
  }
1948
1994
  };
1995
+ if (popup.opener === null) {
1996
+ try {
1997
+ popup.close();
1998
+ } catch {
1999
+ }
2000
+ cleanup();
2001
+ window.location.href = buildAuthUrl("redirect").toString();
2002
+ return;
2003
+ }
1949
2004
  timeoutId = setTimeout(() => {
1950
- window.removeEventListener("message", messageListener);
2005
+ cleanup();
1951
2006
  if (!popup.closed) {
1952
2007
  popup.close();
1953
2008
  }
1954
2009
  reject(new BlinkAuthError("AUTH_TIMEOUT" /* AUTH_TIMEOUT */, "Authentication timed out"));
1955
2010
  }, 3e5);
1956
- const checkClosed = setInterval(() => {
2011
+ closedIntervalId = setInterval(() => {
1957
2012
  if (popup.closed) {
1958
- clearInterval(checkClosed);
1959
- clearTimeout(timeoutId);
1960
- window.removeEventListener("message", messageListener);
2013
+ cleanup();
1961
2014
  reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Authentication was canceled"));
1962
2015
  }
1963
2016
  }, 1e3);
package/dist/index.mjs CHANGED
@@ -1236,10 +1236,7 @@ var BlinkAuth = class {
1236
1236
  setupParentWindowListener() {
1237
1237
  if (!isWeb || !this.isIframe || !hasWindow()) return;
1238
1238
  window.addEventListener("message", (event) => {
1239
- const origin = event.origin;
1240
- const isTrustedOrigin = origin === "https://blink.new" || origin === "http://localhost:3000" || origin === "http://localhost:3001" || origin.endsWith(".sites.blink.new") || // Trust all preview URLs
1241
- origin.endsWith(".preview-blink.com");
1242
- if (!isTrustedOrigin) {
1239
+ if (event.origin !== "https://blink.new" && event.origin !== "http://localhost:3000" && event.origin !== "http://localhost:3001") {
1243
1240
  return;
1244
1241
  }
1245
1242
  if (event.data?.type === "BLINK_AUTH_TOKENS") {
@@ -1615,6 +1612,16 @@ var BlinkAuth = class {
1615
1612
  }
1616
1613
  /**
1617
1614
  * Sign in with Google (headless mode)
1615
+ *
1616
+ * **Universal OAuth** - Works on both Web and React Native!
1617
+ *
1618
+ * On React Native, requires `webBrowser` to be configured in client:
1619
+ * ```typescript
1620
+ * const blink = createClient({
1621
+ * auth: { mode: 'headless', webBrowser: WebBrowser }
1622
+ * })
1623
+ * await blink.auth.signInWithGoogle() // Works on both platforms!
1624
+ * ```
1618
1625
  */
1619
1626
  async signInWithGoogle(options) {
1620
1627
  if (this.authConfig.mode !== "headless") {
@@ -1624,6 +1631,9 @@ var BlinkAuth = class {
1624
1631
  }
1625
1632
  /**
1626
1633
  * Sign in with GitHub (headless mode)
1634
+ *
1635
+ * **Universal OAuth** - Works on both Web and React Native!
1636
+ * See signInWithGoogle() for setup instructions.
1627
1637
  */
1628
1638
  async signInWithGitHub(options) {
1629
1639
  if (this.authConfig.mode !== "headless") {
@@ -1633,6 +1643,9 @@ var BlinkAuth = class {
1633
1643
  }
1634
1644
  /**
1635
1645
  * Sign in with Apple (headless mode)
1646
+ *
1647
+ * **Universal OAuth** - Works on both Web and React Native!
1648
+ * See signInWithGoogle() for setup instructions.
1636
1649
  */
1637
1650
  async signInWithApple(options) {
1638
1651
  if (this.authConfig.mode !== "headless") {
@@ -1642,6 +1655,9 @@ var BlinkAuth = class {
1642
1655
  }
1643
1656
  /**
1644
1657
  * Sign in with Microsoft (headless mode)
1658
+ *
1659
+ * **Universal OAuth** - Works on both Web and React Native!
1660
+ * See signInWithGoogle() for setup instructions.
1645
1661
  */
1646
1662
  async signInWithMicrosoft(options) {
1647
1663
  if (this.authConfig.mode !== "headless") {
@@ -1803,58 +1819,65 @@ var BlinkAuth = class {
1803
1819
  };
1804
1820
  }
1805
1821
  /**
1806
- * Sign in with Google using expo-web-browser (React Native convenience)
1807
- */
1808
- async signInWithGoogleMobile(options) {
1809
- return this.signInWithProviderMobile("google", options);
1810
- }
1811
- /**
1812
- * Sign in with GitHub using expo-web-browser (React Native convenience)
1813
- */
1814
- async signInWithGitHubMobile(options) {
1815
- return this.signInWithProviderMobile("github", options);
1816
- }
1817
- /**
1818
- * Sign in with Apple using expo-web-browser (React Native convenience)
1819
- */
1820
- async signInWithAppleMobile(options) {
1821
- return this.signInWithProviderMobile("apple", options);
1822
- }
1823
- /**
1824
- * React Native OAuth flow using expo-web-browser (internal)
1825
- * Automatically handles opening browser and extracting tokens from redirect
1822
+ * Universal OAuth flow using session-based authentication (internal)
1823
+ * Works on ALL platforms: Web, iOS, Android
1824
+ * Uses expo-web-browser to open auth URL and polls for completion
1826
1825
  */
1827
- async signInWithProviderReactNative(provider, options) {
1828
- throw new BlinkAuthError(
1829
- "NETWORK_ERROR" /* NETWORK_ERROR */,
1830
- 'React Native OAuth detected!\n\nPlease use the mobile-specific methods:\n\nimport * as WebBrowser from "expo-web-browser";\n\nconst { authUrl, authenticate } = await blink.auth.signInWithGoogleMobile();\nawait WebBrowser.openAuthSessionAsync(authUrl);\nconst user = await authenticate();\n\nOr update your useAuth hook to handle this automatically.'
1831
- );
1826
+ async signInWithProviderUniversal(provider, options) {
1827
+ const webBrowser = this.authConfig.webBrowser;
1828
+ if (!webBrowser) {
1829
+ throw new BlinkAuthError(
1830
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
1831
+ "webBrowser module is required for universal OAuth flow"
1832
+ );
1833
+ }
1834
+ const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
1835
+ console.log("\u{1F510} Opening OAuth browser for", provider);
1836
+ const result = await webBrowser.openAuthSessionAsync(authUrl);
1837
+ console.log("\u{1F510} Browser closed with result:", result.type);
1838
+ try {
1839
+ const user = await this.pollMobileOAuthSession(sessionId, {
1840
+ maxAttempts: 60,
1841
+ // 30 seconds (500ms intervals)
1842
+ intervalMs: 500
1843
+ });
1844
+ console.log("\u2705 OAuth completed successfully");
1845
+ return user;
1846
+ } catch (pollError) {
1847
+ if (result.type === "cancel" || result.type === "dismiss") {
1848
+ throw new BlinkAuthError(
1849
+ "POPUP_CANCELED" /* POPUP_CANCELED */,
1850
+ "Authentication was canceled"
1851
+ );
1852
+ }
1853
+ throw pollError;
1854
+ }
1832
1855
  }
1833
1856
  /**
1834
1857
  * Generic provider sign-in method (headless mode)
1835
1858
  *
1836
- * Supports both web (popup) and React Native (deep link) OAuth flows.
1859
+ * **Universal OAuth** - Works seamlessly on both Web and React Native!
1860
+ *
1861
+ * When `webBrowser` is configured in the client, this method automatically
1862
+ * uses the session-based OAuth flow that works on ALL platforms.
1837
1863
  *
1838
- * **React Native Setup:**
1839
- * 1. Configure deep linking in your app (app.json):
1840
- * ```json
1841
- * { "expo": { "scheme": "com.yourapp" } }
1842
- * ```
1864
+ * **Universal Setup (configure once, works everywhere):**
1865
+ * ```typescript
1866
+ * import * as WebBrowser from 'expo-web-browser'
1867
+ * import AsyncStorage from '@react-native-async-storage/async-storage'
1843
1868
  *
1844
- * 2. Add redirect URL in Blink Dashboard:
1845
- * `com.yourapp://**`
1869
+ * const blink = createClient({
1870
+ * projectId: 'your-project',
1871
+ * auth: {
1872
+ * mode: 'headless',
1873
+ * webBrowser: WebBrowser // Pass the module here
1874
+ * },
1875
+ * storage: new AsyncStorageAdapter(AsyncStorage)
1876
+ * })
1846
1877
  *
1847
- * 3. Handle deep link callbacks:
1848
- * ```typescript
1849
- * import * as Linking from 'expo-linking'
1850
- *
1851
- * Linking.addEventListener('url', async ({ url }) => {
1852
- * const { queryParams } = Linking.parse(url)
1853
- * if (queryParams.access_token) {
1854
- * await blink.auth.setSession(queryParams)
1855
- * }
1856
- * })
1857
- * ```
1878
+ * // Now this works on ALL platforms - no platform checks needed!
1879
+ * const user = await blink.auth.signInWithGoogle()
1880
+ * ```
1858
1881
  *
1859
1882
  * @param provider - OAuth provider (google, github, apple, etc.)
1860
1883
  * @param options - Optional redirect URL and metadata
@@ -1864,28 +1887,45 @@ var BlinkAuth = class {
1864
1887
  if (this.authConfig.mode !== "headless") {
1865
1888
  throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
1866
1889
  }
1890
+ if (this.authConfig.webBrowser) {
1891
+ return this.signInWithProviderUniversal(provider, options);
1892
+ }
1893
+ if (isReactNative2()) {
1894
+ throw new BlinkAuthError(
1895
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
1896
+ 'React Native OAuth requires webBrowser in config!\n\nimport * as WebBrowser from "expo-web-browser";\n\nconst blink = createClient({\n projectId: "your-project",\n auth: {\n mode: "headless",\n webBrowser: WebBrowser\n }\n})\n\nawait blink.auth.signInWithGoogle() // Works on all platforms!'
1897
+ );
1898
+ }
1867
1899
  if (!hasWindow()) {
1868
1900
  throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, "signInWithProvider requires a browser environment");
1869
1901
  }
1870
- if (isReactNative2()) {
1871
- return this.signInWithProviderReactNative(provider, options);
1902
+ const shouldPreferRedirect = isWeb && this.isIframe || typeof window !== "undefined" && window.crossOriginIsolated === true;
1903
+ const state = this.generateState();
1904
+ try {
1905
+ const sessionStorage = getSessionStorage();
1906
+ if (sessionStorage) {
1907
+ sessionStorage.setItem("blink_oauth_state", state);
1908
+ }
1909
+ } catch {
1910
+ }
1911
+ const redirectUrl = options?.redirectUrl || getLocationOrigin() || "";
1912
+ const buildAuthUrl = (mode) => {
1913
+ const url = new URL("/auth", this.authUrl);
1914
+ url.searchParams.set("provider", provider);
1915
+ url.searchParams.set("project_id", this.config.projectId);
1916
+ url.searchParams.set("state", state);
1917
+ url.searchParams.set("mode", mode);
1918
+ url.searchParams.set("redirect_url", redirectUrl);
1919
+ url.searchParams.set("opener_origin", getLocationOrigin() || "");
1920
+ return url;
1921
+ };
1922
+ if (shouldPreferRedirect) {
1923
+ window.location.href = buildAuthUrl("redirect").toString();
1924
+ return new Promise(() => {
1925
+ });
1872
1926
  }
1873
1927
  return new Promise((resolve, reject) => {
1874
- const state = this.generateState();
1875
- try {
1876
- const sessionStorage = getSessionStorage();
1877
- if (sessionStorage) {
1878
- sessionStorage.setItem("blink_oauth_state", state);
1879
- }
1880
- } catch {
1881
- }
1882
- const redirectUrl = options?.redirectUrl || getLocationOrigin() || "";
1883
- const popupUrl = new URL("/auth", this.authUrl);
1884
- popupUrl.searchParams.set("provider", provider);
1885
- popupUrl.searchParams.set("project_id", this.config.projectId);
1886
- popupUrl.searchParams.set("state", state);
1887
- popupUrl.searchParams.set("mode", "popup");
1888
- popupUrl.searchParams.set("redirect_url", redirectUrl);
1928
+ const popupUrl = buildAuthUrl("popup");
1889
1929
  const popup = window.open(
1890
1930
  popupUrl.toString(),
1891
1931
  "blink-auth",
@@ -1896,6 +1936,15 @@ var BlinkAuth = class {
1896
1936
  return;
1897
1937
  }
1898
1938
  let timeoutId;
1939
+ let closedIntervalId;
1940
+ let cleanedUp = false;
1941
+ const cleanup = () => {
1942
+ if (cleanedUp) return;
1943
+ cleanedUp = true;
1944
+ clearTimeout(timeoutId);
1945
+ if (closedIntervalId) clearInterval(closedIntervalId);
1946
+ window.removeEventListener("message", messageListener);
1947
+ };
1899
1948
  const messageListener = (event) => {
1900
1949
  let allowed = false;
1901
1950
  try {
@@ -1904,7 +1953,6 @@ var BlinkAuth = class {
1904
1953
  } catch {
1905
1954
  }
1906
1955
  if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
1907
- if (event.origin.endsWith(".sites.blink.new") || event.origin.endsWith(".preview-blink.com")) allowed = true;
1908
1956
  if (!allowed) return;
1909
1957
  if (event.data?.type === "BLINK_AUTH_TOKENS") {
1910
1958
  const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
@@ -1933,29 +1981,34 @@ var BlinkAuth = class {
1933
1981
  }, true).then(() => {
1934
1982
  resolve(this.authState.user);
1935
1983
  }).catch(reject);
1936
- clearTimeout(timeoutId);
1937
- window.removeEventListener("message", messageListener);
1984
+ cleanup();
1938
1985
  popup.close();
1939
1986
  } else if (event.data?.type === "BLINK_AUTH_ERROR") {
1940
1987
  const errorCode = this.mapErrorCodeFromResponse(event.data.code);
1941
1988
  reject(new BlinkAuthError(errorCode, event.data.message || "Authentication failed"));
1942
- clearTimeout(timeoutId);
1943
- window.removeEventListener("message", messageListener);
1989
+ cleanup();
1944
1990
  popup.close();
1945
1991
  }
1946
1992
  };
1993
+ if (popup.opener === null) {
1994
+ try {
1995
+ popup.close();
1996
+ } catch {
1997
+ }
1998
+ cleanup();
1999
+ window.location.href = buildAuthUrl("redirect").toString();
2000
+ return;
2001
+ }
1947
2002
  timeoutId = setTimeout(() => {
1948
- window.removeEventListener("message", messageListener);
2003
+ cleanup();
1949
2004
  if (!popup.closed) {
1950
2005
  popup.close();
1951
2006
  }
1952
2007
  reject(new BlinkAuthError("AUTH_TIMEOUT" /* AUTH_TIMEOUT */, "Authentication timed out"));
1953
2008
  }, 3e5);
1954
- const checkClosed = setInterval(() => {
2009
+ closedIntervalId = setInterval(() => {
1955
2010
  if (popup.closed) {
1956
- clearInterval(checkClosed);
1957
- clearTimeout(timeoutId);
1958
- window.removeEventListener("message", messageListener);
2011
+ cleanup();
1959
2012
  reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Authentication was canceled"));
1960
2013
  }
1961
2014
  }, 1e3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.19.6",
3
+ "version": "2.0.1",
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",