botschat 0.1.4 → 0.1.7
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 +64 -24
- package/migrations/0011_e2e_encryption.sql +35 -0
- package/package.json +7 -2
- package/packages/api/package.json +2 -1
- package/packages/api/src/do/connection-do.ts +162 -42
- package/packages/api/src/index.ts +132 -13
- package/packages/api/src/routes/auth.ts +127 -30
- package/packages/api/src/routes/pairing.ts +14 -1
- package/packages/api/src/routes/setup.ts +72 -24
- package/packages/api/src/routes/upload.ts +12 -8
- package/packages/api/src/utils/auth.ts +212 -43
- package/packages/api/src/utils/id.ts +30 -14
- package/packages/api/src/utils/rate-limit.ts +73 -0
- package/packages/plugin/dist/src/accounts.d.ts.map +1 -1
- package/packages/plugin/dist/src/accounts.js +1 -0
- package/packages/plugin/dist/src/accounts.js.map +1 -1
- package/packages/plugin/dist/src/channel.d.ts +1 -0
- package/packages/plugin/dist/src/channel.d.ts.map +1 -1
- package/packages/plugin/dist/src/channel.js +151 -9
- package/packages/plugin/dist/src/channel.js.map +1 -1
- package/packages/plugin/dist/src/types.d.ts +16 -0
- package/packages/plugin/dist/src/types.d.ts.map +1 -1
- package/packages/plugin/dist/src/ws-client.d.ts +2 -0
- package/packages/plugin/dist/src/ws-client.d.ts.map +1 -1
- package/packages/plugin/dist/src/ws-client.js +14 -3
- package/packages/plugin/dist/src/ws-client.js.map +1 -1
- package/packages/plugin/package.json +4 -3
- package/packages/web/dist/architecture.png +0 -0
- package/packages/web/dist/assets/index-BoNQoJjQ.js +1497 -0
- package/packages/web/dist/assets/{index-DuGeoFJT.css → index-ewBIratI.css} +1 -1
- package/packages/web/dist/botschat-icon.svg +4 -0
- package/packages/web/dist/index.html +23 -3
- package/packages/web/dist/manifest.json +24 -0
- package/packages/web/dist/sw.js +40 -0
- package/packages/web/index.html +21 -1
- package/packages/web/package.json +1 -0
- package/packages/web/src/App.tsx +286 -103
- package/packages/web/src/analytics.ts +57 -0
- package/packages/web/src/api.ts +67 -3
- package/packages/web/src/components/ChatWindow.tsx +11 -11
- package/packages/web/src/components/ConnectionSettings.tsx +477 -0
- package/packages/web/src/components/CronDetail.tsx +475 -235
- package/packages/web/src/components/CronSidebar.tsx +1 -1
- package/packages/web/src/components/DebugLogPanel.tsx +116 -3
- package/packages/web/src/components/E2ESettings.tsx +122 -0
- package/packages/web/src/components/IconRail.tsx +56 -27
- package/packages/web/src/components/JobList.tsx +2 -6
- package/packages/web/src/components/LoginPage.tsx +143 -104
- package/packages/web/src/components/MobileLayout.tsx +480 -0
- package/packages/web/src/components/OnboardingPage.tsx +159 -21
- package/packages/web/src/components/ResizeHandle.tsx +34 -0
- package/packages/web/src/components/Sidebar.tsx +1 -1
- package/packages/web/src/components/TaskBar.tsx +2 -2
- package/packages/web/src/components/ThreadPanel.tsx +2 -5
- package/packages/web/src/e2e.ts +133 -0
- package/packages/web/src/hooks/useIsMobile.ts +27 -0
- package/packages/web/src/index.css +59 -0
- package/packages/web/src/main.tsx +12 -0
- package/packages/web/src/store.ts +16 -8
- package/packages/web/src/ws.ts +78 -4
- package/scripts/dev.sh +16 -16
- package/scripts/test-e2e-live.ts +194 -0
- package/scripts/verify-e2e-db.ts +48 -0
- package/scripts/verify-e2e.ts +56 -0
- package/wrangler.toml +3 -1
- package/packages/web/dist/assets/index-DyzTR_Y4.js +0 -847
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { authApi, setToken } from "../api";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { authApi, setToken, setRefreshToken } from "../api";
|
|
3
|
+
import type { AuthConfig } from "../api";
|
|
3
4
|
import { useAppDispatch } from "../store";
|
|
4
5
|
import { dlog } from "../debug-log";
|
|
5
6
|
import { isFirebaseConfigured, signInWithGoogle, signInWithGitHub } from "../firebase";
|
|
@@ -34,12 +35,26 @@ export function LoginPage() {
|
|
|
34
35
|
const [error, setError] = useState("");
|
|
35
36
|
const [loading, setLoading] = useState(false);
|
|
36
37
|
const [oauthLoading, setOauthLoading] = useState<"google" | "github" | null>(null);
|
|
38
|
+
const [authConfig, setAuthConfig] = useState<AuthConfig | null>(null);
|
|
37
39
|
|
|
38
40
|
const firebaseEnabled = isFirebaseConfigured();
|
|
39
41
|
const anyLoading = loading || !!oauthLoading;
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
// Fetch server-side auth config to determine which methods are available
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
authApi.config().then(setAuthConfig).catch(() => {
|
|
46
|
+
// Fallback: assume email enabled (local dev) if config endpoint fails
|
|
47
|
+
setAuthConfig({ emailEnabled: true, googleEnabled: firebaseEnabled, githubEnabled: firebaseEnabled });
|
|
48
|
+
});
|
|
49
|
+
}, [firebaseEnabled]);
|
|
50
|
+
|
|
51
|
+
const emailEnabled = authConfig?.emailEnabled ?? true;
|
|
52
|
+
const configLoaded = authConfig !== null;
|
|
53
|
+
const hasAnyLoginMethod = configLoaded && (firebaseEnabled || emailEnabled);
|
|
54
|
+
|
|
55
|
+
const handleAuthSuccess = (res: { id: string; email: string; displayName?: string; token: string; refreshToken?: string }) => {
|
|
42
56
|
setToken(res.token);
|
|
57
|
+
if (res.refreshToken) setRefreshToken(res.refreshToken);
|
|
43
58
|
dispatch({
|
|
44
59
|
type: "SET_USER",
|
|
45
60
|
user: { id: res.id, email: res.email, displayName: res.displayName },
|
|
@@ -130,11 +145,27 @@ export function LoginPage() {
|
|
|
130
145
|
}}
|
|
131
146
|
>
|
|
132
147
|
<h2 className="text-h1 mb-6" style={{ color: "var(--text-primary)" }}>
|
|
133
|
-
{
|
|
148
|
+
{emailEnabled
|
|
149
|
+
? (isRegister ? "Create account" : "Sign in")
|
|
150
|
+
: "Sign in"}
|
|
134
151
|
</h2>
|
|
135
152
|
|
|
153
|
+
{/* Loading: avoid showing empty card on first paint before config is loaded */}
|
|
154
|
+
{!configLoaded && (
|
|
155
|
+
<div className="py-8 text-center" style={{ color: "var(--text-muted)" }}>
|
|
156
|
+
<span className="text-body">Loading sign-in options…</span>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
{/* No methods available (e.g. misconfiguration) */}
|
|
161
|
+
{configLoaded && !hasAnyLoginMethod && (
|
|
162
|
+
<div className="py-4 text-caption" style={{ color: "var(--text-secondary)" }}>
|
|
163
|
+
Sign-in is not configured. Please contact support.
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
|
|
136
167
|
{/* OAuth buttons */}
|
|
137
|
-
{firebaseEnabled && (
|
|
168
|
+
{configLoaded && firebaseEnabled && (
|
|
138
169
|
<>
|
|
139
170
|
<div className="space-y-3">
|
|
140
171
|
{/* Google */}
|
|
@@ -182,113 +213,121 @@ export function LoginPage() {
|
|
|
182
213
|
</button>
|
|
183
214
|
</div>
|
|
184
215
|
|
|
185
|
-
{/* Divider */}
|
|
186
|
-
|
|
187
|
-
<div className="flex-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
{/* Divider — only show if email login is also available */}
|
|
217
|
+
{configLoaded && emailEnabled && (
|
|
218
|
+
<div className="flex items-center gap-3 my-5">
|
|
219
|
+
<div className="flex-1 h-px" style={{ background: "var(--border)" }} />
|
|
220
|
+
<span className="text-caption" style={{ color: "var(--text-muted)" }}>
|
|
221
|
+
or
|
|
222
|
+
</span>
|
|
223
|
+
<div className="flex-1 h-px" style={{ background: "var(--border)" }} />
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
193
226
|
</>
|
|
194
227
|
)}
|
|
195
228
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
type="text"
|
|
204
|
-
value={displayName}
|
|
205
|
-
onChange={(e) => setDisplayName(e.target.value)}
|
|
206
|
-
className="w-full px-3 py-2.5 text-body rounded-sm focus:outline-none placeholder:text-[--text-muted]"
|
|
207
|
-
style={{
|
|
208
|
-
background: "var(--bg-surface)",
|
|
209
|
-
color: "var(--text-primary)",
|
|
210
|
-
border: "1px solid var(--border)",
|
|
211
|
-
}}
|
|
212
|
-
placeholder="Your name"
|
|
213
|
-
/>
|
|
214
|
-
</div>
|
|
215
|
-
)}
|
|
216
|
-
|
|
217
|
-
<div>
|
|
218
|
-
<label className="block text-caption font-bold mb-1" style={{ color: "var(--text-secondary)" }}>
|
|
219
|
-
Email
|
|
220
|
-
</label>
|
|
221
|
-
<input
|
|
222
|
-
type="email"
|
|
223
|
-
value={email}
|
|
224
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
225
|
-
required
|
|
226
|
-
className="w-full px-3 py-2.5 text-body rounded-sm focus:outline-none placeholder:text-[--text-muted]"
|
|
227
|
-
style={{
|
|
228
|
-
background: "var(--bg-surface)",
|
|
229
|
-
color: "var(--text-primary)",
|
|
230
|
-
border: "1px solid var(--border)",
|
|
231
|
-
}}
|
|
232
|
-
placeholder="you@example.com"
|
|
233
|
-
/>
|
|
229
|
+
{/* Error display (always visible, e.g. OAuth errors) */}
|
|
230
|
+
{error && (
|
|
231
|
+
<div
|
|
232
|
+
className="text-caption px-3 py-2 rounded-sm mt-4"
|
|
233
|
+
style={{ background: "rgba(224,30,90,0.1)", color: "var(--accent-red)" }}
|
|
234
|
+
>
|
|
235
|
+
{error}
|
|
234
236
|
</div>
|
|
237
|
+
)}
|
|
235
238
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
239
|
+
{/* Email/password form — only in local/dev mode */}
|
|
240
|
+
{configLoaded && emailEnabled && (
|
|
241
|
+
<>
|
|
242
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
243
|
+
{isRegister && (
|
|
244
|
+
<div>
|
|
245
|
+
<label className="block text-caption font-bold mb-1" style={{ color: "var(--text-secondary)" }}>
|
|
246
|
+
Display Name
|
|
247
|
+
</label>
|
|
248
|
+
<input
|
|
249
|
+
type="text"
|
|
250
|
+
value={displayName}
|
|
251
|
+
onChange={(e) => setDisplayName(e.target.value)}
|
|
252
|
+
className="w-full px-3 py-2.5 text-body rounded-sm focus:outline-none placeholder:text-[--text-muted]"
|
|
253
|
+
style={{
|
|
254
|
+
background: "var(--bg-surface)",
|
|
255
|
+
color: "var(--text-primary)",
|
|
256
|
+
border: "1px solid var(--border)",
|
|
257
|
+
}}
|
|
258
|
+
placeholder="Your name"
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
254
262
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
<div>
|
|
264
|
+
<label className="block text-caption font-bold mb-1" style={{ color: "var(--text-secondary)" }}>
|
|
265
|
+
Email
|
|
266
|
+
</label>
|
|
267
|
+
<input
|
|
268
|
+
type="email"
|
|
269
|
+
value={email}
|
|
270
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
271
|
+
required
|
|
272
|
+
className="w-full px-3 py-2.5 text-body rounded-sm focus:outline-none placeholder:text-[--text-muted]"
|
|
273
|
+
style={{
|
|
274
|
+
background: "var(--bg-surface)",
|
|
275
|
+
color: "var(--text-primary)",
|
|
276
|
+
border: "1px solid var(--border)",
|
|
277
|
+
}}
|
|
278
|
+
placeholder="you@example.com"
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
263
281
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
282
|
+
<div>
|
|
283
|
+
<label className="block text-caption font-bold mb-1" style={{ color: "var(--text-secondary)" }}>
|
|
284
|
+
Password
|
|
285
|
+
</label>
|
|
286
|
+
<input
|
|
287
|
+
type="password"
|
|
288
|
+
value={password}
|
|
289
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
290
|
+
required
|
|
291
|
+
className="w-full px-3 py-2.5 text-body rounded-sm focus:outline-none placeholder:text-[--text-muted]"
|
|
292
|
+
style={{
|
|
293
|
+
background: "var(--bg-surface)",
|
|
294
|
+
color: "var(--text-primary)",
|
|
295
|
+
border: "1px solid var(--border)",
|
|
296
|
+
}}
|
|
297
|
+
placeholder="Enter password"
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
277
300
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
301
|
+
<button
|
|
302
|
+
type="submit"
|
|
303
|
+
disabled={anyLoading}
|
|
304
|
+
className="w-full py-2.5 font-bold text-body text-white rounded-sm disabled:opacity-50 transition-colors hover:brightness-110"
|
|
305
|
+
style={{ background: "var(--bg-active)" }}
|
|
306
|
+
>
|
|
307
|
+
{loading
|
|
308
|
+
? "..."
|
|
309
|
+
: isRegister
|
|
310
|
+
? "Create account"
|
|
311
|
+
: "Sign in with email"}
|
|
312
|
+
</button>
|
|
313
|
+
</form>
|
|
314
|
+
|
|
315
|
+
<div className="mt-6 text-center">
|
|
316
|
+
<button
|
|
317
|
+
onClick={() => {
|
|
318
|
+
setIsRegister(!isRegister);
|
|
319
|
+
setError("");
|
|
320
|
+
}}
|
|
321
|
+
className="text-caption hover:underline"
|
|
322
|
+
style={{ color: "var(--text-link)" }}
|
|
323
|
+
>
|
|
324
|
+
{isRegister
|
|
325
|
+
? "Already have an account? Sign in"
|
|
326
|
+
: "Don't have an account? Register"}
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
</>
|
|
330
|
+
)}
|
|
292
331
|
</div>
|
|
293
332
|
</div>
|
|
294
333
|
</div>
|