@lovable.dev/cloud-auth-js 0.0.1-dev.2

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 ADDED
@@ -0,0 +1,88 @@
1
+ # @lovable.dev/cloud-auth-js
2
+
3
+ OAuth authentication library for Lovable projects using Supabase.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lovable.dev/cloud-auth-js
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { createLovableAuth } from "@lovable.dev/cloud-auth-js";
15
+ import { supabase } from "./supabase/client";
16
+
17
+ const lovableAuth = createLovableAuth();
18
+
19
+ // Sign in with OAuth
20
+ const result = await lovableAuth.signInWithOAuth("google");
21
+
22
+ if (result.redirected) {
23
+ // Page is redirecting to OAuth provider
24
+ return;
25
+ }
26
+
27
+ if (result.error) {
28
+ console.error("Sign in failed:", result.error.message);
29
+ return;
30
+ }
31
+
32
+ await supabase.auth.setSession(result.tokens);
33
+ ```
34
+
35
+ ## API
36
+
37
+ ### `createLovableAuth(config?)`
38
+
39
+ Creates a Lovable auth instance.
40
+
41
+ **Config options:**
42
+
43
+ | Option | Type | Default | Description |
44
+ |--------|------|---------|-------------|
45
+ | `oauthBrokerUrl` | `string` | `"/~oauth/initiate"` | OAuth broker initiate URL |
46
+ | `supportedOAuthOrigins` | `string[]` | `["https://oauth.lovable.app"]` | Allowed origins for OAuth postMessage |
47
+
48
+ ### `signInWithOAuth(provider, options?)`
49
+
50
+ Initiates OAuth sign-in flow.
51
+
52
+ **Parameters:**
53
+
54
+ | Parameter | Type | Description |
55
+ |-----------|------|-------------|
56
+ | `provider` | `"google" \| "apple"` | OAuth provider |
57
+ | `options.redirect_uri` | `string` | Custom redirect URI (defaults to `window.location.origin`) |
58
+ | `options.extraParams` | `Record<string, string>` | Additional params to send to the OAuth broker |
59
+
60
+ **Returns:** `Promise<SignInWithOAuthResult>`
61
+
62
+ ## Types
63
+
64
+ ```typescript
65
+ interface OAuthTokens {
66
+ access_token: string;
67
+ refresh_token: string;
68
+ }
69
+
70
+ interface LovableAuthConfig {
71
+ oauthBrokerUrl?: string;
72
+ supportedOAuthOrigins?: string[];
73
+ }
74
+
75
+ interface SignInWithOAuthOptions {
76
+ redirect_uri?: string;
77
+ extraParams?: Record<string, string>;
78
+ }
79
+
80
+ type SignInWithOAuthResult =
81
+ | { tokens: OAuthTokens; error: null; redirected?: false }
82
+ | { tokens?: undefined; error: Error; redirected?: false }
83
+ | { tokens?: undefined; error: null; redirected: true };
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,168 @@
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"];
31
+ function startWebMessageListener(supportedOrigins) {
32
+ let resolvePromise;
33
+ const promise = new Promise((resolve) => {
34
+ resolvePromise = resolve;
35
+ });
36
+ const callback = (e) => {
37
+ const isValidOrigin = supportedOrigins.some((origin) => e.origin === origin);
38
+ if (!isValidOrigin) {
39
+ return;
40
+ }
41
+ const data = e.data;
42
+ if (!data || typeof data !== "object") {
43
+ return;
44
+ }
45
+ if (data.type !== EXPECTED_MESSAGE_TYPE) {
46
+ return;
47
+ }
48
+ resolvePromise(data.response);
49
+ };
50
+ const cleanup = () => {
51
+ window.removeEventListener("message", callback);
52
+ };
53
+ window.addEventListener("message", callback);
54
+ return {
55
+ cleanup,
56
+ messagePromise: promise
57
+ };
58
+ }
59
+ function getPopupDimensions(isInIframe) {
60
+ const hasBrowserPosition = window.screenX !== 0 || window.screenY !== 0 || !isInIframe;
61
+ const width = hasBrowserPosition ? window.outerWidth * 0.5 : window.screen.width * 0.5;
62
+ const height = hasBrowserPosition ? window.outerHeight * 0.5 : window.screen.height * 0.5;
63
+ const left = hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2;
64
+ const top = hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2;
65
+ return { width, height, left, top };
66
+ }
67
+ function generateState() {
68
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
69
+ return [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
70
+ }
71
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
72
+ }
73
+ function createAuth(config = {}) {
74
+ const oauthBrokerUrl = config.oauthBrokerUrl ?? DEFAULT_OAUTH_BROKER_URL;
75
+ const supportedOAuthOrigins = config.supportedOAuthOrigins ?? DEFAULT_SUPPORTED_OAUTH_ORIGINS;
76
+ async function signInWithOAuth(provider, opts = {}) {
77
+ let isInIframe = false;
78
+ try {
79
+ isInIframe = window.self !== window.top;
80
+ } catch {
81
+ isInIframe = true;
82
+ }
83
+ const state = generateState();
84
+ const redirectUri = opts.redirect_uri ?? window.location.origin;
85
+ const params = new URLSearchParams({
86
+ provider,
87
+ redirect_uri: redirectUri,
88
+ state,
89
+ ...opts.extraParams
90
+ });
91
+ if (!isInIframe) {
92
+ window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
93
+ return { error: null, redirected: true };
94
+ }
95
+ params.set("response_mode", "web_message");
96
+ const url = `${oauthBrokerUrl}?${params.toString()}`;
97
+ const { messagePromise, cleanup } = startWebMessageListener(supportedOAuthOrigins);
98
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
99
+ let popup;
100
+ if (isMobile) {
101
+ popup = window.open(url, "_blank");
102
+ } else {
103
+ const { width, height, left, top } = getPopupDimensions(isInIframe);
104
+ popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
105
+ }
106
+ if (!popup) {
107
+ cleanup();
108
+ return { error: new Error("Popup was blocked") };
109
+ }
110
+ let popupCheckInterval;
111
+ const popupClosedPromise = new Promise((_, reject) => {
112
+ popupCheckInterval = setInterval(() => {
113
+ if (popup.closed) {
114
+ clearInterval(popupCheckInterval);
115
+ reject(new Error("Sign in was cancelled"));
116
+ }
117
+ }, 500);
118
+ });
119
+ try {
120
+ const data = await Promise.race([messagePromise, popupClosedPromise]);
121
+ if (data.error) {
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
+ };
146
+ } catch (error) {
147
+ return {
148
+ error: error instanceof Error ? error : new Error(String(error))
149
+ };
150
+ } finally {
151
+ clearInterval(popupCheckInterval);
152
+ cleanup();
153
+ popup?.close();
154
+ }
155
+ }
156
+ return {
157
+ signInWithOAuth
158
+ };
159
+ }
160
+
161
+ // src/index.ts
162
+ function createLovableAuth(config) {
163
+ return createAuth(config);
164
+ }
165
+ // Annotate the CommonJS export names for ESM import in node:
166
+ 0 && (module.exports = {
167
+ createLovableAuth
168
+ });
@@ -0,0 +1,33 @@
1
+ type OAuthProvider = "google" | "apple";
2
+ interface LovableAuthConfig {
3
+ oauthBrokerUrl?: string;
4
+ supportedOAuthOrigins?: string[];
5
+ }
6
+ interface OAuthTokens {
7
+ access_token: string;
8
+ refresh_token: string;
9
+ }
10
+ interface SignInWithOAuthOptions {
11
+ redirect_uri?: string;
12
+ extraParams?: Record<string, string>;
13
+ }
14
+ type SignInWithOAuthResult = {
15
+ tokens: OAuthTokens;
16
+ error: null;
17
+ redirected?: false;
18
+ } | {
19
+ tokens?: undefined;
20
+ error: Error;
21
+ redirected?: false;
22
+ } | {
23
+ tokens?: undefined;
24
+ error: null;
25
+ redirected: true;
26
+ };
27
+ interface LovableAuth {
28
+ signInWithOAuth: (provider: OAuthProvider, opts?: SignInWithOAuthOptions) => Promise<SignInWithOAuthResult>;
29
+ }
30
+
31
+ declare function createLovableAuth(config: LovableAuthConfig): LovableAuth;
32
+
33
+ export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
@@ -0,0 +1,33 @@
1
+ type OAuthProvider = "google" | "apple";
2
+ interface LovableAuthConfig {
3
+ oauthBrokerUrl?: string;
4
+ supportedOAuthOrigins?: string[];
5
+ }
6
+ interface OAuthTokens {
7
+ access_token: string;
8
+ refresh_token: string;
9
+ }
10
+ interface SignInWithOAuthOptions {
11
+ redirect_uri?: string;
12
+ extraParams?: Record<string, string>;
13
+ }
14
+ type SignInWithOAuthResult = {
15
+ tokens: OAuthTokens;
16
+ error: null;
17
+ redirected?: false;
18
+ } | {
19
+ tokens?: undefined;
20
+ error: Error;
21
+ redirected?: false;
22
+ } | {
23
+ tokens?: undefined;
24
+ error: null;
25
+ redirected: true;
26
+ };
27
+ interface LovableAuth {
28
+ signInWithOAuth: (provider: OAuthProvider, opts?: SignInWithOAuthOptions) => Promise<SignInWithOAuthResult>;
29
+ }
30
+
31
+ declare function createLovableAuth(config: LovableAuthConfig): LovableAuth;
32
+
33
+ export { type LovableAuth, type LovableAuthConfig, type OAuthProvider, type OAuthTokens, type SignInWithOAuthOptions, type SignInWithOAuthResult, createLovableAuth };
package/dist/index.js ADDED
@@ -0,0 +1,141 @@
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"];
5
+ function startWebMessageListener(supportedOrigins) {
6
+ let resolvePromise;
7
+ const promise = new Promise((resolve) => {
8
+ resolvePromise = resolve;
9
+ });
10
+ const callback = (e) => {
11
+ const isValidOrigin = supportedOrigins.some((origin) => e.origin === origin);
12
+ if (!isValidOrigin) {
13
+ return;
14
+ }
15
+ const data = e.data;
16
+ if (!data || typeof data !== "object") {
17
+ return;
18
+ }
19
+ if (data.type !== EXPECTED_MESSAGE_TYPE) {
20
+ return;
21
+ }
22
+ resolvePromise(data.response);
23
+ };
24
+ const cleanup = () => {
25
+ window.removeEventListener("message", callback);
26
+ };
27
+ window.addEventListener("message", callback);
28
+ return {
29
+ cleanup,
30
+ messagePromise: promise
31
+ };
32
+ }
33
+ function getPopupDimensions(isInIframe) {
34
+ const hasBrowserPosition = window.screenX !== 0 || window.screenY !== 0 || !isInIframe;
35
+ const width = hasBrowserPosition ? window.outerWidth * 0.5 : window.screen.width * 0.5;
36
+ const height = hasBrowserPosition ? window.outerHeight * 0.5 : window.screen.height * 0.5;
37
+ const left = hasBrowserPosition ? window.screenX + (window.outerWidth - width) / 2 : (window.screen.width - width) / 2;
38
+ const top = hasBrowserPosition ? window.screenY + (window.outerHeight - height) / 2 : (window.screen.height - height) / 2;
39
+ return { width, height, left, top };
40
+ }
41
+ function generateState() {
42
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
43
+ return [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
44
+ }
45
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
46
+ }
47
+ function createAuth(config = {}) {
48
+ const oauthBrokerUrl = config.oauthBrokerUrl ?? DEFAULT_OAUTH_BROKER_URL;
49
+ const supportedOAuthOrigins = config.supportedOAuthOrigins ?? DEFAULT_SUPPORTED_OAUTH_ORIGINS;
50
+ async function signInWithOAuth(provider, opts = {}) {
51
+ let isInIframe = false;
52
+ try {
53
+ isInIframe = window.self !== window.top;
54
+ } catch {
55
+ isInIframe = true;
56
+ }
57
+ const state = generateState();
58
+ const redirectUri = opts.redirect_uri ?? window.location.origin;
59
+ const params = new URLSearchParams({
60
+ provider,
61
+ redirect_uri: redirectUri,
62
+ state,
63
+ ...opts.extraParams
64
+ });
65
+ if (!isInIframe) {
66
+ window.location.href = `${oauthBrokerUrl}?${params.toString()}`;
67
+ return { error: null, redirected: true };
68
+ }
69
+ params.set("response_mode", "web_message");
70
+ const url = `${oauthBrokerUrl}?${params.toString()}`;
71
+ const { messagePromise, cleanup } = startWebMessageListener(supportedOAuthOrigins);
72
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
73
+ let popup;
74
+ if (isMobile) {
75
+ popup = window.open(url, "_blank");
76
+ } else {
77
+ const { width, height, left, top } = getPopupDimensions(isInIframe);
78
+ popup = window.open(url, "oauth", `width=${width},height=${height},left=${left},top=${top}`);
79
+ }
80
+ if (!popup) {
81
+ cleanup();
82
+ return { error: new Error("Popup was blocked") };
83
+ }
84
+ let popupCheckInterval;
85
+ const popupClosedPromise = new Promise((_, reject) => {
86
+ popupCheckInterval = setInterval(() => {
87
+ if (popup.closed) {
88
+ clearInterval(popupCheckInterval);
89
+ reject(new Error("Sign in was cancelled"));
90
+ }
91
+ }, 500);
92
+ });
93
+ try {
94
+ const data = await Promise.race([messagePromise, popupClosedPromise]);
95
+ if (data.error) {
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
+ };
120
+ } catch (error) {
121
+ return {
122
+ error: error instanceof Error ? error : new Error(String(error))
123
+ };
124
+ } finally {
125
+ clearInterval(popupCheckInterval);
126
+ cleanup();
127
+ popup?.close();
128
+ }
129
+ }
130
+ return {
131
+ signInWithOAuth
132
+ };
133
+ }
134
+
135
+ // src/index.ts
136
+ function createLovableAuth(config) {
137
+ return createAuth(config);
138
+ }
139
+ export {
140
+ createLovableAuth
141
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@lovable.dev/cloud-auth-js",
3
+ "version": "0.0.1-dev.2",
4
+ "description": "Lovable Cloud Auth JS",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "type": "module",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format cjs,esm --dts --outDir dist",
14
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
15
+ "test": "vitest run",
16
+ "typecheck": "tsc --noEmit",
17
+ "prepublishOnly": "npm run clean && npm run build",
18
+ "clean": "rimraf dist"
19
+ },
20
+ "keywords": [
21
+ "lovable",
22
+ "oauth",
23
+ "authentication"
24
+ ],
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "@types/node": "^22",
28
+ "rimraf": "^5.0.0",
29
+ "tsup": "^7.2.0",
30
+ "typescript": "^5",
31
+ "vitest": "^3"
32
+ }
33
+ }