state.go raw
1 package main
2
3 import (
4 "common/helpers"
5 "common/jsbridge/subtle"
6 "common/jsbridge/sw"
7 )
8
9 // Shared state and utilities for the relay SW.
10
11 var (
12 cryptoCBs map[int]func(string, string)
13 nextCryptoID int
14 )
15
16 func initSharedState() {
17 cryptoCBs = make(map[int]func(string, string))
18 }
19
20 // cryptoProxy routes a crypto request through the bus to the shell SW,
21 // which proxies it to the main page (NIP-07 extension).
22 func cryptoProxy(method, peerPubkey, data string, cb func(string, string)) {
23 id := nextCryptoID
24 nextCryptoID++
25 cryptoCBs[id] = cb
26 busSend("shell", "[\"CRYPTO_REQ\","+jstr("relay")+","+helpers.Itoa(int64(id))+","+jstr(method)+","+jstr(peerPubkey)+","+jstr(data)+"]")
27 }
28
29 // Batched FWD — accumulate messages and flush every 50ms to reduce
30 // BroadcastChannel pressure. CRYPTO_RESULT and other control messages
31 // bypass the batch and send immediately via busSend.
32
33 var (
34 fwdBuf []string
35 fwdTimer sw.Timer
36 )
37
38 func fwd(clientID, msg string) {
39 fwdBuf = append(fwdBuf, "[\"FWD\","+jstr(clientID)+","+msg+"]")
40 scheduleFwdFlush()
41 }
42
43 func fwdAll(msg string) {
44 fwdBuf = append(fwdBuf, "[\"FWD_ALL\","+msg+"]")
45 scheduleFwdFlush()
46 }
47
48 func scheduleFwdFlush() {
49 if fwdTimer != 0 {
50 return
51 }
52 fwdTimer = sw.SetTimeout(50, flushFwd)
53 }
54
55 func flushFwd() {
56 fwdTimer = 0
57 if len(fwdBuf) == 0 {
58 return
59 }
60 // Build a single FWD_BATCH message: ["FWD_BATCH", [msg1, msg2, ...]]
61 out := "[\"FWD_BATCH\",["
62 for i, m := range fwdBuf {
63 if i > 0 {
64 out += ","
65 }
66 out += m
67 }
68 out += "]]"
69 fwdBuf = nil
70 busSend("shell", out)
71 }
72
73 func strsJSON(ss []string) string {
74 if len(ss) == 0 {
75 return "[]"
76 }
77 out := "["
78 for i, s := range ss {
79 if i > 0 {
80 out += ","
81 }
82 out += jstr(s)
83 }
84 return out + "]"
85 }
86
87 func jstr(s string) string { return helpers.JsonString(s) }
88
89 func boolStr(b bool) string {
90 if b {
91 return "true"
92 }
93 return "false"
94 }
95
96 func hexTo32(s string) [32]byte {
97 out, _ := helpers.HexDecode32(s)
98 return out
99 }
100
101 func random32() [32]byte {
102 var b [32]byte
103 subtle.RandomBytes(b[:])
104 return b
105 }
106
107 // jsonField extracts a string value (unquoted) for a key from a JSON object string.
108 func jsonField(json, key string) string {
109 v := jsonFieldRaw(json, key)
110 if len(v) >= 2 && v[0] == '"' && v[len(v)-1] == '"' {
111 return v[1 : len(v)-1]
112 }
113 return v
114 }
115
116 // jsonFieldRaw extracts a raw JSON value for a key from a JSON object string.
117 func jsonFieldRaw(json, key string) string {
118 needle := "\"" + key + "\":"
119 idx := -1
120 for i := 0; i <= len(json)-len(needle); i++ {
121 if json[i:i+len(needle)] == needle {
122 idx = i + len(needle)
123 break
124 }
125 }
126 if idx < 0 {
127 return ""
128 }
129 for idx < len(json) && (json[idx] == ' ' || json[idx] == '\t') {
130 idx++
131 }
132 if idx >= len(json) {
133 return ""
134 }
135 end := skipval(json, idx)
136 if end < 0 {
137 return ""
138 }
139 return json[idx:end]
140 }
141
142 // mw is a JSON array message walker for parsing bus messages.
143 type mw struct {
144 s string
145 i int
146 }
147
148 func newMW(s string) mw {
149 w := mw{s: s}
150 for w.i < len(s) && s[w.i] != '[' {
151 w.i++
152 }
153 if w.i < len(s) {
154 w.i++
155 }
156 return w
157 }
158
159 func (w *mw) sep() {
160 for w.i < len(w.s) {
161 c := w.s[w.i]
162 if c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != ',' {
163 break
164 }
165 w.i++
166 }
167 }
168
169 func (w *mw) str() string {
170 w.sep()
171 if w.i >= len(w.s) || w.s[w.i] != '"' {
172 return ""
173 }
174 w.i++
175 var buf []byte
176 hasEsc := false
177 start := w.i
178 for w.i < len(w.s) && w.s[w.i] != '"' {
179 if w.s[w.i] == '\\' && w.i+1 < len(w.s) {
180 hasEsc = true
181 buf = append(buf, w.s[start:w.i]...)
182 w.i++
183 switch w.s[w.i] {
184 case '"', '\\', '/':
185 buf = append(buf, w.s[w.i])
186 case 'n':
187 buf = append(buf, '\n')
188 case 't':
189 buf = append(buf, '\t')
190 case 'r':
191 buf = append(buf, '\r')
192 case 'u':
193 if w.i+4 < len(w.s) {
194 var cp int
195 for k := 1; k <= 4; k++ {
196 c := w.s[w.i+k]
197 cp <<= 4
198 if c >= '0' && c <= '9' {
199 cp |= int(c - '0')
200 } else if c >= 'a' && c <= 'f' {
201 cp |= int(c-'a') + 10
202 } else if c >= 'A' && c <= 'F' {
203 cp |= int(c-'A') + 10
204 }
205 }
206 if cp < 0x80 {
207 buf = append(buf, byte(cp))
208 } else if cp < 0x800 {
209 buf = append(buf, byte(0xC0|(cp>>6)), byte(0x80|(cp&0x3F)))
210 } else {
211 buf = append(buf, byte(0xE0|(cp>>12)), byte(0x80|((cp>>6)&0x3F)), byte(0x80|(cp&0x3F)))
212 }
213 w.i += 4
214 } else {
215 buf = append(buf, '\\', 'u')
216 }
217 default:
218 buf = append(buf, '\\', w.s[w.i])
219 }
220 w.i++
221 start = w.i
222 continue
223 }
224 w.i++
225 }
226 if w.i >= len(w.s) {
227 return ""
228 }
229 var result string
230 if hasEsc {
231 buf = append(buf, w.s[start:w.i]...)
232 result = string(buf)
233 } else {
234 result = w.s[start:w.i]
235 }
236 w.i++
237 return result
238 }
239
240 func (w *mw) num() int64 {
241 w.sep()
242 neg := false
243 if w.i < len(w.s) && w.s[w.i] == '-' {
244 neg = true
245 w.i++
246 }
247 var n int64
248 for w.i < len(w.s) && w.s[w.i] >= '0' && w.s[w.i] <= '9' {
249 n = n*10 + int64(w.s[w.i]-'0')
250 w.i++
251 }
252 if neg {
253 n = -n
254 }
255 return n
256 }
257
258 func (w *mw) raw() string {
259 w.sep()
260 start := w.i
261 w.i = skipval(w.s, w.i)
262 if w.i < 0 {
263 w.i = len(w.s)
264 return ""
265 }
266 return w.s[start:w.i]
267 }
268
269 func (w *mw) strs() []string {
270 w.sep()
271 if w.i >= len(w.s) || w.s[w.i] != '[' {
272 return nil
273 }
274 w.i++
275 var out []string
276 for {
277 w.sep()
278 if w.i >= len(w.s) {
279 return out
280 }
281 if w.s[w.i] == ']' {
282 w.i++
283 return out
284 }
285 if w.s[w.i] != '"' {
286 return out
287 }
288 out = append(out, w.str())
289 }
290 }
291
292 func skipval(s string, i int) int {
293 if i >= len(s) {
294 return -1
295 }
296 switch s[i] {
297 case '"':
298 i++
299 for i < len(s) {
300 if s[i] == '\\' {
301 i += 2
302 continue
303 }
304 if s[i] == '"' {
305 return i + 1
306 }
307 i++
308 }
309 return -1
310 case '{':
311 return skipBrack(s, i, '{', '}')
312 case '[':
313 return skipBrack(s, i, '[', ']')
314 case 't':
315 if i+4 <= len(s) {
316 return i + 4
317 }
318 return -1
319 case 'f':
320 if i+5 <= len(s) {
321 return i + 5
322 }
323 return -1
324 case 'n':
325 if i+4 <= len(s) {
326 return i + 4
327 }
328 return -1
329 default:
330 for i < len(s) && s[i] != ',' && s[i] != '}' && s[i] != ']' && s[i] != ' ' && s[i] != '\n' {
331 i++
332 }
333 return i
334 }
335 }
336
337 func skipBrack(s string, i int, open, close byte) int {
338 depth := 1
339 i++
340 inStr := false
341 for i < len(s) && depth > 0 {
342 if inStr {
343 if s[i] == '\\' {
344 i++
345 } else if s[i] == '"' {
346 inStr = false
347 }
348 } else {
349 switch s[i] {
350 case '"':
351 inStr = true
352 case open:
353 depth++
354 case close:
355 depth--
356 }
357 }
358 i++
359 }
360 if depth != 0 {
361 return -1
362 }
363 return i
364 }
365