@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.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;
|
|
@@ -1039,13 +1175,16 @@ var BlinkAuth = class {
|
|
|
1039
1175
|
this.authConfig = {
|
|
1040
1176
|
mode: "managed",
|
|
1041
1177
|
// Default mode
|
|
1042
|
-
authUrl: "
|
|
1178
|
+
authUrl: "http://localhost:3000",
|
|
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,7 +1234,7 @@ 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
|
if (event.origin !== "https://blink.new" && event.origin !== "http://localhost:3000" && event.origin !== "http://localhost:3001") {
|
|
1101
1240
|
return;
|
|
@@ -1117,7 +1256,7 @@ var BlinkAuth = class {
|
|
|
1117
1256
|
this.clearTokens();
|
|
1118
1257
|
}
|
|
1119
1258
|
});
|
|
1120
|
-
if (window.parent !== window) {
|
|
1259
|
+
if (hasWindow() && window.parent !== window) {
|
|
1121
1260
|
console.log("\u{1F504} Requesting auth tokens from parent window");
|
|
1122
1261
|
window.parent.postMessage({
|
|
1123
1262
|
type: "BLINK_REQUEST_AUTH_TOKENS",
|
|
@@ -1142,67 +1281,14 @@ var BlinkAuth = class {
|
|
|
1142
1281
|
return;
|
|
1143
1282
|
}
|
|
1144
1283
|
}
|
|
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
|
-
}
|
|
1284
|
+
if (this.authConfig.detectSessionInUrl !== false) {
|
|
1285
|
+
const tokensFromUrl = this.extractTokensFromUrl();
|
|
1286
|
+
if (tokensFromUrl) {
|
|
1287
|
+
console.log("\u{1F4E5} Found tokens in URL, setting them...");
|
|
1288
|
+
await this.setTokens(tokensFromUrl, true);
|
|
1289
|
+
this.clearUrlTokens();
|
|
1290
|
+
console.log("\u2705 Auth initialization complete (from URL)");
|
|
1291
|
+
return;
|
|
1206
1292
|
}
|
|
1207
1293
|
}
|
|
1208
1294
|
const storedTokens = await this.getStoredTokens();
|
|
@@ -1227,11 +1313,11 @@ var BlinkAuth = class {
|
|
|
1227
1313
|
}
|
|
1228
1314
|
}
|
|
1229
1315
|
console.log("\u274C No tokens found");
|
|
1230
|
-
if (this.config.authRequired) {
|
|
1316
|
+
if (this.config.authRequired && hasWindowLocation()) {
|
|
1231
1317
|
console.log("\u{1F504} Auth required, redirecting to auth page...");
|
|
1232
1318
|
this.redirectToAuth();
|
|
1233
1319
|
} else {
|
|
1234
|
-
console.log("\u26A0\uFE0F Auth not required, continuing without authentication");
|
|
1320
|
+
console.log("\u26A0\uFE0F Auth not required or no window.location, continuing without authentication");
|
|
1235
1321
|
}
|
|
1236
1322
|
} finally {
|
|
1237
1323
|
this.setLoading(false);
|
|
@@ -1242,15 +1328,20 @@ var BlinkAuth = class {
|
|
|
1242
1328
|
* Redirect to Blink auth page
|
|
1243
1329
|
*/
|
|
1244
1330
|
login(nextUrl) {
|
|
1331
|
+
if (!hasWindowLocation()) {
|
|
1332
|
+
console.warn("login() called in non-browser environment (no window.location available)");
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1245
1335
|
let redirectUrl = nextUrl || this.authConfig.redirectUrl;
|
|
1246
|
-
if (!redirectUrl
|
|
1247
|
-
|
|
1248
|
-
|
|
1336
|
+
if (!redirectUrl) {
|
|
1337
|
+
const href = getLocationHref();
|
|
1338
|
+
if (href && href.startsWith("http")) {
|
|
1339
|
+
redirectUrl = href;
|
|
1249
1340
|
} else {
|
|
1250
|
-
redirectUrl =
|
|
1341
|
+
redirectUrl = constructFullUrl() || void 0;
|
|
1251
1342
|
}
|
|
1252
1343
|
}
|
|
1253
|
-
if (redirectUrl
|
|
1344
|
+
if (redirectUrl) {
|
|
1254
1345
|
try {
|
|
1255
1346
|
const url = new URL(redirectUrl);
|
|
1256
1347
|
url.searchParams.delete("redirect_url");
|
|
@@ -1265,16 +1356,14 @@ var BlinkAuth = class {
|
|
|
1265
1356
|
if (this.config.projectId) {
|
|
1266
1357
|
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1267
1358
|
}
|
|
1268
|
-
|
|
1269
|
-
window.location.href = authUrl.toString();
|
|
1270
|
-
}
|
|
1359
|
+
window.location.href = authUrl.toString();
|
|
1271
1360
|
}
|
|
1272
1361
|
/**
|
|
1273
1362
|
* Logout and clear stored tokens
|
|
1274
1363
|
*/
|
|
1275
1364
|
logout(redirectUrl) {
|
|
1276
1365
|
this.clearTokens();
|
|
1277
|
-
if (redirectUrl &&
|
|
1366
|
+
if (redirectUrl && hasWindowLocation()) {
|
|
1278
1367
|
window.location.href = redirectUrl;
|
|
1279
1368
|
}
|
|
1280
1369
|
}
|
|
@@ -1523,6 +1612,16 @@ var BlinkAuth = class {
|
|
|
1523
1612
|
}
|
|
1524
1613
|
/**
|
|
1525
1614
|
* Sign in with Google (headless mode)
|
|
1615
|
+
*
|
|
1616
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1617
|
+
*
|
|
1618
|
+
* On React Native, requires `webBrowser` to be configured in client:
|
|
1619
|
+
* ```typescript
|
|
1620
|
+
* const blink = createClient({
|
|
1621
|
+
* auth: { mode: 'headless', webBrowser: WebBrowser }
|
|
1622
|
+
* })
|
|
1623
|
+
* await blink.auth.signInWithGoogle() // Works on both platforms!
|
|
1624
|
+
* ```
|
|
1526
1625
|
*/
|
|
1527
1626
|
async signInWithGoogle(options) {
|
|
1528
1627
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1532,6 +1631,9 @@ var BlinkAuth = class {
|
|
|
1532
1631
|
}
|
|
1533
1632
|
/**
|
|
1534
1633
|
* Sign in with GitHub (headless mode)
|
|
1634
|
+
*
|
|
1635
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1636
|
+
* See signInWithGoogle() for setup instructions.
|
|
1535
1637
|
*/
|
|
1536
1638
|
async signInWithGitHub(options) {
|
|
1537
1639
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1541,6 +1643,9 @@ var BlinkAuth = class {
|
|
|
1541
1643
|
}
|
|
1542
1644
|
/**
|
|
1543
1645
|
* Sign in with Apple (headless mode)
|
|
1646
|
+
*
|
|
1647
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1648
|
+
* See signInWithGoogle() for setup instructions.
|
|
1544
1649
|
*/
|
|
1545
1650
|
async signInWithApple(options) {
|
|
1546
1651
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1550,6 +1655,9 @@ var BlinkAuth = class {
|
|
|
1550
1655
|
}
|
|
1551
1656
|
/**
|
|
1552
1657
|
* Sign in with Microsoft (headless mode)
|
|
1658
|
+
*
|
|
1659
|
+
* **Universal OAuth** - Works on both Web and React Native!
|
|
1660
|
+
* See signInWithGoogle() for setup instructions.
|
|
1553
1661
|
*/
|
|
1554
1662
|
async signInWithMicrosoft(options) {
|
|
1555
1663
|
if (this.authConfig.mode !== "headless") {
|
|
@@ -1558,144 +1666,264 @@ var BlinkAuth = class {
|
|
|
1558
1666
|
return this.signInWithProvider("microsoft", options);
|
|
1559
1667
|
}
|
|
1560
1668
|
/**
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1669
|
+
* Initiate OAuth for mobile without deep linking (expo-web-browser pattern)
|
|
1670
|
+
*
|
|
1671
|
+
* This method:
|
|
1672
|
+
* 1. Generates a unique session ID
|
|
1673
|
+
* 2. Returns OAuth URL with session parameter
|
|
1674
|
+
* 3. App opens URL in expo-web-browser
|
|
1675
|
+
* 4. App polls checkMobileOAuthSession() until complete
|
|
1676
|
+
*
|
|
1677
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1678
|
+
* @param options - Optional metadata
|
|
1679
|
+
* @returns Session ID and OAuth URL
|
|
1680
|
+
*
|
|
1681
|
+
* @example
|
|
1682
|
+
* // React Native with expo-web-browser
|
|
1683
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1684
|
+
*
|
|
1685
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1686
|
+
*
|
|
1687
|
+
* // Open browser
|
|
1688
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1689
|
+
*
|
|
1690
|
+
* // Poll for completion
|
|
1691
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId);
|
|
1692
|
+
* console.log('Authenticated:', user.email);
|
|
1693
|
+
*/
|
|
1694
|
+
async initiateMobileOAuth(provider, options) {
|
|
1695
|
+
if (this.authConfig.mode !== "headless") {
|
|
1696
|
+
throw new BlinkAuthError(
|
|
1697
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
1698
|
+
"initiateMobileOAuth is only available in headless mode"
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
const sessionId = this.generateSessionId();
|
|
1702
|
+
const authUrl = new URL("/auth", this.authUrl);
|
|
1703
|
+
authUrl.searchParams.set("provider", provider);
|
|
1704
|
+
authUrl.searchParams.set("project_id", this.config.projectId);
|
|
1705
|
+
authUrl.searchParams.set("mode", "mobile-session");
|
|
1706
|
+
authUrl.searchParams.set("session_id", sessionId);
|
|
1707
|
+
if (options?.metadata) {
|
|
1708
|
+
authUrl.searchParams.set("metadata", JSON.stringify(options.metadata));
|
|
1709
|
+
}
|
|
1710
|
+
return {
|
|
1711
|
+
sessionId,
|
|
1712
|
+
authUrl: authUrl.toString()
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Check mobile OAuth session status (single check)
|
|
1717
|
+
*
|
|
1718
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1719
|
+
* @returns Tokens if session is complete, null if still pending
|
|
1563
1720
|
*/
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const isMacSafari = /macintosh/i.test(ua) && isSafariVendor && !/chrome|chromium|firefox|edg/i.test(ua);
|
|
1572
|
-
const isSafari = !!(hasSafariUA || isSafariVendor || isIOSSafari || hasSafariProperties || isMacSafari);
|
|
1573
|
-
if (isSafari) {
|
|
1574
|
-
console.log("\u{1F34E} Safari browser detected - will use redirect flow", {
|
|
1575
|
-
hasSafariUA,
|
|
1576
|
-
isSafariVendor,
|
|
1577
|
-
isIOSSafari,
|
|
1578
|
-
hasSafariProperties,
|
|
1579
|
-
isMacSafari,
|
|
1580
|
-
userAgent: ua.substring(0, 100),
|
|
1581
|
-
vendor: navigator.vendor
|
|
1721
|
+
async checkMobileOAuthSession(sessionId) {
|
|
1722
|
+
try {
|
|
1723
|
+
const response = await fetch(`${this.authUrl}/api/auth/mobile-session/${sessionId}`, {
|
|
1724
|
+
method: "GET",
|
|
1725
|
+
headers: {
|
|
1726
|
+
"Content-Type": "application/json"
|
|
1727
|
+
}
|
|
1582
1728
|
});
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1729
|
+
if (response.status === 404 || response.status === 202) {
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
if (!response.ok) {
|
|
1733
|
+
const errorData = await response.json();
|
|
1734
|
+
const errorCode = this.mapErrorCodeFromResponse(errorData.code);
|
|
1735
|
+
throw new BlinkAuthError(
|
|
1736
|
+
errorCode,
|
|
1737
|
+
errorData.error || "Failed to check OAuth session"
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
const data = await response.json();
|
|
1741
|
+
return {
|
|
1742
|
+
access_token: data.access_token,
|
|
1743
|
+
refresh_token: data.refresh_token,
|
|
1744
|
+
token_type: data.token_type || "Bearer",
|
|
1745
|
+
expires_in: data.expires_in || 3600,
|
|
1746
|
+
refresh_expires_in: data.refresh_expires_in
|
|
1747
|
+
};
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
if (error instanceof BlinkAuthError) {
|
|
1750
|
+
throw error;
|
|
1751
|
+
}
|
|
1752
|
+
throw new BlinkAuthError(
|
|
1753
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1754
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Poll mobile OAuth session until complete (convenience method)
|
|
1760
|
+
*
|
|
1761
|
+
* @param sessionId - Session ID from initiateMobileOAuth
|
|
1762
|
+
* @param options - Polling options
|
|
1763
|
+
* @returns Authenticated user
|
|
1764
|
+
*
|
|
1765
|
+
* @example
|
|
1766
|
+
* const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
|
|
1767
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1768
|
+
* const user = await blink.auth.pollMobileOAuthSession(sessionId, {
|
|
1769
|
+
* maxAttempts: 60,
|
|
1770
|
+
* intervalMs: 1000
|
|
1771
|
+
* });
|
|
1772
|
+
*/
|
|
1773
|
+
async pollMobileOAuthSession(sessionId, options) {
|
|
1774
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
1775
|
+
const intervalMs = options?.intervalMs || 1e3;
|
|
1776
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1777
|
+
const tokens = await this.checkMobileOAuthSession(sessionId);
|
|
1778
|
+
if (tokens) {
|
|
1779
|
+
await this.setTokens(tokens, true);
|
|
1780
|
+
return this.authState.user;
|
|
1781
|
+
}
|
|
1782
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1783
|
+
}
|
|
1784
|
+
throw new BlinkAuthError(
|
|
1785
|
+
"AUTH_TIMEOUT" /* AUTH_TIMEOUT */,
|
|
1786
|
+
"Mobile OAuth session timed out"
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Sign in with OAuth provider using expo-web-browser (React Native)
|
|
1791
|
+
*
|
|
1792
|
+
* This is a convenience method that handles the entire flow:
|
|
1793
|
+
* 1. Initiates mobile OAuth session
|
|
1794
|
+
* 2. Returns auth URL to open in WebBrowser
|
|
1795
|
+
* 3. Provides polling function to call after browser opens
|
|
1796
|
+
*
|
|
1797
|
+
* @param provider - OAuth provider
|
|
1798
|
+
* @returns Object with authUrl and authenticate function
|
|
1799
|
+
*
|
|
1800
|
+
* @example
|
|
1801
|
+
* import * as WebBrowser from 'expo-web-browser';
|
|
1802
|
+
*
|
|
1803
|
+
* const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google');
|
|
1804
|
+
*
|
|
1805
|
+
* // Open browser
|
|
1806
|
+
* await WebBrowser.openAuthSessionAsync(authUrl);
|
|
1807
|
+
*
|
|
1808
|
+
* // Wait for authentication
|
|
1809
|
+
* const user = await authenticate();
|
|
1810
|
+
*/
|
|
1811
|
+
async signInWithProviderMobile(provider, options) {
|
|
1812
|
+
const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
|
|
1813
|
+
return {
|
|
1814
|
+
authUrl,
|
|
1815
|
+
authenticate: () => this.pollMobileOAuthSession(sessionId, {
|
|
1816
|
+
maxAttempts: 60,
|
|
1817
|
+
intervalMs: 1e3
|
|
1818
|
+
})
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Universal OAuth flow using session-based authentication (internal)
|
|
1823
|
+
* Works on ALL platforms: Web, iOS, Android
|
|
1824
|
+
* Uses expo-web-browser to open auth URL and polls for completion
|
|
1825
|
+
*/
|
|
1826
|
+
async signInWithProviderUniversal(provider, options) {
|
|
1827
|
+
const webBrowser = this.authConfig.webBrowser;
|
|
1828
|
+
if (!webBrowser) {
|
|
1829
|
+
throw new BlinkAuthError(
|
|
1830
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1831
|
+
"webBrowser module is required for universal OAuth flow"
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
const { sessionId, authUrl } = await this.initiateMobileOAuth(provider, options);
|
|
1835
|
+
console.log("\u{1F510} Opening OAuth browser for", provider);
|
|
1836
|
+
const result = await webBrowser.openAuthSessionAsync(authUrl);
|
|
1837
|
+
console.log("\u{1F510} Browser closed with result:", result.type);
|
|
1838
|
+
try {
|
|
1839
|
+
const user = await this.pollMobileOAuthSession(sessionId, {
|
|
1840
|
+
maxAttempts: 60,
|
|
1841
|
+
// 30 seconds (500ms intervals)
|
|
1842
|
+
intervalMs: 500
|
|
1592
1843
|
});
|
|
1844
|
+
console.log("\u2705 OAuth completed successfully");
|
|
1845
|
+
return user;
|
|
1846
|
+
} catch (pollError) {
|
|
1847
|
+
if (result.type === "cancel" || result.type === "dismiss") {
|
|
1848
|
+
throw new BlinkAuthError(
|
|
1849
|
+
"POPUP_CANCELED" /* POPUP_CANCELED */,
|
|
1850
|
+
"Authentication was canceled"
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
throw pollError;
|
|
1593
1854
|
}
|
|
1594
|
-
return isSafari;
|
|
1595
1855
|
}
|
|
1596
1856
|
/**
|
|
1597
1857
|
* Generic provider sign-in method (headless mode)
|
|
1598
|
-
*
|
|
1858
|
+
*
|
|
1859
|
+
* **Universal OAuth** - Works seamlessly on both Web and React Native!
|
|
1860
|
+
*
|
|
1861
|
+
* When `webBrowser` is configured in the client, this method automatically
|
|
1862
|
+
* uses the session-based OAuth flow that works on ALL platforms.
|
|
1863
|
+
*
|
|
1864
|
+
* **Universal Setup (configure once, works everywhere):**
|
|
1865
|
+
* ```typescript
|
|
1866
|
+
* import * as WebBrowser from 'expo-web-browser'
|
|
1867
|
+
* import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
1868
|
+
*
|
|
1869
|
+
* const blink = createClient({
|
|
1870
|
+
* projectId: 'your-project',
|
|
1871
|
+
* auth: {
|
|
1872
|
+
* mode: 'headless',
|
|
1873
|
+
* webBrowser: WebBrowser // Pass the module here
|
|
1874
|
+
* },
|
|
1875
|
+
* storage: new AsyncStorageAdapter(AsyncStorage)
|
|
1876
|
+
* })
|
|
1877
|
+
*
|
|
1878
|
+
* // Now this works on ALL platforms - no platform checks needed!
|
|
1879
|
+
* const user = await blink.auth.signInWithGoogle()
|
|
1880
|
+
* ```
|
|
1881
|
+
*
|
|
1882
|
+
* @param provider - OAuth provider (google, github, apple, etc.)
|
|
1883
|
+
* @param options - Optional redirect URL and metadata
|
|
1884
|
+
* @returns Promise that resolves with authenticated user
|
|
1599
1885
|
*/
|
|
1600
1886
|
async signInWithProvider(provider, options) {
|
|
1601
1887
|
if (this.authConfig.mode !== "headless") {
|
|
1602
1888
|
throw new BlinkAuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, "signInWithProvider is only available in headless mode");
|
|
1603
1889
|
}
|
|
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
|
-
}
|
|
1890
|
+
if (this.authConfig.webBrowser) {
|
|
1891
|
+
return this.signInWithProviderUniversal(provider, options);
|
|
1612
1892
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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);
|
|
1893
|
+
if (isReactNative2()) {
|
|
1894
|
+
throw new BlinkAuthError(
|
|
1895
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1896
|
+
'React Native OAuth requires webBrowser in config!\n\nimport * as WebBrowser from "expo-web-browser";\n\nconst blink = createClient({\n projectId: "your-project",\n auth: {\n mode: "headless",\n webBrowser: WebBrowser\n }\n})\n\nawait blink.auth.signInWithGoogle() // Works on all platforms!'
|
|
1897
|
+
);
|
|
1624
1898
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
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
|
-
});
|
|
1899
|
+
if (!hasWindow()) {
|
|
1900
|
+
throw new BlinkAuthError("NETWORK_ERROR" /* NETWORK_ERROR */, "signInWithProvider requires a browser environment");
|
|
1655
1901
|
}
|
|
1656
1902
|
return new Promise((resolve, reject) => {
|
|
1903
|
+
const state = this.generateState();
|
|
1904
|
+
try {
|
|
1905
|
+
const sessionStorage = getSessionStorage();
|
|
1906
|
+
if (sessionStorage) {
|
|
1907
|
+
sessionStorage.setItem("blink_oauth_state", state);
|
|
1908
|
+
}
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
const redirectUrl = options?.redirectUrl || getLocationOrigin() || "";
|
|
1657
1912
|
const popupUrl = new URL("/auth", this.authUrl);
|
|
1658
1913
|
popupUrl.searchParams.set("provider", provider);
|
|
1659
1914
|
popupUrl.searchParams.set("project_id", this.config.projectId);
|
|
1660
1915
|
popupUrl.searchParams.set("state", state);
|
|
1661
1916
|
popupUrl.searchParams.set("mode", "popup");
|
|
1662
1917
|
popupUrl.searchParams.set("redirect_url", redirectUrl);
|
|
1663
|
-
if (typeof window !== "undefined") {
|
|
1664
|
-
popupUrl.searchParams.set("opener_origin", window.location.origin);
|
|
1665
|
-
}
|
|
1666
1918
|
const popup = window.open(
|
|
1667
1919
|
popupUrl.toString(),
|
|
1668
1920
|
"blink-auth",
|
|
1669
1921
|
"width=500,height=600,scrollbars=yes,resizable=yes"
|
|
1670
1922
|
);
|
|
1671
1923
|
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();
|
|
1924
|
+
reject(new BlinkAuthError("POPUP_CANCELED" /* POPUP_CANCELED */, "Popup was blocked"));
|
|
1681
1925
|
return;
|
|
1682
1926
|
}
|
|
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
1927
|
let timeoutId;
|
|
1700
1928
|
const messageListener = (event) => {
|
|
1701
1929
|
let allowed = false;
|
|
@@ -1705,16 +1933,12 @@ var BlinkAuth = class {
|
|
|
1705
1933
|
} catch {
|
|
1706
1934
|
}
|
|
1707
1935
|
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);
|
|
1936
|
+
if (!allowed) return;
|
|
1714
1937
|
if (event.data?.type === "BLINK_AUTH_TOKENS") {
|
|
1715
1938
|
const { access_token, refresh_token, token_type, expires_in, refresh_expires_in, projectId, state: returnedState } = event.data;
|
|
1716
1939
|
try {
|
|
1717
|
-
const
|
|
1940
|
+
const sessionStorage = getSessionStorage();
|
|
1941
|
+
const expected = sessionStorage?.getItem("blink_oauth_state");
|
|
1718
1942
|
if (returnedState && expected && returnedState !== expected) {
|
|
1719
1943
|
reject(new BlinkAuthError("VERIFICATION_FAILED" /* VERIFICATION_FAILED */, "State mismatch"));
|
|
1720
1944
|
clearTimeout(timeoutId);
|
|
@@ -2238,6 +2462,48 @@ var BlinkAuth = class {
|
|
|
2238
2462
|
};
|
|
2239
2463
|
await this.setTokens(tokens, persist);
|
|
2240
2464
|
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Manually set auth session from tokens (React Native deep link OAuth)
|
|
2467
|
+
*
|
|
2468
|
+
* Use this method to set the user session after receiving tokens from a deep link callback.
|
|
2469
|
+
* This is the React Native equivalent of automatic URL token detection on web.
|
|
2470
|
+
*
|
|
2471
|
+
* @param tokens - Auth tokens received from deep link or OAuth callback
|
|
2472
|
+
* @param persist - Whether to persist tokens to storage (default: true)
|
|
2473
|
+
*
|
|
2474
|
+
* @example
|
|
2475
|
+
* // React Native: Handle deep link OAuth callback
|
|
2476
|
+
* import * as Linking from 'expo-linking'
|
|
2477
|
+
*
|
|
2478
|
+
* Linking.addEventListener('url', async ({ url }) => {
|
|
2479
|
+
* const { queryParams } = Linking.parse(url)
|
|
2480
|
+
*
|
|
2481
|
+
* if (queryParams.access_token) {
|
|
2482
|
+
* await blink.auth.setSession({
|
|
2483
|
+
* access_token: queryParams.access_token,
|
|
2484
|
+
* refresh_token: queryParams.refresh_token,
|
|
2485
|
+
* expires_in: parseInt(queryParams.expires_in) || 3600,
|
|
2486
|
+
* refresh_expires_in: parseInt(queryParams.refresh_expires_in)
|
|
2487
|
+
* })
|
|
2488
|
+
*
|
|
2489
|
+
* console.log('User authenticated:', blink.auth.currentUser())
|
|
2490
|
+
* }
|
|
2491
|
+
* })
|
|
2492
|
+
*/
|
|
2493
|
+
async setSession(tokens, persist = true) {
|
|
2494
|
+
const authTokens = {
|
|
2495
|
+
access_token: tokens.access_token,
|
|
2496
|
+
refresh_token: tokens.refresh_token,
|
|
2497
|
+
token_type: "Bearer",
|
|
2498
|
+
expires_in: tokens.expires_in || 3600,
|
|
2499
|
+
// Default 1 hour
|
|
2500
|
+
refresh_expires_in: tokens.refresh_expires_in,
|
|
2501
|
+
issued_at: Math.floor(Date.now() / 1e3)
|
|
2502
|
+
};
|
|
2503
|
+
await this.setTokens(authTokens, persist);
|
|
2504
|
+
const user = await this.me();
|
|
2505
|
+
return user;
|
|
2506
|
+
}
|
|
2241
2507
|
/**
|
|
2242
2508
|
* Refresh access token using refresh token
|
|
2243
2509
|
*/
|
|
@@ -2470,50 +2736,18 @@ var BlinkAuth = class {
|
|
|
2470
2736
|
return null;
|
|
2471
2737
|
}
|
|
2472
2738
|
}
|
|
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
2739
|
extractTokensFromUrl() {
|
|
2497
|
-
|
|
2498
|
-
|
|
2740
|
+
const search = getLocationSearch();
|
|
2741
|
+
if (!search) return null;
|
|
2742
|
+
const params = new URLSearchParams(search);
|
|
2499
2743
|
const accessToken = params.get("access_token");
|
|
2500
2744
|
const refreshToken = params.get("refresh_token");
|
|
2501
|
-
const state = params.get("state");
|
|
2502
|
-
const error = params.get("error");
|
|
2503
2745
|
console.log("\u{1F50D} Extracting tokens from URL:", {
|
|
2504
|
-
url:
|
|
2505
|
-
search: window.location.search,
|
|
2506
|
-
hash: window.location.hash,
|
|
2746
|
+
url: getLocationHref(),
|
|
2507
2747
|
accessToken: accessToken ? `${accessToken.substring(0, 20)}...` : null,
|
|
2508
2748
|
refreshToken: refreshToken ? `${refreshToken.substring(0, 20)}...` : null,
|
|
2509
|
-
state: state ? `${state.substring(0, 10)}...` : null,
|
|
2510
|
-
error: error || null,
|
|
2511
2749
|
allParams: Object.fromEntries(params.entries())
|
|
2512
2750
|
});
|
|
2513
|
-
if (error) {
|
|
2514
|
-
console.log("\u274C Error parameter found in URL, not extracting tokens");
|
|
2515
|
-
return null;
|
|
2516
|
-
}
|
|
2517
2751
|
if (accessToken) {
|
|
2518
2752
|
const tokens = {
|
|
2519
2753
|
access_token: accessToken,
|
|
@@ -2528,8 +2762,7 @@ var BlinkAuth = class {
|
|
|
2528
2762
|
};
|
|
2529
2763
|
console.log("\u2705 Tokens extracted successfully:", {
|
|
2530
2764
|
hasAccessToken: !!tokens.access_token,
|
|
2531
|
-
hasRefreshToken: !!tokens.refresh_token
|
|
2532
|
-
state: state || "no state"
|
|
2765
|
+
hasRefreshToken: !!tokens.refresh_token
|
|
2533
2766
|
});
|
|
2534
2767
|
return tokens;
|
|
2535
2768
|
}
|
|
@@ -2537,8 +2770,9 @@ var BlinkAuth = class {
|
|
|
2537
2770
|
return null;
|
|
2538
2771
|
}
|
|
2539
2772
|
clearUrlTokens() {
|
|
2540
|
-
|
|
2541
|
-
|
|
2773
|
+
const href = getLocationHref();
|
|
2774
|
+
if (!href || !hasWindowLocation()) return;
|
|
2775
|
+
const url = new URL(href);
|
|
2542
2776
|
url.searchParams.delete("access_token");
|
|
2543
2777
|
url.searchParams.delete("refresh_token");
|
|
2544
2778
|
url.searchParams.delete("token_type");
|
|
@@ -2549,12 +2783,11 @@ var BlinkAuth = class {
|
|
|
2549
2783
|
url.searchParams.delete("code");
|
|
2550
2784
|
url.searchParams.delete("error");
|
|
2551
2785
|
url.searchParams.delete("error_description");
|
|
2552
|
-
url.hash = "";
|
|
2553
2786
|
window.history.replaceState({}, "", url.toString());
|
|
2554
|
-
console.log("\u{1F9F9} URL cleaned up, removed auth parameters
|
|
2787
|
+
console.log("\u{1F9F9} URL cleaned up, removed auth parameters");
|
|
2555
2788
|
}
|
|
2556
2789
|
redirectToAuth() {
|
|
2557
|
-
if (
|
|
2790
|
+
if (hasWindowLocation()) {
|
|
2558
2791
|
this.login();
|
|
2559
2792
|
}
|
|
2560
2793
|
}
|
|
@@ -2586,12 +2819,25 @@ var BlinkAuth = class {
|
|
|
2586
2819
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2587
2820
|
}
|
|
2588
2821
|
}
|
|
2822
|
+
/**
|
|
2823
|
+
* Generate unique session ID for mobile OAuth
|
|
2824
|
+
*/
|
|
2825
|
+
generateSessionId() {
|
|
2826
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
2827
|
+
const array = new Uint8Array(32);
|
|
2828
|
+
crypto.getRandomValues(array);
|
|
2829
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2830
|
+
} else {
|
|
2831
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2589
2834
|
/**
|
|
2590
2835
|
* Extract magic link token from URL
|
|
2591
2836
|
*/
|
|
2592
2837
|
extractMagicTokenFromUrl() {
|
|
2593
|
-
|
|
2594
|
-
|
|
2838
|
+
const search = getLocationSearch();
|
|
2839
|
+
if (!search) return null;
|
|
2840
|
+
const params = new URLSearchParams(search);
|
|
2595
2841
|
return params.get("magic_token") || params.get("token");
|
|
2596
2842
|
}
|
|
2597
2843
|
/**
|
|
@@ -2647,7 +2893,7 @@ var BlinkAuth = class {
|
|
|
2647
2893
|
* Setup cross-tab authentication synchronization
|
|
2648
2894
|
*/
|
|
2649
2895
|
setupCrossTabSync() {
|
|
2650
|
-
if (!isWeb) return;
|
|
2896
|
+
if (!isWeb || !hasWindow()) return;
|
|
2651
2897
|
window.addEventListener("storage", (e) => {
|
|
2652
2898
|
if (e.key === this.getStorageKey("tokens")) {
|
|
2653
2899
|
const newTokens = e.newValue ? JSON.parse(e.newValue) : null;
|
|
@@ -4951,9 +5197,9 @@ var BlinkAnalyticsImpl = class {
|
|
|
4951
5197
|
user_id: this.userId,
|
|
4952
5198
|
user_email: this.userEmail,
|
|
4953
5199
|
session_id: sessionId,
|
|
4954
|
-
pathname:
|
|
4955
|
-
referrer:
|
|
4956
|
-
screen_width:
|
|
5200
|
+
pathname: getLocationPathname(),
|
|
5201
|
+
referrer: getDocumentReferrer(),
|
|
5202
|
+
screen_width: getWindowInnerWidth(),
|
|
4957
5203
|
channel,
|
|
4958
5204
|
utm_source: this.utmParams.utm_source || this.persistedAttribution.utm_source || null,
|
|
4959
5205
|
utm_medium: this.utmParams.utm_medium || this.persistedAttribution.utm_medium || null,
|
|
@@ -5106,7 +5352,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5106
5352
|
window.__blinkAnalyticsInstances?.add(this);
|
|
5107
5353
|
}
|
|
5108
5354
|
setupUnloadListener() {
|
|
5109
|
-
if (!isWeb) return;
|
|
5355
|
+
if (!isWeb || !hasWindow()) return;
|
|
5110
5356
|
window.addEventListener("pagehide", () => {
|
|
5111
5357
|
this.flush();
|
|
5112
5358
|
});
|
|
@@ -5116,7 +5362,12 @@ var BlinkAnalyticsImpl = class {
|
|
|
5116
5362
|
}
|
|
5117
5363
|
captureUTMParams() {
|
|
5118
5364
|
if (!isWeb) return;
|
|
5119
|
-
const
|
|
5365
|
+
const search = getLocationSearch();
|
|
5366
|
+
if (!search) {
|
|
5367
|
+
this.utmParams = {};
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
const urlParams = new URLSearchParams(search);
|
|
5120
5371
|
this.utmParams = {
|
|
5121
5372
|
utm_source: urlParams.get("utm_source"),
|
|
5122
5373
|
utm_medium: urlParams.get("utm_medium"),
|
|
@@ -5153,7 +5404,7 @@ var BlinkAnalyticsImpl = class {
|
|
|
5153
5404
|
}
|
|
5154
5405
|
}
|
|
5155
5406
|
detectChannel() {
|
|
5156
|
-
const referrer =
|
|
5407
|
+
const referrer = getDocumentReferrer();
|
|
5157
5408
|
const utmMedium = this.utmParams.utm_medium;
|
|
5158
5409
|
this.utmParams.utm_source;
|
|
5159
5410
|
if (utmMedium) {
|