podcast manager
1import {useSignal} from '@preact/signals'
2import {nanoid} from 'nanoid'
3import {useCallback} from 'preact/hooks'
4
5import {generateSignableJwt} from '#common/crypto/jwks'
6import {jwtSchema} from '#common/crypto/jwts'
7import {RealmBrand} from '#realm/protocol/index'
8
9import {useRealmConnection} from '../context-connection'
10import {useRealmIdentity} from '../context-identity'
11
12export const RealmConnectionManager: preact.FunctionComponent = () => {
13 const {identity} = useRealmIdentity()
14 const connection = useRealmConnection()
15
16 const invitation = useSignal<string>()
17 const register = useCallback(() => {
18 console.debug('register realm')
19 connection.connectopts.value = {
20 realmid: RealmBrand.generate(),
21 register: true,
22 }
23 }, [connection.connectopts])
24
25 const exchange = useCallback(() => {
26 console.debug('exchange invitation:', invitation.value)
27 jwtSchema
28 .parseAsync(invitation.value)
29 .then((token) => {
30 connection.connectopts.value = {
31 realmid: RealmBrand.parse(token.claims.aud),
32 invitation: invitation.value,
33 }
34 })
35 .catch((exc: unknown) => {
36 console.error('couldnt exchange', exc)
37 })
38 }, [invitation, connection.connectopts])
39
40 const generate = useCallback(() => {
41 if (!connection.realm.value) return
42
43 identity
44 .sign(
45 generateSignableJwt({
46 aud: connection.realm.value.realmid,
47 iss: identity.identid,
48 sub: 'invitation',
49 jti: nanoid(),
50 exp: Date.now() + 5 * 60,
51 }),
52 )
53 .then((token) => (invitation.value = token))
54 .catch((exc: unknown) => {
55 console.error('error generating invite', exc)
56 })
57 }, [invitation, connection.realm, identity])
58
59 return connection.realm.value ? (
60 <div>
61 <pre>
62 {connection.connected.value ? ' 🟢 Connected: ' : ' 🔴 Disconnected: '}
63 {connection.realm.value.realmid} '/' {identity.identid} '/' {identity.latest}
64 </pre>
65 <textarea value={invitation.value} />
66 <br />
67 <button type="button" onClick={generate}>
68 Generate Invitation
69 </button>
70 </div>
71 ) : (
72 <div>
73 <h1>
74 Connection: <code>NONE</code>
75 </h1>
76 <p>
77 Generate a new realm:{' '}
78 <button type="button" onClick={register}>
79 Register
80 </button>
81 </p>
82 <p>
83 Exchange an invite:
84 <textarea value={invitation.value} onInput={(e) => (invitation.value = e.currentTarget.value)} />
85 <button type="button" onClick={exchange}>
86 Exchange
87 </button>
88 </p>
89 </div>
90 )
91}