@lovable.dev/cloud-auth-js 1.1.3-rc.8 → 1.2.0
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 +15 -0
- package/assets/lovable-mark.svg +16 -0
- package/dist/index.cjs +144 -190
- package/dist/index.d.cts +21 -19
- package/dist/index.d.ts +21 -19
- package/dist/index.js +143 -163
- package/package.json +13 -7
- package/styles.css +71 -0
package/README.md
CHANGED
|
@@ -32,6 +32,21 @@ if (result.error) {
|
|
|
32
32
|
await supabase.auth.setSession(result.tokens);
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Sign in with Lovable button
|
|
36
|
+
|
|
37
|
+
Import the first-party stylesheet and apply its class to the button that invokes the Lovable provider:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import "@lovable.dev/cloud-auth-js/styles.css";
|
|
41
|
+
<button type="button" className="lovable-auth-button lovable-auth-button--full-width" onClick={handleLovableSignIn}>
|
|
42
|
+
Continue with Lovable
|
|
43
|
+
</button>;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use the `signInWithOAuth` flow above with `"lovable"` in `handleLovableSignIn`. Add
|
|
47
|
+
`lovable-auth-button--dark` on dark surfaces. The stylesheet owns the official mark, provider spacing, colors, focus
|
|
48
|
+
treatment, and disabled state.
|
|
49
|
+
|
|
35
50
|
## API
|
|
36
51
|
|
|
37
52
|
### `createLovableAuth(config?)`
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="121" height="122" viewBox="0 0 121 122" fill="none">
|
|
2
|
+
<clipPath id="mask0_572_319" clipPathUnits="userSpaceOnUse"><path clip-rule="evenodd" d="M36.0687 0C55.9888 0 72.1373 16.1551 72.1373 36.0835V49.7975H84.141C104.061 49.7975 120.21 65.9526 120.21 85.8809C120.21 105.809 104.061 121.964 84.141 121.964H0V36.0835C0 16.1551 16.1485 0 36.0687 0Z"/></clipPath>
|
|
3
|
+
<g clip-path="url(#mask0_572_319)">
|
|
4
|
+
<rect x="0" y="0" width="121" height="122" fill="url(#blur0_f_572_319)"/>
|
|
5
|
+
<rect x="0" y="0" width="121" height="122" fill="url(#blur1_f_572_319)"/>
|
|
6
|
+
<rect x="0" y="0" width="121" height="122" fill="url(#blur2_f_572_319)"/>
|
|
7
|
+
<rect x="0" y="0" width="121" height="122" fill="url(#blur3_f_572_319)"/>
|
|
8
|
+
</g>
|
|
9
|
+
<defs>
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
<radialGradient id="blur0_f_572_319" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(141.41178 0 0 141.23118 52.7381 65.1011)"><stop offset="0" stop-color="#4B73FF" stop-opacity="1"/><stop offset="0.17" stop-color="#4B73FF" stop-opacity="0.9984"/><stop offset="0.25" stop-color="#4B73FF" stop-opacity="0.9908"/><stop offset="0.31" stop-color="#4B73FF" stop-opacity="0.972"/><stop offset="0.335" stop-color="#4B73FF" stop-opacity="0.9575"/><stop offset="0.365" stop-color="#4B73FF" stop-opacity="0.9325"/><stop offset="0.39" stop-color="#4B73FF" stop-opacity="0.9039"/><stop offset="0.415" stop-color="#4B73FF" stop-opacity="0.8667"/><stop offset="0.44" stop-color="#4B73FF" stop-opacity="0.8218"/><stop offset="0.465" stop-color="#4B73FF" stop-opacity="0.7674"/><stop offset="0.48" stop-color="#4B73FF" stop-opacity="0.7315"/><stop offset="0.52" stop-color="#4B73FF" stop-opacity="0.6221"/><stop offset="0.59" stop-color="#4B73FF" stop-opacity="0.4091"/><stop offset="0.61" stop-color="#4B73FF" stop-opacity="0.3547"/><stop offset="0.615" stop-color="#4B73FF" stop-opacity="0.3352"/><stop offset="0.625" stop-color="#4B73FF" stop-opacity="0.3119"/><stop offset="0.63" stop-color="#4B73FF" stop-opacity="0.2942"/><stop offset="0.65" stop-color="#4B73FF" stop-opacity="0.2477"/><stop offset="0.655" stop-color="#4B73FF" stop-opacity="0.2306"/><stop offset="0.69" stop-color="#4B73FF" stop-opacity="0.1597"/><stop offset="0.71" stop-color="#4B73FF" stop-opacity="0.1249"/><stop offset="0.735" stop-color="#4B73FF" stop-opacity="0.0898"/><stop offset="0.76" stop-color="#4B73FF" stop-opacity="0.0623"/><stop offset="0.785" stop-color="#4B73FF" stop-opacity="0.0418"/><stop offset="0.815" stop-color="#4B73FF" stop-opacity="0.025"/><stop offset="0.89" stop-color="#4B73FF" stop-opacity="0.0056"/><stop offset="1" stop-color="#4B73FF" stop-opacity="0"/></radialGradient><radialGradient id="blur1_f_572_319" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(164.25488 0 0 141.23118 61.6734 20.547)"><stop offset="0" stop-color="#FF66F4" stop-opacity="1"/><stop offset="0.19" stop-color="#FF66F4" stop-opacity="0.9992"/><stop offset="0.275" stop-color="#FF66F4" stop-opacity="0.9945"/><stop offset="0.345" stop-color="#FF66F4" stop-opacity="0.9778"/><stop offset="0.38" stop-color="#FF66F4" stop-opacity="0.9553"/><stop offset="0.405" stop-color="#FF66F4" stop-opacity="0.922"/><stop offset="0.44" stop-color="#FF66F4" stop-opacity="0.8652"/><stop offset="0.475" stop-color="#FF66F4" stop-opacity="0.7911"/><stop offset="0.51" stop-color="#FF66F4" stop-opacity="0.698"/><stop offset="0.535" stop-color="#FF66F4" stop-opacity="0.6239"/><stop offset="0.615" stop-color="#FF66F4" stop-opacity="0.3691"/><stop offset="0.65" stop-color="#FF66F4" stop-opacity="0.2693"/><stop offset="0.69" stop-color="#FF66F4" stop-opacity="0.1761"/><stop offset="0.725" stop-color="#FF66F4" stop-opacity="0.1144"/><stop offset="0.755" stop-color="#FF66F4" stop-opacity="0.0768"/><stop offset="0.78" stop-color="#FF66F4" stop-opacity="0.053"/><stop offset="0.8" stop-color="#FF66F4" stop-opacity="0.0384"/><stop offset="0.84" stop-color="#FF66F4" stop-opacity="0.0208"/><stop offset="0.9" stop-color="#FF66F4" stop-opacity="0.0056"/><stop offset="1" stop-color="#FF66F4" stop-opacity="0"/></radialGradient><radialGradient id="blur2_f_572_319" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(141.41178 0 0 131.34308 78.6659 5.26802)"><stop offset="0" stop-color="#FF0105" stop-opacity="0.9998"/><stop offset="0.14" stop-color="#FF0105" stop-opacity="0.9981"/><stop offset="0.225" stop-color="#FF0105" stop-opacity="0.9894"/><stop offset="0.26" stop-color="#FF0105" stop-opacity="0.9802"/><stop offset="0.295" stop-color="#FF0105" stop-opacity="0.9645"/><stop offset="0.34" stop-color="#FF0105" stop-opacity="0.9277"/><stop offset="0.365" stop-color="#FF0105" stop-opacity="0.899"/><stop offset="0.395" stop-color="#FF0105" stop-opacity="0.8549"/><stop offset="0.42" stop-color="#FF0105" stop-opacity="0.8098"/><stop offset="0.44" stop-color="#FF0105" stop-opacity="0.7683"/><stop offset="0.48" stop-color="#FF0105" stop-opacity="0.6713"/><stop offset="0.555" stop-color="#FF0105" stop-opacity="0.4582"/><stop offset="0.57" stop-color="#FF0105" stop-opacity="0.4086"/><stop offset="0.59" stop-color="#FF0105" stop-opacity="0.3497"/><stop offset="0.625" stop-color="#FF0105" stop-opacity="0.2585"/><stop offset="0.665" stop-color="#FF0105" stop-opacity="0.1721"/><stop offset="0.685" stop-color="#FF0105" stop-opacity="0.1367"/><stop offset="0.705" stop-color="#FF0105" stop-opacity="0.1076"/><stop offset="0.725" stop-color="#FF0105" stop-opacity="0.0827"/><stop offset="0.765" stop-color="#FF0105" stop-opacity="0.0465"/><stop offset="0.81" stop-color="#FF0105" stop-opacity="0.0226"/><stop offset="0.885" stop-color="#FF0105" stop-opacity="0.0054"/><stop offset="1" stop-color="#FF0105" stop-opacity="0"/></radialGradient><radialGradient id="blur3_f_572_319" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(108.97628 0 0 108.86768 63.121 20.5275)"><stop offset="0" stop-color="#FE7B02" stop-opacity="0.9728"/><stop offset="0.075" stop-color="#FE7B02" stop-opacity="0.9627"/><stop offset="0.135" stop-color="#FE7B02" stop-opacity="0.9373"/><stop offset="0.16" stop-color="#FE7B02" stop-opacity="0.9211"/><stop offset="0.2" stop-color="#FE7B02" stop-opacity="0.8856"/><stop offset="0.25" stop-color="#FE7B02" stop-opacity="0.825"/><stop offset="0.295" stop-color="#FE7B02" stop-opacity="0.7538"/><stop offset="0.35" stop-color="#FE7B02" stop-opacity="0.6472"/><stop offset="0.395" stop-color="#FE7B02" stop-opacity="0.5483"/><stop offset="0.45" stop-color="#FE7B02" stop-opacity="0.4201"/><stop offset="0.525" stop-color="#FE7B02" stop-opacity="0.2629"/><stop offset="0.57" stop-color="#FE7B02" stop-opacity="0.1851"/><stop offset="0.595" stop-color="#FE7B02" stop-opacity="0.1489"/><stop offset="0.645" stop-color="#FE7B02" stop-opacity="0.0913"/><stop offset="0.7" stop-color="#FE7B02" stop-opacity="0.0489"/><stop offset="0.77" stop-color="#FE7B02" stop-opacity="0.0193"/><stop offset="0.855" stop-color="#FE7B02" stop-opacity="0.0051"/><stop offset="1" stop-color="#FE7B02" stop-opacity="0"/></radialGradient></defs>
|
|
16
|
+
</svg>
|
package/dist/index.cjs
CHANGED
|
@@ -1,204 +1,158 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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;
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/auth.ts
|
|
3
|
+
const EXPECTED_MESSAGE_TYPE = "authorization_response";
|
|
4
|
+
const DEFAULT_OAUTH_BROKER_URL = "/~oauth/initiate";
|
|
5
|
+
const DEFAULT_SUPPORTED_OAUTH_ORIGINS = ["https://oauth.lovable.app", "https://lovable.dev"];
|
|
6
|
+
const DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI = "lovable://oauth-callback";
|
|
7
|
+
const DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI = "http://127.0.0.1/iframe-oauth/callback";
|
|
8
|
+
const POPUP_CHECK_INTERVAL_MS = 500;
|
|
9
|
+
const IFRAME_FALLBACK_TIMEOUT_MS = 12e4;
|
|
10
|
+
const MOBILE_APP_SCHEME_UA_TOKEN = /LovableAppScheme\/([a-z0-9.+-]+)/i;
|
|
11
|
+
function mobileDeepLinkRedirectUri(ua) {
|
|
12
|
+
const scheme = MOBILE_APP_SCHEME_UA_TOKEN.exec(ua)?.[1];
|
|
13
|
+
return scheme ? `${scheme}://oauth-callback` : DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI;
|
|
14
|
+
}
|
|
35
15
|
function startWebMessageListener(supportedOrigins) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
window.removeEventListener("message", callback);
|
|
56
|
-
};
|
|
57
|
-
window.addEventListener("message", callback);
|
|
58
|
-
return {
|
|
59
|
-
cleanup,
|
|
60
|
-
messagePromise: promise
|
|
61
|
-
};
|
|
16
|
+
let resolvePromise;
|
|
17
|
+
const promise = new Promise((resolve) => {
|
|
18
|
+
resolvePromise = resolve;
|
|
19
|
+
});
|
|
20
|
+
const callback = (e) => {
|
|
21
|
+
if (!supportedOrigins.some((origin) => e.origin === origin)) return;
|
|
22
|
+
const data = e.data;
|
|
23
|
+
if (!data || typeof data !== "object") return;
|
|
24
|
+
if (data.type !== EXPECTED_MESSAGE_TYPE) return;
|
|
25
|
+
resolvePromise(data.response);
|
|
26
|
+
};
|
|
27
|
+
const cleanup = () => {
|
|
28
|
+
window.removeEventListener("message", callback);
|
|
29
|
+
};
|
|
30
|
+
window.addEventListener("message", callback);
|
|
31
|
+
return {
|
|
32
|
+
cleanup,
|
|
33
|
+
messagePromise: promise
|
|
34
|
+
};
|
|
62
35
|
}
|
|
63
36
|
function getPopupDimensions(isInIframe) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
37
|
+
const hasBrowserPosition = window.screenX !== 0 || window.screenY !== 0 || !isInIframe;
|
|
38
|
+
const width = hasBrowserPosition ? window.outerWidth * .5 : window.screen.width * .5;
|
|
39
|
+
const height = hasBrowserPosition ? window.outerHeight * .5 : window.screen.height * .5;
|
|
40
|
+
return {
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
left: hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2,
|
|
44
|
+
top: hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2
|
|
45
|
+
};
|
|
70
46
|
}
|
|
71
47
|
function processOAuthResponse(data, expectedState) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
tokens: { access_token: data.access_token, refresh_token: data.refresh_token },
|
|
88
|
-
error: null
|
|
89
|
-
};
|
|
48
|
+
if (data.state !== expectedState) return { error: /* @__PURE__ */ new Error("State is invalid") };
|
|
49
|
+
if (data.error) {
|
|
50
|
+
if (data.error === "legacy_flow") return { error: /* @__PURE__ */ new Error("This flow is not supported in Preview mode. Please open the app in a new tab to sign in.") };
|
|
51
|
+
return { error: new Error(data.error_description ?? "Sign in failed") };
|
|
52
|
+
}
|
|
53
|
+
if (!data.access_token || !data.refresh_token) return { error: /* @__PURE__ */ new Error("No tokens received") };
|
|
54
|
+
return {
|
|
55
|
+
tokens: {
|
|
56
|
+
access_token: data.access_token,
|
|
57
|
+
refresh_token: data.refresh_token
|
|
58
|
+
},
|
|
59
|
+
error: null
|
|
60
|
+
};
|
|
90
61
|
}
|
|
91
62
|
function isDevice() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return true;
|
|
97
|
-
return false;
|
|
63
|
+
const ua = navigator.userAgent;
|
|
64
|
+
if (/iPhone|iPad|iPod|Android/i.test(ua)) return true;
|
|
65
|
+
if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1) return true;
|
|
66
|
+
return false;
|
|
98
67
|
}
|
|
99
68
|
function generateState() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
69
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) return [...crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
70
|
+
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
104
71
|
}
|
|
105
72
|
function createAuth(config = {}) {
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
};
|
|
73
|
+
const oauthBrokerUrl = config.oauthBrokerUrl ?? DEFAULT_OAUTH_BROKER_URL;
|
|
74
|
+
const supportedOAuthOrigins = config.supportedOAuthOrigins ?? DEFAULT_SUPPORTED_OAUTH_ORIGINS;
|
|
75
|
+
async function signInWithOAuth(provider, opts = {}) {
|
|
76
|
+
let isInIframe = false;
|
|
77
|
+
try {
|
|
78
|
+
isInIframe = window.self !== window.top;
|
|
79
|
+
} catch {
|
|
80
|
+
isInIframe = true;
|
|
81
|
+
}
|
|
82
|
+
const ua = navigator.userAgent;
|
|
83
|
+
const isMobileApp = /LovableApp\//.test(ua);
|
|
84
|
+
const isDesktopApp = !isMobileApp && /lovable/i.test(ua);
|
|
85
|
+
const state = generateState();
|
|
86
|
+
let redirectUri = opts.redirect_uri ?? window.location.origin;
|
|
87
|
+
if (isMobileApp && isInIframe) redirectUri = mobileDeepLinkRedirectUri(ua);
|
|
88
|
+
else if (isDesktopApp && isInIframe) redirectUri = DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI;
|
|
89
|
+
const params = new URLSearchParams({
|
|
90
|
+
...opts.extraParams,
|
|
91
|
+
provider,
|
|
92
|
+
redirect_uri: redirectUri,
|
|
93
|
+
state
|
|
94
|
+
});
|
|
95
|
+
if (!isInIframe) {
|
|
96
|
+
window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
|
|
97
|
+
return {
|
|
98
|
+
error: null,
|
|
99
|
+
redirected: true
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (!isMobileApp && !isDesktopApp) params.set("response_mode", "web_message");
|
|
103
|
+
const url = `${oauthBrokerUrl}?${params.toString()}`;
|
|
104
|
+
const { messagePromise, cleanup } = startWebMessageListener(isDesktopApp ? [...supportedOAuthOrigins, window.location.origin] : supportedOAuthOrigins);
|
|
105
|
+
let popup;
|
|
106
|
+
if (isDevice()) popup = window.open(url, "_blank");
|
|
107
|
+
else {
|
|
108
|
+
const { width, height, left, top } = getPopupDimensions(isInIframe);
|
|
109
|
+
popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
110
|
+
}
|
|
111
|
+
if (!popup && (isMobileApp || isDesktopApp)) {
|
|
112
|
+
let webViewTimeoutId;
|
|
113
|
+
const webViewTimeoutPromise = new Promise((_, reject) => {
|
|
114
|
+
webViewTimeoutId = setTimeout(() => {
|
|
115
|
+
reject(/* @__PURE__ */ new Error("OAuth timed out waiting for response"));
|
|
116
|
+
}, IFRAME_FALLBACK_TIMEOUT_MS);
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
return processOAuthResponse(await Promise.race([messagePromise, webViewTimeoutPromise]), state);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
122
|
+
} finally {
|
|
123
|
+
if (webViewTimeoutId) clearTimeout(webViewTimeoutId);
|
|
124
|
+
cleanup();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!popup) {
|
|
128
|
+
cleanup();
|
|
129
|
+
return { error: /* @__PURE__ */ new Error("Popup was blocked") };
|
|
130
|
+
}
|
|
131
|
+
let popupCheckInterval;
|
|
132
|
+
const popupClosedPromise = new Promise((_, reject) => {
|
|
133
|
+
popupCheckInterval = setInterval(() => {
|
|
134
|
+
if (popup.closed) {
|
|
135
|
+
clearInterval(popupCheckInterval);
|
|
136
|
+
reject(/* @__PURE__ */ new Error("Sign in was cancelled"));
|
|
137
|
+
}
|
|
138
|
+
}, POPUP_CHECK_INTERVAL_MS);
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
return processOAuthResponse(await Promise.race([messagePromise, popupClosedPromise]), state);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
144
|
+
} finally {
|
|
145
|
+
clearInterval(popupCheckInterval);
|
|
146
|
+
cleanup();
|
|
147
|
+
popup?.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { signInWithOAuth };
|
|
195
151
|
}
|
|
196
|
-
|
|
197
|
-
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/index.ts
|
|
198
154
|
function createLovableAuth(config = {}) {
|
|
199
|
-
|
|
155
|
+
return createAuth(config);
|
|
200
156
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
createLovableAuth
|
|
204
|
-
});
|
|
157
|
+
//#endregion
|
|
158
|
+
exports.createLovableAuth = createLovableAuth;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
1
2
|
type OAuthProvider = "google" | "apple" | "microsoft" | "lovable";
|
|
2
3
|
interface LovableAuthConfig {
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
oauthBrokerUrl?: string;
|
|
5
|
+
supportedOAuthOrigins?: string[];
|
|
5
6
|
}
|
|
6
7
|
interface OAuthTokens {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
access_token: string;
|
|
9
|
+
refresh_token: string;
|
|
9
10
|
}
|
|
10
11
|
interface SignInWithOAuthOptions {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
redirect_uri?: string;
|
|
13
|
+
extraParams?: Record<string, string>;
|
|
13
14
|
}
|
|
14
15
|
type SignInWithOAuthResult = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
tokens: OAuthTokens;
|
|
17
|
+
error: null;
|
|
18
|
+
redirected?: false;
|
|
18
19
|
} | {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
tokens?: undefined;
|
|
21
|
+
error: Error;
|
|
22
|
+
redirected?: false;
|
|
22
23
|
} | {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
tokens?: undefined;
|
|
25
|
+
error: null;
|
|
26
|
+
redirected: true;
|
|
26
27
|
};
|
|
27
28
|
interface LovableAuth {
|
|
28
|
-
|
|
29
|
+
signInWithOAuth: (provider: OAuthProvider, opts?: SignInWithOAuthOptions) => Promise<SignInWithOAuthResult>;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/index.d.ts
|
|
31
33
|
declare function createLovableAuth(config?: LovableAuthConfig): LovableAuth;
|
|
32
|
-
|
|
33
|
-
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
|
34
|
+
//#endregion
|
|
35
|
+
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
1
2
|
type OAuthProvider = "google" | "apple" | "microsoft" | "lovable";
|
|
2
3
|
interface LovableAuthConfig {
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
oauthBrokerUrl?: string;
|
|
5
|
+
supportedOAuthOrigins?: string[];
|
|
5
6
|
}
|
|
6
7
|
interface OAuthTokens {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
access_token: string;
|
|
9
|
+
refresh_token: string;
|
|
9
10
|
}
|
|
10
11
|
interface SignInWithOAuthOptions {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
redirect_uri?: string;
|
|
13
|
+
extraParams?: Record<string, string>;
|
|
13
14
|
}
|
|
14
15
|
type SignInWithOAuthResult = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
tokens: OAuthTokens;
|
|
17
|
+
error: null;
|
|
18
|
+
redirected?: false;
|
|
18
19
|
} | {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
tokens?: undefined;
|
|
21
|
+
error: Error;
|
|
22
|
+
redirected?: false;
|
|
22
23
|
} | {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
tokens?: undefined;
|
|
25
|
+
error: null;
|
|
26
|
+
redirected: true;
|
|
26
27
|
};
|
|
27
28
|
interface LovableAuth {
|
|
28
|
-
|
|
29
|
+
signInWithOAuth: (provider: OAuthProvider, opts?: SignInWithOAuthOptions) => Promise<SignInWithOAuthResult>;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/index.d.ts
|
|
31
33
|
declare function createLovableAuth(config?: LovableAuthConfig): LovableAuth;
|
|
32
|
-
|
|
33
|
-
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
|
34
|
+
//#endregion
|
|
35
|
+
export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
|
package/dist/index.js
CHANGED
|
@@ -1,177 +1,157 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
//#region src/auth.ts
|
|
2
|
+
const EXPECTED_MESSAGE_TYPE = "authorization_response";
|
|
3
|
+
const DEFAULT_OAUTH_BROKER_URL = "/~oauth/initiate";
|
|
4
|
+
const DEFAULT_SUPPORTED_OAUTH_ORIGINS = ["https://oauth.lovable.app", "https://lovable.dev"];
|
|
5
|
+
const DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI = "lovable://oauth-callback";
|
|
6
|
+
const DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI = "http://127.0.0.1/iframe-oauth/callback";
|
|
7
|
+
const POPUP_CHECK_INTERVAL_MS = 500;
|
|
8
|
+
const IFRAME_FALLBACK_TIMEOUT_MS = 12e4;
|
|
9
|
+
const MOBILE_APP_SCHEME_UA_TOKEN = /LovableAppScheme\/([a-z0-9.+-]+)/i;
|
|
10
|
+
function mobileDeepLinkRedirectUri(ua) {
|
|
11
|
+
const scheme = MOBILE_APP_SCHEME_UA_TOKEN.exec(ua)?.[1];
|
|
12
|
+
return scheme ? `${scheme}://oauth-callback` : DEFAULT_MOBILE_DEEP_LINK_REDIRECT_URI;
|
|
13
|
+
}
|
|
9
14
|
function startWebMessageListener(supportedOrigins) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
window.removeEventListener("message", callback);
|
|
30
|
-
};
|
|
31
|
-
window.addEventListener("message", callback);
|
|
32
|
-
return {
|
|
33
|
-
cleanup,
|
|
34
|
-
messagePromise: promise
|
|
35
|
-
};
|
|
15
|
+
let resolvePromise;
|
|
16
|
+
const promise = new Promise((resolve) => {
|
|
17
|
+
resolvePromise = resolve;
|
|
18
|
+
});
|
|
19
|
+
const callback = (e) => {
|
|
20
|
+
if (!supportedOrigins.some((origin) => e.origin === origin)) return;
|
|
21
|
+
const data = e.data;
|
|
22
|
+
if (!data || typeof data !== "object") return;
|
|
23
|
+
if (data.type !== EXPECTED_MESSAGE_TYPE) return;
|
|
24
|
+
resolvePromise(data.response);
|
|
25
|
+
};
|
|
26
|
+
const cleanup = () => {
|
|
27
|
+
window.removeEventListener("message", callback);
|
|
28
|
+
};
|
|
29
|
+
window.addEventListener("message", callback);
|
|
30
|
+
return {
|
|
31
|
+
cleanup,
|
|
32
|
+
messagePromise: promise
|
|
33
|
+
};
|
|
36
34
|
}
|
|
37
35
|
function getPopupDimensions(isInIframe) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
const hasBrowserPosition = window.screenX !== 0 || window.screenY !== 0 || !isInIframe;
|
|
37
|
+
const width = hasBrowserPosition ? window.outerWidth * .5 : window.screen.width * .5;
|
|
38
|
+
const height = hasBrowserPosition ? window.outerHeight * .5 : window.screen.height * .5;
|
|
39
|
+
return {
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
left: hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2,
|
|
43
|
+
top: hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2
|
|
44
|
+
};
|
|
44
45
|
}
|
|
45
46
|
function processOAuthResponse(data, expectedState) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
tokens: { access_token: data.access_token, refresh_token: data.refresh_token },
|
|
62
|
-
error: null
|
|
63
|
-
};
|
|
47
|
+
if (data.state !== expectedState) return { error: /* @__PURE__ */ new Error("State is invalid") };
|
|
48
|
+
if (data.error) {
|
|
49
|
+
if (data.error === "legacy_flow") return { error: /* @__PURE__ */ new Error("This flow is not supported in Preview mode. Please open the app in a new tab to sign in.") };
|
|
50
|
+
return { error: new Error(data.error_description ?? "Sign in failed") };
|
|
51
|
+
}
|
|
52
|
+
if (!data.access_token || !data.refresh_token) return { error: /* @__PURE__ */ new Error("No tokens received") };
|
|
53
|
+
return {
|
|
54
|
+
tokens: {
|
|
55
|
+
access_token: data.access_token,
|
|
56
|
+
refresh_token: data.refresh_token
|
|
57
|
+
},
|
|
58
|
+
error: null
|
|
59
|
+
};
|
|
64
60
|
}
|
|
65
61
|
function isDevice() {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return true;
|
|
71
|
-
return false;
|
|
62
|
+
const ua = navigator.userAgent;
|
|
63
|
+
if (/iPhone|iPad|iPod|Android/i.test(ua)) return true;
|
|
64
|
+
if (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1) return true;
|
|
65
|
+
return false;
|
|
72
66
|
}
|
|
73
67
|
function generateState() {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
68
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) return [...crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
69
|
+
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
78
70
|
}
|
|
79
71
|
function createAuth(config = {}) {
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
};
|
|
72
|
+
const oauthBrokerUrl = config.oauthBrokerUrl ?? DEFAULT_OAUTH_BROKER_URL;
|
|
73
|
+
const supportedOAuthOrigins = config.supportedOAuthOrigins ?? DEFAULT_SUPPORTED_OAUTH_ORIGINS;
|
|
74
|
+
async function signInWithOAuth(provider, opts = {}) {
|
|
75
|
+
let isInIframe = false;
|
|
76
|
+
try {
|
|
77
|
+
isInIframe = window.self !== window.top;
|
|
78
|
+
} catch {
|
|
79
|
+
isInIframe = true;
|
|
80
|
+
}
|
|
81
|
+
const ua = navigator.userAgent;
|
|
82
|
+
const isMobileApp = /LovableApp\//.test(ua);
|
|
83
|
+
const isDesktopApp = !isMobileApp && /lovable/i.test(ua);
|
|
84
|
+
const state = generateState();
|
|
85
|
+
let redirectUri = opts.redirect_uri ?? window.location.origin;
|
|
86
|
+
if (isMobileApp && isInIframe) redirectUri = mobileDeepLinkRedirectUri(ua);
|
|
87
|
+
else if (isDesktopApp && isInIframe) redirectUri = DEFAULT_DESKTOP_LOCALHOST_REDIRECT_URI;
|
|
88
|
+
const params = new URLSearchParams({
|
|
89
|
+
...opts.extraParams,
|
|
90
|
+
provider,
|
|
91
|
+
redirect_uri: redirectUri,
|
|
92
|
+
state
|
|
93
|
+
});
|
|
94
|
+
if (!isInIframe) {
|
|
95
|
+
window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
|
|
96
|
+
return {
|
|
97
|
+
error: null,
|
|
98
|
+
redirected: true
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!isMobileApp && !isDesktopApp) params.set("response_mode", "web_message");
|
|
102
|
+
const url = `${oauthBrokerUrl}?${params.toString()}`;
|
|
103
|
+
const { messagePromise, cleanup } = startWebMessageListener(isDesktopApp ? [...supportedOAuthOrigins, window.location.origin] : supportedOAuthOrigins);
|
|
104
|
+
let popup;
|
|
105
|
+
if (isDevice()) popup = window.open(url, "_blank");
|
|
106
|
+
else {
|
|
107
|
+
const { width, height, left, top } = getPopupDimensions(isInIframe);
|
|
108
|
+
popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
109
|
+
}
|
|
110
|
+
if (!popup && (isMobileApp || isDesktopApp)) {
|
|
111
|
+
let webViewTimeoutId;
|
|
112
|
+
const webViewTimeoutPromise = new Promise((_, reject) => {
|
|
113
|
+
webViewTimeoutId = setTimeout(() => {
|
|
114
|
+
reject(/* @__PURE__ */ new Error("OAuth timed out waiting for response"));
|
|
115
|
+
}, IFRAME_FALLBACK_TIMEOUT_MS);
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
return processOAuthResponse(await Promise.race([messagePromise, webViewTimeoutPromise]), state);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
121
|
+
} finally {
|
|
122
|
+
if (webViewTimeoutId) clearTimeout(webViewTimeoutId);
|
|
123
|
+
cleanup();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!popup) {
|
|
127
|
+
cleanup();
|
|
128
|
+
return { error: /* @__PURE__ */ new Error("Popup was blocked") };
|
|
129
|
+
}
|
|
130
|
+
let popupCheckInterval;
|
|
131
|
+
const popupClosedPromise = new Promise((_, reject) => {
|
|
132
|
+
popupCheckInterval = setInterval(() => {
|
|
133
|
+
if (popup.closed) {
|
|
134
|
+
clearInterval(popupCheckInterval);
|
|
135
|
+
reject(/* @__PURE__ */ new Error("Sign in was cancelled"));
|
|
136
|
+
}
|
|
137
|
+
}, POPUP_CHECK_INTERVAL_MS);
|
|
138
|
+
});
|
|
139
|
+
try {
|
|
140
|
+
return processOAuthResponse(await Promise.race([messagePromise, popupClosedPromise]), state);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
143
|
+
} finally {
|
|
144
|
+
clearInterval(popupCheckInterval);
|
|
145
|
+
cleanup();
|
|
146
|
+
popup?.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { signInWithOAuth };
|
|
169
150
|
}
|
|
170
|
-
|
|
171
|
-
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/index.ts
|
|
172
153
|
function createLovableAuth(config = {}) {
|
|
173
|
-
|
|
154
|
+
return createAuth(config);
|
|
174
155
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
};
|
|
156
|
+
//#endregion
|
|
157
|
+
export { createLovableAuth };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/cloud-auth-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Lovable legacy OAuth broker JS",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,11 +20,17 @@
|
|
|
20
20
|
"types": "./dist/index.d.cts",
|
|
21
21
|
"default": "./dist/index.cjs"
|
|
22
22
|
}
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"./styles.css": "./styles.css"
|
|
24
25
|
},
|
|
25
26
|
"type": "module",
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"styles.css"
|
|
29
|
+
],
|
|
26
30
|
"files": [
|
|
27
|
-
"
|
|
31
|
+
"assets",
|
|
32
|
+
"dist",
|
|
33
|
+
"styles.css"
|
|
28
34
|
],
|
|
29
35
|
"keywords": [
|
|
30
36
|
"lovable",
|
|
@@ -36,14 +42,14 @@
|
|
|
36
42
|
"devDependencies": {
|
|
37
43
|
"jsdom": "^26",
|
|
38
44
|
"rimraf": "^5.0.0",
|
|
39
|
-
"
|
|
40
|
-
"typescript": "
|
|
45
|
+
"tsdown": "^0.22.5",
|
|
46
|
+
"typescript": "7.0.2",
|
|
41
47
|
"vitest": "^4.1.0"
|
|
42
48
|
},
|
|
43
49
|
"scripts": {
|
|
44
|
-
"build": "
|
|
50
|
+
"build": "tsdown",
|
|
45
51
|
"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": "
|
|
52
|
+
"dev": "tsdown --watch",
|
|
47
53
|
"test": "vitest run",
|
|
48
54
|
"typecheck": "tsgo --noEmit",
|
|
49
55
|
"clean": "rimraf dist"
|
package/styles.css
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* Camera Plain subset for "Continue", "Sign in", and "Sign up with Lovable". */
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: "Lovable Camera";
|
|
4
|
+
src: url("data:font/woff2;base64,d09GMgABAAAAAAd8AA4AAAAADtQAAAckAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiIbhTQcKgZgP1NUQVQyAHwKjHyLFQE2AiQDVAssAAQgBYQ8ByAbzAxRVHLGJPmqwCYyfeBqUOxlWdQnzDhDPGUcl7sHW1HWYlOE+mlLn/CESGj4GES0hs7u7dPdJ1xECssyKxIGQZV1dFmhQrKs6Yd/T3sdmVHKulRM67RLncknMSfz1zw83Ps79/05aOqoMMAptBZY/ACbYglMjja1AcPeP1nT+xIrcE1lNcpaxG2OgB0QQAH8J8cEo//9fq3+u7tYMmnmpfya7vtiZ60h4k3EsqbuNh2fzhASIUOi0go1EiIxsq6ZaxSDsxKoOOflf6ciQA4AKM6LNvJooxRGOt3T3XA6fa2ZCtw27ugxaSXwfXz3Gw5wDDgACghKzExWpAgpykAGDE9molAG8F2Kc4oDgXQCvNMVT2dCDoBQSUBlXqrS3HUueRCvmisGTyz+FBXfyP606Xn2LyHsk97xiOb9IEbC7B+mkoxhVD35n7FDNRTjnwBb7J98SdFyYhquLnCBl2Mas5Cx20UfxnTtR2++B306hKbwFy+lSobchkopCRkROQrEeKIMdxVzQAmBEqGKkdqeHp0BAgWZORQSUkrJu1jdU+9rG+Y/ip/trCaOQEQaDc0/hfPlXPrIfn7g+wJmqrE6QKwXm7JUVQ3KhdtTXJvJAEi+JTEAAmDVhXMAnTnkEZRBtXy37nTPkUxPAf7OAuW6qyBRJkegnAy49GoOiTbW3rWlco6YzqZp1kkIJaeiZZoFSEDSKIz+s5p9Htu3Y/XKFcuXLSbwMqGFeX5f0AbyB3CtQZ67MJ8pHFslRaGRHt+1NHSBwWJgeo0ZbIy1btPxVTr0COXtl8s3End0FTJ2dlb4F7yXT9/eJ33bqW3e26sNz8zePqK2b9Gd8k95sCYoqglriRq7RiMoceaY5tI245YVDx6QAi4ozZbaS49ul9Wo3Ohf6gRJqH14/KVdRPjoC0m1ID2uKFjeSZD8ojGsZgLwWWB06EtVA3FDsVkeXtPESDqVR9lyT3V9GTPx3ixAOKUIMq12drpZO5YS4iHkzhojalYzhfq8jw5KGTHYaU77FlaKMa2xUzec6VlgrH3NyrBedoTdoPZsluTtwE692OV7BQQRM4EaUu9W7JrIsbdsYHt//sf352FS4qbWQsbKBOn1smeOmG1Llb/vzxgHh09lvxvmvSOUjZC9thcuk+0On0x2juZuN+cTU2xPmtYXOLveuVZmXr7uOAv8lcJhiPps9nvuthRw22f32APKnDNWVGRiRYsUHsltVrcUp8KyQhq0P3EodCMPXqQxEidfYXMXPFLqT9qGnkNXV2mHK3jwaH6TrAbG5H2hd7Oq4o7vXjNivnSing/kljfSp3Nm71i13H9j4N+6wODYxJQmXlqduFdma3cGJVKuOsHs6dtP5241XpRBRHrIuaNu/nmOfuVS+fxiZGDpUGv1eOeKytPWmEY3Lx8xxKE2N6gQGESbViYc8uObX3RC5JfjEFM0PKcaGTs5Lvpdt0n6eypltHC5SVi0x2Ao2mcSIwghLt1n0JfuJYRI8MIgcpwEDR6YLb/6NxK/95Inx6/0YD8bwsKKF9xjpOOFi7UNcOcArX67xTmpbu8YjSMYxN3cjjn7aLae5PaDJZ+gokspjkftbEc5ivGZK+Y8Pw3ZCYfmVDNGTgyJv+QvQb+NjQwzZlcKhiH2ji+Hby/Ab28qZwsaCVbpeqUy35JgIoiJWbROpSpabz8pAhy7lpEXAlVeMNsyu5wtXdDVskL+oP+MVKwWtplbqpZhZSQgUiESItXLAHaZaBOHyvz1jih3bwL/U7sJ/72VPGNGzmXGLGLxVWZ0LSfY5Xv7js++MYwQrPI9fX3AsGLI1v4il01kLQlK68mpJkR95GKvajghgWYttXWeX8n7n/exu662qDwhbvGnHA4wWNLRvhx70H8alWvQFn1TxQJlAwkdKAtt9oW5lukVdGBFhc57BXCjvLy5x/wOQNsAGoKOu0/Nb/X0nn/To2t+jbth/h7vaGin571nVK/9z3YB8vsDgw7cCBiEA7lefuWsqwCmVHeBzmA3IBsLNyXv/4ACKLzStdzAa43/c7BOAfigXvUAgK8vv4qzkQw4lCYXARIUAIHk2/8Q0tkEaWINBH7dFoV1tsU80SKS3G/b8iNQkx9p9zwMJ/N6TLtvU36lhXHp2HRdRuQdYStvOZkMQk/RV5qRexdTrDYkbsFKFKBFlJKwme2wPvejzt3AGrGs4gRwptBgQaZSyo9MY73/GTErY3UhgVwekrzdF5ZpaFng5KRk9MINNdhQQ3Q2nRxGQE0jXG9zacgIYDAonXBT4VAWKFyyzmYSEJLTmNHs3DihBANLplCls8OHE52onlu3uxFKLNV8GsaubJUQzYhJLQ0wImkJTguEQ69TFm7aJIGKW8ItpjIhn4K7JiDvDvCPHgbST8WHWrFwf6xpeuN7cilVNOjWljTQmx9IRU7EGgAKvpU4AAA=")
|
|
5
|
+
format("woff2");
|
|
6
|
+
font-weight: 400;
|
|
7
|
+
font-style: normal;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.lovable-auth-button {
|
|
11
|
+
position: relative;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
min-width: 220px;
|
|
14
|
+
min-height: 44px;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
padding: 10px 16px;
|
|
19
|
+
border: 1px solid #c5cad3;
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
appearance: none;
|
|
22
|
+
background: #ffffff;
|
|
23
|
+
color: #000000;
|
|
24
|
+
font-family: "Lovable Camera", ui-sans-serif, system-ui, sans-serif;
|
|
25
|
+
font-synthesis: none;
|
|
26
|
+
font-size: 16px;
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
line-height: 22px;
|
|
29
|
+
text-align: center;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.lovable-auth-button::before {
|
|
34
|
+
position: absolute;
|
|
35
|
+
inset-inline-start: 16px;
|
|
36
|
+
width: 16px;
|
|
37
|
+
height: 17px;
|
|
38
|
+
background: url("./assets/lovable-mark.svg") center / contain no-repeat;
|
|
39
|
+
content: "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.lovable-auth-button:hover:not(:disabled) {
|
|
43
|
+
border-color: #9ca3af;
|
|
44
|
+
background: #f9fafb;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.lovable-auth-button--dark {
|
|
48
|
+
border-color: #4b5563;
|
|
49
|
+
background: #000000;
|
|
50
|
+
color: #ffffff;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.lovable-auth-button--dark:hover:not(:disabled) {
|
|
54
|
+
border-color: #6b7280;
|
|
55
|
+
background: #0a0a0a;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.lovable-auth-button:focus-visible {
|
|
59
|
+
outline: 3px solid #4b73ff;
|
|
60
|
+
outline-offset: 2px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.lovable-auth-button:disabled {
|
|
64
|
+
cursor: not-allowed;
|
|
65
|
+
opacity: 0.55;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.lovable-auth-button--full-width {
|
|
69
|
+
width: 100%;
|
|
70
|
+
min-width: 0;
|
|
71
|
+
}
|