@lovable.dev/cloud-auth-js 1.1.3-rc.19 → 1.1.3-rc.20
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 +190 -139
- package/dist/index.d.cts +19 -21
- package/dist/index.d.ts +19 -21
- package/dist/index.js +163 -138
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,153 +1,204 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
createLovableAuth: () => createLovableAuth
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(src_exports);
|
|
26
|
+
|
|
27
|
+
// src/auth.ts
|
|
28
|
+
var EXPECTED_MESSAGE_TYPE = "authorization_response";
|
|
29
|
+
var DEFAULT_OAUTH_BROKER_URL = "/~oauth/initiate";
|
|
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 DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI = "http://127.0.0.1/iframe-oauth/callback";
|
|
33
|
+
var POPUP_CHECK_INTERVAL_MS = 500;
|
|
34
|
+
var IFRAME_FALLBACK_TIMEOUT_MS = 12e4;
|
|
10
35
|
function startWebMessageListener(supportedOrigins) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
let resolvePromise;
|
|
37
|
+
const promise = new Promise((resolve) => {
|
|
38
|
+
resolvePromise = resolve;
|
|
39
|
+
});
|
|
40
|
+
const callback = (e) => {
|
|
41
|
+
const isValidOrigin = supportedOrigins.some((origin) => e.origin === origin);
|
|
42
|
+
if (!isValidOrigin) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const data = e.data;
|
|
46
|
+
if (!data || typeof data !== "object") {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (data.type !== EXPECTED_MESSAGE_TYPE) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
resolvePromise(data.response);
|
|
53
|
+
};
|
|
54
|
+
const cleanup = () => {
|
|
55
|
+
window.removeEventListener("message", callback);
|
|
56
|
+
};
|
|
57
|
+
window.addEventListener("message", callback);
|
|
58
|
+
return {
|
|
59
|
+
cleanup,
|
|
60
|
+
messagePromise: promise
|
|
61
|
+
};
|
|
30
62
|
}
|
|
31
63
|
function getPopupDimensions(isInIframe) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
left: hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2,
|
|
39
|
-
top: hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2
|
|
40
|
-
};
|
|
64
|
+
const hasBrowserPosition = window.screenX !== 0 || window.screenY !== 0 || !isInIframe;
|
|
65
|
+
const width = hasBrowserPosition ? window.outerWidth * 0.5 : window.screen.width * 0.5;
|
|
66
|
+
const height = hasBrowserPosition ? window.outerHeight * 0.5 : window.screen.height * 0.5;
|
|
67
|
+
const left = hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2;
|
|
68
|
+
const top = hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2;
|
|
69
|
+
return { width, height, left, top };
|
|
41
70
|
}
|
|
42
71
|
function processOAuthResponse(data, expectedState) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
if (data.state !== expectedState) {
|
|
73
|
+
return { error: new Error("State is invalid") };
|
|
74
|
+
}
|
|
75
|
+
if (data.error) {
|
|
76
|
+
if (data.error === "legacy_flow") {
|
|
77
|
+
return {
|
|
78
|
+
error: new Error("This flow is not supported in Preview mode. Please open the app in a new tab to sign in.")
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { error: new Error(data.error_description ?? "Sign in failed") };
|
|
82
|
+
}
|
|
83
|
+
if (!data.access_token || !data.refresh_token) {
|
|
84
|
+
return { error: new Error("No tokens received") };
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
tokens: { access_token: data.access_token, refresh_token: data.refresh_token },
|
|
88
|
+
error: null
|
|
89
|
+
};
|
|
56
90
|
}
|
|
57
91
|
function isDevice() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
92
|
+
const ua = navigator.userAgent;
|
|
93
|
+
if (/iPhone|iPad|iPod|Android/i.test(ua))
|
|
94
|
+
return true;
|
|
95
|
+
if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1)
|
|
96
|
+
return true;
|
|
97
|
+
return false;
|
|
62
98
|
}
|
|
63
99
|
function generateState() {
|
|
64
|
-
|
|
65
|
-
|
|
100
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
101
|
+
return [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
102
|
+
}
|
|
103
|
+
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
66
104
|
}
|
|
67
105
|
function createAuth(config = {}) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
106
|
+
const oauthBrokerUrl = config.oauthBrokerUrl ?? DEFAULT_OAUTH_BROKER_URL;
|
|
107
|
+
const supportedOAuthOrigins = config.supportedOAuthOrigins ?? DEFAULT_SUPPORTED_OAUTH_ORIGINS;
|
|
108
|
+
async function signInWithOAuth(provider, opts = {}) {
|
|
109
|
+
let isInIframe = false;
|
|
110
|
+
try {
|
|
111
|
+
isInIframe = window.self !== window.top;
|
|
112
|
+
} catch {
|
|
113
|
+
isInIframe = true;
|
|
114
|
+
}
|
|
115
|
+
const ua = navigator.userAgent;
|
|
116
|
+
const isMobileApp = /LovableApp\//.test(ua);
|
|
117
|
+
const isDesktopApp = !isMobileApp && /lovable/i.test(ua);
|
|
118
|
+
const state = generateState();
|
|
119
|
+
let redirectUri = opts.redirect_uri ?? window.location.origin;
|
|
120
|
+
if (isMobileApp && isInIframe) {
|
|
121
|
+
redirectUri = DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI;
|
|
122
|
+
} else if (isDesktopApp && isInIframe) {
|
|
123
|
+
redirectUri = DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI;
|
|
124
|
+
}
|
|
125
|
+
const params = new URLSearchParams({
|
|
126
|
+
...opts.extraParams,
|
|
127
|
+
provider,
|
|
128
|
+
redirect_uri: redirectUri,
|
|
129
|
+
state
|
|
130
|
+
});
|
|
131
|
+
if (!isInIframe) {
|
|
132
|
+
window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
|
|
133
|
+
return { error: null, redirected: true };
|
|
134
|
+
}
|
|
135
|
+
if (!isMobileApp && !isDesktopApp) {
|
|
136
|
+
params.set("response_mode", "web_message");
|
|
137
|
+
}
|
|
138
|
+
const url = `${oauthBrokerUrl}?${params.toString()}`;
|
|
139
|
+
const effectiveOrigins = isDesktopApp ? [...supportedOAuthOrigins, window.location.origin] : supportedOAuthOrigins;
|
|
140
|
+
const { messagePromise, cleanup } = startWebMessageListener(effectiveOrigins);
|
|
141
|
+
let popup;
|
|
142
|
+
if (isDevice()) {
|
|
143
|
+
popup = window.open(url, "_blank");
|
|
144
|
+
} else {
|
|
145
|
+
const { width, height, left, top } = getPopupDimensions(isInIframe);
|
|
146
|
+
popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
147
|
+
}
|
|
148
|
+
if (!popup && (isMobileApp || isDesktopApp)) {
|
|
149
|
+
let webViewTimeoutId;
|
|
150
|
+
const webViewTimeoutPromise = new Promise((_, reject) => {
|
|
151
|
+
webViewTimeoutId = setTimeout(() => {
|
|
152
|
+
reject(new Error("OAuth timed out waiting for response"));
|
|
153
|
+
}, IFRAME_FALLBACK_TIMEOUT_MS);
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
const data = await Promise.race([messagePromise, webViewTimeoutPromise]);
|
|
157
|
+
return processOAuthResponse(data, state);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
160
|
+
} finally {
|
|
161
|
+
if (webViewTimeoutId)
|
|
162
|
+
clearTimeout(webViewTimeoutId);
|
|
163
|
+
cleanup();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!popup) {
|
|
167
|
+
cleanup();
|
|
168
|
+
return { error: new Error("Popup was blocked") };
|
|
169
|
+
}
|
|
170
|
+
let popupCheckInterval;
|
|
171
|
+
const popupClosedPromise = new Promise((_, reject) => {
|
|
172
|
+
popupCheckInterval = setInterval(() => {
|
|
173
|
+
if (popup.closed) {
|
|
174
|
+
clearInterval(popupCheckInterval);
|
|
175
|
+
reject(new Error("Sign in was cancelled"));
|
|
176
|
+
}
|
|
177
|
+
}, POPUP_CHECK_INTERVAL_MS);
|
|
178
|
+
});
|
|
179
|
+
try {
|
|
180
|
+
const data = await Promise.race([messagePromise, popupClosedPromise]);
|
|
181
|
+
return processOAuthResponse(data, state);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
185
|
+
};
|
|
186
|
+
} finally {
|
|
187
|
+
clearInterval(popupCheckInterval);
|
|
188
|
+
cleanup();
|
|
189
|
+
popup?.close();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
signInWithOAuth
|
|
194
|
+
};
|
|
146
195
|
}
|
|
147
|
-
|
|
148
|
-
|
|
196
|
+
|
|
197
|
+
// src/index.ts
|
|
149
198
|
function createLovableAuth(config = {}) {
|
|
150
|
-
|
|
199
|
+
return createAuth(config);
|
|
151
200
|
}
|
|
152
|
-
|
|
153
|
-
exports
|
|
201
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
202
|
+
0 && (module.exports = {
|
|
203
|
+
createLovableAuth
|
|
204
|
+
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
2
1
|
type OAuthProvider = "google" | "apple" | "microsoft" | "lovable";
|
|
3
2
|
interface LovableAuthConfig {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
oauthBrokerUrl?: string;
|
|
4
|
+
supportedOAuthOrigins?: string[];
|
|
6
5
|
}
|
|
7
6
|
interface OAuthTokens {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
access_token: string;
|
|
8
|
+
refresh_token: string;
|
|
10
9
|
}
|
|
11
10
|
interface SignInWithOAuthOptions {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
redirect_uri?: string;
|
|
12
|
+
extraParams?: Record<string, string>;
|
|
14
13
|
}
|
|
15
14
|
type SignInWithOAuthResult = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
tokens: OAuthTokens;
|
|
16
|
+
error: null;
|
|
17
|
+
redirected?: false;
|
|
19
18
|
} | {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
tokens?: undefined;
|
|
20
|
+
error: Error;
|
|
21
|
+
redirected?: false;
|
|
23
22
|
} | {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
tokens?: undefined;
|
|
24
|
+
error: null;
|
|
25
|
+
redirected: true;
|
|
27
26
|
};
|
|
28
27
|
interface LovableAuth {
|
|
29
|
-
|
|
28
|
+
signInWithOAuth: (provider: OAuthProvider, opts?: SignInWithOAuthOptions) => Promise<SignInWithOAuthResult>;
|
|
30
29
|
}
|
|
31
|
-
|
|
32
|
-
//#region src/index.d.ts
|
|
30
|
+
|
|
33
31
|
declare function createLovableAuth(config?: LovableAuthConfig): LovableAuth;
|
|
34
|
-
|
|
35
|
-
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
|
32
|
+
|
|
33
|
+
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
2
1
|
type OAuthProvider = "google" | "apple" | "microsoft" | "lovable";
|
|
3
2
|
interface LovableAuthConfig {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
oauthBrokerUrl?: string;
|
|
4
|
+
supportedOAuthOrigins?: string[];
|
|
6
5
|
}
|
|
7
6
|
interface OAuthTokens {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
access_token: string;
|
|
8
|
+
refresh_token: string;
|
|
10
9
|
}
|
|
11
10
|
interface SignInWithOAuthOptions {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
redirect_uri?: string;
|
|
12
|
+
extraParams?: Record<string, string>;
|
|
14
13
|
}
|
|
15
14
|
type SignInWithOAuthResult = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
tokens: OAuthTokens;
|
|
16
|
+
error: null;
|
|
17
|
+
redirected?: false;
|
|
19
18
|
} | {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
tokens?: undefined;
|
|
20
|
+
error: Error;
|
|
21
|
+
redirected?: false;
|
|
23
22
|
} | {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
tokens?: undefined;
|
|
24
|
+
error: null;
|
|
25
|
+
redirected: true;
|
|
27
26
|
};
|
|
28
27
|
interface LovableAuth {
|
|
29
|
-
|
|
28
|
+
signInWithOAuth: (provider: OAuthProvider, opts?: SignInWithOAuthOptions) => Promise<SignInWithOAuthResult>;
|
|
30
29
|
}
|
|
31
|
-
|
|
32
|
-
//#region src/index.d.ts
|
|
30
|
+
|
|
33
31
|
declare function createLovableAuth(config?: LovableAuthConfig): LovableAuth;
|
|
34
|
-
|
|
35
|
-
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
|
32
|
+
|
|
33
|
+
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
package/dist/index.js
CHANGED
|
@@ -1,152 +1,177 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// src/auth.ts
|
|
2
|
+
var EXPECTED_MESSAGE_TYPE = "authorization_response";
|
|
3
|
+
var DEFAULT_OAUTH_BROKER_URL = "/~oauth/initiate";
|
|
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 DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI = "http://127.0.0.1/iframe-oauth/callback";
|
|
7
|
+
var POPUP_CHECK_INTERVAL_MS = 500;
|
|
8
|
+
var IFRAME_FALLBACK_TIMEOUT_MS = 12e4;
|
|
9
9
|
function startWebMessageListener(supportedOrigins) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
let resolvePromise;
|
|
11
|
+
const promise = new Promise((resolve) => {
|
|
12
|
+
resolvePromise = resolve;
|
|
13
|
+
});
|
|
14
|
+
const callback = (e) => {
|
|
15
|
+
const isValidOrigin = supportedOrigins.some((origin) => e.origin === origin);
|
|
16
|
+
if (!isValidOrigin) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const data = e.data;
|
|
20
|
+
if (!data || typeof data !== "object") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (data.type !== EXPECTED_MESSAGE_TYPE) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
resolvePromise(data.response);
|
|
27
|
+
};
|
|
28
|
+
const cleanup = () => {
|
|
29
|
+
window.removeEventListener("message", callback);
|
|
30
|
+
};
|
|
31
|
+
window.addEventListener("message", callback);
|
|
32
|
+
return {
|
|
33
|
+
cleanup,
|
|
34
|
+
messagePromise: promise
|
|
35
|
+
};
|
|
29
36
|
}
|
|
30
37
|
function getPopupDimensions(isInIframe) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
left: hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2,
|
|
38
|
-
top: hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2
|
|
39
|
-
};
|
|
38
|
+
const hasBrowserPosition = window.screenX !== 0 || window.screenY !== 0 || !isInIframe;
|
|
39
|
+
const width = hasBrowserPosition ? window.outerWidth * 0.5 : window.screen.width * 0.5;
|
|
40
|
+
const height = hasBrowserPosition ? window.outerHeight * 0.5 : window.screen.height * 0.5;
|
|
41
|
+
const left = hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2;
|
|
42
|
+
const top = hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2;
|
|
43
|
+
return { width, height, left, top };
|
|
40
44
|
}
|
|
41
45
|
function processOAuthResponse(data, expectedState) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
if (data.state !== expectedState) {
|
|
47
|
+
return { error: new Error("State is invalid") };
|
|
48
|
+
}
|
|
49
|
+
if (data.error) {
|
|
50
|
+
if (data.error === "legacy_flow") {
|
|
51
|
+
return {
|
|
52
|
+
error: new Error("This flow is not supported in Preview mode. Please open the app in a new tab to sign in.")
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { error: new Error(data.error_description ?? "Sign in failed") };
|
|
56
|
+
}
|
|
57
|
+
if (!data.access_token || !data.refresh_token) {
|
|
58
|
+
return { error: new Error("No tokens received") };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
tokens: { access_token: data.access_token, refresh_token: data.refresh_token },
|
|
62
|
+
error: null
|
|
63
|
+
};
|
|
55
64
|
}
|
|
56
65
|
function isDevice() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
const ua = navigator.userAgent;
|
|
67
|
+
if (/iPhone|iPad|iPod|Android/i.test(ua))
|
|
68
|
+
return true;
|
|
69
|
+
if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1)
|
|
70
|
+
return true;
|
|
71
|
+
return false;
|
|
61
72
|
}
|
|
62
73
|
function generateState() {
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
75
|
+
return [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
76
|
+
}
|
|
77
|
+
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
65
78
|
}
|
|
66
79
|
function createAuth(config = {}) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
80
|
+
const oauthBrokerUrl = config.oauthBrokerUrl ?? DEFAULT_OAUTH_BROKER_URL;
|
|
81
|
+
const supportedOAuthOrigins = config.supportedOAuthOrigins ?? DEFAULT_SUPPORTED_OAUTH_ORIGINS;
|
|
82
|
+
async function signInWithOAuth(provider, opts = {}) {
|
|
83
|
+
let isInIframe = false;
|
|
84
|
+
try {
|
|
85
|
+
isInIframe = window.self !== window.top;
|
|
86
|
+
} catch {
|
|
87
|
+
isInIframe = true;
|
|
88
|
+
}
|
|
89
|
+
const ua = navigator.userAgent;
|
|
90
|
+
const isMobileApp = /LovableApp\//.test(ua);
|
|
91
|
+
const isDesktopApp = !isMobileApp && /lovable/i.test(ua);
|
|
92
|
+
const state = generateState();
|
|
93
|
+
let redirectUri = opts.redirect_uri ?? window.location.origin;
|
|
94
|
+
if (isMobileApp && isInIframe) {
|
|
95
|
+
redirectUri = DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI;
|
|
96
|
+
} else if (isDesktopApp && isInIframe) {
|
|
97
|
+
redirectUri = DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI;
|
|
98
|
+
}
|
|
99
|
+
const params = new URLSearchParams({
|
|
100
|
+
...opts.extraParams,
|
|
101
|
+
provider,
|
|
102
|
+
redirect_uri: redirectUri,
|
|
103
|
+
state
|
|
104
|
+
});
|
|
105
|
+
if (!isInIframe) {
|
|
106
|
+
window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
|
|
107
|
+
return { error: null, redirected: true };
|
|
108
|
+
}
|
|
109
|
+
if (!isMobileApp && !isDesktopApp) {
|
|
110
|
+
params.set("response_mode", "web_message");
|
|
111
|
+
}
|
|
112
|
+
const url = `${oauthBrokerUrl}?${params.toString()}`;
|
|
113
|
+
const effectiveOrigins = isDesktopApp ? [...supportedOAuthOrigins, window.location.origin] : supportedOAuthOrigins;
|
|
114
|
+
const { messagePromise, cleanup } = startWebMessageListener(effectiveOrigins);
|
|
115
|
+
let popup;
|
|
116
|
+
if (isDevice()) {
|
|
117
|
+
popup = window.open(url, "_blank");
|
|
118
|
+
} else {
|
|
119
|
+
const { width, height, left, top } = getPopupDimensions(isInIframe);
|
|
120
|
+
popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
121
|
+
}
|
|
122
|
+
if (!popup && (isMobileApp || isDesktopApp)) {
|
|
123
|
+
let webViewTimeoutId;
|
|
124
|
+
const webViewTimeoutPromise = new Promise((_, reject) => {
|
|
125
|
+
webViewTimeoutId = setTimeout(() => {
|
|
126
|
+
reject(new Error("OAuth timed out waiting for response"));
|
|
127
|
+
}, IFRAME_FALLBACK_TIMEOUT_MS);
|
|
128
|
+
});
|
|
129
|
+
try {
|
|
130
|
+
const data = await Promise.race([messagePromise, webViewTimeoutPromise]);
|
|
131
|
+
return processOAuthResponse(data, state);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
134
|
+
} finally {
|
|
135
|
+
if (webViewTimeoutId)
|
|
136
|
+
clearTimeout(webViewTimeoutId);
|
|
137
|
+
cleanup();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!popup) {
|
|
141
|
+
cleanup();
|
|
142
|
+
return { error: new Error("Popup was blocked") };
|
|
143
|
+
}
|
|
144
|
+
let popupCheckInterval;
|
|
145
|
+
const popupClosedPromise = new Promise((_, reject) => {
|
|
146
|
+
popupCheckInterval = setInterval(() => {
|
|
147
|
+
if (popup.closed) {
|
|
148
|
+
clearInterval(popupCheckInterval);
|
|
149
|
+
reject(new Error("Sign in was cancelled"));
|
|
150
|
+
}
|
|
151
|
+
}, POPUP_CHECK_INTERVAL_MS);
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
const data = await Promise.race([messagePromise, popupClosedPromise]);
|
|
155
|
+
return processOAuthResponse(data, state);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return {
|
|
158
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
159
|
+
};
|
|
160
|
+
} finally {
|
|
161
|
+
clearInterval(popupCheckInterval);
|
|
162
|
+
cleanup();
|
|
163
|
+
popup?.close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
signInWithOAuth
|
|
168
|
+
};
|
|
145
169
|
}
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
|
|
171
|
+
// src/index.ts
|
|
148
172
|
function createLovableAuth(config = {}) {
|
|
149
|
-
|
|
173
|
+
return createAuth(config);
|
|
150
174
|
}
|
|
151
|
-
|
|
152
|
-
|
|
175
|
+
export {
|
|
176
|
+
createLovableAuth
|
|
177
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/cloud-auth-js",
|
|
3
|
-
"version": "1.1.3-rc.
|
|
3
|
+
"version": "1.1.3-rc.20",
|
|
4
4
|
"description": "Lovable legacy OAuth broker JS",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"jsdom": "^26",
|
|
38
38
|
"rimraf": "^5.0.0",
|
|
39
|
-
"
|
|
39
|
+
"tsup": "^7.2.0",
|
|
40
40
|
"typescript": "^5",
|
|
41
41
|
"vitest": "^4.1.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"build": "
|
|
44
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --outDir dist",
|
|
45
45
|
"check:package": "tmp=$(mktemp -d) && pnpm pack --out \"$tmp/pkg.tgz\" && tar -xzf \"$tmp/pkg.tgz\" -C \"$tmp\" && publint \"$tmp/package\" --strict && attw \"$tmp/pkg.tgz\"",
|
|
46
|
-
"dev": "
|
|
46
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
47
47
|
"test": "vitest run",
|
|
48
48
|
"typecheck": "tsgo --noEmit",
|
|
49
49
|
"clean": "rimraf dist"
|