@blinkdotnew/sdk 0.19.4 → 0.19.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -16
- package/dist/index.d.mts +231 -15
- package/dist/index.d.ts +231 -15
- package/dist/index.js +495 -244
- package/dist/index.mjs +495 -244
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1020,6 +1020,142 @@ var HttpClient = class {
|
|
|
1020
1020
|
}
|
|
1021
1021
|
};
|
|
1022
1022
|
|
|
1023
|
+
// src/utils/browser-env.ts
|
|
1024
|
+
function hasWindow() {
|
|
1025
|
+
return typeof window !== "undefined";
|
|
1026
|
+
}
|
|
1027
|
+
function hasWindowLocation() {
|
|
1028
|
+
return typeof window !== "undefined" && typeof window.location !== "undefined";
|
|
1029
|
+
}
|
|
1030
|
+
function hasDocument() {
|
|
1031
|
+
return typeof document !== "undefined";
|
|
1032
|
+
}
|
|
1033
|
+
function isReactNative2() {
|
|
1034
|
+
return typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
1035
|
+
}
|
|
1036
|
+
function getWindowLocation() {
|
|
1037
|
+
if (!hasWindow()) return null;
|
|
1038
|
+
try {
|
|
1039
|
+
return window.location;
|
|
1040
|
+
} catch {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function getLocationHref() {
|
|
1045
|
+
const loc = getWindowLocation();
|
|
1046
|
+
if (!loc) return null;
|
|
1047
|
+
try {
|
|
1048
|
+
return loc.href;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function getLocationOrigin() {
|
|
1054
|
+
const loc = getWindowLocation();
|
|
1055
|
+
if (!loc) return null;
|
|
1056
|
+
try {
|
|
1057
|
+
return loc.origin;
|
|
1058
|
+
} catch {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function getLocationHostname() {
|
|
1063
|
+
const loc = getWindowLocation();
|
|
1064
|
+
if (!loc) return null;
|
|
1065
|
+
try {
|
|
1066
|
+
return loc.hostname;
|
|
1067
|
+
} catch {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function getLocationPathname() {
|
|
1072
|
+
const loc = getWindowLocation();
|
|
1073
|
+
if (!loc) return null;
|
|
1074
|
+
try {
|
|
1075
|
+
return loc.pathname;
|
|
1076
|
+
} catch {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
function getLocationSearch() {
|
|
1081
|
+
const loc = getWindowLocation();
|
|
1082
|
+
if (!loc) return null;
|
|
1083
|
+
try {
|
|
1084
|
+
return loc.search;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function getLocationHash() {
|
|
1090
|
+
const loc = getWindowLocation();
|
|
1091
|
+
if (!loc) return null;
|
|
1092
|
+
try {
|
|
1093
|
+
return loc.hash;
|
|
1094
|
+
} catch {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function getLocationProtocol() {
|
|
1099
|
+
const loc = getWindowLocation();
|
|
1100
|
+
if (!loc) return null;
|
|
1101
|
+
try {
|
|
1102
|
+
return loc.protocol;
|
|
1103
|
+
} catch {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
function getLocationHost() {
|
|
1108
|
+
const loc = getWindowLocation();
|
|
1109
|
+
if (!loc) return null;
|
|
1110
|
+
try {
|
|
1111
|
+
return loc.host;
|
|
1112
|
+
} catch {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function constructFullUrl() {
|
|
1117
|
+
if (!hasWindow()) return null;
|
|
1118
|
+
const protocol = getLocationProtocol();
|
|
1119
|
+
const host = getLocationHost();
|
|
1120
|
+
const pathname = getLocationPathname();
|
|
1121
|
+
const search = getLocationSearch();
|
|
1122
|
+
const hash = getLocationHash();
|
|
1123
|
+
if (!protocol || !host) return null;
|
|
1124
|
+
return `${protocol}//${host}${pathname || ""}${search || ""}${hash || ""}`;
|
|
1125
|
+
}
|
|
1126
|
+
function getDocumentReferrer() {
|
|
1127
|
+
if (!hasDocument()) return null;
|
|
1128
|
+
try {
|
|
1129
|
+
return document.referrer || null;
|
|
1130
|
+
} catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function getWindowInnerWidth() {
|
|
1135
|
+
if (!hasWindow()) return null;
|
|
1136
|
+
try {
|
|
1137
|
+
return window.innerWidth;
|
|
1138
|
+
} catch {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
function isIframe() {
|
|
1143
|
+
if (!hasWindow()) return false;
|
|
1144
|
+
try {
|
|
1145
|
+
return window.self !== window.top;
|
|
1146
|
+
} catch {
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function getSessionStorage() {
|
|
1151
|
+
if (!hasWindow()) return null;
|
|
1152
|
+
try {
|
|
1153
|
+
return window.sessionStorage;
|
|
1154
|
+
} catch {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1023
1159
|
// src/auth.ts
|
|
1024
1160
|
var BlinkAuth = class {
|
|
1025
1161
|
config;
|
|
@@ -1041,13 +1177,16 @@ var BlinkAuth = class {
|
|
|
1041
1177
|
this.authConfig = {
|
|
1042
1178
|
mode: "managed",
|
|
1043
1179
|
// Default mode
|
|
1044
|
-
authUrl: "
|
|
1180
|
+
authUrl: "http://localhost:3000",
|
|
1045
1181
|
coreUrl: "https://core.blink.new",
|
|
1182
|
+
detectSessionInUrl: true,
|
|
1183
|
+
// Default to true for web compatibility
|
|
1046
1184
|
...config.auth
|
|
1047
1185
|
};
|
|
1048
1186
|
this.authUrl = this.authConfig.authUrl || "https://blink.new";
|
|
1049
1187
|
this.coreUrl = this.authConfig.coreUrl || "https://core.blink.new";
|
|
1050
|
-
|
|
1188
|
+
const hostname = getLocationHostname();
|
|
1189
|
+
if (hostname && this.authUrl === "https://blink.new" && (hostname === "localhost" || hostname === "127.0.0.1")) {
|
|
1051
1190
|
console.warn("\u26A0\uFE0F Using default authUrl in development. Set auth.authUrl to your app origin for headless auth endpoints to work.");
|
|
1052
1191
|
}
|
|
1053
1192
|
if (config.authRequired !== void 0 && !config.auth?.mode) {
|
|
@@ -1061,7 +1200,7 @@ var BlinkAuth = class {
|
|
|
1061
1200
|
};
|
|
1062
1201
|
this.storage = config.auth?.storage || config.storage || getDefaultStorageAdapter();
|
|
1063
1202
|
if (isWeb) {
|
|
1064
|
-
this.isIframe =
|
|
1203
|
+
this.isIframe = isIframe();
|
|
1065
1204
|
this.setupParentWindowListener();
|
|
1066
1205
|
this.setupCrossTabSync();
|
|
1067
1206
|
this.initializationPromise = this.initialize();
|
|
@@ -1097,7 +1236,7 @@ var BlinkAuth = class {
|
|
|
1097
1236
|
* Setup listener for tokens from parent window
|
|
1098
1237
|
*/
|
|
1099
1238
|
setupParentWindowListener() {
|
|
1100
|
-
if (!isWeb || !this.isIframe) return;
|
|
1239
|
+
if (!isWeb || !this.isIframe || !hasWindow()) return;
|
|
1101
1240
|
window.addEventListener("message", (event) => {
|
|
1102
1241
|
if (event.origin !== "https://blink.new" && event.origin !== "http://localhost:3000" && event.origin !== "http://localhost:3001") {
|
|
1103
1242
|
return;
|
|
@@ -1119,7 +1258,7 @@ var BlinkAuth = class {
|
|
|
1119
1258
|
this.clearTokens();
|
|
1120
1259
|
}
|
|
1121
1260
|
});
|
|
1122
|
-
if (window.parent !== window) {
|
|
1261
|
+
if (hasWindow() && window.parent !== window) {
|
|
1123
1262
|
console.log("\u{1F504} Requesting auth tokens from parent window");
|
|
1124
1263
|
window.parent.postMessage({
|
|
1125
1264
|
type: "BLINK_REQUEST_AUTH_TOKENS",
|
|
@@ -1144,67 +1283,14 @@ var BlinkAuth = class {
|
|
|
1144
1283
|
return;
|
|
1145
1284
|
}
|
|
1146
1285
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
-
}
|
|
1177
|
-
const tokensFromUrl = this.extractTokensFromUrl();
|
|
1178
|
-
if (tokensFromUrl) {
|
|
1179
|
-
console.log("\u{1F4E5} Found tokens in URL, setting them...");
|
|
1180
|
-
await this.setTokens(tokensFromUrl, true);
|
|
1181
|
-
this.clearUrlTokens();
|
|
1182
|
-
console.log("\u2705 Auth initialization complete (from URL)");
|
|
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
|
-
}
|
|
1286
|
+
if (this.authConfig.detectSessionInUrl !== false) {
|
|
1287
|
+
const tokensFromUrl = this.extractTokensFromUrl();
|
|
1288
|
+
if (tokensFromUrl) {
|
|
1289
|
+
console.log("\u{1F4E5} Found tokens in URL, setting them...");
|
|
1290
|
+
await this.setTokens(tokensFromUrl, true);
|
|
1291
|
+
this.clearUrlTokens();
|
|
1292
|
+
console.log("\u2705 Auth initialization complete (from URL)");
|
|
1293
|
+
return;
|
|
1208
1294
|
}
|
|
1209
1295
|
}
|
|
1210
1296
|
const storedTokens = await this.getStoredTokens();
|
|
@@ -1229,11 +1315,11 @@ var BlinkAuth = class {
|
|
|
1229
1315
|
}
|
|
1230
1316
|
}
|
|
1231
1317
|
console.log("\u274C No tokens found");
|
|
1232
|
-
if (this.config.authRequired) {
|
|
1318
|
+
if (this.config.authRequired && hasWindowLocation()) {
|
|
1233
1319
|
console.log("\u{1F504} Auth required, redirecting to auth page...");
|
|
1234
1320
|
this.redirectToAuth();
|
|
1235
1321
|
} else {
|
|
1236
|
-
console.log("\u26A0\uFE0F Auth not required, continuing without authentication");
|
|
1322
|
+
console.log("\u26A0\uFE0F Auth not required or no window.location, continuing without authentication");
|
|
1237
1323
|
}
|
|
1238
1324
|
} finally {
|
|
1239
1325
|
this.setLoading(false);
|
|
@@ -1244,15 +1330,20 @@ var BlinkAuth = class {
|
|
|
1244
1330
|
* Redirect to Blink auth page
|
|
1245
1331
|
*/
|
|
1246
1332
|
login(nextUrl) {
|
|
1333
|
+
if (!hasWindowLocation()) {
|
|
1334
|
+
console.warn("login() called in non-browser environment (no window.location available)");
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1247
1337
|
let redirectUrl = nextUrl || this.authConfig.redirectUrl;
|
|
1248
|
-
if (!redirectUrl
|
|
1249
|
-
|
|
1250
|
-
|
|
1338
|
+
if (!redirectUrl) {
|
|
1339
|
+
const href = getLocationHref();
|
|
1340
|
+
if (href && href.startsWith("http")) {
|
|
1341
|
+
redirectUrl = href;
|
|
1251
1342
|
} else {
|
|
1252
|
-
redirectUrl =
|
|
1343
|
+
redirectUrl = constructFullUrl() || void 0;
|
|
1253
1344
|
}
|
|
1254
1345
|
}
|
|
1255
|
-
if (redirectUrl
|
|
1346
|
+
if (redirectUrl) {
|
|
1256
1347
|
try {
|
|
1257
1348
|
const url = new URL(redirectUrl);
|
|
1258
1349
|
url.searchParams.delete("redirect_url");
|
|
@@ -1267,16 +1358,14 @@ var BlinkAuth = class {
|
|
|
1267
1358
|
if (this.config.projectId) {
|
|
1268
1359
|
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1269
1360
|
}
|
|
1270
|
-
|
|
1271
|
-
window.location.href = authUrl.toString();
|
|
1272
|
-
}
|
|
1361
|
+
window.location.href = authUrl.toString();
|
|
1273
1362
|
}
|
|
1274
1363
|
/**
|
|
1275
1364
|
* Logout and clear stored tokens
|
|
1276
1365
|
*/
|
|
1277
1366
|
logout(redirectUrl) {
|
|
1278
1367
|
this.clearTokens();
|
|
1279
|
-
if (redirectUrl &&
|
|
1368
|
+
if (redirectUrl && hasWindowLocation()) {
|
|
1280
1369
|
window.location.href = redirectUrl;
|
|
1281
1370
|
}
|
|
1282
1371
|
}
|
|
@@ -1525,6 +1614,16 @@ var BlinkAuth = class {
|
|
|
1525
1614
|
}
|
|
1526
1615
|
/**
|
|
1527
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
|
+
* ```
|
|
1528
1627
|
*/
|
|
1529
1628
|
async signInWithGoogle(options) {
|
|
1530
1629
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1534,6 +1633,9 @@ var BlinkAuth = class {
|
|
|
1534
1633
|
}
|
|
1535
1634
|
/**
|
|
1536
1635
|
* Sign in with GitHub (headless mode)
|
|
1636
|
+
*
|
|
1637
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1638
|
+
* See signInWithGoogle() for setup instructions.
|
|
1537
1639
|
*/
|
|
1538
1640
|
async signInWithGitHub(options) {
|
|
1539
1641
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1543,6 +1645,9 @@ var BlinkAuth = class {
|
|
|
1543
1645
|
}
|
|
1544
1646
|
/**
|
|
1545
1647
|
* Sign in with Apple (headless mode)
|
|
1648
|
+
*
|
|
1649
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1650
|
+
* See signInWithGoogle() for setup instructions.
|
|
1546
1651
|
*/
|
|
1547
1652
|
async signInWithApple(options) {
|
|
1548
1653
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1552,6 +1657,9 @@ var BlinkAuth = class {
|
|
|
1552
1657
|
}
|
|
1553
1658
|
/**
|
|
1554
1659
|
* Sign in with Microsoft (headless mode)
|
|
1660
|
+
*
|
|
1661
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1662
|
+
* See signInWithGoogle() for setup instructions.
|
|
1555
1663
|
*/
|
|
1556
1664
|
async signInWithMicrosoft(options) {
|
|
1557
1665
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1560,144 +1668,264 @@ var BlinkAuth = class {
|
|
|
1560
1668
|
return this.signInWithProvider("microsoft", options);
|
|
1561
1669
|
}
|
|
1562
1670
|
/**
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1671
|
+
* Initiate OAuth for mobile without deep linking (expo-web-browser pattern)
|
|
1672
|
+
*
|
|
1673
|
+
* This method:
|
|
1674
|
+
* 1. Generates a unique session ID
|
|
1675
|
+
* 2. Returns OAuth URL with session parameter
|
|
1676
|
+
* 3. App opens URL in expo-web-browser
|
|
1677
|
+
* 4. App polls checkMobileOAuthSession() until complete
|
|
1678
|
+
*
|
|
1679
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1680
|
+
* @param options - Optional metadata
|
|
1681
|
+
* @returns Session ID and OAuth URL
|
|
1682
|
+
*
|
|
1683
|
+
* @example
|
|
1684
|
+
* // React Native with expo-web-browser
|
|
1685
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1686
|
+
*
|
|
1687
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1688
|
+
*
|
|
1689
|
+
* // Open browser
|
|
1690
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1691
|
+
*
|
|
1692
|
+
* // Poll for completion
|
|
1693
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId);
|
|
1694
|
+
* console.log('Authenticated:', user.email);
|
|
1695
|
+
*/
|
|
1696
|
+
async initiateMobileOAuth(provider, options) {
|
|
1697
|
+
if (this.authConfig.mode !== "headless") {
|
|
1698
|
+
throw new BlinkAuthError(
|
|
1699
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
1700
|
+
"initiateMobileOAuth is only available in headless mode"
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
const sessionId = this.generateSessionId();
|
|
1704
|
+
const authUrl = new URL("/auth", this.authUrl);
|
|
1705
|
+
authUrl.searchParams.set("provider", provider);
|
|
1706
|
+
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1707
|
+
authUrl.searchParams.set("mode", "mobile-session");
|
|
1708
|
+
authUrl.searchParams.set("session_id", sessionId);
|
|
1709
|
+
if (options?.metadata) {
|
|
1710
|
+
authUrl.searchParams.set("metadata", JSON.stringify(options.metadata));
|
|
1711
|
+
}
|
|
1712
|
+
return {
|
|
1713
|
+
sessionId,
|
|
1714
|
+
authUrl: authUrl.toString()
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Check mobile OAuth session status (single check)
|
|
1719
|
+
*
|
|
1720
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1721
|
+
* @returns Tokens if session is complete, null if still pending
|
|
1565
1722
|
*/
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
|
1723
|
+
async checkMobileOAuthSession(sessionId) {
|
|
1724
|
+
try {
|
|
1725
|
+
const response = await fetch(`${this.authUrl}/api/auth/mobile-session/${sessionId}`, {
|
|
1726
|
+
method: "GET",
|
|
1727
|
+
headers: {
|
|
1728
|
+
"Content-Type": "application/json"
|
|
1729
|
+
}
|
|
1584
1730
|
});
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1731
|
+
if (response.status === 404 || response.status === 202) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
if (!response.ok) {
|
|
1735
|
+
const errorData = await response.json();
|
|
1736
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1737
|
+
throw new BlinkAuthError(
|
|
1738
|
+
errorCode,
|
|
1739
|
+
errorData.error || "Failed to check OAuth session"
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
const data = await response.json();
|
|
1743
|
+
return {
|
|
1744
|
+
access_token: data.access_token,
|
|
1745
|
+
refresh_token: data.refresh_token,
|
|
1746
|
+
token_type: data.token_type || "Bearer",
|
|
1747
|
+
expires_in: data.expires_in || 3600,
|
|
1748
|
+
refresh_expires_in: data.refresh_expires_in
|
|
1749
|
+
};
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
if (error instanceof BlinkAuthError) {
|
|
1752
|
+
throw error;
|
|
1753
|
+
}
|
|
1754
|
+
throw new BlinkAuthError(
|
|
1755
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1756
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Poll mobile OAuth session until complete (convenience method)
|
|
1762
|
+
*
|
|
1763
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1764
|
+
* @param options - Polling options
|
|
1765
|
+
* @returns Authenticated user
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1769
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1770
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId, {
|
|
1771
|
+
* maxAttempts: 60,
|
|
1772
|
+
* intervalMs: 1000
|
|
1773
|
+
* });
|
|
1774
|
+
*/
|
|
1775
|
+
async pollMobileOAuthSession(sessionId, options) {
|
|
1776
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
1777
|
+
const intervalMs = options?.intervalMs || 1e3;
|
|
1778
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1779
|
+
const tokens = await this.checkMobileOAuthSession(sessionId);
|
|
1780
|
+
if (tokens) {
|
|
1781
|
+
await this.setTokens(tokens, true);
|
|
1782
|
+
return this.authState.user;
|
|
1783
|
+
}
|
|
1784
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1785
|
+
}
|
|
1786
|
+
throw new BlinkAuthError(
|
|
1787
|
+
"AUTH_TIMEOUT" /* AUTH_TIMEOUT */,
|
|
1788
|
+
"Mobile OAuth session timed out"
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Sign in with OAuth provider using expo-web-browser (React Native)
|
|
1793
|
+
*
|
|
1794
|
+
* This is a convenience method that handles the entire flow:
|
|
1795
|
+
* 1. Initiates mobile OAuth session
|
|
1796
|
+
* 2. Returns auth URL to open in WebBrowser
|
|
1797
|
+
* 3. Provides polling function to call after browser opens
|
|
1798
|
+
*
|
|
1799
|
+
* @param provider - OAuth provider
|
|
1800
|
+
* @returns Object with authUrl and authenticate function
|
|
1801
|
+
*
|
|
1802
|
+
* @example
|
|
1803
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1804
|
+
*
|
|
1805
|
+
* const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google');
|
|
1806
|
+
*
|
|
1807
|
+
* // Open browser
|
|
1808
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1809
|
+
*
|
|
1810
|
+
* // Wait for authentication
|
|
1811
|
+
* const user = await authenticate();
|
|
1812
|
+
*/
|
|
1813
|
+
async signInWithProviderMobile(provider, options) {
|
|
1814
|
+
const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
|
|
1815
|
+
return {
|
|
1816
|
+
authUrl,
|
|
1817
|
+
authenticate: () => this.pollMobileOAuthSession(sessionId, {
|
|
1818
|
+
maxAttempts: 60,
|
|
1819
|
+
intervalMs: 1e3
|
|
1820
|
+
})
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
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
|
|
1827
|
+
*/
|
|
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
|
|
1594
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;
|
|
1595
1856
|
}
|
|
1596
|
-
return isSafari;
|
|
1597
1857
|
}
|
|
1598
1858
|
/**
|
|
1599
1859
|
* Generic provider sign-in method (headless mode)
|
|
1600
|
-
*
|
|
1860
|
+
*
|
|
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.
|
|
1865
|
+
*
|
|
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'
|
|
1870
|
+
*
|
|
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
|
+
* })
|
|
1879
|
+
*
|
|
1880
|
+
* // Now this works on ALL platforms - no platform checks needed!
|
|
1881
|
+
* const user = await blink.auth.signInWithGoogle()
|
|
1882
|
+
* ```
|
|
1883
|
+
*
|
|
1884
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1885
|
+
* @param options - Optional redirect URL and metadata
|
|
1886
|
+
* @returns Promise that resolves with authenticated user
|
|
1601
1887
|
*/
|
|
1602
1888
|
async signInWithProvider(provider, options) {
|
|
1603
1889
|
if (this.authConfig.mode !== "headless") {
|
|
1604
1890
|
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
|
|
1605
1891
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
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}`;
|
|
1613
|
-
}
|
|
1892
|
+
if (this.authConfig.webBrowser) {
|
|
1893
|
+
return this.signInWithProviderUniversal(provider, options);
|
|
1614
1894
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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);
|
|
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
|
+
);
|
|
1626
1900
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
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
|
-
});
|
|
1901
|
+
if (!hasWindow()) {
|
|
1902
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, "signInWithProvider requires a browser environment");
|
|
1657
1903
|
}
|
|
1658
1904
|
return new Promise((resolve, reject) => {
|
|
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() || "";
|
|
1659
1914
|
const popupUrl = new URL("/auth", this.authUrl);
|
|
1660
1915
|
popupUrl.searchParams.set("provider", provider);
|
|
1661
1916
|
popupUrl.searchParams.set("project_id", this.config.projectId);
|
|
1662
1917
|
popupUrl.searchParams.set("state", state);
|
|
1663
1918
|
popupUrl.searchParams.set("mode", "popup");
|
|
1664
1919
|
popupUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1665
|
-
if (typeof window !== "undefined") {
|
|
1666
|
-
popupUrl.searchParams.set("opener_origin", window.location.origin);
|
|
1667
|
-
}
|
|
1668
1920
|
const popup = window.open(
|
|
1669
1921
|
popupUrl.toString(),
|
|
1670
1922
|
"blink-auth",
|
|
1671
1923
|
"width=500,height=600,scrollbars=yes,resizable=yes"
|
|
1672
1924
|
);
|
|
1673
1925
|
if (!popup) {
|
|
1674
|
-
|
|
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();
|
|
1926
|
+
reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
|
|
1683
1927
|
return;
|
|
1684
1928
|
}
|
|
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
|
-
}
|
|
1701
1929
|
let timeoutId;
|
|
1702
1930
|
const messageListener = (event) => {
|
|
1703
1931
|
let allowed = false;
|
|
@@ -1707,16 +1935,12 @@ var BlinkAuth = class {
|
|
|
1707
1935
|
} catch {
|
|
1708
1936
|
}
|
|
1709
1937
|
if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
|
|
1710
|
-
if (
|
|
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);
|
|
1938
|
+
if (!allowed) return;
|
|
1716
1939
|
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
1717
1940
|
const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
|
|
1718
1941
|
try {
|
|
1719
|
-
const
|
|
1942
|
+
const sessionStorage = getSessionStorage();
|
|
1943
|
+
const expected = sessionStorage?.getItem("blink_oauth_state");
|
|
1720
1944
|
if (returnedState && expected && returnedState !== expected) {
|
|
1721
1945
|
reject(new BlinkAuthError("VERIFICATION_FAILED" /* VERIFICATION_FAILED */, "State mismatch"));
|
|
1722
1946
|
clearTimeout(timeoutId);
|
|
@@ -2240,6 +2464,48 @@ var BlinkAuth = class {
|
|
|
2240
2464
|
};
|
|
2241
2465
|
await this.setTokens(tokens, persist);
|
|
2242
2466
|
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Manually set auth session from tokens (React Native deep link OAuth)
|
|
2469
|
+
*
|
|
2470
|
+
* Use this method to set the user session after receiving tokens from a deep link callback.
|
|
2471
|
+
* This is the React Native equivalent of automatic URL token detection on web.
|
|
2472
|
+
*
|
|
2473
|
+
* @param tokens - Auth tokens received from deep link or OAuth callback
|
|
2474
|
+
* @param persist - Whether to persist tokens to storage (default: true)
|
|
2475
|
+
*
|
|
2476
|
+
* @example
|
|
2477
|
+
* // React Native: Handle deep link OAuth callback
|
|
2478
|
+
* import * as Linking from 'expo-linking'
|
|
2479
|
+
*
|
|
2480
|
+
* Linking.addEventListener('url', async ({ url }) => {
|
|
2481
|
+
* const { queryParams } = Linking.parse(url)
|
|
2482
|
+
*
|
|
2483
|
+
* if (queryParams.access_token) {
|
|
2484
|
+
* await blink.auth.setSession({
|
|
2485
|
+
* access_token: queryParams.access_token,
|
|
2486
|
+
* refresh_token: queryParams.refresh_token,
|
|
2487
|
+
* expires_in: parseInt(queryParams.expires_in) || 3600,
|
|
2488
|
+
* refresh_expires_in: parseInt(queryParams.refresh_expires_in)
|
|
2489
|
+
* })
|
|
2490
|
+
*
|
|
2491
|
+
* console.log('User authenticated:', blink.auth.currentUser())
|
|
2492
|
+
* }
|
|
2493
|
+
* })
|
|
2494
|
+
*/
|
|
2495
|
+
async setSession(tokens, persist = true) {
|
|
2496
|
+
const authTokens = {
|
|
2497
|
+
access_token: tokens.access_token,
|
|
2498
|
+
refresh_token: tokens.refresh_token,
|
|
2499
|
+
token_type: "Bearer",
|
|
2500
|
+
expires_in: tokens.expires_in || 3600,
|
|
2501
|
+
// Default 1 hour
|
|
2502
|
+
refresh_expires_in: tokens.refresh_expires_in,
|
|
2503
|
+
issued_at: Math.floor(Date.now() / 1e3)
|
|
2504
|
+
};
|
|
2505
|
+
await this.setTokens(authTokens, persist);
|
|
2506
|
+
const user = await this.me();
|
|
2507
|
+
return user;
|
|
2508
|
+
}
|
|
2243
2509
|
/**
|
|
2244
2510
|
* Refresh access token using refresh token
|
|
2245
2511
|
*/
|
|
@@ -2472,50 +2738,18 @@ var BlinkAuth = class {
|
|
|
2472
2738
|
return null;
|
|
2473
2739
|
}
|
|
2474
2740
|
}
|
|
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
|
-
}
|
|
2498
2741
|
extractTokensFromUrl() {
|
|
2499
|
-
|
|
2500
|
-
|
|
2742
|
+
const search = getLocationSearch();
|
|
2743
|
+
if (!search) return null;
|
|
2744
|
+
const params = new URLSearchParams(search);
|
|
2501
2745
|
const accessToken = params.get("access_token");
|
|
2502
2746
|
const refreshToken = params.get("refresh_token");
|
|
2503
|
-
const state = params.get("state");
|
|
2504
|
-
const error = params.get("error");
|
|
2505
2747
|
console.log("\u{1F50D} Extracting tokens from URL:", {
|
|
2506
|
-
url:
|
|
2507
|
-
search: window.location.search,
|
|
2508
|
-
hash: window.location.hash,
|
|
2748
|
+
url: getLocationHref(),
|
|
2509
2749
|
accessToken: accessToken ? `${accessToken.substring(0, 20)}...` : null,
|
|
2510
2750
|
refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null,
|
|
2511
|
-
state: state ? `${state.substring(0, 10)}...` : null,
|
|
2512
|
-
error: error || null,
|
|
2513
2751
|
allParams: Object.fromEntries(params.entries())
|
|
2514
2752
|
});
|
|
2515
|
-
if (error) {
|
|
2516
|
-
console.log("\u274C Error parameter found in URL, not extracting tokens");
|
|
2517
|
-
return null;
|
|
2518
|
-
}
|
|
2519
2753
|
if (accessToken) {
|
|
2520
2754
|
const tokens = {
|
|
2521
2755
|
access_token: accessToken,
|
|
@@ -2530,8 +2764,7 @@ var BlinkAuth = class {
|
|
|
2530
2764
|
};
|
|
2531
2765
|
console.log("\u2705 Tokens extracted successfully:", {
|
|
2532
2766
|
hasAccessToken: !!tokens.access_token,
|
|
2533
|
-
hasRefreshToken: !!tokens.refresh_token
|
|
2534
|
-
state: state || "no state"
|
|
2767
|
+
hasRefreshToken: !!tokens.refresh_token
|
|
2535
2768
|
});
|
|
2536
2769
|
return tokens;
|
|
2537
2770
|
}
|
|
@@ -2539,8 +2772,9 @@ var BlinkAuth = class {
|
|
|
2539
2772
|
return null;
|
|
2540
2773
|
}
|
|
2541
2774
|
clearUrlTokens() {
|
|
2542
|
-
|
|
2543
|
-
|
|
2775
|
+
const href = getLocationHref();
|
|
2776
|
+
if (!href || !hasWindowLocation()) return;
|
|
2777
|
+
const url = new URL(href);
|
|
2544
2778
|
url.searchParams.delete("access_token");
|
|
2545
2779
|
url.searchParams.delete("refresh_token");
|
|
2546
2780
|
url.searchParams.delete("token_type");
|
|
@@ -2551,12 +2785,11 @@ var BlinkAuth = class {
|
|
|
2551
2785
|
url.searchParams.delete("code");
|
|
2552
2786
|
url.searchParams.delete("error");
|
|
2553
2787
|
url.searchParams.delete("error_description");
|
|
2554
|
-
url.hash = "";
|
|
2555
2788
|
window.history.replaceState({}, "", url.toString());
|
|
2556
|
-
console.log("\u{1F9F9} URL cleaned up, removed auth parameters
|
|
2789
|
+
console.log("\u{1F9F9} URL cleaned up, removed auth parameters");
|
|
2557
2790
|
}
|
|
2558
2791
|
redirectToAuth() {
|
|
2559
|
-
if (
|
|
2792
|
+
if (hasWindowLocation()) {
|
|
2560
2793
|
this.login();
|
|
2561
2794
|
}
|
|
2562
2795
|
}
|
|
@@ -2588,12 +2821,25 @@ var BlinkAuth = class {
|
|
|
2588
2821
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2589
2822
|
}
|
|
2590
2823
|
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Generate unique session ID for mobile OAuth
|
|
2826
|
+
*/
|
|
2827
|
+
generateSessionId() {
|
|
2828
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2829
|
+
const array = new Uint8Array(32);
|
|
2830
|
+
crypto.getRandomValues(array);
|
|
2831
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2832
|
+
} else {
|
|
2833
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2591
2836
|
/**
|
|
2592
2837
|
* Extract magic link token from URL
|
|
2593
2838
|
*/
|
|
2594
2839
|
extractMagicTokenFromUrl() {
|
|
2595
|
-
|
|
2596
|
-
|
|
2840
|
+
const search = getLocationSearch();
|
|
2841
|
+
if (!search) return null;
|
|
2842
|
+
const params = new URLSearchParams(search);
|
|
2597
2843
|
return params.get("magic_token") || params.get("token");
|
|
2598
2844
|
}
|
|
2599
2845
|
/**
|
|
@@ -2649,7 +2895,7 @@ var BlinkAuth = class {
|
|
|
2649
2895
|
* Setup cross-tab authentication synchronization
|
|
2650
2896
|
*/
|
|
2651
2897
|
setupCrossTabSync() {
|
|
2652
|
-
if (!isWeb) return;
|
|
2898
|
+
if (!isWeb || !hasWindow()) return;
|
|
2653
2899
|
window.addEventListener("storage", (e) => {
|
|
2654
2900
|
if (e.key === this.getStorageKey("tokens")) {
|
|
2655
2901
|
const newTokens = e.newValue ? JSON.parse(e.newValue) : null;
|
|
@@ -4953,9 +5199,9 @@ var BlinkAnalyticsImpl = class {
|
|
|
4953
5199
|
user_id: this.userId,
|
|
4954
5200
|
user_email: this.userEmail,
|
|
4955
5201
|
session_id: sessionId,
|
|
4956
|
-
pathname:
|
|
4957
|
-
referrer:
|
|
4958
|
-
screen_width:
|
|
5202
|
+
pathname: getLocationPathname(),
|
|
5203
|
+
referrer: getDocumentReferrer(),
|
|
5204
|
+
screen_width: getWindowInnerWidth(),
|
|
4959
5205
|
channel,
|
|
4960
5206
|
utm_source: this.utmParams.utm_source || this.persistedAttribution.utm_source || null,
|
|
4961
5207
|
utm_medium: this.utmParams.utm_medium || this.persistedAttribution.utm_medium || null,
|
|
@@ -5108,7 +5354,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5108
5354
|
window.__blinkAnalyticsInstances?.add(this);
|
|
5109
5355
|
}
|
|
5110
5356
|
setupUnloadListener() {
|
|
5111
|
-
if (!isWeb) return;
|
|
5357
|
+
if (!isWeb || !hasWindow()) return;
|
|
5112
5358
|
window.addEventListener("pagehide", () => {
|
|
5113
5359
|
this.flush();
|
|
5114
5360
|
});
|
|
@@ -5118,7 +5364,12 @@ var BlinkAnalyticsImpl = class {
|
|
|
5118
5364
|
}
|
|
5119
5365
|
captureUTMParams() {
|
|
5120
5366
|
if (!isWeb) return;
|
|
5121
|
-
const
|
|
5367
|
+
const search = getLocationSearch();
|
|
5368
|
+
if (!search) {
|
|
5369
|
+
this.utmParams = {};
|
|
5370
|
+
return;
|
|
5371
|
+
}
|
|
5372
|
+
const urlParams = new URLSearchParams(search);
|
|
5122
5373
|
this.utmParams = {
|
|
5123
5374
|
utm_source: urlParams.get("utm_source"),
|
|
5124
5375
|
utm_medium: urlParams.get("utm_medium"),
|
|
@@ -5155,7 +5406,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5155
5406
|
}
|
|
5156
5407
|
}
|
|
5157
5408
|
detectChannel() {
|
|
5158
|
-
const referrer =
|
|
5409
|
+
const referrer = getDocumentReferrer();
|
|
5159
5410
|
const utmMedium = this.utmParams.utm_medium;
|
|
5160
5411
|
this.utmParams.utm_source;
|
|
5161
5412
|
if (utmMedium) {
|