@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.mjs
CHANGED
|
@@ -1018,6 +1018,142 @@ var HttpClient = class {
|
|
|
1018
1018
|
}
|
|
1019
1019
|
};
|
|
1020
1020
|
|
|
1021
|
+
// src/utils/browser-env.ts
|
|
1022
|
+
function hasWindow() {
|
|
1023
|
+
return typeof window !== "undefined";
|
|
1024
|
+
}
|
|
1025
|
+
function hasWindowLocation() {
|
|
1026
|
+
return typeof window !== "undefined" && typeof window.location !== "undefined";
|
|
1027
|
+
}
|
|
1028
|
+
function hasDocument() {
|
|
1029
|
+
return typeof document !== "undefined";
|
|
1030
|
+
}
|
|
1031
|
+
function isReactNative2() {
|
|
1032
|
+
return typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
1033
|
+
}
|
|
1034
|
+
function getWindowLocation() {
|
|
1035
|
+
if (!hasWindow()) return null;
|
|
1036
|
+
try {
|
|
1037
|
+
return window.location;
|
|
1038
|
+
} catch {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function getLocationHref() {
|
|
1043
|
+
const loc = getWindowLocation();
|
|
1044
|
+
if (!loc) return null;
|
|
1045
|
+
try {
|
|
1046
|
+
return loc.href;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function getLocationOrigin() {
|
|
1052
|
+
const loc = getWindowLocation();
|
|
1053
|
+
if (!loc) return null;
|
|
1054
|
+
try {
|
|
1055
|
+
return loc.origin;
|
|
1056
|
+
} catch {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function getLocationHostname() {
|
|
1061
|
+
const loc = getWindowLocation();
|
|
1062
|
+
if (!loc) return null;
|
|
1063
|
+
try {
|
|
1064
|
+
return loc.hostname;
|
|
1065
|
+
} catch {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
function getLocationPathname() {
|
|
1070
|
+
const loc = getWindowLocation();
|
|
1071
|
+
if (!loc) return null;
|
|
1072
|
+
try {
|
|
1073
|
+
return loc.pathname;
|
|
1074
|
+
} catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function getLocationSearch() {
|
|
1079
|
+
const loc = getWindowLocation();
|
|
1080
|
+
if (!loc) return null;
|
|
1081
|
+
try {
|
|
1082
|
+
return loc.search;
|
|
1083
|
+
} catch {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
function getLocationHash() {
|
|
1088
|
+
const loc = getWindowLocation();
|
|
1089
|
+
if (!loc) return null;
|
|
1090
|
+
try {
|
|
1091
|
+
return loc.hash;
|
|
1092
|
+
} catch {
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
function getLocationProtocol() {
|
|
1097
|
+
const loc = getWindowLocation();
|
|
1098
|
+
if (!loc) return null;
|
|
1099
|
+
try {
|
|
1100
|
+
return loc.protocol;
|
|
1101
|
+
} catch {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function getLocationHost() {
|
|
1106
|
+
const loc = getWindowLocation();
|
|
1107
|
+
if (!loc) return null;
|
|
1108
|
+
try {
|
|
1109
|
+
return loc.host;
|
|
1110
|
+
} catch {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
function constructFullUrl() {
|
|
1115
|
+
if (!hasWindow()) return null;
|
|
1116
|
+
const protocol = getLocationProtocol();
|
|
1117
|
+
const host = getLocationHost();
|
|
1118
|
+
const pathname = getLocationPathname();
|
|
1119
|
+
const search = getLocationSearch();
|
|
1120
|
+
const hash = getLocationHash();
|
|
1121
|
+
if (!protocol || !host) return null;
|
|
1122
|
+
return `${protocol}//${host}${pathname || ""}${search || ""}${hash || ""}`;
|
|
1123
|
+
}
|
|
1124
|
+
function getDocumentReferrer() {
|
|
1125
|
+
if (!hasDocument()) return null;
|
|
1126
|
+
try {
|
|
1127
|
+
return document.referrer || null;
|
|
1128
|
+
} catch {
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function getWindowInnerWidth() {
|
|
1133
|
+
if (!hasWindow()) return null;
|
|
1134
|
+
try {
|
|
1135
|
+
return window.innerWidth;
|
|
1136
|
+
} catch {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function isIframe() {
|
|
1141
|
+
if (!hasWindow()) return false;
|
|
1142
|
+
try {
|
|
1143
|
+
return window.self !== window.top;
|
|
1144
|
+
} catch {
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function getSessionStorage() {
|
|
1149
|
+
if (!hasWindow()) return null;
|
|
1150
|
+
try {
|
|
1151
|
+
return window.sessionStorage;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1021
1157
|
// src/auth.ts
|
|
1022
1158
|
var BlinkAuth = class {
|
|
1023
1159
|
config;
|
|
@@ -1041,11 +1177,14 @@ var BlinkAuth = class {
|
|
|
1041
1177
|
// Default mode
|
|
1042
1178
|
authUrl: "https://blink.new",
|
|
1043
1179
|
coreUrl: "https://core.blink.new",
|
|
1180
|
+
detectSessionInUrl: true,
|
|
1181
|
+
// Default to true for web compatibility
|
|
1044
1182
|
...config.auth
|
|
1045
1183
|
};
|
|
1046
1184
|
this.authUrl = this.authConfig.authUrl || "https://blink.new";
|
|
1047
1185
|
this.coreUrl = this.authConfig.coreUrl || "https://core.blink.new";
|
|
1048
|
-
|
|
1186
|
+
const hostname = getLocationHostname();
|
|
1187
|
+
if (hostname && this.authUrl === "https://blink.new" && (hostname === "localhost" || hostname === "127.0.0.1")) {
|
|
1049
1188
|
console.warn("\u26A0\uFE0F Using default authUrl in development. Set auth.authUrl to your app origin for headless auth endpoints to work.");
|
|
1050
1189
|
}
|
|
1051
1190
|
if (config.authRequired !== void 0 && !config.auth?.mode) {
|
|
@@ -1059,7 +1198,7 @@ var BlinkAuth = class {
|
|
|
1059
1198
|
};
|
|
1060
1199
|
this.storage = config.auth?.storage || config.storage || getDefaultStorageAdapter();
|
|
1061
1200
|
if (isWeb) {
|
|
1062
|
-
this.isIframe =
|
|
1201
|
+
this.isIframe = isIframe();
|
|
1063
1202
|
this.setupParentWindowListener();
|
|
1064
1203
|
this.setupCrossTabSync();
|
|
1065
1204
|
this.initializationPromise = this.initialize();
|
|
@@ -1095,9 +1234,12 @@ var BlinkAuth = class {
|
|
|
1095
1234
|
* Setup listener for tokens from parent window
|
|
1096
1235
|
*/
|
|
1097
1236
|
setupParentWindowListener() {
|
|
1098
|
-
if (!isWeb || !this.isIframe) return;
|
|
1237
|
+
if (!isWeb || !this.isIframe || !hasWindow()) return;
|
|
1099
1238
|
window.addEventListener("message", (event) => {
|
|
1100
|
-
|
|
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) {
|
|
1101
1243
|
return;
|
|
1102
1244
|
}
|
|
1103
1245
|
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
@@ -1117,7 +1259,7 @@ var BlinkAuth = class {
|
|
|
1117
1259
|
this.clearTokens();
|
|
1118
1260
|
}
|
|
1119
1261
|
});
|
|
1120
|
-
if (window.parent !== window) {
|
|
1262
|
+
if (hasWindow() && window.parent !== window) {
|
|
1121
1263
|
console.log("\u{1F504} Requesting auth tokens from parent window");
|
|
1122
1264
|
window.parent.postMessage({
|
|
1123
1265
|
type: "BLINK_REQUEST_AUTH_TOKENS",
|
|
@@ -1142,67 +1284,14 @@ var BlinkAuth = class {
|
|
|
1142
1284
|
return;
|
|
1143
1285
|
}
|
|
1144
1286
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
const errorParam = urlParams.get("error");
|
|
1154
|
-
if (errorParam) {
|
|
1155
|
-
const errorCode = this.mapErrorCodeFromResponse(errorParam);
|
|
1156
|
-
const errorMessage = urlParams.get("error_description") || "Authentication failed";
|
|
1157
|
-
const error = new BlinkAuthError(errorCode, errorMessage);
|
|
1158
|
-
console.error("\u274C Auth error in URL:", {
|
|
1159
|
-
error: errorParam,
|
|
1160
|
-
errorMessage,
|
|
1161
|
-
allParams: Object.fromEntries(urlParams.entries())
|
|
1162
|
-
});
|
|
1163
|
-
if (typeof window !== "undefined") {
|
|
1164
|
-
window.dispatchEvent(new CustomEvent("blink:auth:error", {
|
|
1165
|
-
detail: { error, errorMessage }
|
|
1166
|
-
}));
|
|
1167
|
-
}
|
|
1168
|
-
this.clearUrlTokens();
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
} catch (error) {
|
|
1173
|
-
console.error("Error handling failed redirect:", error);
|
|
1174
|
-
}
|
|
1175
|
-
const tokensFromUrl = this.extractTokensFromUrl();
|
|
1176
|
-
if (tokensFromUrl) {
|
|
1177
|
-
console.log("\u{1F4E5} Found tokens in URL, setting them...");
|
|
1178
|
-
await this.setTokens(tokensFromUrl, true);
|
|
1179
|
-
this.clearUrlTokens();
|
|
1180
|
-
console.log("\u2705 Auth initialization complete (from URL)");
|
|
1181
|
-
return;
|
|
1182
|
-
} else {
|
|
1183
|
-
console.log("\u26A0\uFE0F No tokens found in URL after redirect - checking if this was a redirect callback");
|
|
1184
|
-
if (typeof window !== "undefined") {
|
|
1185
|
-
const urlParams = this.extractUrlParams();
|
|
1186
|
-
const state = urlParams.get("state");
|
|
1187
|
-
if (state) {
|
|
1188
|
-
console.log("\u26A0\uFE0F State found in URL but no tokens - redirect may have failed silently");
|
|
1189
|
-
try {
|
|
1190
|
-
const expectedState = sessionStorage.getItem("blink_oauth_state");
|
|
1191
|
-
if (expectedState === state) {
|
|
1192
|
-
console.error("\u274C Redirect callback received but no tokens - authentication may have failed");
|
|
1193
|
-
const errorMessage = "Authentication failed. Please try again.";
|
|
1194
|
-
if (typeof window !== "undefined") {
|
|
1195
|
-
window.dispatchEvent(new CustomEvent("blink:auth:error", {
|
|
1196
|
-
detail: { error: new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, errorMessage), errorMessage }
|
|
1197
|
-
}));
|
|
1198
|
-
}
|
|
1199
|
-
this.clearUrlTokens();
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
} catch (e) {
|
|
1203
|
-
console.error("Error checking sessionStorage:", e);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1287
|
+
if (this.authConfig.detectSessionInUrl !== false) {
|
|
1288
|
+
const tokensFromUrl = this.extractTokensFromUrl();
|
|
1289
|
+
if (tokensFromUrl) {
|
|
1290
|
+
console.log("\u{1F4E5} Found tokens in URL, setting them...");
|
|
1291
|
+
await this.setTokens(tokensFromUrl, true);
|
|
1292
|
+
this.clearUrlTokens();
|
|
1293
|
+
console.log("\u2705 Auth initialization complete (from URL)");
|
|
1294
|
+
return;
|
|
1206
1295
|
}
|
|
1207
1296
|
}
|
|
1208
1297
|
const storedTokens = await this.getStoredTokens();
|
|
@@ -1227,11 +1316,11 @@ var BlinkAuth = class {
|
|
|
1227
1316
|
}
|
|
1228
1317
|
}
|
|
1229
1318
|
console.log("\u274C No tokens found");
|
|
1230
|
-
if (this.config.authRequired) {
|
|
1319
|
+
if (this.config.authRequired && hasWindowLocation()) {
|
|
1231
1320
|
console.log("\u{1F504} Auth required, redirecting to auth page...");
|
|
1232
1321
|
this.redirectToAuth();
|
|
1233
1322
|
} else {
|
|
1234
|
-
console.log("\u26A0\uFE0F Auth not required, continuing without authentication");
|
|
1323
|
+
console.log("\u26A0\uFE0F Auth not required or no window.location, continuing without authentication");
|
|
1235
1324
|
}
|
|
1236
1325
|
} finally {
|
|
1237
1326
|
this.setLoading(false);
|
|
@@ -1242,15 +1331,20 @@ var BlinkAuth = class {
|
|
|
1242
1331
|
* Redirect to Blink auth page
|
|
1243
1332
|
*/
|
|
1244
1333
|
login(nextUrl) {
|
|
1334
|
+
if (!hasWindowLocation()) {
|
|
1335
|
+
console.warn("login() called in non-browser environment (no window.location available)");
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1245
1338
|
let redirectUrl = nextUrl || this.authConfig.redirectUrl;
|
|
1246
|
-
if (!redirectUrl
|
|
1247
|
-
|
|
1248
|
-
|
|
1339
|
+
if (!redirectUrl) {
|
|
1340
|
+
const href = getLocationHref();
|
|
1341
|
+
if (href && href.startsWith("http")) {
|
|
1342
|
+
redirectUrl = href;
|
|
1249
1343
|
} else {
|
|
1250
|
-
redirectUrl =
|
|
1344
|
+
redirectUrl = constructFullUrl() || void 0;
|
|
1251
1345
|
}
|
|
1252
1346
|
}
|
|
1253
|
-
if (redirectUrl
|
|
1347
|
+
if (redirectUrl) {
|
|
1254
1348
|
try {
|
|
1255
1349
|
const url = new URL(redirectUrl);
|
|
1256
1350
|
url.searchParams.delete("redirect_url");
|
|
@@ -1265,16 +1359,14 @@ var BlinkAuth = class {
|
|
|
1265
1359
|
if (this.config.projectId) {
|
|
1266
1360
|
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1267
1361
|
}
|
|
1268
|
-
|
|
1269
|
-
window.location.href = authUrl.toString();
|
|
1270
|
-
}
|
|
1362
|
+
window.location.href = authUrl.toString();
|
|
1271
1363
|
}
|
|
1272
1364
|
/**
|
|
1273
1365
|
* Logout and clear stored tokens
|
|
1274
1366
|
*/
|
|
1275
1367
|
logout(redirectUrl) {
|
|
1276
1368
|
this.clearTokens();
|
|
1277
|
-
if (redirectUrl &&
|
|
1369
|
+
if (redirectUrl && hasWindowLocation()) {
|
|
1278
1370
|
window.location.href = redirectUrl;
|
|
1279
1371
|
}
|
|
1280
1372
|
}
|
|
@@ -1558,144 +1650,251 @@ var BlinkAuth = class {
|
|
|
1558
1650
|
return this.signInWithProvider("microsoft", options);
|
|
1559
1651
|
}
|
|
1560
1652
|
/**
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1653
|
+
* Initiate OAuth for mobile without deep linking (expo-web-browser pattern)
|
|
1654
|
+
*
|
|
1655
|
+
* This method:
|
|
1656
|
+
* 1. Generates a unique session ID
|
|
1657
|
+
* 2. Returns OAuth URL with session parameter
|
|
1658
|
+
* 3. App opens URL in expo-web-browser
|
|
1659
|
+
* 4. App polls checkMobileOAuthSession() until complete
|
|
1660
|
+
*
|
|
1661
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1662
|
+
* @param options - Optional metadata
|
|
1663
|
+
* @returns Session ID and OAuth URL
|
|
1664
|
+
*
|
|
1665
|
+
* @example
|
|
1666
|
+
* // React Native with expo-web-browser
|
|
1667
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1668
|
+
*
|
|
1669
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1670
|
+
*
|
|
1671
|
+
* // Open browser
|
|
1672
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1673
|
+
*
|
|
1674
|
+
* // Poll for completion
|
|
1675
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId);
|
|
1676
|
+
* console.log('Authenticated:', user.email);
|
|
1563
1677
|
*/
|
|
1564
|
-
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1678
|
+
async initiateMobileOAuth(provider, options) {
|
|
1679
|
+
if (this.authConfig.mode !== "headless") {
|
|
1680
|
+
throw new BlinkAuthError(
|
|
1681
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
1682
|
+
"initiateMobileOAuth is only available in headless mode"
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
const sessionId = this.generateSessionId();
|
|
1686
|
+
const authUrl = new URL("/auth", this.authUrl);
|
|
1687
|
+
authUrl.searchParams.set("provider", provider);
|
|
1688
|
+
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1689
|
+
authUrl.searchParams.set("mode", "mobile-session");
|
|
1690
|
+
authUrl.searchParams.set("session_id", sessionId);
|
|
1691
|
+
if (options?.metadata) {
|
|
1692
|
+
authUrl.searchParams.set("metadata", JSON.stringify(options.metadata));
|
|
1693
|
+
}
|
|
1694
|
+
return {
|
|
1695
|
+
sessionId,
|
|
1696
|
+
authUrl: authUrl.toString()
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Check mobile OAuth session status (single check)
|
|
1701
|
+
*
|
|
1702
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1703
|
+
* @returns Tokens if session is complete, null if still pending
|
|
1704
|
+
*/
|
|
1705
|
+
async checkMobileOAuthSession(sessionId) {
|
|
1706
|
+
try {
|
|
1707
|
+
const response = await fetch(`${this.authUrl}/api/auth/mobile-session/${sessionId}`, {
|
|
1708
|
+
method: "GET",
|
|
1709
|
+
headers: {
|
|
1710
|
+
"Content-Type": "application/json"
|
|
1711
|
+
}
|
|
1592
1712
|
});
|
|
1713
|
+
if (response.status === 404 || response.status === 202) {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
if (!response.ok) {
|
|
1717
|
+
const errorData = await response.json();
|
|
1718
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1719
|
+
throw new BlinkAuthError(
|
|
1720
|
+
errorCode,
|
|
1721
|
+
errorData.error || "Failed to check OAuth session"
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
const data = await response.json();
|
|
1725
|
+
return {
|
|
1726
|
+
access_token: data.access_token,
|
|
1727
|
+
refresh_token: data.refresh_token,
|
|
1728
|
+
token_type: data.token_type || "Bearer",
|
|
1729
|
+
expires_in: data.expires_in || 3600,
|
|
1730
|
+
refresh_expires_in: data.refresh_expires_in
|
|
1731
|
+
};
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
if (error instanceof BlinkAuthError) {
|
|
1734
|
+
throw error;
|
|
1735
|
+
}
|
|
1736
|
+
throw new BlinkAuthError(
|
|
1737
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1738
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1739
|
+
);
|
|
1593
1740
|
}
|
|
1594
|
-
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Poll mobile OAuth session until complete (convenience method)
|
|
1744
|
+
*
|
|
1745
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1746
|
+
* @param options - Polling options
|
|
1747
|
+
* @returns Authenticated user
|
|
1748
|
+
*
|
|
1749
|
+
* @example
|
|
1750
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1751
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1752
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId, {
|
|
1753
|
+
* maxAttempts: 60,
|
|
1754
|
+
* intervalMs: 1000
|
|
1755
|
+
* });
|
|
1756
|
+
*/
|
|
1757
|
+
async pollMobileOAuthSession(sessionId, options) {
|
|
1758
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
1759
|
+
const intervalMs = options?.intervalMs || 1e3;
|
|
1760
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1761
|
+
const tokens = await this.checkMobileOAuthSession(sessionId);
|
|
1762
|
+
if (tokens) {
|
|
1763
|
+
await this.setTokens(tokens, true);
|
|
1764
|
+
return this.authState.user;
|
|
1765
|
+
}
|
|
1766
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1767
|
+
}
|
|
1768
|
+
throw new BlinkAuthError(
|
|
1769
|
+
"AUTH_TIMEOUT" /* AUTH_TIMEOUT */,
|
|
1770
|
+
"Mobile OAuth session timed out"
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Sign in with OAuth provider using expo-web-browser (React Native)
|
|
1775
|
+
*
|
|
1776
|
+
* This is a convenience method that handles the entire flow:
|
|
1777
|
+
* 1. Initiates mobile OAuth session
|
|
1778
|
+
* 2. Returns auth URL to open in WebBrowser
|
|
1779
|
+
* 3. Provides polling function to call after browser opens
|
|
1780
|
+
*
|
|
1781
|
+
* @param provider - OAuth provider
|
|
1782
|
+
* @returns Object with authUrl and authenticate function
|
|
1783
|
+
*
|
|
1784
|
+
* @example
|
|
1785
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1786
|
+
*
|
|
1787
|
+
* const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google');
|
|
1788
|
+
*
|
|
1789
|
+
* // Open browser
|
|
1790
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1791
|
+
*
|
|
1792
|
+
* // Wait for authentication
|
|
1793
|
+
* const user = await authenticate();
|
|
1794
|
+
*/
|
|
1795
|
+
async signInWithProviderMobile(provider, options) {
|
|
1796
|
+
const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
|
|
1797
|
+
return {
|
|
1798
|
+
authUrl,
|
|
1799
|
+
authenticate: () => this.pollMobileOAuthSession(sessionId, {
|
|
1800
|
+
maxAttempts: 60,
|
|
1801
|
+
intervalMs: 1e3
|
|
1802
|
+
})
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
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
|
|
1826
|
+
*/
|
|
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
|
+
);
|
|
1595
1832
|
}
|
|
1596
1833
|
/**
|
|
1597
1834
|
* Generic provider sign-in method (headless mode)
|
|
1598
|
-
*
|
|
1835
|
+
*
|
|
1836
|
+
* Supports both web (popup) and React Native (deep link) OAuth flows.
|
|
1837
|
+
*
|
|
1838
|
+
* **React Native Setup:**
|
|
1839
|
+
* 1. Configure deep linking in your app (app.json):
|
|
1840
|
+
* ```json
|
|
1841
|
+
* { "expo": { "scheme": "com.yourapp" } }
|
|
1842
|
+
* ```
|
|
1843
|
+
*
|
|
1844
|
+
* 2. Add redirect URL in Blink Dashboard:
|
|
1845
|
+
* `com.yourapp://**`
|
|
1846
|
+
*
|
|
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
|
+
* ```
|
|
1858
|
+
*
|
|
1859
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1860
|
+
* @param options - Optional redirect URL and metadata
|
|
1861
|
+
* @returns Promise that resolves with authenticated user
|
|
1599
1862
|
*/
|
|
1600
1863
|
async signInWithProvider(provider, options) {
|
|
1601
1864
|
if (this.authConfig.mode !== "headless") {
|
|
1602
1865
|
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
|
|
1603
1866
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (!redirectUrl && typeof window !== "undefined") {
|
|
1607
|
-
if (window.location.href.startsWith("http")) {
|
|
1608
|
-
redirectUrl = window.location.href;
|
|
1609
|
-
} else {
|
|
1610
|
-
redirectUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
1611
|
-
}
|
|
1867
|
+
if (!hasWindow()) {
|
|
1868
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, "signInWithProvider requires a browser environment");
|
|
1612
1869
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
redirectUrlObj.searchParams.delete("access_token");
|
|
1616
|
-
redirectUrlObj.searchParams.delete("refresh_token");
|
|
1617
|
-
redirectUrlObj.searchParams.delete("state");
|
|
1618
|
-
redirectUrlObj.searchParams.delete("error");
|
|
1619
|
-
redirectUrlObj.searchParams.delete("error_description");
|
|
1620
|
-
redirectUrlObj.hash = "";
|
|
1621
|
-
redirectUrl = redirectUrlObj.toString();
|
|
1622
|
-
} catch (e) {
|
|
1623
|
-
console.warn("Failed to clean redirect URL:", e);
|
|
1624
|
-
}
|
|
1625
|
-
try {
|
|
1626
|
-
if (typeof window !== "undefined") {
|
|
1627
|
-
sessionStorage.setItem("blink_oauth_state", state);
|
|
1628
|
-
sessionStorage.setItem("blink_oauth_redirect_url", redirectUrl);
|
|
1629
|
-
console.log("\u{1F4BE} Stored OAuth state:", { state, redirectUrl });
|
|
1630
|
-
}
|
|
1631
|
-
} catch (e) {
|
|
1632
|
-
console.warn("Failed to store OAuth state in sessionStorage:", e);
|
|
1633
|
-
}
|
|
1634
|
-
const isSafari = this.isSafari();
|
|
1635
|
-
const forceRedirect = options?.forceRedirect === true;
|
|
1636
|
-
if ((isSafari || forceRedirect) && typeof window !== "undefined") {
|
|
1637
|
-
console.log("\u{1F310} Using redirect flow (no popup)", {
|
|
1638
|
-
provider,
|
|
1639
|
-
projectId: this.config.projectId,
|
|
1640
|
-
state,
|
|
1641
|
-
redirectUrl,
|
|
1642
|
-
authUrl: this.authUrl,
|
|
1643
|
-
reason: isSafari ? "Safari detected" : "forceRedirect option set"
|
|
1644
|
-
});
|
|
1645
|
-
const authRedirectUrl = new URL("/auth", this.authUrl);
|
|
1646
|
-
authRedirectUrl.searchParams.set("provider", provider);
|
|
1647
|
-
authRedirectUrl.searchParams.set("project_id", this.config.projectId);
|
|
1648
|
-
authRedirectUrl.searchParams.set("state", state);
|
|
1649
|
-
authRedirectUrl.searchParams.set("mode", "redirect");
|
|
1650
|
-
authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1651
|
-
console.log("\u{1F504} Redirecting to:", authRedirectUrl.toString());
|
|
1652
|
-
window.location.href = authRedirectUrl.toString();
|
|
1653
|
-
return new Promise(() => {
|
|
1654
|
-
});
|
|
1870
|
+
if (isReactNative2()) {
|
|
1871
|
+
return this.signInWithProviderReactNative(provider, options);
|
|
1655
1872
|
}
|
|
1656
1873
|
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() || "";
|
|
1657
1883
|
const popupUrl = new URL("/auth", this.authUrl);
|
|
1658
1884
|
popupUrl.searchParams.set("provider", provider);
|
|
1659
1885
|
popupUrl.searchParams.set("project_id", this.config.projectId);
|
|
1660
1886
|
popupUrl.searchParams.set("state", state);
|
|
1661
1887
|
popupUrl.searchParams.set("mode", "popup");
|
|
1662
1888
|
popupUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1663
|
-
if (typeof window !== "undefined") {
|
|
1664
|
-
popupUrl.searchParams.set("opener_origin", window.location.origin);
|
|
1665
|
-
}
|
|
1666
1889
|
const popup = window.open(
|
|
1667
1890
|
popupUrl.toString(),
|
|
1668
1891
|
"blink-auth",
|
|
1669
1892
|
"width=500,height=600,scrollbars=yes,resizable=yes"
|
|
1670
1893
|
);
|
|
1671
1894
|
if (!popup) {
|
|
1672
|
-
|
|
1673
|
-
const authRedirectUrl = new URL("/auth", this.authUrl);
|
|
1674
|
-
authRedirectUrl.searchParams.set("provider", provider);
|
|
1675
|
-
authRedirectUrl.searchParams.set("project_id", this.config.projectId);
|
|
1676
|
-
authRedirectUrl.searchParams.set("state", state);
|
|
1677
|
-
authRedirectUrl.searchParams.set("mode", "redirect");
|
|
1678
|
-
authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1679
|
-
console.log("\u{1F504} Falling back to redirect:", authRedirectUrl.toString());
|
|
1680
|
-
window.location.href = authRedirectUrl.toString();
|
|
1895
|
+
reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
|
|
1681
1896
|
return;
|
|
1682
1897
|
}
|
|
1683
|
-
try {
|
|
1684
|
-
if (popup.closed || !popup.window) {
|
|
1685
|
-
console.warn("\u26A0\uFE0F Popup appears to be blocked (closed immediately), falling back to redirect flow");
|
|
1686
|
-
const authRedirectUrl = new URL("/auth", this.authUrl);
|
|
1687
|
-
authRedirectUrl.searchParams.set("provider", provider);
|
|
1688
|
-
authRedirectUrl.searchParams.set("project_id", this.config.projectId);
|
|
1689
|
-
authRedirectUrl.searchParams.set("state", state);
|
|
1690
|
-
authRedirectUrl.searchParams.set("mode", "redirect");
|
|
1691
|
-
authRedirectUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1692
|
-
console.log("\u{1F504} Falling back to redirect:", authRedirectUrl.toString());
|
|
1693
|
-
window.location.href = authRedirectUrl.toString();
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
} catch (e) {
|
|
1697
|
-
console.log("\u26A0\uFE0F Could not verify popup state, assuming it opened successfully");
|
|
1698
|
-
}
|
|
1699
1898
|
let timeoutId;
|
|
1700
1899
|
const messageListener = (event) => {
|
|
1701
1900
|
let allowed = false;
|
|
@@ -1705,16 +1904,13 @@ var BlinkAuth = class {
|
|
|
1705
1904
|
} catch {
|
|
1706
1905
|
}
|
|
1707
1906
|
if (event.origin === "http://localhost:3000" || event.origin === "http://localhost:3001") allowed = true;
|
|
1708
|
-
if (
|
|
1709
|
-
if (!allowed)
|
|
1710
|
-
console.log("\u{1F6AB} Blocked postMessage from untrusted origin:", event.origin);
|
|
1711
|
-
return;
|
|
1712
|
-
}
|
|
1713
|
-
console.log("\u2705 Accepted postMessage from origin:", event.origin);
|
|
1907
|
+
if (event.origin.endsWith(".sites.blink.new") || event.origin.endsWith(".preview-blink.com")) allowed = true;
|
|
1908
|
+
if (!allowed) return;
|
|
1714
1909
|
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
1715
1910
|
const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
|
|
1716
1911
|
try {
|
|
1717
|
-
const
|
|
1912
|
+
const sessionStorage = getSessionStorage();
|
|
1913
|
+
const expected = sessionStorage?.getItem("blink_oauth_state");
|
|
1718
1914
|
if (returnedState && expected && returnedState !== expected) {
|
|
1719
1915
|
reject(new BlinkAuthError("VERIFICATION_FAILED" /* VERIFICATION_FAILED */, "State mismatch"));
|
|
1720
1916
|
clearTimeout(timeoutId);
|
|
@@ -2238,6 +2434,48 @@ var BlinkAuth = class {
|
|
|
2238
2434
|
};
|
|
2239
2435
|
await this.setTokens(tokens, persist);
|
|
2240
2436
|
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Manually set auth session from tokens (React Native deep link OAuth)
|
|
2439
|
+
*
|
|
2440
|
+
* Use this method to set the user session after receiving tokens from a deep link callback.
|
|
2441
|
+
* This is the React Native equivalent of automatic URL token detection on web.
|
|
2442
|
+
*
|
|
2443
|
+
* @param tokens - Auth tokens received from deep link or OAuth callback
|
|
2444
|
+
* @param persist - Whether to persist tokens to storage (default: true)
|
|
2445
|
+
*
|
|
2446
|
+
* @example
|
|
2447
|
+
* // React Native: Handle deep link OAuth callback
|
|
2448
|
+
* import * as Linking from 'expo-linking'
|
|
2449
|
+
*
|
|
2450
|
+
* Linking.addEventListener('url', async ({ url }) => {
|
|
2451
|
+
* const { queryParams } = Linking.parse(url)
|
|
2452
|
+
*
|
|
2453
|
+
* if (queryParams.access_token) {
|
|
2454
|
+
* await blink.auth.setSession({
|
|
2455
|
+
* access_token: queryParams.access_token,
|
|
2456
|
+
* refresh_token: queryParams.refresh_token,
|
|
2457
|
+
* expires_in: parseInt(queryParams.expires_in) || 3600,
|
|
2458
|
+
* refresh_expires_in: parseInt(queryParams.refresh_expires_in)
|
|
2459
|
+
* })
|
|
2460
|
+
*
|
|
2461
|
+
* console.log('User authenticated:', blink.auth.currentUser())
|
|
2462
|
+
* }
|
|
2463
|
+
* })
|
|
2464
|
+
*/
|
|
2465
|
+
async setSession(tokens, persist = true) {
|
|
2466
|
+
const authTokens = {
|
|
2467
|
+
access_token: tokens.access_token,
|
|
2468
|
+
refresh_token: tokens.refresh_token,
|
|
2469
|
+
token_type: "Bearer",
|
|
2470
|
+
expires_in: tokens.expires_in || 3600,
|
|
2471
|
+
// Default 1 hour
|
|
2472
|
+
refresh_expires_in: tokens.refresh_expires_in,
|
|
2473
|
+
issued_at: Math.floor(Date.now() / 1e3)
|
|
2474
|
+
};
|
|
2475
|
+
await this.setTokens(authTokens, persist);
|
|
2476
|
+
const user = await this.me();
|
|
2477
|
+
return user;
|
|
2478
|
+
}
|
|
2241
2479
|
/**
|
|
2242
2480
|
* Refresh access token using refresh token
|
|
2243
2481
|
*/
|
|
@@ -2470,50 +2708,18 @@ var BlinkAuth = class {
|
|
|
2470
2708
|
return null;
|
|
2471
2709
|
}
|
|
2472
2710
|
}
|
|
2473
|
-
/**
|
|
2474
|
-
* Extract URL parameters from both search params and hash fragments
|
|
2475
|
-
* Safari OAuth redirects often use hash fragments instead of query params
|
|
2476
|
-
*/
|
|
2477
|
-
extractUrlParams() {
|
|
2478
|
-
const params = new URLSearchParams();
|
|
2479
|
-
if (typeof window !== "undefined" && window.location.search) {
|
|
2480
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
2481
|
-
for (const [key, value] of searchParams.entries()) {
|
|
2482
|
-
params.set(key, value);
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
if (typeof window !== "undefined" && window.location.hash) {
|
|
2486
|
-
const hash = window.location.hash.substring(1);
|
|
2487
|
-
const hashParams = new URLSearchParams(hash);
|
|
2488
|
-
for (const [key, value] of hashParams.entries()) {
|
|
2489
|
-
if (!params.has(key)) {
|
|
2490
|
-
params.set(key, value);
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
return params;
|
|
2495
|
-
}
|
|
2496
2711
|
extractTokensFromUrl() {
|
|
2497
|
-
|
|
2498
|
-
|
|
2712
|
+
const search = getLocationSearch();
|
|
2713
|
+
if (!search) return null;
|
|
2714
|
+
const params = new URLSearchParams(search);
|
|
2499
2715
|
const accessToken = params.get("access_token");
|
|
2500
2716
|
const refreshToken = params.get("refresh_token");
|
|
2501
|
-
const state = params.get("state");
|
|
2502
|
-
const error = params.get("error");
|
|
2503
2717
|
console.log("\u{1F50D} Extracting tokens from URL:", {
|
|
2504
|
-
url:
|
|
2505
|
-
search: window.location.search,
|
|
2506
|
-
hash: window.location.hash,
|
|
2718
|
+
url: getLocationHref(),
|
|
2507
2719
|
accessToken: accessToken ? `${accessToken.substring(0, 20)}...` : null,
|
|
2508
2720
|
refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null,
|
|
2509
|
-
state: state ? `${state.substring(0, 10)}...` : null,
|
|
2510
|
-
error: error || null,
|
|
2511
2721
|
allParams: Object.fromEntries(params.entries())
|
|
2512
2722
|
});
|
|
2513
|
-
if (error) {
|
|
2514
|
-
console.log("\u274C Error parameter found in URL, not extracting tokens");
|
|
2515
|
-
return null;
|
|
2516
|
-
}
|
|
2517
2723
|
if (accessToken) {
|
|
2518
2724
|
const tokens = {
|
|
2519
2725
|
access_token: accessToken,
|
|
@@ -2528,8 +2734,7 @@ var BlinkAuth = class {
|
|
|
2528
2734
|
};
|
|
2529
2735
|
console.log("\u2705 Tokens extracted successfully:", {
|
|
2530
2736
|
hasAccessToken: !!tokens.access_token,
|
|
2531
|
-
hasRefreshToken: !!tokens.refresh_token
|
|
2532
|
-
state: state || "no state"
|
|
2737
|
+
hasRefreshToken: !!tokens.refresh_token
|
|
2533
2738
|
});
|
|
2534
2739
|
return tokens;
|
|
2535
2740
|
}
|
|
@@ -2537,8 +2742,9 @@ var BlinkAuth = class {
|
|
|
2537
2742
|
return null;
|
|
2538
2743
|
}
|
|
2539
2744
|
clearUrlTokens() {
|
|
2540
|
-
|
|
2541
|
-
|
|
2745
|
+
const href = getLocationHref();
|
|
2746
|
+
if (!href || !hasWindowLocation()) return;
|
|
2747
|
+
const url = new URL(href);
|
|
2542
2748
|
url.searchParams.delete("access_token");
|
|
2543
2749
|
url.searchParams.delete("refresh_token");
|
|
2544
2750
|
url.searchParams.delete("token_type");
|
|
@@ -2549,12 +2755,11 @@ var BlinkAuth = class {
|
|
|
2549
2755
|
url.searchParams.delete("code");
|
|
2550
2756
|
url.searchParams.delete("error");
|
|
2551
2757
|
url.searchParams.delete("error_description");
|
|
2552
|
-
url.hash = "";
|
|
2553
2758
|
window.history.replaceState({}, "", url.toString());
|
|
2554
|
-
console.log("\u{1F9F9} URL cleaned up, removed auth parameters
|
|
2759
|
+
console.log("\u{1F9F9} URL cleaned up, removed auth parameters");
|
|
2555
2760
|
}
|
|
2556
2761
|
redirectToAuth() {
|
|
2557
|
-
if (
|
|
2762
|
+
if (hasWindowLocation()) {
|
|
2558
2763
|
this.login();
|
|
2559
2764
|
}
|
|
2560
2765
|
}
|
|
@@ -2586,12 +2791,25 @@ var BlinkAuth = class {
|
|
|
2586
2791
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2587
2792
|
}
|
|
2588
2793
|
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Generate unique session ID for mobile OAuth
|
|
2796
|
+
*/
|
|
2797
|
+
generateSessionId() {
|
|
2798
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2799
|
+
const array = new Uint8Array(32);
|
|
2800
|
+
crypto.getRandomValues(array);
|
|
2801
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2802
|
+
} else {
|
|
2803
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2589
2806
|
/**
|
|
2590
2807
|
* Extract magic link token from URL
|
|
2591
2808
|
*/
|
|
2592
2809
|
extractMagicTokenFromUrl() {
|
|
2593
|
-
|
|
2594
|
-
|
|
2810
|
+
const search = getLocationSearch();
|
|
2811
|
+
if (!search) return null;
|
|
2812
|
+
const params = new URLSearchParams(search);
|
|
2595
2813
|
return params.get("magic_token") || params.get("token");
|
|
2596
2814
|
}
|
|
2597
2815
|
/**
|
|
@@ -2647,7 +2865,7 @@ var BlinkAuth = class {
|
|
|
2647
2865
|
* Setup cross-tab authentication synchronization
|
|
2648
2866
|
*/
|
|
2649
2867
|
setupCrossTabSync() {
|
|
2650
|
-
if (!isWeb) return;
|
|
2868
|
+
if (!isWeb || !hasWindow()) return;
|
|
2651
2869
|
window.addEventListener("storage", (e) => {
|
|
2652
2870
|
if (e.key === this.getStorageKey("tokens")) {
|
|
2653
2871
|
const newTokens = e.newValue ? JSON.parse(e.newValue) : null;
|
|
@@ -4951,9 +5169,9 @@ var BlinkAnalyticsImpl = class {
|
|
|
4951
5169
|
user_id: this.userId,
|
|
4952
5170
|
user_email: this.userEmail,
|
|
4953
5171
|
session_id: sessionId,
|
|
4954
|
-
pathname:
|
|
4955
|
-
referrer:
|
|
4956
|
-
screen_width:
|
|
5172
|
+
pathname: getLocationPathname(),
|
|
5173
|
+
referrer: getDocumentReferrer(),
|
|
5174
|
+
screen_width: getWindowInnerWidth(),
|
|
4957
5175
|
channel,
|
|
4958
5176
|
utm_source: this.utmParams.utm_source || this.persistedAttribution.utm_source || null,
|
|
4959
5177
|
utm_medium: this.utmParams.utm_medium || this.persistedAttribution.utm_medium || null,
|
|
@@ -5106,7 +5324,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5106
5324
|
window.__blinkAnalyticsInstances?.add(this);
|
|
5107
5325
|
}
|
|
5108
5326
|
setupUnloadListener() {
|
|
5109
|
-
if (!isWeb) return;
|
|
5327
|
+
if (!isWeb || !hasWindow()) return;
|
|
5110
5328
|
window.addEventListener("pagehide", () => {
|
|
5111
5329
|
this.flush();
|
|
5112
5330
|
});
|
|
@@ -5116,7 +5334,12 @@ var BlinkAnalyticsImpl = class {
|
|
|
5116
5334
|
}
|
|
5117
5335
|
captureUTMParams() {
|
|
5118
5336
|
if (!isWeb) return;
|
|
5119
|
-
const
|
|
5337
|
+
const search = getLocationSearch();
|
|
5338
|
+
if (!search) {
|
|
5339
|
+
this.utmParams = {};
|
|
5340
|
+
return;
|
|
5341
|
+
}
|
|
5342
|
+
const urlParams = new URLSearchParams(search);
|
|
5120
5343
|
this.utmParams = {
|
|
5121
5344
|
utm_source: urlParams.get("utm_source"),
|
|
5122
5345
|
utm_medium: urlParams.get("utm_medium"),
|
|
@@ -5153,7 +5376,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5153
5376
|
}
|
|
5154
5377
|
}
|
|
5155
5378
|
detectChannel() {
|
|
5156
|
-
const referrer =
|
|
5379
|
+
const referrer = getDocumentReferrer();
|
|
5157
5380
|
const utmMedium = this.utmParams.utm_medium;
|
|
5158
5381
|
this.utmParams.utm_source;
|
|
5159
5382
|
if (utmMedium) {
|