use-convex 0.0.2 → 1.1.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 +115 -35
- package/dist/module.d.mts +5 -3
- package/dist/module.mjs +35 -1
- package/dist/runtime/composables/useAuth.d.ts +5 -5
- package/dist/runtime/composables/useAuth.js +11 -24
- package/dist/runtime/composables/useAuthToken.d.ts +1 -1
- package/dist/runtime/composables/useAuthToken.js +2 -10
- package/dist/runtime/composables/useConvex.js +1 -1
- package/dist/runtime/composables/useConvexAction.js +6 -18
- package/dist/runtime/composables/useConvexAuth.js +4 -13
- package/dist/runtime/composables/useConvexFileUpload.d.ts +43 -0
- package/dist/runtime/composables/useConvexFileUpload.js +102 -0
- package/dist/runtime/composables/useConvexGate.js +3 -9
- package/dist/runtime/composables/useConvexMutation.js +7 -17
- package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +1 -1
- package/dist/runtime/composables/useConvexPaginatedQuery.js +7 -29
- package/dist/runtime/composables/useConvexQueries.d.ts +12 -7
- package/dist/runtime/composables/useConvexQueries.js +1 -3
- package/dist/runtime/composables/useConvexQuery.js +6 -12
- package/dist/runtime/composables/useConvexR2Upload.d.ts +40 -0
- package/dist/runtime/composables/useConvexR2Upload.js +86 -0
- package/dist/runtime/plugin.client.js +3 -7
- package/dist/runtime/plugin.devtools.client.d.ts +15 -2
- package/dist/runtime/plugin.devtools.client.js +18 -4
- package/dist/runtime/plugin.server.js +3 -7
- package/dist/runtime/server/api/convex/auth/session.js +7 -12
- package/dist/runtime/server/auth.js +1 -9
- package/dist/runtime/server/authCookies.js +8 -30
- package/dist/runtime/server/convexConfig.d.ts +12 -0
- package/dist/runtime/server/convexConfig.js +9 -0
- package/dist/runtime/server/fetch.d.ts +2 -4
- package/dist/runtime/server/fetch.js +3 -13
- package/dist/runtime/server/routes/__convex_devtools.get.d.ts +6 -2
- package/dist/runtime/server/routes/__convex_devtools.get.js +184 -16
- package/dist/runtime/server/sameOrigin.js +1 -5
- package/dist/runtime/utils/authCookie.d.ts +0 -2
- package/dist/runtime/utils/authCookie.js +0 -1
- package/dist/runtime/utils/authState.js +1 -6
- package/dist/runtime/utils/authStorage.js +1 -1
- package/dist/runtime/utils/context.js +3 -2
- package/dist/runtime/utils/convexDashboard.d.ts +44 -0
- package/dist/runtime/utils/convexDashboard.js +77 -0
- package/dist/runtime/utils/errors.d.ts +14 -0
- package/dist/runtime/utils/errors.js +25 -0
- package/dist/runtime/utils/oauthRedirect.js +3 -2
- package/dist/runtime/utils/paginatedOptimistic.js +4 -22
- package/dist/runtime/utils/payloadCache.d.ts +6 -0
- package/dist/runtime/utils/payloadCache.js +7 -0
- package/dist/runtime/utils/pendingError.d.ts +9 -0
- package/dist/runtime/utils/pendingError.js +23 -0
- package/dist/types.d.mts +7 -3
- package/package.json +36 -20
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
deleteCookie,
|
|
3
|
-
getCookie,
|
|
4
|
-
getRequestHeader,
|
|
5
|
-
setCookie
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
1
|
+
import { deleteCookie, getCookie, getRequestHeader, setCookie } from "h3";
|
|
8
2
|
import {
|
|
9
3
|
AUTH_JWT_COOKIE_MAX_AGE,
|
|
10
4
|
isHttpOnlyAuth,
|
|
@@ -12,32 +6,18 @@ import {
|
|
|
12
6
|
resolveAuthPresentCookieName,
|
|
13
7
|
resolveAuthRefreshCookieName
|
|
14
8
|
} from "../utils/authStorage.js";
|
|
9
|
+
import { readConvexConfig } from "./convexConfig.js";
|
|
15
10
|
function readAuthConfig(event) {
|
|
16
|
-
|
|
17
|
-
const config = useRuntimeConfig(event);
|
|
18
|
-
return config.public?.convex?.auth;
|
|
19
|
-
} catch {
|
|
20
|
-
return void 0;
|
|
21
|
-
}
|
|
11
|
+
return readConvexConfig(event)?.auth;
|
|
22
12
|
}
|
|
23
13
|
function isLocalHost(event) {
|
|
24
14
|
const host = getRequestHeader(event, "host") ?? "";
|
|
25
15
|
return host.startsWith("localhost") || host.startsWith("127.0.0.1") || host.startsWith("[::1]");
|
|
26
16
|
}
|
|
27
|
-
function
|
|
28
|
-
const local = isLocalHost(event);
|
|
29
|
-
return {
|
|
30
|
-
httpOnly: true,
|
|
31
|
-
secure: !local,
|
|
32
|
-
sameSite: "lax",
|
|
33
|
-
path: "/",
|
|
34
|
-
maxAge: AUTH_JWT_COOKIE_MAX_AGE
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function readableCookieOptions(event) {
|
|
17
|
+
function cookieOptions(event, httpOnly) {
|
|
38
18
|
const local = isLocalHost(event);
|
|
39
19
|
return {
|
|
40
|
-
httpOnly
|
|
20
|
+
httpOnly,
|
|
41
21
|
secure: !local,
|
|
42
22
|
sameSite: "lax",
|
|
43
23
|
path: "/",
|
|
@@ -47,15 +27,13 @@ function readableCookieOptions(event) {
|
|
|
47
27
|
export function setAuthCookies(event, tokens) {
|
|
48
28
|
const auth = readAuthConfig(event);
|
|
49
29
|
if (!isHttpOnlyAuth(auth)) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
"[convex-nuxt] setAuthCookies requires convex.auth.httpOnly: true"
|
|
52
|
-
);
|
|
30
|
+
throw new Error("[use-convex] setAuthCookies requires convex.auth.httpOnly: true");
|
|
53
31
|
}
|
|
54
32
|
const jwtName = resolveAuthCookieName(auth);
|
|
55
33
|
const refreshName = resolveAuthRefreshCookieName(auth);
|
|
56
34
|
const presentName = resolveAuthPresentCookieName(auth);
|
|
57
|
-
const httpOpts =
|
|
58
|
-
const presentOpts =
|
|
35
|
+
const httpOpts = cookieOptions(event, true);
|
|
36
|
+
const presentOpts = cookieOptions(event, false);
|
|
59
37
|
if (!tokens.token) {
|
|
60
38
|
deleteCookie(event, jwtName, httpOpts);
|
|
61
39
|
deleteCookie(event, refreshName, httpOpts);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
export interface ConvexPublicConfig {
|
|
3
|
+
url?: string;
|
|
4
|
+
auth?: {
|
|
5
|
+
provider?: string;
|
|
6
|
+
cookie?: string;
|
|
7
|
+
httpOnly?: boolean;
|
|
8
|
+
presentCookie?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Read `runtimeConfig.public.convex`, or `undefined` outside a Nitro request. */
|
|
12
|
+
export declare function readConvexConfig(event?: H3Event): ConvexPublicConfig | undefined;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
|
-
import { resolveFetchToken } from '../utils/fetchToken.js';
|
|
4
|
-
export { resolveFetchToken };
|
|
5
3
|
export interface ConvexFetchOptions {
|
|
6
4
|
/**
|
|
7
5
|
* Deployment URL. Defaults to `NUXT_PUBLIC_CONVEX_URL` / runtimeConfig.
|
|
@@ -18,9 +16,9 @@ export interface ConvexFetchOptions {
|
|
|
18
16
|
event?: H3Event;
|
|
19
17
|
skipConvexDeploymentUrlCheck?: boolean;
|
|
20
18
|
}
|
|
21
|
-
/** @internal
|
|
19
|
+
/** @internal */
|
|
22
20
|
export declare function resolveUrl(options: ConvexFetchOptions): string;
|
|
23
|
-
/** @internal
|
|
21
|
+
/** @internal */
|
|
24
22
|
export declare function resolveToken(options: ConvexFetchOptions): string | undefined;
|
|
25
23
|
/**
|
|
26
24
|
* One-shot query via ConvexHttpClient. Use in Nitro routes / server middleware.
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { getCookie } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
2
|
import { createHttpClient } from "../utils/http.js";
|
|
4
3
|
import { resolveAuthCookieName } from "../utils/authStorage.js";
|
|
5
4
|
import { resolveFetchToken } from "../utils/fetchToken.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const config = useRuntimeConfig(event);
|
|
10
|
-
return config.public?.convex;
|
|
11
|
-
} catch {
|
|
12
|
-
return void 0;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
5
|
+
import { missingConvexUrlError } from "../utils/errors.js";
|
|
6
|
+
import { readConvexConfig } from "./convexConfig.js";
|
|
15
7
|
export function resolveUrl(options) {
|
|
16
8
|
if (options.url) {
|
|
17
9
|
return options.url;
|
|
@@ -24,9 +16,7 @@ export function resolveUrl(options) {
|
|
|
24
16
|
if (fromEnv) {
|
|
25
17
|
return fromEnv;
|
|
26
18
|
}
|
|
27
|
-
throw
|
|
28
|
-
"[convex-nuxt] No Convex URL. Pass { url } or set NUXT_PUBLIC_CONVEX_URL."
|
|
29
|
-
);
|
|
19
|
+
throw missingConvexUrlError("fetchQuery/Mutation/Action");
|
|
30
20
|
}
|
|
31
21
|
export function resolveToken(options) {
|
|
32
22
|
if (options.token !== void 0) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* DevTools iframe — embeds Convex dashboard (cloud) + public config tips.
|
|
3
|
+
* Live auth/connection: `window.__CONVEX_NUXT__` (devtools client plugin).
|
|
4
|
+
*
|
|
5
|
+
* Auto-login uses CONVEX_DEPLOY_KEY via postMessage to dashboard-embedded.
|
|
6
|
+
* Without a key, the embed shows Convex's credential form; Open dashboard
|
|
7
|
+
* still deep-links to dashboard.convex.dev (existing session).
|
|
4
8
|
*/
|
|
5
9
|
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, string>;
|
|
6
10
|
export default _default;
|
|
@@ -1,32 +1,200 @@
|
|
|
1
1
|
import { defineEventHandler } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { buildDevtoolsDashboardPayload } from "../../utils/convexDashboard.js";
|
|
3
4
|
export default defineEventHandler((event) => {
|
|
4
5
|
const config = useRuntimeConfig(event);
|
|
5
6
|
const convex = config.public?.convex ?? {};
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const devtools = config.convexDevtools;
|
|
8
|
+
const url = typeof convex.url === "string" && convex.url.trim() || typeof devtools?.url === "string" && devtools.url.trim() || process.env.NUXT_PUBLIC_CONVEX_URL?.trim() || process.env.CONVEX_URL?.trim() || null;
|
|
9
|
+
const hasUrl = Boolean(url);
|
|
10
|
+
const summary = {
|
|
11
|
+
url,
|
|
12
|
+
server: convex.server ?? true,
|
|
13
|
+
auth: convex.auth ? {
|
|
14
|
+
provider: convex.auth.provider ?? null,
|
|
15
|
+
cookie: convex.auth.cookie ?? null,
|
|
16
|
+
httpOnly: convex.auth.httpOnly ?? false,
|
|
17
|
+
presentCookie: convex.auth.presentCookie ?? null
|
|
18
|
+
} : null
|
|
19
|
+
};
|
|
20
|
+
const tips = [
|
|
21
|
+
hasUrl ? "Deployment URL is set." : "No URL \u2014 set convex.url or NUXT_PUBLIC_CONVEX_URL.",
|
|
22
|
+
"Set CONVEX_DEPLOY_KEY for dashboard auto-login in this tab (dev only).",
|
|
23
|
+
"In the app console: window.__CONVEX_NUXT__.connection (WebSocket).",
|
|
24
|
+
"In the app console: window.__CONVEX_NUXT__.auth (gate flags).",
|
|
25
|
+
"Gate private live queries with { authenticated: true }.",
|
|
26
|
+
"SSR off globally: convex.server: false \u2014 or per call { server: false }."
|
|
27
|
+
];
|
|
28
|
+
const dashboard = buildDevtoolsDashboardPayload({
|
|
29
|
+
url,
|
|
30
|
+
convexDeployment: devtools?.deployment || process.env.CONVEX_DEPLOYMENT,
|
|
31
|
+
deployKey: devtools?.deployKey || process.env.CONVEX_DEPLOY_KEY
|
|
32
|
+
});
|
|
33
|
+
const configJson = JSON.stringify({ config: summary, tips }).replace(/</g, "\\u003c");
|
|
34
|
+
const dashboardJson = JSON.stringify({
|
|
35
|
+
deploymentUrl: dashboard.deploymentUrl,
|
|
36
|
+
deploymentName: dashboard.deploymentName,
|
|
37
|
+
adminKey: dashboard.adminKey,
|
|
38
|
+
embed: dashboard.embed,
|
|
39
|
+
embedSrc: dashboard.embedSrc,
|
|
40
|
+
embedOrigin: dashboard.embedOrigin
|
|
41
|
+
}).replace(/</g, "\\u003c");
|
|
42
|
+
const openHref = dashboard.openDashboardUrl ? escapeHtml(dashboard.openDashboardUrl) : null;
|
|
43
|
+
const urlLabel = dashboard.deploymentUrl ? escapeHtml(dashboard.deploymentUrl) : "No deployment URL";
|
|
44
|
+
const statusLabel = !dashboard.embed ? "Hosted embed needs a *.convex.cloud URL" : dashboard.adminKey ? "Auto-login via CONVEX_DEPLOY_KEY" : "Paste credentials in the embed, or Open dashboard";
|
|
45
|
+
const embedBlock = dashboard.embed ? `<div id="embed-wrap" class="embed${dashboard.adminKey ? " pending" : ""}">
|
|
46
|
+
<iframe
|
|
47
|
+
id="convex-dashboard"
|
|
48
|
+
title="Convex dashboard"
|
|
49
|
+
src="${escapeHtml(dashboard.embedSrc)}"
|
|
50
|
+
allow="clipboard-write"
|
|
51
|
+
></iframe>
|
|
52
|
+
</div>` : `<div class="notice">
|
|
53
|
+
<p>The hosted Convex dashboard embed only works with Convex Cloud URLs (<code>*.convex.cloud</code>).</p>
|
|
54
|
+
<p>Local / self-hosted deployments: use the CLI dashboard or Open dashboard when a name is known.</p>
|
|
55
|
+
</div>`;
|
|
16
56
|
return `<!doctype html>
|
|
17
57
|
<html lang="en">
|
|
18
58
|
<head>
|
|
19
59
|
<meta charset="utf-8" />
|
|
20
|
-
<title>
|
|
60
|
+
<title>use-convex \xB7 DevTools</title>
|
|
21
61
|
<style>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
62
|
+
* { box-sizing: border-box; }
|
|
63
|
+
html, body {
|
|
64
|
+
height: 100%;
|
|
65
|
+
margin: 0;
|
|
66
|
+
}
|
|
67
|
+
body {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
71
|
+
color: #e5e7eb;
|
|
72
|
+
background: #0b1220;
|
|
73
|
+
}
|
|
74
|
+
header {
|
|
75
|
+
flex: 0 0 auto;
|
|
76
|
+
padding: 0.65rem 0.85rem;
|
|
77
|
+
border-bottom: 1px solid #1f2937;
|
|
78
|
+
background: #0f172a;
|
|
79
|
+
}
|
|
80
|
+
.row {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
align-items: baseline;
|
|
84
|
+
gap: 0.5rem 0.85rem;
|
|
85
|
+
}
|
|
86
|
+
h1 {
|
|
87
|
+
font-size: 0.9rem;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
margin: 0;
|
|
90
|
+
color: #93c5fd;
|
|
91
|
+
}
|
|
92
|
+
.url {
|
|
93
|
+
font-size: 0.7rem;
|
|
94
|
+
color: #9ca3af;
|
|
95
|
+
word-break: break-all;
|
|
96
|
+
}
|
|
97
|
+
.status {
|
|
98
|
+
font-size: 0.7rem;
|
|
99
|
+
color: #a5b4fc;
|
|
100
|
+
}
|
|
101
|
+
a.open {
|
|
102
|
+
font-size: 0.75rem;
|
|
103
|
+
color: #67e8f9;
|
|
104
|
+
text-decoration: none;
|
|
105
|
+
}
|
|
106
|
+
a.open:hover { text-decoration: underline; }
|
|
107
|
+
details {
|
|
108
|
+
margin-top: 0.45rem;
|
|
109
|
+
}
|
|
110
|
+
summary {
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
font-size: 0.7rem;
|
|
113
|
+
color: #9ca3af;
|
|
114
|
+
user-select: none;
|
|
115
|
+
}
|
|
116
|
+
pre {
|
|
117
|
+
margin: 0.4rem 0 0;
|
|
118
|
+
white-space: pre-wrap;
|
|
119
|
+
word-break: break-word;
|
|
120
|
+
font-size: 0.72rem;
|
|
121
|
+
line-height: 1.4;
|
|
122
|
+
max-height: 12rem;
|
|
123
|
+
overflow: auto;
|
|
124
|
+
}
|
|
125
|
+
.embed {
|
|
126
|
+
flex: 1 1 auto;
|
|
127
|
+
min-height: 0;
|
|
128
|
+
position: relative;
|
|
129
|
+
}
|
|
130
|
+
.embed.pending iframe { visibility: hidden; }
|
|
131
|
+
iframe {
|
|
132
|
+
display: block;
|
|
133
|
+
width: 100%;
|
|
134
|
+
height: 100%;
|
|
135
|
+
border: 0;
|
|
136
|
+
background: #111827;
|
|
137
|
+
}
|
|
138
|
+
.notice {
|
|
139
|
+
flex: 1 1 auto;
|
|
140
|
+
padding: 1rem 0.85rem;
|
|
141
|
+
font-size: 0.8rem;
|
|
142
|
+
color: #9ca3af;
|
|
143
|
+
line-height: 1.5;
|
|
144
|
+
}
|
|
145
|
+
.notice code { color: #93c5fd; }
|
|
25
146
|
</style>
|
|
26
147
|
</head>
|
|
27
148
|
<body>
|
|
28
|
-
<
|
|
29
|
-
|
|
149
|
+
<header>
|
|
150
|
+
<div class="row">
|
|
151
|
+
<h1>use-convex</h1>
|
|
152
|
+
<span class="url">${urlLabel}</span>
|
|
153
|
+
${openHref ? `<a class="open" href="${openHref}" target="_blank" rel="noopener noreferrer">Open dashboard \u2197</a>` : ""}
|
|
154
|
+
</div>
|
|
155
|
+
<p class="status">${escapeHtml(statusLabel)}</p>
|
|
156
|
+
<details>
|
|
157
|
+
<summary>Module config & tips</summary>
|
|
158
|
+
<pre id="config-pre"></pre>
|
|
159
|
+
</details>
|
|
160
|
+
</header>
|
|
161
|
+
${embedBlock}
|
|
162
|
+
<script>
|
|
163
|
+
(function () {
|
|
164
|
+
var config = ${configJson};
|
|
165
|
+
var dash = ${dashboardJson};
|
|
166
|
+
var pre = document.getElementById('config-pre');
|
|
167
|
+
if (pre) pre.textContent = JSON.stringify(config, null, 2);
|
|
168
|
+
|
|
169
|
+
if (!dash.embed || !dash.adminKey) return;
|
|
170
|
+
|
|
171
|
+
var iframe = document.getElementById('convex-dashboard');
|
|
172
|
+
var wrap = document.getElementById('embed-wrap');
|
|
173
|
+
if (!iframe || !iframe.contentWindow) return;
|
|
174
|
+
|
|
175
|
+
function reveal() {
|
|
176
|
+
if (wrap) wrap.classList.remove('pending');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
window.addEventListener('message', function (event) {
|
|
180
|
+
if (event.origin !== dash.embedOrigin) return;
|
|
181
|
+
if (!event.data || event.data.type !== 'dashboard-credentials-request') return;
|
|
182
|
+
iframe.contentWindow.postMessage(
|
|
183
|
+
{
|
|
184
|
+
type: 'dashboard-credentials',
|
|
185
|
+
adminKey: dash.adminKey,
|
|
186
|
+
deploymentUrl: dash.deploymentUrl,
|
|
187
|
+
deploymentName: dash.deploymentName,
|
|
188
|
+
},
|
|
189
|
+
dash.embedOrigin,
|
|
190
|
+
);
|
|
191
|
+
reveal();
|
|
192
|
+
});
|
|
193
|
+
})();
|
|
194
|
+
<\/script>
|
|
30
195
|
</body>
|
|
31
196
|
</html>`;
|
|
32
197
|
});
|
|
198
|
+
function escapeHtml(value) {
|
|
199
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
200
|
+
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createError,
|
|
3
|
-
getRequestHeader,
|
|
4
|
-
getRequestProtocol
|
|
5
|
-
} from "h3";
|
|
1
|
+
import { createError, getRequestHeader, getRequestProtocol } from "h3";
|
|
6
2
|
export function assertSameOrigin(event) {
|
|
7
3
|
const secFetchSite = getRequestHeader(event, "sec-fetch-site");
|
|
8
4
|
if (secFetchSite === "same-origin" || secFetchSite === "none") {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { type ComputedRef } from 'vue';
|
|
2
|
-
import { AUTH_JWT_COOKIE_MAX_AGE } from './authStorage.js';
|
|
3
|
-
export { AUTH_JWT_COOKIE_MAX_AGE };
|
|
4
2
|
/**
|
|
5
3
|
* Shared JWT cookie for SSR snapshots. Same name + options as persistTokens
|
|
6
4
|
* so plugin reads and sign-in/out writes share one Nuxt cookie ref.
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
export function resolveConvexAuthState(flags) {
|
|
2
|
-
const {
|
|
3
|
-
authProviderLoading,
|
|
4
|
-
authProviderAuthenticated,
|
|
5
|
-
isConvexAuthenticated,
|
|
6
|
-
isRefreshing
|
|
7
|
-
} = flags;
|
|
2
|
+
const { authProviderLoading, authProviderAuthenticated, isConvexAuthenticated, isRefreshing } = flags;
|
|
8
3
|
if (authProviderLoading) {
|
|
9
4
|
return {
|
|
10
5
|
isLoading: true,
|
|
@@ -7,7 +7,7 @@ export const DEFAULT_AUTH_PRESENT_COOKIE = "convex_auth_present";
|
|
|
7
7
|
export const HTTPONLY_JWT_COOKIE = "__convexAuthJWT";
|
|
8
8
|
export const HTTPONLY_REFRESH_COOKIE = "__convexAuthRefreshToken";
|
|
9
9
|
export function storageNamespace(url) {
|
|
10
|
-
return url.replace(/[^a-
|
|
10
|
+
return url.replace(/[^a-z0-9]/gi, "");
|
|
11
11
|
}
|
|
12
12
|
export function storageKey(base, url) {
|
|
13
13
|
return `${base}_${storageNamespace(url)}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { inject, ref } from "vue";
|
|
2
2
|
import { useNuxtApp } from "nuxt/app";
|
|
3
|
+
import { missingConvexUrlError } from "./errors.js";
|
|
3
4
|
export const convexNuxtKey = Symbol("convex-nuxt");
|
|
4
5
|
export function createAuthContext() {
|
|
5
6
|
return {
|
|
@@ -33,8 +34,8 @@ export function tryUseConvexContext() {
|
|
|
33
34
|
export function useConvexContext() {
|
|
34
35
|
const ctx = tryUseConvexContext();
|
|
35
36
|
if (!ctx) {
|
|
36
|
-
throw
|
|
37
|
-
"
|
|
37
|
+
throw missingConvexUrlError(
|
|
38
|
+
"Convex plugin did not start (the module no-ops when the URL is empty)"
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
return ctx;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for linking / embedding the Convex dashboard from Nuxt DevTools.
|
|
3
|
+
* Pure — no Nitro / Nuxt imports so unit tests stay lightweight.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DASHBOARD_EMBED_ORIGIN = "https://dashboard-embedded.convex.dev";
|
|
6
|
+
export declare const DASHBOARD_EMBED_DATA_URL = "https://dashboard-embedded.convex.dev/data";
|
|
7
|
+
export declare const DASHBOARD_HOST = "https://dashboard.convex.dev";
|
|
8
|
+
/**
|
|
9
|
+
* Strip CLI prefixes like `dev:happy-animal-123` → `happy-animal-123`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function deploymentNameFromEnv(value: string | undefined | null): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Derive deployment name from a cloud URL host: `happy-animal-123.convex.cloud`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deploymentNameFromUrl(url: string | undefined | null): string | null;
|
|
16
|
+
export declare function resolveDeploymentName(options: {
|
|
17
|
+
url?: string | null;
|
|
18
|
+
convexDeployment?: string | null;
|
|
19
|
+
}): string | null;
|
|
20
|
+
/** True when the URL looks like Convex Cloud (embeddable hosted dashboard). */
|
|
21
|
+
export declare function isConvexCloudUrl(url: string | undefined | null): boolean;
|
|
22
|
+
/** True for a loopback Convex backend (`npx convex dev --local`). */
|
|
23
|
+
export declare function isLocalConvexUrl(url: string | undefined | null): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* When CONVEX_DEPLOYMENT is a cloud `dev:`/`prod:` name but the app URL is
|
|
26
|
+
* still loopback (CLI: "Can't safely modify .env.local"), return the cloud URL.
|
|
27
|
+
*/
|
|
28
|
+
export declare function suggestedCloudUrlFromMismatch(url: string | undefined | null, convexDeployment: string | undefined | null): string | null;
|
|
29
|
+
export declare function dashboardDeepLink(deploymentName: string | null): string | null;
|
|
30
|
+
export interface DevtoolsDashboardPayload {
|
|
31
|
+
deploymentUrl: string | null;
|
|
32
|
+
deploymentName: string | null;
|
|
33
|
+
/** Present only when CONVEX_DEPLOY_KEY is set (dev server only). */
|
|
34
|
+
adminKey: string | null;
|
|
35
|
+
embed: boolean;
|
|
36
|
+
openDashboardUrl: string | null;
|
|
37
|
+
embedSrc: string;
|
|
38
|
+
embedOrigin: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function buildDevtoolsDashboardPayload(options: {
|
|
41
|
+
url?: string | null;
|
|
42
|
+
convexDeployment?: string | null;
|
|
43
|
+
deployKey?: string | null;
|
|
44
|
+
}): DevtoolsDashboardPayload;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const DASHBOARD_EMBED_ORIGIN = "https://dashboard-embedded.convex.dev";
|
|
2
|
+
export const DASHBOARD_EMBED_DATA_URL = `${DASHBOARD_EMBED_ORIGIN}/data`;
|
|
3
|
+
export const DASHBOARD_HOST = "https://dashboard.convex.dev";
|
|
4
|
+
export function deploymentNameFromEnv(value) {
|
|
5
|
+
if (!value) return null;
|
|
6
|
+
const trimmed = value.split("#")[0]?.trim() ?? "";
|
|
7
|
+
if (!trimmed) return null;
|
|
8
|
+
const colon = trimmed.indexOf(":");
|
|
9
|
+
if (colon >= 0 && colon < trimmed.length - 1) {
|
|
10
|
+
return trimmed.slice(colon + 1);
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
export function deploymentNameFromUrl(url) {
|
|
15
|
+
if (!url) return null;
|
|
16
|
+
try {
|
|
17
|
+
const { hostname } = new URL(url);
|
|
18
|
+
if (hostname.endsWith(".convex.cloud")) {
|
|
19
|
+
const name = hostname.slice(0, -".convex.cloud".length);
|
|
20
|
+
return name || null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function resolveDeploymentName(options) {
|
|
28
|
+
return deploymentNameFromUrl(options.url) ?? deploymentNameFromEnv(options.convexDeployment) ?? null;
|
|
29
|
+
}
|
|
30
|
+
export function isConvexCloudUrl(url) {
|
|
31
|
+
if (!url) return false;
|
|
32
|
+
try {
|
|
33
|
+
const { hostname } = new URL(url);
|
|
34
|
+
return hostname.endsWith(".convex.cloud");
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function isLocalConvexUrl(url) {
|
|
40
|
+
if (!url) return false;
|
|
41
|
+
try {
|
|
42
|
+
const { hostname } = new URL(url);
|
|
43
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "[::1]";
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function suggestedCloudUrlFromMismatch(url, convexDeployment) {
|
|
49
|
+
if (!isLocalConvexUrl(url)) return null;
|
|
50
|
+
const trimmed = convexDeployment?.split("#")[0]?.trim() ?? "";
|
|
51
|
+
if (!/^(dev|prod):/.test(trimmed)) return null;
|
|
52
|
+
const name = deploymentNameFromEnv(trimmed);
|
|
53
|
+
if (!name) return null;
|
|
54
|
+
return `https://${name}.convex.cloud`;
|
|
55
|
+
}
|
|
56
|
+
export function dashboardDeepLink(deploymentName) {
|
|
57
|
+
if (!deploymentName) return null;
|
|
58
|
+
return `${DASHBOARD_HOST}/d/${encodeURIComponent(deploymentName)}`;
|
|
59
|
+
}
|
|
60
|
+
export function buildDevtoolsDashboardPayload(options) {
|
|
61
|
+
const deploymentUrl = options.url?.trim() || null;
|
|
62
|
+
const deploymentName = resolveDeploymentName({
|
|
63
|
+
url: deploymentUrl,
|
|
64
|
+
convexDeployment: options.convexDeployment
|
|
65
|
+
});
|
|
66
|
+
const adminKey = options.deployKey?.trim() || null;
|
|
67
|
+
const embed = isConvexCloudUrl(deploymentUrl);
|
|
68
|
+
return {
|
|
69
|
+
deploymentUrl,
|
|
70
|
+
deploymentName,
|
|
71
|
+
adminKey: embed && adminKey ? adminKey : null,
|
|
72
|
+
embed,
|
|
73
|
+
openDashboardUrl: dashboardDeepLink(deploymentName),
|
|
74
|
+
embedSrc: DASHBOARD_EMBED_DATA_URL,
|
|
75
|
+
embedOrigin: DASHBOARD_EMBED_ORIGIN
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consistent, actionable errors for consumers of `use-convex`.
|
|
3
|
+
*
|
|
4
|
+
* Branding uses `[use-convex]` (package name). Older `[use-convex]` strings
|
|
5
|
+
* are migrated here so setup failures point to the same fix.
|
|
6
|
+
*/
|
|
7
|
+
export declare const USE_CONVEX_PREFIX = "[use-convex]";
|
|
8
|
+
/** Missing deployment URL — the most common first-run failure. */
|
|
9
|
+
export declare const MISSING_URL_HINT = "Set convex.url in nuxt.config or NUXT_PUBLIC_CONVEX_URL (runtimeConfig.public.convex.url).";
|
|
10
|
+
export declare function useConvexError(message: string): Error;
|
|
11
|
+
export declare function missingConvexUrlError(context?: string): Error;
|
|
12
|
+
export declare function warnMissingConvexUrl(scope: 'client' | 'server'): void;
|
|
13
|
+
export declare function unreachableConvexUrlError(convexUrl: string): Error;
|
|
14
|
+
export declare function warnStaleLocalConvexUrl(localUrl: string, cloudUrl: string): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const USE_CONVEX_PREFIX = "[use-convex]";
|
|
2
|
+
export const MISSING_URL_HINT = "Set convex.url in nuxt.config or NUXT_PUBLIC_CONVEX_URL (runtimeConfig.public.convex.url).";
|
|
3
|
+
export function useConvexError(message) {
|
|
4
|
+
const text = message.startsWith(USE_CONVEX_PREFIX) ? message : `${USE_CONVEX_PREFIX} ${message}`;
|
|
5
|
+
return new Error(text);
|
|
6
|
+
}
|
|
7
|
+
export function missingConvexUrlError(context) {
|
|
8
|
+
const where = context ? `${context}: ` : "";
|
|
9
|
+
return useConvexError(`${where}No Convex URL. ${MISSING_URL_HINT}`);
|
|
10
|
+
}
|
|
11
|
+
export function warnMissingConvexUrl(scope) {
|
|
12
|
+
console.warn(
|
|
13
|
+
`${USE_CONVEX_PREFIX} No Convex URL on ${scope}. ${MISSING_URL_HINT} The module no-ops until a URL is set.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
export function unreachableConvexUrlError(convexUrl) {
|
|
17
|
+
return useConvexError(
|
|
18
|
+
`Could not reach Convex at ${convexUrl}. If you switched from a local to a cloud deployment, set CONVEX_URL and NUXT_PUBLIC_CONVEX_URL in .env.local to the URL printed by \`npx convex dev\` and restart Nuxt. If you use a local backend, keep \`npx convex dev\` running.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
export function warnStaleLocalConvexUrl(localUrl, cloudUrl) {
|
|
22
|
+
console.warn(
|
|
23
|
+
`${USE_CONVEX_PREFIX} Convex URL is ${localUrl} but CONVEX_DEPLOYMENT is a cloud deployment. The Convex CLI could not update .env.local \u2014 set CONVEX_URL and NUXT_PUBLIC_CONVEX_URL to ${cloudUrl} and restart Nuxt.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { useConvexError } from "./errors.js";
|
|
1
2
|
export function parseOAuthRedirect(redirect) {
|
|
2
3
|
let url;
|
|
3
4
|
try {
|
|
4
5
|
url = new URL(redirect);
|
|
5
6
|
} catch {
|
|
6
|
-
throw
|
|
7
|
+
throw useConvexError("Invalid OAuth redirect URL");
|
|
7
8
|
}
|
|
8
9
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
9
|
-
throw
|
|
10
|
+
throw useConvexError("OAuth redirect must be http(s)");
|
|
10
11
|
}
|
|
11
12
|
return url;
|
|
12
13
|
}
|