@lovable.dev/cloud-auth-js 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +53 -32
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +53 -32
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,10 @@ module.exports = __toCommonJS(src_exports);
|
|
|
27
27
|
// src/auth.ts
|
|
28
28
|
var EXPECTED_MESSAGE_TYPE = "authorization_response";
|
|
29
29
|
var DEFAULT_OAUTH_BROKER_URL = "/~oauth/initiate";
|
|
30
|
-
var DEFAULT_SUPPORTED_OAUTH_ORIGINS = ["https://oauth.lovable.app"];
|
|
30
|
+
var DEFAULT_SUPPORTED_OAUTH_ORIGINS = ["https://oauth.lovable.app", "https://lovable.dev"];
|
|
31
|
+
var DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI = "lovable://oauth-callback";
|
|
32
|
+
var POPUP_CHECK_INTERVAL_MS = 500;
|
|
33
|
+
var IFRAME_FALLBACK_TIMEOUT_MS = 12e4;
|
|
31
34
|
function startWebMessageListener(supportedOrigins) {
|
|
32
35
|
let resolvePromise;
|
|
33
36
|
const promise = new Promise((resolve) => {
|
|
@@ -64,6 +67,26 @@ function getPopupDimensions(isInIframe) {
|
|
|
64
67
|
const top = hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2;
|
|
65
68
|
return { width, height, left, top };
|
|
66
69
|
}
|
|
70
|
+
function processOAuthResponse(data, expectedState) {
|
|
71
|
+
if (data.state !== expectedState) {
|
|
72
|
+
return { error: new Error("State is invalid") };
|
|
73
|
+
}
|
|
74
|
+
if (data.error) {
|
|
75
|
+
if (data.error === "legacy_flow") {
|
|
76
|
+
return {
|
|
77
|
+
error: new Error("This flow is not supported in Preview mode. Please open the app in a new tab to sign in.")
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { error: new Error(data.error_description ?? "Sign in failed") };
|
|
81
|
+
}
|
|
82
|
+
if (!data.access_token || !data.refresh_token) {
|
|
83
|
+
return { error: new Error("No tokens received") };
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
tokens: { access_token: data.access_token, refresh_token: data.refresh_token },
|
|
87
|
+
error: null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
67
90
|
function generateState() {
|
|
68
91
|
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
69
92
|
return [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
@@ -80,22 +103,26 @@ function createAuth(config = {}) {
|
|
|
80
103
|
} catch {
|
|
81
104
|
isInIframe = true;
|
|
82
105
|
}
|
|
106
|
+
const ua = navigator.userAgent;
|
|
107
|
+
const isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
|
|
108
|
+
const isMobileApp = /LovableApp\//.test(ua);
|
|
83
109
|
const state = generateState();
|
|
84
|
-
const redirectUri = opts.redirect_uri ?? window.location.origin;
|
|
110
|
+
const redirectUri = isMobileApp && isInIframe ? DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI : opts.redirect_uri ?? window.location.origin;
|
|
85
111
|
const params = new URLSearchParams({
|
|
112
|
+
...opts.extraParams,
|
|
86
113
|
provider,
|
|
87
114
|
redirect_uri: redirectUri,
|
|
88
|
-
state
|
|
89
|
-
...opts.extraParams
|
|
115
|
+
state
|
|
90
116
|
});
|
|
91
117
|
if (!isInIframe) {
|
|
92
118
|
window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
|
|
93
119
|
return { error: null, redirected: true };
|
|
94
120
|
}
|
|
95
|
-
|
|
121
|
+
if (!isMobileApp) {
|
|
122
|
+
params.set("response_mode", "web_message");
|
|
123
|
+
}
|
|
96
124
|
const url = `${oauthBrokerUrl}?${params.toString()}`;
|
|
97
125
|
const { messagePromise, cleanup } = startWebMessageListener(supportedOAuthOrigins);
|
|
98
|
-
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
99
126
|
let popup;
|
|
100
127
|
if (isMobile) {
|
|
101
128
|
popup = window.open(url, "_blank");
|
|
@@ -103,6 +130,24 @@ function createAuth(config = {}) {
|
|
|
103
130
|
const { width, height, left, top } = getPopupDimensions(isInIframe);
|
|
104
131
|
popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
105
132
|
}
|
|
133
|
+
if (!popup && isMobileApp) {
|
|
134
|
+
let webViewTimeoutId;
|
|
135
|
+
const webViewTimeoutPromise = new Promise((_, reject) => {
|
|
136
|
+
webViewTimeoutId = setTimeout(() => {
|
|
137
|
+
reject(new Error("OAuth timed out waiting for response"));
|
|
138
|
+
}, IFRAME_FALLBACK_TIMEOUT_MS);
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
const data = await Promise.race([messagePromise, webViewTimeoutPromise]);
|
|
142
|
+
return processOAuthResponse(data, state);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
145
|
+
} finally {
|
|
146
|
+
if (webViewTimeoutId)
|
|
147
|
+
clearTimeout(webViewTimeoutId);
|
|
148
|
+
cleanup();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
106
151
|
if (!popup) {
|
|
107
152
|
cleanup();
|
|
108
153
|
return { error: new Error("Popup was blocked") };
|
|
@@ -114,35 +159,11 @@ function createAuth(config = {}) {
|
|
|
114
159
|
clearInterval(popupCheckInterval);
|
|
115
160
|
reject(new Error("Sign in was cancelled"));
|
|
116
161
|
}
|
|
117
|
-
},
|
|
162
|
+
}, POPUP_CHECK_INTERVAL_MS);
|
|
118
163
|
});
|
|
119
164
|
try {
|
|
120
165
|
const data = await Promise.race([messagePromise, popupClosedPromise]);
|
|
121
|
-
|
|
122
|
-
if (data.error === "legacy_flow") {
|
|
123
|
-
return {
|
|
124
|
-
error: new Error(
|
|
125
|
-
"This flow is not supported in Preview mode. Please open the app in a new tab to sign in."
|
|
126
|
-
)
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
error: new Error(data.error_description ?? "Sign in failed")
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
if (data.state !== state) {
|
|
134
|
-
return { error: new Error("State is invalid") };
|
|
135
|
-
}
|
|
136
|
-
if (!data.access_token || !data.refresh_token) {
|
|
137
|
-
return { error: new Error("No tokens received") };
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
tokens: {
|
|
141
|
-
access_token: data.access_token,
|
|
142
|
-
refresh_token: data.refresh_token
|
|
143
|
-
},
|
|
144
|
-
error: null
|
|
145
|
-
};
|
|
166
|
+
return processOAuthResponse(data, state);
|
|
146
167
|
} catch (error) {
|
|
147
168
|
return {
|
|
148
169
|
error: error instanceof Error ? error : new Error(String(error))
|
package/dist/index.d.cts
CHANGED
|
@@ -30,4 +30,4 @@ interface LovableAuth {
|
|
|
30
30
|
|
|
31
31
|
declare function createLovableAuth(config?: LovableAuthConfig): LovableAuth;
|
|
32
32
|
|
|
33
|
-
export {
|
|
33
|
+
export { LovableAuth, LovableAuthConfig, OAuthProvider, OAuthTokens, SignInWithOAuthOptions, SignInWithOAuthResult, createLovableAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -30,4 +30,4 @@ interface LovableAuth {
|
|
|
30
30
|
|
|
31
31
|
declare function createLovableAuth(config?: LovableAuthConfig): LovableAuth;
|
|
32
32
|
|
|
33
|
-
export {
|
|
33
|
+
export { LovableAuth, LovableAuthConfig, OAuthProvider, OAuthTokens, SignInWithOAuthOptions, SignInWithOAuthResult, createLovableAuth };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// src/auth.ts
|
|
2
2
|
var EXPECTED_MESSAGE_TYPE = "authorization_response";
|
|
3
3
|
var DEFAULT_OAUTH_BROKER_URL = "/~oauth/initiate";
|
|
4
|
-
var DEFAULT_SUPPORTED_OAUTH_ORIGINS = ["https://oauth.lovable.app"];
|
|
4
|
+
var DEFAULT_SUPPORTED_OAUTH_ORIGINS = ["https://oauth.lovable.app", "https://lovable.dev"];
|
|
5
|
+
var DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI = "lovable://oauth-callback";
|
|
6
|
+
var POPUP_CHECK_INTERVAL_MS = 500;
|
|
7
|
+
var IFRAME_FALLBACK_TIMEOUT_MS = 12e4;
|
|
5
8
|
function startWebMessageListener(supportedOrigins) {
|
|
6
9
|
let resolvePromise;
|
|
7
10
|
const promise = new Promise((resolve) => {
|
|
@@ -38,6 +41,26 @@ function getPopupDimensions(isInIframe) {
|
|
|
38
41
|
const top = hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2;
|
|
39
42
|
return { width, height, left, top };
|
|
40
43
|
}
|
|
44
|
+
function processOAuthResponse(data, expectedState) {
|
|
45
|
+
if (data.state !== expectedState) {
|
|
46
|
+
return { error: new Error("State is invalid") };
|
|
47
|
+
}
|
|
48
|
+
if (data.error) {
|
|
49
|
+
if (data.error === "legacy_flow") {
|
|
50
|
+
return {
|
|
51
|
+
error: new Error("This flow is not supported in Preview mode. Please open the app in a new tab to sign in.")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { error: new Error(data.error_description ?? "Sign in failed") };
|
|
55
|
+
}
|
|
56
|
+
if (!data.access_token || !data.refresh_token) {
|
|
57
|
+
return { error: new Error("No tokens received") };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
tokens: { access_token: data.access_token, refresh_token: data.refresh_token },
|
|
61
|
+
error: null
|
|
62
|
+
};
|
|
63
|
+
}
|
|
41
64
|
function generateState() {
|
|
42
65
|
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
43
66
|
return [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
@@ -54,22 +77,26 @@ function createAuth(config = {}) {
|
|
|
54
77
|
} catch {
|
|
55
78
|
isInIframe = true;
|
|
56
79
|
}
|
|
80
|
+
const ua = navigator.userAgent;
|
|
81
|
+
const isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
|
|
82
|
+
const isMobileApp = /LovableApp\//.test(ua);
|
|
57
83
|
const state = generateState();
|
|
58
|
-
const redirectUri = opts.redirect_uri ?? window.location.origin;
|
|
84
|
+
const redirectUri = isMobileApp && isInIframe ? DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI : opts.redirect_uri ?? window.location.origin;
|
|
59
85
|
const params = new URLSearchParams({
|
|
86
|
+
...opts.extraParams,
|
|
60
87
|
provider,
|
|
61
88
|
redirect_uri: redirectUri,
|
|
62
|
-
state
|
|
63
|
-
...opts.extraParams
|
|
89
|
+
state
|
|
64
90
|
});
|
|
65
91
|
if (!isInIframe) {
|
|
66
92
|
window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
|
|
67
93
|
return { error: null, redirected: true };
|
|
68
94
|
}
|
|
69
|
-
|
|
95
|
+
if (!isMobileApp) {
|
|
96
|
+
params.set("response_mode", "web_message");
|
|
97
|
+
}
|
|
70
98
|
const url = `${oauthBrokerUrl}?${params.toString()}`;
|
|
71
99
|
const { messagePromise, cleanup } = startWebMessageListener(supportedOAuthOrigins);
|
|
72
|
-
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
73
100
|
let popup;
|
|
74
101
|
if (isMobile) {
|
|
75
102
|
popup = window.open(url, "_blank");
|
|
@@ -77,6 +104,24 @@ function createAuth(config = {}) {
|
|
|
77
104
|
const { width, height, left, top } = getPopupDimensions(isInIframe);
|
|
78
105
|
popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
79
106
|
}
|
|
107
|
+
if (!popup && isMobileApp) {
|
|
108
|
+
let webViewTimeoutId;
|
|
109
|
+
const webViewTimeoutPromise = new Promise((_, reject) => {
|
|
110
|
+
webViewTimeoutId = setTimeout(() => {
|
|
111
|
+
reject(new Error("OAuth timed out waiting for response"));
|
|
112
|
+
}, IFRAME_FALLBACK_TIMEOUT_MS);
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
const data = await Promise.race([messagePromise, webViewTimeoutPromise]);
|
|
116
|
+
return processOAuthResponse(data, state);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
119
|
+
} finally {
|
|
120
|
+
if (webViewTimeoutId)
|
|
121
|
+
clearTimeout(webViewTimeoutId);
|
|
122
|
+
cleanup();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
80
125
|
if (!popup) {
|
|
81
126
|
cleanup();
|
|
82
127
|
return { error: new Error("Popup was blocked") };
|
|
@@ -88,35 +133,11 @@ function createAuth(config = {}) {
|
|
|
88
133
|
clearInterval(popupCheckInterval);
|
|
89
134
|
reject(new Error("Sign in was cancelled"));
|
|
90
135
|
}
|
|
91
|
-
},
|
|
136
|
+
}, POPUP_CHECK_INTERVAL_MS);
|
|
92
137
|
});
|
|
93
138
|
try {
|
|
94
139
|
const data = await Promise.race([messagePromise, popupClosedPromise]);
|
|
95
|
-
|
|
96
|
-
if (data.error === "legacy_flow") {
|
|
97
|
-
return {
|
|
98
|
-
error: new Error(
|
|
99
|
-
"This flow is not supported in Preview mode. Please open the app in a new tab to sign in."
|
|
100
|
-
)
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
error: new Error(data.error_description ?? "Sign in failed")
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
if (data.state !== state) {
|
|
108
|
-
return { error: new Error("State is invalid") };
|
|
109
|
-
}
|
|
110
|
-
if (!data.access_token || !data.refresh_token) {
|
|
111
|
-
return { error: new Error("No tokens received") };
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
tokens: {
|
|
115
|
-
access_token: data.access_token,
|
|
116
|
-
refresh_token: data.refresh_token
|
|
117
|
-
},
|
|
118
|
-
error: null
|
|
119
|
-
};
|
|
140
|
+
return processOAuthResponse(data, state);
|
|
120
141
|
} catch (error) {
|
|
121
142
|
return {
|
|
122
143
|
error: error instanceof Error ? error : new Error(String(error))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/cloud-auth-js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Lovable Cloud Auth JS",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "tsup src/index.ts --format cjs,esm --dts --outDir dist",
|
|
14
14
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
15
15
|
"test": "vitest run",
|
|
16
|
-
"typecheck": "
|
|
16
|
+
"typecheck": "tsgo --noEmit",
|
|
17
17
|
"prepublishOnly": "npm run clean && npm run build",
|
|
18
18
|
"clean": "rimraf dist"
|
|
19
19
|
},
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^22",
|
|
28
|
+
"jsdom": "^26",
|
|
28
29
|
"rimraf": "^5.0.0",
|
|
29
30
|
"tsup": "^7.2.0",
|
|
30
31
|
"typescript": "^5",
|
|
31
|
-
"vitest": "^
|
|
32
|
+
"vitest": "^4.1.0"
|
|
32
33
|
}
|
|
33
34
|
}
|