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