personal web client for Bluesky
typescript solidjs bluesky atcute

refactor: public client on dev

mary.my.id ff3b8448 f5d87790

verified
Changed files
+68 -23
src
+33 -22
src/main.tsx
··· 4 5 import { Client, ok, simpleFetchHandler } from '@atcute/client'; 6 import type { Did } from '@atcute/lexicons'; 7 - import { configureOAuth } from '@atcute/oauth-browser-client'; 8 9 import * as navigation from '~/globals/navigation'; 10 import * as preferences from '~/globals/preferences'; ··· 36 37 // Configure OAuth 38 { 39 const host = new Client({ 40 handler: simpleFetchHandler({ service: location.origin }), 41 }); 42 43 configureOAuth({ 44 metadata: { 45 - client_id: `${location.origin}/oauth-client-metadata.json`, 46 - redirect_uri: `${location.origin}/oauth/callback`, 47 }, 48 49 identityResolver: { ··· 59 return data; 60 }, 61 }, 62 - async fetchClientAssertion({ aud, jkt, createDpopProof }) { 63 - const dpop = await createDpopProof(`${location.origin}/xrpc/x.aglais.requestAssertion`); 64 65 - const data = await ok( 66 - host.post('x.aglais.requestAssertion', { 67 - input: { 68 - aud: aud, 69 - jkt: jkt, 70 - }, 71 - headers: { 72 - dpop: dpop, 73 - }, 74 - }), 75 - ); 76 - 77 - return { 78 - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 79 - client_assertion: data.assertion, 80 - }; 81 - }, 82 }); 83 } 84
··· 4 5 import { Client, ok, simpleFetchHandler } from '@atcute/client'; 6 import type { Did } from '@atcute/lexicons'; 7 + import { type ClientAssertionFetcher, configureOAuth } from '@atcute/oauth-browser-client'; 8 9 import * as navigation from '~/globals/navigation'; 10 import * as preferences from '~/globals/preferences'; ··· 36 37 // Configure OAuth 38 { 39 + // Development mode uses public client with http://localhost client ID 40 + // Production mode uses confidential client with server-side JWT assertions 41 + const isPublicClient = !!import.meta.env.VITE_OAUTH_CLIENT_ID; 42 + 43 const host = new Client({ 44 handler: simpleFetchHandler({ service: location.origin }), 45 }); 46 47 + const fetchClientAssertion: ClientAssertionFetcher = async ({ aud, jkt, createDpopProof }) => { 48 + const dpop = await createDpopProof(`${location.origin}/xrpc/x.aglais.requestAssertion`); 49 + 50 + const data = await ok( 51 + host.post('x.aglais.requestAssertion', { 52 + input: { 53 + aud: aud, 54 + jkt: jkt, 55 + }, 56 + headers: { 57 + dpop: dpop, 58 + }, 59 + }), 60 + ); 61 + 62 + return { 63 + client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 64 + client_assertion: data.assertion, 65 + }; 66 + }; 67 + 68 configureOAuth({ 69 metadata: { 70 + client_id: isPublicClient 71 + ? import.meta.env.VITE_OAUTH_CLIENT_ID 72 + : `${location.origin}/oauth-client-metadata.json`, 73 + redirect_uri: isPublicClient 74 + ? import.meta.env.VITE_OAUTH_REDIRECT_URL 75 + : `${location.origin}/oauth/callback`, 76 }, 77 78 identityResolver: { ··· 88 return data; 89 }, 90 }, 91 92 + fetchClientAssertion: isPublicClient ? undefined : fetchClientAssertion, 93 }); 94 } 95
+3
src/vite-env.d.ts
··· 6 7 interface ImportMetaEnv { 8 readonly VITE_APP_NAME: string; 9 } 10 11 interface ImportMeta {
··· 6 7 interface ImportMetaEnv { 8 readonly VITE_APP_NAME: string; 9 + readonly VITE_OAUTH_CLIENT_ID: string; 10 + readonly VITE_OAUTH_REDIRECT_URL: string; 11 + readonly VITE_OAUTH_SCOPE: string; 12 } 13 14 interface ImportMeta {
+32 -1
vite.config.ts
··· 5 import { VitePWA } from 'vite-plugin-pwa'; 6 import solid from 'vite-plugin-solid'; 7 8 export default defineConfig({ 9 build: { 10 target: 'esnext', ··· 54 }, 55 }, 56 server: { 57 - allowedHosts: ['.trycloudflare.com'], 58 }, 59 optimizeDeps: { 60 esbuildOptions: { ··· 110 ); 111 112 return { code: transformed, map: null }; 113 }, 114 }, 115 ],
··· 5 import { VitePWA } from 'vite-plugin-pwa'; 6 import solid from 'vite-plugin-solid'; 7 8 + const SERVER_HOST = '127.0.0.1'; 9 + const SERVER_PORT = 52222; 10 + 11 + const OAUTH_SCOPE = 'atproto transition:generic transition:chat.bsky'; 12 + 13 export default defineConfig({ 14 build: { 15 target: 'esnext', ··· 59 }, 60 }, 61 server: { 62 + host: SERVER_HOST, 63 + port: SERVER_PORT, 64 }, 65 optimizeDeps: { 66 esbuildOptions: { ··· 116 ); 117 118 return { code: transformed, map: null }; 119 + }, 120 + }, 121 + 122 + // Injects OAuth-related variables for development mode 123 + { 124 + name: 'aglais-oauth-inject', 125 + config(_conf, { command }) { 126 + if (command === 'build') { 127 + // Production uses confidential client 128 + process.env.VITE_OAUTH_CLIENT_ID = ''; 129 + process.env.VITE_OAUTH_REDIRECT_URL = ''; 130 + } else { 131 + // Development uses public client with http://localhost format 132 + const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}/oauth/callback`; 133 + 134 + const clientId = 135 + `http://localhost` + 136 + `?redirect_uri=${encodeURIComponent(redirectUri)}` + 137 + `&scope=${encodeURIComponent(OAUTH_SCOPE)}`; 138 + 139 + process.env.VITE_OAUTH_CLIENT_ID = clientId; 140 + process.env.VITE_OAUTH_REDIRECT_URL = redirectUri; 141 + } 142 + 143 + process.env.VITE_OAUTH_SCOPE = OAUTH_SCOPE; 144 }, 145 }, 146 ],