@blinkdotnew/sdk 0.19.4 → 0.19.6
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 +232 -15
- package/dist/index.d.ts +232 -15
- package/dist/index.js +469 -246
- package/dist/index.mjs +469 -246
- 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;
|
|
@@ -1043,11 +1179,14 @@ var BlinkAuth = class {
|
|
|
1043
1179
|
// Default mode
|
|
1044
1180
|
authUrl: "https://blink.new",
|
|
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,9 +1236,12 @@ 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
|
+
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) {
|
|
1103
1245
|
return;
|
|
1104
1246
|
}
|
|
1105
1247
|
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
@@ -1119,7 +1261,7 @@ var BlinkAuth = class {
|
|
|
1119
1261
|
this.clearTokens();
|
|
1120
1262
|
}
|
|
1121
1263
|
});
|
|
1122
|
-
if (window.parent !== window) {
|
|
1264
|
+
if (hasWindow() && window.parent !== window) {
|
|
1123
1265
|
console.log("\u{1F504} Requesting auth tokens from parent window");
|
|
1124
1266
|
window.parent.postMessage({
|
|
1125
1267
|
type: "BLINK_REQUEST_AUTH_TOKENS",
|
|
@@ -1144,67 +1286,14 @@ var BlinkAuth = class {
|
|
|
1144
1286
|
return;
|
|
1145
1287
|
}
|
|
1146
1288
|
}
|
|
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
|
-
}
|
|
1289
|
+
if (this.authConfig.detectSessionInUrl !== false) {
|
|
1290
|
+
const tokensFromUrl = this.extractTokensFromUrl();
|
|
1291
|
+
if (tokensFromUrl) {
|
|
1292
|
+
console.log("\u{1F4E5} Found tokens in URL, setting them...");
|
|
1293
|
+
await this.setTokens(tokensFromUrl, true);
|
|
1294
|
+
this.clearUrlTokens();
|
|
1295
|
+
console.log("\u2705 Auth initialization complete (from URL)");
|
|
1296
|
+
return;
|
|
1208
1297
|
}
|
|
1209
1298
|
}
|
|
1210
1299
|
const storedTokens = await this.getStoredTokens();
|
|
@@ -1229,11 +1318,11 @@ var BlinkAuth = class {
|
|
|
1229
1318
|
}
|
|
1230
1319
|
}
|
|
1231
1320
|
console.log("\u274C No tokens found");
|
|
1232
|
-
if (this.config.authRequired) {
|
|
1321
|
+
if (this.config.authRequired && hasWindowLocation()) {
|
|
1233
1322
|
console.log("\u{1F504} Auth required, redirecting to auth page...");
|
|
1234
1323
|
this.redirectToAuth();
|
|
1235
1324
|
} else {
|
|
1236
|
-
console.log("\u26A0\uFE0F Auth not required, continuing without authentication");
|
|
1325
|
+
console.log("\u26A0\uFE0F Auth not required or no window.location, continuing without authentication");
|
|
1237
1326
|
}
|
|
1238
1327
|
} finally {
|
|
1239
1328
|
this.setLoading(false);
|
|
@@ -1244,15 +1333,20 @@ var BlinkAuth = class {
|
|
|
1244
1333
|
* Redirect to Blink auth page
|
|
1245
1334
|
*/
|
|
1246
1335
|
login(nextUrl) {
|
|
1336
|
+
if (!hasWindowLocation()) {
|
|
1337
|
+
console.warn("login() called in non-browser environment (no window.location available)");
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1247
1340
|
let redirectUrl = nextUrl || this.authConfig.redirectUrl;
|
|
1248
|
-
if (!redirectUrl
|
|
1249
|
-
|
|
1250
|
-
|
|
1341
|
+
if (!redirectUrl) {
|
|
1342
|
+
const href = getLocationHref();
|
|
1343
|
+
if (href && href.startsWith("http")) {
|
|
1344
|
+
redirectUrl = href;
|
|
1251
1345
|
} else {
|
|
1252
|
-
redirectUrl =
|
|
1346
|
+
redirectUrl = constructFullUrl() || void 0;
|
|
1253
1347
|
}
|
|
1254
1348
|
}
|
|
1255
|
-
if (redirectUrl
|
|
1349
|
+
if (redirectUrl) {
|
|
1256
1350
|
try {
|
|
1257
1351
|
const url = new URL(redirectUrl);
|
|
1258
1352
|
url.searchParams.delete("redirect_url");
|
|
@@ -1267,16 +1361,14 @@ var BlinkAuth = class {
|
|
|
1267
1361
|
if (this.config.projectId) {
|
|
1268
1362
|
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1269
1363
|
}
|
|
1270
|
-
|
|
1271
|
-
window.location.href = authUrl.toString();
|
|
1272
|
-
}
|
|
1364
|
+
window.location.href = authUrl.toString();
|
|
1273
1365
|
}
|
|
1274
1366
|
/**
|
|
1275
1367
|
* Logout and clear stored tokens
|
|
1276
1368
|
*/
|
|
1277
1369
|
logout(redirectUrl) {
|
|
1278
1370
|
this.clearTokens();
|
|
1279
|
-
if (redirectUrl &&
|
|
1371
|
+
if (redirectUrl && hasWindowLocation()) {
|
|
1280
1372
|
window.location.href = redirectUrl;
|
|
1281
1373
|
}
|
|
1282
1374
|
}
|
|
@@ -1560,144 +1652,251 @@ var BlinkAuth = class {
|
|
|
1560
1652
|
return this.signInWithProvider("microsoft", options);
|
|
1561
1653
|
}
|
|
1562
1654
|
/**
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1655
|
+
* Initiate OAuth for mobile without deep linking (expo-web-browser pattern)
|
|
1656
|
+
*
|
|
1657
|
+
* This method:
|
|
1658
|
+
* 1. Generates a unique session ID
|
|
1659
|
+
* 2. Returns OAuth URL with session parameter
|
|
1660
|
+
* 3. App opens URL in expo-web-browser
|
|
1661
|
+
* 4. App polls checkMobileOAuthSession() until complete
|
|
1662
|
+
*
|
|
1663
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1664
|
+
* @param options - Optional metadata
|
|
1665
|
+
* @returns Session ID and OAuth URL
|
|
1666
|
+
*
|
|
1667
|
+
* @example
|
|
1668
|
+
* // React Native with expo-web-browser
|
|
1669
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1670
|
+
*
|
|
1671
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1672
|
+
*
|
|
1673
|
+
* // Open browser
|
|
1674
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1675
|
+
*
|
|
1676
|
+
* // Poll for completion
|
|
1677
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId);
|
|
1678
|
+
* console.log('Authenticated:', user.email);
|
|
1565
1679
|
*/
|
|
1566
|
-
|
|
1567
|
-
if (
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
const
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1680
|
+
async initiateMobileOAuth(provider, options) {
|
|
1681
|
+
if (this.authConfig.mode !== "headless") {
|
|
1682
|
+
throw new BlinkAuthError(
|
|
1683
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
1684
|
+
"initiateMobileOAuth is only available in headless mode"
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
const sessionId = this.generateSessionId();
|
|
1688
|
+
const authUrl = new URL("/auth", this.authUrl);
|
|
1689
|
+
authUrl.searchParams.set("provider", provider);
|
|
1690
|
+
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1691
|
+
authUrl.searchParams.set("mode", "mobile-session");
|
|
1692
|
+
authUrl.searchParams.set("session_id", sessionId);
|
|
1693
|
+
if (options?.metadata) {
|
|
1694
|
+
authUrl.searchParams.set("metadata", JSON.stringify(options.metadata));
|
|
1695
|
+
}
|
|
1696
|
+
return {
|
|
1697
|
+
sessionId,
|
|
1698
|
+
authUrl: authUrl.toString()
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Check mobile OAuth session status (single check)
|
|
1703
|
+
*
|
|
1704
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1705
|
+
* @returns Tokens if session is complete, null if still pending
|
|
1706
|
+
*/
|
|
1707
|
+
async checkMobileOAuthSession(sessionId) {
|
|
1708
|
+
try {
|
|
1709
|
+
const response = await fetch(`${this.authUrl}/api/auth/mobile-session/${sessionId}`, {
|
|
1710
|
+
method: "GET",
|
|
1711
|
+
headers: {
|
|
1712
|
+
"Content-Type": "application/json"
|
|
1713
|
+
}
|
|
1594
1714
|
});
|
|
1715
|
+
if (response.status === 404 || response.status === 202) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
if (!response.ok) {
|
|
1719
|
+
const errorData = await response.json();
|
|
1720
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1721
|
+
throw new BlinkAuthError(
|
|
1722
|
+
errorCode,
|
|
1723
|
+
errorData.error || "Failed to check OAuth session"
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
const data = await response.json();
|
|
1727
|
+
return {
|
|
1728
|
+
access_token: data.access_token,
|
|
1729
|
+
refresh_token: data.refresh_token,
|
|
1730
|
+
token_type: data.token_type || "Bearer",
|
|
1731
|
+
expires_in: data.expires_in || 3600,
|
|
1732
|
+
refresh_expires_in: data.refresh_expires_in
|
|
1733
|
+
};
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
if (error instanceof BlinkAuthError) {
|
|
1736
|
+
throw error;
|
|
1737
|
+
}
|
|
1738
|
+
throw new BlinkAuthError(
|
|
1739
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1740
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1741
|
+
);
|
|
1595
1742
|
}
|
|
1596
|
-
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Poll mobile OAuth session until complete (convenience method)
|
|
1746
|
+
*
|
|
1747
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1748
|
+
* @param options - Polling options
|
|
1749
|
+
* @returns Authenticated user
|
|
1750
|
+
*
|
|
1751
|
+
* @example
|
|
1752
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1753
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1754
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId, {
|
|
1755
|
+
* maxAttempts: 60,
|
|
1756
|
+
* intervalMs: 1000
|
|
1757
|
+
* });
|
|
1758
|
+
*/
|
|
1759
|
+
async pollMobileOAuthSession(sessionId, options) {
|
|
1760
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
1761
|
+
const intervalMs = options?.intervalMs || 1e3;
|
|
1762
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1763
|
+
const tokens = await this.checkMobileOAuthSession(sessionId);
|
|
1764
|
+
if (tokens) {
|
|
1765
|
+
await this.setTokens(tokens, true);
|
|
1766
|
+
return this.authState.user;
|
|
1767
|
+
}
|
|
1768
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1769
|
+
}
|
|
1770
|
+
throw new BlinkAuthError(
|
|
1771
|
+
"AUTH_TIMEOUT" /* AUTH_TIMEOUT */,
|
|
1772
|
+
"Mobile OAuth session timed out"
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Sign in with OAuth provider using expo-web-browser (React Native)
|
|
1777
|
+
*
|
|
1778
|
+
* This is a convenience method that handles the entire flow:
|
|
1779
|
+
* 1. Initiates mobile OAuth session
|
|
1780
|
+
* 2. Returns auth URL to open in WebBrowser
|
|
1781
|
+
* 3. Provides polling function to call after browser opens
|
|
1782
|
+
*
|
|
1783
|
+
* @param provider - OAuth provider
|
|
1784
|
+
* @returns Object with authUrl and authenticate function
|
|
1785
|
+
*
|
|
1786
|
+
* @example
|
|
1787
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1788
|
+
*
|
|
1789
|
+
* const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google');
|
|
1790
|
+
*
|
|
1791
|
+
* // Open browser
|
|
1792
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1793
|
+
*
|
|
1794
|
+
* // Wait for authentication
|
|
1795
|
+
* const user = await authenticate();
|
|
1796
|
+
*/
|
|
1797
|
+
async signInWithProviderMobile(provider, options) {
|
|
1798
|
+
const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
|
|
1799
|
+
return {
|
|
1800
|
+
authUrl,
|
|
1801
|
+
authenticate: () => this.pollMobileOAuthSession(sessionId, {
|
|
1802
|
+
maxAttempts: 60,
|
|
1803
|
+
intervalMs: 1e3
|
|
1804
|
+
})
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
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
|
|
1828
|
+
*/
|
|
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
|
+
);
|
|
1597
1834
|
}
|
|
1598
1835
|
/**
|
|
1599
1836
|
* Generic provider sign-in method (headless mode)
|
|
1600
|
-
*
|
|
1837
|
+
*
|
|
1838
|
+
* Supports both web (popup) and React Native (deep link) OAuth flows.
|
|
1839
|
+
*
|
|
1840
|
+
* **React Native Setup:**
|
|
1841
|
+
* 1. Configure deep linking in your app (app.json):
|
|
1842
|
+
* ```json
|
|
1843
|
+
* { "expo": { "scheme": "com.yourapp" } }
|
|
1844
|
+
* ```
|
|
1845
|
+
*
|
|
1846
|
+
* 2. Add redirect URL in Blink Dashboard:
|
|
1847
|
+
* `com.yourapp://**`
|
|
1848
|
+
*
|
|
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
|
+
* ```
|
|
1860
|
+
*
|
|
1861
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1862
|
+
* @param options - Optional redirect URL and metadata
|
|
1863
|
+
* @returns Promise that resolves with authenticated user
|
|
1601
1864
|
*/
|
|
1602
1865
|
async signInWithProvider(provider, options) {
|
|
1603
1866
|
if (this.authConfig.mode !== "headless") {
|
|
1604
1867
|
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
|
|
1605
1868
|
}
|
|
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
|
-
}
|
|
1869
|
+
if (!hasWindow()) {
|
|
1870
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, "signInWithProvider requires a browser environment");
|
|
1614
1871
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
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
|
-
});
|
|
1872
|
+
if (isReactNative2()) {
|
|
1873
|
+
return this.signInWithProviderReactNative(provider, options);
|
|
1657
1874
|
}
|
|
1658
1875
|
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() || "";
|
|
1659
1885
|
const popupUrl = new URL("/auth", this.authUrl);
|
|
1660
1886
|
popupUrl.searchParams.set("provider", provider);
|
|
1661
1887
|
popupUrl.searchParams.set("project_id", this.config.projectId);
|
|
1662
1888
|
popupUrl.searchParams.set("state", state);
|
|
1663
1889
|
popupUrl.searchParams.set("mode", "popup");
|
|
1664
1890
|
popupUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1665
|
-
if (typeof window !== "undefined") {
|
|
1666
|
-
popupUrl.searchParams.set("opener_origin", window.location.origin);
|
|
1667
|
-
}
|
|
1668
1891
|
const popup = window.open(
|
|
1669
1892
|
popupUrl.toString(),
|
|
1670
1893
|
"blink-auth",
|
|
1671
1894
|
"width=500,height=600,scrollbars=yes,resizable=yes"
|
|
1672
1895
|
);
|
|
1673
1896
|
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();
|
|
1897
|
+
reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
|
|
1683
1898
|
return;
|
|
1684
1899
|
}
|
|
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
1900
|
let timeoutId;
|
|
1702
1901
|
const messageListener = (event) => {
|
|
1703
1902
|
let allowed = false;
|
|
@@ -1707,16 +1906,13 @@ var BlinkAuth = class {
|
|
|
1707
1906
|
} catch {
|
|
1708
1907
|
}
|
|
1709
1908
|
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);
|
|
1909
|
+
if (event.origin.endsWith(".sites.blink.new") || event.origin.endsWith(".preview-blink.com")) allowed = true;
|
|
1910
|
+
if (!allowed) return;
|
|
1716
1911
|
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
1717
1912
|
const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
|
|
1718
1913
|
try {
|
|
1719
|
-
const
|
|
1914
|
+
const sessionStorage = getSessionStorage();
|
|
1915
|
+
const expected = sessionStorage?.getItem("blink_oauth_state");
|
|
1720
1916
|
if (returnedState && expected && returnedState !== expected) {
|
|
1721
1917
|
reject(new BlinkAuthError("VERIFICATION_FAILED" /* VERIFICATION_FAILED */, "State mismatch"));
|
|
1722
1918
|
clearTimeout(timeoutId);
|
|
@@ -2240,6 +2436,48 @@ var BlinkAuth = class {
|
|
|
2240
2436
|
};
|
|
2241
2437
|
await this.setTokens(tokens, persist);
|
|
2242
2438
|
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Manually set auth session from tokens (React Native deep link OAuth)
|
|
2441
|
+
*
|
|
2442
|
+
* Use this method to set the user session after receiving tokens from a deep link callback.
|
|
2443
|
+
* This is the React Native equivalent of automatic URL token detection on web.
|
|
2444
|
+
*
|
|
2445
|
+
* @param tokens - Auth tokens received from deep link or OAuth callback
|
|
2446
|
+
* @param persist - Whether to persist tokens to storage (default: true)
|
|
2447
|
+
*
|
|
2448
|
+
* @example
|
|
2449
|
+
* // React Native: Handle deep link OAuth callback
|
|
2450
|
+
* import * as Linking from 'expo-linking'
|
|
2451
|
+
*
|
|
2452
|
+
* Linking.addEventListener('url', async ({ url }) => {
|
|
2453
|
+
* const { queryParams } = Linking.parse(url)
|
|
2454
|
+
*
|
|
2455
|
+
* if (queryParams.access_token) {
|
|
2456
|
+
* await blink.auth.setSession({
|
|
2457
|
+
* access_token: queryParams.access_token,
|
|
2458
|
+
* refresh_token: queryParams.refresh_token,
|
|
2459
|
+
* expires_in: parseInt(queryParams.expires_in) || 3600,
|
|
2460
|
+
* refresh_expires_in: parseInt(queryParams.refresh_expires_in)
|
|
2461
|
+
* })
|
|
2462
|
+
*
|
|
2463
|
+
* console.log('User authenticated:', blink.auth.currentUser())
|
|
2464
|
+
* }
|
|
2465
|
+
* })
|
|
2466
|
+
*/
|
|
2467
|
+
async setSession(tokens, persist = true) {
|
|
2468
|
+
const authTokens = {
|
|
2469
|
+
access_token: tokens.access_token,
|
|
2470
|
+
refresh_token: tokens.refresh_token,
|
|
2471
|
+
token_type: "Bearer",
|
|
2472
|
+
expires_in: tokens.expires_in || 3600,
|
|
2473
|
+
// Default 1 hour
|
|
2474
|
+
refresh_expires_in: tokens.refresh_expires_in,
|
|
2475
|
+
issued_at: Math.floor(Date.now() / 1e3)
|
|
2476
|
+
};
|
|
2477
|
+
await this.setTokens(authTokens, persist);
|
|
2478
|
+
const user = await this.me();
|
|
2479
|
+
return user;
|
|
2480
|
+
}
|
|
2243
2481
|
/**
|
|
2244
2482
|
* Refresh access token using refresh token
|
|
2245
2483
|
*/
|
|
@@ -2472,50 +2710,18 @@ var BlinkAuth = class {
|
|
|
2472
2710
|
return null;
|
|
2473
2711
|
}
|
|
2474
2712
|
}
|
|
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
2713
|
extractTokensFromUrl() {
|
|
2499
|
-
|
|
2500
|
-
|
|
2714
|
+
const search = getLocationSearch();
|
|
2715
|
+
if (!search) return null;
|
|
2716
|
+
const params = new URLSearchParams(search);
|
|
2501
2717
|
const accessToken = params.get("access_token");
|
|
2502
2718
|
const refreshToken = params.get("refresh_token");
|
|
2503
|
-
const state = params.get("state");
|
|
2504
|
-
const error = params.get("error");
|
|
2505
2719
|
console.log("\u{1F50D} Extracting tokens from URL:", {
|
|
2506
|
-
url:
|
|
2507
|
-
search: window.location.search,
|
|
2508
|
-
hash: window.location.hash,
|
|
2720
|
+
url: getLocationHref(),
|
|
2509
2721
|
accessToken: accessToken ? `${accessToken.substring(0, 20)}...` : null,
|
|
2510
2722
|
refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null,
|
|
2511
|
-
state: state ? `${state.substring(0, 10)}...` : null,
|
|
2512
|
-
error: error || null,
|
|
2513
2723
|
allParams: Object.fromEntries(params.entries())
|
|
2514
2724
|
});
|
|
2515
|
-
if (error) {
|
|
2516
|
-
console.log("\u274C Error parameter found in URL, not extracting tokens");
|
|
2517
|
-
return null;
|
|
2518
|
-
}
|
|
2519
2725
|
if (accessToken) {
|
|
2520
2726
|
const tokens = {
|
|
2521
2727
|
access_token: accessToken,
|
|
@@ -2530,8 +2736,7 @@ var BlinkAuth = class {
|
|
|
2530
2736
|
};
|
|
2531
2737
|
console.log("\u2705 Tokens extracted successfully:", {
|
|
2532
2738
|
hasAccessToken: !!tokens.access_token,
|
|
2533
|
-
hasRefreshToken: !!tokens.refresh_token
|
|
2534
|
-
state: state || "no state"
|
|
2739
|
+
hasRefreshToken: !!tokens.refresh_token
|
|
2535
2740
|
});
|
|
2536
2741
|
return tokens;
|
|
2537
2742
|
}
|
|
@@ -2539,8 +2744,9 @@ var BlinkAuth = class {
|
|
|
2539
2744
|
return null;
|
|
2540
2745
|
}
|
|
2541
2746
|
clearUrlTokens() {
|
|
2542
|
-
|
|
2543
|
-
|
|
2747
|
+
const href = getLocationHref();
|
|
2748
|
+
if (!href || !hasWindowLocation()) return;
|
|
2749
|
+
const url = new URL(href);
|
|
2544
2750
|
url.searchParams.delete("access_token");
|
|
2545
2751
|
url.searchParams.delete("refresh_token");
|
|
2546
2752
|
url.searchParams.delete("token_type");
|
|
@@ -2551,12 +2757,11 @@ var BlinkAuth = class {
|
|
|
2551
2757
|
url.searchParams.delete("code");
|
|
2552
2758
|
url.searchParams.delete("error");
|
|
2553
2759
|
url.searchParams.delete("error_description");
|
|
2554
|
-
url.hash = "";
|
|
2555
2760
|
window.history.replaceState({}, "", url.toString());
|
|
2556
|
-
console.log("\u{1F9F9} URL cleaned up, removed auth parameters
|
|
2761
|
+
console.log("\u{1F9F9} URL cleaned up, removed auth parameters");
|
|
2557
2762
|
}
|
|
2558
2763
|
redirectToAuth() {
|
|
2559
|
-
if (
|
|
2764
|
+
if (hasWindowLocation()) {
|
|
2560
2765
|
this.login();
|
|
2561
2766
|
}
|
|
2562
2767
|
}
|
|
@@ -2588,12 +2793,25 @@ var BlinkAuth = class {
|
|
|
2588
2793
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2589
2794
|
}
|
|
2590
2795
|
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Generate unique session ID for mobile OAuth
|
|
2798
|
+
*/
|
|
2799
|
+
generateSessionId() {
|
|
2800
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2801
|
+
const array = new Uint8Array(32);
|
|
2802
|
+
crypto.getRandomValues(array);
|
|
2803
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2804
|
+
} else {
|
|
2805
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2591
2808
|
/**
|
|
2592
2809
|
* Extract magic link token from URL
|
|
2593
2810
|
*/
|
|
2594
2811
|
extractMagicTokenFromUrl() {
|
|
2595
|
-
|
|
2596
|
-
|
|
2812
|
+
const search = getLocationSearch();
|
|
2813
|
+
if (!search) return null;
|
|
2814
|
+
const params = new URLSearchParams(search);
|
|
2597
2815
|
return params.get("magic_token") || params.get("token");
|
|
2598
2816
|
}
|
|
2599
2817
|
/**
|
|
@@ -2649,7 +2867,7 @@ var BlinkAuth = class {
|
|
|
2649
2867
|
* Setup cross-tab authentication synchronization
|
|
2650
2868
|
*/
|
|
2651
2869
|
setupCrossTabSync() {
|
|
2652
|
-
if (!isWeb) return;
|
|
2870
|
+
if (!isWeb || !hasWindow()) return;
|
|
2653
2871
|
window.addEventListener("storage", (e) => {
|
|
2654
2872
|
if (e.key === this.getStorageKey("tokens")) {
|
|
2655
2873
|
const newTokens = e.newValue ? JSON.parse(e.newValue) : null;
|
|
@@ -4953,9 +5171,9 @@ var BlinkAnalyticsImpl = class {
|
|
|
4953
5171
|
user_id: this.userId,
|
|
4954
5172
|
user_email: this.userEmail,
|
|
4955
5173
|
session_id: sessionId,
|
|
4956
|
-
pathname:
|
|
4957
|
-
referrer:
|
|
4958
|
-
screen_width:
|
|
5174
|
+
pathname: getLocationPathname(),
|
|
5175
|
+
referrer: getDocumentReferrer(),
|
|
5176
|
+
screen_width: getWindowInnerWidth(),
|
|
4959
5177
|
channel,
|
|
4960
5178
|
utm_source: this.utmParams.utm_source || this.persistedAttribution.utm_source || null,
|
|
4961
5179
|
utm_medium: this.utmParams.utm_medium || this.persistedAttribution.utm_medium || null,
|
|
@@ -5108,7 +5326,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5108
5326
|
window.__blinkAnalyticsInstances?.add(this);
|
|
5109
5327
|
}
|
|
5110
5328
|
setupUnloadListener() {
|
|
5111
|
-
if (!isWeb) return;
|
|
5329
|
+
if (!isWeb || !hasWindow()) return;
|
|
5112
5330
|
window.addEventListener("pagehide", () => {
|
|
5113
5331
|
this.flush();
|
|
5114
5332
|
});
|
|
@@ -5118,7 +5336,12 @@ var BlinkAnalyticsImpl = class {
|
|
|
5118
5336
|
}
|
|
5119
5337
|
captureUTMParams() {
|
|
5120
5338
|
if (!isWeb) return;
|
|
5121
|
-
const
|
|
5339
|
+
const search = getLocationSearch();
|
|
5340
|
+
if (!search) {
|
|
5341
|
+
this.utmParams = {};
|
|
5342
|
+
return;
|
|
5343
|
+
}
|
|
5344
|
+
const urlParams = new URLSearchParams(search);
|
|
5122
5345
|
this.utmParams = {
|
|
5123
5346
|
utm_source: urlParams.get("utm_source"),
|
|
5124
5347
|
utm_medium: urlParams.get("utm_medium"),
|
|
@@ -5155,7 +5378,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5155
5378
|
}
|
|
5156
5379
|
}
|
|
5157
5380
|
detectChannel() {
|
|
5158
|
-
const referrer =
|
|
5381
|
+
const referrer = getDocumentReferrer();
|
|
5159
5382
|
const utmMedium = this.utmParams.utm_medium;
|
|
5160
5383
|
this.utmParams.utm_source;
|
|
5161
5384
|
if (utmMedium) {
|